# Разметка датасета и выбор правила метки Документ описывает, откуда берутся метки снимков, как проверялось правило разметки и какие результаты из этого следуют. Числа проверяемы: ссылки на источники приведены рядом. ## 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`. Они приведены к виду `<область>_[_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+), чтобы сузить интервалы. - Калибровка порога определения области под конкретное оборудование.