Сортировочная ячейка Isaac Sim: CV-пайплайн и меши товаров
Замкнутый контур "поток -> 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>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user