diff --git a/android/src/main/kotlin/com/ultralytics/yolo/YOLOPlugin.kt b/android/src/main/kotlin/com/ultralytics/yolo/YOLOPlugin.kt index 97af753b..7344a951 100644 --- a/android/src/main/kotlin/com/ultralytics/yolo/YOLOPlugin.kt +++ b/android/src/main/kotlin/com/ultralytics/yolo/YOLOPlugin.kt @@ -297,13 +297,30 @@ 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 class indices (available from native API) + val top5List = if (probs.top5Indices != null) { + probs.top5Indices!! + .zip(probs.top5Labels) + .zip(probs.top5Confs.toList()) + .take(5) + .map { ((classIdx, name), conf) -> + mapOf( + "class" to classIdx, + "name" to name, + "confidence" to conf.toDouble() + ) + } + } else { + // Fallback: omit class when indices unavailable + 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 @@ -319,8 +336,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, diff --git a/android/src/main/kotlin/com/ultralytics/yolo/YOLOView.kt b/android/src/main/kotlin/com/ultralytics/yolo/YOLOView.kt index 447aa386..4042a849 100644 --- a/android/src/main/kotlin/com/ultralytics/yolo/YOLOView.kt +++ b/android/src/main/kotlin/com/ultralytics/yolo/YOLOView.kt @@ -1712,6 +1712,64 @@ 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 && result.boxes.isEmpty()) { + val probs = result.probs!! + + val top5List = if (probs.top5Indices != null) { + probs.top5Indices!! + .zip(probs.top5Labels) + .zip(probs.top5Confs.toList()) + .take(5) + .map { ((classIdx, name), conf) -> + mapOf( + "class" to classIdx, + "name" to name, + "confidence" to conf.toDouble() + ) + } + } else { + // Fallback: omit class when indices unavailable + 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>)?.toMutableList() ?: ArrayList() + + val classificationDetection = HashMap() + classificationDetection["class"] = probs.top1Index + classificationDetection["name"] = probs.top1Label + classificationDetection["confidence"] = probs.top1Conf.toDouble() + classificationDetection["top5"] = top5List + + // Full image bounding box for classification + val boundingBox = HashMap() + 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() + 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) { diff --git a/ios/Classes/YOLOView.swift b/ios/Classes/YOLOView.swift index 4e89a85d..8c509c1e 100644 --- a/ios/Classes/YOLOView.swift +++ b/ios/Classes/YOLOView.swift @@ -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..