# .memory — состояние пайплайна control_test Живая справка по замкнутому контуру «поток → CV → механика». Обновлять при изменении конфигурации или при появлении нового замеренного факта. Здесь только то, что **измерено**; предположения помечены отдельно. Последнее обновление: 2026-08-01. --- ## 1. Что сейчас работает **Полный прогон с кинематикой и CV — `run_sorting_cv.py` в паре с `cv_worker.py`.** Класс товара приходит от стереопайплайна **во время движения**, а не из разметки. Пушер и плуг реагируют физически на предсказанный класс. Разметка используется только для подсчёта ошибки в конце. Прежний `run_pipeline.py` (классы из `labels.json`, без CV) остаётся рабочим и нужен как контроль: он разделяет ошибки механики и ошибки распознавания. ### Почему два процесса torch внутри Isaac роняет процесс. Поэтому CV живёт отдельно, обмен через каталог: ``` run_sorting_cv.py (в Isaac, без torch) cv_worker.py (отдельный процесс, torch+GPU) поток 700 мм при 1 м/с ворота x = -0.750 -> 6 кадров ──заявка──> runtime/req/<товар>.json DEFOM vitl / вход 480 / iters 24 облако -> габариты -> k -> класс класс <──ответ── runtime/res/<товар>.json пушер: класс D -> Cell.stroke() плуг: класс B/C -> Plow.target(-16 / +16) ``` ### Запуск ```bash # 1. работник CV (прогрев ~90 с: грузится vitl-энкодер) cd /home/dasha/robozon-sorter/control_test nohup /home/whatevenif/isaacsim/python.sh cv_worker.py > /tmp/cvworker.log 2>&1 & # 2. УБЕДИТЬСЯ ПО PID, а не по файлу runtime/worker_ready pgrep -af "python.*cv_worker.py" # 3. открыть сцену заново (cell.prepare меняет её состояние), затем прогон cd /home/dasha/robozon-sorter python3 isaacsim_send.py --context cvsort --timeout 2580 --execution-timeout 2560 \ --file control_test/run_sorting_cv.py ``` --- ## 2. Зафиксированный бейзлайн CV В `measure_plane.py` значения по умолчанию: | параметр | значение | |---|---| | стереодвижок | **DEFOM-Stereo vitl** (`STEREO=defom`) | | вход сети | **480** px по ширине (`SW=480`) | | итерации | **24** + scale_iters 8 | | окно | **кроп зоны осмотра** (`CROP=1`) | | сегментация | **не используется** | Товар отделяется от полотна превышением над плоскостью (порог 20 мм) плюс отсев по плотности; сегментация не участвует вовсе. **Замер на статичных кадрах потока 700 мм (9 товаров):** классы 8/9 = 89 %, габариты MAE медиана 32.8 мм, 469 мс на товар при такте 700 мс. Веса: `/home/dasha/isaac_assets/cv/defom-stereo/checkpoints/` — `defomstereo_vitl_sceneflow.pth` (1.53 ГБ), `defomstereo_vits_sceneflow.pth` (173 МБ), энкодер `depth_anything_v2_vitl.pth` (1.34 ГБ). ### Сравнение движков на том же потоке | конфигурация | MAE медиана | классы | время | в такт 700 мс | |---|---|---|---|---| | **DEFOM vitl, вход 480, iters 24** | 32.8 мм | **8/9 = 89 %** | **469 мс** | да, +231 мс | | DEFOM vits, вход 640, iters 24 | 33.9 мм | 7/9 | 502 мс | да | | DEFOM vitl, вход 640, iters 12 | 37.5 мм | 6/9 | 627 мс | да | | DEFOM vitl, вход 640, iters 24 | 29.9 мм | 7/9 | 735 мс | нет, −35 мс | | CRE, кроп, вход 640 | **23.5 мм** | 7/9 | 551 мс | да | | CRE, кроп, исходный вход | 25.6 мм | 7/9 | 1366 мс | нет | | CRE, вся лента, исходный вход | 32.4 мм | 7/9 | 1701 мс | нет | | FastSAM + CRE | 40.0 мм | 5/9 | 753 мс | нет | | yolo26n-seg + CRE | 41.2 мм | 2/6 | 238 мс | да | | FoundationStereo (только CPU) | 152.8 мм | 4/9 | 7159 мс | нет | **CRE точнее по габаритам (23.5 против 32.8), DEFOM лучше по классам (8/9 против 7/9).** --- ## 3. Кинематика ### Ленты — 1.0 м/с Семь дорожек, привод через `PhysxSurfaceVelocityAPI`. Скорость задаётся **в локальной системе тела**, а дорожки уложены по-разному: у `_04` и `_06` локальный +X смотрит в мировой −X. Направление выводится из мировой цели, величина делится на то, сколько мирового стоит одна локальная единица (у `_06` масштаб 0.5). `ConveyorTrack_06` — **криволинейный** угол на 90°, четверть кольца с центром (−8.005, +1.042), радиусы 0.517…1.018. Линейный привод уводил товар в пустую середину кольца; направление задаётся хордой, сохраняющей радиус, и в мировых координатах. Проверено: товар 160 мм проходит **7.93 м за 7.9 с ровно на 1.00 м/с** без замедлений; поток из 9 товаров проходит всю линию 10.9 м. ### Пушер — класс D Нож двигается **записью трансформа** (`Cell.blade_to`), его призматический сустав **выключен**: иначе сустав тянет нож к своей цели, пока скрипт пишет его в другое место, и нож дрожит весь прогон. Ход 720 мм, срабатывание у `PUSH_X = -3.900`. ### Плуг — классы B и C Лезвие **кинематическое**, шарнир **выключен** (`physics:jointEnabled=False`), угол пишется напрямую (`Plow.target`). Силовой привод перенастраивали трижды и он не держал: звенел на ±21.4° быстрее, чем его успевала вести команда. Углы: `PLOW_PRESET = {"B": -16.0, "C": +16.0, "D": 0.0}`. Положительный поворот отклоняет в −Y. Лезвие ставится **заранее**, до подхода товара. Скольжение товара вдоль кромки измерено: 315–350 мм, то есть товар ведётся, а не отбрасывается ударом. ### Камеры — E60 Шесть камер, три стереопары, все на 700 мм над лентой, возвышение 60°, дистанция 808 мм, азимуты 90° / 208.3° / 330°. Точка осмотра (−0.750, 0.0, 1.781). Базы: D435 73.5 мм, Gemini305 26.5 мм, Gemini345 129.4 мм. Цена глубины соответственно 13.2 / 37.2 / 8.1 мм на пиксель диспаратности. **Расхождение с реальным стендом:** у настоящего D435 база **50.0 мм** (прочитано из прошивки), а в симуляции 73.5 — в 1.47 раза больше. Значит результаты симуляции для этого рига оптимистичнее реальности примерно в полтора раза. Не исправлено сознательно. --- ## 4. Замер последнего прогона замкнутого контура **Классов получено 7 из 9, верно 5 из 7.** Верно: `bag` (D), `backpack` (C), `lunchbox` (B), `detergent` (B), `box_400x400x300` (C). **Задержка от ворот до класса — медиана 0.70 с** при 3.15 с до пушера и 7.10 с до плуга. Инференс 631–1284 мс. Запас четырёх- и десятикратный. Пушер сработал по классу D от камер. Плуг предпозиционировался шесть раз. **Сквозная доставка: в контейнеры попало 5 из 9, в СВОЙ контейнер - 3 из 9.** | причина потери | сколько | что именно | |---|---|---| | ошибка CV | 2 | bucket (D->C), box_300x200x200 (B->C) | | класс верный, механика не довела | 1 | bag - пушер сработал, товар остался в лотке B вместо BinD | | столкновение на входе | 2 | helmet и pillow, выпущены подряд | | бросок пушера | 1 | detergent улетел на (+262, +2085) | | плуг сдвинул недостаточно | 1 | box_400x400x300 на y = -1.49, за краем лотка C | Из четырёх потерь по механике ни одна не связана с распознаванием. Подробная таблица с координатами и зонами лотков - в README, раздел 10.6. Сквозную доставку осмысленно мерить на шаге 1.4 м (там прежний прогон давал 9/9), а шаг 0.7 м использовать для замера классификации и габаритов. --- ## 5. Проблемы, которые сейчас есть ### Не решены 1. **Класс D берётся неустойчиво.** `bucket` (истинный k = 0.995) не определяется ни одной конфигурацией. На **эталонной геометрии меша** та же функция даёт 0.934, на нашем облаке 0.66 — разрыв целиком в качестве облака, не в метрике. Габарит ведра выходит вытянутым (333 × 239 при истинных 287 × 287), а вытянутое сечение высокого k дать не может. 2. **Габариты в движении хуже статичных.** `box_300x200x200` в потоке дал 513 × 452 против 311 × 218 на статичных кадрах и из-за этого ушёл в C вместо B. Причина видна в логе: `detergent` попал на ворота уже на x = −1.708, то есть **мимо точки осмотра**, а кроп привязан к неподвижной точке (−0.750). Товар в кадре смещён, в кроп попадает соседний. 3. **Два товара не доехали до ворот.** `helmet` встал на x = +4.02, `pillow` на +1.15. Оба выпускались подряд (2.15 и 2.85 с); при их габаритах (354 и 455 мм) шаг 700 мм оставляет мало зазора, и они, судя по позициям, столкнулись у входа. 4. **Пушер выбрасывает товар.** `detergent` закончил на (+262, +2085) — улетел на километры. Скорость ножа на пределе: `PUSHER_MAX_SAFE = 2.5` м/с с пометкой «выше ~2.5 м/с кинематический нож сбрасывает товар с линии». 5. **Шаг 700 мм механически не даёт B/C.** Лезвие плуга 0.63 м, на смену угла остаётся 0.07 м (10 % шага). Замерено: при 1.4 м — 9/9, при 0.7 м — 5/9. От скорости ленты не зависит: доля занятости лезвия = 0.63/0.70 = 90 %. Нужен шаг > ~0.95 м либо другой отводящий орган. 6. **Очень тонкие товары проходят под лезвием плуга.** `watch` (4.2 мм) класса C проехал мимо: класс определяется верно, механика — нет. ### Проверено и НЕ помогло Каждый пункт — отдельный замер, все ухудшили результат: | попытка | результат | |---|---| | выбор маски по плоскости ленты вместо воротного пикселя | MAE 39.9 (было 40.0), классы 4/9 (было 5/9) | | k подгонкой окружности P10/P90 | подняло k без разбора формы: коробки пошли в D | | k по трём **мировым** сечениям | классы 6/9 (было 7/9) | | сглаживание контура по угловым секторам | k макс 0.67 (было 0.85), классы 6/9 | | проверка лево-право 0.5 / 1.0 / 1.5 px | отсеивает 28–52 % пикселей, MAE 34.6 (было 32.4), время ×2 | | подгонка цилиндра RANSAC | не сработала ни на одном товаре: доля точек в допуске < 60 % | | отбраковка ракурса по центроиду, порог 60 мм | MAE 65.4 (было 49.5) — откидывала два вида из трёх | | ICP/RANSAC-совмещение облаков | 11.9 → 40.5 мм, три ракурса видят разные поверхности | **Общий вывод из этой серии:** у нас не выбросы, а **дырки в диспаратности**. Любая правка, которая *вычитает* точки (сглаживание, лево-право, отбраковка), делает хуже. Помогает то, что *повышает плотность* или *уменьшает область поиска*: кроп зоны осмотра (MAE 32.4 → 25.6) и понижение входа сети (25.6 → 23.5). Недоделанная половина рецепта лево-право: заполнение мелких внутренних дырок и edge-aware фильтр с запретом интерполяции через границу. Сейчас реализовано только удаление. ### Установлено, что НЕ виновато - **Калибровка камер.** Восстановленное полотно садится на эталонную плоскость со смещением 0.59 / 0.56 / 1.13 мм и наклоном 0.56° / 0.35° / 1.72° по трём ригам. Ни интринсики, ни боковое расположение, ни положение виртуальных камер не при чём. - **Стереодвижок как таковой.** CRE проверен против штатной глубины RealSense на физическом стенде: отношение 0.998 и 1.000 на 249 тыс. пикселей. - **Покрытие ракурсами.** Дуга сечения у ведра покрыта на 295–360°. --- ## 6. Ловушки, на которых уже теряли время Каждая давала правдоподобный, но неверный результат. 1. **Узлы OmniGraph надо УДАЛЯТЬ, а не деактивировать.** `SetActive(False)` убирает прем из обхода, но собранный граф продолжает работать: `ConveyorBeltGraph` обнулял `surfaceVelocity` за 5 шагов после `play`, `DiverterAnimGraph` останавливал таймлайн. 2. **Невидимость не убирает коллайдер.** `capture_roi.py` прятал снятый товар через `MakeInvisible()`, и после двух прогонов захвата в точке осмотра стояло **18 невидимых, но твёрдых предметов**. Поток вставал на них «посреди ConveyorTrack_02». Лечится `cell.clear_capture_parks()`, вызывается в `prepare()`. 3. **`BBoxCache` во время прогона врёт.** Он читает авторские трансформы из слоя USD, а физика пишет в Fabric. Отчёт показывал, что все товары стоят в точках выпуска, хотя таймлайн отработал 26 с. Положения читать через `RigidPrim.get_world_poses()`. 4. **`play()` после `stop()` перематывает в начало** и сбрасывает физику. Обработчик, «возобновляющий» остановившийся таймлайн, обнуляет весь опыт. 5. **Заданная частота физики не применяется.** `timeStepsPerSecond=120` не подействовал, фактический шаг 83.33 мс (60 Гц). Скорости выходили ровно вдвое завышенными. Время брать из таймлайна. 6. **Файл `runtime/worker_ready` остаётся от прошлого запуска** и даёт ложную готовность. Проверять работника по PID. 7. **`cloud_from_roi` возвращает ПАРУ** (облако товара, облако полотна). Складывание кортежа целиком роняет `np.vstack` на разнородных формах. 8. **Меши `items_flow/` уже в каталожном масштабе и уже посажены на z = 0**, коллайдеры в них уже есть. Домасштабирование и свои коллайдеры ломают спавн — товары не едут. 9. **ArUco-метки не видны в ИК** (на физическом стенде): типографская краска на 850 нм в значительной мере прозрачна. Позу брать из цветного кадра с ЦВЕТНЫМИ интринсиками и переводить в систему ИК заводскими экстринсиками. --- ## 7. Что делать дальше — по приоритету 1. **Привязать кроп к товару, а не к неподвижной точке осмотра.** Это лечит проблему 2 — самую вредную из открытых: из-за неё габариты в движении вдвое хуже статичных. 2. **Снизить скорость ножа пушера** — проблема 4, товар улетает. 3. **Разнести выпуск товаров по времени** либо увеличить шаг — проблема 3. 4. Доделать вторую половину фильтрации лево-право (заполнение дырок, edge-aware). 5. FoundationStereo на GPU: заблокировано внешне — `onnxruntime-gpu` требует CUDA 13, на сервере 12.8; колёс под CUDA 12 для python 3.12 нет; `onnx2torch` падает на динамическом `Clip`; TensorRT не установлен. Нужен либо `.pth` через код репозитория, либо CUDA 13 (установка требует прав root).