predict method
Runs inference on a single image.
Takes raw image bytes as input and returns a map containing the inference results. The returned map contains:
- 'boxes': List of detected objects with bounding box coordinates
- 'detections': List of detections in YOLOResult-compatible format
- Task-specific data (keypoints for pose, mask for segmentation, etc.)
The model must be loaded with loadModel before calling this method.
Example:
// Basic detection usage
final results = await yolo.predict(imageBytes);
final boxes = results['boxes'] as List<dynamic>;
for (var box in boxes) {
print('Class: ${box['class']}, Confidence: ${box['confidence']}');
}
// Pose estimation with YOLOResult
final results = await yolo.predict(imageBytes);
final detections = results['detections'] as List<dynamic>;
for (var detection in detections) {
final result = YOLOResult.fromMap(detection);
if (result.keypoints != null) {
print('Found ${result.keypoints!.length} keypoints');
for (int i = 0; i < result.keypoints!.length; i++) {
final kp = result.keypoints![i];
final conf = result.keypointConfidences![i];
print('Keypoint $i: (${kp.x}, ${kp.y}) confidence: $conf');
}
}
}
@param imageBytes The raw image data as a Uint8List @param confidenceThreshold Optional confidence threshold (0.0-1.0). Defaults to 0.25 if not specified. @param iouThreshold Optional IoU threshold for NMS (0.0-1.0). Defaults to 0.4 if not specified. @return A map containing:
- 'boxes': List of bounding boxes
- 'detections': List of YOLOResult-compatible detection maps
- 'keypoints': (pose only) Raw keypoints data from platform @throws ModelNotLoadedException if the model has not been loaded @throws InferenceException if there's an error during inference @throws PlatformException if there's an issue with the platform-specific code
Implementation
Future<Map<String, dynamic>> predict(
Uint8List imageBytes, {
double? confidenceThreshold,
double? iouThreshold,
}) async {
if (imageBytes.isEmpty) {
throw InvalidInputException('Image data is empty');
}
// Validate threshold values if provided
if (confidenceThreshold != null &&
(confidenceThreshold < 0.0 || confidenceThreshold > 1.0)) {
throw InvalidInputException(
'Confidence threshold must be between 0.0 and 1.0',
);
}
if (iouThreshold != null && (iouThreshold < 0.0 || iouThreshold > 1.0)) {
throw InvalidInputException('IoU threshold must be between 0.0 and 1.0');
}
try {
final Map<String, dynamic> arguments = {'image': imageBytes};
// Add optional thresholds if provided
if (confidenceThreshold != null) {
arguments['confidenceThreshold'] = confidenceThreshold;
}
if (iouThreshold != null) {
arguments['iouThreshold'] = iouThreshold;
}
// Only include instanceId for multi-instance mode
if (_instanceId != 'default') {
arguments['instanceId'] = _instanceId;
}
final result = await _channel.invokeMethod(
'predictSingleImage',
arguments,
);
if (result is Map) {
// Convert Map<Object?, Object?> to Map<String, dynamic>
final Map<String, dynamic> resultMap = Map<String, dynamic>.fromEntries(
result.entries.map((e) => MapEntry(e.key.toString(), e.value)),
);
// Convert boxes list if it exists
final List<Map<String, dynamic>> boxes = [];
if (resultMap.containsKey('boxes') && resultMap['boxes'] is List) {
boxes.addAll(
(resultMap['boxes'] as List).map((item) {
if (item is Map) {
return Map<String, dynamic>.fromEntries(
item.entries.map((e) => MapEntry(e.key.toString(), e.value)),
);
}
return <String, dynamic>{};
}),
);
resultMap['boxes'] = boxes;
}
// Create detections array with YOLOResult-compatible structure
final List<Map<String, dynamic>> detections = [];
// Handle different task types
switch (task) {
case YOLOTask.pose:
// For pose estimation, merge boxes with keypoints
if (resultMap.containsKey('keypoints')) {
final keypointsList =
resultMap['keypoints'] as List<dynamic>? ?? [];
for (
int i = 0;
i < boxes.length && i < keypointsList.length;
i++
) {
final box = boxes[i];
final detection = _createDetectionMap(box);
// Convert keypoints to flat array format expected by YOLOResult
if (keypointsList[i] is Map) {
final personKeypoints =
keypointsList[i] as Map<dynamic, dynamic>;
final coordinates =
personKeypoints['coordinates'] as List<dynamic>? ?? [];
final flatKeypoints = <double>[];
for (final coord in coordinates) {
if (coord is Map) {
flatKeypoints.add(
(coord['x'] as num?)?.toDouble() ?? 0.0,
);
flatKeypoints.add(
(coord['y'] as num?)?.toDouble() ?? 0.0,
);
flatKeypoints.add(
(coord['confidence'] as num?)?.toDouble() ?? 0.0,
);
}
}
if (flatKeypoints.isNotEmpty) {
detection['keypoints'] = flatKeypoints;
}
}
detections.add(detection);
}
}
break;
case YOLOTask.segment:
// For segmentation, include mask data with boxes
final masks = resultMap['masks'] as List<dynamic>? ?? [];
for (int i = 0; i < boxes.length; i++) {
final box = boxes[i];
final detection = _createDetectionMap(box);
// Add mask data if available for this detection
if (i < masks.length && masks[i] != null) {
// masks[i] is already in the correct List<List<double>> format
final maskData = masks[i] as List<dynamic>;
final mask = maskData.map((row) {
return (row as List<dynamic>).map((val) {
return (val as num).toDouble();
}).toList();
}).toList();
detection['mask'] = mask;
}
detections.add(detection);
}
break;
case YOLOTask.classify:
// For classification, create a single detection with classification data
if (resultMap.containsKey('classification')) {
final classification =
resultMap['classification'] as Map<dynamic, dynamic>;
// Classification doesn't have boxes, create a full-image detection
final detection = <String, dynamic>{
'classIndex': 0, // Would need class mapping
'className': classification['topClass'] ?? '',
'confidence':
(classification['topConfidence'] as num?)?.toDouble() ??
0.0,
'boundingBox': {
'left': 0.0,
'top': 0.0,
'right': 1.0, // Full image
'bottom': 1.0,
},
'normalizedBox': {
'left': 0.0,
'top': 0.0,
'right': 1.0,
'bottom': 1.0,
},
};
detections.add(detection);
}
break;
case YOLOTask.obb:
// For OBB, convert oriented bounding boxes
if (resultMap.containsKey('obb')) {
final obbList = resultMap['obb'] as List<dynamic>? ?? [];
for (final obb in obbList) {
if (obb is Map) {
final points = obb['points'] as List<dynamic>? ?? [];
// Calculate bounding box from OBB points
double minX = double.infinity, minY = double.infinity;
double maxX = double.negativeInfinity,
maxY = double.negativeInfinity;
for (final point in points) {
if (point is Map) {
final x = (point['x'] as num?)?.toDouble() ?? 0.0;
final y = (point['y'] as num?)?.toDouble() ?? 0.0;
minX = minX > x ? x : minX;
minY = minY > y ? y : minY;
maxX = maxX < x ? x : maxX;
maxY = maxY < y ? y : maxY;
}
}
final detection = <String, dynamic>{
'classIndex': 0, // Would need class mapping
'className': obb['class'] ?? '',
'confidence':
(obb['confidence'] as num?)?.toDouble() ?? 0.0,
'boundingBox': {
'left': minX,
'top': minY,
'right': maxX,
'bottom': maxY,
},
'normalizedBox': {
'left': minX,
'top': minY,
'right': maxX,
'bottom': maxY,
},
};
detections.add(detection);
}
}
}
break;
case YOLOTask.detect:
// For detection, just convert boxes
for (final box in boxes) {
detections.add(_createDetectionMap(box));
}
break;
}
// Add detections to result map
resultMap['detections'] = detections;
return resultMap;
}
throw InferenceException('Invalid result format returned from inference');
} on PlatformException catch (e) {
if (e.code == 'MODEL_NOT_LOADED') {
throw ModelNotLoadedException(
'Model has not been loaded. Call loadModel() first.',
);
} else if (e.code == 'INVALID_IMAGE') {
throw InvalidInputException(
'Invalid image format or corrupted image data',
);
} else if (e.code == 'INFERENCE_ERROR') {
throw InferenceException('Error during inference: ${e.message}');
} else {
throw InferenceException(
'Platform error during inference: ${e.message}',
);
}
} catch (e) {
throw InferenceException('Unknown error during inference: $e');
}
}