diff --git a/QWEN.md b/QWEN.md index d4a7b50..7d743fa 100644 --- a/QWEN.md +++ b/QWEN.md @@ -35,7 +35,13 @@ ``` src/dxa/ -├── labels.py # имена -> метки, склейка дублей, разбиение по исследованиям +├── labels.py # имена -> метки, склейка дублей, разбиение, фиксация сплита +├── excel_labels.py # разметка снимков по экспертной таблице (labels_images.csv) +├── 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 # сеть, метрики, подбор порога, сохранение/загрузка @@ -43,37 +49,47 @@ src/dxa/ └── inference.py # пакетный инференс, определение области, визуализация ``` +Артефакты вне кода: `labels/labels_images.csv|.xlsx` (официальная разметка), +`labels/labels_images_{table,union,expert}.csv` (варианты правила и эталон), +`labels/split_expert_seed42.json` (зафиксированное разбиение), +`labels/rename_map.csv` (карта переименований), `docs/labeling.md` +(как построена разметка и как выбиралось правило), `models/archive/` +(прежние чекпоинты), `models/compare_rules/` (чекпоинты и отчёт сравнения правил). + ### Ключевые решения (проверены экспериментально) | Решение | Причина | |---|---| -| Метки из имён файлов: `_bad` > `_good` > нет метки (=good) | Явная оценка в имени файла; отсутствие метки означает «хорошее» | +| Единый словарь типов нарушений (`src/dxa/violations.py`) | Коды, подписи и коды SR были в трёх копиях (инференс, `main.py`, `dxa-app.js`) и не знали кодов экспертной таблицы. Теперь подписи отдаёт сервер, фронт копий не держит | +| Метки только из экспертной таблицы (правило `table`): `labels/labels_images.csv`, 77 нарушений | Таблица описывает исследование, но каждая область встречается в нём один раз, поэтому вердикт переносится на снимок однозначно. Правило выбрано измерением: учёт ручных пометок из имён файлов дал ROC-AUC 0.6199 против 0.6764, хуже на всех 5 seed'ах. См. `docs/labeling.md` | | Склейка побайтных дублей | 544 файла, но 252 уникальных снимка; без склейки снимок попадал в оба класса | | Разбиение по исследованиям, не по снимкам | Исключение утечки: снимки одного исследования в одной части | | Линейный зонд (замороженный backbone) | Полный fine-tune при ~250 снимках переобучается (val AUC → 0.5) | -| Порог по логиту, подбор по F1 | При 15 % нарушений порог 0.5 даёт нулевой recall; вероятности насыщаются | +| Порог по логиту, подбор по F1 | При доле нарушений около 30 % порог 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`: +Результат на рабочем чекпоинте `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.854 | 0.928 | 0.861 | 0.853 | +| Правило «позвоночник = нарушение» | 0.529 | 0.500 | 0.500 | 0.500 | -Модель использует содержимое снимка: внутри областей она даёт 0.84–0.95. -Правило по области внутри области всегда 0.50 (подсказки нет). +Модель использует содержимое снимка: внутри областей она даёт 0.85–0.93. +Правило по области внутри области всегда 0.50 (подсказки нет). Честная оценка на +held-out — в `docs/labeling.md` §8: ROC-AUC 0.6764 [0.6309, 0.7218] по пяти seed'ам. ## Данные (`dataset_hack/`) @@ -89,37 +105,75 @@ dataset_hack/ Факты, важные для обучения: -- 544 файла на диске, но **252 уникальных снимка** (по пиксельному содержимому). -- 86 файлов имеют явную метку; после склейки дублей — **37 нарушений из 252 (14.7 %)**. +- 544 файла на диске, но **252 уникальных снимка** (по пиксельному содержимому) + на 100 исследований: позвоночник 99, бедро R 79, бедро L 73, 1 с неопределённой + областью. +- Экспертная таблица отмечает нарушения у **74 снимков (29.4 %)**; три снимка + таблица область не оценивала. +- Рабочая разметка (правило `table`) — **77 нарушений из 252 (30.6 %)**: 74 по + таблице плюс 3 снимка без экспертной оценки, помеченных `filename_fallback`. - Дубли не пересекают границы исследований, конфликтов меток при склейке нет. -- Имена неоднородны: `spine_01`, `Spine`, `r_spine`, `spine-1`, `l_hip`, - `l_hip-2`, `r_hip`, `r_hop`. -- В DICOM **нет** разметки ROI (ни OverlayData, ни GraphicAnnotationSequence), - поэтому корректность нанесённых областей нельзя проверить прямым сравнением. -- Метка в Excel относится к исследованию и раздаётся его снимкам; имена файлов - имеют приоритет. Excel используется только для предупреждения о расхождениях. + Два побайтных дубля названы по-разному, поэтому область определяется + голосованием по именам файлов. +- Имена файлов приведены к виду `<область>_[_good|_bad].dcm` (`spine`, + `l_hip`, `r_hip`); инструмент — `src/dxa/rename_files.py`, карта отката — + `labels/rename_map.csv`. Суффиксы `_good`/`_bad` проставлялись вручную, в + метках **не участвуют** — только как диагностический столбец + `quality_from_filename`: они расходились с оценкой эксперта в 15 случаях из 252. +- В DICOM **нет** разметки ROI (ни OverlayData, ни GraphicAnnotationSequence) и + пусты теги `Laterality`/`ImageLaterality`, поэтому ни корректность областей, ни + сторону бедра нельзя проверить по метаданным. +- Столбец `study` в таблице — имя каталога исследования, а **не** + StudyInstanceUID из DICOM (в датасете они разные, соответствие 1:1). -### Единица разметки — источник шума +### Единица разметки -Один снимок в исследовании помечен `_bad`, остальные не размечены. Метка снимка -считается унаследованной от исследования, поэтому часть меток заведомо шумная. -Это главное ограничение текущего качества модели. +Таблица описывает исследование, а не снимок. Однако каждая анатомическая область +встречается в исследовании ровно один раз (после склейки дублей), поэтому вердикт +исследования по области переносится на снимок однозначно — не нужно решать, какой +из нескольких снимков «плохой». Так получены метки и типы нарушений +(`labels/labels_images.csv`); каждый источник свидетельства сохранён в отдельном +столбце, поэтому правило можно переиграть без повторного разбора. Почему выбрано +именно правило «только таблица» — в `docs/labeling.md` §4. --- ## Обучение ```bash -./run.sh train # режим по умолчанию +./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 варианта и оценивает оба по этому же эталону; отчёт — +`models/compare_rules/rule_comparison.md`. Возобновить без переобучения — +`./run.sh compare --skip-training`. + Артефакты в `--output-dir`: `dxa_model.pth` (веса, порог, параметры предобработки), `train_report.md`, `train_report.json`. -Чекпоинт самодостаточен: `backbone`, `head`, `preprocess`, `threshold_logit` -хранятся внутри, поэтому инференс не может рассинхронизироваться с обучением. +Чекпоинт самодостаточен: `backbone`, `head`, `preprocess`, `threshold`, а также +`labels_csv` и `split_file` хранятся внутри, поэтому инференс не может +рассинхронизироваться с обучением, а по файлу видно, на какой разметке он обучен. + +Рабочий чекпоинт — `models/dxa_model.pth` (правило `table`, seed 42 по умолчанию, +эпоха 39, порог логита −0.4930 → вероятность 0.379; val ROC-AUC 0.6706 при +честной оценке 0.6764 [0.6309, 0.7218] по пяти seed'ам). Прежние чекпоинты — +в `models/archive/` (см. README внутри), откат одной командой `cp`. --- @@ -128,7 +182,8 @@ python -m src.dxa.train --head mlp --freeze-epochs 0 --epochs 30 | Метод | Путь | Назначение | |---|---|---| | GET | `/` | Веб-интерфейс | -| GET | `/api/v1/health` | Статус, признак загрузки модели | +| GET | `/api/v1/health` | Статус, признак загрузки модели и её происхождение (разметка, разбиение, порог, эпоха) | +| GET | `/api/v1/model` | Карточка решения: разметка, данные, метрики с интервалами, словарь нарушений, ограничения | | POST | `/api/v1/analyze` | Базовый анализ файла | | POST | `/api/v1/analyze/detailed` | Расширенный отчёт, опционально маска | | POST | `/api/v1/analyze/sr` | Текстовый отчёт DICOM SR | @@ -146,11 +201,19 @@ API и CLI используют один код предсказания (`predi ```bash ./run.sh test -python -m pytest tests/ -q # 79 тестов +python -m pytest tests/ -q # 208 тестов ``` - `tests/test_labels.py` — разбор имён, склейка дублей, отсутствие утечки при - разбиении. + разбиении, фиксация разбиения в файле (`export_split` / `load_split`). +- `tests/test_rename_files.py` — приведение имён: разбор, поиск свободного + номера при конфликте, отказ от угадывания области, цикл «применить → откатить». +- `tests/test_excel_labels.py` — разметка по экспертной таблице: чтение + критериев, «1 = нарушение», голосование по области, перенос оценки на + единственное бедро, три правила метки (`table` / `union` / `expert`) и их + согласованность, подключение к обучению. +- `tests/test_violations.py` — единый словарь типов: коды и подписи, коды SR, + приведение устаревших значений, согласованность с критериями таблицы. - `tests/test_preprocess_and_model.py` — предобработка, метрики, подбор порога, контракт модели, BatchNorm при заморозке, roundtrip чекпоинта. - `tests/test_api_contract.py` — поля ответов, которые читает `dxa-app.js` @@ -165,10 +228,24 @@ python -m pytest tests/ -q # 79 тестов панели деталей. Используется временный профиль Chrome, профиль пользователя не затрагивается. Требуется запущенный сервер на `127.0.0.1:8123`. -- `ui_check.js` — значения панели меняются при переключении строк. +- `ui_check.js` — подсказка о кликабельности строк видна и скрывается, когда + фильтр не оставил строк; кнопка «Открыть» в строке открывает панель деталей; + значения панели меняются при переключении строк; панель «О модели» наполняется + метриками и словарём (иначе раздел остался бы пустым каркасом). - `ui_violation.js` — ветка «нарушение» (бейдж, POOR, HIGH, заключение). - `ui_offline.js` — страница не обращается к внешним хостам. +### Честность интерфейса + +Веб-интерфейс не должен утверждать больше, чем известно решению, поэтому: + +- тип нарушения показан с пометкой «эвристика» и пояснением, что модель решает + только бинарную задачу; +- ROC-AUC рабочего чекпоинта в баннере состояния помечена как завышенная (он + выбран лучшим из пяти seed'ов), а честная оценка лежит в панели «О модели»; +- плитка средней уверенности не называется точностью; +- подписи типов и метрики приходят с сервера (`/api/v1/model`), копий в JS нет. + ### Офлайн-работа фронтенда Tailwind и FontAwesome лежат локально (`src/api/static/vendor`, @@ -180,11 +257,21 @@ Tailwind и FontAwesome лежат локально (`src/api/static/vendor`, ## Известные ограничения -1. Разметка на уровне исследования → шум в метках снимков. -2. Мало данных: 252 снимка, 37 нарушений; доверительные интервалы широкие. -3. Тип нарушения определяется эвристиками, а не обученной моделью. -4. Порог `SPINE_MIN_WIDTH` привязан к текущему оборудованию. -5. Grad-CAM (`src/models/visualization/gradcam.py`) есть, но не подключён. +1. Разметка снимков выведена из таблицы, описывающей исследование: поштучной + экспертной оценки снимков в наборе нет. Оценка качества модели упирается в + качество этой разметки, а не только в объём данных. +2. Мало данных: 252 снимка, 77 нарушений; доверительные интервалы широкие + (ROC-AUC 0.6764 [0.6309, 0.7218] по пяти seed'ам). +3. Эталон оценки — та же экспертная таблица, независимой истины нет; сравнение + правил разметки частично благоприятствует варианту «только таблица». +4. Тип нарушения определяется эвристиками, а не обученной моделью; 5 снимков + имеют только `unspecified`, потому что источник не указывает критерий. +5. Три снимка без экспертной оценки размечены по пометке в имени файла и помечены + `filename_fallback`. +6. Сторона бедра в 7 исследованиях с единственным снимком не проверяема: теги + `Laterality` пусты, оценка взята из единственного заполненного столбца. +7. Порог `SPINE_MIN_WIDTH` привязан к текущему оборудованию. +8. Grad-CAM (`src/models/visualization/gradcam.py`) есть, но не подключён. ## Устаревший код (не подключён к API) @@ -207,6 +294,10 @@ docker build -t dxa-quality . docker run -v /path/to/data:/data -p 8000:8000 dxa-quality ``` -Dockerfile ставит зафиксированные версии, копирует только `src/`, `models/` и -`run.sh`, проверяет чекпоинт на этапе сборки и имеет HEALTHCHECK. Данные и тесты -в образ не попадают (`.dockerignore`). +Dockerfile ставит зафиксированные версии, копирует только `src/` и `run.sh`, +проверяет импорт приложения и наличие офлайн-ассетов фронтенда на этапе сборки и +имеет HEALTHCHECK. Чекпоинт в образ не копируется — он монтируется в `/app/models` +при запуске (`DXA_MODEL_PATH`). Данные, тесты и `labels/` в образ не попадают +(`.dockerignore`), поэтому `./run.sh train` внутри контейнера возьмёт метки из +имён файлов (с предупреждением); для обучения в контейнере смонтируйте `labels/` +или передайте свой `--labels-csv`. diff --git a/README.md b/README.md index ec6145d..4e72d09 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ принимает DICOM, определяет анатомическую область, оценивает, пригодно ли изображение для клинической интерпретации, и формирует структурированный отчёт. +
+ Результаты обработки в веб-интерфейсе: полоса состояния, статистика и таблица снимков +
+ ## Что делает решение | Шаг | Реализация | @@ -13,7 +17,7 @@ | Тип нарушения | Общая категория для снимков с нарушением; детальный тип требует разметки типов на уровне снимка | | Отчёт | XLSX/CSV со столбцами из требований; опционально zip с визуализацией зоны интереса | | API | FastAPI: анализ, детальный анализ, пакетная обработка, экспорт, DICOM SR (текст) | -| Веб-интерфейс | Загрузка DICOM, таблица результатов, панель деталей с визуализацией | +| Веб-интерфейс | Загрузка DICOM, таблица результатов, панель деталей с визуализацией, панель «О модели» | ## Установка и запуск @@ -21,9 +25,12 @@ 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 # тесты ``` @@ -60,6 +67,12 @@ docker run -v /path/to/data:/data -p 8000:8000 dxa-quality ## Архитектура +
+ Пайплайн обработки DXA: загрузка DICOM, разбор метаданных, предобработка, инференс, определение области, метрики качества, отчёт +
+ +Схема работающего пути в терминах решения: + ``` DICOM ──▶ предобработка ──▶ ResNet18 (заморожен) ──▶ линейная голова ──▶ логит │ │ @@ -77,9 +90,15 @@ DICOM ──▶ предобработка ──▶ ResNet18 (замороже ≈ 0.7–0.85. Режим `--head mlp --freeze-epochs 0` оставлен для экспериментов на большем объёме данных. -2. **Метки из имён файлов.** Суффикс `_good`/`_bad` — экспертная оценка снимка; - отсутствие суффикса означает «изображение хорошее». Приоритет: - `_bad` > `_good` > нет метки. +2. **Метки на уровне снимка.** Источник — `labels/labels_images.csv`, построенный + из экспертной таблицы командой `./run.sh label` (разбор — в + `assets/labeling.md`): в таблице отмечены критерии качества по каждому + исследованию, а каждая область встречается в нём ровно один раз, поэтому + вердикт переносится на снимок однозначно. Такой разметки — 77 нарушений из + 252 (30.6 %). Правило выбрано измерением: учёт ручных пометок из имён файлов + дал худший результат на held-out наборе, поэтому в метках они не участвуют. + Резервный режим `--labels-csv ""` берёт метку из суффикса `_good`/`_bad` и + оставлен для совместимости. 3. **Склейка побайтных дублей.** В датасете 544 файла, но 252 уникальных снимка: один и тот же кадр сохранён многократно под разными именами (часть — с меткой, @@ -97,6 +116,25 @@ DICOM ──▶ предобработка ──▶ ResNet18 (замороже Порог подбирается по F1 на валидации и сохраняется в чекпоинт; решение принимается по логиту (численно устойчиво при насыщении вероятностей). +### Схемы системы + +| Компоненты | Поток данных | +|---|---| +| Диаграмма компонентов: веб-клиент, FastAPI, модели DXA, оценка качества, файловая система | Поток данных: вход, предобработка, модели, анализ качества, выход | + +| Последовательность детального анализа | Развёртывание | +|---|---| +| Диаграмма последовательности: запрос /analyze/detailed, предобработка, инференс, регион, сегментация, отчёт | Диаграмма развёртывания: клиент, приложение, ML-пайплайн, инфраструктура | + +| Определение анатомической области | Основные сущности | +|---|---| +| Алгоритм определения области: порог по перцентилю яркости, bounding box, соотношение сторон, сторона бедра | Диаграмма классов: классификатор, модель, детальная оценка, определение области | + +Схемы взяты из проектного документа и показывают целевую архитектуру: блоки +`Orchestrator`, UNet-сегментации и готовых вердиктов качества в API не подключены. +Работающий путь описан выше, неподключённые модули перечислены в разделе +«Структура проекта». + --- ## Формат выходных данных @@ -122,7 +160,8 @@ DICOM ──▶ предобработка ──▶ ResNet18 (замороже | Метод | Путь | Назначение | |---|---|---| | GET | `/` | Веб-интерфейс | -| GET | `/api/v1/health` | Статус и признак загрузки модели | +| GET | `/api/v1/health` | Статус, признак загрузки модели и её происхождение | +| GET | `/api/v1/model` | Карточка решения: разметка, данные, метрики с интервалами, словарь нарушений | | POST | `/api/v1/analyze` | Базовый анализ одного файла | | POST | `/api/v1/analyze/detailed` | Расширенный отчёт, опционально маска | | POST | `/api/v1/analyze/sr` | Текстовое представление отчёта DICOM SR | @@ -139,37 +178,136 @@ curl -X POST http://localhost:8000/api/v1/analyze -F "file=@study/spine.dcm" "image_uid": "1.2.643...", "anatomical_region": "spine", "quality_class": 1, - "quality_label": "Violation detected", - "violation_type": "quality_violation_detected", - "reason": "Выявлено нарушение качества изображения", + "quality_label": "Есть нарушение качества", + "violation_type": "artifact", + "violation_type_label": "Артефакты и импланты", + "violation_type_is_heuristic": true, + "violation_type_note": "Тип нарушения определён эвристикой по метрикам снимка, а не моделью...", + "reason": "Посторонние включения или артефакты в зоне интереса", "confidence": 0.72, - "threshold_probability": 0.6154, + "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` показывает, что тип определён эвристикой, а не +моделью: модель решает только бинарную задачу. Подписи для интерфейса отдаёт +сервер, своей копии словаря фронтенд не держит. + +--- + +## Веб-интерфейс + +Полоса состояния в шапке показывает, **какая** модель сейчас работает: имя +чекпоинта, источник разметки, эпоху и порог. По интерфейсу сразу видно, какая +версия решения отвечает, — без обращения к логам. + +
+ Полоса состояния: загруженная модель, разметка, эпоха, порог +
+ +Строки таблицы результатов кликабельны, но об этом надо сказать прямо — иначе +не догадываются. Над таблицей висит подсказка «Нажмите на любую строку», в конце +каждой строки есть кнопка «Открыть», а клик по строке прокручивает страницу к +панели деталей. Подсказка скрывается, когда фильтр не оставил ни одной строки. +Кнопка «Открыть» — настоящая кнопка, поэтому панель доступна и с клавиатуры +(Tab + Enter), а не только мышью. Сама таблица со статистикой и подсказкой — на +первом скриншоте. + +Панель деталей открывается по клику на строку: заключение, измерения и +визуализация снимка. Специально показаны обе ветки — «нарушение» и «норма»: + + + + + + + + + + +
Панель деталей для снимка с нарушением: красный бейдж, уровень HIGH, заключение с вероятностью и порогом, визуализацияПанель деталей для качественного снимка: зелёный бейдж, справочные измерения
Нарушение. Бейдж «Нарушение», уровень HIGH, заключение с вероятностью и порогом, тип нарушения с пометкой «эвристика».Норма. Зелёный бейдж. Измерения (резкость, границы области) подписаны как справочные — их пороги не калиброваны.
+ +Панель **«О модели»** (кнопка в шапке): что за чекпоинт работает, на какой +разметке он обучен, с каким порогом решает, метрики сравнения с 95 % интервалами, +состав данных, словарь типов нарушений и список ограничений. + +
+ Панель «О модели»: сведения о чекпоинте, метрики с интервалами, состав данных, словарь нарушений +
+ +Источник всех этих значений — сервер (`/api/v1/health`, `/api/v1/model`). +Фронтенд намеренно не держит собственных копий: раньше подписи типов нарушений +были продублированы в `dxa-app.js` и разошлись с серверными, из-за чего коды +экспертной таблицы показывались как есть. + +Что интерфейс теперь не утверждает: + +- **тип нарушения помечен как «эвристика»** с пояснением — модель решает только + бинарную задачу и тип не предсказывает; +- **метрика рабочего чекпоинта помечена как завышенная** прямо в баннере + состояния и в карточке: чекпоинт выбран лучшим из пяти seed'ов, честная + оценка варианта — среднее по seed'ам; +- **плитка «Ср. уверенность модели»** больше не называется точностью: точность + требует эталонных меток, которых для произвольного файла нет; +- числовые метрики в панели деталей по-прежнему идут под дисклеймером о + некалиброванности порогов. + --- ## Метрики Метрики зависят от выбранного разбиения по исследованиям, поэтому приводятся -с разбросом. Оценка на валидационной части (19 исследований, 51 снимок, -8 нарушений), разбиение по исследованиям: +с разбросом. Основная оценка — на **фиксированном** разбиении по исследованиям +(обучение 199 снимков / 81 исследование, валидация 53 снимка / 19 исследований, +16 нарушений), пять seed'ов обучения, эталон — вердикт эксперта: | Что измерено | Значение | Как измерено | |---|---|---| -| ROC-AUC, 5 разбиений | **0.76 ± 0.08** (0.64 – 0.84) | обучение по seed 0..4, порог по F1 | -| PR-AUC, 5 разбиений | 0.48 ± 0.17 | там же; базовый уровень при 15 % нарушений — 0.15 | -| F1, 5 разбиений | 0.54 ± 0.11 | там же (порог подобран на той же валидации — смещено вверх) | -| ROC-AUC, 5-фолдовая CV | **0.81 ± 0.08** | линейный зонд на тех же признаках, разбиение по исследованиям | +| ROC-AUC | **0.6764** [0.6309, 0.7218] | 5 seed'ов на фиксированном разбиении | +| PR-AUC | 0.4759 [0.4141, 0.5377] | там же; базовый уровень при 30 % нарушений — 0.30 | +| F1 | 0.5676 [0.5270, 0.6082] | там же; порог подобран на той же валидации — смещено вверх | +| ROC-AUC по областям | позвоночник 0.943, бедро R 0.576, бедро L 0.550 | рабочий чекпоинт, собственная валидация | | Контрольная задача «позвоночник / бедро» | AUC 1.00 | проверка работоспособности пайплайна | -| Перестановка меток (нулевая гипотеза) | AUC 0.64 | вклад случайных корреляций | +| Модель использует снимок, а не область | AUC 0.854 против 0.529 у правила области | `discriminator` на всём наборе, включая обучающие снимки | Метрики по областям — в `models/train_report.md`, он создаётся при обучении. -Разбивка важна, потому что нарушения распределены крайне неравномерно: в -позвоночнике ~29 % снимков с нарушением против ~4–5 % у бёдер, а область почти -однозначно определяется по ширине кадра. Поэтому общий AUC частично отражает -различение области, а не только распознавание дефекта. +Разбивка важна, потому что нарушения распределены неравномерно: в позвоночнике +33 из 99, у бёдер 21–22 из 73–79, а область почти однозначно определяется по +ширине кадра. Поэтому общий AUC частично отражает различение области, а не только +распознавание дефекта. + +### Выбор правила разметки + +Метку можно было строить только из экспертной таблицы или дополнительно +учитывать пометки, проставленные вручную в именах файлов (суффикс `_bad`). +Пометки расходились с оценкой эксперта в 15 случаях из 252, поэтому правило +выбиралось измерением: одно разбиение, пять seed'ов, один эталон. + +| Метрика (эталон) | только таблица | с суффиксами имён | +|---|---|---| +| ROC-AUC | **0.6764** [0.6309, 0.7218] | 0.6199 [0.5840, 0.6559] | +| PR-AUC | **0.4759** [0.4141, 0.5377] | 0.4046 [0.3702, 0.4391] | +| F1 | **0.5676** [0.5270, 0.6082] | 0.5426 [0.5073, 0.5778] | +| Recall / Precision | 0.700 / 0.486 | 0.863 / 0.404 | + +Парная разница (только таблица − с суффиксами): ROC-AUC **+0.0564** +[+0.0403, +0.0725], PR-AUC **+0.0713** [+0.0398, +0.1028] — знаки `+++++`, то +есть преимущество на всех пяти seed'ах. + +**Принято правило «только экспертная таблица».** Пометки в именах файлов +проставлялись вручную и оказались ненадёжными: они ухудшали согласие модели с +экспертом на невиданных исследованиях. Оговорка: эталон — та же таблица, поэтому +вариант, обучавшийся на ней, в выигрышном положении; значимо то, что добавление +ненадёжных пометок согласие **снижает**. + +Отчёт с полными числами и разбором ограничений — `assets/labeling.md`; +машинные отчёты — `models/compare_rules/rule_comparison.md` и +`models/train_report.json`. Время обработки одного снимка — порядка 0.02–0.05 с на CPU (ResNet18 с замороженным backbone), то есть требование «не более 3 минут на исследование» @@ -179,11 +317,12 @@ curl -X POST http://localhost:8000/api/v1/analyze -F "file=@study/spine.dcm" ## Ограничения (важно для интерпретации) -1. **Разметка исходных данных — на уровне исследования, а не снимка.** - В наборе один снимок помечен `_bad`, остальные снимки того же исследования - не размечены. Метка снимка считается унаследованной от исследования, поэтому - часть меток заведомо шумная. -2. **Мало данных.** 252 уникальных снимка, 37 нарушений. Доверительные интервалы +1. **Разметка выведена из оценки исследования.** Экспертная таблица описывает + исследование, а не снимок; перенос однозначен (область встречается один раз), + но поштучной экспертной оценки снимков в наборе нет. Происхождение каждой + строки зафиксировано в `labels/labels_images.csv`. Разбор — в + `assets/labeling.md`. +2. **Мало данных.** 252 уникальных снимка, 77 нарушений. Доверительные интервалы широкие; оценка на закрытом наборе может отличаться. 3. **Тип нарушения определяется эвристиками, а не обученной моделью.** Для честного мультикласса нужна разметка типов на уровне снимка. @@ -209,11 +348,15 @@ curl -X POST http://localhost:8000/api/v1/analyze -F "file=@study/spine.dcm" | Проверка | Команда | Результат | |---|---|---| -| Модель использует снимок, а не только область | `python -m src.dxa.discriminator` | AUC 0.82 против 0.72 у правила «позвоночник = нарушение»; внутри областей у модели 0.84–0.95, у правила 0.50 | -| Контракт API для веб-интерфейса | `python -m pytest tests/test_api_contract.py` | 15 тестов: поля панели деталей, различимость метрик, PNG-визуализации, отсутствие некалиброванных вердиктов | -| Разметка и разбиение данных | `python -m pytest tests/test_labels.py` | метки из имён, склейка дублей, отсутствие утечки между train/val | +| Модель использует снимок, а не только область | `python -m src.dxa.discriminator` | AUC 0.854 против 0.529 у правила «позвоночник = нарушение»; внутри областей у модели 0.85–0.93, у правила 0.50 | +| Контракт API для веб-интерфейса | `python -m pytest tests/test_api_contract.py` | поля панели деталей, различимость метрик, PNG-визуализации, отсутствие некалиброванных вердиктов, канонические коды типов нарушений, карточка модели | +| Единый словарь нарушений | `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.6764 против 0.6199 по эталону, парная Δ +0.0564 [+0.0403, +0.0725], 5/5 seed'ов в пользу экспертной таблицы | | Метрики и порог | `python -m pytest tests/test_preprocess_and_model.py` | подбор порога при дисбалансе, roundtrip чекпоинта, BatchNorm | -| Веб-интерфейс в браузере | `node tests/browser/ui_check.js` | значения панели меняются при переключении строк | +| Веб-интерфейс в браузере | `node tests/browser/ui_check.js` | подсказка о кликабельности строк видна и скрывается на пустом фильтре; кнопка «Открыть» открывает панель; значения панели меняются при переключении строк; панель «О модели» наполняется метриками и словарём | | Работа без сети | `node tests/browser/ui_offline.js` | ноль внешних запросов, стили и иконки на месте | Браузерные проверки требуют запущенного сервера: @@ -244,6 +387,13 @@ bone_2026/ │ ├── main.py # FastAPI: маршруты и загрузка модели │ ├── dxa/ # действующий модуль оценки качества │ │ ├── labels.py # разбор имён, метки, склейка дублей, сплит +│ │ ├── excel_labels.py # разметка снимков по экспертной таблице +│ │ ├── 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 # сеть, метрики, подбор порога @@ -252,8 +402,18 @@ bone_2026/ │ ├── quality/ # эвристики (частично используются API) │ ├── api/static/ # веб-интерфейс │ └── model/, core/, pipeline/ # устаревшие модули, не подключены к API -├── models/dxa_model.pth # чекпоинт (+ train_report.md) -├── tests/ # pytest: метки, сплит, метрики, модель +├── labels/labels_images.csv # разметка снимков: официальная (+ .xlsx) +├── 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) +├── models/archive/ # прежние чекпоинты (см. README внутри) +├── models/compare_rules/ # чекпоинты и отчёт сравнения правил метки +├── tests/ # pytest: метки, сплит, метрики, модель, API ├── dataset_hack/ # данные (в git не хранятся) ├── Dockerfile ├── requirements.txt @@ -270,6 +430,8 @@ 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` — обучать всю сеть | diff --git a/assets/img/architecture-classes.png b/assets/img/architecture-classes.png new file mode 100644 index 0000000..abe152a Binary files /dev/null and b/assets/img/architecture-classes.png differ diff --git a/assets/img/architecture-components.png b/assets/img/architecture-components.png new file mode 100644 index 0000000..9519af8 Binary files /dev/null and b/assets/img/architecture-components.png differ diff --git a/assets/img/architecture-dataflow.png b/assets/img/architecture-dataflow.png new file mode 100644 index 0000000..f29290c Binary files /dev/null and b/assets/img/architecture-dataflow.png differ diff --git a/assets/img/architecture-deployment.png b/assets/img/architecture-deployment.png new file mode 100644 index 0000000..44ffd64 Binary files /dev/null and b/assets/img/architecture-deployment.png differ diff --git a/assets/img/architecture-pipeline.png b/assets/img/architecture-pipeline.png new file mode 100644 index 0000000..f3fd6ac Binary files /dev/null and b/assets/img/architecture-pipeline.png differ diff --git a/assets/img/architecture-region-detection.png b/assets/img/architecture-region-detection.png new file mode 100644 index 0000000..4784a76 Binary files /dev/null and b/assets/img/architecture-region-detection.png differ diff --git a/assets/img/architecture-sequence.png b/assets/img/architecture-sequence.png new file mode 100644 index 0000000..50fdae7 Binary files /dev/null and b/assets/img/architecture-sequence.png differ diff --git a/assets/img/ui-banner.png b/assets/img/ui-banner.png new file mode 100644 index 0000000..5b1a3fa Binary files /dev/null and b/assets/img/ui-banner.png differ diff --git a/assets/img/ui-detail-clean.png b/assets/img/ui-detail-clean.png new file mode 100644 index 0000000..c6f1c35 Binary files /dev/null and b/assets/img/ui-detail-clean.png differ diff --git a/assets/img/ui-detail-violation.png b/assets/img/ui-detail-violation.png new file mode 100644 index 0000000..0810d2d Binary files /dev/null and b/assets/img/ui-detail-violation.png differ diff --git a/assets/img/ui-model-panel.png b/assets/img/ui-model-panel.png new file mode 100644 index 0000000..d97578a Binary files /dev/null and b/assets/img/ui-model-panel.png differ diff --git a/assets/img/ui-results.png b/assets/img/ui-results.png new file mode 100644 index 0000000..6c1255d Binary files /dev/null and b/assets/img/ui-results.png differ diff --git a/assets/labeling.md b/assets/labeling.md new file mode 100644 index 0000000..7bdf25b --- /dev/null +++ b/assets/labeling.md @@ -0,0 +1,214 @@ +# Разметка датасета и выбор правила метки + +Документ описывает, откуда берутся метки снимков, как проверялось правило +разметки и какие результаты из этого следуют. Числа проверяемы: ссылки на +источники приведены рядом. + +## 1. Задача разметки + +Экспертная оценка в наборе сделана на уровне **исследования**: в таблице +`разметка.xlsx` по каждому исследованию отмечены критерии качества, а не по +отдельному снимку. Модель же работает со снимками, поэтому вердикт исследования +нужно было перенести на каждый снимок — без догадок и без потери информации о +том, откуда метка взялась. + +## 2. Экспертная таблица + +Столбцы (две строки заголовков, данные с третьей): + +| Столбец | Смысл | Тип нарушения | +|---|---|---| +| 2 | Позвоночник: укладка | `positioning` | +| 3 | Позвоночник: ось | `axis_deviation` | +| 4 | Позвоночник: артефакты, наложения | `artifact` | +| 5, 6 | Бедро R: позиционирование/ротация, область интереса | `rotation`, `roi_incorrect` | +| 7, 8 | Бедро L: то же | `rotation`, `roi_incorrect` | +| 9, 10, 11 | Итог по области | — | +| 12 | Комментарий эксперта | — | + +**Семантика значений** установлена по данным, а не по формулировкам заголовков: + +- `1` в столбце критерия означает **нарушение**, хотя часть заголовков + сформулирована положительно («корректная укладка»); +- итог области равен логическому ИЛИ критериев: бёдра 72/72 и 78/78, + позвоночник 96/99; +- три расхождения позвоночника (два случая «итог без критериев», один «критерий + без итога») трактуются как нарушение — по правилу «хотя бы один существенный + пункт нарушен»; +- заполненность: позвоночник 99/100, бедро R 72/100, бедро L 78/100; у 23 + исследований есть комментарий. + +## 3. Перенос вердикта на снимок + +| Шаг | Что делается | Почему так | +|---|---|---| +| Ключ склейки | имя каталога исследования | таблица ссылается на каталог (`2.25…`), а в DICOM лежит другой идентификатор (`1.2.643…`); соответствие каталог → тег 100/100 | +| Склейка дублей | по хешу пиксельных данных | 544 файла — это 252 уникальных снимка | +| Область снимка | голосование по именам файлов группы | один снимок назван и как позвоночник, и как бедро; при равенстве голосов область остаётся неопределённой (такой снимок один) | +| Вердикт | оценка области переносится на её снимок | **каждая область встречается в исследовании ровно один раз**, поэтому не нужно решать, какой из нескольких снимков «плохой» | +| Сторона бедра | при единственном снимке бедра берётся единственный заполненный столбец | в 71 из 72 исследований с двумя бёдрами столбцы совпадают с именами файлов; в 7 исследованиях с одним снимком заполнена противоположная сторона. Факт переноса фиксируется флагом `laterality_mirrored`; теги `Laterality` в DICOM пусты | +| Тип нарушения | только из структурированных критериев | комментарии («сколиоз», «эндопротезирование ТБС») сохранены дословно: перекладывать свободный текст в код — догадка | + +## 4. Правило метки и почему оно такое + +Метка могла строиться двумя способами: только из таблицы или с добавлением +пометок, которые вручную проставлялись в именах файлов (суффикс `_bad`). Пометки +в именах оказались ненадёжными: они расходились с оценкой эксперта в **15 случаях +из 252**. Выбор сделан измерением, а не по вкусу. + +**Постановка.** Разбиение по исследованиям зафиксировано один раз, оба варианта +обучены пятью seed'ами на нём, оценены по одному эталону — вердикту эксперта на +снимках валидации. Снимки валидации не участвуют в обучении ни в одном варианте. + +Воспроизведение: `python -m src.dxa.excel_labels --label-rule {table,union,expert}` +и `python -m src.dxa.compare_labels --split-file labels/split_expert_seed42.json`. + +**Результат** (53 снимка валидации, 16 нарушений по эталону, 5 seed'ов): + +| Метрика (эталон) | только таблица | таблица или суффикс `_bad` | +|---|---|---| +| ROC-AUC | **0.6764** [0.6309, 0.7218] | 0.6199 [0.5840, 0.6559] | +| PR-AUC | **0.4759** [0.4141, 0.5377] | 0.4046 [0.3702, 0.4391] | +| F1 | **0.5676** [0.5270, 0.6082] | 0.5426 [0.5073, 0.5778] | +| Recall | 0.7000 | 0.8625 | +| Precision | 0.4857 | 0.4043 | + +Парная разница («только таблица» − «с суффиксами»): + +| Метрика | Δ, среднее [95 % ДИ] | Знаки по seed'ам | +|---|---|---| +| ROC-AUC | **+0.0564** [+0.0403, +0.0725] | `+++++` | +| PR-AUC | **+0.0713** [+0.0398, +0.1028] | `+++++` | +| F1 | +0.0251 [−0.0056, +0.0558] | `+++-+` | +| Precision | +0.0815 [+0.0590, +0.1039] | `+++++` | +| Recall | −0.1625 [−0.2715, −0.0535] | `--0--` | + +**Решение: принято правило «только экспертная таблица».** Дополнительные пометки +из имён файлов ухудшали согласие модели с экспертом на невиданных +исследованиях: вариант с ними чаще срабатывал (recall 0.86), но за счёт +точности, а по беспороговым метрикам проигрывал на всех пяти seed'ах. + +Оговорка: эталон — та же экспертная таблица, поэтому вариант, обучавшийся +непосредственно на ней, находится в выигрышном положении. Значимо здесь другое: +добавление ненадёжных пометок **снижает** согласие с экспертом, что и служит +аргументом против них. + +## 5. Словарь типов нарушений + +| Код | Подпись | Область | Источник | +|---|---|---|---| +| `positioning` | Некорректная укладка | позвоночник | таблица | +| `axis_deviation` | Отклонение оси | позвоночник | таблица | +| `artifact` | Артефакты и импланты | любая | таблица | +| `rotation` | Ротация, позиционирование | бедро | таблица | +| `roi_incorrect` | Некорректная область интереса | любая | таблица | +| `motion` | Движение, размытие | любая | критерии методики | +| `incomplete_anatomy` | Анатомия видна не полностью | любая | критерии методики | +| `labeling_error` | Ошибка разметки | позвоночник | критерии методики | +| `unspecified` | Нарушение без уточнения | любая | служебный | + +Распределение в разметке: `rotation` 36, `artifact` 17, `axis_deviation` 10, +`roi_incorrect` 7, `positioning` 6, `unspecified` 5. + +Словарь один на всё решение (`src/dxa/violations.py`): коды используют инференс, +отчёт DICOM SR и веб-интерфейс, подписи отдаёт сервер, копий в JavaScript нет. + +## 6. Результат разметки + +`labels/labels_images.csv` (и XLSX) — по одной строке на уникальный снимок: + +| | позвоночник | бедро R | бедро L | неопред. | всего | +|---|---|---|---|---|---| +| качественных | 66 | 58 | 51 | 0 | 175 | +| с нарушением | 33 | 21 | 22 | 1 | 77 | +| **итого** | **99** | **79** | **73** | **1** | **252** | + +Помимо метки в CSV сохранено происхождение: `quality_from_excel` (вердикт +эксперта), `quality_from_filename` (пометка из имени файла), `sources_conflict`, +`label_rule`, `filename_fallback`, `laterality_mirrored`, `region_ambiguous`, +`expert_comment`. Поэтому правило можно переиграть без повторного разбора. + +Рядом лежат варианты для воспроизведения сравнения: +`labels_images_table.csv`, `labels_images_union.csv`, `labels_images_expert.csv` +(эталон: только снимки с экспертной оценкой, 249 строк) и +`split_expert_seed42.json` (зафиксированное разбиение). + +## 7. Гигиена данных: имена файлов + +Имена проставлялись вручную и разошлись: `spine_1` и `spine_01`, `Spine_01`, +`r_spine_03`, `r_hip03`, `r_hop_02` (опечатка), `spine-1`. Они приведены к виду +`<область>_[_good|_bad].dcm` инструментом `src/dxa/rename_files.py`; +переименовано 344 файла из 548, карта отката — `labels/rename_map.csv`. + +Переименование сделано **после** того, как разметка и метрики были посчитаны, и +проверено, что оно на них не влияет: набор из 252 пиксельных групп идентичен до и +после, разметка не изменилась ни в одной строке. Суффиксы `_good`/`_bad` +сохранены как были и в метках не участвуют — только как диагностический столбец. + +## 8. Оценка качества модели + +Разбиение по исследованиям (по умолчанию seed 42): обучение 199 снимков / +81 исследование, валидация 53 снимка / 19 исследований, 16 нарушений. + +| Метрика | Значение | Как получено | +|---|---|---| +| ROC-AUC | **0.6764** [0.6309, 0.7218] | 5 seed'ов на фиксированном разбиении, эталон — вердикт эксперта | +| PR-AUC | 0.4759 [0.4141, 0.5377] | там же | +| F1 | 0.5676 [0.5270, 0.6082] | там же; порог подобран по F1 на валидации, поэтому смещён вверх | +| ROC-AUC по областям | позвоночник 0.943, бедро R 0.576, бедро L 0.550 | рабочий чекпоинт, его собственная валидация | + +Рабочий чекпоинт — обычный прогон с seed по умолчанию (эпоха 39, порог логита +−0.4930 → вероятность 0.379), его собственная валидационная ROC-AUC 0.6706 +близка к среднему по seed'ам, то есть результат не отобран по удачности. + +Проверка, что модель смотрит на снимок, а не угадывает анатомию: внутри областей +она даёт AUC 0.85–0.93, правило «позвоночник значит нарушение» — ровно 0.50. +Числа считаются на всём наборе, включая обучающие снимки, поэтому смещены вверх и +отвечают на вопрос «есть ли вклад содержимого», а не «каково качество на новых +данных». + +## 9. Ограничения + +1. **Разметка унаследована от исследования.** Таблица оценивает исследование, а + не снимок; перенос однозначен, потому что область встречается один раз, но + исходная оценка всё равно не поштучная. +2. **Мало данных:** 252 снимка, 77 нарушений. Интервалы широкие. +3. **Эталон — та же таблица.** Независимой истины нет; вариант, обучавшийся на + таблице, в сравнении в выигрышном положении. +4. **Тип нарушения — эвристика**, а не вывод модели: 5 снимков имеют только + `unspecified`, у остальных тип приходит из критериев таблицы. +5. **Три снимка без экспертной оценки** размечены по пометке в имени файла и + помечены `filename_fallback`. +6. **Сторона бедра в 7 исследованиях не проверяема:** теги латеральности пусты. +7. **Корректность областей интереса наследуется из таблицы:** разметки ROI в + DICOM нет, сравнить её напрямую не с чем. + +## 10. Отрицательный результат: локальная vision-модель + +Планировалась визуальная разметка локальной vision-моделью (9 млрд параметров, +офлайн), чтобы не зависеть от таблицы. На калибровке по 14 снимкам, из которых 8 +заведомо с нарушениями, модель вынесла «непригоден» всем 14, включая все +качественные, с шаблонными формулировками и выдуманными имплантами. Разделяющая +способность — на уровне случайной, поэтому как разметчик модель непригодна. + +Вывод, который стоит зафиксировать: разделяющую способность инструмента нужно +проверять **до** того, как строить на нём пайплайн. Инструменты рендера снимков и +контактных листов остались в `src/dxa/render.py` — они полезны для выборочной +ручной проверки. + +## 11. Воспроизведение + +```bash +./run.sh label # разметка по экспертной таблице +./run.sh rename # имена файлов (план; --apply) +./run.sh split && ./run.sh compare # выбор правила метки, 5 seed'ов +python -m src.dxa.excel_labels --label-rule union --out-name labels_images_union +python -m src.dxa.render --by-region --out dataset_hack/_preview +``` + +## 12. Что дальше + +- Поштучная разметка снимков специалистом — снимет ограничение №1. +- Мультилейбл по типам нарушений вместо эвристики. +- Больше исследований (500+), чтобы сузить интервалы. +- Калибровка порога определения области под конкретное оборудование. diff --git a/docs/deck.pptx b/docs/deck.pptx new file mode 100644 index 0000000..09e2d52 Binary files /dev/null and b/docs/deck.pptx differ diff --git a/docs/img/ui-banner.png b/docs/img/ui-banner.png new file mode 100644 index 0000000..5b1a3fa Binary files /dev/null and b/docs/img/ui-banner.png differ diff --git a/docs/img/ui-detail-clean.png b/docs/img/ui-detail-clean.png new file mode 100644 index 0000000..c6f1c35 Binary files /dev/null and b/docs/img/ui-detail-clean.png differ diff --git a/docs/img/ui-detail-violation.png b/docs/img/ui-detail-violation.png new file mode 100644 index 0000000..0810d2d Binary files /dev/null and b/docs/img/ui-detail-violation.png differ diff --git a/docs/img/ui-model-panel.png b/docs/img/ui-model-panel.png new file mode 100644 index 0000000..d97578a Binary files /dev/null and b/docs/img/ui-model-panel.png differ diff --git a/docs/img/ui-results.png b/docs/img/ui-results.png new file mode 100644 index 0000000..6c1255d Binary files /dev/null and b/docs/img/ui-results.png differ diff --git a/docs/labeling.md b/docs/labeling.md new file mode 100644 index 0000000..7bdf25b --- /dev/null +++ b/docs/labeling.md @@ -0,0 +1,214 @@ +# Разметка датасета и выбор правила метки + +Документ описывает, откуда берутся метки снимков, как проверялось правило +разметки и какие результаты из этого следуют. Числа проверяемы: ссылки на +источники приведены рядом. + +## 1. Задача разметки + +Экспертная оценка в наборе сделана на уровне **исследования**: в таблице +`разметка.xlsx` по каждому исследованию отмечены критерии качества, а не по +отдельному снимку. Модель же работает со снимками, поэтому вердикт исследования +нужно было перенести на каждый снимок — без догадок и без потери информации о +том, откуда метка взялась. + +## 2. Экспертная таблица + +Столбцы (две строки заголовков, данные с третьей): + +| Столбец | Смысл | Тип нарушения | +|---|---|---| +| 2 | Позвоночник: укладка | `positioning` | +| 3 | Позвоночник: ось | `axis_deviation` | +| 4 | Позвоночник: артефакты, наложения | `artifact` | +| 5, 6 | Бедро R: позиционирование/ротация, область интереса | `rotation`, `roi_incorrect` | +| 7, 8 | Бедро L: то же | `rotation`, `roi_incorrect` | +| 9, 10, 11 | Итог по области | — | +| 12 | Комментарий эксперта | — | + +**Семантика значений** установлена по данным, а не по формулировкам заголовков: + +- `1` в столбце критерия означает **нарушение**, хотя часть заголовков + сформулирована положительно («корректная укладка»); +- итог области равен логическому ИЛИ критериев: бёдра 72/72 и 78/78, + позвоночник 96/99; +- три расхождения позвоночника (два случая «итог без критериев», один «критерий + без итога») трактуются как нарушение — по правилу «хотя бы один существенный + пункт нарушен»; +- заполненность: позвоночник 99/100, бедро R 72/100, бедро L 78/100; у 23 + исследований есть комментарий. + +## 3. Перенос вердикта на снимок + +| Шаг | Что делается | Почему так | +|---|---|---| +| Ключ склейки | имя каталога исследования | таблица ссылается на каталог (`2.25…`), а в DICOM лежит другой идентификатор (`1.2.643…`); соответствие каталог → тег 100/100 | +| Склейка дублей | по хешу пиксельных данных | 544 файла — это 252 уникальных снимка | +| Область снимка | голосование по именам файлов группы | один снимок назван и как позвоночник, и как бедро; при равенстве голосов область остаётся неопределённой (такой снимок один) | +| Вердикт | оценка области переносится на её снимок | **каждая область встречается в исследовании ровно один раз**, поэтому не нужно решать, какой из нескольких снимков «плохой» | +| Сторона бедра | при единственном снимке бедра берётся единственный заполненный столбец | в 71 из 72 исследований с двумя бёдрами столбцы совпадают с именами файлов; в 7 исследованиях с одним снимком заполнена противоположная сторона. Факт переноса фиксируется флагом `laterality_mirrored`; теги `Laterality` в DICOM пусты | +| Тип нарушения | только из структурированных критериев | комментарии («сколиоз», «эндопротезирование ТБС») сохранены дословно: перекладывать свободный текст в код — догадка | + +## 4. Правило метки и почему оно такое + +Метка могла строиться двумя способами: только из таблицы или с добавлением +пометок, которые вручную проставлялись в именах файлов (суффикс `_bad`). Пометки +в именах оказались ненадёжными: они расходились с оценкой эксперта в **15 случаях +из 252**. Выбор сделан измерением, а не по вкусу. + +**Постановка.** Разбиение по исследованиям зафиксировано один раз, оба варианта +обучены пятью seed'ами на нём, оценены по одному эталону — вердикту эксперта на +снимках валидации. Снимки валидации не участвуют в обучении ни в одном варианте. + +Воспроизведение: `python -m src.dxa.excel_labels --label-rule {table,union,expert}` +и `python -m src.dxa.compare_labels --split-file labels/split_expert_seed42.json`. + +**Результат** (53 снимка валидации, 16 нарушений по эталону, 5 seed'ов): + +| Метрика (эталон) | только таблица | таблица или суффикс `_bad` | +|---|---|---| +| ROC-AUC | **0.6764** [0.6309, 0.7218] | 0.6199 [0.5840, 0.6559] | +| PR-AUC | **0.4759** [0.4141, 0.5377] | 0.4046 [0.3702, 0.4391] | +| F1 | **0.5676** [0.5270, 0.6082] | 0.5426 [0.5073, 0.5778] | +| Recall | 0.7000 | 0.8625 | +| Precision | 0.4857 | 0.4043 | + +Парная разница («только таблица» − «с суффиксами»): + +| Метрика | Δ, среднее [95 % ДИ] | Знаки по seed'ам | +|---|---|---| +| ROC-AUC | **+0.0564** [+0.0403, +0.0725] | `+++++` | +| PR-AUC | **+0.0713** [+0.0398, +0.1028] | `+++++` | +| F1 | +0.0251 [−0.0056, +0.0558] | `+++-+` | +| Precision | +0.0815 [+0.0590, +0.1039] | `+++++` | +| Recall | −0.1625 [−0.2715, −0.0535] | `--0--` | + +**Решение: принято правило «только экспертная таблица».** Дополнительные пометки +из имён файлов ухудшали согласие модели с экспертом на невиданных +исследованиях: вариант с ними чаще срабатывал (recall 0.86), но за счёт +точности, а по беспороговым метрикам проигрывал на всех пяти seed'ах. + +Оговорка: эталон — та же экспертная таблица, поэтому вариант, обучавшийся +непосредственно на ней, находится в выигрышном положении. Значимо здесь другое: +добавление ненадёжных пометок **снижает** согласие с экспертом, что и служит +аргументом против них. + +## 5. Словарь типов нарушений + +| Код | Подпись | Область | Источник | +|---|---|---|---| +| `positioning` | Некорректная укладка | позвоночник | таблица | +| `axis_deviation` | Отклонение оси | позвоночник | таблица | +| `artifact` | Артефакты и импланты | любая | таблица | +| `rotation` | Ротация, позиционирование | бедро | таблица | +| `roi_incorrect` | Некорректная область интереса | любая | таблица | +| `motion` | Движение, размытие | любая | критерии методики | +| `incomplete_anatomy` | Анатомия видна не полностью | любая | критерии методики | +| `labeling_error` | Ошибка разметки | позвоночник | критерии методики | +| `unspecified` | Нарушение без уточнения | любая | служебный | + +Распределение в разметке: `rotation` 36, `artifact` 17, `axis_deviation` 10, +`roi_incorrect` 7, `positioning` 6, `unspecified` 5. + +Словарь один на всё решение (`src/dxa/violations.py`): коды используют инференс, +отчёт DICOM SR и веб-интерфейс, подписи отдаёт сервер, копий в JavaScript нет. + +## 6. Результат разметки + +`labels/labels_images.csv` (и XLSX) — по одной строке на уникальный снимок: + +| | позвоночник | бедро R | бедро L | неопред. | всего | +|---|---|---|---|---|---| +| качественных | 66 | 58 | 51 | 0 | 175 | +| с нарушением | 33 | 21 | 22 | 1 | 77 | +| **итого** | **99** | **79** | **73** | **1** | **252** | + +Помимо метки в CSV сохранено происхождение: `quality_from_excel` (вердикт +эксперта), `quality_from_filename` (пометка из имени файла), `sources_conflict`, +`label_rule`, `filename_fallback`, `laterality_mirrored`, `region_ambiguous`, +`expert_comment`. Поэтому правило можно переиграть без повторного разбора. + +Рядом лежат варианты для воспроизведения сравнения: +`labels_images_table.csv`, `labels_images_union.csv`, `labels_images_expert.csv` +(эталон: только снимки с экспертной оценкой, 249 строк) и +`split_expert_seed42.json` (зафиксированное разбиение). + +## 7. Гигиена данных: имена файлов + +Имена проставлялись вручную и разошлись: `spine_1` и `spine_01`, `Spine_01`, +`r_spine_03`, `r_hip03`, `r_hop_02` (опечатка), `spine-1`. Они приведены к виду +`<область>_[_good|_bad].dcm` инструментом `src/dxa/rename_files.py`; +переименовано 344 файла из 548, карта отката — `labels/rename_map.csv`. + +Переименование сделано **после** того, как разметка и метрики были посчитаны, и +проверено, что оно на них не влияет: набор из 252 пиксельных групп идентичен до и +после, разметка не изменилась ни в одной строке. Суффиксы `_good`/`_bad` +сохранены как были и в метках не участвуют — только как диагностический столбец. + +## 8. Оценка качества модели + +Разбиение по исследованиям (по умолчанию seed 42): обучение 199 снимков / +81 исследование, валидация 53 снимка / 19 исследований, 16 нарушений. + +| Метрика | Значение | Как получено | +|---|---|---| +| ROC-AUC | **0.6764** [0.6309, 0.7218] | 5 seed'ов на фиксированном разбиении, эталон — вердикт эксперта | +| PR-AUC | 0.4759 [0.4141, 0.5377] | там же | +| F1 | 0.5676 [0.5270, 0.6082] | там же; порог подобран по F1 на валидации, поэтому смещён вверх | +| ROC-AUC по областям | позвоночник 0.943, бедро R 0.576, бедро L 0.550 | рабочий чекпоинт, его собственная валидация | + +Рабочий чекпоинт — обычный прогон с seed по умолчанию (эпоха 39, порог логита +−0.4930 → вероятность 0.379), его собственная валидационная ROC-AUC 0.6706 +близка к среднему по seed'ам, то есть результат не отобран по удачности. + +Проверка, что модель смотрит на снимок, а не угадывает анатомию: внутри областей +она даёт AUC 0.85–0.93, правило «позвоночник значит нарушение» — ровно 0.50. +Числа считаются на всём наборе, включая обучающие снимки, поэтому смещены вверх и +отвечают на вопрос «есть ли вклад содержимого», а не «каково качество на новых +данных». + +## 9. Ограничения + +1. **Разметка унаследована от исследования.** Таблица оценивает исследование, а + не снимок; перенос однозначен, потому что область встречается один раз, но + исходная оценка всё равно не поштучная. +2. **Мало данных:** 252 снимка, 77 нарушений. Интервалы широкие. +3. **Эталон — та же таблица.** Независимой истины нет; вариант, обучавшийся на + таблице, в сравнении в выигрышном положении. +4. **Тип нарушения — эвристика**, а не вывод модели: 5 снимков имеют только + `unspecified`, у остальных тип приходит из критериев таблицы. +5. **Три снимка без экспертной оценки** размечены по пометке в имени файла и + помечены `filename_fallback`. +6. **Сторона бедра в 7 исследованиях не проверяема:** теги латеральности пусты. +7. **Корректность областей интереса наследуется из таблицы:** разметки ROI в + DICOM нет, сравнить её напрямую не с чем. + +## 10. Отрицательный результат: локальная vision-модель + +Планировалась визуальная разметка локальной vision-моделью (9 млрд параметров, +офлайн), чтобы не зависеть от таблицы. На калибровке по 14 снимкам, из которых 8 +заведомо с нарушениями, модель вынесла «непригоден» всем 14, включая все +качественные, с шаблонными формулировками и выдуманными имплантами. Разделяющая +способность — на уровне случайной, поэтому как разметчик модель непригодна. + +Вывод, который стоит зафиксировать: разделяющую способность инструмента нужно +проверять **до** того, как строить на нём пайплайн. Инструменты рендера снимков и +контактных листов остались в `src/dxa/render.py` — они полезны для выборочной +ручной проверки. + +## 11. Воспроизведение + +```bash +./run.sh label # разметка по экспертной таблице +./run.sh rename # имена файлов (план; --apply) +./run.sh split && ./run.sh compare # выбор правила метки, 5 seed'ов +python -m src.dxa.excel_labels --label-rule union --out-name labels_images_union +python -m src.dxa.render --by-region --out dataset_hack/_preview +``` + +## 12. Что дальше + +- Поштучная разметка снимков специалистом — снимет ограничение №1. +- Мультилейбл по типам нарушений вместо эвристики. +- Больше исследований (500+), чтобы сузить интервалы. +- Калибровка порога определения области под конкретное оборудование. diff --git a/docs/lct_temppalte.pptx b/docs/lct_temppalte.pptx new file mode 100644 index 0000000..5e79b8c Binary files /dev/null and b/docs/lct_temppalte.pptx differ diff --git a/docs/pitch.html b/docs/pitch.html new file mode 100644 index 0000000..6d7ab02 --- /dev/null +++ b/docs/pitch.html @@ -0,0 +1,611 @@ + + + + + +Материалы к презентации — контроль качества DXA + + + + + +
+ +
+

Контроль качества денситометрических исследований (DXA)

+

Материалы к презентации в трёх вариантах: краткий спич, словарь терминов к нему и полная техническая выкладка. Все числа проверяемы, источник указан рядом.

+
+ +
+ + + +
+ + +
+
+

Краткий спич + Около трёх минут. Каждый блок — один абзац для произнесения; ниже — что показать на слайде. +

+ +

1. Задача

+
Произнести +Мы делали цифрового помощника по контролю качества денситометрии. По снимку DXA нужно решить, пригоден ли он для дальнейшего анализа, и объяснить, что именно не так. Вход — DICOM, выход — таблица XLSX или CSV, одна строка на снимок. Работает офлайн, в контейнере, не дольше трёх минут на исследование. Области — поясничный отдел позвоночника и проксимальный отдел бедра. +
+

На слайд: колонки результата и требования — офлайн, контейнер, три минуты.

+ +

2. Данные

+
Произнести +В наборе 544 файла, но это 252 уникальных снимка в ста исследованиях: остальное — те же кадры, сохранённые повторно. Экспертная оценка сделана в таблице, по каждому исследованию и с разбивкой на критерии: укладка, ось, артефакты, позиционирование, область интереса. +
+ +

3. Как из оценки исследования получилась метка снимка

+
Произнести +Оценка в таблице относится к исследованию, а модель работает со снимками — вердикт нужно было перенести. Здесь помогло свойство набора: после склейки дублей каждая анатомическая область встречается в исследовании ровно один раз. Значит, не нужно угадывать, какой из снимков «плохой»: вердикт области переносится на её снимок однозначно. Так размечены все 252 снимка, из них 77 с нарушениями. +
+

На слайд: 544 файла → 252 снимка → 100 исследований; одна область на исследование.

+ +

4. Выбор правила разметки делали измерением

+
Произнести +Метку можно было строить только из экспертной таблицы или дополнительно учитывать служебные пометки, которые проставлялись при подготовке набора. Они расходились с оценкой эксперта в пятнадцати случаях из двухсот пятидесяти двух, поэтому мы не стали верить на слово ни одному варианту: зафиксировали разбиение, обучили оба пятью seed'ами и сравнили по одному эталону. Победило правило «только таблица»: ROC-AUC 0.68 против 0.62, преимущество на всех пяти seed'ах. Служебные пометки в метках не участвуют. +
+ +

5. Модель

+
Произнести +Архитектура намеренно простая: ResNet18 с весами ImageNet как замороженный экстрактор признаков и линейная голова. Полное дообучение на двухстах снимках переобучается — train-метрика уходит в единицу, а качество на валидации падает до случайного. Аугментацию отключили: яркость и положение снимка сами являются признаками качества, и её включение роняло площадь под ROC-кривой с 0.87 до 0.56. Порог решения подбираем по логиту, максимизируя F1: вероятности насыщаются, и порог 0.5 даёт почти нулевой recall. +
+ +

6. Результат

+
Произнести +На фиксированном разбиении, пять seed'ов, оценка по вердикту эксперта: ROC-AUC 0.676 с интервалом от 0.63 до 0.72, PR-AUC 0.476, F1 0.568. Рабочий чекпоинт — обычный прогон с seed по умолчанию, его собственная оценка 0.671, то есть близка к среднему: результат не отобран по удачности. +
+

На слайд: три строки метрик с интервалами и одна оговорка про эталон.

+ +

7. Модель смотрит на снимок, а не на область

+
Произнести +Область исследования почти однозначно определяется шириной кадра, поэтому есть риск, что модель просто угадывает анатомию. Мы это проверили отдельно: внутри областей модель даёт AUC от 0.85 до 0.93, а правило «позвоночник — значит нарушение» — ровно 0.5, потому что внутри области подсказки нет. Значит, модель работает с содержимым снимка. +
+ +

8. Скорость

+
Произнести +Обработка одного снимка — около пятнадцати миллисекунд, на процессоре двадцать. При бюджете три минуты на исследование запас более чем тысячекратный. Чекпоинт занимает 43 мегабайта, ускоритель для этой задачи не обязателен: время уходит на декодирование снимка, а не на сеть. +
+ +

9. Чего мы не утверждаем

+
Произнести +Модель бинарная: она не определяет тип нарушения. Тип, который вы видите в интерфейсе, посчитан эвристикой по метрикам снимка и помечен соответствующей пометкой — мы намеренно не выдаём предположение за заключение. Эталон, по которому мы мерили, — та же экспертная таблица, независимой истины у нас нет. Данных мало: 252 снимка, в валидации шестнадцать нарушений, интервалы широкие. И отдельно: локальная vision-модель на девять миллиардов параметров, которой мы хотели размечать снимки визуально, вынесла «непригоден» всем четырнадцати снимкам калибровки, включая заведомо качественные. От этого пути отказались, проверив его одним прогоном, а не неделей разработки. +
+ +

10. Что дальше

+
Произнести +Три направления: поштучная разметка снимков специалистом, чтобы снять главное ограничение; мультилейбл-модель для типов нарушений вместо эвристики; больше исследований, чтобы сузить интервалы. +
+
+ +
+

Шпаргалка: цифры, которые будут спрашивать

+ + + + + + + + + + + + + +
ВопросОтветОткуда
Сколько снимков?252 уникальных в 100 исследованиях (544 файла на диске)склейка дублей по пикселям
Сколько нарушений?77 из 252 (30.6 %): 74 по экспертной таблице + 3 без экспертной оценкиlabels/labels_images.csv
ROC-AUC0.6764 [0.6309, 0.7218], 5 seed'овmodels/compare_rules/rule_comparison.md
PR-AUC / F10.4759 [0.4141, 0.5377] / 0.5676 [0.5270, 0.6082]там же
Почему такое правило разметки?замер: +0.0564 ROC-AUC против варианта со служебными пометками, 5 из 5 seed'овтам же
Вклад содержимого снимкавнутри областей AUC 0.85–0.93 против 0.50 у правила областиdiscriminator
Скорость≈15 мс на снимок на ускорителе, 20 мс на CPUзамер на 544 файлах
Размер модели42.8 МБ, 11.2 млн параметров, обучается только головаmodels/dxa_model.pth
Тесты208./run.sh test
+
+
+ + +
+
+

Определения + Расширение краткого спича: термины, которыми придётся отвечать на вопросы. Формулировки привязаны к тому, как эти слова используются в решении. +

+ +

Данные и формат

+
+
DXA / ДРА двухэнергетическая рентгеновская абсорбциометрия
+
Метод измерения минеральной плотности костной ткани. От качества укладки и разметки зависит не «красивость» снимка, а само число. + Задача — контроль качества измерения, а не диагностика перелома.
+ +
DICOM
+
Стандарт хранения и передачи медицинских изображений: пиксельные данные плюс метаданные. + Вход решения; из метаданных берём идентификаторы, но решение не зависит от персональных данных.
+ +
StudyInstanceUID и SOPInstanceUID
+
StudyInstanceUID идентифицирует исследование, SOPInstanceUID — конкретный снимок. Оба идут в выходной файл: study_uid и image_uid. + Подводный камень: имя каталога исследования в наборе не совпадает с этим тегом, а экспертная таблица ссылается на каталог. Склейка идёт по каталогу, в отчёт попадает тег.
+ +
ROI region of interest
+
Область интереса: рамка или контур, по которой считают плотность. Правильность её проведения — самостоятельный критерий качества. + В предоставленных DICOM разметка ROI отсутствует, сравнить её не с чем: корректность областей оценивается экспертом в таблице, и это открытое ограничение решения.
+ +
Побайтный дубль
+
Файлы с идентичным пиксельным содержимым под разными именами. В наборе 544 файла против 252 уникальных снимков. + Зачем склеивать: иначе один и тот же снимок попадал и в обучение, и в валидацию.
+ +
Анатомическая область
+
В решении три области: spine (поясничный отдел), hip_right и hip_left (проксимальный отдел бедра). + Область определяется по геометрии кадра, а не по метаданным: ширина кадра у позвоночника около 300 пикселей, у бедра около 280. Порог привязан к текущему оборудованию — это ограничение.
+
+ +

Разметка

+
+
Экспертная таблица
+
Файл разметка.xlsx: по каждому исследованию отмечены критерии — укладка, ось, артефакты для позвоночника; позиционирование и область интереса для каждого бедра; плюс итог по области и комментарий. + Значение 1 в критерии означает нарушение, хотя часть заголовков сформулирована положительно. Проверено: итог области равен логическому ИЛИ критериев (бёдра 72/72 и 78/78, позвоночник 96/99).
+ +
Единица разметки
+
То, к чему относится метка. Таблица описывает исследование, решение работает со снимками. + Ключевое свойство набора: после склейки дублей каждая область встречается в исследовании ровно один раз, поэтому вердикт переносится на снимок области однозначно.
+ +
Правило метки table / union
+
Как из оценки получается метка снимка. table — только экспертная таблица (принято), union — таблица и служебные пометки, проставленные при подготовке набора. + Выбрано измерением: table дал ROC-AUC 0.6764 против 0.6199, преимущество на всех пяти seed'ах. Есть третье правило, expert: только снимки с экспертной оценкой — оно служит эталоном при оценке, а не для обучения.
+ +
quality_class
+
Бинарный класс: 0 — качественное изображение, 1 — есть нарушение. Это итоговое решение модели.
+ +
Пригоден / непригоден
+
Формулировка того же решения словами. «Пригоден» означает, что нет видимых причин мешать специалисту выполнить дальнейший анализ; это не значит, что снимок диагностически нормален. + Правило методики: если хотя бы один существенный пункт нарушен или не подтверждается по изображению, снимок непригоден. Сомнительные случаи не пропускаются.
+ +
violation_type
+
Тип нарушения — строка, перечень через точку с запятой, словарь канонический и единый для решения, отчёта DICOM SR и интерфейса. + Пять кодов кодирует экспертная таблица: positioning, axis_deviation, artifact, rotation, roi_incorrect. Ещё три задаёт методика: motion, incomplete_anatomy, labeling_error. Плюс unspecified — нарушение без уточнения.
+ +
Эвристика
+
Правило, а не обученная модель. Тип нарушения определяется эвристикой по метрикам снимка, потому что модель бинарная. + В интерфейсе это помечено пометкой «эвристика» с пояснением: показывать предположение как заключение нельзя.
+ +
Зеркалирование стороны бедра
+
Ситуация, когда в исследовании снят один снимок бедра, а в таблице заполнен столбец противоположной стороны — 6 исследований из 7 с одним бедром. + Логика: снимок один и заполненный столбец один — они соответствуют друг другу. Факт переноса фиксируется флагом; теги латеральности в DICOM пусты, поэтому сторону иначе не проверить.
+
+ +

Модель и обучение

+
+
Линейный зонд linear probe
+
Режим, при котором свёрточная часть сети заморожена и обучается только линейный слой поверх её признаков. + На двухстах снимках полное дообучение переобучается: train F1 уходит в единицу при случайном AUC на валидации. Обучаемых параметров остаются тысячи вместо миллионов.
+ +
Frozen backbone и BatchNorm
+
При заморозке отключается не только градиент, но и train-режим слоёв нормализации: иначе бегущие статистики продолжают меняться на обучающих батчах и входной слой получает не те данные, на которых калибровался.
+ +
Препроцессинг
+
Один путь для обучения и API: прочитать DICOM с учётом наклона и инверсии, привести к диапазону по перцентилям 0.5–99.5, увеличить до 224×224, повторить в три канала, нормировать по статистикам ImageNet. + Перцентили вместо минимума-максимума: одиночные яркие пиксели (металл, метка оператора) иначе сжимают весь диапазон.
+ +
Порог по логиту
+
Решающее правило сравнивает логит с порогом, который подобран по F1 на валидации и хранится в чекпоинте вместе с весами (у рабочего — логит −0.493, вероятность 0.379). + Почему не 0.5: вероятности насыщаются, и фиксированный порог давал почти нулевой recall при доле нарушений около 30 %.
+ +
Аугментация
+
Случайные искажения при обучении. Здесь отключена по умолчанию. + Причина содержательная: яркость и положение снимка сами являются признаками качества, поэтому искажать их — значит уничтожать целевую информацию. Проверено: включение роняло AUC с 0.87 до 0.56.
+ +
Разбиение по исследованиям
+
Все снимки одного исследования попадают только в одну часть — обучение или валидацию. + Иначе получается утечка: снимки одного пациента похожи, и качество на валидации оказывается завышенным.
+ +
Зафиксированное разбиение
+
Файл со списком исследований валидации, который не пересчитывается при смене правил разметки. + Разбиение стратифицируется по наличию нарушений, а оно зависит от меток. Без фиксации варианты сравнивались бы на разных наборах.
+ +
Эталон оценки
+
Разметка, по которой считается качество: в нашем случае — вердикт эксперта, взятый только на снимках с экспертной оценкой. + Оценивать вариант на его же метках — круговое сравнение. Эталон один и тот же для всех вариантов.
+
+ +

Метрики

+
+
ROC-AUC
+
Вероятность, что случайно взятый снимок с нарушением получит более высокий балл, чем случайно взятый качественный. Не зависит от порога, поэтому это основная метрика сравнения.
+ +
PR-AUC
+
Площадь под кривой точность-полнота. Чувствительна к доле положительного класса: при 30 % нарушений базовый уровень — 0.30, поэтому сравнивать её между наборами с разной долей нельзя.
+ +
F1, precision, recall
+
F1 — среднее гармоническое точности и полноты. Recall — какая доля нарушений найдена, precision — какая доля тревог подтверждается. + В нашей задаче пропустить нарушение дороже, чем отправить снимок на ручную проверку.
+ +
Доверительный интервал 95 %
+
Диапазон, в котором с вероятностью 95 % лежит истинное значение. Здесь интервалы считаются по пяти seed'ам обучения через распределение Стьюдента. + Чего он не покрывает: неопределённость самой разметки. Поэтому интервал по seed'ам не заменяет независимый тест на закрытом наборе.
+ +
Парная разница
+
Разность метрик двух вариантов, посчитанная для каждого seed'а отдельно и затем усреднённая. + Варианты обучались на одном разбиении и одном seed'е, поэтому разброс обучения вычитается и видно эффект самого правила.
+
+ +

Инженерия и эксплуатация

+
+
Контейнеризация
+
Решение поставляется образом с зафиксированными версиями зависимостей; запуск — скриптом в Linux и UNIX-подобных системах. Веса монтируются при запуске, данные в образ не попадают.
+ +
Офлайн-работа
+
Медицинские изображения не покидают контур: ни внешних сервисов, ни обращений к CDN. Стили и шрифты интерфейса лежат локально. + Это требование методики, а не оптимизация; оно ограничило и выбор способа разметки.
+ +
processing_status
+
Поле отчёта: Success или Failure. Необработанных исключений быть не должно — любая ошибка фиксируется в строке результата.
+ +
DICOM SR
+
Structured Report — текстовое структурированное представление результата. + Оговорка: полноценного справочника SNOMED для контролёра качества DXA в наборе нет, поэтому числовые коды условные, и рядом всегда идёт текстовая формулировка.
+ +
Чекпоинт
+
Файл с весами, порогом, параметрами препроцессинга и метаданными: на какой разметке обучен, по какому разбиению, с каким seed'ом. + Самодостаточность важна: инференс не может рассинхронизироваться с обучением, а по файлу видно, что именно работает.
+
+
+
+ + +
+
+

Полная техническая выкладка + Для вопросов «а как именно» и для инженера, который будет разворачивать решение. +

+ +

1. Требования и что именно решается

+
    +
  • Объект: поясничный отдел позвоночника и проксимальный отдел бедренной кости.
  • +
  • Решение: бинарная классификация — пригоден (0) или есть нарушение (1) — плюс определение анатомической области и типа нарушения.
  • +
  • Вход: DICOM без разметки; в исследовании до трёх изображений.
  • +
  • Выход: XLSX или CSV, одна строка на снимок: path_to_study, study_uid, image_uid, anatomical_region, quality_class, violation_type, processing_status, time_of_processing.
  • +
  • Приоритетные метрики: F1 и ROC-AUC с 95 % доверительными интервалами.
  • +
  • Ограничения: офлайн, контейнеризация, до трёх минут на исследование, воспроизводимость, отсутствие необработанных исключений.
  • +
+ +

2. Данные: состав

+ + + + + + + + + + + +
ПоказательЗначение
Файлов DICOM на диске544
Уникальных снимков (по пикселям)252
Исследований100
Снимков: позвоночник / бедро R / бедро L / неопределено99 / 79 / 73 / 1
Нарушений по экспертной таблице74 (29.4 %)
Нарушений в обучающей разметке77 (30.6 %)
Снимков без экспертной оценки3
+ +

Аномалии, повлиявшие на решения

+
    +
  • Дубли. Один кадр сохранён многократно; склейка по хешу пиксельных данных.
  • +
  • Конфликт имён внутри одной группы дублей. Два файла одного и того же снимка названы как разные области, поэтому область определяется голосованием по именам; при равенстве голосов остаётся неопределённой (такой снимок один).
  • +
  • Имя каталога ≠ StudyInstanceUID. Таблица ссылается на каталог, в DICOM лежит другой идентификатор; соответствие один к одному, 100 из 100.
  • +
  • Пустые теги латеральности. Метаданных о стороне бедра нет.
  • +
  • Разметки ROI нет. Ни OverlayData, ни GraphicAnnotationSequence.
  • +
  • Служебные пометки в данных. Метки из них не строятся — правило выбрано измерением (§5); имена приведены к единому виду отдельным инструментом (§7).
  • +
+ +

3. Семантика экспертной таблицы

+

Две строки заголовков, данные с третьей. Столбцы 2–4 — критерии позвоночника (укладка, ось, артефакты), 5–6 и 7–8 — позиционирование и область интереса для правого и левого бедра, 9–11 — итоги по областям, 12 — комментарий.

+
+Значение 1 в критерии означает нарушение, хотя часть заголовков сформулирована положительно («корректная укладка»). Проверено на данных: итог области равен логическому ИЛИ критериев — для бёдер 72/72 и 78/78, для позвоночника 96/99. Три расхождения позвоночника трактуются как нарушение, что соответствует правилу «хотя бы один существенный пункт нарушен». +
+

Заполненность: позвоночник 99 из 100 исследований, бедро R 72, бедро L 78; комментарий есть у 23 исследований.

+ +

4. Как построена разметка на уровне снимка

+
    +
  1. Ключ склейки — имя каталога исследования, а не тег DICOM.
  2. +
  3. Склейка дублей по хешу пиксельных данных: 544 файла → 252 снимка.
  4. +
  5. Область — голосование по именам файлов одной группы изображений.
  6. +
  7. Перенос вердикта — оценка области переносится на её снимок. Основание: каждая область встречается в исследовании ровно один раз.
  8. +
  9. Зеркалирование стороны. В 71 из 72 исследований с двумя бёдрами столбцы совпадают с именами файлов. В 7 исследованиях снят один снимок бедра, и в 6 из них заполнен столбец противоположной стороны: раз снимок один и заполненный столбец один, они соответствуют друг другу. Перенос фиксируется флагом laterality_mirrored.
  10. +
  11. Тип нарушения берётся только из структурированных критериев. Комментарии («сколиоз», «эндо протезирование ТБС») сохранены дословно и в код не перекладываются.
  12. +
  13. Три снимка, по которым таблица область не оценивала, получают метку из служебной пометки и помечены filename_fallback — иначе они остались бы без метки вообще.
  14. +
+

Результат: labels/labels_images.csv (и XLSX) — 252 строки, 175 качественных и 77 с нарушениями.

+ +

5. Выбор правила метки: измерение

+

Правило «только таблица» против «таблица плюс служебные пометки при подготовке набора». Пометки расходились с оценкой эксперта в 15 случаях из 252, поэтому выбор делался измерением: одно фиксированное разбиение, пять seed'ов, один эталон (вердикт эксперта на снимках валидации).

+ + + + + + + + +
Метрика (эталон)только таблицасо служебными пометками
ROC-AUC0.6764 [0.6309, 0.7218]0.6199 [0.5840, 0.6559]
PR-AUC0.4759 [0.4141, 0.5377]0.4046 [0.3702, 0.4391]
F10.5676 [0.5270, 0.6082]0.5426 [0.5073, 0.5778]
Recall / Precision0.700 / 0.4860.863 / 0.404
+ + + + + + + + +
Парная разница (таблица − с пометками)Δ, среднее [95 % ДИ]Знаки по seed'ам
ROC-AUC+0.0564 [+0.0403, +0.0725]+++++
PR-AUC+0.0713 [+0.0398, +0.1028]+++++
F1+0.0251 [−0.0056, +0.0558]+++-+
Precision+0.0815 [+0.0590, +0.1039]+++++
+
./run.sh split      # зафиксировать разбиение (стратификация по эталону)
+./run.sh compare    # 5 seed'ов × 2 варианта, отчёт models/compare_rules/rule_comparison.md
+
+Оговорка. Эталон — та же экспертная таблица, на которой обучался победивший вариант, поэтому сравнение частично ему благоприятствует. Значимый вывод другой: добавление служебных пометок снижает согласие модели с экспертом на невиданных исследованиях. +
+ +

6. Словарь типов нарушений

+ + + + + + + + + + + + + +
КодПодписьОбластьИсточник
positioningНекорректная укладкапозвоночниктаблица
axis_deviationОтклонение осипозвоночниктаблица
artifactАртефакты и имплантылюбаятаблица
rotationРотация, позиционированиебедротаблица
roi_incorrectНекорректная область интересалюбаятаблица
motionДвижение, размытиелюбаяметодика
incomplete_anatomyАнатомия видна не полностьюлюбаяметодика
labeling_errorОшибка разметкипозвоночникметодика
unspecifiedНарушение без уточнениялюбаяслужебный
+

Распределение в разметке: rotation 36, artifact 17, axis_deviation 10, roi_incorrect 7, positioning 6, unspecified 5.

+
+Словарь раньше существовал в трёх копиях — в модуле инференса, в API и в JavaScript интерфейса, — и ни один из них не знал кодов экспертной таблицы: в интерфейсе они показывались как есть. Теперь словарь один (src/dxa/violations.py), подписи отдаёт сервер, старые значения приводятся к канону. +
+ +

7. Гигиена данных: имена файлов

+

Отдельный инструмент приводит имена файлов к единому виду: область, две цифры номера, при наличии — пометка качества. Приведено 344 файла из 548, карта отката сохранена, разметка и метрики при этом не менялись.

+
./run.sh rename            # план правки, без изменений на диске
+./run.sh rename --apply    # выполнить; карта отката labels/rename_map.csv
+./run.sh rename --rollback --apply   # вернуть прежние имена
+

Правка сделана после того, как разметка и метрики были посчитаны, и проверено, что она на них не влияет: набор из 252 пиксельных групп идентичен до и после, разметка не изменилась ни в одной строке. Пометки качества в именах в метках не участвуют — только как диагностический столбец.

+ +

8. Предобработка

+
DICOM → pixel_array (RescaleSlope/Intercept, MONOCHROME1)
+      → нормализация по перцентилям 0.5–99.5 → [0, 1]
+      → ×255, uint8
+      → повтор в 3 канала, билинейный resize 224×224
+      → /255, нормировка по статистикам ImageNet
+      → тензор (3, 224, 224) float32
+

Один и тот же путь используется при обучении и в API. Обучение и API вызывают общий код предсказания, поэтому порог и препроцессинг совпадают по построению.

+ +

9. Модель

+ + + + + + + + + + + +
КомпонентЗначение
BackboneResNet18, веса ImageNet, заморожен (заморозка на всё обучение)
Классификационная головалинейный слой, 2 класса; обучаемых параметров — тысячи
Вспомогательная голова областивес в функции потерь 0.3
Вход224×224, 3 канала; BatchNorm в eval-режиме
Порог−0.4930 по логиту (вероятность 0.379), подбор по F1 на валидации
Размер чекпоинта42.8 МБ, 11.2 млн параметров сети
Определение областиэвристика по ширине кадра (позвоночник ≈300 px, бедро ≈280 px)
+ +

Гиперпараметры по умолчанию

+
    +
  • Эпох 100, batch 16, early stopping с терпением 25, отбор эпохи по сглаженному (окно 5) ROC-AUC.
  • +
  • Оптимизатор AdamW, lr 3e-4, weight decay 0.05 (decoupled).
  • +
  • Балансировка классов и аугментация: выключены.
  • +
  • Валидационная доля 0.2, разбиение по исследованиям, seed 42.
  • +
+ +

10. Оценка качества

+

Фиксированное разбиение: обучение 199 снимков / 81 исследование, валидация 53 снимка / 19 исследований, 16 нарушений по эталону. Пять seed'ов.

+ + + + + + + +
МетрикаЗначение
ROC-AUC0.6764 [0.6309, 0.7218]
PR-AUC0.4759 [0.4141, 0.5377]
F10.5676 [0.5270, 0.6082]
+ + + + + + + + + +
Рабочий чекпоинтЗначение
Обучение / валидация199 снимков / 81 исследование — 53 снимка / 19 исследований, 16 нарушений
Эпоха / порог39 / логит −0.4930 (вероятность 0.379)
ROC-AUC / PR-AUC0.6706 / 0.4585
F1 / recall / precision0.5600 / 0.875 / 0.412
ROC-AUC по областямпозвоночник 0.943, бедро R 0.576, бедро L 0.550
+
+Это обычный прогон с seed по умолчанию, а не лучший из выборки. Его собственная валидационная ROC-AUC 0.6706 близка к среднему по пяти seed'ам 0.6764 — результат не отобран по удачности. +
+ +

11. Проверка вклада модели: смотрит ли она на снимок

+

В позвоночнике нарушений треть, у бёдер около трети, а область почти однозначно определяется шириной кадра. Отсюда вопрос: не выучила ли модель просто «область вместо качества». Проверка — сравнение с правилом «позвоночник значит нарушение».

+ + + + + + +
ПредикторОбщий AUCspinehip_righthip_left
Модель0.8540.9280.8610.853
Правило «позвоночник = нарушение»0.5290.5000.5000.500
+

Внутри областей правило не имеет подсказки и даёт ровно 0.5; модель — 0.85–0.93. Значит, она использует содержимое снимка. Оговорка: проверка считается на всём наборе, включая обучающие снимки, поэтому значения смещены вверх; она отвечает на вопрос «есть ли вклад содержимого», а не «каково качество на новых данных».

+ +

12. Инференс, API и формат результата

+ + + + + + + + + + + +
МетодПутьНазначение
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
+

Ответ анализа по одному файлу, сверх обязательных колонок: confidence, threshold_probability, region_confidence, violation_type_label, violation_type_is_heuristic, violation_type_note, reason, metrics, samples.

+

Ошибки не приводят к исключению: строка получает processing_status = Failure. Путь к чекпоинту задаётся переменной DXA_MODEL_PATH, чтобы контейнер не зависел от рабочего каталога.

+ +

13. Скорость и системные требования

+ + + + + + + + +
ПоказательУскоритель (Apple MPS)CPU, 8 потоков
Обработка одного снимка, медиана15 мс20 мс
Весь набор (544 файла)8 с—
На одно исследование (до 3 снимков)≈0.05 с≈0.06 с
Запас к бюджету 3 минуты>3000×>2500×
+

Основное время уходит на декодирование DICOM и препроцессинг, а не на сеть: разница между ускорителем и процессором в пределах 1.4×. Значит, ускоритель для этой задачи не является узким местом. Минимальная конфигурация — CPU; чекпоинт занимает 43 МБ, память ограничена накладными расходами среды исполнения. Образ содержит только код и фронтенд; веса монтируются при запуске.

+ +

14. Тесты и воспроизводимость

+
    +
  • 208 тестов в tests/: разбор имён и склейка дублей, отсутствие утечки при разбиении, фиксация разбиения, разметка по таблице и три правила метки, единый словарь типов, приведение имён файлов с откатом, препроцессинг и метрики, контракт API для интерфейса.
  • +
  • Три браузерных теста на живом сервере: переключение строк меняет панель деталей, ветка «нарушение» показывает пометку «эвристика», страница не обращается к внешним хостам.
  • +
  • Воспроизводимость: фиксированные seed'ы, гиперпараметры в отчёте, состав разбиения в файле, происхождение модели внутри чекпоинта.
  • +
  • Команды: ./run.sh label | rename | split | compare | train | infer | serve | test.
  • +
+ +

15. Ограничения и что с ними делать

+ + + + + + + + + + + +
ОграничениеСледствиеЧто планируется
Разметка выведена из оценки исследованияпоштучной экспертной оценки снимков нетразметка отдельных снимков специалистом
Мало данных: 252 снимка, 77 нарушенийширокие интервалы, закрытый набор может дать другие цифры500+ исследований
Тип нарушения — эвристика5 снимков имеют только «нарушение без уточнения»мультилейбл-модель по типам
Эталон — та же таблицасравнение правил частично благоприятствует таблиценезависимая экспертная оценка снимков
Область определяется по ширине кадрапорог привязан к текущему оборудованиюкалибровка по метаданным аппарата
Теги латеральности пустысторона бедра в 7 исследованиях не проверяемазапрос тега у источника данных
Разметки ROI нет в DICOMкорректность областей наследуется из таблицыпроверка на данных с экспортной разметкой
+ +

16. Негативный результат, который стоит упомянуть

+

Планировалась визуальная разметка снимков локальной vision-моделью (9 млрд параметров, офлайн): это сняло бы зависимость от экспертной таблицы. На калибровке по 14 снимкам, из которых 8 заведомо с нарушениями, модель вынесла «непригоден» всем 14, включая все качественные, с шаблонными формулировками и выдуманными имплантами. Разделяющая способность — на уровне случайной.

+
+Вывод: разделяющую способность инструмента нужно проверять до того, как строить на нём пайплайн. Инструменты рендера снимков и контактных листов остались в проекте — они полезны для выборочной ручной проверки. +
+ +

17. Быстрые ответы на неудобные вопросы

+
+
Почему ROC-AUC 0.68 — это не много?
+
Двести пятьдесят снимков и разметка, выведенная из оценки исследования. На таком объёме это ожидаемый порядок; выше 0.8 означало бы утечку или подгонку.
+
Почему не дообучали всю сеть?
+
Пробовали: train уходит в единицу, валидация — к случайной. Замороженный backbone и линейная голова дают устойчивый результат.
+
Почему не использовать все доступные пометки?
+
Проверили: служебные пометки расходились с экспертом в 15 случаях из 252, и обучение с ними дало ROC-AUC 0.6199 против 0.6764 — хуже на всех пяти seed'ах.
+
Почему тип нарушения не от модели?
+
Разметки типов на уровне снимка мало, а мультилейбл на 77 нарушениях дал бы ещё более широкие интервалы. Честнее показать эвристику с пометкой, чем модель, которой нельзя верить.
+
Что если на вход придёт область, которой не было?
+
Область определяется эвристикой; при неуверенности снимок относится к неизвестной области, а решение всё равно формируется и помечается в отчёте.
+
Можно ли доверять метрике на закрытом наборе?
+
Ожидаемо близко к валидационной, но с оговорками: порог подобран на валидации, объём мал, эталон — та же таблица. Ориентир — 0.68, а не выше.
+
+
+
+ +
+ Все числа получены из артефактов проекта. Источники: labels/labels_images.csv, models/compare_rules/rule_comparison.md, models/train_report.json, docs/labeling.md, README.md, QWEN.md. + Страница самодостаточна и не обращается к внешним ресурсам. +
+
+ + + + diff --git a/docs/slides.html b/docs/slides.html new file mode 100644 index 0000000..291c561 --- /dev/null +++ b/docs/slides.html @@ -0,0 +1,855 @@ + + + + + +Контроль качества DXA — презентация + + + + + +
+ + +
+

Контроль качества денситометрииЦифровой помощник по оценке качества DXA

+

Принимает снимок DICOM и решает, пригоден ли он для дальнейшего анализа, с указанием причины

+
+
+
2анатомические области: поясничный отдел, проксимальный отдел бедра
+
252уникальных снимка в 100 исследованиях
+
≈0.05 сна исследование при бюджете 3 минуты
+
офлайнв контейнере, без передачи снимков наружу
+
+
+ Главная работа — не архитектура, а разметка. Экспертная оценка в наборе сделана на уровне исследования; мы перенесли её на отдельные снимки и измерили, какое правило разметки даёт лучшее качество. Правило выбиралось экспериментом, а не по вкусу. +
+
+
Формат результата: одна строка на снимок — path_to_study, study_uid, image_uid, anatomical_region, quality_class, violation_type, processing_status, time_of_processing
+ +
+ + +
+

ПостановкаЧто подаём на вход и что получаем на выходе

+
+
+
+
+
DICOMдо трёх изображений в исследовании, разметки ROI внутри нет
+
→
+
Решениеобласть + бинарный класс + тип нарушения
+
→
+
ТаблицаXLSX или CSV, строка на снимок
+
+ + + + + + + + +
Колонка результатаСодержание
study_uid, image_uidидентификаторы исследования и снимка из DICOM
anatomical_regionпозвоночник, правое или левое бедро
quality_class0 — качественное, 1 — есть нарушение
violation_typeкод нарушения из единого словаря
processing_statusSuccess или Failure, ошибки не выбрасываются исключением
time_of_processingвремя обработки, секунды
+
+
+

Требования, которые определили решения

+
    +
  • Офлайн. Медицинские изображения не покидают контур — это исключило внешние модели и облачные API на всех этапах, включая разметку.
  • +
  • Контейнер и скрипт запуска. Зафиксированные версии зависимостей, образ без данных; веса монтируются при запуске.
  • +
  • До трёх минут на исследование. Мы укладываемся с запасом более тысячи раз.
  • +
  • Воспроизводимость. Фиксированные генераторы, состав разбиения в файле, происхождение модели внутри чекпоинта.
  • +
  • Приоритетные метрики. F1 и ROC-AUC с 95 % доверительными интервалами — отсюда вся логика сравнения на слайдах 8–9.
  • +
+
+
+
+ +
+ + +
+

Данные544 файла — это 252 снимка: половина набора оказалась дублями

+
+
+
+
+
544файла на диске
+
252уникальных снимка по пикселям
+
100исследований
+
99 / 79 / 73позвоночник / бедро R / бедро L
+
+

Что пришлось учесть

+
    +
  • Дубли. Склейка по хешу пиксельных данных: иначе один снимок попадал в обучение и в валидацию одновременно.
  • +
  • Конфликт имён. Два дубля одного снимка названы как разные области — область решается голосованием по именам.
  • +
  • Каталог ≠ StudyInstanceUID. Экспертная таблица ссылается на имя каталога, в DICOM лежит другой идентификатор.
  • +
  • Латеральность пуста. Теги стороны бедра не заполнены — сторону нельзя проверить по метаданным.
  • +
  • Разметки ROI нет. Ни оверлеев, ни графических аннотаций — сравнить нанесённые области напрямую не с чем.
  • +
+
+
+

Почему дубли — это не мелочь

+
+ Без склейки один и тот же снимок мог оказаться и в обучении, и в валидации. Метрика при этом выглядела бы лучше, а решение — хуже. +
+
+ После склейки обнаружилось свойство, на котором держится вся разметка: каждая анатомическая область встречается в исследовании ровно один раз. +
+

Распределение

+

Позвоночник 33 нарушения из 99, правое бедро 21 из 79, левое 22 из 73. Неравномерность учитываем отдельно: область почти однозначно определяется шириной кадра, поэтому общая метрика частично отражает различение области, а не качества.

+
+
+
+ +
+ + +
+

РазметкаОт оценки исследования — к метке отдельного снимка

+

Экспертиза в наборе сделана по исследованиям; модель работает со снимками, поэтому вердикт нужно было перенести

+
+
+
+
    +
  1. Ключ склейки — имя каталога. Таблица ссылается на каталог исследования, а не на тег DICOM; соответствие проверено — сто из ста.
  2. +
  3. Область — голосование по именам файлов. Когда один снимок назван и как позвоночник, и как бедро, побеждает частое имя; при равенстве область остаётся неопределённой.
  4. +
  5. Перенос вердикта. Оценка области переносится на её снимок. Основание — область встречается в исследовании ровно один раз, поэтому не нужно решать, какой из снимков «плохой».
  6. +
  7. Тип нарушения — из структурированных критериев таблицы. Комментарии вроде «сколиоз» сохранены дословно: перекладывать свободный текст в код означало бы догадку.
  8. +
+
+
+

Что получилось

+
+

252 снимка размечены

+

175 качественных и 77 с нарушениями. Происхождение каждой строки сохранено отдельными столбцами: вердикт эксперта, первичная пометка из данных, факт расхождения, признак переноса стороны.

+
+
+ Три снимка таблица область не оценивала. Для них метка взята из служебной пометки и помечена флагом — иначе они остались бы без метки вообще. +
+
+ Правило разметки выбиралось измерением — об этом следующий слайд: служебные пометки при подготовке набора расходились с оценкой эксперта в 15 случаях из 252. +
+
+
+
+ +
+ + +
+

Выбор правилаКакое правило разметки лучше — решил эксперимент

+

Служебные пометки расходились с оценкой эксперта в 15 случаях из 252: верить на слово нельзя ни одному варианту

+
+
+
+
Только экспертная таблицапринято по результату измерения
+
+
175 качественных
+
77 нарушений
+
+
+
+
Таблица и служебные пометкипометки при подготовке набора
+
+
160 качественных
+
92
+
+
+
+
нарушениекачественноевсего 252 снимка
+
+
15снимков, где пометка противоречит оценке эксперта
+
5 × 2прогонов: пять seed'ов на два варианта
+
одно разбиениеи один эталон для обоих вариантов
+
+
+ Учитывать ли служебные пометки — вопрос не вкуса, а измеримого качества. Постановка и результат — на слайдах 8–9. +
+
+ +
+ + +
+

ТаксономияДевять кодов нарушений — из двух документов задания

+

Пять кодирует экспертная таблица, три задаёт методика оценки пригодности, один служебный

+
+
+
Некорректная укладкаpositioning · позвоночник
+
Отклонение осиaxis_deviation · позвоночник
+
Артефакты и имплантыartifact · любая область
+
Ротация, позиционированиеrotation · бедро
+
Некорректная область интересаroi_incorrect · любая область
+
Движение, размытиеmotion · любая область
+
Анатомия видна не полностьюincomplete_anatomy · любая область
+
Ошибка разметкиlabeling_error · позвоночник
+
Нарушение без уточненияunspecified · служебный код
+
+
+ экспертная таблица + критерии методики + служебный +
+
+ Раньше словарь существовал в трёх копиях — в модуле инференса, в API и в JavaScript интерфейса, — и ни одна не знала кодов экспертной таблицы: в интерфейсе они показывались как есть. Теперь словарь один, подписи отдаёт сервер. +
+

Распределение в разметке: rotation 36, artifact 17, axis_deviation 10, roi_incorrect 7, positioning 6, unspecified 5.

+
+ +
+ + +
+

МодельЗамороженный ResNet18 и линейная голова

+
+
+
+
+
DICOMперцентильная нормализация, 224×224
+
→
+
ResNet18веса ImageNet, заморожен
+
→
+
Головалинейный слой, 2 класса
+
+ + + + + + + +
ПараметрЗначение
Обучаемые параметрытысячи вместо 11.2 млн
Вспомогательная голова областивес в потерях 0.3
Пороглогит −0.493 (вероятность 0.379)
Чекпоинт42.8 МБ
Отбор эпохисглаженный ROC-AUC, окно 5
+
+
+

Почему не полное дообучение

+
+ Полный fine-tune на двухстах снимках переобучается: train-метрика уходит в единицу, качество на валидации — к случайному. Замороженный backbone даёт стабильные признаки. +
+

Почему порог не 0.5

+

Вероятности насыщаются у краёв распределения, поэтому фиксированный порог при доле нарушений около 30 % давал почти нулевой recall. Порог подбирается по F1 на валидации и хранится в чекпоинте вместе с весами.

+

Почему при заморозке отключён и train-режим

+

Слои нормализации остаются в eval: иначе бегущие статистики продолжают обновляться на обучающих батчах, и признаки уезжают от тех, на которых калибровалась входная нормализация.

+
+
+
+ +
+ + +
+

Проверено и отвергнутоТри решения приняты по результатам экспериментов

+
+
+
+ отвергнуто +

Аугментация

+

Яркость и положение снимка сами являются признаками качества. Случайные искажения уничтожают ровно ту информацию, которую надо распознать.

+

AUC 0.87 → 0.56

+

при включении аугментации

+
+
+ отвергнуто +

Служебные пометки в данных

+

Служебные пометки расходились с оценкой эксперта в 15 случаях из 252. Обучение с ними дало худшее качество на невиданных исследованиях.

+

ROC-AUC 0.620

+

против 0.676 у правила «только таблица»

+
+
+ отвергнуто +

Локальная vision-модель 9B

+

Планировали размечать снимки визуально, офлайн. На калибровке из 14 снимков модель вынесла «непригоден» всем 14 — включая заведомо качественные — с выдуманными имплантами.

+

14 из 14

+

разделяющая способность на уровне случайной

+
+
+
+ Общее правило: инструмент проверяется на разделяющую способность до того, как строить на нём пайплайн. Каждая из трёх проверок заняла один прогон, а не дни разработки. +
+
+ +
+ + +
+

ЭкспериментКак сравнивать варианты, не обманывая себя

+
+
+
+
+ Ловушка первая. Обучать и оценивать на одних и тех же метках — круговое сравнение: выигрывает тот вариант, который проще запомнить. +

Ловушка вторая. Разбиение стратифицируется по нарушениям, а они зависят от меток. Без фиксации у вариантов были бы разные валидационные наборы, и метрики оказались бы несравнимы.

+
+

Что сделали

+
    +
  1. Зафиксировали разбиение по исследованиям — стратификация по вердикту эксперта.
  2. +
  3. Обучили оба варианта пятью seed'ами на одном и том же наборе исследований; отличаются только метки.
  4. +
  5. Оценили оба по одному эталону — вердикту эксперта на снимках валидации.
  6. +
+
+
+

Схема

+
+

Валидация: 53 снимка, 16 нарушений по эталону. Снимки валидации не участвуют в обучении ни в одном варианте.

+
+
Вариант A — только экспертная таблица77 нарушений в наборе
+
Вариант B — таблица и служебные пометки92 нарушения в наборе
+
Один эталон для обоихвердикт эксперта, а не собственные метки варианта
+
+
+

Порог для F1 подбирается на эталоне отдельно для каждой модели, поэтому F1 сравнивается как рабочая точка, а ROC-AUC и PR-AUC — как беспороговые метрики.

+
+
+
+ +
+ + +
+

РезультатПравило «только экспертная таблица» выигрывает устойчиво

+

Среднее по пяти seed'ам, 95 % доверительный интервал, оценка по одному эталону

+
+
+
+ + + + + + + + + + 0.40 + 0.50 + 0.60 + 0.70 + 0.5 — случайное угадывание + + + ROC-AUC + PR-AUC + F1 + + + + + + + 0.6764 + + + + + + 0.6199 + + + + + + 0.4759 + + + + + + 0.4046 + + + + + + 0.5676 + + + + + + 0.5426 + +
+ только экспертная таблица + со служебными пометками + отрезок — 95 % доверительный интервал +
+
+
+
+ Парная разница (таблица − с пометками) +

ROC-AUC +0.0564

+

интервал +0.0403 … +0.0725

+

PR-AUC +0.0713

+

интервал +0.0398 … +0.1028

+

F1 +0.0251

+

интервал −0.0056 … +0.0558

+
+
+ ROC-AUC и PR-AUC выше на всех пяти seed'ах, доверительный интервал не включает ноль. +
+

Оговорка: эталон — та же таблица, поэтому сравнение частично благоприятствует ей. Значимо то, что добавление служебных пометок согласие с экспертом снижает.

+
+
+
+ +
+ + +
+

Проверка вклада моделиМодель смотрит на снимок, а не угадывает анатомию

+

Нарушения распределены почти равномерно, но область почти однозначно определяется шириной кадра — поэтому нужна отдельная проверка

+
+
+
+ + + + + + + + + + 0.5 + 0.7 + 0.9 + 1.0 + порог случайного угадывания + + + Общий + Позвоночник + Бедро R + Бедро L + + + + + + 0.529 + 0.854 + + + + + 0.500 + 0.928 + + + + + 0.500 + 0.861 + + + + + 0.500 + 0.853 + +
+ модель + правило «позвоночник = нарушение» +
+
+
+
+ Внутри области правило не имеет подсказки и даёт ровно 0.500. Модель на тех же снимках — 0.85–0.93. Значит, она использует содержимое изображения. +
+

Как читать числа

+
    +
  • Общий — по всему набору, включая обучающие снимки, поэтому значения смещены вверх.
  • +
  • Проверка отвечает на вопрос «есть ли вклад содержимого», а не «каково качество на новых данных».
  • +
  • Общий AUC у правила 0.529 — тоже выше 0.5, потому что в позвоночнике нарушений чуть больше: это и есть та подсказка, которую проверка исключает.
  • +
+
+ Без этой проверки можно было бы принять за распознавание качества обычное определение анатомической области. +
+
+
+
+ +
+ + +
+

Рабочая модельМетрики чекпоинта и одна честная оговорка

+
+
+
+ + + + + + + +
Собственная валидацияЗначение
Объём: 53 снимка / 19 исследований16 нарушений
ROC-AUC0.6706
PR-AUC0.4585
F1 / recall / precision0.5600 / 0.875 / 0.412
Пороглогит −0.493 (вероятность 0.379)
+ + + + + +
ROC-AUC по областямn / нарушенийAUC
Позвоночник19 / 50.943
Бедро R17 / 60.576
Бедро L17 / 50.550
+
+
+
+ Это обычный прогон с seed по умолчанию, а не лучший из выборки. Его собственная оценка 0.6706 близка к среднему по пяти seed'ам 0.6764 — результат не отобран по удачности. +
+
+ Чего нельзя утверждать. Порог подобран на той же валидации по F1, поэтому F1 смещён вверх. Эталон — экспертная таблица, независимой истины нет. В валидации всего 16 нарушений, интервалы широкие. +
+

Что это означает для внедрения

+

Ориентир для планирования — ROC-AUC около 0.68. Модель стоит использовать как фильтр для приоритизации ручного просмотра, а не как единственное заключение: пропуск нарушения дороже ложной тревоги.

+
+
+
+ +
+ + +
+

ИнтерфейсРезультаты обработки — и как открыть детали

+

Скриншоты настоящего сервиса: загружены три снимка, обработаны за доли секунды

+
+ Полоса состояния: загруженная модель, разметка, эпоха, порог + Таблица результатов: статистика по файлам, подсказка о кликабельности строк, строки с качеством и уверенностью +

+ Сверху — что за модель работает; ниже — статистика и таблица. Подсказка над таблицей объясняет, что строки открываются кликом. +

+
+ +
+ + +
+

ИнтерфейсПанель деталей: нарушение и норма

+

Открывается кликом по строке: заключение, измерения и визуализация снимка

+
+
+
+ Панель деталей для снимка с нарушением: красный бейдж, заключение, визуализация +

Нарушение. Бейдж «Нарушение», уровень HIGH, заключение с вероятностью и порогом, тип нарушения с пометкой «эвристика».

+
+
+ Панель деталей для качественного снимка: визуализация и измерения +

Норма. Зелёный бейдж. Измерения (резкость, границы области) подписаны как справочные — их пороги не калиброваны.

+
+
+
+ +
+ + +
+

ИнтерфейсПанель «О модели»: числа без завышения

+
+
+
+ Панель «О модели»: происхождение чекпоинта, метрики сравнения правил разметки, состав данных, словарь нарушений +
+
+

Что здесь важно

+
    +
  • Названо происхождение чекпоинта: разметка, эпоха, порог решения.
  • +
  • Метрики сравнения правил с 95 % интервалами — те же числа, что в отчёте, а не отдельная «красивая» сводка.
  • +
  • Состав данных: 252 снимка, 100 исследований, 77 нарушений в разметке, 3 снимка без экспертной оценки — они не спрятаны.
  • +
  • Словарь типов нарушений с указанием источника каждого кода.
  • +
  • Список ограничений — прямо в интерфейсе, а не только в документации.
  • +
+
+ Панель собирается из ответа сервера: подписи и числа не дублируются в JavaScript, поэтому интерфейс не может разойтись с моделью. +
+
+
+
+ +
+ + +
+

ЭксплуатацияСкорость, требования и упаковка

+
+
+
15 мсна снимок на ускорителе, медиана
+
20 мсна снимок на CPU, 8 потоков
+
≈0.05 сна исследование из трёх снимков
+
>3000×запас к бюджету три минуты
+
+
+
+

Что показали замеры

+
    +
  • Разница между ускорителем и процессором — в пределах 1.4×. Время уходит на декодирование DICOM и препроцессинг, а не на сеть.
  • +
  • Значит, ускоритель для этой задачи не обязателен: минимальная конфигурация — CPU.
  • +
  • Чекпоинт 42.8 МБ, память ограничена средой исполнения, а не моделью.
  • +
+
+
+

Упаковка и API

+
    +
  • Контейнер с зафиксированными версиями; образ без данных и без весов — чекпоинт монтируется при запуске.
  • +
  • Пакетная обработка, выгрузка XLSX, текстовый отчёт DICOM SR, веб-интерфейс с панелью «О модели».
  • +
  • processing_status = Failure вместо исключения — отчёт формируется всегда.
  • +
  • 208 автотестов, включая три браузерных сценария на живом сервере.
  • +
+
+
+
+ Интерфейс не утверждает больше, чем известно решению: тип нарушения помечен как эвристика, метрика названа без завышения, средняя уверенность не называется точностью. +
+
+ +
+ + +
+

Честные границыЧто решение не умеет и почему это сказано прямо

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ОграничениеСледствиеЧто планируется
Разметка выведена из оценки исследованияпоштучной экспертной оценки снимков в наборе нетразметка отдельных снимков специалистом
Мало данных: 252 снимка, 77 нарушенийширокие интервалы, закрытый набор может дать другие цифры500+ исследований
Эталон — та же экспертная таблицасравнение правил частично благоприятствует таблиценезависимая экспертная оценка снимков
Тип нарушения — эвристика, не модель5 снимков имеют только «нарушение без уточнения»мультилейбл-модель по типам
Область определяется по ширине кадрапорог привязан к текущему оборудованиюкалибровка по метаданным аппарата
Сторона бедра в 7 исследованиях не проверяематеги латеральности в DICOM пустызапрос тега у источника данных
Разметки ROI нет в DICOMкорректность областей наследуется из таблицыпроверка на данных с экспортной разметкой
+
+ Позиция: сомнительный снимок лучше отправить специалисту, чем пропустить. Поэтому решение проектировалось так, чтобы ошибаться в сторону ручной проверки, а не автоматического одобрения. +
+
+ +
+ + +
+

ИтогТри тезиса

+
+
+

1. Правило разметки выбрано измерением, а не по вкусу

+

Одно разбиение, пять seed'ов, один эталон: учёт служебных пометок дал ROC-AUC 0.6199 против 0.6764 у разметки только по экспертной таблице — хуже на всех пяти seed'ах.

+
+
+

2. Модель проверена на то, что она смотрит на снимок

+

Внутри областей 0.85–0.93 против 0.50 у правила «позвоночник значит нарушение». Это исключает подмену распознавания качества определением анатомии.

+
+
+

3. Сказано и то, чего решение не умеет

+

Тип нарушения — эвристика, эталон — та же таблица, данных мало. Ориентир для внедрения — ROC-AUC около 0.68, использование как фильтра для приоритизации ручного просмотра.

+
+
+
Материалы: docs/pitch.html — спич, определения и техническая выкладка; docs/labeling.md — разметка и выбор правила; README.md — сборка и запуск
+ +
+ +
+ +
+ 1 / 15 +
+ → далее · ← назад · N заметки · F полный экран +
+ + + + diff --git a/requirements.txt b/requirements.txt index 64f2aa5..39685f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,3 +50,8 @@ pytest==8.4.2 # simpleitk==2.5.6 # pydicom-seg==0.4.1 # timm==1.0.29 + +# --- Сборка презентации (приложению не нужна) --- +# tools/build_deck.py собирает docs/deck.pptx из шаблона организаторов. +# В образ инференса эту зависимость ставить не нужно. +# python-pptx==1.0.2 diff --git a/run.sh b/run.sh index 1c0f786..9a4e298 100755 --- a/run.sh +++ b/run.sh @@ -6,6 +6,10 @@ # ./run.sh train [доп. аргументы для src.dxa.train] # ./run.sh infer <вход> <выход.xlsx|.csv> [доп. аргументы] # ./run.sh serve [порт] +# ./run.sh label +# ./run.sh split +# ./run.sh rename [--apply] +# ./run.sh compare # ./run.sh test # # Скрипт намеренно не содержит своей реализации обучения и инференса: вся @@ -19,6 +23,11 @@ PYTHON=${PYTHON:-python3} DATA_ROOT=${DATA_ROOT:-dataset_hack} ANNOTATION_PATH=${ANNOTATION_PATH:-"dataset_hack/НД_для_обучения/разметка.xlsx"} MODEL_PATH=${MODEL_PATH:-models/dxa_model.pth} +LABELS_DIR=${LABELS_DIR:-labels} +LABELS_CSV=${LABELS_CSV:-labels/labels_images.csv} +LABELS_REFERENCE_CSV=${LABELS_REFERENCE_CSV:-labels/labels_images_expert.csv} +SPLIT_FILE=${SPLIT_FILE:-labels/split_expert_seed42.json} +RENAME_MAP=${RENAME_MAP:-labels/rename_map.csv} PORT=${PORT:-8000} command=${1:-help} @@ -27,9 +36,11 @@ shift || true case "$command" in train) echo -e "${YELLOW}Обучение классификатора качества DXA${NC}" + echo " метки: $LABELS_CSV" "$PYTHON" -m src.dxa.train \ --data-root "$DATA_ROOT" \ --annotation-path "$ANNOTATION_PATH" \ + --labels-csv "$LABELS_CSV" \ --output-dir "$(dirname "$MODEL_PATH")" \ "$@" echo -e "${GREEN}Готово. Чекпоинт: ${MODEL_PATH}${NC}" @@ -56,6 +67,54 @@ case "$command" in "$PYTHON" -m uvicorn src.main:app --host 0.0.0.0 --port "$PORT" ;; + label) + echo -e "${YELLOW}Разметка датасета на уровне снимков${NC}" + echo " таблица: $ANNOTATION_PATH" + "$PYTHON" -m src.dxa.excel_labels \ + --data-root "$DATA_ROOT" \ + --excel "$ANNOTATION_PATH" \ + --out-dir "$LABELS_DIR" \ + "$@" + ;; + + split) + echo -e "${YELLOW}Фиксация разбиения для сравнения правил разметки${NC}" + echo " стратификация по эталону: $LABELS_REFERENCE_CSV" + "$PYTHON" -m src.dxa.train \ + --data-root "$DATA_ROOT" \ + --annotation-path "$ANNOTATION_PATH" \ + --labels-csv "$LABELS_REFERENCE_CSV" \ + --export-split "$SPLIT_FILE" \ + "$@" + ;; + + rename) + echo -e "${YELLOW}Приведение имён DICOM к единому виду${NC}" + if [ "${1:-}" = "--apply" ]; then + echo -e "${RED}Правка имён. Карта отката: $RENAME_MAP${NC}" + else + echo " режим плана; для правки добавьте --apply" + fi + "$PYTHON" -m src.dxa.rename_files \ + --data-root "$DATA_ROOT" \ + --mapping "$RENAME_MAP" \ + "$@" + ;; + + compare) + echo -e "${YELLOW}Сравнение источников разметки на одном held-out наборе${NC}" + echo " разбиение: $SPLIT_FILE" + if [ ! -f "$SPLIT_FILE" ]; then + echo -e "${RED}Нет файла разбиения. Сначала: ./run.sh split${NC}" >&2 + exit 1 + fi + "$PYTHON" -m src.dxa.compare_labels \ + --data-root "$DATA_ROOT" \ + --annotation-path "$ANNOTATION_PATH" \ + --split-file "$SPLIT_FILE" \ + "$@" + ;; + test) echo -e "${YELLOW}Запуск тестов${NC}" "$PYTHON" -m pytest tests/ -q @@ -72,19 +131,35 @@ DXA Quality Assessment — управление запуском Пример: ./run.sh infer "dataset_hack/Для теста" results.xlsx Дополнительно: --zip-out masks.zip serve [порт] Запустить HTTP API и веб-интерфейс. + label [аргументы] Разметить датасет на уровне снимков по Excel. + Результат: labels/labels_images.csv|.xlsx + split [аргументы] Зафиксировать разбиение по исследованиям. + Результат: labels/split_expert_seed42.json + rename [--apply] Привести имена DICOM к виду область_NN[_метка]. + Без --apply — только план; карта отката пишется + до правки (labels/rename_map.csv) + compare [аргументы] Сравнить источники разметки на одном held-out + наборе (нужен сначала `./run.sh split`). test Запустить тесты. Переменные окружения: DATA_ROOT Каталог датасета (по умолчанию dataset_hack) - ANNOTATION_PATH Excel с разметкой (только для отчёта о расхождениях) + ANNOTATION_PATH Excel с экспертной разметкой исследований MODEL_PATH Путь к чекпоинту (по умолчанию models/dxa_model.pth) + LABELS_DIR Каталог с результатом разметки (по умолчанию labels) + LABELS_CSV Разметка снимков для обучения (пустая строка — метки из имён файлов) + SPLIT_FILE Файл фиксированного разбиения (labels/split_expert_seed42.json) + LABELS_REFERENCE_CSV Эталон для стратификации разбиения (labels/labels_images_expert.csv) + RENAME_MAP Карта переименований для отката (по умолчанию labels/rename_map.csv) PORT Порт API (по умолчанию 8000) PYTHON Интерпретатор (по умолчанию python3) Типовой порядок работы: + ./run.sh label # разметить датасет по Excel ./run.sh train # обучить модель ./run.sh infer dataset_hack results.xlsx ./run.sh serve # веб-интерфейс на http://localhost:8000 + ./run.sh split && ./run.sh compare # сравнить варианты разметки USAGE ;; esac diff --git a/src/api/static/index.html b/src/api/static/index.html index 0b96ab5..0a1de90 100644 --- a/src/api/static/index.html +++ b/src/api/static/index.html @@ -88,6 +88,10 @@ + API Docs @@ -181,8 +185,8 @@
-

-

-

Ср. уверенность

+

-

+

Ср. уверенность модели

@@ -200,6 +204,15 @@ + +
+ + + Нажмите на любую строку — ниже откроется панель деталей: измерения снимка, тип нарушения и визуализация. + Заголовки столбцов сортируют таблицу, а кнопка «Открыть» в конце строки делает то же самое, что и клик по ней. + +
+
@@ -235,6 +248,7 @@ Уверенность + Открыть детали @@ -378,6 +392,56 @@
+ + +