Sign in

    moondeteval

    Detector-independent bounding-box and instance-mask evaluation for MoonBit

    computer-vision
    object-detection
    evaluation
    metrics
    Download zip
    Version
    0.2.0
    License
    Apache-2.0
    Last updated
    6 days ago
    Downloads
    49

    #MoonDetEval

    MoonDetEval is a MoonBit library and native CLI for detector-independent bounding-box and instance-mask evaluation. It complements MBMOT, which measures multi-object tracking.

    Explore the bilingual product site and live reports. The site uses synthetic reports generated by the MoonBit CLI; the report viewer processes local files in your browser.

    Version 0.2.0 includes bbox, instance-mask, polygon and batch segmentation evaluation, metric gates and bounded mask overlap caching. Reports use evaluator_version="0.2.0" with the unchanged schema_version="0.1.0". Ground-truth annotation IDs are Int64; direct callers of GroundTruth::new pass an Int64 ID. The previous 0.1.0 release and tag remain unchanged. Check the versioned package page for registry availability.

    Choose an entry point:

    NeedStart here
    Evaluate in MoonBit codeInstall and call the library (moon add python123-ops/moondeteval@0.2.0)
    Evaluate one or two prediction sets from filesRun the source examples (moon run cli --target native -- --help)
    Inspect an existing JSON reportRead-only browser viewer
    Compare more than two prediction setsBatch evaluator

    The source repository includes examples, the batch helper and browser assets, distributed separately from the Mooncakes library and CLI archive. For a reproducible problem, open a GitHub issue with the version, target, minimal non-sensitive input, expected behavior, and actual output.

    #Current scope

    The library accepts validated in-memory images, categories, ground truths, and detections, or COCO-style JSON files. It computes the 12 standard COCO bbox summary metrics and per-category values. Scores are ranked in descending order; equal scores preserve input order within an image/category, while cross-image accumulation follows sorted image IDs, matching the COCO reference. Crowd boxes use intersection over detection area and do not count as positive ground truths. Values of -1 mean a metric has no eligible ground truths. Boxes use continuous xywh coordinates, without pixel-endpoint +1 or automatic clipping.

    The source checkout also provides CocoRle for mask evaluation. CocoRle::new(height, width, counts) accepts uncompressed COCO RLE counts: column-major pixels, alternating background/foreground runs, beginning with background (an initial zero run means the first pixel is foreground). It validates that the runs exactly cover one positive-size image of at most 2,147,483,647 pixels. area() returns foreground pixels; iou(other) returns ordinary mask IoU; crowd_iou(other) treats self as the detection and other as crowd ground truth, dividing intersection by detection area. Both IoU methods reject mismatched image dimensions and return zero for an empty denominator. The implementation processes runs directly and copies constructor input.

    CocoRle::from_compressed(height, width, counts) reads COCO ASCII compressed counts, including signed run differences; to_compressed() exports the stored run sequence in the same format. Reading rejects invalid characters, truncated groups, runs needing more than seven characters, negative reconstructed runs, and lengths exceeding the image. Character positions in errors are zero-based. parse_coco_rle(source) accepts one segmentation RLE JSON object with size: [height, width] and counts as either a run array or a compressed string. Sizes and array entries must decode to nonnegative integer-valued numbers within the Int range. It uses the core JSON parser's numeric and duplicate-key behavior; producers should emit integer tokens and unique keys. The parser handles JSON escaping, including backslashes within counts.

    These APIs require 0.2.0 and are absent from 0.1.0. The library and native CLI evaluate RLE mask AP/AR. COCO polygon rings can also be rasterized into RLE using declared image dimensions. The differential script checks 35 fixed/seeded mask pairs in three input forms against pycocotools==2.0.7, including byte-for-byte compressed exports. Int-limit codec cases use explicit MoonBit fixtures: the Windows reference C decoder mishandles some large signed differences, so they are outside this script's reference geometry claim.

    ///|
    let detection = @moondeteval.CocoRle::new(3, 4, [1, 2, 2, 3, 4])

    ///|
    let truth = @moondeteval.CocoRle::new(3, 4, [0, 3, 2, 1, 6])
    // detection.area() == 5; detection.iou(truth) == 0.5
    // detection.crowd_iou(truth) == 0.6

    ///|
    let compressed = @moondeteval.parse_coco_rle(
    "{\"size\":[3,4],\"counts\":\"032N4\"}",
    )
    // compressed.to_compressed() == "032N4"; detection.iou(compressed) == 0.5

    COCO annotation IDs use signed 64-bit integers because real validation annotations can exceed the 32-bit range. The parser accepts integer JSON numbers within that range, rejects fractional or out-of-range IDs, and preserves the exact ID in diagnostics. Report IDs remain JSON numbers. Library callers constructing GroundTruth directly now pass an Int64 ID.

    All 12 summary fields are unitless fractions in [0,1] when defined, in this order: ap, ap50, ap75, ap_small, ap_medium, ap_large, ar1, ar10, ar100, ar_small, ar_medium, ar_large. ap and ar* average over the configured IoU thresholds; ap50 and ap75 are fixed probes. Each field is -1 when its category/area has no eligible ground truth, including a dataset with no categories or only crowd truths. In comparison metrics arrays use this same order; an undefined side and its delta are JSON null, while the nested single-model reports retain -1. Categories and per-class comparisons follow dataset category order, image summaries follow ascending image ID, and PR curves follow category order then configured ascending IoU threshold. Optional arrays are present but empty when disabled; enabling them does not change summary metrics. JSON object key order is not an API guarantee.

    COCO JSON errors identify the source file (in CLI messages), record index, and decoded field path where available. Duplicate image/category/annotation IDs and unknown annotation references report their record and field. The underlying JSON parser resolves repeated keys within one object to the last value before model validation; duplicate object keys are not rejected. Producers should emit unique keys. A JSON number larger than JavaScript's exact integer range remains exact in MoonDetEval's annotation ID output, but a JavaScript consumer must use a lossless JSON parser to preserve it.

    The implementation is under compatibility validation. The differential script checks all 12 summary metrics, per-image matches, and exported 101-point precision/recall at all 10 default IoU thresholds against pycocotools==2.0.7 on nine synthetic scenarios. These include explicit ignore, crowd, empty detections, maxDets=100, area and IoU boundaries, score ties within and across images, out-of-image boxes, and fixed-seed mixed images. This is stronger evidence for the covered bbox cases, but does not establish full COCO equivalence.

    In the default coco_bbox mode, COCO bbox evaluation uses iscrowd to determine whether a ground truth is ignored, even when the source annotation contains ignore=1. The input flag remains available in the model. Callers who need that flag to affect matching can construct EvalConfig::new(..., ignore_policy="respect_explicit"); the chosen policy is recorded in the report. The latter is a MoonDetEval extension, not a COCO compatibility claim.

    #Instance-mask evaluation (source checkout)

    Use parse_coco_mask_dataset, parse_coco_mask_detections_with_dataset, and evaluate_masks for segmentation-only COCO JSON, or construct MaskGroundTruth, MaskDetection, and MaskDataset from validated CocoRle values. Compressed strings, uncompressed run arrays and COCO polygon rings are supported. Polygon predictions use parse_coco_mask_detections_with_dataset(source, dataset) so canvas size comes from the declared image. RLE objects must have size=[image.height,image.width]; polygons use the declared image canvas. malformed RLE, duplicate IDs, unknown references and missing segmentation are rejected. Scores must be finite. Empty masks are valid; their overlap is zero, so an empty regular truth remains eligible and cannot match.

    Mask matching uses foreground intersection/union, or intersection/detection foreground area for crowd. It uses the same stable ranking, ignored-truth priority, maxDets 1/10/100, area intervals, ten default IoUs and 101-point interpolation as bbox evaluation. GT area is required and retained from the annotation, even when it differs from the RLE pixel count. Prediction area always comes from the RLE; supplied bbox and area fields are ignored. For comparable pycocotools reference inputs, remove prediction bbox: COCO.loadRes prioritizes its bbox branch and otherwise overwrites detection area with box area.

    evaluate_masks_with_config and compare_mask_predictions_with_config support custom IoU thresholds, diagnostics, image summaries and PR curves. The default EvalConfig::coco() is normalized to ignore_policy="coco_segm" in a mask report; both COCO policies use iscrowd to ignore GT. You may explicitly construct EvalConfig::new(..., ignore_policy="coco_segm"), or opt into respect_explicit. Bbox entry points reject the segm policy. Reports reuse schema 0.1.0 and add iou_type="segm"; bbox JSON retains its original fields, and report.iou_type() returns bbox or segm. Comparisons contain this marker in both nested reports. The viewer labels mask AP and rejects mixed bbox/segm comparisons. Python batch evaluation supports both bbox and segm, with one task per batch.

    ///|
    let masks = @moondeteval.parse_coco_mask_dataset(ground_truth_json)

    ///|
    let predictions = @moondeteval.parse_coco_mask_detections(detections_json)

    ///|
    let report = @moondeteval.evaluate_masks(masks, predictions)

    ///|
    let comparison = @moondeteval.compare_mask_predictions(
    masks, predictions, other_predictions,
    )

    moon run cli --target native -- examples/mask-ground-truth.json examples/mask-baseline.json --iou-type segm --compare examples/mask-candidate.json --diagnostics --image-ranking --pr-curves --min-ap50 1

    The checked-in 2×2 example uses masks with identical enclosing boxes and disjoint foregrounds. Its baseline AP is 0, candidate AP is 1, and candidate-minus-baseline image error count is -2. Add --output PATH to write a new report. All existing CLI limits and output/quality gates apply; --iou-type defaults to bbox, and segm rejects YOLO inputs. This is evaluation of supplied masks, with no inference or external polygon conversion dependency.

    The mask AP differential script verifies 44 synthetic cases (20 fixed boundary cases and 24 seeded cases, seed 20261012) against COCOeval(iouType="segm") from pycocotools==2.0.7. It checks all 12 summary and per-class metrics, 69,690 exported PR samples, 7,746 diagnostic entries and per-image results, at absolute tolerance 1e-9, plus real-process CLI failures and paired output. This establishes the covered RLE behavior, with polygon validation described below. These APIs require 0.2.0 and are absent from 0.1.0.

    #COCO polygon input (source checkout)

    CocoRle::from_polygons(height, width, polygons) rasterizes a COCO array of flat coordinate rings ([[x0,y0,x1,y1,...], ...]) directly into RLE. parse_coco_segmentation(source, height, width) accepts this polygon JSON or a compressed/uncompressed RLE object and checks the canvas dimensions. A polygon ring has at least three x/y pairs and finite coordinates. Both clockwise and counterclockwise rings are accepted; all rings form a union, including nested rings, so winding does not define holes. Use RLE for a mask with holes. Empty ring lists, short/odd coordinate lists, nonnumeric coordinates and malformed JSON are rejected; collinear or collapsed rings may produce valid empty masks. Consecutive equal quantized vertices and an explicit closing vertex are normalized.

    The conversion uses COCO's 5× coordinate quantization, truncation toward zero, edge sampling, boundary downsampling and clipping to the image canvas. It streams boundary points and builds RLE runs without allocating an image-sized pixel array. Negative and out-of-image coordinates are allowed when within the coordinate/work bounds. This is a COCO rasterization contract, not a generic continuous-area polygon calculation: GT annotation area is retained while prediction area comes from foreground pixels.

    There are fixed per-mask limits: at most 100,000 input vertices, 2,000,000 boundary samples after consecutive-point normalization, and 2,000,000 cumulative RLE union run steps. 5*coordinate+0.5 must fit the signed 32-bit range. These limits reject oversized work before large sampling loops or during cumulative merges; they do not promise a hard process-memory cap. Errors identify the record and polygon ring, with coordinate indices for invalid values.

    parse_coco_mask_dataset accepts polygon and RLE annotations together. For predictions, call parse_coco_mask_detections_with_dataset(source, dataset) to parse either form, check image/category references and rasterize at each image's declared size. The original parse_coco_mask_detections(source) remains an RLE-only API and reports the dataset-aware entry point when given polygons. parse_coco_rle also remains RLE-only. The native CLI automatically uses the dataset-aware parser in --iou-type segm mode, including comparisons.

    moon run cli --target native -- examples/polygon-ground-truth.json examples/polygon-detections.json --iou-type segm --compare examples/mask-candidate.json --pr-curves --image-ranking --min-ap50 1

    This synthetic example compares polygon predictions with equivalent compressed RLE: both AP values are 1 and defined metric deltas are 0. The polygon reference script checks 178 fixed/seeded geometries against exact pycocotools==2.0.7 pixels, area and IoU; it also checks 42 polygon-input AP cases (42,420 PR samples and 1,330 diagnostic entries), mixed polygon/RLE comparison, output protection and malformed/oversized CLI input. Seed is 20261013; metric tolerance is 1e-9. Cases cover subpixel rounding, negative coordinates, clipping, concave/self-intersecting shapes, multiple/nested rings, repeated points and degenerate rings. This evidence covers these tested cases and bounds; it does not establish arbitrary geometry equivalence.

    The polygon boundary algorithm is adapted from COCO's rleFrPoly and carries its BSD-2-Clause attribution in THIRD_PARTY_NOTICES.md, included in the source archive. Polygon APIs require 0.2.0 and are absent from 0.1.0.

    There is real feature overlap with moonbit-visual-debug and moonbit-synthetic-vision. In the versions inspected on 2026-10-02 (0.2.2 and 0.3.0), the former exposes single-threshold detection precision/recall/F1, confusion data, and COCO/YOLO adapters; the latter exposes synthetic fixtures and integer-scale detection precision/recall. MoonDetEval focuses on the 12 COCO bbox AP/AR summaries, maxDets/crowd/area behavior checked against pycocotools, and a two-prediction native CLI with diagnostics and quality gates. This describes the inspected APIs, not a claim that no other package can evaluate detections.

    #Install and call the library

    Use version 0.2.0. Add the exact version to your own MoonBit module with moon add python123-ops/moondeteval@0.2.0, then add "python123-ops/moondeteval" to that package's moon.pkg imports. This minimal in-memory consumer produces AP50=1. Release-archive and independent registry checks are described below:

    ///|
    fn main {
    let box = @moondeteval.Box::from_xywh(0.0, 0.0, 10.0, 10.0) catch {
    error => abort(error.message())
    }
    let dataset = @moondeteval.Dataset::new(
    [@moondeteval.Image::new(1, 20, 20)],
    [@moondeteval.Category::new(7, "object")],
    [@moondeteval.GroundTruth::new(9L, 1, 7, box, area=100.0)],
    ) catch {
    error => abort(error.message())
    }
    let prediction = @moondeteval.Detection::new(0, 1, 7, box, 0.9) catch {
    error => abort(error.message())
    }
    let report = @moondeteval.evaluate(dataset, [prediction]) catch {
    error => abort(error.message())
    }
    println("AP50=\{report.summary().ap50()}")
    }

    #Run the source examples

    The hosted viewer loads one of three CLI-generated synthetic reports with ?demo=bbox, ?demo=mask, or ?demo=polygon. Hosted examples require HTTP; selecting your own local JSON also works when the viewer is opened as a file. The product site supports Chinese and English; the viewer uses Chinese.

    The optional read-only report viewer opens directly in a browser from a checkout. Generate a JSON file with --output report.json, open viewer/index.html, and choose that file. It accepts bbox or RLE mask single-model and --compare reports with schema_version=0.1.0. It shows the existing 12 metrics, class results, per-image changes, PR samples, and paged matching diagnostics; optional sections say when their CLI flag was not enabled. The viewer reads the file locally, sends no data to a server, and never recalculates AP/AR or comparison deltas. It accepts files up to 128 MiB; a browser may need more memory than the file size. It rejects numeric report IDs outside JavaScript's exact integer range rather than displaying rounded IDs; use a lossless JSON reader for those reports. The viewer is distributed with the source repository, outside the Mooncakes library archive.

    To inspect a batch, choose 选择批量结果文件夹 in the viewer and select the output folder containing index.json and its NAME.report.json files. Format 1 supports bbox; format 2 supports bbox or segm. The table displays the index's existing summary values and deltas. Selecting a model reads only that report, verifies its SHA-256, then checks task type, evaluator version, configuration, counts and summary against the index before showing details. Source paths and replay commands in the index are neither displayed nor followed. This checks consistency with the supplied index, not authenticity of its producer or proof that its input data was correct. No predictions are re-evaluated in the browser.

    Batch import requires directory-selection support and Web Crypto SHA-256 (HTTPS, localhost, or a supporting browser's file:// context). Unsupported browsers can still import individual JSON reports. Indexes are limited to 4 MiB and 2–1,000 models; each referenced report is limited to 128 MiB. All referenced files must exist beside the index; missing or altered files produce an error. Optional diagnostic sections and local-file import behave as above.

    Browser file access is also subject to platform limits. If Windows reports that a selected file cannot be read, shorten the folder path or model filename and select the folder again. The viewer shows the error and keeps the detail view hidden.

    For more than two COCO bbox or segmentation prediction files, the source-checkout batch evaluator evaluates every model against one frozen copy of the same ground-truth JSON and emits an index.json plus one full report per model. It records SHA-256 hashes of inputs and reports, exact source paths, replay commands, configuration, 12 metrics, and per-model differences from a named baseline. Undefined -1 metrics have null differences; metrics are never averaged across models. This tool does not infer comparability from unrelated existing report files. Use a new output directory, for example:

    python examples/batch_eval.py --ground-truth examples/compare-ground-truth.json --model baseline examples/compare-baseline.json --model candidate examples/compare-candidate.json --baseline baseline --image-ranking --pr-curves --output-dir ../moondeteval-batch-results

    For instance masks, use the same tool with one shared task selection:

    python examples/batch_eval.py --ground-truth examples/polygon-ground-truth.json --iou-type segm --model polygon examples/polygon-detections.json --model rle examples/mask-candidate.json --model miss examples/mask-baseline.json --baseline polygon --diagnostics --image-ranking --pr-curves --output-dir ../moondeteval-segm-batch-results

    This example produces AP values 1, 1, 0 and baseline-relative AP differences 0, 0, -1. Polygon and RLE encodings can coexist within a segm batch. --iou-type accepts bbox (default) or segm and applies to every model in the run. Every report must match that type; missing type markers are accepted only for legacy bbox reports. A segm report requires explicit iou_type="segm". Unknown/mismatched markers and conflicting COCO ignore policies are rejected before publishing the bundle, as are differences in configuration, ground-truth counts or evaluator version.

    New indexes use format_version="2" and record iou_type at the top level and in every model entry. Format 1 was bbox-only; consumers should check the version and task before comparing indexes. The tool generates one comparable batch from frozen inputs; it does not merge unrelated indexes or existing reports. Each segm replay command retains --iou-type segm. Default bbox commands omit the new CLI flag to remain compatible with the published 0.1.0 native executable; segm requires a 0.2.0 CLI with mask support. Run python -m unittest examples/batch_eval_test.py -v for seven integration/boundary checks including exact report replay, three-input mixed encoding, failed-input cleanup and rejected report types.

    Real-model validation also used fixed Ultralytics 8.3.0 YOLO11n-seg weights on 64 cached COCO val2017 images, with CPU inference at imgsz=640 and 320, conf=0.001, NMS IoU 0.7, max_det=100, and retina_masks=True. Masks came from original-resolution binary masks.data, encoded as RLE; category IDs were mapped by COCO class names. A third input filtered the 640 predictions at confidence 0.25. All three reports matched COCOeval(segm) summary/per-class metrics, 242,400 PR samples, 111,844 diagnostic entries and image counts at tolerance 1e-9; batch deltas matched direct comparison, and GT polygon-to-RLE conversion/removing supplied detection box area preserved metrics. Reference inputs remove detection bbox/area to select COCO's mask-area branch. These are subset validation results, not official full-validation accuracy or runtime ML dependencies.

    The index and all reports appear only when evaluation finishes without input or resource errors. Quality-gate failures retain the complete bundle and return exit 3. Existing output directories are refused. This standard-library Python tool is a development companion to the native CLI, not part of the Mooncakes package.

    #Metric regression gates (0.2.0)

    Use GateRule::minimum("ap", 0.30) or GateRule::max_drop("ap", 0.05) to construct rules; optional category_id=85 scopes either rule to a category. GatePolicy::new(rules, undefined_policy="fail") accepts 1–1,000 unique rules. EvaluationReport::check_gates(policy) checks minimum values; ComparisonReport::check_gates(policy) checks the candidate, using its paired baseline for drop rules. Both return a GateReport; they do not modify the evaluation report. GateReport::status(), reason(), checks() and to_json_string() expose the result. Each GateCheck exposes kind, metric, category ID, threshold, candidate, baseline, drop, status and reason through methods of the same names.

    All 12 existing metric names are supported: ap, ap50, ap75, ap_small, ap_medium, ap_large, ar1, ar10, ar100, ar_small, ar_medium, ar_large. Thresholds are finite fractions in [0,1]: 0.01 means one percentage point. Minimum checks use candidate + 1e-12 >= threshold; drop checks use baseline - candidate <= threshold + 1e-12. Negative drop means improvement. Duplicate kind/metric/scope rules, unknown metrics or categories, oversized policies and single-model drop rules raise InvalidConfig. Signed, non-contiguous category IDs are supported. Undefined metrics fail by default; undefined_policy="skip" records a skipped rule, but an entirely skipped policy still fails.

    The CLI accepts these repeatable options:

    --min-metric METRIC=VALUE --max-metric-drop METRIC=VALUE --min-class-metric CATEGORY:METRIC=VALUE --max-class-drop CATEGORY:METRIC=VALUE --undefined-gate fail|skip

    moon run cli --target native -- examples/compare-ground-truth.json examples/compare-baseline.json --compare examples/compare-candidate.json --max-metric-drop ap50=0.1 --min-metric ar100=0.5 --output ../regression.report.json

    This intentionally returns 3: AP50 drops from 1 to 0.25, exceeding 0.1. The full report is saved before the quality failure. Input/configuration errors return 2. Drop options require --compare; --undefined-gate requires at least one rule. Legacy --min-ap50 keeps its behavior when used alone and cannot be combined with these new rules. --fail-on-warning remains an independent gate and may fail even when all metric rules pass.

    New rules add an optional root metric_gates object to schema 0.1.0; no-rule JSON stays unchanged. Gate format 1 records status, undefined_policy, reason and ordered checks. Each check records kind, metric, explicit nullable category_id, threshold, nullable candidate/baseline/drop, status (passed, failed, skipped) and reason (ok, below_minimum, drop_exceeded, undefined_candidate, undefined_baseline). Whole-policy reasons are ok, rules_failed or all_skipped. Unavailable values are null, rather than a passing zero.

    The batch tool accepts the same new options. The named baseline is only a reference; every other model is checked with the same policy. Minimum-only batches use single evaluation. If any drop rule is present, each candidate uses the native comparison CLI, which evaluates the baseline again; expect extra CPU time and paired-report memory/output overhead under the existing resource limits. The saved candidate report carries the comparison's metric_gates. report_source="comparison.candidate" documents that projection: its replay command produces a full paired report; take candidate and attach root metric_gates to reproduce the stored single report.

    python examples/batch_eval.py --ground-truth examples/compare-ground-truth.json --model baseline examples/compare-baseline.json --model candidate examples/compare-candidate.json --model unchanged examples/compare-baseline.json --baseline baseline --max-metric-drop ap50=0.1 --output-dir ../gated-batch

    The tool completes every model, saves all reports plus per-model and aggregate metric_gates in format 2, then returns 3 if any candidate fails. Input/configuration/resource errors abort without publishing a partial directory. The viewer shows the native rule outcomes and failure reasons, verifies report/index gate consistency, and accepts old reports and format 1 indexes. It does not decide thresholds in JavaScript. These gates require 0.2.0 and are absent from 0.1.0. Run python -m unittest examples/gate_test.py -v for CLI and batch failure-path checks.

    #Reusable policy files

    Keep the same rules across local runs and CI with GatePolicy::from_json_string(source), to_json() and to_json_string(). The parser uses the existing constructors and gate evaluator. For example, regression-policy.json contains:

    {"format_version":"1","undefined_policy":"fail","rules":[{"kind":"max_drop","metric":"ap50","threshold":0.1},{"kind":"minimum","metric":"ar100","threshold":0.5}]}

    format_version and rules are required; undefined_policy defaults to fail. Each rule requires kind, metric and numeric threshold. Omitted or null category_id means overall; a signed 32-bit integer selects a category. Serialization writes the default policy and explicit null scope, preserving rule order. Unknown keys, versions, kinds and invalid numeric/category types are rejected as InvalidConfig; shared duplicate/1,000-rule/finite-threshold checks also apply. Like the existing JSON inputs, duplicate object keys use the last value; duplicate rules are rejected separately.

    moon run cli --target native -- --check-gate-policy examples/regression-policy.json moon run cli --target native -- examples/compare-ground-truth.json examples/compare-baseline.json --compare examples/compare-candidate.json --gate-policy examples/regression-policy.json python examples/batch_eval.py --ground-truth examples/compare-ground-truth.json --model baseline examples/compare-baseline.json --model candidate examples/compare-candidate.json --model unchanged examples/compare-baseline.json --baseline baseline --gate-policy examples/regression-policy.json --output-dir ../policy-batch

    The first command validates configuration without loading a dataset and prints canonical policy JSON, returning 0 or 2. It cannot check whether a category exists in a particular dataset. The evaluation commands intentionally return 3 for the AP50 regression while retaining complete results. File policies are exclusive with inline metric rules, --undefined-gate and legacy --min-ap50; --fail-on-warning remains independent. Drop rules still require a comparison. Policy files must be UTF-8 JSON and at most 256 KiB; evaluation also counts their bytes toward --max-input-bytes and --max-total-input-bytes. The library string API uses the JSON parser's default nesting guard; it does not impose filesystem byte limits.

    Batch evaluation validates the frozen policy through the native CLI before model evaluation. It uses the same snapshot for every candidate and rejects original or frozen-policy changes before publishing a directory. Successful evaluation, including quality failures, saves the original bytes as gate-policy.json. The optional format-2 index field gate_policy={"file":"gate-policy.json","sha256":"…"} identifies that snapshot; metric_gates still carries native rule outcomes. Replay commands reference the original policy path; if it has changed, use the saved snapshot to restore the checked configuration. The viewer requires this file when the index references it, verifies its SHA-256 and matches the policy to native rule metadata before displaying the batch. Hash checks establish consistency, not producer authenticity. Old indexes need no policy file. Run python -m unittest examples/policy_test.py -v for file, preflight, snapshot-mutation and resource-boundary checks.

    From a checkout of this repository, run:

    moon run cli --target native -- examples/ground-truth.json examples/detections.json moon run cli --target native -- examples/ground-truth.json examples/detections.json --diagnostics --min-ap50 0.8 moon run cli --target native -- examples/ground-truth.json examples/detections.json --output report.json python examples/compare_demo.py python examples/showcase_demo.py

    The first command's actual schema_version and summary fields are:

    {"schema_version":"0.1.0","summary":{"ap":1,"ap50":1,"ap75":1,"ap_small":1,"ap_medium":-1,"ap_large":-1,"ar1":0,"ar10":1,"ar100":1,"ar_small":1,"ar_medium":-1,"ar_large":-1}}

    The command prints a JSON report with schema_version, evaluator_version, config, counts, summary, classes, diagnostics, and quality_warnings. The example's AP and AP50 are 1, while AR@1 is 0: the highest-scored prediction lands inside an ignored crowd box and occupies the single-detection slot. The second prediction matches the regular ground truth. counts includes raw input totals and evaluated_detections, the predictions retained after the all-area maxDets=100 cap for each image/category. A retained prediction need not be a true positive. Warnings identify out-of-image ground-truth and detection boxes by source type and record/image/category IDs. They appear even without --diagnostics, and never clip boxes or alter AP/AR. --diagnostics includes decisions for each configured IoU threshold using the all-area range and maxDets=100. Each entry has source IDs, status (tp, fp, ignored, or fn), reason, and matched IoU (zero when unmatched). FP reasons use fixed priority: duplicate, wrong_class, localization (same-class IoU at least 0.1), then background. These explanations do not affect metrics.

    For a direct two-detector comparison, run:

    moon run cli --target native -- examples/compare-ground-truth.json examples/compare-baseline.json --compare examples/compare-candidate.json --diagnostics moon run cli --target native -- examples/compare-ground-truth.json examples/compare-baseline.json --compare examples/compare-candidate.json --image-ranking moon run cli --target native -- examples/compare-ground-truth.json examples/compare-baseline.json --compare examples/compare-candidate.json --pr-curves

    --image-ranking adds image_summaries to each evaluation: per image tp, fp, fn_count, and ignored at fixed IoU 0.50, all areas, and maxDets=100 per image/category. These are detection counts, not per-image AP. The option works without --diagnostics and does not alter AP/AR. In comparison mode, image_changes ranks images by error_delta = candidate(fp + fn_count) - baseline(fp + fn_count), largest regression first, then image ID for ties. An image with no truths or predictions still has a zero-count row; crowd matches are ignored, not errors. The option is available to library callers through EvalConfig::new(..., include_image_summaries=true). Without it the arrays are empty. This ranking is a review aid, not an official COCO metric.

    --pr-curves adds a shared recall_thresholds grid (0.00 to 1.00, 101 values) and pr_curves entries for each category and configured IoU threshold, in dataset category order and ascending threshold order. Each curve contains category_id, iou_threshold, 101 sampled precision values, max_recall, and ap. It uses the same all-area, maxDets=100 precision envelope as AP; the mean of a defined curve's samples equals its ap. With no eligible ground truths, all 101 precision values, max_recall, and ap are -1; with eligible truths but no detections they are zero. A custom config exports only its configured IoU thresholds, even though AP50/AP75 remain fixed summary probes. Library callers use EvalConfig::new(..., include_pr_curves=true). The option works without diagnostics or image ranking; in comparison mode each nested report carries its own curves. Without the option both arrays are empty. Existing --max-report-bytes also bounds the larger JSON output.

    --compare evaluates both prediction files against the same ground truths and configuration. The comparison JSON contains complete baseline and candidate reports, then 12 overall metric changes and 12 changes per class in stable metric order. Every delta is candidate minus baseline; a COCO -1 undefined value becomes null in the comparison entries and its delta is null. The full nested reports still retain their original -1 representation. In the included synthetic example, baseline AP50 is 1, candidate AP50 is 0.25, and the delta is -0.75; at IoU 0.5 candidate detection IDs 0, 1, and 3 are wrong_class, localization, and duplicate. python examples/compare_demo.py checks these values and prints a short readable trace. The diagnostic reason labels are MoonDetEval explanations, not official COCO categories.

    #Multi-image YOLO predictions

    The native CLI can evaluate six-column YOLO prediction TXT files directly through a JSON manifest. The manifest's category_ids maps zero-based YOLO class indices to the ground-truth category IDs; each images entry names a ground-truth image ID and a TXT file relative to the manifest directory:

    { "category_ids": [42, 7], "images": [ {"image_id": 12, "file": "labels/image-12.txt"}, {"image_id": 9, "file": "labels/image-9.txt"} ] }

    These commands use the checked-in two-image fixture. The second command compares a COCO baseline against a YOLO candidate; the third deliberately fails its AP50 gate with exit code 3 while keeping the complete report in failed-gate.json.

    moon run cli --target native -- examples/yolo/ground-truth.json examples/yolo/manifest.json --prediction-format yolo --diagnostics moon run cli --target native -- examples/yolo/ground-truth.json examples/yolo/detections.json --compare examples/yolo/manifest.json --compare-format yolo --diagnostics moon run cli --target native -- examples/yolo/ground-truth.json examples/yolo/manifest.json --prediction-format yolo --min-ap50 0.8 --output failed-gate.json

    --prediction-format and --compare-format independently accept coco (default) or yolo; --compare-format requires --compare. A YOLO TXT row is class_index center_x center_y width height confidence. Class/coordinate/score validation is the same as parse_yolo_detections. Blank TXT files are valid, and an image absent from the manifest has zero predictions. Detection IDs start at zero and follow manifest order, then nonblank TXT row order. Duplicate image IDs or file names, unknown image/category IDs, invalid field types, and relative paths containing empty, . or .. segments, backslashes or drive prefixes are rejected. A referenced file must exist and contain valid UTF-8; errors identify its path and, for malformed rows, the line. Path checks are lexical; the CLI operates on local files the caller supplies.

    python examples/yolo_manifest_demo.py checks the 12 summary metrics, per-class results, diagnostics, and counts against equivalent COCO JSON, plus mixed-format zero deltas and failure behavior. This is a file-ingestion parity test, not an additional claim of COCO equivalence.

    --min-ap50 N accepts a finite value in [0,1] and checks the candidate in comparison mode. --fail-on-warning fails when any evaluated input has an out-of-image box. Either failed gate keeps the complete report and exits with code 3; invalid options or evaluation input exit with code 2. When both gates fail, the AP50 failure is reported first.

    With --output, the CLI writes the JSON file and prints a one-line summary. It refuses to overwrite an existing path, creates an exclusive temporary file beside the target, syncs it, then renames it without replacement. A failed evaluation creates no report; a failed gate keeps the complete report. A pre-existing .moondeteval.tmp file is never deleted automatically. The default CLI limits are 64 MiB per input file (including each YOLO TXT), 128 MiB total input bytes across ground truth, prediction files/manifests, and referenced TXT files, 100,000 total image/category/ground-truth/detection records across both models, an upper bound of 100,000 diagnostic entries across both reports, and 64 MiB of serialized report text. Use --max-input-bytes, --max-total-input-bytes, --max-records, --max-diagnostics, and --max-report-bytes with positive integers to change them. These guard against accidental oversized work; they are not a hard process-memory limit.

    For performance measurements, build the native CLI with moon build cli --target native --release and add --timings. Successful runs print input, evaluation, serialization, and output durations in microseconds to stderr while leaving the JSON on stdout unchanged. input includes reading, parsing, and input validation; output includes report-size validation and writing to stdout or the requested file. The durations exclude process startup. In particular, --max-report-bytes is checked after serialization, so a large diagnostic report can consume substantial memory before the limit rejects it.

    #Single-machine evaluation (0.2.0)

    The evaluator indexes ground truths and detections once by category and image, and visits only image/category groups containing records. Empty images remain in report counts and image summaries. AP still uses the global score ranking; it is not an average of per-image or per-shard AP.

    Mask evaluation caches at most 262,144 IoU pairs per category by default. evaluate_masks_with_config and compare_mask_predictions_with_config accept the optional named argument max_iou_cache_pairs; the native CLI and batch tool accept --max-iou-cache-pairs N. Values are integers in [0,16777216]; 0 disables caching. Only complete matrices for up to 100 ranked detections per image/category are cached. A matrix that does not fit is computed on demand, without discarding detections or changing matching. Crowd overlap, stable score ties, area ranges and diagnostics retain their existing semantics. Bbox uses the same input index but does not allocate an overlap cache.

    The cache budget counts pairs, not input records or process memory. Each cached value occupies eight bytes, with additional array/index overhead. Inputs and output JSON are still materialized in memory. Start large jobs with summary reports; diagnostics and per-image results can substantially increase memory use. Increase the existing byte/record limits explicitly for larger inputs, measure with a native release build and --timings, and use an operating-system memory limit if a hard process limit is required.

    moon build cli --target native --release # Replace the executable path with the build artifact on your platform. _build/native/release/build/cli/cli.exe ground-truth.json predictions.json --iou-type segm --max-input-bytes 536870912 --max-total-input-bytes 1073741824 --max-records 5000000 --max-iou-cache-pairs 262144 --timings --output report.json

    These execution settings do not change the report's scientific configuration or schema. Batch replay commands retain an explicitly supplied cache budget; omitting the option preserves compatibility with the published bbox CLI. The cache option and mask APIs described here require 0.2.0 and are absent from 0.1.0.

    #Library use

    ///|
    let dataset = @moondeteval.parse_coco_dataset(ground_truth_json)

    ///|
    let predictions = @moondeteval.parse_coco_detections(detections_json)

    ///|
    let report = @moondeteval.evaluate(dataset, predictions)

    ///|
    let ap50 = report.summary().ap50()

    ///|
    let config = @moondeteval.EvalConfig::new([0.5, 0.75], include_diagnostics=true)

    ///|
    let custom = @moondeteval.evaluate_with_config(dataset, predictions, config)

    ///|
    let comparison = @moondeteval.compare_predictions(
    dataset, predictions, other_predictions,
    )

    ///|
    let ap50_change = comparison.metrics()[1].delta()

    ///|
    let yolo_predictions = @moondeteval.parse_yolo_detections(
    yolo_txt,
    dataset,
    9,
    [42, 7],
    first_detection_id=100,
    )

    ///|
    let manifest = @moondeteval.parse_yolo_manifest(manifest_json, dataset)

    ///|
    let retained = report.counts().evaluated_detections()

    ///|
    let warnings = report.quality_warnings()

    ///|
    let explicit_ignore = @moondeteval.EvalConfig::new(
    [0.5],
    ignore_policy="respect_explicit",
    )

    The public API also supports Image::new, Category::new, Box::from_xywh, GroundTruth::new, Detection::new, and Dataset::new for callers with records already in memory. Detection IDs must be unique per evaluation; the JSON adapter assigns zero-based IDs from source order. Coordinates and scores must be finite, and boxes must have positive dimensions. Image and category IDs may be sparse or zero. Custom IoU thresholds must be strictly increasing in (0,1]; AP and AR average over them, while AP50 and AP75 remain fixed probes. Area bins and maxDets remain the COCO defaults. With no --output, the CLI prints JSON to standard output.

    parse_yolo_detections accepts one image's six-column YOLO prediction TXT (class_index center_x center_y width height confidence). Ultralytics save_txt omits confidence by default, so use save_conf=True; five-column training labels and seven-column tracking rows are intentionally rejected here because AP requires a score and tracking IDs are outside this adapter's scope. Pass the ground-truth Dataset, its image ID, and an explicit category map: [42, 7] maps YOLO class 0 to category 42 and class 1 to category 7. It uses that image's width and height to convert normalized center coordinates to continuous boxes, skips blank lines, and gives nonblank rows sequential IDs starting at first_detection_id. Use a nonoverlapping ID range when combining multiple image files. Class indices must be mapped, center/score fields must lie in [0,1], and normalized width/height in (0,1]; errors identify the source line and field. A derived box that crosses the image edge is retained for evaluation and produces a quality warning. parse_yolo_manifest validates the ordered multi-image file list against a dataset; the native CLI then loads those files and assigns consecutive detection IDs.

    The consumer example is a separate MoonBit module linked to this checkout by its moon.work file. Its MBMOT bridge converts detector frames into MoonDetEval predictions. The multi-frame form takes distinct target image_id values, an explicit MBMOT class-ID to dataset category-ID map, and a starting detection ID; it allocates consecutive IDs across frames and rejects unmapped classes, repeated frame IDs, ambiguous mappings, and ID overflow. It evaluates detector outputs only: MBMOT track IDs and MOT metrics are not reused. Run moon test --target all --deny-warn from examples/consumer for the source-workspace tests.

    The same consumer also imports the moon-cv-geometry package at version 0.2.2. Its geometry adapter converts a Rect2 to a validated MoonDetEval Box, or maps four corners through a homography and evaluates the target-image envelope. The integration test gets AP50 1 for a known transform and rejects a projective horizon crossing or zero-area rectangle. No geometry dependency is added to the MoonDetEval library; applications opt into the bridge when they need cross-view coordinates. The geometry repository's examples/detection_bounds independently shows the source side of the workflow.

    To verify the published versions together, run python examples/consumer/registry_smoke.py. It creates a temporary module with no moon.work, checks the resolved dependency tree for registry-sourced MoonDetEval 0.1.0, MBMOT 0.3.0, and moon-cv-geometry 0.2.2, runs the adapter tests on all four stable targets, then runs a native two-frame pipeline through geometry, MBMOT detections, and MoonDetEval (AP50=1). Pass --output-dir PATH to retain the independent module and registry-result.json; the destination must not exist. The target-image envelope is not clipped to the image boundary, and an axis-aligned envelope is not a rotated-box or mask evaluation.

    #Verify a source checkout

    Use a MoonBit 0.10.15-compatible toolchain. Build the static site with python site/build.py; its allowlisted assets and freshly evaluated synthetic reports appear under _build/site/. To serve the project subpath locally, serve _build with an HTTP server and open /site/. Browser checks run with node --test site/browser.test.cjs after making Playwright 1.62.1 and its Chromium browser available. Viewer model checks run with node --test viewer/model.test.cjs viewer/batch.test.cjs. Batch checks generate actual native CLI outputs in temporary folders; set PYTHON to override the test interpreter.

    Verify a candidate archive with python examples/consumer/archive_smoke.py --archive _build/publish/python123-ops-moondeteval-0.2.0.zip after moon package. This extracts the archive into a separate module and tests the core, consumer APIs, native mask comparison and three-input batch segmentation without linking this checkout. python examples/consumer/registry_smoke.py --moondeteval-version 0.1.0 separately verifies the exact published release; use --moondeteval-version 0.2.0 to verify the new release, including mask and gate APIs.

    moon fmt --check moon check --target all --deny-warn moon test --target all --deny-warn python -m unittest examples/scale_test.py -v python examples/compare_demo.py python examples/yolo_manifest_demo.py python -m pip install numpy==1.26.4 pycocotools==2.0.7 python reference_compare.py python examples/mask_rle_reference.py python examples/mask_ap_reference.py python examples/polygon_reference.py

    pycocotools is used only for development comparison; the MoonBit library does not depend on Python. The native CLI uses moonbitlang/async@0.21.3 for file access.

    #AI 使用说明

    开发 MoonDetEval 时,我使用了 OpenAI Codex,帮助我梳理功能、编写和修改 MoonBit 与 Python 代码、补充测试、排查问题以及整理文档。AI 参与了实际代码的辅助开发。

    整体开发由我把控技术路线和质量边界。我根据项目需求逐步确定和调整开发目标,并通过实际运行测试、检查边界情况,以及与 pycocotools 对照结果来验证功能。

    项目参考或改编的 COCO 算法会保留相应的来源和许可证说明。AI 工具不属于评测库的运行依赖,真实分割模型的推理用于外部验收。

    #License and attribution

    MoonDetEval is Apache-2.0. COCO's official evaluator and mask encoding API are behavioral and format references for the original bbox and RLE implementations. The polygon rasterization rules are adapted from COCO rleFrPoly; its BSD-2-Clause notice is retained in THIRD_PARTY_NOTICES.md. The COCO API license is separate from this repository's license. pycocotools==2.0.7 and NumPy are development-only reference tools, while the CLI's MoonBit runtime dependency is moonbitlang/async@0.21.3. The checked-in example data is synthetic. Real COCO images/annotations and YOLO model weights used for external validation are not bundled or redistributed in this repository.

    EvalError

    pub(all) suberror EvalError {
    InvalidBox(String)
    InvalidInput(String)
    InvalidConfig(String)
    } derive(
    Debug
    )

    EvalError::message

    fn EvalError::message(self : EvalError) -> String

    Box

    pub struct Box {
    x1 : Double
    y1 : Double
    x2 : Double
    y2 : Double
    } derive(Eq,
    Debug
    )

    An axis-aligned rectangle in continuous pixel coordinates.

    Box::area

    fn Box::area(self : Box) -> Double

    Box::equal

    fn Box::equal(Box, Box) -> Bool

    Box::from_xywh

    fn Box::from_xywh(x : Double, y : Double, width : Double, height : Double) -> Box raise EvalError

    Creates a box from COCO-style top-left coordinates and dimensions.

    Box::iou

    fn Box::iou(self : Box, other : Box) -> Double

    Computes IoU for two ordinary (non-crowd) boxes.

    Box::new

    fn Box::new(x1 : Double, y1 : Double, x2 : Double, y2 : Double) -> Box raise EvalError

    Creates a nonempty box. Coordinates outside an image are allowed.

    Box::not_equal

    fn Box::not_equal(x : Box, y : Box) -> Bool

    Box::to_repr

    Box::to_xywh

    fn Box::to_xywh(self : Box) -> (Double, Double, Double, Double)

    Category

    pub struct Category {
    id : Int
    name : String
    } derive(Eq,
    Debug
    )

    Category::equal

    fn Category::equal(Category, Category) -> Bool

    Category::id

    fn Category::id(self : Category) -> Int

    Category::name

    fn Category::name(self : Category) -> String

    Category::new

    fn Category::new(id : Int, name : String) -> Category raise EvalError

    Category::not_equal

    fn Category::not_equal(x : Category, y : Category) -> Bool

    Category::to_repr

    ClassComparison

    pub struct ClassComparison {
    category_id : Int
    metrics : Array[MetricChange]
    } derive(ToJson)

    Category values use the same metric order as the overall comparison.

    ClassComparison::category_id

    fn ClassComparison::category_id(self : ClassComparison) -> Int

    ClassComparison::metrics

    ClassComparison::to_json

    ClassMetrics

    pub struct ClassMetrics {
    category_id : Int
    metrics : Metrics
    } derive(ToJson)

    ClassMetrics::category_id

    fn ClassMetrics::category_id(self : ClassMetrics) -> Int

    ClassMetrics::metrics

    fn ClassMetrics::metrics(self : ClassMetrics) -> Metrics

    ClassMetrics::to_json

    CocoRle

    pub struct CocoRle {
    height : Int
    width : Int
    counts : Array[Int]
    foreground_area : Int
    } derive(Eq,
    Debug
    )

    A validated COCO mask RLE. Runs alternate background and foreground pixels in column-major image order, starting with background.

    CocoRle::area

    fn CocoRle::area(self : CocoRle) -> Int

    CocoRle::crowd_iou

    fn CocoRle::crowd_iou(self : CocoRle, other : CocoRle) -> Double raise EvalError

    COCO crowd overlap: self is the detection, other is a crowd ground truth. The denominator is the detection area, not the union.

    CocoRle::equal

    fn CocoRle::equal(CocoRle, CocoRle) -> Bool

    CocoRle::from_compressed

    fn CocoRle::from_compressed(height : Int, width : Int, encoded : String) -> CocoRle raise EvalError

    Reads the COCO compressed counts string (signed 5-bit groups, ASCII 48–111). Counts from the fourth run onward are differences from two runs earlier. At most seven characters per run are accepted for Int-sized masks.

    CocoRle::from_polygons

    fn CocoRle::from_polygons(height : Int, width : Int, polygons : Array[Array[Double]]) -> CocoRle raise EvalError

    Rasterizes COCO polygon rings and unions them into one instance mask. Rings contain >=3 x/y pairs; winding does not create holes. Empty ring lists are invalid. Degenerate rings may yield empty masks. Uses COCO's 5x boundary quantization. Limits: 100000 total vertices, 2000000 boundary samples and 2000000 union run steps per mask; scaled coordinates fit signed 32-bit Int.

    CocoRle::height

    fn CocoRle::height(self : CocoRle) -> Int

    CocoRle::iou

    fn CocoRle::iou(self : CocoRle, other : CocoRle) -> Double raise EvalError

    Returns the mask IoU for equal-sized images. Empty masks have IoU zero.

    CocoRle::new

    fn CocoRle::new(height : Int, width : Int, counts : Array[Int]) -> CocoRle raise EvalError

    Creates an RLE without expanding it into a pixel array. The run lengths must sum to height * width; an initial zero run represents a mask whose first pixel is foreground. Dimensions are limited to Int-sized images.

    CocoRle::not_equal

    fn CocoRle::not_equal(x : CocoRle, y : CocoRle) -> Bool

    CocoRle::to_compressed

    fn CocoRle::to_compressed(self : CocoRle) -> String

    Exports the stored run sequence in COCO compressed counts format.

    CocoRle::to_repr

    CocoRle::width

    fn CocoRle::width(self : CocoRle) -> Int

    ComparisonReport

    pub struct ComparisonReport {
    schema_version : String
    baseline : EvaluationReport
    candidate : EvaluationReport
    metrics : Array[MetricChange]
    classes : Array[ClassComparison]
    image_changes : Array[ImageChange]
    } derive(ToJson)

    Both evaluations share one dataset and one configuration.

    ComparisonReport::baseline

    ComparisonReport::candidate

    ComparisonReport::check_gates

    fn ComparisonReport::check_gates(self : ComparisonReport, policy : GatePolicy) -> GateReport raise EvalError

    Check the candidate using the baseline from the same dataset/configuration.

    ComparisonReport::classes

    ComparisonReport::image_changes

    fn ComparisonReport::image_changes(self : ComparisonReport) -> Array[ImageChange]

    ComparisonReport::metrics

    ComparisonReport::to_json

    ComparisonReport::to_json_string

    fn ComparisonReport::to_json_string(self : ComparisonReport) -> String

    Dataset

    pub struct Dataset {
    images : Array[Image]
    categories : Array[Category]
    truths : Array[GroundTruth]
    }

    A reusable, immutable evaluation input.

    Dataset::categories

    fn Dataset::categories(self : Dataset) -> Array[Category]

    Dataset::images

    fn Dataset::images(self : Dataset) -> Array[Image]

    Dataset::new

    fn Dataset::new(images : Array[Image], categories : Array[Category], truths : Array[GroundTruth]) -> Dataset raise EvalError

    Dataset::truths

    fn Dataset::truths(self : Dataset) -> Array[GroundTruth]

    Detection

    pub struct Detection {
    id : Int
    image_id : Int
    category_id : Int
    bbox : Box
    mask : CocoRle?
    score : Double
    } derive(Eq,
    Debug
    )

    A scored detector output. id is stable within one evaluation.

    Detection::bbox

    fn Detection::bbox(self : Detection) -> Box

    Detection::category_id

    fn Detection::category_id(self : Detection) -> Int

    Detection::equal

    fn Detection::equal(Detection, Detection) -> Bool

    Detection::id

    fn Detection::id(self : Detection) -> Int

    Detection::image_id

    fn Detection::image_id(self : Detection) -> Int

    Detection::new

    fn Detection::new(id : Int, image_id : Int, category_id : Int, bbox : Box, score : Double) -> Detection raise EvalError

    Detection::not_equal

    fn Detection::not_equal(x : Detection, y : Detection) -> Bool

    Detection::score

    fn Detection::score(self : Detection) -> Double

    EvalConfig

    pub struct EvalConfig {
    iou_thresholds : Array[Double]
    include_diagnostics : Bool
    include_image_summaries : Bool
    include_pr_curves : Bool
    ignore_policy : String
    } derive(ToJson)

    AP and AR average over these IoU thresholds. AP50 and AP75 remain fixed probes.

    EvalConfig::coco

    fn EvalConfig::coco() -> EvalConfig

    EvalConfig::ignore_policy

    fn EvalConfig::ignore_policy(self : EvalConfig) -> String

    EvalConfig::include_diagnostics

    fn EvalConfig::include_diagnostics(self : EvalConfig) -> Bool

    EvalConfig::include_image_summaries

    fn EvalConfig::include_image_summaries(self : EvalConfig) -> Bool

    EvalConfig::include_pr_curves

    fn EvalConfig::include_pr_curves(self : EvalConfig) -> Bool

    EvalConfig::iou_thresholds

    fn EvalConfig::iou_thresholds(self : EvalConfig) -> Array[Double]

    EvalConfig::new

    fn EvalConfig::new(iou_thresholds : Array[Double], include_diagnostics? : Bool, ignore_policy? : String, include_image_summaries? : Bool, include_pr_curves? : Bool) -> EvalConfig raise EvalError

    EvalConfig::to_json

    fn EvalConfig::to_json(EvalConfig) -> Json

    EvaluationReport

    pub struct EvaluationReport {
    schema_version : String
    evaluator_version : String
    iou_type : String
    config : EvalConfig
    counts : InputCounts
    summary : Metrics
    classes : Array[ClassMetrics]
    diagnostics : Array[MatchDiagnostic]
    image_summaries : Array[ImageSummary]
    recall_thresholds : Array[Double]
    pr_curves : Array[PrecisionRecallCurve]
    quality_warnings : Array[QualityWarning]
    }

    EvaluationReport::check_gates

    fn EvaluationReport::check_gates(self : EvaluationReport, policy : GatePolicy) -> GateReport raise EvalError

    Check absolute rules on this report. Drop rules require ComparisonReport.

    EvaluationReport::classes

    EvaluationReport::config

    EvaluationReport::counts

    EvaluationReport::diagnostics

    EvaluationReport::evaluator_version

    fn EvaluationReport::evaluator_version(self : EvaluationReport) -> String

    EvaluationReport::image_summaries

    fn EvaluationReport::image_summaries(self : EvaluationReport) -> Array[ImageSummary]

    EvaluationReport::iou_type

    fn EvaluationReport::iou_type(self : EvaluationReport) -> String

    EvaluationReport::pr_curves

    EvaluationReport::quality_warnings

    fn EvaluationReport::quality_warnings(self : EvaluationReport) -> Array[QualityWarning]

    EvaluationReport::recall_thresholds

    fn EvaluationReport::recall_thresholds(self : EvaluationReport) -> Array[Double]

    EvaluationReport::summary

    EvaluationReport::to_json

    fn EvaluationReport::to_json(self : EvaluationReport) -> Json

    EvaluationReport::to_json_string

    fn EvaluationReport::to_json_string(self : EvaluationReport) -> String

    GateCheck

    pub struct GateCheck {
    kind : String
    metric : String
    category_id : Int?
    threshold : Double
    baseline : Double?
    candidate : Double?
    drop : Double?
    status : String
    reason : String
    }

    impl ToJson for GateCheck

    GateCheck::baseline

    fn GateCheck::baseline(self : GateCheck) -> Double?

    GateCheck::candidate

    fn GateCheck::candidate(self : GateCheck) -> Double?

    GateCheck::category_id

    fn GateCheck::category_id(self : GateCheck) -> Int?

    GateCheck::drop

    fn GateCheck::drop(self : GateCheck) -> Double?

    GateCheck::kind

    fn GateCheck::kind(self : GateCheck) -> String

    GateCheck::metric

    fn GateCheck::metric(self : GateCheck) -> String

    GateCheck::reason

    fn GateCheck::reason(self : GateCheck) -> String

    GateCheck::status

    fn GateCheck::status(self : GateCheck) -> String

    GateCheck::threshold

    fn GateCheck::threshold(self : GateCheck) -> Double

    GateCheck::to_json

    fn GateCheck::to_json(self : GateCheck) -> Json

    GatePolicy

    pub struct GatePolicy {
    rules : Array[GateRule]
    undefined_policy : String
    }

    GatePolicy::from_json_string

    fn GatePolicy::from_json_string(source : String) -> GatePolicy raise EvalError

    Parse a version-1 policy. Unknown fields are rejected to catch misspelled rules.

    GatePolicy::new

    fn GatePolicy::new(rules : Array[GateRule], undefined_policy? : String) -> GatePolicy raise EvalError

    GatePolicy::rules

    fn GatePolicy::rules(self : GatePolicy) -> Array[GateRule]

    GatePolicy::to_json

    fn GatePolicy::to_json(self : GatePolicy) -> Json

    GatePolicy::to_json_string

    fn GatePolicy::to_json_string(self : GatePolicy) -> String

    Serialize ordered rules, including explicit null for an overall scope.

    GatePolicy::undefined_policy

    fn GatePolicy::undefined_policy(self : GatePolicy) -> String

    GateReport

    pub struct GateReport {
    format_version : String
    status : String
    undefined_policy : String
    reason : String
    checks : Array[GateCheck]
    } derive(ToJson)

    GateReport::checks

    fn GateReport::checks(self : GateReport) -> Array[GateCheck]

    GateReport::reason

    fn GateReport::reason(self : GateReport) -> String

    GateReport::status

    fn GateReport::status(self : GateReport) -> String

    GateReport::to_json

    fn GateReport::to_json(GateReport) -> Json

    GateReport::to_json_string

    fn GateReport::to_json_string(self : GateReport) -> String

    GateReport::undefined_policy

    fn GateReport::undefined_policy(self : GateReport) -> String

    GateRule

    pub struct GateRule {
    kind : String
    metric : String
    threshold : Double
    category_id : Int?
    } derive(ToJson)

    A minimum value or maximum baseline-minus-candidate drop, in fraction units.

    GateRule::category_id

    fn GateRule::category_id(self : GateRule) -> Int?

    GateRule::kind

    fn GateRule::kind(self : GateRule) -> String

    GateRule::max_drop

    fn GateRule::max_drop(metric : String, threshold : Double, category_id? : Int) -> GateRule raise EvalError

    GateRule::metric

    fn GateRule::metric(self : GateRule) -> String

    GateRule::minimum

    fn GateRule::minimum(metric : String, threshold : Double, category_id? : Int) -> GateRule raise EvalError

    GateRule::threshold

    fn GateRule::threshold(self : GateRule) -> Double

    GateRule::to_json

    fn GateRule::to_json(GateRule) -> Json

    GroundTruth

    pub struct GroundTruth {
    id : Int64
    image_id : Int
    category_id : Int
    bbox : Box
    mask : CocoRle?
    area : Double
    iscrowd : Bool
    ignore : Bool
    } derive(Eq,
    Debug
    )

    A validated COCO-style ground-truth object.

    GroundTruth::area

    fn GroundTruth::area(self : GroundTruth) -> Double

    GroundTruth::bbox

    fn GroundTruth::bbox(self : GroundTruth) -> Box

    GroundTruth::category_id

    fn GroundTruth::category_id(self : GroundTruth) -> Int

    GroundTruth::equal

    fn GroundTruth::equal(GroundTruth, GroundTruth) -> Bool

    GroundTruth::id

    fn GroundTruth::id(self : GroundTruth) -> Int64

    GroundTruth::ignore

    fn GroundTruth::ignore(self : GroundTruth) -> Bool

    GroundTruth::image_id

    fn GroundTruth::image_id(self : GroundTruth) -> Int

    GroundTruth::iscrowd

    fn GroundTruth::iscrowd(self : GroundTruth) -> Bool

    GroundTruth::new

    fn GroundTruth::new(id : Int64, image_id : Int, category_id : Int, bbox : Box, area~ : Double, iscrowd? : Bool, ignore? : Bool) -> GroundTruth raise EvalError

    GroundTruth::not_equal

    fn GroundTruth::not_equal(x : GroundTruth, y : GroundTruth) -> Bool

    Image

    pub struct Image {
    id : Int
    width : Int
    height : Int
    } derive(Eq,
    Debug
    )

    An image declared by the ground-truth dataset.

    Image::equal

    fn Image::equal(Image, Image) -> Bool

    Image::height

    fn Image::height(self : Image) -> Int

    Image::id

    fn Image::id(self : Image) -> Int

    Image::new

    fn Image::new(id : Int, width : Int, height : Int) -> Image raise EvalError

    Image::not_equal

    fn Image::not_equal(x : Image, y : Image) -> Bool

    Image::to_repr

    Image::width

    fn Image::width(self : Image) -> Int

    ImageChange

    pub struct ImageChange {
    image_id : Int
    baseline_errors : Int
    candidate_errors : Int
    error_delta : Int
    } derive(ToJson)

    Candidate minus baseline non-ignored errors at IoU=0.50.

    ImageChange::baseline_errors

    fn ImageChange::baseline_errors(self : ImageChange) -> Int

    ImageChange::candidate_errors

    fn ImageChange::candidate_errors(self : ImageChange) -> Int

    ImageChange::error_delta

    fn ImageChange::error_delta(self : ImageChange) -> Int

    ImageChange::image_id

    fn ImageChange::image_id(self : ImageChange) -> Int

    ImageChange::to_json

    fn ImageChange::to_json(ImageChange) -> Json

    ImageSummary

    pub struct ImageSummary {
    image_id : Int
    tp : Int
    fp : Int
    fn_count : Int
    ignored : Int
    } derive(ToJson)

    Per-image decisions at IoU=0.50, all areas, maxDets=100 per category.

    ImageSummary::fn_count

    fn ImageSummary::fn_count(self : ImageSummary) -> Int

    ImageSummary::fp

    fn ImageSummary::fp(self : ImageSummary) -> Int

    ImageSummary::ignored

    fn ImageSummary::ignored(self : ImageSummary) -> Int

    ImageSummary::image_id

    fn ImageSummary::image_id(self : ImageSummary) -> Int

    ImageSummary::to_json

    ImageSummary::tp

    fn ImageSummary::tp(self : ImageSummary) -> Int

    InputCounts

    pub struct InputCounts {
    images : Int
    categories : Int
    ground_truths : Int
    detections : Int
    evaluated_detections : Int
    } derive(ToJson)

    Input sizes and detections retained by the all-area maxDets=100 evaluation.

    InputCounts::categories

    fn InputCounts::categories(self : InputCounts) -> Int

    InputCounts::detections

    fn InputCounts::detections(self : InputCounts) -> Int

    InputCounts::evaluated_detections

    fn InputCounts::evaluated_detections(self : InputCounts) -> Int

    InputCounts::ground_truths

    fn InputCounts::ground_truths(self : InputCounts) -> Int

    InputCounts::images

    fn InputCounts::images(self : InputCounts) -> Int

    InputCounts::to_json

    fn InputCounts::to_json(InputCounts) -> Json

    MaskDataset

    pub struct MaskDataset {
    value : Dataset
    }

    Mask inputs are separated from bounding boxes to prevent mixed geometry.

    MaskDataset::categories

    fn MaskDataset::categories(self : MaskDataset) -> Array[Category]

    MaskDataset::images

    fn MaskDataset::images(self : MaskDataset) -> Array[Image]

    MaskDataset::new

    fn MaskDataset::new(images : Array[Image], categories : Array[Category], truths : Array[MaskGroundTruth]) -> MaskDataset raise EvalError

    MaskDataset::truths

    MaskDetection

    pub struct MaskDetection {
    value : Detection
    }

    A scored mask. Detection area is always the RLE foreground pixel count.

    MaskDetection::category_id

    fn MaskDetection::category_id(self : MaskDetection) -> Int

    MaskDetection::id

    fn MaskDetection::id(self : MaskDetection) -> Int

    MaskDetection::image_id

    fn MaskDetection::image_id(self : MaskDetection) -> Int

    MaskDetection::mask

    fn MaskDetection::mask(self : MaskDetection) -> CocoRle

    MaskDetection::new

    fn MaskDetection::new(id : Int, image_id : Int, category_id : Int, mask : CocoRle, score : Double) -> MaskDetection raise EvalError

    MaskDetection::score

    fn MaskDetection::score(self : MaskDetection) -> Double

    MaskGroundTruth

    pub struct MaskGroundTruth {
    value : GroundTruth
    }

    An instance mask with COCO annotation area (which may differ from pixel area).

    MaskGroundTruth::area

    fn MaskGroundTruth::area(self : MaskGroundTruth) -> Double

    MaskGroundTruth::category_id

    fn MaskGroundTruth::category_id(self : MaskGroundTruth) -> Int

    MaskGroundTruth::id

    fn MaskGroundTruth::id(self : MaskGroundTruth) -> Int64

    MaskGroundTruth::ignore

    fn MaskGroundTruth::ignore(self : MaskGroundTruth) -> Bool

    MaskGroundTruth::image_id

    fn MaskGroundTruth::image_id(self : MaskGroundTruth) -> Int

    MaskGroundTruth::iscrowd

    fn MaskGroundTruth::iscrowd(self : MaskGroundTruth) -> Bool

    MaskGroundTruth::mask

    MaskGroundTruth::new

    fn MaskGroundTruth::new(id : Int64, image_id : Int, category_id : Int, mask : CocoRle, area~ : Double, iscrowd? : Bool, ignore? : Bool) -> MaskGroundTruth raise EvalError

    MatchDiagnostic

    pub struct MatchDiagnostic {
    iou_threshold : Double
    image_id : Int
    category_id : Int
    detection_id : Int?
    ground_truth_id : Int64?
    iou : Double
    status : String
    reason : String
    }

    One detection decision, or one unmatched eligible ground truth, at an IoU threshold. Diagnostics use the all-area range and maxDets=100.

    MatchDiagnostic::category_id

    fn MatchDiagnostic::category_id(self : MatchDiagnostic) -> Int

    MatchDiagnostic::detection_id

    fn MatchDiagnostic::detection_id(self : MatchDiagnostic) -> Int?

    MatchDiagnostic::ground_truth_id

    fn MatchDiagnostic::ground_truth_id(self : MatchDiagnostic) -> Int64?

    MatchDiagnostic::image_id

    fn MatchDiagnostic::image_id(self : MatchDiagnostic) -> Int

    MatchDiagnostic::iou

    fn MatchDiagnostic::iou(self : MatchDiagnostic) -> Double

    IoU of the chosen match, or zero when no ground truth was matched.

    MatchDiagnostic::iou_threshold

    fn MatchDiagnostic::iou_threshold(self : MatchDiagnostic) -> Double

    MatchDiagnostic::reason

    fn MatchDiagnostic::reason(self : MatchDiagnostic) -> String

    MatchDiagnostic::status

    fn MatchDiagnostic::status(self : MatchDiagnostic) -> String

    MatchDiagnostic::to_json

    fn MatchDiagnostic::to_json(self : MatchDiagnostic) -> Json

    MetricChange

    pub struct MetricChange {
    name : String
    baseline : Double?
    candidate : Double?
    delta : Double?
    }

    A paired metric difference. Undefined COCO values are JSON null here.

    MetricChange::baseline

    fn MetricChange::baseline(self : MetricChange) -> Double?

    MetricChange::candidate

    fn MetricChange::candidate(self : MetricChange) -> Double?

    MetricChange::delta

    fn MetricChange::delta(self : MetricChange) -> Double?

    MetricChange::name

    fn MetricChange::name(self : MetricChange) -> String

    MetricChange::to_json

    fn MetricChange::to_json(self : MetricChange) -> Json

    Metrics

    pub struct Metrics {
    ap : Double
    ap50 : Double
    ap75 : Double
    ap_small : Double
    ap_medium : Double
    ap_large : Double
    ar1 : Double
    ar10 : Double
    ar100 : Double
    ar_small : Double
    ar_medium : Double
    ar_large : Double
    } derive(ToJson,
    Debug
    )

    One COCO-style summary value. -1 means there are no eligible ground truths.

    Metrics::ap

    fn Metrics::ap(self : Metrics) -> Double

    Metrics::ap50

    fn Metrics::ap50(self : Metrics) -> Double

    Metrics::ap75

    fn Metrics::ap75(self : Metrics) -> Double

    Metrics::ap_large

    fn Metrics::ap_large(self : Metrics) -> Double

    Metrics::ap_medium

    fn Metrics::ap_medium(self : Metrics) -> Double

    Metrics::ap_small

    fn Metrics::ap_small(self : Metrics) -> Double

    Metrics::ar1

    fn Metrics::ar1(self : Metrics) -> Double

    Metrics::ar10

    fn Metrics::ar10(self : Metrics) -> Double

    Metrics::ar100

    fn Metrics::ar100(self : Metrics) -> Double

    Metrics::ar_large

    fn Metrics::ar_large(self : Metrics) -> Double

    Metrics::ar_medium

    fn Metrics::ar_medium(self : Metrics) -> Double

    Metrics::ar_small

    fn Metrics::ar_small(self : Metrics) -> Double

    Metrics::to_json

    fn Metrics::to_json(Metrics) -> Json

    Metrics::to_repr

    PrecisionRecallCurve

    pub struct PrecisionRecallCurve {
    category_id : Int
    iou_threshold : Double
    precision : Array[Double]
    max_recall : Double
    ap : Double
    } derive(ToJson)

    A 101-point COCO precision envelope at one category and IoU threshold. A value of -1 means this category has no eligible ground truth.

    PrecisionRecallCurve::ap

    PrecisionRecallCurve::category_id

    fn PrecisionRecallCurve::category_id(self : PrecisionRecallCurve) -> Int

    PrecisionRecallCurve::iou_threshold

    fn PrecisionRecallCurve::iou_threshold(self : PrecisionRecallCurve) -> Double

    PrecisionRecallCurve::max_recall

    fn PrecisionRecallCurve::max_recall(self : PrecisionRecallCurve) -> Double

    PrecisionRecallCurve::precision

    fn PrecisionRecallCurve::precision(self : PrecisionRecallCurve) -> Array[Double]

    PrecisionRecallCurve::to_json

    QualityWarning

    pub struct QualityWarning {
    source : String
    record_id : Int64
    image_id : Int
    category_id : Int
    kind : String
    }

    A nonfatal spatial input warning; scoring uses the original box unchanged.

    QualityWarning::category_id

    fn QualityWarning::category_id(self : QualityWarning) -> Int

    QualityWarning::image_id

    fn QualityWarning::image_id(self : QualityWarning) -> Int

    QualityWarning::kind

    fn QualityWarning::kind(self : QualityWarning) -> String

    QualityWarning::record_id

    fn QualityWarning::record_id(self : QualityWarning) -> Int64

    QualityWarning::source

    fn QualityWarning::source(self : QualityWarning) -> String

    QualityWarning::to_json

    fn QualityWarning::to_json(self : QualityWarning) -> Json

    YoloFile

    pub struct YoloFile {
    image_id : Int
    file : String
    }

    One TXT file, relative to its manifest directory.

    YoloFile::file

    fn YoloFile::file(self : YoloFile) -> String

    YoloFile::image_id

    fn YoloFile::image_id(self : YoloFile) -> Int

    YoloManifest

    pub struct YoloManifest {
    category_ids : Array[Int]
    images : Array[YoloFile]
    }

    Class mapping and ordered per-image prediction files.

    YoloManifest::category_ids

    fn YoloManifest::category_ids(self : YoloManifest) -> Array[Int]

    YoloManifest::images

    fn YoloManifest::images(self : YoloManifest) -> Array[YoloFile]

    compare_mask_predictions

    fn compare_mask_predictions(dataset : MaskDataset, baseline : Array[MaskDetection], candidate : Array[MaskDetection]) -> ComparisonReport raise EvalError

    compare_mask_predictions_with_config

    fn compare_mask_predictions_with_config(dataset : MaskDataset, baseline : Array[MaskDetection], candidate : Array[MaskDetection], config : EvalConfig, max_iou_cache_pairs? : Int) -> ComparisonReport raise EvalError

    Both evaluations use the same per-category cache budget, sequentially.

    compare_predictions

    fn compare_predictions(dataset : Dataset, baseline : Array[Detection], candidate : Array[Detection]) -> ComparisonReport raise EvalError

    Compare two prediction arrays against exactly the same ground truths.

    compare_predictions_with_config

    fn compare_predictions_with_config(dataset : Dataset, baseline : Array[Detection], candidate : Array[Detection], config : EvalConfig) -> ComparisonReport raise EvalError

    Compare with a shared IoU/diagnostic/ignore configuration.

    evaluate

    fn evaluate(dataset : Dataset, predictions : Array[Detection]) -> EvaluationReport raise EvalError

    Evaluate continuous-coordinate bounding boxes with default COCO thresholds.

    evaluate_masks

    fn evaluate_masks(dataset : MaskDataset, predictions : Array[MaskDetection]) -> EvaluationReport raise EvalError

    COCO segm defaults: ten IoUs, three area bins, maxDets 1/10/100, 101 recall points.

    evaluate_masks_with_config

    fn evaluate_masks_with_config(dataset : MaskDataset, predictions : Array[MaskDetection], config : EvalConfig, max_iou_cache_pairs? : Int) -> EvaluationReport raise EvalError

    Optional diagnostics and PR curves use mask overlap. COCO default ignore handling uses iscrowd; respect_explicit remains an opt-in extension. max_iou_cache_pairs bounds cached DT/GT pairs per category, in [0,16777216]. Zero disables caching. Oversized matrices use exact on-demand overlap; this execution setting does not bound total process memory or change metrics.

    evaluate_with_config

    fn evaluate_with_config(dataset : Dataset, predictions : Array[Detection], config : EvalConfig) -> EvaluationReport raise EvalError

    Evaluate with custom AP/AR IoU thresholds. Area bins and maxDets remain COCO defaults.

    evaluate_with_diagnostics

    fn evaluate_with_diagnostics(dataset : Dataset, predictions : Array[Detection]) -> EvaluationReport raise EvalError

    Evaluate and retain per-detection decisions for the default IoU thresholds.

    parse_coco_dataset

    fn parse_coco_dataset(source : String) -> Dataset raise EvalError

    Parse the image, category, and annotation fields of a COCO ground-truth file.

    parse_coco_detections

    fn parse_coco_detections(source : String) -> Array[Detection] raise EvalError

    Parse standard COCO detections; their stable IDs are their zero-based positions.

    parse_coco_mask_dataset

    fn parse_coco_mask_dataset(source : String) -> MaskDataset raise EvalError

    Reads compressed/uncompressed RLE or polygon-ring annotations. bbox is not required and does not affect mask scoring. Annotation area is kept.

    parse_coco_mask_detections

    fn parse_coco_mask_detections(source : String) -> Array[MaskDetection] raise EvalError

    Stable detection IDs are zero-based input positions. bbox and supplied area fields are ignored: segmentation pixel area controls unmatched area filtering. This original entry point accepts RLE only. For polygons, use the dataset-aware entry point below so canvas size comes from the declared image.

    parse_coco_mask_detections_with_dataset

    fn parse_coco_mask_detections_with_dataset(source : String, dataset : MaskDataset) -> Array[MaskDetection] raise EvalError

    Reads polygon or RLE predictions, validating references and canvas sizes against the same ground-truth dataset used for evaluation.

    parse_coco_rle

    fn parse_coco_rle(source : String) -> CocoRle raise EvalError

    Parses one COCO segmentation RLE object: size is [height, width] and counts is either an integer run array or a compressed string. Polygons are rejected. Size and array counts must decode to nonnegative Int-sized integer values.

    parse_coco_segmentation

    fn parse_coco_segmentation(source : String, height : Int, width : Int) -> CocoRle raise EvalError

    Parses a COCO segmentation RLE object or array of polygon rings, using the declared image dimensions. RLE sizes must agree; polygon rings are unioned.

    parse_yolo_detections

    fn parse_yolo_detections(source : String, dataset : Dataset, image_id : Int, category_ids : Array[Int], first_detection_id? : Int) -> Array[Detection] raise EvalError

    Parse six-column YOLO prediction TXT for one image: class_index center_x center_y width height confidence. Coordinates are normalized; category_ids maps zero-based YOLO class indices to category IDs in the dataset. Blank lines are ignored. IDs are assigned from first_detection_id in row order, excluding blank lines. Derived boxes may cross image bounds and are reported by evaluation warnings.

    parse_yolo_manifest

    fn parse_yolo_manifest(source : String, dataset : Dataset) -> YoloManifest raise EvalError

    Parse a multi-image YOLO manifest against a ground-truth dataset. Relative paths use forward slashes; . and .. segments are rejected.