0d32f32db0
Замкнутый контур "поток -> CV -> механика": товары идут по конвейеру с шагом 700 мм, класс определяется стереопайплайном во время движения, пушер и плуг реагируют физически. Состав: * control_test/ - ячейка и CV. run_sorting_cv.py + cv_worker.py (два процесса, потому что torch внутри Isaac роняет сцену), cell.py (физика лент, плуга, пушера), measure_plane.py (замер габаритов), README.md и .memory.md с замерами, проблемами и ловушками * robozon_sorter/ - модули симуляции, scripts/ - утилиты, scene/ - сцены * assets/ - меши товаров, плуг, объекты Objaverse Бейзлайн CV: DEFOM-Stereo vitl, вход 480, iters 24, кроп зоны осмотра, без сегментации. На потоке 700 мм - классы 8/9, габариты MAE 32.8 мм, 469 мс на товар при такте 700 мс. Веса моделей (4.5 ГБ) и пропсы конвейера NVIDIA (274 МБ) не включены - источники и команды скачивания в MODELS.md. Выход прогонов (captures/, runtime/) не включён: воспроизводится. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
86 lines
3.5 KiB
Python
86 lines
3.5 KiB
Python
"""Item library for control_test: meshes are DISCOVERED in items/, classes are READ from
|
|
items/labels.json.
|
|
|
|
No geometric auto-measurement. Dimensions and k are taken from the labelling, which is the
|
|
ground truth this cell is verified against - measuring them from the mesh was tried and
|
|
the roundness estimate under-read handled/hollow bodies (bucket 0.737 vs 0.995, mug 0.731
|
|
vs 0.985), i.e. class D silently became B. Reading the label removes that whole class of
|
|
error from the mechanics test.
|
|
|
|
The folder is still the source of items: drop a .usd in, add one line to labels.json, and
|
|
it joins the next run. Anything in the folder without a label is reported and skipped
|
|
rather than guessed at.
|
|
|
|
The documented rules are kept in `classify()` so a labelling can be checked for internal
|
|
consistency (`verify_labels()`), not to derive it:
|
|
|
|
D "не подходит без доупаковки" габариты как у B, но k > 0.8 хотя бы в одном сечении
|
|
C "не подходит по габаритам" любой размер < 10 мм ИЛИ не влезает в 450x320x320 мм.
|
|
Форма не важна.
|
|
B "подходит для сортировки" всё от 10x10x10 до 450x320x320 мм и k <= 0.8
|
|
|
|
Fit is tested with the item's extents and the envelope both sorted descending - a parcel
|
|
may be presented on any face.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import pathlib
|
|
|
|
ENVELOPE_MM = sorted((450.0, 320.0, 320.0), reverse=True)
|
|
MIN_DIM_MM = 10.0
|
|
K_THRESHOLD = 0.8
|
|
|
|
LABELS_FILE = "labels.json"
|
|
|
|
|
|
def classify(dims_mm, k):
|
|
"""the documented decision, D checked first"""
|
|
d = sorted(dims_mm, reverse=True)
|
|
undersize = min(d) < MIN_DIM_MM
|
|
fits = all(a <= b + 1e-9 for a, b in zip(d, ENVELOPE_MM))
|
|
if undersize or not fits:
|
|
return "C" # shape irrelevant
|
|
return "D" if k > K_THRESHOLD else "B"
|
|
|
|
|
|
def load_library(items_dir):
|
|
"""every .usd in items_dir, sorted, paired with its label.
|
|
|
|
Returns dicts with name/path/cls/dims_mm/k, or name/path/error for meshes that have
|
|
no entry in labels.json - those are skipped by the runner, never guessed.
|
|
"""
|
|
items_dir = pathlib.Path(items_dir)
|
|
labels_path = items_dir / LABELS_FILE
|
|
if not labels_path.exists():
|
|
raise FileNotFoundError(f"{labels_path} missing - the item classes live there")
|
|
labels = json.loads(labels_path.read_text())
|
|
|
|
out = []
|
|
for f in sorted(items_dir.glob("*.usd")):
|
|
rec = labels.get(f.stem)
|
|
if rec is None:
|
|
out.append(dict(name=f.stem, path=str(f),
|
|
error="no entry in labels.json"))
|
|
continue
|
|
out.append(dict(name=f.stem, path=str(f), cls=rec["zone"],
|
|
dims_mm=rec.get("dims_mm"), k=rec.get("k")))
|
|
return out
|
|
|
|
|
|
def verify_labels(items_dir):
|
|
"""check each label against the documented rules; returns the rows that disagree.
|
|
|
|
A label whose own dims/k imply a different class is a labelling bug, and it would
|
|
otherwise show up as a mysterious mechanical failure.
|
|
"""
|
|
bad = []
|
|
for r in load_library(items_dir):
|
|
if "error" in r or r.get("dims_mm") is None or r.get("k") is None:
|
|
continue
|
|
implied = classify(r["dims_mm"], r["k"])
|
|
if implied != r["cls"]:
|
|
bad.append(dict(name=r["name"], labelled=r["cls"], implied=implied,
|
|
dims_mm=r["dims_mm"], k=r["k"]))
|
|
return bad
|