YORU-Explain Why a YORU detection scores as it does

User guide

Python API

Everything the window does, without the window. The GUI calls these same functions, so a script and a click give the same files.

The workflow in a script

from yoru_explain import api

adapter = api.load_model("best.pt", device="auto")      # any YORU model; backend as YORU picks it
project = api.Project.open_or_create("my_project")
src = project.add_source("video.mp4")
mdl = project.add_model("best.pt", adapter.name)
ev = api.add_event(project, adapter, mdl.model_id, src["source_id"], frame_index=6450, detection_index=0)
project.save()
outcome = api.run_analysis(project, adapter, mdl.model_id, [ev], api.AnalysisRequest("occlusion"))
print(outcome.status, outcome.identity_check)

outcome.run_dir is the run folder, laid out as in Output files; the project now lists the run, and the window shows it.

Loading a model

adapter = api.load_model("best.pt", backend="auto", device="auto",
                         conf_thresh=0.25, iou_thresh=0.45)
Argument Meaning
backend A YORU backend: auto (what YORU’s get_detector("auto", ...) would pick), yolov5, ultralytics, rtdetr, torchvision, onnx
device auto, cuda or cpu
black_box=True Explain YORU’s detect() only: re-detection, no Grad-CAM, for any model YORU loads
conf_thresh, iou_thresh As YORU applies them
rgb_input YOLOv5 only: None follows YORU’s rule, False BGR (YORU v1), True RGB

adapter.capabilities says what the model offers: family, candidate_kind, identity_modes, score_types, gradients and its reason, batch_invariant, warnings. api.load_adapter(name, weights, ...) loads a named adapter directly, e.g. api.load_adapter("toy-grid", device="cpu") for the synthetic detector.

Settings

Settings are dataclasses with to_dict() / from_dict(), so they can be kept in JSON; from_dict refuses unknown keys. Every field has the default the window shows.

request = api.AnalysisRequest(
    method="occlusion",                       # or "gradcam"
    occlusion=api.OcclusionConfig(
        patch_size=(32, 32), stride=(16, 16),
        baseline=api.BaselineSpec(kind="constant", value=(180, 105, 255)),   # B,G,R: hot pink
        region="context", context_scale=3.0,
        score=api.ScoreDefinition(score_type="confidence", identity_mode="fixed_candidate",
                                  iou_threshold=0.5),
        batch_size=1, seed=0, include_ambiguous=False),
    gradcam=api.GradCamConfig(layer="auto", score_type="confidence", precision="inference"),
    verify_identity=True, use_cache=True, write_previews=True)

A run’s config.json holds its request: api.AnalysisRequest.from_dict(config["request"]) re-creates it exactly.

Events from a YORU CSV

added = api.add_events_from_detections_csv(project, src["source_id"], "video_detections.csv",
                                           model_id=mdl.model_id, class_names=["copulation"],
                                           limit=100)
project.save()

Such events carry neither a pre-NMS candidate nor a frame hash. The run decodes the frame, runs the detector and must find each box again (same class, IoU ≥ 0.9), or reports target_not_reproduced.

Reading a run

reader = api.RunReader(outcome.run_dir)
for row in reader.typed_results():            # results.csv, numbers as numbers, empty cells as None
    print(row["event_id"], row["status"], row["original_score"], row["delta_score"])

row = reader.result(ev)
maps = reader.load_map(row["map_file"])       # the NPZ arrays, plus "meta"
delta = maps["delta_map"]                     # float32 H x W, NaN where no patch was counted
patches = reader.perturbations(ev)            # perturbations.csv rows of this event

The NPZ files open with NumPy alone as well: numpy.load(path, allow_pickle=False).

One frame, no files

api.explain(adapter, image, target, request) runs one method on one image in memory and returns the result object (the map, the patches, the controls), writing nothing. api.target_from_detection(detection, index, image) makes the target from a detection of adapter.detect(image).

Example scripts

The repository’s examples/ folder has three complete scripts; each has a CONFIG at the top to edit.

Script Does
synthetic_occlusion.py Occlusion and Grad-CAM on the synthetic detector, where the answer is known. No weights or video needed.
yolov5_video_event.py One YOLOv5 detection on one video frame: occlusion and Grad-CAM, saved as a project
any_model_image.py One detection of a model of any YORU backend on one image
uv run python examples/synthetic_occlusion.py

There is deliberately no command-line interface: settings live in dataclasses and JSON, and yoru_explain.app.main() takes its options as function arguments.