324 lines
27 KiB
Markdown
324 lines
27 KiB
Markdown
# Разметка датасета и выбор правила метки
|
||
|
||
Документ описывает, откуда берутся метки снимков, как проверялось правило
|
||
разметки и какие результаты из этого следуют. Числа проверяемы: ссылки на
|
||
источники приведены рядом.
|
||
|
||
## 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 |
|
||
| Склейка дублей | по хешу пиксельных данных | 478 файлов — это 251 уникальный снимок |
|
||
| Область снимка | голосование по именам файлов группы | один снимок назван и как позвоночник, и как бедро; при равенстве голосов область остаётся неопределённой (такой снимок один) |
|
||
| Вердикт | оценка области переносится на её снимок | **каждая область встречается в исследовании ровно один раз**, поэтому не нужно решать, какой из нескольких снимков «плохой» |
|
||
| Сторона бедра | при единственном снимке бедра берётся единственный заполненный столбец | в 71 из 72 исследований с двумя бёдрами столбцы совпадают с именами файлов; в 7 исследованиях с одним снимком заполнена противоположная сторона. Факт переноса фиксируется флагом `laterality_mirrored`; теги `Laterality` в DICOM пусты |
|
||
| Тип нарушения | только из структурированных критериев | комментарии («сколиоз», «эндопротезирование ТБС») сохранены дословно: перекладывать свободный текст в код — догадка |
|
||
|
||
## 4. Правило метки и почему оно такое
|
||
|
||
Метка могла строиться двумя способами: только из таблицы или с добавлением
|
||
пометок, которые вручную проставлялись в именах файлов (суффикс `_bad`). Пометки
|
||
в именах оказались ненадёжными: они расходились с оценкой эксперта в **62 случаях
|
||
из 251**. Выбор сделан измерением, а не по вкусу.
|
||
|
||
**Постановка.** Разбиение по исследованиям зафиксировано один раз, оба варианта
|
||
обучены пятью 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.6726** [0.6367, 0.7086] | 0.6003 [0.5592, 0.6415] |
|
||
| PR-AUC | **0.4753** [0.4188, 0.5318] | 0.3864 [0.3487, 0.4242] |
|
||
| F1 | **0.5559** [0.5212, 0.5906] | 0.5354 [0.4978, 0.5729] |
|
||
| Recall | 0.7125 | 0.7875 |
|
||
| Precision | 0.4716 | 0.4087 |
|
||
|
||
Парная разница («только таблица» − «с суффиксами»):
|
||
|
||
| Метрика | Δ, среднее [95 % ДИ] | Знаки по seed'ам |
|
||
|---|---|---|
|
||
| ROC-AUC | **+0.0723** [+0.0541, +0.0905] | `+++++` |
|
||
| PR-AUC | **+0.0889** [+0.0500, +0.1277] | `+++++` |
|
||
| F1 | +0.0205 [−0.0182, +0.0592] | `+-+-+` |
|
||
| Precision | +0.0629 [−0.0133, +0.1392] | `+++-+` |
|
||
| Recall | −0.0750 [−0.1931, +0.0431] | `0---0` |
|
||
|
||
**Решение: принято правило «только экспертная таблица».** Дополнительные пометки
|
||
из имён файлов ухудшали согласие модели с экспертом на невиданных
|
||
исследованиях: вариант с ними чаще срабатывал (recall 0.79), но за счёт
|
||
точности, а по беспороговым метрикам проигрывал на всех пяти seed'ах.
|
||
|
||
Оговорка: эталон — та же экспертная таблица, поэтому вариант, обучавшийся
|
||
непосредственно на ней, находится в выигрышном положении. Значимо здесь другое:
|
||
добавление ненадёжных пометок **снижает** согласие с экспертом, что и служит
|
||
аргументом против них.
|
||
|
||
## 5. Словарь типов нарушений
|
||
|
||
| Код | Подпись | Область | Источник |
|
||
|---|---|---|---|
|
||
| `positioning` | Некорректная укладка | позвоночник | таблица |
|
||
| `axis_deviation` | Отклонение оси | позвоночник | таблица |
|
||
| `artifact` | Артефакты и импланты | любая | таблица |
|
||
| `rotation` | Ротация, позиционирование | бедро | таблица |
|
||
| `roi_incorrect` | Некорректная область интереса | любая | таблица |
|
||
| `motion` | Движение, размытие | любая | критерии методики |
|
||
| `incomplete_anatomy` | Анатомия видна не полностью | любая | критерии методики |
|
||
| `labeling_error` | Ошибка разметки | позвоночник | критерии методики |
|
||
| `unspecified` | Нарушение без уточнения | любая | служебный |
|
||
|
||
Распределение в разметке: `rotation` 35, `artifact` 17, `axis_deviation` 10,
|
||
`roi_incorrect` 7, `positioning` 6, `unspecified` 5. Всего 80 критериев на 76
|
||
нарушений: у части снимков таблица отмечает по два критерия.
|
||
|
||
Словарь один на всё решение (`src/dxa/violations.py`): коды используют инференс,
|
||
отчёт DICOM SR и веб-интерфейс, подписи отдаёт сервер, копий в JavaScript нет.
|
||
|
||
## 6. Результат разметки
|
||
|
||
`labels/labels_images.csv` (и XLSX) — по одной строке на уникальный снимок:
|
||
|
||
| | позвоночник | бедро R | бедро L | неопред. | всего |
|
||
|---|---|---|---|---|---|
|
||
| качественных | 66 | 58 | 51 | 0 | 175 |
|
||
| с нарушением | 33 | 20 | 22 | 1 | 76 |
|
||
| **итого** | **99** | **78** | **73** | **1** | **251** |
|
||
|
||
Помимо метки в 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`
|
||
(эталон: только снимки с экспертной оценкой, 248 строк) и
|
||
`split_expert_seed42.json` (зафиксированное разбиение).
|
||
|
||
## 7. Гигиена данных: имена файлов
|
||
|
||
Имена проставлялись вручную и разошлись: `spine_1` и `spine_01`, `Spine_01`,
|
||
`r_spine_03`, `r_hip03`, `r_hop_02` (опечатка), `spine-1`. Они приведены к виду
|
||
`<область>_<NN>[_good|_bad].dcm` инструментом `src/dxa/rename_files.py`;
|
||
переименовано 344 файла из 548, карта отката — `labels/rename_map.csv`.
|
||
|
||
Переименование сделано **после** того, как разметка и метрики были посчитаны, и
|
||
проверено, что оно на них не влияет: набор из 252 пиксельных групп идентичен до и
|
||
после, разметка не изменилась ни в одной строке. Суффиксы `_good`/`_bad`
|
||
сохранены как были и в метках не участвуют — только как диагностический столбец.
|
||
Числа в этих двух абзацах — состояние на момент проверки; актуальное состояние
|
||
набора и разметки приведено ниже.
|
||
|
||
### Расхождение разметки с датасетом и его устранение (2026-09-27)
|
||
|
||
К описанному проходу добавился более поздний: 2026-09-27 около 02:02 суффиксы
|
||
`_good`/`_bad` дописаны ещё 54 файлам, то есть **после** сборки разметки
|
||
(2026-09-26 22:35). `apply_excel_labels` сопоставляет метки строго по
|
||
`path_to_image`, поэтому во всех четырёх файлах разметки (`labels_images.csv`,
|
||
`_table`, `_union`, `_expert`) 20 строк стали ссылаться на имена, которых больше
|
||
нет. Последствие: эти 20 снимков теряли экспертную метку и падали на пометку из
|
||
имени файла, у 6 из них метка инвертировалась 0 → 1 (вердикт эксперта — «годное»,
|
||
файл назван `..._bad.dcm`), и пайплайн отдавал 251 снимок / 82 нарушения вместо
|
||
записанных выше 252 / 77. Расхождение нашли сравнением меток с именами файлов;
|
||
тесты падали на числах (`assert 251 == 252`), то есть оно было замечено, а не
|
||
осталось незамеченным.
|
||
|
||
Устранено пересборкой: `./run.sh label` и три варианта правила, `./run.sh split`,
|
||
затем переобучение `./run.sh train`. Актуальное состояние — 251 снимок / 76
|
||
нарушений, все пути разметки существуют, расхождений с датасетом нет. Пометки в
|
||
именах при этом расходятся с вердиктом эксперта в **62 случаях из 251** (было 15
|
||
из 252): после второго переименования пометок стало больше, и они чаще спорят с
|
||
таблицей. На метку это не влияет — правило `table` их не использует (см. §4), —
|
||
но именно эти 62 снимка имеет смысл разобрать первыми в интерфейсе ручной
|
||
разметки (см. §10).
|
||
|
||
Чистка датасета в тот же день: удалены 38 лишних байт-идентичных копий `.dcm`
|
||
внутри `НД_для_обучения` и 10 файлов `.DS_Store`. На разметку и на набор
|
||
пиксельных групп это не влияет: групп 251, все совпадают с `path_to_image`. На
|
||
диске 482 `.dcm` из 494 файлов. У каждого удалённого файла остался байт-идентичный
|
||
близнец, поэтому потеряно только имя, а не содержимое.
|
||
|
||
## 8. Оценка качества модели
|
||
|
||
Разбиение по исследованиям (по умолчанию seed 42): обучение 198 снимков /
|
||
81 исследование, валидация 53 снимка / 19 исследований, 16 нарушений.
|
||
|
||
| Метрика | Значение | Как получено |
|
||
|---|---|---|
|
||
| ROC-AUC | **0.6726** [0.6367, 0.7086] | 5 seed'ов на фиксированном разбиении, эталон — вердикт эксперта |
|
||
| PR-AUC | 0.4753 [0.4188, 0.5318] | там же |
|
||
| F1 | 0.5559 [0.5212, 0.5906] | там же; порог подобран по F1 на валидации, поэтому смещён вверх |
|
||
| ROC-AUC по областям | позвоночник 0.929, бедро R 0.606, бедро L 0.567 | рабочий чекпоинт, его собственная валидация |
|
||
|
||
Рабочий чекпоинт — обычный прогон с seed по умолчанию (эпоха 57, порог логита
|
||
−0.1370 → вероятность 0.466), его собственная валидационная ROC-AUC 0.6689
|
||
близка к среднему по seed'ам, то есть результат не отобран по удачности.
|
||
|
||
Проверка, что модель смотрит на снимок, а не угадывает анатомию: внутри областей
|
||
она даёт AUC 0.88–0.95, правило «позвоночник значит нарушение» — ровно 0.50.
|
||
Числа считаются на всём наборе, включая обучающие снимки, поэтому смещены вверх и
|
||
отвечают на вопрос «есть ли вклад содержимого», а не «каково качество на новых
|
||
данных».
|
||
|
||
## 9. Ограничения
|
||
|
||
1. **Разметка унаследована от исследования.** Таблица оценивает исследование, а
|
||
не снимок; перенос однозначен, потому что область встречается один раз, но
|
||
исходная оценка всё равно не поштучная.
|
||
2. **Мало данных:** 251 снимок, 76 нарушений. Интервалы широкие.
|
||
3. **Эталон — та же таблица.** Независимой истины нет; вариант, обучавшийся на
|
||
таблице, в сравнении в выигрышном положении.
|
||
4. **Тип нарушения — эвристика**, а не вывод модели: 5 снимков имеют только
|
||
`unspecified`, у остальных тип приходит из критериев таблицы.
|
||
5. **Три снимка без экспертной оценки** размечены по пометке в имени файла и
|
||
помечены `filename_fallback`.
|
||
6. **Сторона бедра в 7 исследованиях не проверяема:** теги латеральности пусты.
|
||
7. **Корректность областей интереса наследуется из таблицы:** разметки ROI в
|
||
DICOM нет, сравнить её напрямую не с чем.
|
||
8. **Пометки в именах файлов спорят с таблицей в 62 случаях из 251.** На метку
|
||
это не влияет (правило `table`), но означает, что один из двух источников
|
||
ошибается почти в четверти набора. Разбирать их поштучно можно в `/label`; для
|
||
выбора правила измерения в §8 такие снимки не использовались.
|
||
9. **Поштучной разметки всё ещё нет.** Интерфейс `/label` (§10) её даёт, но им
|
||
ещё не пользовались: пока набор размечен на уровне исследования.
|
||
|
||
## 10. Ручная разметка: интерфейс `/label`
|
||
|
||
Поштучной экспертной оценки снимков в наборе нет — это ограничение №1, и
|
||
автоматически его не закрыть: локальная vision-модель оказалась непригодна (§11),
|
||
а экспертная таблица описывает исследование. Поэтому в сервисе есть интерфейс
|
||
ручной разметки — раздел 2.6 задания прямо просит «автоматическую коррекцию
|
||
разметки с возможностью подтверждения специалистом».
|
||
|
||
```bash
|
||
./run.sh serve # http://localhost:8000/label
|
||
```
|
||
|
||
Что он делает:
|
||
|
||
* показывает снимок целиком (PNG через `/api/v1/labeling/image`) без наложений,
|
||
чтобы разметчик судил по изображению, а не по подсказке алгоритма;
|
||
* отдаёт список снимков с текущей меткой и её источником (`table`, `filename` или
|
||
`manual`), причём снимки с расхождением метки и пометки в имени файла идут
|
||
первыми — именно там один из источников ошибается (см. §9, п. 8);
|
||
* сохраняет вердикт: анатомическая область, «годное / нарушение», тип нарушения из
|
||
`violations.py` и комментарий. Тип нарушения проверяется на совместимость с
|
||
областью (ротация — критерий бедра), у качественного снимка типа быть не может,
|
||
а опечатка в коде не превращается молча в `unspecified`;
|
||
* пишет вердикты в `labels/manual_labels.csv`. Формат тот же, что у построенной
|
||
разметки, поэтому файл читается `load_labels_csv` и принимается обучением как
|
||
`--labels-csv`;
|
||
* выгружает `scope=all` — весь набор с наложенными ручными вердиктами (готовый
|
||
источник меток для `./run.sh train`) — или `scope=reviewed` — только разобранные
|
||
снимки, чтобы сверить их с построенной разметкой.
|
||
|
||
Оценка модели в интерфейсе намеренно не показывается: разметчик, видя подсказку,
|
||
соглашался бы с ней, и поштучная разметка теряла бы ценность независимого
|
||
суждения. Прогресс по областям и число расхождений показаны, чтобы работу можно
|
||
было вести частями и прерывать.
|
||
|
||
Горячие клавиши: `g` — годное, `b` — нарушение, `Enter` — сохранить и перейти к
|
||
следующему, `d` — снять вердикт, `j`/`k` — навигация по списку.
|
||
|
||
Инструмент не заменяет эксперта: он только фиксирует суждение специалиста в
|
||
формате, который пайплайн уже умеет читать.
|
||
|
||
### Передача врачу: автономный пакет
|
||
|
||
`/label` требует запущенного сервиса и Python, а размечать должен врач — обычно на
|
||
своей машине, вне сети и без установки чего-либо. Поэтому тот же сценарий
|
||
собирается в один HTML-файл со встроенными снимками (`src/dxa/review_pack.py`):
|
||
|
||
```bash
|
||
python -m src.dxa.review_pack --out review/doctor_review.html # 251 снимок, ~7 МБ
|
||
python -m src.dxa.review_pack --merge review/doctor.csv \
|
||
--out labels/labels_images_reviewed.csv # наложить вердикты врача
|
||
```
|
||
|
||
Страница не делает ни одного сетевого запроса (проверяется тестом), вердикты
|
||
хранит браузер, выгрузка — CSV в формате `manual_labels.csv` с дополнительной
|
||
колонкой `pack_id` (отпечаток набора: по нему видно, что файл вернулся из того же
|
||
пакета). Модель в пакет не попадает: если показать разметчику оценку алгоритма,
|
||
он будет с ней соглашаться, и независимого суждения не получится.
|
||
|
||
**Что стоит размечать.** Порядок в пакете и в интерфейсе — как в §7: сперва 62
|
||
снимка, где метка разметки расходится с пометкой в имени файла (там ошибается
|
||
один из двух источников). Но одних расхождений мало: они отобраны по признаку,
|
||
который сам может быть смещён. Чтобы получить **независимую** оценку качества,
|
||
нужен ещё случайный подсчёт — если врач разметит, например, случайные 50 снимков
|
||
из 251, по ним можно будет измерить ROC-AUC и F1 модели на человеческом суждении,
|
||
а не на той же экспертной таблице, из которой выведена разметка (ограничение 3 в
|
||
§9). Такой замер в проекте ещё не делался: ни один снимок пока не размечен
|
||
поштучно.
|
||
|
||
## 11. Отрицательный результат: локальная vision-модель
|
||
|
||
Планировалась визуальная разметка локальной vision-моделью (9 млрд параметров,
|
||
офлайн), чтобы не зависеть от таблицы. На калибровке по 14 снимкам, из которых 8
|
||
заведомо с нарушениями, модель вынесла «непригоден» всем 14, включая все
|
||
качественные, с шаблонными формулировками и выдуманными имплантами. Разделяющая
|
||
способность — на уровне случайной, поэтому как разметчик модель непригодна.
|
||
|
||
Вывод, который стоит зафиксировать: разделяющую способность инструмента нужно
|
||
проверять **до** того, как строить на нём пайплайн. Инструменты рендера снимков и
|
||
контактных листов остались в `src/dxa/render.py` — они полезны для выборочной
|
||
ручной проверки.
|
||
|
||
## 12. Воспроизведение
|
||
|
||
```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
|
||
```
|
||
|
||
## 13. Что дальше
|
||
|
||
- Поштучная разметка снимков специалистом через `/label` (§10) — снимет
|
||
ограничение №1 и позволит перемерить качество на независимом суждении.
|
||
- Мультилейбл по типам нарушений вместо эвристики: в интерфейсе тип уже
|
||
проставляется вручную, но модель его не предсказывает.
|
||
- Больше исследований (500+), чтобы сузить интервалы.
|
||
- Калибровка порога определения области под конкретное оборудование.
|