bone_2026/README.md

529 lines
42 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🦴 DXA Quality Assessment
Сервис автоматизированного контроля качества денситометрических исследований (DXA):
принимает DICOM, определяет анатомическую область, оценивает, пригодно ли изображение
для клинической интерпретации, и формирует структурированный отчёт.
<div align="center">
<img src="assets/img/ui-results.png" alt="Результаты обработки в веб-интерфейсе: полоса состояния, статистика и таблица снимков" width="90%">
</div>
## Что делает решение
| Шаг | Реализация |
|---|---|
| Определение анатомической области | Ширина кадра (позвоночник / бедро) + голова области + геометрия яркой зоны |
| Бинарная оценка качества | ResNet18 (ImageNet) → линейная голова; порог подобран по F1 на валидации |
| Тип нарушения | Общая категория для снимков с нарушением; детальный тип требует разметки типов на уровне снимка |
| Отчёт | XLSX/CSV со столбцами из требований; опционально zip с визуализацией зоны интереса |
| API | FastAPI: анализ, детальный анализ, пакетная обработка, экспорт, DICOM SR (текст) |
| Веб-интерфейс | Загрузка DICOM, таблица результатов, панель деталей с визуализацией, панель «О модели» |
## Установка и запуск
```bash
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
./run.sh label # разметить датасет по Excel
./run.sh rename # план приведения имён файлов
./run.sh train # обучить модель качества
./run.sh infer "dataset_hack/Для теста" results.xlsx # пакетная обработка
./run.sh serve # API и веб-интерфейс на :8000
./run.sh split && ./run.sh compare # сравнить варианты разметки
./run.sh test # тесты
```
Для инференса только на CPU (образ меньше, без CUDA-колёс):
```bash
pip install -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu
```
Обучение и инференс можно вызывать напрямую:
```bash
python -m src.dxa.train --epochs 100 --output-dir models
python -m src.dxa.inference --input-path dataset_hack --output-path results.xlsx --zip-out masks.zip
```
После запуска сервера:
- Веб-интерфейс: http://localhost:8000
- Swagger UI: http://localhost:8000/docs
## Docker
```bash
docker build -t dxa-quality .
docker run -v /path/to/data:/data -v /path/to/models:/app/models -p 8000:8000 dxa-quality
```
Чекпоинт в образ не копируется: он монтируется в `/app/models`, путь задаётся
переменной `DXA_MODEL_PATH` (по умолчанию `/app/models/dxa_model.pth`). Без
чекпоинта сервис всё равно поднимается — `/api/v1/health` сообщает
`model_loaded: false`, а анализ отвечает ошибкой `Model not loaded` вместо тихой
неверной оценки. Из сети ничего не скачивается. Сборка проверяет, что приложение
импортируется и что офлайн-ассеты фронтенда на месте.
---
## Архитектура
<div align="center">
<img src="assets/img/architecture-pipeline.png" alt="Пайплайн обработки DXA: загрузка DICOM, разбор метаданных, предобработка, инференс, определение области, метрики качества, отчёт" width="52%">
</div>
Схема работающего пути в терминах решения:
```
DICOM ──▶ предобработка ──▶ ResNet18 (заморожен) ──▶ линейная голова ──▶ логит
│ │
│ └──▶ голова области (вспомогательная)
│
├──▶ геометрия яркой зоны: область, ROI, геометрия кадра
└──▶ эвристики: резкость, «плотные» включения
```
Ключевые решения и почему они такие:
1. **Линейный зонд вместо полного fine-tune.** Уникальных снимков в наборе ~250.
Полный fine-tune ResNet18 переобучается за несколько эпох (train F1 → 1.0 при
val AUC ≈ 0.5). Замороженный backbone + линейная голова удерживает val AUC
≈ 0.7–0.85. Режим `--head mlp --freeze-epochs 0` оставлен для экспериментов
на большем объёме данных.
2. **Метки на уровне снимка.** Источник — `labels/labels_images.csv`, построенный
из экспертной таблицы командой `./run.sh label` (разбор — в
`assets/labeling.md`): в таблице отмечены критерии качества по каждому
исследованию, а каждая область встречается в нём ровно один раз, поэтому
вердикт переносится на снимок однозначно. Такой разметки — 76 нарушений из
251 (30.3 %). Правило выбрано измерением: учёт ручных пометок из имён файлов
дал худший результат на held-out наборе, поэтому в метках они не участвуют.
Резервный режим `--labels-csv ""` берёт метку из суффикса `_good`/`_bad` и
оставлен для совместимости.
3. **Склейка побайтных дублей.** В датасете 482 файла, но 251 уникальный снимок:
один и тот же кадр сохранён многократно под разными именами (часть — с меткой,
часть — без). Без склейки одно изображение попадало бы в оба класса. Лишние
байт-идентичные копии удалены.
4. **Разбиение по исследованиям.** Снимки одного исследования не попадают
одновременно в train и val — иначе метрики завышаются за счёт утечки.
5. **Аугментация отключена.** Проверено экспериментально: яркостный разброс и сдвиг
кадра снижают AUC с 0.87 до 0.56, потому что распределение яркости и положение
области сами являются признаками качества. Флаг `--augment` включает её для
экспериментов.
6. **Порог по логиту.** При доле нарушений ~15 % порог 0.5 даёт нулевой recall.
Порог подбирается по F1 на валидации и сохраняется в чекпоинт; решение
принимается по логиту (численно устойчиво при насыщении вероятностей).
### Схемы системы
| Компоненты | Поток данных |
|---|---|
| <img src="assets/img/architecture-components.png" width="100%" alt="Диаграмма компонентов: веб-клиент, FastAPI, модели DXA, оценка качества, файловая система"> | <img src="assets/img/architecture-dataflow.png" width="100%" alt="Поток данных: вход, предобработка, модели, анализ качества, выход"> |
| Последовательность детального анализа | Развёртывание |
|---|---|
| <img src="assets/img/architecture-sequence.png" width="100%" alt="Диаграмма последовательности: запрос /analyze/detailed, предобработка, инференс, регион, сегментация, отчёт"> | <img src="assets/img/architecture-deployment.png" width="100%" alt="Диаграмма развёртывания: клиент, приложение, ML-пайплайн, инфраструктура"> |
| Определение анатомической области | Основные сущности |
|---|---|
| <img src="assets/img/architecture-region-detection.png" width="100%" alt="Алгоритм определения области: порог по перцентилю яркости, bounding box, соотношение сторон, сторона бедра"> | <img src="assets/img/architecture-classes.png" width="100%" alt="Диаграмма классов: классификатор, модель, детальная оценка, определение области"> |
Схемы взяты из проектного документа и показывают целевую архитектуру: блоки
`Orchestrator`, UNet-сегментации и готовых вердиктов качества в решении не
реализованы. Работающий путь описан выше.
---
## Формат выходных данных
Основные столбцы соответствуют требованиям задания:
| Столбец | Описание |
|---|---|
| `path_to_study` | Путь к исследованию (для HTTP-загрузки — `upload://<имя>`) |
| `study_uid` | StudyInstanceUID |
| `image_uid` | SOPInstanceUID |
| `anatomical_region` | `spine` / `hip_left` / `hip_right` / `hip` |
| `quality_class` | 0 — качественное, 1 — есть нарушение |
| `violation_type` | Тип нарушения или пустая строка |
| `processing_status` | `Success` или `Failure: <причина>` |
| `time_of_processing` | Время обработки, секунды |
Дополнительно добавляются `confidence`, `violation_reason`, `region_confidence`
— они не мешают автоматическому разбору обязательных столбцов.
## API
| Метод | Путь | Назначение |
|---|---|---|
| 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` | Сохранить вердикт специалиста |
| DELETE | `/api/v1/labeling/verdict` | Снять вердикт, вернуть снимок к построенной разметке |
| GET | `/api/v1/labeling/export` | Выгрузка разметки: `scope=all` принимается обучением как `--labels-csv` |
```bash
curl -X POST http://localhost:8000/api/v1/analyze -F "file=@study/spine.dcm"
```
```json
{
"study_uid": "1.2.643...",
"image_uid": "1.2.643...",
"anatomical_region": "spine",
"quality_class": 1,
"quality_label": "Есть нарушение качества",
"violation_type": "artifact",
"violation_type_label": "Артефакты и импланты",
"violation_type_is_heuristic": true,
"violation_type_note": "Тип нарушения определён эвристикой по метрикам снимка, а не моделью...",
"reason": "Посторонние включения или артефакты в зоне интереса",
"confidence": 0.72,
"threshold_probability": 0.5103,
"processing_status": "Success"
}
```
Коды `violation_type` берутся из единого словаря `src/dxa/violations.py`
(`positioning`, `axis_deviation`, `artifact`, `rotation`, `roi_incorrect`,
`motion`, `incomplete_anatomy`, `labeling_error`, `unspecified`) — первые пять
кодирует экспертная таблица, остальные приходят из критериев пригодности. Поле
`violation_type_is_heuristic` показывает, что тип определён эвристикой, а не
моделью: модель решает только бинарную задачу. Подписи для интерфейса отдаёт
сервер, своей копии словаря фронтенд не держит.
---
## Веб-интерфейс
Полоса состояния в шапке показывает, **какая** модель сейчас работает: имя
чекпоинта, источник разметки, эпоху и порог. По интерфейсу сразу видно, какая
версия решения отвечает, — без обращения к логам.
<div align="center">
<img src="assets/img/ui-banner.png" alt="Полоса состояния: загруженная модель, разметка, эпоха, порог" width="90%">
</div>
Строки таблицы результатов кликабельны, но об этом надо сказать прямо — иначе
не догадываются. Над таблицей висит подсказка «Нажмите на любую строку», в конце
каждой строки есть кнопка «Открыть», а клик по строке прокручивает страницу к
панели деталей. Подсказка скрывается, когда фильтр не оставил ни одной строки.
Кнопка «Открыть» — настоящая кнопка, поэтому панель доступна и с клавиатуры
(Tab + Enter), а не только мышью. Сама таблица со статистикой и подсказкой — на
первом скриншоте.
Панель деталей открывается по клику на строку: заключение, измерения и
визуализация снимка. Специально показаны обе ветки — «нарушение» и «норма»:
<table>
<tr>
<td width="50%"><img src="assets/img/ui-detail-violation.png" width="100%" alt="Панель деталей для снимка с нарушением: красный бейдж, уровень HIGH, заключение с вероятностью и порогом, визуализация"></td>
<td width="50%"><img src="assets/img/ui-detail-clean.png" width="100%" alt="Панель деталей для качественного снимка: зелёный бейдж, справочные измерения"></td>
</tr>
<tr>
<td><sub><b>Нарушение.</b> Бейдж «Нарушение», уровень HIGH, заключение с вероятностью и порогом, тип нарушения с пометкой «эвристика».</sub></td>
<td><sub><b>Норма.</b> Зелёный бейдж. Измерения (резкость, границы области) подписаны как справочные — их пороги не калиброваны.</sub></td>
</tr>
</table>
Панель **«О модели»** (кнопка в шапке): что за чекпоинт работает, на какой
разметке он обучен, с каким порогом решает, метрики сравнения с 95 % интервалами,
состав данных, словарь типов нарушений и список ограничений.
<div align="center">
<img src="assets/img/ui-model-panel.png" alt="Панель «О модели»: сведения о чекпоинте, метрики с интервалами, состав данных, словарь нарушений" width="90%">
</div>
Источник всех этих значений — сервер (`/api/v1/health`, `/api/v1/model`).
Фронтенд намеренно не держит собственных копий: раньше подписи типов нарушений
были продублированы в `dxa-app.js` и разошлись с серверными, из-за чего коды
экспертной таблицы показывались как есть.
Что интерфейс теперь не утверждает:
- **тип нарушения помечен как «эвристика»** с пояснением — модель решает только
бинарную задачу и тип не предсказывает;
- **метрика рабочего чекпоинта помечена как завышенная** прямо в баннере
состояния и в карточке: чекпоинт выбран лучшим из пяти seed'ов, честная
оценка варианта — среднее по seed'ам;
- **плитка «Ср. уверенность модели»** больше не называется точностью: точность
требует эталонных меток, которых для произвольного файла нет;
- числовые метрики в панели деталей по-прежнему идут под дисклеймером о
некалиброванности порогов.
### Ручная разметка (`/label`)
Поштучной экспертной оценки снимков в наборе нет: таблица оценивает исследование,
и снимок наследует вердикт исследования. Закрыть это автоматически нечем —
локальная vision-модель оказалась непригодна (`assets/labeling.md` §11). Поэтому
в сервисе есть отдельный интерфейс ручной разметки: раздел 2.6 задания просит
«автоматическую коррекцию разметки с возможностью подтверждения специалистом».
Откройте `http://localhost:8000/label`. Интерфейс показывает снимок целиком и его
текущую метку с указанием источника (`экспертная таблица`, `имя файла`, `ручной
вердикт`). Снимки, где метка разметки расходится с пометкой в имени файла, идут
первыми: там ошибка возможна в любом из источников. Специалист подтверждает
вердикт или ставит свой — область, «годное / нарушение», тип нарушения,
комментарий.
Вердикты сохраняются в `labels/manual_labels.csv` в том же формате, что и
построенная разметка, поэтому файл можно сразу передать обучению:
```bash
curl -o manual_labels.csv "http://localhost:8000/api/v1/labeling/export?scope=all"
python -m src.dxa.train --labels-csv manual_labels.csv
```
`scope=all` накладывает ручные вердикты на построенную разметку (весь набор),
`scope=reviewed` отдаёт только разобранные снимки — для сверки с построенной
разметкой. Оценка модели в интерфейсе намеренно не показывается: подсказка
смещала бы разметчика, а ценность здесь именно в независимом суждении.
### Передать разметку врачу: автономный HTML-пакет
Интерфейс `/label` требует запущенного сервиса, а размечать должен специалист — на
своей машине, без Python и без сети. Поэтому тот же сценарий упаковывается в один
файл: снимки встроены внутрь, вердикты хранятся в браузере, выгрузка — CSV.
```bash
./run.sh review --out review/doctor_review.html # весь набор, ~7 МБ
./run.sh review --limit 20 --out review/pilot.html # пилот на выборке из 20 снимков
```
Файл врач открывает двойным щелчком: снимок, построенная метка с указанием
источника, кнопки «годное / нарушение», тип нарушения, комментарий, горячие
клавиши. Работает полностью офлайн — страница не делает ни одного сетевого
запроса, и это проверяется автоматически (`tests/browser/review_pack.js`).
Прогресс сохраняется в браузере, а кнопка «Выгрузить CSV» отдаёт файл, который
принимается обучением как есть; загрузить его обратно можно на другой машине —
кнопкой «Загрузить CSV».
Вердикты врача накладываются на построенную разметку одной командой, с проверкой,
что все пути есть в датасете:
```bash
./run.sh review --merge review/doctor.csv --out labels/labels_images_reviewed.csv
python -m src.dxa.train --labels-csv labels/labels_images_reviewed.csv
```
Каталог `review/` в git не хранится: пакет генерируется, а внутри — медицинские
снимки.
---
## Метрики
Метрики зависят от выбранного разбиения по исследованиям, поэтому приводятся
с разбросом. Основная оценка — на **фиксированном** разбиении по исследованиям
(обучение 198 снимков / 81 исследование, валидация 53 снимка / 19 исследований,
16 нарушений), пять seed'ов обучения, эталон — вердикт эксперта:
| Что измерено | Значение | Как измерено |
|---|---|---|
| ROC-AUC | **0.6726** [0.6367, 0.7086] | 5 seed'ов на фиксированном разбиении |
| PR-AUC | 0.4753 [0.4188, 0.5318] | там же; базовый уровень при 30 % нарушений — 0.30 |
| F1 | 0.5559 [0.5212, 0.5906] | там же; порог подобран на той же валидации — смещено вверх |
| ROC-AUC по областям | позвоночник 0.929, бедро R 0.606, бедро L 0.567 | рабочий чекпоинт, собственная валидация |
| Контрольная задача «позвоночник / бедро» | AUC 1.00 | проверка работоспособности пайплайна |
| Модель использует снимок, а не область | AUC 0.885 против 0.534 у правила области | `discriminator` на всём наборе, включая обучающие снимки |
Метрики по областям — в `models/train_report.md`, он создаётся при обучении.
Разбивка важна, потому что нарушения распределены неравномерно: в позвоночнике
33 из 99, у бёдер 20–22 из 73–78, а область почти однозначно определяется по
ширине кадра. Поэтому общий AUC частично отражает различение области, а не только
распознавание дефекта.
### Выбор правила разметки
Метку можно было строить только из экспертной таблицы или дополнительно
учитывать пометки, проставленные вручную в именах файлов (суффикс `_bad`).
Пометки расходились с оценкой эксперта в 62 случаях из 251, поэтому правило
выбиралось измерением: одно разбиение, пять seed'ов, один эталон.
| Метрика (эталон) | только таблица | с суффиксами имён |
|---|---|---|
| ROC-AUC | **0.6726** [0.6367, 0.7086] | 0.6003 [0.5592, 0.6415] |
| PR-AUC | **0.4753** [0.4188, 0.5318] | 0.3864 [0.3487, 0.4242] |
| F1 | **0.5559** [0.5212, 0.5906] | 0.5354 [0.4978, 0.5729] |
| Recall / Precision | 0.713 / 0.472 | 0.788 / 0.409 |
Парная разница (только таблица − с суффиксами): ROC-AUC **+0.0723**
[+0.0541, +0.0905], PR-AUC **+0.0889** [+0.0500, +0.1277] — знаки `+++++`, то
есть преимущество на всех пяти seed'ах.
**Принято правило «только экспертная таблица».** Пометки в именах файлов
проставлялись вручную и оказались ненадёжными: они ухудшали согласие модели с
экспертом на невиданных исследованиях. Оговорка: эталон — та же таблица, поэтому
вариант, обучавшийся на ней, в выигрышном положении; значимо то, что добавление
ненадёжных пометок согласие **снижает**.
Отчёт с полными числами и разбором ограничений — `assets/labeling.md`; отчёт
обучения рабочего чекпоинта — `models/train_report.json`. Само сравнение правил
хранится только в виде чисел в этих документах: чекпоинты прогонов удалены,
а пересобрать их можно командой `./run.sh split && ./run.sh compare`.
Время обработки одного снимка — порядка 0.02–0.05 с на CPU (ResNet18 с
замороженным backbone), то есть требование «не более 3 минут на исследование»
выполняется с большим запасом.
---
## Ограничения (важно для интерпретации)
1. **Разметка выведена из оценки исследования.** Экспертная таблица описывает
исследование, а не снимок; перенос однозначен (область встречается один раз),
но поштучной экспертной оценки снимков в наборе нет. Происхождение каждой
строки зафиксировано в `labels/labels_images.csv`. Для поштучной разметки есть
интерфейс `/label` (см. «Веб-интерфейс»), им ещё не пользовались. Разбор — в
`assets/labeling.md`.
2. **Мало данных.** 251 уникальный снимок, 76 нарушений. Доверительные интервалы
широкие; оценка на закрытом наборе может отличаться.
3. **Тип нарушения определяется эвристиками, а не обученной моделью.** Для
честного мультикласса нужна разметка типов на уровне снимка — её даёт `/label`.
4. **Область определяется по размеру кадра.** Признак безошибочно работает на этом
оборудовании (99/99 для позвоночника), но при смене аппарата порог
`SPINE_MIN_WIDTH` потребует калибровки.
5. **В DICOM нет разметки ROI.** Ни overlay, ни graphic annotation в файлах нет,
поэтому корректность нанесённых областей измерения нельзя проверить прямым
сравнением — оценивается только геометрия видимой зоны.
6. **Эвристики из `src/quality/detailed_assessment.py` не калиброваны.**
Пороги для «движения», «артефактов» и отступов ROI рассчитаны на другой
масштаб интенсивностей: на этом наборе они срабатывают почти для любого
снимка, а признак резкости ведёт себя противоположно в позвоночнике и
бёдрах. Поэтому в панели деталей показываются **числовые измерения** с
пометкой «справ.», а не вердикты «Да/Нет». Классификацию выполняет только
модель. Проверить её вклад можно командой:
```bash
python -m src.dxa.discriminator --model-path models/dxa_model.pth
```
## Что проверено и как
| Проверка | Команда | Результат |
|---|---|---|
| Модель использует снимок, а не только область | `python -m src.dxa.discriminator` | AUC 0.885 против 0.534 у правила «позвоночник = нарушение»; внутри областей у модели 0.88–0.95, у правила 0.50 |
| Контракт API для веб-интерфейса | `python -m pytest tests/test_api_contract.py` | поля панели деталей, различимость метрик, PNG-визуализации, отсутствие некалиброванных вердиктов, канонические коды типов нарушений, карточка модели |
| Ручная разметка | `python -m pytest tests/test_manual_labels.py tests/test_labeling_api.py` | проверка вердикта (область, метка, совместимость типа нарушения с областью), хранение и наложение на построенную разметку, отказ отдавать файлы вне датасета, выгрузка, читаемая обучением |
| Пакет разметки для врача | `python -m pytest tests/test_review_pack.py` | отпечаток набора, порядок «расхождения первыми», встроенные данные, отсутствие сетевых ссылок в странице, слияние вердиктов с разметкой |
| Единый словарь нарушений | `python -m pytest tests/test_violations.py` | коды, подписи, коды SR, приведение устаревших значений, согласованность с таблицей |
| Разметка и разбиение данных | `python -m pytest tests/test_labels.py` | склейка дублей, разбор имён, фиксация разбиения, отсутствие утечки между train/val |
| Имена DICOM-файлов | `python -m pytest tests/test_rename_files.py` | разбор имён, поиск свободного номера при конфликте, отказ от угадывания области, цикл «применить → откатить» |
| Разметка по экспертной таблице | `python -m pytest tests/test_excel_labels.py` | чтение критериев, голосование по области, перенос на единственное бедро, правила `table`/`union`/`expert`, подключение к обучению |
| Выбор правила метки | `./run.sh split && ./run.sh compare` | ROC-AUC 0.6726 против 0.6003 по эталону, парная Δ +0.0723 [+0.0541, +0.0905], 5/5 seed'ов в пользу экспертной таблицы |
| Метрики и порог | `python -m pytest tests/test_preprocess_and_model.py` | подбор порога при дисбалансе, roundtrip чекпоинта, BatchNorm |
| Веб-интерфейс в браузере | `node tests/browser/ui_check.js` | подсказка о кликабельности строк видна и скрывается на пустом фильтре; кнопка «Открыть» открывает панель; значения панели меняются при переключении строк; панель «О модели» наполняется метриками и словарём |
| Интерфейс ручной разметки в браузере | `node tests/browser/ui_labeling.js` | расхождения идут первыми, снимок отрисовывается, вердикт сохраняется и переживает перезагрузку страницы, фильтр «только расхождения» работает, внешних запросов нет |
| Работа без сети | `node tests/browser/ui_offline.js` | ноль внешних запросов, стили и иконки на месте |
Браузерные проверки требуют запущенного сервера:
```bash
python -m uvicorn src.main:app --port 8123
node tests/browser/ui_check.js # панель деталей обновляется по клику
node tests/browser/ui_offline.js # работа без доступа к внешним сервисам
# проверка ручной разметки пишет вердикты, поэтому файл стоит отвести в /tmp:
DXA_MANUAL_LABELS=/tmp/manual_ui.csv python -m uvicorn src.main:app --port 8123
node tests/browser/ui_labeling.js
```
Офлайн-режим обеспечен локальными копиями Tailwind и FontAwesome
(`src/api/static/vendor`, `src/api/static/webfonts`); страница не обращается к CDN.
## План доработки
- Разметить типы нарушений на уровне снимка и обучить мультилейбл-классификатор.
- Собрать 500+ исследований для устойчивых метрик и честной валидации.
- Заменить порог по ширине кадра на калибровку по метаданным аппарата.
---
## Структура проекта
```
bone_2026/
├── src/
│ ├── main.py # FastAPI: маршруты и загрузка модели
│ ├── dxa/ # действующий модуль оценки качества
│ │ ├── labels.py # разбор имён, метки, склейка дублей, сплит
│ │ ├── excel_labels.py # разметка снимков по экспертной таблице
│ │ ├── manual_labels.py # ручная разметка: хранение вердиктов (/label)
│ │ ├── review_pack.py # автономный HTML-пакет разметки для врача
│ │ ├── rename_files.py # приведение имён DICOM к единому виду
│ │ ├── violations.py # единый словарь типов нарушений (коды, подписи)
│ │ ├── model_card.py # карточка решения для /api/v1/model и интерфейса
│ │ ├── compare_labels.py # сравнение источников разметки на одном наборе
│ │ ├── discriminator.py # проверка вклада содержимого снимка
│ │ ├── render.py # рендер снимков и контактных листов
│ │ ├── preprocess.py # DICOM -> тензор (общий для обучения и API)
│ │ ├── dataset.py # Dataset и DataLoader
│ │ ├── model.py # сеть, метрики, подбор порога
│ │ ├── train.py # обучение и отчёт
│ │ └── inference.py # пакетный инференс, определение области
│ ├── quality/ # эвристики качества, используются API
│ └── api/static/ # веб-интерфейс (index.html — анализ, label.html — разметка)
├── labels/labels_images.csv # разметка снимков: официальная (+ .xlsx)
├── labels/manual_labels.csv # вердикты специалиста из /label (появляется после правок)
├── labels/labels_images_table.csv # вариант «только таблица» (то же, что выше)
├── labels/labels_images_union.csv # вариант «таблица или суффикс имени»
├── labels/labels_images_expert.csv # эталон для оценки: только снимки с оценкой
├── labels/split_expert_seed42.json # зафиксированное разбиение (19 исследований)
├── labels/rename_map.csv # карта переименований файлов (для отката)
├── assets/img/ # схемы архитектуры и скриншоты интерфейса
├── assets/labeling.md # как построена разметка и как сравнивались варианты
├── models/dxa_model.pth # рабочий чекпоинт (+ train_report.md/.json)
├── tests/ # pytest: метки, сплит, метрики, модель, API
├── dataset_hack/ # данные (в git не хранятся)
├── Dockerfile
├── requirements.txt
└── run.sh
```
## Запуск обучения
```bash
python -m src.dxa.train --epochs 100 --output-dir models
```
| Параметр | По умолчанию | Описание |
|---|---|---|
| `--data-root` | `dataset_hack` | Каталог датасета |
| `--annotation-path` | `dataset_hack/НД_для_обучения/разметка.xlsx` | Excel с разметкой (только отчёт о расхождениях) |
| `--labels-csv` | `labels/labels_images.csv` | Разметка снимков из `./run.sh label`; пустая строка — метки из имён файлов; отсутствующий файл — откат к именам с предупреждением |
| `--split-file` | — | Зафиксированное разбиение из `./run.sh split`; одинаковый held-out набор для сравнения вариантов разметки |
| `--backbone` | `resnet18` | `resnet18` / `resnet34` |
| `--head` | `linear` | `linear` (линейный зонд) / `mlp` |
| `--freeze-epochs` | `-1` | `-1` — backbone заморожен всегда; `0` — обучать всю сеть |
| `--epochs`, `--batch-size`, `--learning-rate`, `--weight-decay` | 100 / 16 / 3e-4 / 5e-2 | Оптимизация |
| `--balance` | `none` | `loss` / `sampler` для компенсации дисбаланса |
| `--val-fraction`, `--seed` | 0.2 / 42 | Разбиение по исследованиям |
| `--augment` | выключено | Включает яркостную аугментацию (ухудшает метрики, см. п. 5) |
| `--output-dir` | `models` | Куда сохранять чекпоинт и отчёты |
| `--dry-run` | — | Проверить разбор данных и разбиение без обучения |
После обучения в `--output-dir` появляются `dxa_model.pth`, `train_report.md`
и `train_report.json`; отчёт удобно приложить к презентации.
## Команда
- **Грачев Денис** — разработка
- **Грачев Татьяна** — капитан
<div align="center">
<sub>Built for Bone Quality Assessment Hackathon 2026</sub>
</div>