Сортировочная ячейка 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,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fetch the model weights the pipeline needs. They are far too big for git.
|
||||
|
||||
python scripts/fetch_models.py
|
||||
|
||||
Downloads in small Range chunks with per-chunk retries: the CRE weights come off a slow
|
||||
mirror and a plain single-stream GET truncates silently, leaving an unloadable file.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
MODELS = ROOT / "assets" / "models"
|
||||
CHUNK = 1 << 21
|
||||
TRIES = 12
|
||||
|
||||
SOURCES = {
|
||||
"FastSAM-s.pt":
|
||||
"https://huggingface.co/Ultralytics/FastSAM/resolve/main/FastSAM-s.pt?download=true",
|
||||
# CRE-Stereo ETH3D weights, from the megvii research release mirror
|
||||
"crestereo_eth3d.pth":
|
||||
"https://github.com/ibaiGorordo/CREStereo-Pytorch/releases/download/0.0.1/crestereo_eth3d.pth",
|
||||
}
|
||||
|
||||
CRE_REPO = "https://github.com/ibaiGorordo/CREStereo-Pytorch"
|
||||
|
||||
|
||||
def content_length(url):
|
||||
req = urllib.request.Request(url, method="HEAD")
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
if r.status in (301, 302) and r.headers.get("Location"):
|
||||
return content_length(r.headers["Location"])
|
||||
return int(r.headers.get("Content-Length", 0))
|
||||
|
||||
|
||||
def get_range(url, a, b):
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header("Range", f"bytes={a}-{b}")
|
||||
with urllib.request.urlopen(req, timeout=120) as r:
|
||||
return r.read()
|
||||
|
||||
|
||||
def fetch(name, url, out_dir):
|
||||
out = out_dir / name
|
||||
total = content_length(url)
|
||||
have = out.stat().st_size if out.exists() else 0
|
||||
if total and have == total:
|
||||
print(f" {name}: already complete ({total/1e6:.1f} MB)")
|
||||
return True
|
||||
if not total: # server refuses HEAD: plain download
|
||||
print(f" {name}: streaming (no content-length)")
|
||||
urllib.request.urlretrieve(url, out)
|
||||
return out.exists()
|
||||
if have > total:
|
||||
have = 0
|
||||
mode = "r+b" if have else "wb"
|
||||
pos = have
|
||||
with open(out, mode) as fh:
|
||||
fh.seek(pos)
|
||||
t0 = time.time()
|
||||
while pos < total:
|
||||
end = min(pos + CHUNK - 1, total - 1)
|
||||
for attempt in range(TRIES):
|
||||
try:
|
||||
data = get_range(url, pos, end)
|
||||
if len(data) != end - pos + 1:
|
||||
raise IOError("short chunk")
|
||||
fh.write(data)
|
||||
fh.flush()
|
||||
pos += len(data)
|
||||
pct = 100.0 * pos / total
|
||||
rate = (pos - have) / max(time.time() - t0, 1e-6) / 1024
|
||||
print(f"\r {name}: {pct:5.1f}% {rate:6.0f} KB/s", end="", flush=True)
|
||||
break
|
||||
except Exception as exc:
|
||||
if attempt == TRIES - 1:
|
||||
print(f"\n {name}: FAILED at byte {pos}: {exc}")
|
||||
return False
|
||||
time.sleep(min(2 ** attempt, 20))
|
||||
print()
|
||||
ok = out.stat().st_size == total
|
||||
print(f" {name}: {'ok' if ok else 'SIZE MISMATCH'} ({out.stat().st_size/1e6:.1f} MB)")
|
||||
return ok
|
||||
|
||||
|
||||
def main():
|
||||
MODELS.mkdir(parents=True, exist_ok=True)
|
||||
print(f"fetching model weights into {MODELS}")
|
||||
results = {n: fetch(n, u, MODELS) for n, u in SOURCES.items()}
|
||||
|
||||
cre_dir = MODELS / "crestereo"
|
||||
if not (cre_dir / "nets").exists():
|
||||
print(f"\nCRE-Stereo network code is not vendored here. Clone it next to the weights:")
|
||||
print(f" git clone {CRE_REPO} {cre_dir}")
|
||||
print(" (only the `nets` package is imported)")
|
||||
|
||||
missing = [n for n, ok in results.items() if not ok]
|
||||
if missing:
|
||||
print(f"\nincomplete: {missing} - re-run, downloads resume where they stopped")
|
||||
return 1
|
||||
print("\nall weights present")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user