Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions android/src/main/kotlin/com/ultralytics/yolo/YOLOPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -297,14 +297,18 @@ class YOLOPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCallHandler
}
YOLOTask.CLASSIFY -> {
yoloResult.probs?.let { probs ->
// Build top5 list safely using zip to handle mismatched list lengths
// Convert top5Confs to List to ensure Iterable compatibility (may be FloatArray)
val top5List = probs.top5Labels.zip(probs.top5Confs.toList()).map { (name, conf) ->
mapOf(
"name" to name,
"confidence" to conf.toDouble()
)
}
// Build top5 list with labels and confidence only
// Omit class field to maintain consistency across platforms
// (iOS native API doesn't provide top5 indices)
val top5List = probs.top5Labels
.zip(probs.top5Confs.toList())
.take(5)
.map { (name, conf) ->
mapOf(
"name" to name,
"confidence" to conf.toDouble()
)
}

// Classification response following Results.summary() format
// Reference: https://docs.ultralytics.com/reference/engine/results/
Expand All @@ -319,8 +323,9 @@ class YOLOPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCallHandler
response["boxes"] = listOf(
mapOf(
"class" to probs.top1Index,
"className" to probs.top1Label,
"name" to probs.top1Label,
"classIndex" to probs.top1Index,
"className" to probs.top1Label,
"confidence" to probs.top1Conf.toDouble(),
"x1" to 0.0,
"y1" to 0.0,
Expand Down
44 changes: 44 additions & 0 deletions android/src/main/kotlin/com/ultralytics/yolo/YOLOView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1712,6 +1712,50 @@ class YOLOView @JvmOverloads constructor(
map["detections"] = detections
Log.d(TAG, "✅ Total detections in stream: ${detections.size} (boxes: ${result.boxes.size}, obb: ${result.obb.size})")
}

// Add classification results (if available and enabled for CLASSIFY task)
if (config.includeClassifications && result.probs != null) {
val probs = result.probs!!

val top5List = probs.top5Labels
.zip(probs.top5Confs.toList())
.take(5)
.map { (name, conf) ->
mapOf(
"name" to name,
"confidence" to conf.toDouble()
)
}

// Add classification result to detections array (for compatibility with YOLOResult.fromMap)
val detections = (map["detections"] as? List<Map<String, Any>>)?.toMutableList() ?: ArrayList()

val classificationDetection = HashMap<String, Any>()
// Omit class field from streaming to maintain consistency with iOS
// (iOS native API doesn't provide class index)
classificationDetection["name"] = probs.top1Label
classificationDetection["confidence"] = probs.top1Conf.toDouble()
classificationDetection["top5"] = top5List

// Full image bounding box for classification
val boundingBox = HashMap<String, Any>()
boundingBox["left"] = 0.0
boundingBox["top"] = 0.0
boundingBox["right"] = result.origShape.width.toDouble()
boundingBox["bottom"] = result.origShape.height.toDouble()
classificationDetection["boundingBox"] = boundingBox

// Normalized bounding box (full image)
val normalizedBox = HashMap<String, Any>()
normalizedBox["left"] = 0.0
normalizedBox["top"] = 0.0
normalizedBox["right"] = 1.0
normalizedBox["bottom"] = 1.0
classificationDetection["normalizedBox"] = normalizedBox

detections.add(classificationDetection)
map["detections"] = detections
}

// Add performance metrics (if enabled)
if (config.includeProcessingTimeMs) {
Expand Down
50 changes: 50 additions & 0 deletions ios/Classes/YOLOView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1768,6 +1768,56 @@ extension YOLOView: AVCapturePhotoCaptureDelegate {
map["detections"] = detections
}

// Add classification results (if available and enabled for CLASSIFY task)
if config.includeClassifications, let probs = result.probs, result.boxes.isEmpty {
// Get or create detections array (for compatibility with YOLOResult deserialization)
var detections = map["detections"] as? [[String: Any]] ?? []

// Build top5 list with labels and confidence
// Note: iOS native API doesn't provide top5 indices, so we omit class field
// to maintain consistency with Android when indices are unavailable
var top5List: [[String: Any]] = []
let top5Labels = probs.top5Labels
let top5Confs = probs.top5Confs

for i in 0..<min(top5Labels.count, top5Confs.count) {
top5List.append([
"name": top5Labels[i],
"confidence": Double(top5Confs[i])
])
}

// Create single detection object with top1 and top5 info
var detection: [String: Any] = [:]

// Note: iOS native API doesn't provide real class index, so we omit it
// to avoid sending potentially incorrect values (e.g., -1 or wrong index)
detection["name"] = probs.top1Label
detection["confidence"] = Double(probs.top1Conf)
detection["top5"] = top5List

// Classification doesn't have bounding boxes, use full image bounds
let boundingBox: [String: Any] = [
"left": 0.0,
"top": 0.0,
"right": Double(result.orig_shape.width),
"bottom": Double(result.orig_shape.height)
]
detection["boundingBox"] = boundingBox

// Normalized bounding box (full image)
let normalizedBox: [String: Any] = [
"left": 0.0,
"top": 0.0,
"right": 1.0,
"bottom": 1.0
]
detection["normalizedBox"] = normalizedBox

detections.append(detection)
map["detections"] = detections
}

// Add performance metrics (if enabled)
if config.includeProcessingTimeMs {
map["processingTimeMs"] = result.speed * 1000
Expand Down
Loading