bone_2026/QWEN.md

213 lines
13 KiB
Markdown
Raw 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.

# 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 # имена -> метки, склейка дублей, разбиение по исследованиям
├── preprocess.py # DICOM -> CHW-тензор (единый путь для обучения и API)
├── dataset.py # DXADataset, DataLoader
├── model.py # сеть, метрики, подбор порога, сохранение/загрузка
├── train.py # обучение + отчёт (md/json)
└── inference.py # пакетный инференс, определение области, визуализация
```
### Ключевые решения (проверены экспериментально)
| Решение | Причина |
|---|---|
| Метки из имён файлов: `_bad` > `_good` > нет метки (=good) | Явная оценка в имени файла; отсутствие метки означает «хорошее» |
| Склейка побайтных дублей | 544 файла, но 252 уникальных снимка; без склейки снимок попадал в оба класса |
| Разбиение по исследованиям, не по снимкам | Исключение утечки: снимки одного исследования в одной части |
| Линейный зонд (замороженный backbone) | Полный fine-tune при ~250 снимках переобучается (val AUC → 0.5) |
| Порог по логиту, подбор по F1 | При 15 % нарушений порог 0.5 даёт нулевой recall; вероятности насыщаются |
| Аугментация выключена по умолчанию | Яркость и положение сами являются признаками качества: AUC 0.87 → 0.56 |
| Область по ширине кадра | Позвоночник 300 px, бедро 280 px; 99/99 для позвоночника |
| Панель деталей показывает измерения, а не вердикты | Эвристики `detailed_assessment` не калиброваны: `motion_detected`/`any_detected` истинны почти всегда, ROI-отступы срабатывают для 227/252 снимков |
### Проверка вклада модели
Не является ли модель просто детектором анатомии (в позвоночнике ~29 % нарушений
против ~4–5 % у бёдер, а область почти однозначно определяется по ширине кадра):
```bash
python -m src.dxa.discriminator --model-path models/dxa_model.pth
```
Результат на чекпоинте `models/dxa_model.pth`:
| Предиктор | Общий AUC | spine | hip_right | hip_left |
|---|---|---|---|---|
| Модель | 0.822 | 0.838 | 0.895 | 0.948 |
| Правило «позвоночник = нарушение» | 0.724 | 0.500 | 0.500 | 0.500 |
Модель использует содержимое снимка: внутри областей она даёт 0.84–0.95.
Правило по области внутри области всегда 0.50 (подсказки нет).
## Данные (`dataset_hack/`)
Структура:
```
dataset_hack/
├── Для теста/ # bad.dcm, l_hip.dcm, r_hip.dcm, spine.dcm
└── НД_для_обучения/
├── разметка.xlsx # экспертная оценка на уровне ИССЛЕДОВАНИЯ
└── Исследования/<study_uid>/.../<region>_<n>[_good|_bad].dcm
```
Факты, важные для обучения:
- 544 файла на диске, но **252 уникальных снимка** (по пиксельному содержимому).
- 86 файлов имеют явную метку; после склейки дублей — **37 нарушений из 252 (14.7 %)**.
- Дубли не пересекают границы исследований, конфликтов меток при склейке нет.
- Имена неоднородны: `spine_01`, `Spine`, `r_spine`, `spine-1`, `l_hip`,
`l_hip-2`, `r_hip`, `r_hop`.
- В DICOM **нет** разметки ROI (ни OverlayData, ни GraphicAnnotationSequence),
поэтому корректность нанесённых областей нельзя проверить прямым сравнением.
- Метка в Excel относится к исследованию и раздаётся его снимкам; имена файлов
имеют приоритет. Excel используется только для предупреждения о расхождениях.
### Единица разметки — источник шума
Один снимок в исследовании помечен `_bad`, остальные не размечены. Метка снимка
считается унаследованной от исследования, поэтому часть меток заведомо шумная.
Это главное ограничение текущего качества модели.
---
## Обучение
```bash
./run.sh train # режим по умолчанию
python -m src.dxa.train --dry-run # проверить данные без обучения
python -m src.dxa.train --head mlp --freeze-epochs 0 --epochs 30
```
Артефакты в `--output-dir`: `dxa_model.pth` (веса, порог, параметры
предобработки), `train_report.md`, `train_report.json`.
Чекпоинт самодостаточен: `backbone`, `head`, `preprocess`, `threshold_logit`
хранятся внутри, поэтому инференс не может рассинхронизироваться с обучением.
---
## API (`src/main.py`)
| Метод | Путь | Назначение |
|---|---|---|
| GET | `/` | Веб-интерфейс |
| GET | `/api/v1/health` | Статус, признак загрузки модели |
| 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 |
Путь к модели — переменная окружения `DXA_MODEL_PATH` (по умолчанию
`models/dxa_model.pth`), чтобы контейнер не зависел от рабочего каталога.
API и CLI используют один код предсказания (`predict_from_bytes` /
`predict_from_array`), поэтому предобработка и порог совпадают.
---
## Тесты
```bash
./run.sh test
python -m pytest tests/ -q # 79 тестов
```
- `tests/test_labels.py` — разбор имён, склейка дублей, отсутствие утечки при
разбиении.
- `tests/test_preprocess_and_model.py` — предобработка, метрики, подбор порога,
контракт модели, BatchNorm при заморозке, roundtrip чекпоинта.
- `tests/test_api_contract.py` — поля ответов, которые читает `dxa-app.js`
(панель деталей ранее показывала прочерки из-за расхождения ключей),
различимость метрик между снимками, валидность PNG-визуализаций, отсутствие
некалиброванных вердиктов в ответе.
### Проверка веб-интерфейса в браузере
`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_offline.js` — страница не обращается к внешним хостам.
### Офлайн-работа фронтенда
Tailwind и FontAwesome лежат локально (`src/api/static/vendor`,
`src/api/static/webfonts`), страница не обращается к CDN. Требование методики —
работа без внешних сервисов; CDN-версии ломали оформление в закрытом контуре.
Наличие ассетов проверяется на этапе сборки образа (см. Dockerfile).
---
## Известные ограничения
1. Разметка на уровне исследования → шум в метках снимков.
2. Мало данных: 252 снимка, 37 нарушений; доверительные интервалы широкие.
3. Тип нарушения определяется эвристиками, а не обученной моделью.
4. Порог `SPINE_MIN_WIDTH` привязан к текущему оборудованию.
5. Grad-CAM (`src/models/visualization/gradcam.py`) есть, но не подключён.
## Устаревший код (не подключён к API)
Эти модули не импортируются из `src/main.py` и `src/dxa/*`; их зависимости
закомментированы в `requirements.txt`:
- `src/api/endpoints.py` — падает при импорте, роутер не монтируется.
- `src/api/annotation.py` — маршруты под `/api/annotation`, не монтируются.
- `src/core/orchestrator.py`, `src/pipeline/pipeline.py` — веса не загружаются.
- `src/model/unet.py`, `src/model/segmentator.py` — UNet-заглушки.
- `src/quality/artifact_detector.py`, `position_validator.py`, `medical_quality.py` — заглушки.
- `src/dataloaders/pet_dataset.py` — остаток прототипа (Oxford-IIIT Pet).
---
## Docker
```bash
docker build -t dxa-quality .
docker run -v /path/to/data:/data -p 8000:8000 dxa-quality
```
Dockerfile ставит зафиксированные версии, копирует только `src/`, `models/` и
`run.sh`, проверяет чекпоинт на этапе сборки и имеет HEALTHCHECK. Данные и тесты
в образ не попадают (`.dockerignore`).