408 lines
33 KiB
Markdown
408 lines
33 KiB
Markdown
# Bone Quality Assessment Project
|
||
|
||
## Project Overview
|
||
|
||
Медицинский ИИ-сервис для автоматизированной оценки качества денситометрических
|
||
исследований (DXA). Принимает DICOM, определяет анатомическую область, решает
|
||
бинарную задачу «качественное изображение / есть нарушение» и формирует отчёт.
|
||
|
||
### Ключевые требования (condition.txt)
|
||
|
||
- Области: поясничный отдел позвоночника и проксимальный отдел бедренной кости.
|
||
- Обязательна контейнеризация и скрипт сборки/запуска в Linux.
|
||
- Результат: XLSX/CSV, одна строка на изображение, столбцы
|
||
`path_to_study, study_uid, image_uid, anatomical_region, quality_class,
|
||
violation_type, processing_status, time_of_processing`.
|
||
- Приоритетные метрики: F1 и ROC-AUC (с 95 % доверительными интервалами).
|
||
- Работа офлайн, без передачи изображений во внешние сервисы.
|
||
- Время обработки одного исследования — не более 3 минут.
|
||
|
||
### Технологии
|
||
|
||
| Компонент | Технология |
|
||
|---|---|
|
||
| Backend | Python, FastAPI, Uvicorn |
|
||
| ML | PyTorch, torchvision (ResNet18) |
|
||
| Изображения | pydicom, Pillow, OpenCV, SciPy |
|
||
| Данные | pandas, openpyxl, scikit-learn |
|
||
| Тесты | pytest |
|
||
|
||
---
|
||
|
||
## Действующая архитектура
|
||
|
||
Всё, что реально работает, находится в `src/dxa/` и `src/main.py`.
|
||
|
||
```
|
||
src/dxa/
|
||
├── labels.py # имена -> метки, склейка дублей, разбиение, фиксация сплита
|
||
├── excel_labels.py # разметка снимков по экспертной таблице (labels_images.csv)
|
||
├── manual_labels.py # вердикты специалиста: хранение, наложение, выгрузка (/label)
|
||
├── review_pack.py # автономный HTML-пакет разметки для врача (без сервера и сети)
|
||
├── rename_files.py # приведение имён DICOM к виду область_NN[_метка]
|
||
├── violations.py # единый словарь типов нарушений (коды, подписи, коды SR)
|
||
├── model_card.py # карточка решения: разметка, данные, метрики, ограничения
|
||
├── compare_labels.py # сравнение источников разметки на одном held-out наборе
|
||
├── render.py # рендер DICOM в PNG и контактные листы (для ручной проверки)
|
||
├── preprocess.py # DICOM -> CHW-тензор (единый путь для обучения и API)
|
||
├── dataset.py # DXADataset, DataLoader
|
||
├── model.py # сеть, метрики, подбор порога, сохранение/загрузка
|
||
├── train.py # обучение + отчёт (md/json)
|
||
└── inference.py # пакетный инференс, определение области, визуализация
|
||
```
|
||
|
||
Артефакты вне кода: `labels/labels_images.csv|.xlsx` (официальная разметка),
|
||
`labels/labels_images_{table,union,expert}.csv` (варианты правила и эталон),
|
||
`labels/split_expert_seed42.json` (зафиксированное разбиение),
|
||
`labels/rename_map.csv` (карта переименований), `assets/labeling.md`
|
||
(как построена разметка и как выбиралось правило). Чекпоинты прогонов
|
||
(`models/archive/`, `models/compare_rules/`, `models/compare_labels/`,
|
||
`models/excel_labels/`) удалены 2026-09-27 — остались только числа в
|
||
`assets/labeling.md`; пересобрать можно через `./run.sh split && ./run.sh compare`.
|
||
|
||
### Ключевые решения (проверены экспериментально)
|
||
|
||
| Решение | Причина |
|
||
|---|---|
|
||
| Единый словарь типов нарушений (`src/dxa/violations.py`) | Коды, подписи и коды SR были в трёх копиях (инференс, `main.py`, `dxa-app.js`) и не знали кодов экспертной таблицы. Теперь подписи отдаёт сервер, фронт копий не держит |
|
||
| Метки только из экспертной таблицы (правило `table`): `labels/labels_images.csv`, 76 нарушений | Таблица описывает исследование, но каждая область встречается в нём один раз, поэтому вердикт переносится на снимок однозначно. Правило выбрано измерением: учёт ручных пометок из имён файлов дал ROC-AUC 0.6003 против 0.6726, хуже на всех 5 seed'ах. См. `assets/labeling.md` |
|
||
| Склейка побайтных дублей | 482 файла, но 251 уникальный снимок; без склейки снимок попадал в оба класса |
|
||
| Разбиение по исследованиям, не по снимкам | Исключение утечки: снимки одного исследования в одной части |
|
||
| Линейный зонд (замороженный backbone) | Полный fine-tune при ~250 снимках переобучается (val AUC → 0.5) |
|
||
| Порог по логиту, подбор по F1 | При доле нарушений около 30 % порог 0.5 даёт почти нулевой recall; вероятности насыщаются |
|
||
| Аугментация выключена по умолчанию | Яркость и положение сами являются признаками качества: AUC 0.87 → 0.56 |
|
||
| Область по ширине кадра | Позвоночник 300 px, бедро 280 px; 99/99 для позвоночника |
|
||
| Панель деталей показывает измерения, а не вердикты | Эвристики `detailed_assessment` не калиброваны: `roi.valid` ложен для всех 251 снимка (краевой отступ срабатывает у 249 из 251), поэтому в панели показываются числовые измерения, а не вердикты «Да/Нет» |
|
||
|
||
### Проверка вклада модели
|
||
|
||
Не является ли модель просто детектором анатомии (нарушений около трети и в
|
||
позвоночнике, и у бёдер, а область почти однозначно определяется по ширине кадра):
|
||
|
||
```bash
|
||
python -m src.dxa.discriminator --model-path models/dxa_model.pth
|
||
```
|
||
|
||
Результат на рабочем чекпоинте `models/dxa_model.pth` (оценка на всём наборе,
|
||
включая обучающие снимки, поэтому значения смещены вверх):
|
||
|
||
| Предиктор | Общий AUC | spine | hip_right | hip_left |
|
||
|---|---|---|---|---|
|
||
| Модель | 0.885 | 0.954 | 0.875 | 0.880 |
|
||
| Правило «позвоночник = нарушение» | 0.534 | 0.500 | 0.500 | 0.500 |
|
||
|
||
Модель использует содержимое снимка: внутри областей она даёт 0.88–0.95.
|
||
Правило по области внутри области всегда 0.50 (подсказки нет). Честная оценка на
|
||
held-out — в `assets/labeling.md` §8: ROC-AUC 0.6726 [0.6367, 0.7086] по пяти seed'ам.
|
||
|
||
## Данные (`dataset_hack/`)
|
||
|
||
Структура:
|
||
|
||
```
|
||
dataset_hack/
|
||
├── Для теста/ # l_hip_01.dcm, r_hip_01.dcm, r_hip_01_bad.dcm, spine_01.dcm
|
||
└── НД_для_обучения/
|
||
├── разметка.xlsx # экспертная оценка на уровне ИССЛЕДОВАНИЯ
|
||
└── Исследования/<study_uid>/.../<region>_<n>[_good|_bad].dcm
|
||
```
|
||
|
||
Факты, важные для обучения:
|
||
|
||
- **482 файла `.dcm` на диске** (было 520: удалены 38 лишних побайтных копий и 10
|
||
файлов `.DS_Store`), но **251 уникальный снимок** (по пиксельному содержимому)
|
||
на 100 исследований: позвоночник 99, бедро R 78, бедро L 73, 1 с неопределённой
|
||
областью.
|
||
- Экспертная таблица отмечает нарушения у **73 снимков (29.1 %)**; три снимка
|
||
таблица область не оценивала.
|
||
- Рабочая разметка (правило `table`) — **76 нарушений из 251 (30.3 %)**: 73 по
|
||
таблице плюс 3 снимка без экспертной оценки, помеченных `filename_fallback`.
|
||
- Дубли не пересекают границы исследований, конфликтов меток при склейке нет.
|
||
Два побайтных дубля названы по-разному, поэтому область определяется
|
||
голосованием по именам файлов.
|
||
- Имена файлов приведены к виду `<область>_<NN>[_good|_bad].dcm` (`spine`,
|
||
`l_hip`, `r_hip`); инструмент — `src/dxa/rename_files.py`, карта отката —
|
||
`labels/rename_map.csv`. Суффиксы `_good`/`_bad` проставлялись вручную, в
|
||
метках **не участвуют** — только как диагностический столбец
|
||
`quality_from_filename`: они расходились с оценкой эксперта в 62 случаях из 251
|
||
(повторное переименование 2026-09-27 дописало пометки уже после сборки
|
||
разметки и с тех пор расхождений стало вчетверо больше).
|
||
- В DICOM **нет** разметки ROI (ни OverlayData, ни GraphicAnnotationSequence) и
|
||
пусты теги `Laterality`/`ImageLaterality`, поэтому ни корректность областей, ни
|
||
сторону бедра нельзя проверить по метаданным.
|
||
- Столбец `study` в таблице — имя каталога исследования, а **не**
|
||
StudyInstanceUID из DICOM (в датасете они разные, соответствие 1:1).
|
||
|
||
### Единица разметки
|
||
|
||
Таблица описывает исследование, а не снимок. Однако каждая анатомическая область
|
||
встречается в исследовании ровно один раз (после склейки дублей), поэтому вердикт
|
||
исследования по области переносится на снимок однозначно — не нужно решать, какой
|
||
из нескольких снимков «плохой». Так получены метки и типы нарушений
|
||
(`labels/labels_images.csv`); каждый источник свидетельства сохранён в отдельном
|
||
столбце, поэтому правило можно переиграть без повторного разбора. Почему выбрано
|
||
именно правило «только таблица» — в `assets/labeling.md` §4.
|
||
|
||
---
|
||
|
||
## Обучение
|
||
|
||
```bash
|
||
./run.sh label # построить разметку снимков по Excel
|
||
./run.sh rename # план приведения имён файлов (--apply)
|
||
./run.sh train # режим по умолчанию (метки из таблицы)
|
||
python -m src.dxa.train --dry-run # проверить данные без обучения
|
||
python -m src.dxa.train --labels-csv "" --dry-run # режим меток из имён файлов
|
||
python -m src.dxa.train --head mlp --freeze-epochs 0 --epochs 30
|
||
```
|
||
|
||
Источник меток — `--labels-csv` (по умолчанию `labels/labels_images.csv` с правилом
|
||
`table`; пустая строка возвращает метки из имён файлов, отсутствующий файл — откат
|
||
к ним с предупреждением). Разбиение фиксируется (`--export-split` / `--split-file`),
|
||
чтобы сравнивать варианты на одном held-out наборе:
|
||
|
||
```bash
|
||
./run.sh split && ./run.sh compare # выбор правила разметки
|
||
```
|
||
|
||
`split` стратифицирует по эталону (`labels/labels_images_expert.csv`), `compare`
|
||
прогоняет 5 seed'ов × 2 варианта и оценивает оба по этому же эталону; отчёты
|
||
(`comparison.md` / `comparison.json`) и чекпоинты прогонов пишутся в
|
||
`--output-root` (по умолчанию `models/compare_labels`) и в репозиторий не входят.
|
||
Возобновить без переобучения — `./run.sh compare --skip-training`.
|
||
|
||
Артефакты в `--output-dir`: `dxa_model.pth` (веса, порог, параметры
|
||
предобработки), `train_report.md`, `train_report.json`.
|
||
|
||
Чекпоинт самодостаточен: `backbone`, `head`, `preprocess`, `threshold`, а также
|
||
`labels_csv` и `split_file` хранятся внутри, поэтому инференс не может
|
||
рассинхронизироваться с обучением, а по файлу видно, на какой разметке он обучен.
|
||
|
||
Рабочий чекпоинт — `models/dxa_model.pth` (правило `table`, seed 42 по умолчанию,
|
||
эпоха 57, порог логита −0.1370 → вероятность 0.466; val ROC-AUC 0.6689 при
|
||
честной оценке 0.6726 [0.6367, 0.7086] по пяти seed'ам). Переобучен 2026-09-27
|
||
после пересборки разметки. Это единственный чекпоинт в репозитории: прежние
|
||
версии и прогоны сравнения удалены, откатиться можно только переобучением.
|
||
|
||
---
|
||
|
||
## API (`src/main.py`)
|
||
|
||
| Метод | Путь | Назначение |
|
||
|---|---|---|
|
||
| GET | `/` | Веб-интерфейс |
|
||
| GET | `/label` | Интерфейс ручной разметки (подтверждение вердиктов специалистом) |
|
||
| GET | `/api/v1/health` | Статус, признак загрузки модели и её происхождение (разметка, разбиение, порог, эпоха) |
|
||
| GET | `/api/v1/model` | Карточка решения: разметка, данные, метрики с интервалами, словарь нарушений, ограничения |
|
||
| POST | `/api/v1/analyze` | Базовый анализ файла |
|
||
| POST | `/api/v1/analyze/detailed` | Расширенный отчёт, опционально маска |
|
||
| POST | `/api/v1/analyze/sr` | Текстовый отчёт DICOM SR |
|
||
| POST | `/api/v1/batch` | Пакетный анализ |
|
||
| POST | `/api/v1/export` | Пакетный анализ + XLSX |
|
||
| GET | `/api/v1/labeling/items` | Снимки датасета для разбора: текущая метка, её источник, расхождения |
|
||
| GET | `/api/v1/labeling/image` | PNG снимка для просмотра (путь проверяется на выход за каталог датасета) |
|
||
| POST | `/api/v1/labeling/verdict` | Сохранить вердикт специалиста в `labels/manual_labels.csv` |
|
||
| DELETE | `/api/v1/labeling/verdict` | Снять вердикт и вернуть снимок к построенной разметке |
|
||
| GET | `/api/v1/labeling/export` | Выгрузка разметки: `scope=all` годится как `--labels-csv` |
|
||
|
||
Путь к модели — переменная окружения `DXA_MODEL_PATH` (по умолчанию
|
||
`models/dxa_model.pth`), чтобы контейнер не зависел от рабочего каталога.
|
||
API и CLI используют один код предсказания (`predict_from_bytes` /
|
||
`predict_from_array`), поэтому предобработка и порог совпадают.
|
||
|
||
---
|
||
|
||
## Тесты
|
||
|
||
```bash
|
||
./run.sh test
|
||
python -m pytest tests/ -q # 272 теста
|
||
```
|
||
|
||
- `tests/test_labels.py` — разбор имён, склейка дублей, отсутствие утечки при
|
||
разбиении, фиксация разбиения в файле (`export_split` / `load_split`).
|
||
- `tests/test_rename_files.py` — приведение имён: разбор, поиск свободного
|
||
номера при конфликте, отказ от угадывания области, цикл «применить → откатить».
|
||
- `tests/test_excel_labels.py` — разметка по экспертной таблице: чтение
|
||
критериев, «1 = нарушение», голосование по области, перенос оценки на
|
||
единственное бедро, три правила метки (`table` / `union` / `expert`) и их
|
||
согласованность, подключение к обучению, чтение CSV, сохранённого Excel с BOM.
|
||
- `tests/test_manual_labels.py` — ручная разметка: проверка вердикта (область,
|
||
метка, тип нарушения и его совместимость с областью), хранение и правка,
|
||
наложение поверх построенной разметки, подсчёт прогресса и выгрузка.
|
||
- `tests/test_violations.py` — единый словарь типов: коды и подписи, коды SR,
|
||
приведение устаревших значений, согласованность с критериями таблицы.
|
||
- `tests/test_preprocess_and_model.py` — предобработка, метрики, подбор порога,
|
||
контракт модели, BatchNorm при заморозке, roundtrip чекпоинта.
|
||
- `tests/test_api_contract.py` — поля ответов, которые читает `dxa-app.js`
|
||
(панель деталей ранее показывала прочерки из-за расхождения ключей),
|
||
различимость метрик между снимками, валидность PNG-визуализаций, отсутствие
|
||
некалиброванных вердиктов в ответе.
|
||
- `tests/test_labeling_api.py` — контракт `/api/v1/labeling/*`: отказ отдавать
|
||
файлы вне датасета (обход каталога, не-DICOM), проверка вердикта, выгрузка,
|
||
читаемая обучением как `--labels-csv`.
|
||
- `tests/test_review_pack.py` — пакет для врача: отпечаток набора, порядок
|
||
(расхождения первыми), встроенные данные разбираются как JSON, страница не
|
||
ссылается на сеть, слияние вердиктов с построенной разметкой и сообщение о
|
||
путях из чужого пакета.
|
||
|
||
### Проверка веб-интерфейса в браузере
|
||
|
||
`tests/browser/*.js` (Node + playwright-core из bundled Browser Use) открывают
|
||
интерфейс, загружают DICOM, кликают по строкам таблицы и снимают содержимое
|
||
панели деталей. Используется временный профиль Chrome, профиль пользователя не
|
||
затрагивается. Требуется запущенный сервер на `127.0.0.1:8123`.
|
||
|
||
- `ui_check.js` — подсказка о кликабельности строк видна и скрывается, когда
|
||
фильтр не оставил строк; кнопка «Открыть» в строке открывает панель деталей;
|
||
значения панели меняются при переключении строк; панель «О модели» наполняется
|
||
метриками и словарём (иначе раздел остался бы пустым каркасом).
|
||
- `ui_violation.js` — ветка «нарушение» (бейдж, POOR, HIGH, заключение).
|
||
- `ui_labeling.js` — интерфейс ручной разметки: список снимков с прогрессом,
|
||
расхождения первыми, снимок отрисовывается, вердикт сохраняется и переживает
|
||
перезагрузку страницы, фильтр «только расхождения», отсутствие внешних запросов.
|
||
Запускать с временным файлом вердиктов:
|
||
`DXA_MANUAL_LABELS=/tmp/manual_ui.csv python -m uvicorn src.main:app --port 8123`,
|
||
иначе проверка пишет в рабочий `labels/manual_labels.csv`.
|
||
- `review_pack.js` — автономный пакет разметки: открывает HTML прямо с диска
|
||
(`file://`, сервер не нужен), проверяет отрисовку встроенного снимка, вердикт,
|
||
его сохранение после перезагрузки, выгрузку CSV и восстановление прогресса из
|
||
этого же файла, а также отсутствие любых сетевых запросов. Запуск:
|
||
`PACK=review/doctor_review.html node tests/browser/review_pack.js`.
|
||
- `ui_offline.js` — страница не обращается к внешним хостам.
|
||
|
||
### Честность интерфейса
|
||
|
||
Веб-интерфейс не должен утверждать больше, чем известно решению, поэтому:
|
||
|
||
- тип нарушения показан с пометкой «эвристика» и пояснением, что модель решает
|
||
только бинарную задачу;
|
||
- ROC-AUC рабочего чекпоинта в баннере состояния помечена как завышенная (он
|
||
выбран лучшим из пяти seed'ов), а честная оценка лежит в панели «О модели»;
|
||
- плитка средней уверенности не называется точностью;
|
||
- подписи типов и метрики приходят с сервера (`/api/v1/model`), копий в JS нет.
|
||
|
||
### Офлайн-работа фронтенда
|
||
|
||
Tailwind и FontAwesome лежат локально (`src/api/static/vendor`,
|
||
`src/api/static/webfonts`), страница не обращается к CDN. Требование методики —
|
||
работа без внешних сервисов; CDN-версии ломали оформление в закрытом контуре.
|
||
Наличие ассетов проверяется на этапе сборки образа (см. Dockerfile).
|
||
|
||
---
|
||
|
||
## Известные ограничения
|
||
|
||
1. Разметка снимков выведена из таблицы, описывающей исследование: поштучной
|
||
экспертной оценки снимков в наборе нет. Оценка качества модели упирается в
|
||
качество этой разметки, а не только в объём данных. Для поштучной разметки есть
|
||
интерфейс `/label` (см. ниже) — им ещё не пользовались.
|
||
2. Мало данных: 251 снимок, 76 нарушений; доверительные интервалы широкие
|
||
(ROC-AUC 0.6726 [0.6367, 0.7086] по пяти seed'ам).
|
||
3. Эталон оценки — та же экспертная таблица, независимой истины нет; сравнение
|
||
правил разметки частично благоприятствует варианту «только таблица».
|
||
4. Тип нарушения определяется эвристиками, а не обученной моделью; 5 снимков
|
||
имеют только `unspecified`, потому что источник не указывает критерий.
|
||
5. Три снимка без экспертной оценки размечены по пометке в имени файла и помечены
|
||
`filename_fallback`.
|
||
6. Сторона бедра в 7 исследованиях с единственным снимком не проверяема: теги
|
||
`Laterality` пусты, оценка взята из единственного заполненного столбца.
|
||
7. Порог `SPINE_MIN_WIDTH` привязан к текущему оборудованию.
|
||
8. Разметка и датасет уже расходились: повторное переименование 2026-09-27
|
||
дописало суффиксы `_good`/`_bad` после сборки разметки, из-за чего 20 из 252
|
||
путей устарели, 6 снимков получали метку «нарушение» вопреки эксперту и
|
||
пайплайн отдавал 251/82 вместо 252/77. Расхождение устранено пересборкой
|
||
разметки и переобучением в тот же день; числа в этом файле — уже новые.
|
||
Разбор — в `assets/labeling.md` §7.
|
||
|
||
## Ручная разметка (`/label`)
|
||
|
||
Поштучной экспертной оценки снимков в наборе нет (ограничение №1), а раздел 2.6
|
||
задания просит «автоматическую коррекцию разметки с возможностью подтверждения
|
||
специалистом». Поэтому в сервисе есть отдельный интерфейс `/label`:
|
||
`src/dxa/manual_labels.py` — хранение вердиктов, эндпоинты `/api/v1/labeling/*` —
|
||
список снимков, просмотр и сохранение, страница `src/api/static/label.html` +
|
||
`js/labeling.js`.
|
||
|
||
Вердикты лежат в `labels/manual_labels.csv` (переменная `DXA_MANUAL_LABELS`) в
|
||
формате построенной разметки, поэтому файл читается тем же `load_labels_csv` и
|
||
принимается обучением как `--labels-csv`. Выгрузка `scope=all` накладывает ручные
|
||
вердикты на построенную разметку и годится как источник меток напрямую.
|
||
|
||
Оценка модели в интерфейсе намеренно не показывается: подсказка смещала бы
|
||
разметчика, а цель — независимое суждение человека. Список выводит первыми
|
||
снимки, где метка разметки расходится с пометкой в имени файла: там ошибка
|
||
возможна в любом из источников.
|
||
|
||
### Пакет для врача (разметка без сервиса)
|
||
|
||
`/label` требует запущенного сервиса и Python, а размечать должен врач — на своей
|
||
машине. Поэтому тот же сценарий собирается в **один HTML-файл**:
|
||
|
||
```bash
|
||
./run.sh review --out review/doctor_review.html # ~7 МБ, 251 снимок
|
||
./run.sh review --limit 20 --out review/pilot.html # пилот на выборке
|
||
./run.sh review --merge review/doctor.csv --out labels/labels_images_reviewed.csv
|
||
```
|
||
|
||
Снимки встроены как data-URI, вердикты лежат в `localStorage` браузера, выгрузка —
|
||
CSV в формате `manual_labels.csv` (плюс колонка `pack_id` — отпечаток набора,
|
||
чтобы различить пакеты). Файл открывается двойным щелчком, работает без сети и
|
||
без установки чего-либо; каталог `review/` в git не хранится.
|
||
|
||
Вердикты врача принимаются как есть: `load_verdicts` читает выгрузку, `--merge`
|
||
накладывает её на построенную разметку (и сообщает о путях, которых нет в
|
||
датасете — признак чужого пакета), а `--labels-csv` с этим файлом годится для
|
||
обучения. Оценка модели в пакет не попадает по той же причине, что и в `/label`.
|
||
|
||
## Удалённый устаревший код
|
||
|
||
Не подключённые к API модули (`src/core/orchestrator.py`, `src/pipeline/`,
|
||
`src/model/{unet,segmentator}.py`, `src/models/*`, `src/classifiers/`,
|
||
`src/segmentators/`, `src/dataloaders/`, `src/training/`,
|
||
`src/api/{endpoints,annotation,root,schemas}`, `src/quality/{artifact_detector,
|
||
position_validator,universal_scorer,medical_quality}.py`) удалены 2026-09-27
|
||
вместе с неиспользуемыми чекпоинтами (`quality_classifier.pth`,
|
||
`region_detector.pth`, `violation_classifier.pth`, `dxa_model_final.pth`) и
|
||
папками `docs/`, `tools/`, `public/`.
|
||
|
||
Из живого кода их тянул только `src/__init__.py` — реэкспорт `Config`,
|
||
`FlexibleDataset`, `UNet`, `QualityScorer`; теперь в нём остался только
|
||
`__version__`, а `src/quality/__init__.py` — только докстрока. При добавлении
|
||
новых модулей в `src/quality/` помнить, что пакет больше ничего не импортирует.
|
||
|
||
`docs/` после удаления появился снова — владелец вернул его под сдачу: там лежат
|
||
`technical-description.html` (сдаточный технический документ) и его PDF-версия,
|
||
а также презентация команды. Это не материалы README: корневой `README.md`
|
||
ссылается только на `assets/`. PDF печатается из HTML тем же Chrome, а не
|
||
отдельным конвертером:
|
||
|
||
```bash
|
||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless \
|
||
--disable-gpu --no-pdf-header-footer --user-data-dir=/tmp/chrome-pdf-profile \
|
||
--print-to-pdf="$PWD/docs/technical-description.pdf" \
|
||
"file://$PWD/docs/technical-description.html"
|
||
```
|
||
|
||
---
|
||
|
||
## Docker
|
||
|
||
```bash
|
||
docker compose up -d # CPU, http://localhost:8000
|
||
docker compose --profile cuda up -d dxa-cuda # GPU (нужен nvidia-container-toolkit)
|
||
```
|
||
|
||
Образы: `Dockerfile` (python:3.11-slim, torch из CPU-индекса) и `Dockerfile_cuda`
|
||
(та же база, torch и torchvision из индекса `cu126` — под CUDA 11.8 колёс torch
|
||
2.8.0 нет, индекс заканчивается на 2.7.1). Оба собираются из `src/`,
|
||
`requirements.txt`, `run.sh` и **чекпоинта**: `models/dxa_model.pth` отслеживается
|
||
git и копируется в `/app/models` на этапе сборки, поэтому монтировать пути при
|
||
запуске не нужно (условие приёмки — модели уже внутри образа). Сборка проверяет
|
||
импорт приложения, наличие офлайн-ассетов фронтенда и читаемость чекпоинта,
|
||
образ имеет HEALTHCHECK. Данные, тесты и `labels/` в образ не попадают
|
||
(`.dockerignore`), поэтому `./run.sh train` внутри контейнера возьмёт метки из
|
||
имён файлов (с предупреждением); для обучения в контейнере смонтируйте `labels/`
|
||
или передайте свой `--labels-csv`. Если внешний `docker-swarm.yml` монтирует
|
||
каталог с моделями в `/app/models`, монтирование перекроет встроенный чекпоинт.
|