600 lines
30 KiB
Markdown
Executable file
600 lines
30 KiB
Markdown
Executable file
# Контекст проекта YOLO Train WebUI
|
||
|
||
Дата актуализации: 2026-07-18
|
||
|
||
Этот файл — основной технический контекст проекта для разработчиков и агентов.
|
||
История найденных и исправленных дефектов находится в `PROJECT_ISSUES.md`.
|
||
|
||
## 1. Назначение и границы проекта
|
||
|
||
YOLO Train WebUI — локальное веб-приложение для настройки и запуска обучения
|
||
Ultralytics YOLO. Оно предоставляет форму конфигурации, профили запусков, live-логи,
|
||
прогресс по эпохам, графики метрик, мягкую остановку и интеграцию с MLflow.
|
||
|
||
Поддерживаемые задачи:
|
||
|
||
- `detect` — детекция объектов;
|
||
- `segment` — сегментация;
|
||
- `classify` — классификация;
|
||
- `pose` — оценка поз;
|
||
- `obb` — ориентированные bounding boxes.
|
||
|
||
Приложение рассчитано на локального доверенного пользователя и один активный запуск
|
||
обучения. Это не многопользовательская платформа, не планировщик задач и не сервис
|
||
хранения датасетов. В нём нет встроенных учётных записей, ролей или аутентификации.
|
||
|
||
## 2. Технологии
|
||
|
||
| Область | Технология |
|
||
|---|---|
|
||
| Backend/API | Python 3.11+, FastAPI, Uvicorn |
|
||
| Обучение | Ultralytics YOLO, PyTorch |
|
||
| Эксперименты | MLflow |
|
||
| Frontend | HTML, CSS, vanilla JavaScript |
|
||
| Графики | Chart.js из CDN |
|
||
| Real-time | WebSocket |
|
||
| Зависимости | `uv`, frozen-набор в `uv.lock` |
|
||
| Упаковка | Hatchling |
|
||
| Тесты | pytest, FastAPI TestClient/httpx, Node.js smoke-test |
|
||
| Контейнер | Docker, Docker Compose |
|
||
|
||
Основные зависимости объявлены в `pyproject.toml`: `fastapi`, `uvicorn`,
|
||
`websockets`, `ultralytics`, `mlflow`. Dev-группа содержит `pytest` и `httpx`.
|
||
Python package называется `yolo-train-webui`, текущая версия — `0.1.0`; wheel
|
||
собирается Hatchling только из `src/yolo_webui`.
|
||
|
||
`yolo_webui.__init__` публично экспортирует `TrainingConfig`, `TrainingEvent` и
|
||
`TrainingRunner`.
|
||
|
||
## 3. Структура репозитория
|
||
|
||
```text
|
||
.
|
||
├── .agents/
|
||
│ ├── PROJECT_CONTEXT.md # этот технический контекст
|
||
│ └── PROJECT_ISSUES.md # аудит и история исправлений
|
||
├── src/yolo_webui/
|
||
│ ├── __init__.py # публичные Python-экспорты
|
||
│ ├── __main__.py # запуск `python -m yolo_webui`
|
||
│ ├── app.py # FastAPI, TrainingManager, REST и WebSocket
|
||
│ ├── config.py # dataclass-конфигурации и валидация
|
||
│ ├── dataset_splitter.py # detection-style train/val splitter
|
||
│ ├── subprocess_runner.py # дочерний процесс обучения и stdout-протокол
|
||
│ ├── trainer.py # Ultralytics callbacks, MLflow, cancellation
|
||
│ └── static/
|
||
│ ├── index.html # форма и панель мониторинга
|
||
│ ├── app.js # browser state, API, WebSocket, Chart.js
|
||
│ └── style.css # всё визуальное оформление
|
||
├── tests/
|
||
│ ├── test_app.py # API и TrainingManager
|
||
│ ├── test_config.py # конфигурация, безопасность, MLflow env
|
||
│ ├── test_splitter.py # классы и разбиение датасета
|
||
│ ├── test_subprocess_runner.py
|
||
│ ├── test_trainer.py # callbacks и остановка
|
||
│ ├── test_frontend.py # запуск Node-проверок из pytest
|
||
│ └── frontend_smoke.js # browser stubs, форма и графики
|
||
├── Dockerfile
|
||
├── docker-compose.yml
|
||
├── pyproject.toml
|
||
├── uv.lock
|
||
└── README.md
|
||
```
|
||
|
||
Рабочие каталоги не входят в Git:
|
||
|
||
- `datasets/` — локальные датасеты;
|
||
- `models/` — локальные веса и YAML моделей;
|
||
- `runs/` — результаты Ultralytics и JSON-профили;
|
||
- `.yolo-webui/` — сгенерированные split-файлы внутри датасетов;
|
||
- `mlflow.db`, `mlruns/`, `mlflow/` — локальные данные MLflow;
|
||
- `.venv/`, кэши Python и pytest.
|
||
|
||
## 4. Архитектура во время выполнения
|
||
|
||
```text
|
||
Browser
|
||
├── HTTP JSON ───────────────┐
|
||
└── WebSocket /api/ws ───────┤
|
||
v
|
||
FastAPI / TrainingManager (основной процесс Uvicorn)
|
||
├── хранит LiveState и WebSocket-клиентов
|
||
├── сохраняет профили в runs/sessions
|
||
└── запускает background thread
|
||
|
|
||
v
|
||
Python subprocess: yolo_webui.subprocess_runner
|
||
├── читает временный JSON config
|
||
├── ставит SIGTERM/SIGINT handlers
|
||
├── создаёт TrainingRunner
|
||
├── запускает Ultralytics YOLO.train()
|
||
└── пишет события, логи и результат в stdout
|
||
|
|
||
├── dataset / generated split
|
||
├── models / official model download
|
||
├── runs / training artifacts
|
||
└── MLflow storage
|
||
```
|
||
|
||
Изоляция обучения в subprocess нужна, чтобы тяжёлый Ultralytics/PyTorch не блокировал
|
||
ASGI event loop, stdout можно было транслировать в браузер, а зависший запуск —
|
||
принудительно завершить.
|
||
|
||
Важная деталь: `TrainingRunner` используется в двух процессах.
|
||
|
||
- В родительском `TrainingManager` он хранит ссылку на subprocess и управляет
|
||
сигналами остановки.
|
||
- В дочернем процессе отдельный экземпляр владеет моделью Ultralytics и выставляет
|
||
`trainer.stop = True`.
|
||
|
||
## 5. Точки входа и запуск
|
||
|
||
CLI entry point из `pyproject.toml`:
|
||
|
||
```bash
|
||
uv run yolo-train-webui
|
||
```
|
||
|
||
Альтернативный модульный запуск:
|
||
|
||
```bash
|
||
uv run -m yolo_webui
|
||
```
|
||
|
||
Оба варианта вызывают `yolo_webui.app:main`. CLI принимает:
|
||
|
||
- `--host`, default `127.0.0.1`;
|
||
- `--port`, default `8000`.
|
||
|
||
Перед запуском Uvicorn устанавливается `MPLBACKEND=Agg`. В Docker приложение слушает
|
||
`0.0.0.0:8000` внутри контейнера, но Compose публикует его только как
|
||
`127.0.0.1:8000` на host.
|
||
|
||
## 6. Backend и состояние обучения
|
||
|
||
### `LiveState`
|
||
|
||
Глобальный `TrainingManager` хранит единственное состояние:
|
||
|
||
- `status`;
|
||
- текущую и общую эпохи;
|
||
- список логов;
|
||
- историю метрик;
|
||
- `output_dir`;
|
||
- `stop_requested`;
|
||
- `last_event_kind` для классификации результата.
|
||
|
||
Состояния:
|
||
|
||
```text
|
||
idle
|
||
└── start -> preparing
|
||
├── event started -> training
|
||
│ ├── normal exit 0 -> succeeded
|
||
│ ├── stop -> stopping -> cancelled
|
||
│ └── error -> failed
|
||
├── stop -> stopping -> cancelled/failed
|
||
└── setup error -> failed
|
||
```
|
||
|
||
`finished` больше не создаётся backend-ом; frontend понимает его только для
|
||
совместимости со старым состоянием. Новый запуск разрешён лишь когда нет активного
|
||
статуса и предыдущий background thread уже завершён.
|
||
|
||
### Потоки и lock
|
||
|
||
`TrainingManager._lock` защищает `LiveState`, ссылку на thread и набор WebSocket.
|
||
Нельзя выполнять `broadcast()` внутри `with self._lock`: broadcast сам читает
|
||
защищённые данные, и повторный захват обычного `threading.Lock` вызовет deadlock.
|
||
|
||
WebSocket привязывается к event loop Uvicorn при подключении. Вызовы broadcast из
|
||
фонового потока передаются через `asyncio.run_coroutine_threadsafe()`. Отправки
|
||
сериализуются `asyncio.Lock`; failed-клиенты логируются и удаляются.
|
||
|
||
### Запуск subprocess
|
||
|
||
`TrainingManager._run_subprocess()`:
|
||
|
||
1. сериализует `TrainingConfig.to_dict()` во временный JSON;
|
||
2. запускает текущий interpreter с `-u -m yolo_webui.subprocess_runner`;
|
||
3. объединяет stderr со stdout;
|
||
4. читает поток посимвольно, различая `\r` и `\n` для progress-строк;
|
||
5. обновляет состояние и транслирует события;
|
||
6. ждёт return code, удаляет временный JSON и очищает ссылку на процесс.
|
||
|
||
### Внутренний stdout-протокол
|
||
|
||
Дочерний процесс печатает специальные маркеры:
|
||
|
||
```text
|
||
__YOLO_WEBUI_READY__
|
||
__YOLO_WEBUI_EVENT__:{"kind":"epoch","message":"...","epoch":1,"total_epochs":100}
|
||
__YOLO_WEBUI_RESULT__:/absolute/path/to/run
|
||
```
|
||
|
||
- `READY` означает, что signal handlers уже установлены и отложенный stop можно
|
||
безопасно доставить.
|
||
- `EVENT` несёт `kind`, `message`, `epoch`, `total_epochs`.
|
||
- `RESULT` передаёт каталог результатов.
|
||
- Любая другая строка считается обычным логом.
|
||
|
||
События от `TrainingRunner`: `info`, `started`, `epoch`, `success`, `cancelled`,
|
||
`warning`. Исключение выводится traceback-ом в stderr/stdout и даёт return code `1`.
|
||
|
||
### Классификация завершения
|
||
|
||
Backend различает:
|
||
|
||
- `succeeded` — return code `0` без подтверждённой остановки;
|
||
- `cancelled` — был stop и subprocess завершился с `0`, прислал `cancelled` или был
|
||
убит force-stop таймером;
|
||
- `failed` — ненулевой код без подтверждённой отмены, в том числе реальная ошибка,
|
||
случившаяся после нажатия Stop.
|
||
|
||
## 7. Остановка обучения
|
||
|
||
Остановка кооперативная и двухуровневая:
|
||
|
||
1. `POST /api/train/stop` ставит `stop_requested` и статус `stopping`.
|
||
2. Если subprocess ещё не прислал `READY`, запрос сохраняется.
|
||
3. После `READY` родитель отправляет `SIGTERM`.
|
||
4. Signal handler дочернего процесса вызывает `TrainingRunner.request_stop()`.
|
||
5. Runner выставляет `trainer.stop = True` сразу либо в ближайшем callback.
|
||
6. Ultralytics штатно завершает callbacks и сохранение результатов.
|
||
7. Если subprocess не завершился за 30 секунд, parent вызывает `kill()`.
|
||
|
||
`prepare_run()` перед каждым новым запуском очищает stop-флаги и старый таймер.
|
||
|
||
## 8. REST и WebSocket API
|
||
|
||
| Метод | Путь | Назначение |
|
||
|---|---|---|
|
||
| GET | `/` | Возвращает `static/index.html` |
|
||
| GET | `/static/*` | CSS и JavaScript |
|
||
| GET | `/api/config/defaults` | Полный default `TrainingConfig` |
|
||
| GET | `/api/datasets` | Верхнеуровневые каталоги и YAML из `datasets/` |
|
||
| GET | `/api/models` | Верхнеуровневые `.pt/.pth/.yaml/.yml` из `models/` |
|
||
| GET | `/api/sessions` | Список профилей без `last_run` |
|
||
| GET | `/api/sessions/{name}` | Загрузить профиль; `last_run` читать можно |
|
||
| POST | `/api/sessions/{name}` | Сохранить произвольный JSON профиля |
|
||
| DELETE | `/api/sessions/{name}` | Удалить профиль |
|
||
| GET | `/api/train/status` | Текущее состояние, эпохи, результат, метрики, логи |
|
||
| POST | `/api/train/start` | Провалидировать config, сохранить `last_run`, запустить |
|
||
| POST | `/api/train/stop` | Запросить остановку |
|
||
| WS | `/api/ws` | Init-снимок и live-события |
|
||
|
||
FastAPI также оставляет включёнными стандартные OpenAPI endpoints: `/docs`,
|
||
`/redoc`, `/openapi.json`.
|
||
|
||
WebSocket server → browser сообщения:
|
||
|
||
- `init`: полный snapshot состояния, логов и метрик при подключении;
|
||
- `status`: новое состояние и опциональный `output_dir`;
|
||
- `log`: `message` и `level`;
|
||
- `progress`: эпоха, total, извлечённые метрики и сообщение.
|
||
|
||
Browser → server сообщения не используются; endpoint только читает и отбрасывает их,
|
||
поддерживая соединение открытым.
|
||
|
||
Профили хранятся в `runs/sessions/{name}.json`. Имя: 1–64 символа из латинских
|
||
букв, цифр, `_`, `-`. `last_run` зарезервирован для автосохранения при старте: его
|
||
можно прочитать, но нельзя создать или удалить через profile endpoints.
|
||
|
||
## 9. Конфигурация обучения
|
||
|
||
### `TrainingConfig`
|
||
|
||
| Поле | Default | Передача в Ultralytics |
|
||
|---|---:|---|
|
||
| `dataset` | обязательно; API default `coco8.yaml` | `data` |
|
||
| `model` | обязательно; API default `yolo11n.pt` | аргумент конструктора `YOLO()` |
|
||
| `task` | `detect` | аргумент конструктора `YOLO()` |
|
||
| `epochs` | `100` | `epochs` |
|
||
| `image_size` | `640` | `imgsz` |
|
||
| `batch_size` | `16` | `batch` |
|
||
| `device` | пусто | `device`, только если задано |
|
||
| `workers` | `8` | `workers`; `0` допустим |
|
||
| `patience` | `100` | `patience`; `0` допустим |
|
||
| `project` | `runs/train` | `project` |
|
||
| `run_name` | пусто | `name`, только если задано |
|
||
| `augmentation` | включена | набор augmentation kwargs |
|
||
| `mlflow` | включён | Ultralytics setting и env |
|
||
| `split` | выключен | preprocessing до `YOLO.train()` |
|
||
|
||
Основная валидация:
|
||
|
||
- `epochs >= 1`, `image_size >= 32`;
|
||
- batch положительный или `-1`; `0` и значения `< -1` запрещены;
|
||
- `workers >= 0`, `patience >= 0`;
|
||
- задача входит в фиксированный список;
|
||
- detection-style auto split запрещён для `classify`;
|
||
- dataset/model/project проходят security path validation.
|
||
|
||
Модель без `/` или `\` считается именем и резолвится как `models/{name}`.
|
||
|
||
### `DatasetSplitConfig`
|
||
|
||
- `enabled=False`;
|
||
- `train_ratio=0.8`, допустимо `0.1…0.95`;
|
||
- `classes_path=""`, пустое значение включает автопоиск.
|
||
|
||
### `MlflowConfig`
|
||
|
||
- `enabled=True`;
|
||
- `tracking_uri="sqlite:///mlflow.db"`;
|
||
- `experiment_name="yolo-webui"`;
|
||
- `run_name=""`.
|
||
|
||
Если MLflow включён, tracking URI и experiment name не могут быть пустыми.
|
||
`mlflow_environment()` временно выставляет:
|
||
|
||
- `MLFLOW_TRACKING_URI`;
|
||
- `MLFLOW_EXPERIMENT_NAME`;
|
||
- `MLFLOW_RUN`;
|
||
- `MLFLOW_KEEP_RUN_ACTIVE=False`.
|
||
|
||
После обучения предыдущие значения окружения восстанавливаются. В Ultralytics
|
||
глобальная настройка `mlflow` включается/выключается через `settings.update()`.
|
||
|
||
### `AugmentationConfig`
|
||
|
||
Default-параметры:
|
||
|
||
```text
|
||
hsv_h=0.015 hsv_s=0.7 hsv_v=0.4
|
||
degrees=0.0 translate=0.1 scale=0.5
|
||
shear=0.0 perspective=0.0
|
||
flipud=0.0 fliplr=0.5 bgr=0.0
|
||
mosaic=1.0 mixup=0.0 cutmix=0.0
|
||
copy_paste=0.0 erasing=0.4 close_mosaic=10
|
||
copy_paste_mode=flip
|
||
auto_augment=randaugment
|
||
```
|
||
|
||
Вероятности и доли валидируются в диапазоне `0…1`; `degrees`, `shear` и
|
||
`close_mosaic` не могут быть отрицательными. Режимы copy-paste: `flip`, `mixup`.
|
||
Политики AutoAugment: `none` (отключено), `randaugment`, `autoaugment`, `augmix`. Если augmentation
|
||
выключена, эти kwargs вообще не передаются в Ultralytics. При `auto_augment="none"` параметр транслируется в `None` для Ultralytics.
|
||
|
||
## 10. Работа с датасетами
|
||
|
||
Auto split предназначен только для detection-style структуры:
|
||
|
||
```text
|
||
dataset/
|
||
├── images/
|
||
│ └── **/*.{jpg,jpeg,png,bmp,webp,tif,tiff}
|
||
└── labels/
|
||
└── **/*.txt
|
||
```
|
||
|
||
Изображения и labels могут быть вложенными. Для split нужно минимум два изображения.
|
||
Shuffle детерминирован seed-ом `42`; train и val всегда получают минимум по одному
|
||
изображению.
|
||
|
||
Порядок определения классов:
|
||
|
||
1. явно заданный `classes_path` — авторитетный, без fallback при ошибке;
|
||
2. корневой `classes.txt`;
|
||
3. `labels/classes.txt`;
|
||
4. первый по имени корневой `.yaml/.yml` с полем `names`;
|
||
5. вывод диапазона `0…max_id` из всех label-файлов с именами `class_N`.
|
||
|
||
Поддерживаются text, YAML list и YAML dict. ID должны быть целыми,
|
||
неповторяющимися и последовательными от `0`; пустые имена запрещены.
|
||
|
||
Каждый split создаётся эксклюзивно:
|
||
|
||
```text
|
||
dataset/.yolo-webui/splits/{uuid}/
|
||
├── train.txt # абсолютные пути изображений
|
||
├── val.txt # абсолютные пути изображений
|
||
└── dataset.yaml
|
||
```
|
||
|
||
Итоговый YAML сохраняет дополнительные ключи исходного YAML, например `kpt_shape`
|
||
и `flip_idx`, но перезаписывает `path`, `train`, `val` и `names`. Проверенный
|
||
результат `read_classes()` всегда авторитетен.
|
||
|
||
Для `classify` auto split отключён: пользователь должен предоставить готовую
|
||
структуру `train/`, `val/` или `test/` с подкаталогами классов.
|
||
|
||
## 11. TrainingRunner и метрики
|
||
|
||
Перед обучением Runner:
|
||
|
||
1. повторно валидирует config;
|
||
2. при необходимости создаёт split и заменяет `data` на generated YAML;
|
||
3. импортирует Ultralytics;
|
||
4. включает/выключает MLflow integration;
|
||
5. создаёт `YOLO(config.resolved_model, task=config.task)`;
|
||
6. подключает callbacks `on_train_start`, `on_train_epoch_end`, `on_train_end`;
|
||
7. вызывает `model.train(**config.train_kwargs())`.
|
||
|
||
Epoch callback берёт numeric metrics из `trainer.metrics`, форматирует максимум три
|
||
первых значения и отправляет их в текстовом сообщении. Parent разбирает пары
|
||
`key=value`, поэтому live chart сейчас показывает не более трёх метрик на эпоху.
|
||
|
||
Если `trainer.save_dir` существует, его путь передаётся parent-у как результат.
|
||
|
||
Restricted checkpoint loading принудительно включён:
|
||
|
||
```text
|
||
ULTRALYTICS_SAFE_LOAD=1
|
||
```
|
||
|
||
## 12. Frontend
|
||
|
||
Frontend не имеет сборщика и framework: `index.html`, `style.css` и `app.js`
|
||
отдаются FastAPI как статические файлы. Chart.js загружается с jsDelivr CDN.
|
||
|
||
Левая панель содержит профили и вкладки:
|
||
|
||
- «Основное» — task, model, dataset, auto split;
|
||
- «Обучение» — epochs, image size, batch, device, workers, patience, output;
|
||
- «Аугментация» — все поля `AugmentationConfig`;
|
||
- «MLflow» — enabled, tracking URI, experiment и run name.
|
||
|
||
Правая панель содержит статус, timer, progress bar, Start/Stop, live chart и журнал.
|
||
|
||
Browser state:
|
||
|
||
- активная вкладка хранится в `localStorage.active_tab`;
|
||
- выбранный профиль — `localStorage.selected_profile`;
|
||
- черновик формы — `localStorage.draft_config`;
|
||
- при старте загрузки приоритет: draft → `last_run` → API defaults;
|
||
- список датасетов и моделей запрашивается у backend;
|
||
- стандартные модели YOLO11 выбираются динамически по task;
|
||
- при disconnect WebSocket переподключается через 5 секунд;
|
||
- `init` восстанавливает status, логи, progress и историю графика.
|
||
|
||
Числа читаются через `Number.parseInt/parseFloat` и проверку `Number.isNaN`. Нельзя
|
||
заменять это на `value || default`: допустимые `0` для workers, patience,
|
||
close_mosaic и augmentation-параметров должны сохраняться.
|
||
|
||
Chart datasets создаются по фактически пришедшим ключам. Новая метрика может
|
||
появиться на поздней эпохе; пропущенные точки заполняются `null`, чтобы серии не
|
||
сдвигались относительно labels.
|
||
|
||
## 13. Безопасность и доверенная модель
|
||
|
||
Приложение не имеет аутентификации. Безопасность по умолчанию строится на локальной
|
||
публикации и ограничении файловых путей.
|
||
|
||
Default доверенные корни:
|
||
|
||
| Назначение | Корни |
|
||
|---|---|
|
||
| Dataset и classes | `./datasets` |
|
||
| Model/checkpoint | `./models`, `./runs` |
|
||
| Training output | `./runs` |
|
||
|
||
Дополнительные корни перечисляются через системный `os.pathsep`:
|
||
|
||
- `YOLO_WEBUI_DATA_ROOTS`;
|
||
- `YOLO_WEBUI_MODEL_ROOTS`;
|
||
- `YOLO_WEBUI_RUN_ROOTS`.
|
||
|
||
Проверка запрещает URL, нормализует путь через `resolve(strict=False)` и проверяет
|
||
принадлежность корню, включая существующие symlink-компоненты. Безопасные bare
|
||
identifiers разрешены для официальных имён, но существующий одноимённый файл вне
|
||
доверенного root отвергается. Model-файлы ограничены расширениями `.pt`, `.pth`,
|
||
`.yaml`, `.yml`.
|
||
|
||
Compose публикует только `127.0.0.1:8000:8000`. Для доступа из сети обязателен
|
||
аутентифицирующий reverse proxy и явная оценка риска: API может запускать тяжёлое
|
||
обучение, останавливать его и управлять профилями.
|
||
|
||
## 14. Docker
|
||
|
||
Dockerfile:
|
||
|
||
- основан на `python:3.11-slim`;
|
||
- устанавливает системные библиотеки для OpenCV/PyTorch/Ultralytics;
|
||
- фиксирует `uv==0.10.6`;
|
||
- копирует `pyproject.toml`, `uv.lock`, README;
|
||
- выполняет `uv sync --locked --no-dev` в `/opt/venv`;
|
||
- включает `ULTRALYTICS_SAFE_LOAD=1`;
|
||
- запускает `yolo-train-webui --host 0.0.0.0 --port 8000`.
|
||
|
||
Compose монтирует:
|
||
|
||
```text
|
||
./datasets -> /workspace/datasets
|
||
./runs -> /workspace/runs
|
||
./models -> /workspace/models
|
||
./models/.config -> /root/.config/Ultralytics
|
||
```
|
||
|
||
Порт 8000 опубликован только на loopback. Порт 5000 объявлен в image, но Compose не
|
||
запускает и не публикует MLflow UI. GPU reservation оставлена как закомментированный
|
||
пример для NVIDIA/Linux.
|
||
|
||
## 15. Тесты и проверки
|
||
|
||
Текущий regression suite содержит 49 pytest-тестов.
|
||
|
||
- `test_app.py`: defaults/status, profiles, deadlock, background WebSocket loop,
|
||
финальные состояния.
|
||
- `test_config.py`: kwargs, validation, zero-compatible параметры, MLflow env,
|
||
security roots и URL.
|
||
- `test_splitter.py`: форматы классов, приоритеты, nested data, уникальные outputs,
|
||
сохранение YAML metadata.
|
||
- `test_subprocess_runner.py`: return codes, READY/RESULT и traceback.
|
||
- `test_trainer.py`: Ultralytics callbacks, metrics, ранний stop, сигналы.
|
||
- `test_frontend.py` + `frontend_smoke.js`: syntax, сохранение нулей, динамические
|
||
chart series в fake browser environment.
|
||
|
||
Основные команды:
|
||
|
||
```bash
|
||
uv sync --locked
|
||
uv run pytest -q
|
||
uv run python -m compileall -q src tests
|
||
node --check src/yolo_webui/static/app.js
|
||
node tests/frontend_smoke.js
|
||
uv lock --check
|
||
docker compose config
|
||
git diff --check
|
||
```
|
||
|
||
Python-команды проекта следует выполнять через `uv run`, чтобы использовать
|
||
зафиксированное окружение.
|
||
|
||
## 16. Согласованное изменение проекта
|
||
|
||
При добавлении или переименовании config-поля обычно нужно изменить вместе:
|
||
|
||
1. dataclass, default, validation и `train_kwargs()` в `config.py`;
|
||
2. `TrainingConfig.from_dict()`;
|
||
3. поле в `static/index.html`;
|
||
4. чтение в `getFormConfig()` и восстановление в `applyConfig()` в `app.js`;
|
||
5. backend/frontend regression tests;
|
||
6. README и этот контекст, если меняется пользовательский контракт.
|
||
|
||
При добавлении нового состояния обучения нужно обновить:
|
||
|
||
1. backend state machine и финальную классификацию;
|
||
2. WebSocket status payload;
|
||
3. `updateUIStatus()`;
|
||
4. CSS-селекторы `status-*`;
|
||
5. тесты переходов и reconnect snapshot.
|
||
|
||
При изменении subprocess-протокола синхронно меняются `subprocess_runner.py` и parser
|
||
в `TrainingManager._handle_subprocess_line()`. Префиксы протокола нельзя печатать в
|
||
обычных логах.
|
||
|
||
Критические инварианты:
|
||
|
||
- не вызывать WebSocket send из нового или чужого event loop;
|
||
- не вызывать `broadcast()` под `TrainingManager._lock`;
|
||
- не объединять `succeeded`, `cancelled`, `failed` в общий `finished`;
|
||
- не использовать JS truthiness для числовых полей;
|
||
- явно указанный classes-файл всегда авторитетен;
|
||
- не создавать split поверх пользовательских файлов;
|
||
- не снимать `--locked` с Docker/CI установки;
|
||
- не расширять сетевую публикацию без аутентификации;
|
||
- сохранять traceback и ошибки доставки в наблюдаемых логах.
|
||
|
||
## 17. Текущие ограничения
|
||
|
||
- Только один активный training job и один глобальный in-memory `LiveState`.
|
||
- После перезапуска server live state теряется; сохраняются лишь JSON-профили,
|
||
`last_run`, training artifacts и MLflow data.
|
||
- Нет очереди, scheduler, истории runs API, upload API и файлового браузера.
|
||
- Нет встроенной аутентификации и multi-user isolation.
|
||
- Discovery просматривает только верхний уровень `datasets/` и `models/`.
|
||
- Live chart зависит от внешнего Chart.js CDN.
|
||
- В график попадают максимум три numeric metrics, выбранные callback-ом.
|
||
- Реальное длительное YOLO-обучение и Docker image build не входят в быстрый test
|
||
suite; unit-тесты подменяют Ultralytics и subprocess там, где это возможно.
|
||
- В репозитории нет `.dockerignore` и CI-конфигурации; Docker build context зависит
|
||
от содержимого рабочей копии.
|
||
- FastAPI TestClient выдаёт deprecation warning для текущей связки Starlette/httpx;
|
||
тесты при этом проходят.
|
||
|
||
Отдельного файла лицензии проекта в репозитории нет. README напоминает, что
|
||
Ultralytics распространяется по AGPL-3.0 и предлагает отдельно проверить условия
|
||
Enterprise-лицензии для закрытого коммерческого использования.
|
||
|
||
Перед работой с известными дефектами сверяйтесь с `PROJECT_ISSUES.md`: на дату этого
|
||
контекста перечисленные там 10 проблем исправлены.
|