postProcessDetections function

List<Detection> postProcessDetections({
  1. required List outputs,
  2. required int inputWidth,
  3. required int inputHeight,
  4. required double r,
  5. required int dw,
  6. required int dh,
  7. required int imageWidth,
  8. required int imageHeight,
  9. required double confThres,
  10. required double iouThres,
  11. required int topkPreNms,
  12. required int maxDet,
  13. int? filterClassId,
  14. bool scoresAreProbabilities = false,
})

Post-processes detection model outputs into Detection results.

Decodes model outputs, applies confidence filtering, optional class filtering, top-k pre-NMS selection, NMS, and coordinate transformation from letterbox space to original image coordinates.

Parameters:

  • outputs: Raw model output tensors.
  • inputWidth: Width of the model input tensor (used for coordinate de-normalization).
  • inputHeight: Height of the model input tensor (used for coordinate de-normalization).
  • r: Letterbox scale ratio.
  • dw: Horizontal letterbox padding in pixels.
  • dh: Vertical letterbox padding in pixels.
  • imageWidth: Original image width for coordinate clamping.
  • imageHeight: Original image height for coordinate clamping.
  • confThres: Minimum confidence score to keep a detection.
  • iouThres: IoU threshold for NMS.
  • topkPreNms: Number of top candidates to keep before NMS (0 = auto-scale).
  • maxDet: Maximum number of detections to return after NMS.
  • filterClassId: If non-null, only detections with this class ID are kept.
  • scoresAreProbabilities: Whether class/objectness values are already probabilities. Defaults to false, preserving the logits + sigmoid contract used by existing callers.

Implementation

List<Detection> postProcessDetections({
  required List<dynamic> outputs,
  required int inputWidth,
  required int inputHeight,
  required double r,
  required int dw,
  required int dh,
  required int imageWidth,
  required int imageHeight,
  required double confThres,
  required double iouThres,
  required int topkPreNms,
  required int maxDet,
  int? filterClassId,
  bool scoresAreProbabilities = false,
}) {
  final List<Map<String, dynamic>> decoded = decodeAndSplitOutputs(outputs);
  final List<int> clsIds = <int>[];
  final List<double> scores = <double>[];
  final List<List<double>> xywhs = <List<double>>[];

  for (final Map<String, dynamic> row in decoded) {
    final int C = row['C'] as int;
    final List<double> xywh = (row['xywh'] as List)
        .map((v) => (v as num).toDouble())
        .toList();
    final List<double> rest = (row['rest'] as List)
        .map((v) => (v as num).toDouble())
        .toList();

    if (C == 84) {
      int argMax = 0;
      double best = -1e9;
      for (int i = 0; i < rest.length; i++) {
        final double s = scoresAreProbabilities ? rest[i] : sigmoid(rest[i]);
        if (s > best) {
          best = s;
          argMax = i;
        }
      }
      scores.add(best);
      clsIds.add(argMax);
      xywhs.add(xywh);
    } else {
      final double obj = scoresAreProbabilities ? rest[0] : sigmoid(rest[0]);
      final List<double> clsLogits = rest.sublist(1, 81);
      int argMax = 0;
      double best = -1e9;
      for (int i = 0; i < clsLogits.length; i++) {
        final double s = scoresAreProbabilities
            ? clsLogits[i]
            : sigmoid(clsLogits[i]);
        if (s > best) {
          best = s;
          argMax = i;
        }
      }
      scores.add(obj * best);
      clsIds.add(argMax);
      xywhs.add(xywh);
    }
  }

  final List<int> keep0 = <int>[];
  for (int i = 0; i < scores.length; i++) {
    if (scores[i] >= confThres) keep0.add(i);
  }
  if (keep0.isEmpty) return <Detection>[];

  final List<List<double>> keptXywh = [for (final int i in keep0) xywhs[i]];
  final List<int> keptCls = [for (final int i in keep0) clsIds[i]];
  final List<double> keptScore = [for (final int i in keep0) scores[i]];

  if (keptXywh.isNotEmpty && median([for (final v in keptXywh) v[2]]) <= 2.0) {
    for (final List<double> v in keptXywh) {
      v[0] *= inputWidth.toDouble();
      v[1] *= inputHeight.toDouble();
      v[2] *= inputWidth.toDouble();
      v[3] *= inputHeight.toDouble();
    }
  }

  final List<List<double>> boxesLtr = [
    for (final List<double> v in keptXywh) xywhToXyxy(v),
  ];
  final List<List<double>> boxes = <List<double>>[];
  for (final List<double> b in boxesLtr) {
    boxes.add(scaleFromLetterbox(b, r, dw, dh));
  }
  final double iw = imageWidth.toDouble();
  final double ih = imageHeight.toDouble();
  for (final List<double> b in boxes) {
    b[0] = b[0].clamp(0.0, iw);
    b[2] = b[2].clamp(0.0, iw);
    b[1] = b[1].clamp(0.0, ih);
    b[3] = b[3].clamp(0.0, ih);
  }

  final int effectiveTopk;
  if (topkPreNms > 0) {
    effectiveTopk = topkPreNms;
  } else {
    const int basePixels = 640 * 640;
    const int baseCandidates = 100;
    final int imagePixels = imageWidth * imageHeight;
    final double scale = imagePixels / basePixels;
    effectiveTopk = (baseCandidates * scale).round().clamp(20, 200);
  }

  if (effectiveTopk > 0 && keptScore.length > effectiveTopk) {
    final List<int> ord = argSortDesc(keptScore).take(effectiveTopk).toList();
    final List<List<double>> sortedBoxes = <List<double>>[];
    final List<double> sortedScores = <double>[];
    final List<int> sortedCls = <int>[];
    for (final int i in ord) {
      sortedBoxes.add(boxes[i]);
      sortedScores.add(keptScore[i]);
      sortedCls.add(keptCls[i]);
    }
    boxes
      ..clear()
      ..addAll(sortedBoxes);
    keptScore
      ..clear()
      ..addAll(sortedScores);
    keptCls
      ..clear()
      ..addAll(sortedCls);
  }

  if (filterClassId != null) {
    final List<List<double>> fBoxes = <List<double>>[];
    final List<double> fScores = <double>[];
    final List<int> fCls = <int>[];
    for (int i = 0; i < keptCls.length; i++) {
      if (keptCls[i] == filterClassId) {
        fBoxes.add(boxes[i]);
        fScores.add(keptScore[i]);
        fCls.add(keptCls[i]);
      }
    }
    boxes
      ..clear()
      ..addAll(fBoxes);
    keptScore
      ..clear()
      ..addAll(fScores);
    keptCls
      ..clear()
      ..addAll(fCls);
  }

  final List<int> keep = nms(
    boxes,
    keptScore,
    iouThres: iouThres,
    maxDet: maxDet,
  );
  final List<Detection> out = <Detection>[];
  for (final int i in keep) {
    out.add(
      Detection(cls: keptCls[i], score: keptScore[i], bboxXYXY: boxes[i]),
    );
  }
  return out;
}