Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .claude/skills/mblt-vision/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ description: >-
metric). Load difficult objects as ordinary targets, as Ultralytics' validation does.
- Run YOLOv7 models at `iou_thres: 0.65`, WongKinYiu/yolov7's own `test.py` default.

- ReID is utility-only until published wrappers and routing are added. Reuse
`EmbeddingPost` for dense vectors, explicit float32 mean/std for source preprocessing,
and prepared query/gallery loaders. Preserve ops stable distance ties, camera exclusions
and dataset-specific junk handling; see the ReID API section in `mblt_vision/README.md`.

## Runtime and Packaging

- Route NPU runtime access through mblt-npu-python; do not copy backend classes into Vision.
Expand Down
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,12 @@ The current ownership boundary is deliberate:
any future model needing more than one Hub artifact, rather than duplicating Hub-resolution
logic.

- ReID utilities live in `mblt_vision.person_reid`; concrete published wrappers are separate.
Use `EmbeddingPost` for validated dense vectors and `PersonReIDPost` for dataset binding.
Retrieval protocols and prepared loaders are pinned to ops source in dataset YAMLs; keep
stable distance ties, camera exclusions, junk rules, and preprocessing numeric parity tested.
Named Normalize styles retain legacy arithmetic; explicit mean/std uses source float32 arithmetic.

## Python-First Architecture

- Keep the public layer independent from a particular backend. Define small internal interfaces
Expand Down
3 changes: 2 additions & 1 deletion benchmark/benchmark_vision_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
CORE_MODES: tuple[CoreMode, ...] = cast(
tuple[CoreMode, ...], core_modes_for_target_device("aries-rb")
)
TASK_CHOICES = VISION_TASKS
# Utility-only embedding tasks have no benchmark engine/CLI route yet.
TASK_CHOICES = tuple(task for task in VISION_TASKS if task != "person_reid")
SUPPORTED_TARGET_DEVICES = frozenset({"aries-rb", "regulus-ra", "regulus-rb"})


Expand Down
54 changes: 54 additions & 0 deletions mblt_vision/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,3 +489,57 @@ validation split, following Ultralytics' depth-validation convention.
- Difficult objects count as ordinary targets, as in Ultralytics' validation.

</details>

## Person re-identification utilities

`mblt_vision.person_reid` provides prepared query/gallery loaders, embedding
postprocessing and retrieval scoring. Published model wrappers/HF artifacts are
not added in this stage; no weights or datasets are downloaded by these utilities.
The CLI/benchmark router is unchanged.

```python
from mblt_vision.person_reid import Market1501Dataset, Market1501Evaluator

# root contains query/ and bounding_box_test/; embeddings have one row per sample.
dataset = Market1501Dataset("/path/to/market1501/eval")
# result = Market1501Evaluator().evaluate(embeddings, dataset.samples)
# print(result.as_dict())
```

Also available: `DukeMTMCReIDDataset`, `OccludedDukeDataset` and matching
`DukeMTMCReIDEvaluator`, `OccludedDukeEvaluator`. Loaders emit immutable
`ReIDSample(image, pid, camid, split)` records, sorted query-first then gallery;
cameras are zero-based, identities are not relabeled. Counts are source-checked
by default; caller-owned subsets must explicitly set `validate_counts=False`.
Only Market-1501 drops pid -1 junk; pid 0 background distractors remain.
Duke loaders reject pid -1. Root defaults use `~/.mblt_model_zoo/datasets/<name>/eval`.

`PersonReIDPost(pre_cfg, post_cfg)` extends `EmbeddingPost`; both are exported
from `mblt_vision.person_reid`. Engine postprocessing returns a `Results` object
whose `embeddings` and `output` retain the validated `(N,D)` matrix.
`dataset_ready(root, "person_reid", dataset)` checks source counts/metadata and
decodes every retained query/gallery image (default dataset: Market-1501).
`PersonReIDPost` It validates one
floating-point `(N,D)` output or singleton-spatial NCHW/NHWC tensor, optionally
checks `embedding_dim`, and uses `normalize: true` for row-wise L2 normalization
(zero vectors remain zero). Return type is a float32 torch matrix, never class
probabilities. An explicit `post_cfg.dataset` is required. Processing-only base
profiles in `models/bases/person_reid.yaml` can be selected using `extends`;
concrete models must supply their own HF `file_cfg`, dataset and embedding dimension.
`Normalize` accepts either a historical named style or explicit three-element
`mean` and `std` lists; named styles retain unchanged legacy arithmetic.

`extract_embeddings(batches, infer, postprocess)` runs prepared batches through
the shared bounded inference pipeline, preserving order. Evaluators consume
these embeddings without adding normalization, camera inputs, re-ranking or flip
fusion. Same-ID/same-camera gallery rows are excluded; unmatched queries are
skipped. Squared float32 Euclidean distances use stable ties in gallery order.
Ranks beyond the gallery saturate; mAP is primary, rank-1/5/10 secondary, all
ratios. `as_dict()` rounds to five decimals like ops; raw `mean_ap`/`cmc` remain
unrounded. Dataset YAMLs record exact ops config, loader, evaluator and revision.

Offline tests vendor the inspected ops functions at
`ec537764e36ca10da963fcb7afab157ea60ca022`. Synthetic embeddings require exact
distance/CMC/mAP equality; synthetic RGB preprocessing requires byte equality
(`atol=0`) against the ops adapter for explicit source mean/std profiles. These
are protocol tests, not measured model accuracy claims.
2 changes: 2 additions & 0 deletions mblt_vision/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from . import instance_segmentation as instance_segmentation
from . import obb as obb
from . import object_detection as object_detection
from . import person_reid as person_reid
from . import pose_estimation as pose_estimation
from . import semantic_segmentation as semantic_segmentation
from ._api import list_models as list_models
Expand Down Expand Up @@ -52,6 +53,7 @@
"object_detection",
"obb",
"pose_estimation",
"person_reid",
"semantic_segmentation",
] + sorted(_LEGACY_MODEL_EXPORTS)
# Keep legacy compatibility exports synchronized with their task packages.
Expand Down
1 change: 1 addition & 0 deletions mblt_vision/_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"obb",
"pose_estimation",
"face_detection",
"person_reid",
Comment thread
parkjinman98 marked this conversation as resolved.
Resolved
)


Expand Down
35 changes: 35 additions & 0 deletions mblt_vision/datasets/dukemtmc-reid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: dukemtmc-reid
path: ~/.mblt_model_zoo/datasets/dukemtmc-reid
tasks:
- person_reid
val: eval
cameras: 8
skip_junk: false
retrieval_splits:
query:
directory: query
count: 2228
gallery:
directory: bounding_box_test
count: 17661
source:
repository: https://git.mobilint.com/algorithm-team/testing/mblt-model-ops
revision: ec537764e36ca10da963fcb7afab157ea60ca022
config_path: datasets/dukemtmc_reid/dukemtmc_reid.yaml
name: dukemtmc-reid
dataloader_path: datasets/dukemtmc_reid/_private/dataloader.py
shared_path: datasets/_shared/reid.py
eval_protocol:
id: dukemtmc-reid-reid-map.v1
evaluation:
split: eval/query + eval/bounding_box_test
protocol: dukemtmc-reid-reid-map.v1
primary_metric: map
secondary_metrics:
- rank1
- rank5
- rank10
implementation_reference:
path: datasets/dukemtmc_reid/_private/evaluator.py
shared_path: datasets/_shared/reid.py
revision: ec537764e36ca10da963fcb7afab157ea60ca022
35 changes: 35 additions & 0 deletions mblt_vision/datasets/market1501.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: market1501
path: ~/.mblt_model_zoo/datasets/market1501
tasks:
- person_reid
val: eval
cameras: 6
skip_junk: true
retrieval_splits:
query:
directory: query
count: 3368
gallery:
directory: bounding_box_test
count: 15913
source:
repository: https://git.mobilint.com/algorithm-team/testing/mblt-model-ops
revision: ec537764e36ca10da963fcb7afab157ea60ca022
config_path: datasets/market1501/market1501.yaml
name: market1501
dataloader_path: datasets/market1501/_private/dataloader.py
shared_path: datasets/_shared/reid.py
eval_protocol:
id: market1501-reid-map.v1
evaluation:
split: eval/query + eval/bounding_box_test
protocol: market1501-reid-map.v1
primary_metric: map
secondary_metrics:
- rank1
- rank5
- rank10
implementation_reference:
path: datasets/market1501/_private/evaluator.py
shared_path: datasets/_shared/reid.py
revision: ec537764e36ca10da963fcb7afab157ea60ca022
35 changes: 35 additions & 0 deletions mblt_vision/datasets/occluded-duke.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: occluded-duke
path: ~/.mblt_model_zoo/datasets/occluded-duke
tasks:
- person_reid
val: eval
cameras: 8
skip_junk: false
retrieval_splits:
query:
directory: query
count: 2210
gallery:
directory: bounding_box_test
count: 17661
source:
repository: https://git.mobilint.com/algorithm-team/testing/mblt-model-ops
revision: ec537764e36ca10da963fcb7afab157ea60ca022
config_path: datasets/occluded_duke/occluded_duke.yaml
name: occluded-duke
dataloader_path: datasets/occluded_duke/_private/dataloader.py
shared_path: datasets/_shared/reid.py
eval_protocol:
id: occluded-duke-reid-map.v1
evaluation:
split: eval/query + eval/bounding_box_test
protocol: occluded-duke-reid-map.v1
primary_metric: map
secondary_metrics:
- rank1
- rank5
- rank10
implementation_reference:
path: datasets/occluded_duke/_private/evaluator.py
shared_path: datasets/_shared/reid.py
revision: ec537764e36ca10da963fcb7afab157ea60ca022
26 changes: 26 additions & 0 deletions mblt_vision/models/bases/person_reid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Processing only: published HF artifact identities belong to concrete stage-d2 YAMLs.
# Source: mblt-model-ops ec537764e36ca10da963fcb7afab157ea60ca022
# models/person_reid/CLIP_ReID_RN50_Baseline_DukeMTMC/distribution.yaml
DEFAULT:
pre_cfg:
Reader:
style: pil
Resize:
size: [256, 128]
interpolation: bilinear
Normalize:
mean: [0.485, 0.456, 0.406]
std: [0.229, 0.224, 0.225]
post_cfg:
task: person_reid
dataset: dukemtmc-reid
normalize: true
# Inherit this processing profile and override dataset/dimension/artifacts per model.
MARKET1501:
extends: person_reid.yaml#DEFAULT
post_cfg:
dataset: market1501
OCCLUDED_DUKE:
extends: person_reid.yaml#DEFAULT
post_cfg:
dataset: occluded-duke
34 changes: 34 additions & 0 deletions mblt_vision/person_reid/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Person retrieval utilities; published model wrappers are added separately."""

from ..utils.datasets.reid import (
DukeMTMCReIDDataset,
Market1501Dataset,
OccludedDukeDataset,
ReIDDataset,
ReIDSample,
)
from ..utils.evaluation.retrieval import (
DukeMTMCReIDEvaluator,
Market1501Evaluator,
OccludedDukeEvaluator,
RetrievalEvaluator,
RetrievalResult,
extract_embeddings,
)
from ..utils.postprocess.embedding_post import EmbeddingPost, PersonReIDPost

__all__ = [
"DukeMTMCReIDDataset",
"DukeMTMCReIDEvaluator",
"EmbeddingPost",
"Market1501Dataset",
"Market1501Evaluator",
"OccludedDukeDataset",
"OccludedDukeEvaluator",
"PersonReIDPost",
"ReIDDataset",
"ReIDSample",
"RetrievalEvaluator",
"RetrievalResult",
"extract_embeddings",
]
26 changes: 26 additions & 0 deletions mblt_vision/utils/datasets/readiness.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from ..._tasks import normalize_vision_task
from ...datasets import get_dataset_category_ids
from .cityscapes import CITYSCAPES_SOURCE_TO_TRAIN_ID
from .reid import DukeMTMCReIDDataset, Market1501Dataset, OccludedDukeDataset

IMAGE_SUFFIXES = {".bmp", ".jpeg", ".jpg", ".png", ".tif", ".tiff", ".webp"}
IMAGENET_CLASS_COUNT = 1000
Expand Down Expand Up @@ -1044,6 +1045,29 @@ def dense_dataset_ready(data_path: str | Path, dataset: str) -> bool:
return False


def _reid_ready(root: Path, dataset: str) -> bool:
"""Require source counts, camera/name metadata and decodable query/gallery crops."""
loader = {
"market1501": Market1501Dataset,
"dukemtmc-reid": DukeMTMCReIDDataset,
"occluded-duke": OccludedDukeDataset,
}.get(dataset)
if loader is None or _path_has_symlink_component(root):
return False
try:
samples = loader(root).samples
for sample in samples:
if not sample.image.is_file() or _path_has_symlink_component(sample.image):
return False
with Image.open(sample.image) as image:
image.load()
if min(image.size) <= 0:
return False
except (OSError, ValueError):
return False
return True


def dataset_ready(data_path: str | Path, task: str, dataset: str | None = None) -> bool:
"""Return whether an organized dataset matches its task, taxonomy, and full validation split.

Expand All @@ -1069,6 +1093,8 @@ def dataset_ready(data_path: str | Path, task: str, dataset: str | None = None)
}.get(normalized_task)
normalized_dataset = (dataset or expected_dataset or "").lower()

if normalized_task == "person_reid":
return _reid_ready(root, normalized_dataset or "market1501")
if normalized_task == "semantic_segmentation":
return dense_dataset_ready(root, normalized_dataset or "ade20k")
if expected_dataset is None or normalized_dataset != expected_dataset:
Expand Down
Loading
Loading