bone_2026/assets/labeling.md

27 KiB
Raw Blame History

Разметка датасета и выбор правила метки

Документ описывает, откуда берутся метки снимков, как проверялось правило разметки и какие результаты из этого следуют. Числа проверяемы: ссылки на источники приведены рядом.

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 задания прямо просит «автоматическую коррекцию разметки с возможностью подтверждения специалистом».

./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):

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. Воспроизведение

./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+), чтобы сузить интервалы.
  • Калибровка порога определения области под конкретное оборудование.