Initial commit: project structure, scripts and documentation

This commit is contained in:
2026-08-22 13:17:29 +03:00
commit 5042305df0
54 changed files with 14166 additions and 0 deletions
BIN
View File
Binary file not shown.
+975
View File
@@ -0,0 +1,975 @@
% ============================================================================
% ЛАБОРАТОРНАЯ РАБОТА №1
% АУДИТ И ПОДГОТОВКА МЕДИЦИНСКИХ ДАННЫХ
% ============================================================================
% КОМПИЛЯЦИЯ: pdflatex (ДВА ПРОХОДА)
% ============================================================================
\documentclass[12pt,a4paper]{article}
% ============================================================================
% ПРЕАМБУЛА
% ============================================================================
\usepackage[utf8]{inputenc}
\usepackage[T2A]{fontenc}
\usepackage[russian]{babel}
\usepackage{amsmath,amssymb,amsfonts}
\usepackage{graphicx}
\usepackage{hyperref}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{geometry}
\usepackage{booktabs}
\usepackage{longtable}
\usepackage{caption}
\usepackage{subcaption}
\usepackage{titlesec}
\usepackage{float}
\usepackage{array}
\usepackage{multirow}
\usepackage{enumitem}
\geometry{left=2.5cm,right=2cm,top=2cm,bottom=2cm}
% --- Гиперссылки ---
\hypersetup{
colorlinks=true,
linkcolor=blue,
urlcolor=blue,
pdfauthor={Студент},
pdftitle={Лабораторная работа N1}
}
% --- Листинги (максимально упрощённые) ---
\lstset{
basicstyle=\ttfamily\footnotesize,
keywordstyle=\color{blue},
commentstyle=\color{green!60!black},
stringstyle=\color{red},
breaklines=true,
frame=single,
numbers=left,
numberstyle=\tiny,
tabsize=4,
language=Python,
extendedchars=true,
inputencoding=utf8,
}
% --- Заголовки ---
\titleformat{\section}{\Large\bfseries\centering}{}{0pt}{}
\titleformat{\subsection}{\large\bfseries}{}{0pt}{}
\titleformat{\subsubsection}{\normalsize\bfseries}{}{0pt}{}
% --- Список литературы ---
\renewcommand{\refname}{Список использованной литературы}
\begin{document}
% ============================================================================
% ТИТУЛЬНЫЙ ЛИСТ
% ============================================================================
\begin{titlepage}
\begin{center}
\vspace*{0.5cm}
{\Large \textbf{ФГБУ <<НМИЦ онкологии им. Н.Н. Блохина>> Минздрава России}}
\vspace{0.3cm}
{\large Кафедра медицинской информатики и искусственного интеллекта}
\vspace{2.5cm}
{\Huge \textbf{ЛАБОРАТОРНАЯ РАБОТА №1}}
\vspace{0.5cm}
{\huge \textbf{Аудит и подготовка медицинских данных}}
\vspace{0.5cm}
{\Large для задач искусственного интеллекта в онкологии}
\vspace{2.5cm}
\begin{tabular}{p{0.4\textwidth}p{0.4\textwidth}}
\textbf{Выполнил:} & \textbf{Руководитель:} \\
\underline{\hspace{4cm}} & \underline{\hspace{4cm}} \\
\textit{ФИО студента} & \textit{ФИО преподавателя} \\
Группа: \underline{\hspace{2cm}} & Учёная степень: \underline{\hspace{2cm}} \\
Вариант: \underline{\hspace{2cm}} & \\
\end{tabular}
\vspace{2.5cm}
{\large Москва, 2026}
\end{center}
\end{titlepage}
% ============================================================================
% СОДЕРЖАНИЕ
% ============================================================================
\tableofcontents
\newpage
% ============================================================================
% 1. ВВЕДЕНИЕ
% ============================================================================
\section{Введение}
\subsection{Цель работы}
Сформировать у слушателей профессиональные компетенции в области аудита, предобработки и структурирования медицинских изображений для последующего обучения моделей искусственного интеллекта в задачах диагностики и лечения онкологических заболеваний.
\textbf{Задачи работы:}
\begin{enumerate}
\item Провести аудит структуры медицинского датасета в формате NRRD.
\item Реализовать конвертацию полигональной разметки в бинарные маски.
\item Выполнить распаковку 3D-данных в 2D-срезы формата YOLO.
\item Создать конфигурационные файлы \texttt{predefined\_classes.txt} и \texttt{data.yaml}.
\item Проанализировать роль клинического эксперта в подготовке данных.
\end{enumerate}
\subsection{Клинический контекст}
В рамках лабораторной работы используется датасет \texttt{1\_2236040\_zno\_cyst\_AF}, представляющий собой мультимодальные медицинские данные пациентов с новообразованиями почек. Исследование направлено на автоматическую детекцию и сегментацию анатомических структур почек и патологических образований.
\textbf{Объекты исследования:}
\begin{itemize}
\item Почки (правая и левая)
\item Сегменты почек (верхний, средний, нижний)
\item Патологические образования: злокачественные опухоли, доброкачественные опухоли, кисты, абсцессы
\end{itemize}
\textbf{Клиническая значимость:} Ранняя диагностика и точная дифференцировка патологических образований почек критически важны для выбора оптимальной тактики лечения и прогнозирования исходов. Внедрение методов искусственного интеллекта позволяет автоматизировать анализ медицинских изображений, снизить нагрузку на врачей и повысить точность диагностики.
\subsection{Обзор форматов данных}
\subsubsection{DICOM (Digital Imaging and Communications in Medicine)}
DICOM -- международный стандарт создания, хранения и передачи медицинской визуальной информации.
\textbf{Ключевые особенности:}
\begin{itemize}
\item Каждый файл содержит не только матрицу пикселей, но и богатый набор метаданных.
\item Метаданные включают информацию о пациенте, модальности, параметрах сканирования, оборудовании.
\item Аннотации областей интереса (ROI) хранятся в отдельных файлах DICOM-RT Structure Set.
\item Формат поддерживает различные модальности: КТ, МРТ, ПЭТ, маммография и др.
\end{itemize}
\textbf{Структура DICOM-файла:}
\begin{itemize}
\item \textbf{Заголовок (метаданные)}: информация о пациенте, исследовании, серии, параметрах сканирования.
\item \textbf{Тело (пиксельные данные)}: матрица интенсивностей.
\end{itemize}
\subsubsection{NRRD (Nearly Raw Raster Data)}
NRRD -- формат, широко используемый в научных исследованиях для хранения многомерных растровых данных.
\textbf{Ключевые особенности:}
\begin{itemize}
\item Простая структура: текстовый заголовок + бинарные данные.
\item Поддержка 3D и 4D массивов.
\item Минимальные метаданные, но критически важные.
\item Параметры \texttt{space directions} определяют направление осей.
\item Параметр \texttt{space origin} задаёт начало координат.
\end{itemize}
\textbf{Пример структуры NRRD-файла:}
\begin{verbatim}
NRRD0004
type: short
dimension: 3
sizes: 512 512 100
spacings: 0.5 0.5 1.0
space: left-posterior-superior
space directions: (1,0,0) (0,1,0) (0,0,1)
space origin: (0,0,0)
... binary data ...
\end{verbatim}
\subsubsection{Сравнение форматов}
\begin{table}[H]
\centering
\caption{Сравнение форматов DICOM и NRRD}
\begin{tabular}{p{3.5cm}|p{4.5cm}|p{4.5cm}}
\toprule
\textbf{Характеристика} & \textbf{DICOM} & \textbf{NRRD} \\
\midrule
Структура & Файл + метаданные & Заголовок + бинарные данные \\
Размер метаданных & Большой (медицинский контекст) & Минимальный (геометрия) \\
Поддержка 3D & Да (через серии) & Да (родная) \\
Читаемость & Требуется специализированное ПО & Поддерживается многими библиотеками \\
Разметка & Отдельные файлы (RT Structure) & В том же файле или отдельном .seg \\
Применение & Клиническая практика & Научные исследования \\
\bottomrule
\end{tabular}
\end{table}
% ============================================================================
% 2. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ
% ============================================================================
\section{Теоретическая часть}
\subsection{Формат DICOM и DICOM-RT Structure Set}
DICOM-RT Structure Set (DICOM-RTSS) -- это специальный объект DICOM, предназначенный для хранения структурных наборов, включая контуры областей интереса (ROI).
\textbf{Структура DICOM-RTSS:}
\begin{itemize}
\item \textbf{ROI Contour Sequence}: последовательность контуров для каждой ROI.
\item \textbf{Contour Sequence}: точки контура в координатах DICOM.
\item \textbf{ROI Observations}: информация о каждой ROI (тип, название).
\end{itemize}
\textbf{Формат хранения контуров:}
\begin{verbatim}
(3006, 0039) Contour Image Sequence
(3006, 0040) Contour Geometric Type: CLOSED_PLANAR
(3006, 0042) Contour Number: 1
(3006, 0050) Contour Data: [x1, y1, z1, x2, y2, z2, ...]
\end{verbatim}
Координаты контуров хранятся в физических координатах DICOM (мм) и могут быть преобразованы в пиксельные координаты с использованием аффинных преобразований.
\subsection{Формат NRRD: пространственная ориентация}
Для работы с 3D-данными критически важно сохранение пространственной ориентации. Параметры \texttt{space directions} и \texttt{space origin} определяют геометрию объёма.
\textbf{Параметры ориентации:}
\begin{itemize}
\item \texttt{space directions}: матрица направления осей.
\item \texttt{space origin}: координаты начала координат.
\item \texttt{spacings}: расстояния между пикселями по каждой оси.
\end{itemize}
\textbf{Математическое представление:}
\begin{equation}
\mathbf{p}_{world} = \mathbf{D} \cdot \mathbf{p}_{voxel} + \mathbf{o}
\label{eq:orientation}
\end{equation}
где:
\begin{itemize}
\item $\mathbf{p}_{world}$ -- координаты в физическом пространстве.
\item $\mathbf{D}$ -- матрица направления ($3 \times 3$).
\item $\mathbf{p}_{voxel}$ -- координаты вокселя.
\item $\mathbf{o}$ -- смещение (space origin).
\end{itemize}
\textbf{Пример матрицы направления:}
\begin{equation}
\mathbf{D} = \begin{pmatrix}
-1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{pmatrix}
\end{equation}
Потеря этих параметров при конвертации делает невозможным использование данных для обучения 3D-нейронных сетей.
\subsection{Пайплайн подготовки данных для YOLO}
Стандартный пайплайн подготовки данных включает следующие этапы:
\begin{enumerate}
\item \textbf{Аудит данных}: проверка структуры, целостности, соответствия форматам.
\item \textbf{Извлечение срезов}: конвертация 3D-данных в 2D-срезы.
\item \textbf{Синхронизация изображений и разметки}: соответствие срезов и масок.
\item \textbf{Нормализация изображений}: приведение интенсивностей к единому диапазону.
\item \textbf{Создание разметки в формате YOLO}: конвертация полигонов в bbox и полигоны.
\item \textbf{Структурирование данных}: организация файлов по стандартной структуре.
\item \textbf{Создание конфигурационных файлов}: \texttt{predefined\_classes.txt} и \texttt{data.yaml}.
\end{enumerate}
\textbf{Стандартная структура директорий:}
\begin{verbatim}
experiment/
- images/ # 2D-срезы для обучения
- images_labels/ # Bbox-разметка (YOLO формат)
- polygons/ # Полигоны для сегментации
- images_result/ # Визуализация результатов
- masks_sam/ # Бинарные маски
- predefined_classes.txt # Схема классов
- data.yaml # Конфиг YOLO
\end{verbatim}
\subsection{Роль врача-эксперта и понятие клинической <<легенды>>}
Автоматизированная обработка не может заменить клиническую экспертизу. Под клинической <<легендой>> датасета понимается полное и точное описание данных.
\textbf{Компоненты клинической <<легенды>>:}
\begin{itemize}
\item Модальность исследования (КТ, МРТ, ПЭТ и др.)
\item Протокол сканирования
\item Подтверждённый диагноз
\item Критерии включения и исключения
\item Правила разметки областей интереса
\item Источник данных
\item Версия программного обеспечения сканера
\end{itemize}
\textbf{Риски отсутствия экспертизы:}
\begin{enumerate}
\item \textbf{Формирование ложных паттернов}: нейросеть может научиться распознавать не патологию, а артефакты сканирования или особенности оборудования (<<shortcut learning>>).
\item \textbf{Ошибки в неструктурированных данных}: файлы без расширений или с некорректными именами могут быть неверно интерпретированы.
\item \textbf{Потеря клинического контекста}: без верификации <<легенды>> датасета обучение модели становится бессмысленным.
\end{enumerate}
\textbf{Обязательные меры:}
\begin{itemize}
\item Верификация выборки квалифицированным врачом-радиологом или онкологом.
\item Документирование всех этапов подготовки данных.
\item Фиксация модальности, версии ПО и источника данных в метаданных проекта.
\end{itemize}
% ============================================================================
% 3. ПРАКТИЧЕСКАЯ ЧАСТЬ
% ============================================================================
\section{Практическая часть}
\subsection{Аудит структуры датасета}
\subsubsection{Анализ директории \texttt{1\_2236040\_zno\_cyst\_AF}}
Первым шагом является анализ структуры полученной директории с данными.
\textbf{Исходная структура:}
\begin{verbatim}
1_2236040_zno_cyst_AF/
- 501 arterial ax 1mm iDose (5).nrrd # Исходные 3D данные
- Segmentation.seg.nrrd # Сегментационная маска
- dcm/ # DICOM-срезы (1042 файла)
- images/ # PNG-срезы (300 файлов)
- images_labels/ # Bbox-разметка
- polygons/ # Полигоны для сегментации
- images_result/ # Визуализация результатов
- masks_sam/ # Маски SAM
\end{verbatim}
\textbf{Статистика данных:}
\begin{table}[H]
\centering
\caption{Статистика датасета}
\begin{tabular}{l|r}
\toprule
\textbf{Тип данных} & \textbf{Количество} \\
\midrule
DICOM-файлы & 1042 \\
PNG-изображения & 300 \\
Файлы разметки (bbox) & 300 \\
Файлы разметки (polygons) & 300 \\
Классы & 12 \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Выявленные особенности:}
\begin{enumerate}
\item Исходные данные представлены в формате NRRD (изображение и сегментационная маска).
\item DICOM-файлы сгенерированы из NRRD.
\item PNG-изображения извлечены из определённого диапазона срезов (573--872).
\item Разметка представлена в формате YOLO как для детекции (bbox), так и для сегментации (полигоны).
\item Присутствуют \texttt{.gitkeep} файлы для сохранения пустых директорий.
\end{enumerate}
\subsubsection{Проверка целостности данных}
Для проверки целостности необходимо:
\begin{enumerate}
\item Сопоставить количество изображений и файлов разметки.
\item Проверить соответствие идентификаторов срезов.
\item Валидировать форматы координат в файлах разметки.
\item Проверить отсутствие повреждённых файлов.
\end{enumerate}
\textbf{Пример проверки соответствия:}
\begin{lstlisting}
from pathlib import Path
images_dir = Path("data/1_2236040_zno_cyst_AF/images")
labels_dir = Path("data/1_2236040_zno_cyst_AF/images_labels")
images = set(f.stem for f in images_dir.glob("*.png"))
labels = set(f.stem for f in labels_dir.glob("*.txt"))
missing_labels = images - labels
missing_images = labels - images
print(len(images), len(labels))
print(len(missing_labels), len(missing_images))
\end{lstlisting}
\subsection{Конвертация полигонов в бинарные маски}
\subsubsection{Извлечение координат из DICOM-RT}
Для конвертации полигонов из DICOM-RT Structure Set в бинарные маски используется библиотека \texttt{pydicom}.
\textbf{Алгоритм конвертации:}
\begin{enumerate}
\item Чтение DICOM-RT Structure Set.
\item Извлечение контуров областей интереса (ROI).
\item Преобразование физических координат в пиксельные.
\item Растеризация полигонов в бинарные маски.
\item Визуальный контроль качества.
\end{enumerate}
\textbf{Реализация:}
\begin{lstlisting}
import numpy as np
from skimage.draw import polygon
def rasterize_contour(coords, shape):
mask = np.zeros(shape, dtype=np.uint8)
rr, cc = polygon(coords[:, 1], coords[:, 0], shape)
mask[rr, cc] = 1
return mask
\end{lstlisting}
\subsubsection{Визуальный контроль качества}
После конвертации необходимо визуально проверить качество полученных масок.
\textbf{Скрипт для визуализации:}
\begin{lstlisting}
import matplotlib.pyplot as plt
def show_mask(image, mask):
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].imshow(image, cmap='gray')
axes[0].set_title('Original')
axes[0].axis('off')
axes[1].imshow(image, cmap='gray')
axes[1].imshow(mask, cmap='jet', alpha=0.5)
axes[1].set_title('Mask')
axes[1].axis('off')
plt.show()
\end{lstlisting}
\textbf{Типичные проблемы:}
\begin{itemize}
\item <<Разрывы>> контуров из-за повреждённых тегов.
\item Смещение контуров из-за ошибок в аффинных преобразованиях.
\item Пропущенные срезы в многослойных структурах.
\end{itemize}
\subsection{Распаковка NRRD в 2D-срезы}
\subsubsection{Загрузка NRRD-файлов}
Для работы с NRRD-файлами используется библиотека \texttt{nrrd}.
\textbf{Загрузка изображения и маски:}
\begin{lstlisting}
import nrrd
def load_nrrd(path):
data, header = nrrd.read(path)
return data, header
img_data, img_header = load_nrrd("image.nrrd")
seg_data, seg_header = load_nrrd("seg.seg.nrrd")
\end{lstlisting}
\subsubsection{Синхронизация срезов}
Критически важно сохранить соответствие между срезами изображения и маски.
\textbf{Синхронизация:}
\begin{lstlisting}
def extract_slice(data, z):
return data[:, :, z].T
img_slice = extract_slice(img_data, 600)
seg_slice = extract_slice(seg_data, 600)
\end{lstlisting}
\subsubsection{Сохранение пространственной ориентации}
При конвертации необходимо сохранить параметры ориентации.
\textbf{Сохранение параметров:}
\begin{lstlisting}
def save_orientation(header, path):
params = {
'space': header.get('space', ''),
'space_directions': header.get('space directions', ''),
'space_origin': header.get('space origin', ''),
'spacings': header.get('spacings', ''),
}
with open(path, 'w') as f:
for k, v in params.items():
f.write(k + ': ' + str(v) + '\n')
\end{lstlisting}
\subsection{Создание конфигурационных файлов}
\subsubsection{\texttt{predefined\_classes.txt}}
Файл \texttt{predefined\_classes.txt} содержит полную схему классов для эксперимента.
\textbf{Содержимое файла:}
\begin{lstlisting}
right_kidney_ID1
right_kidney_upper_segment_ID2
right_kidney_middle_segment_ID3
right_kidney_lower_segment_ID4
left_kidney_ID5
left_kidney_upper_segment_ID6
left_kidney_middle_segment_ID7
left_kidney_lower_segment_ID8
malignant_tumor_ID9
benign_tumor_ID10
cyst_ID11
abscess_ID12
\end{lstlisting}
\textbf{Формат:} один класс на строку, позиция в файле = YOLO index (0-based).
\textbf{Особенности:}
\begin{enumerate}
\item Каждый эксперимент имеет свой собственный файл классов.
\item Позволяет использовать разные наборы классов для разных препаратов.
\item Скрипт \texttt{ttv\_freq2.py} автоматически загружает этот файл.
\item Обеспечивает согласованность классов между различными задачами (детекция, сегментация).
\end{enumerate}
\subsubsection{\texttt{data.yaml}}
Файл \texttt{data.yaml} является основным конфигурационным файлом для YOLO.
\textbf{Содержимое файла:}
\begin{lstlisting}
path: D:/ai-med-edu/data/1_2236040_zno_cyst_AF
train: images
val: images
test: images
nc: 12
names: ['right_kidney_ID1', 'right_kidney_upper_segment_ID2']
\end{lstlisting}
\textbf{Параметры:}
\begin{itemize}
\item \texttt{path}: абсолютный путь к папке эксперимента.
\item \texttt{train}/\texttt{val}/\texttt{test}: пути к файлам со списками изображений.
\item \texttt{nc}: количество классов (должно совпадать с количеством строк в \texttt{predefined\_classes.txt}).
\item \texttt{names}: список названий классов в порядке их индексов (0-based).
\end{itemize}
% ============================================================================
% 4. РЕЗУЛЬТАТЫ
% ============================================================================
\section{Результаты}
\subsection{Структура директорий после обработки}
После выполнения всех шагов обработки структура директорий имеет следующий вид:
\begin{verbatim}
1_2236040_zno_cyst_AF/
- images/ # PNG-срезы (300 файлов)
- slice_0573.png
- slice_0574.png
- ...
- images_labels/ # Bbox-разметка
- slice_0573.txt
- slice_0574.txt
- ...
- polygons/ # Полигоны
- slice_0573.txt
- slice_0574.txt
- ...
- images_result/ # Визуализация
- slice_0573_preview.png
- slice_0574_preview.png
- ...
- masks_sam/ # Бинарные маски
- slice_0573_class_8_obj_0000.png
- ...
- dcm/ # DICOM-срезы (1042 файла)
- slice_0000.dcm
- slice_0001.dcm
- ...
- predefined_classes.txt # Схема классов (12 классов)
- data.yaml # Конфиг YOLO
\end{verbatim}
\subsection{Примеры разметки}
\subsubsection{Формат BBOX (детекция)}
Файл: \texttt{images\_labels/slice\_0573.txt}
\begin{verbatim}
8 0.354492 0.541992 0.017578 0.021484
\end{verbatim}
\textbf{Формат:} \texttt{<class\_id> <x\_center> <y\_center> <width> <height>}
\textbf{Где:}
\begin{itemize}
\item \texttt{class\_id}: индекс класса (0-based).
\item \texttt{x\_center}, \texttt{y\_center}: координаты центра объекта (нормализованные от 0 до 1).
\item \texttt{width}, \texttt{height}: размеры объекта (нормализованные от 0 до 1).
\end{itemize}
\subsubsection{Формат POLYGON (сегментация)}
Файл: \texttt{images\_labels/polygons/slice\_0573.txt}
\begin{verbatim}
8 0.349609 0.531250 0.345703 0.535156 0.345703 0.542969 ...
\end{verbatim}
\textbf{Формат:} \texttt{<class\_id> <x1> <y1> <x2> <y2> ... <xn> <yn>}
\textbf{Где:}
\begin{itemize}
\item \texttt{class\_id}: индекс класса (0-based).
\item \texttt{x1}, \texttt{y1}, \texttt{x2}, \texttt{y2}, ...: координаты точек полигона (нормализованные от 0 до 1).
\end{itemize}
\subsection{Статистика по классам}
\begin{table}[H]
\centering
\caption{Распределение классов в датасете}
\begin{tabular}{l|r|r|r|r}
\toprule
\textbf{Класс} & \textbf{ID (1-based)} & \textbf{YOLO idx} & \textbf{Количество} & \textbf{Процент} \\
\midrule
right\_kidney & 1 & 0 & 300 & 100.0\% \\
right\_kidney\_upper\_segment & 2 & 1 & 35 & 11.7\% \\
right\_kidney\_middle\_segment & 3 & 2 & 55 & 18.3\% \\
right\_kidney\_lower\_segment & 4 & 3 & 47 & 15.7\% \\
left\_kidney & 5 & 4 & 26 & 8.7\% \\
left\_kidney\_upper\_segment & 6 & 5 & 28 & 9.3\% \\
left\_kidney\_middle\_segment & 7 & 6 & 36 & 12.0\% \\
left\_kidney\_lower\_segment & 8 & 7 & 44 & 14.7\% \\
malignant\_tumor & 9 & 8 & 52 & 17.3\% \\
benign\_tumor & 10 & 9 & 38 & 12.7\% \\
cyst & 11 & 10 & 41 & 13.7\% \\
abscess & 12 & 11 & 12 & 4.0\% \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Примечание:} Проценты указаны относительно общего количества изображений (300).
% ============================================================================
% 5. ВЫВОДЫ
% ============================================================================
\section{Выводы}
В ходе выполнения лабораторной работы были получены следующие результаты:
\begin{enumerate}
\item \textbf{Проведён аудит структуры датасета}: выявлены форматы данных (NRRD, DICOM, PNG), количество файлов и их соответствие.
\item \textbf{Реализована конвертация полигонов в бинарные маски}: разработан пайплайн для преобразования полигональной разметки в растровые маски, пригодные для расчёта радиометрических характеристик и обучения сегментационных моделей.
\item \textbf{Выполнена распаковка NRRD в 2D-срезы}: разработан автоматизированный скрипт, который извлекает 2D-срезы из 3D-массива, синхронизирует их с соответствующими масками и организует файловую систему по единому шаблону.
\item \textbf{Созданы конфигурационные файлы}: \texttt{predefined\_classes.txt} (12 классов) и \texttt{data.yaml} -- единая точка управления классами и путями к данным.
\item \textbf{Подтверждена роль врача-эксперта}: верификация клинической <<легенды>> датасета является обязательным условием для запуска обучения модели, предотвращая формирование ложных паттернов.
\item \textbf{Выявлены системные риски}: потеря пространственной ориентации при распаковке NRRD, повреждённые теги в DICOM-RT, отсутствие клинической экспертизы.
\end{enumerate}
\textbf{Рекомендации по масштабированию:}
\begin{enumerate}
\item Использовать \texttt{predefined\_classes.txt} как единый источник истины для всех экспериментов.
\item Автоматизировать проверку целостности данных перед обработкой.
\item Внедрить обязательную визуальную верификацию масок врачом-экспертом.
\item Сохранять параметры пространственной ориентации при любой конвертации.
\end{enumerate}
% ============================================================================
% 6. КОНТРОЛЬНЫЕ ВОПРОСЫ
% ============================================================================
\section{Контрольные вопросы}
\begin{enumerate}
\item В чём заключаются принципиальные различия между форматами DICOM и NRRD? В каких случаях предпочтительнее использовать каждый из них при организации радиомических исследований?
\item Почему файлы DICOM-RT Structure Set требуют особой проверки целостности перед конвертацией в бинарные маски? Какие последствия могут возникнуть при наличии повреждённых тегов?
\item Какую роль играют метаданные \texttt{space directions} и \texttt{space origin} в формате NRRD? Почему их потеря критична при работе с 3D-моделями и обучении нейронных сетей?
\item Что такое <<клиническая легенда>> датасета и почему её верификация врачом-экспертом является обязательным этапом перед обучением модели ИИ?
\item Какие преимущества даёт использование конфигурационных файлов \texttt{data.yaml} и \texttt{predefined\_classes.txt} при масштабировании проекта и подключении новых когорт пациентов?
\item Проанализируйте структуру закреплённого за вами датасета. К какой группе (DICOM или NRRD) он относится? Какие особенности хранения данных и разметки вы выявили?
\item Опишите пошагово процесс конвертации полигональной разметки в бинарную маску для вашего формата данных. Какие библиотеки Python вы использовали и почему?
\item Какие радиометрические характеристики были рассчитаны клиент-серверным приложением для областей интереса вашего датасета? Какие из этих признаков, на ваш взгляд, наиболее информативны для решения клинической задачи?
\item Если бы вам потребовалось подключить к проекту новый датасет в неизвестном формате, какую последовательность действий вы бы предприняли? На каком этапе потребовалось бы привлечение врача-эксперта?
\item Предложите стратегию масштабирования текущего датасета для его последующего использования в реальном клиническом внедрении. Какие дополнительные данные и в каком объёме необходимо привлечь?
\end{enumerate}
% ============================================================================
% 7. СПИСОК ЛИТЕРАТУРЫ
% ============================================================================
\section{Список литературы}
\begin{enumerate}
\item Владзимирский, А. В. Компьютерное зрение в лучевой диагностике: первый этап Московского эксперимента / А. В. Владзимирский, Ю. А. Васильев, К. М. Арзамасов [и др.]. -- 2-е изд. -- Москва : ООО <<Издательские решения>>, 2023. -- 388 с. -- ISBN 978-5-0059-3043-9.
\item Гавриченков, Ю. Д. Системы искусственного интеллекта : учебно-методическое пособие / Ю. Д. Гавриченков, В. Ю. Переверзев. -- Самара : Медицинский университет <<Реавиз>>, 2024. -- 55 с.
\item Поляков, Е. В. Цифровая обработка медицинских изображений : учебное пособие / Е. В. Поляков, В. В. Дмитриева, А. А. Фроня. -- Москва : НИЯУ МИФИ, 2024. -- 100 с.
\item Савченко, Е. Ю. Основы и применение нейронных сетей: теория, практика, инновации : учебное пособие / Е. Ю. Савченко. -- Бишкек : Нео Принт, 2024. -- 116 с.
\item Молодяков, С. А. Применение функций OpenCV в компьютерном зрении (60 примеров на Python) / С. А. Молодяков. -- Санкт-Петербург : Изд-во Политехн. ун-та, 2022. -- 296 с.
\item Jocher, G. Ultralytics YOLOv8 Documentation. -- URL: https://docs.ultralytics.com/ (дата обращения: 21.08.2026).
\item van Griethuysen, J. J. M. Computational Radiomics System to Decode the Radiographic Phenotype / J. J. M. van Griethuysen, A. Fedorov, C. Parmar [et al.] // Cancer Research. -- 2017. -- Vol. 77, No. 21. -- P. e104--e107.
\end{enumerate}
% ============================================================================
% ПРИЛОЖЕНИЕ
% ============================================================================
\appendix
\section{Приложение: Код скрипта \texttt{nrrd\_to\_yolo.py}}
\begin{lstlisting}[basicstyle=\ttfamily\footnotesize]
import os
import re
import numpy as np
import nrrd
import pydicom
from pydicom.uid import ExplicitVRLittleEndian
from pydicom.uid import generate_uid
from pathlib import Path
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.colors import ListedColormap
from scipy import ndimage
import cv2
BASE_DIR = "data/1_2236040_zno_cyst_AF"
IMG_NRRD = "501 arterial ax 1mm iDose (5).nrrd"
SEG_NRRD = "Segmentation.seg.nrrd"
CT_CMAP = 'gray'
POLY_EPSILON = 0.01
def extract_segment_metadata(header):
names = {}
colors = {}
for key, value in header.items():
if key.startswith('Segment') and key.endswith('_LabelValue'):
match = re.search(r'Segment(\d+)_LabelValue', key)
if match:
seg_idx = int(match.group(1))
label_val = int(float(value))
name_key = f'Segment{seg_idx}_Name'
names[label_val] = header.get(name_key, '')
color_key = f'Segment{seg_idx}_Color'
color_str = header.get(color_key, '')
if color_str:
parts = color_str.strip().split()
if len(parts) >= 3:
r, g, b = [int(float(c) * 255) for c in parts[:3]]
colors[label_val] = f"#{r:02x}{g:02x}{b:02x}"
return names, colors
def create_dicom_from_slice(slice_data, z_index):
ds = pydicom.Dataset()
ds.file_meta = pydicom.Dataset()
ds.file_meta.TransferSyntaxUID = ExplicitVRLittleEndian
ds.file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2'
ds.file_meta.MediaStorageSOPInstanceUID = generate_uid()
ds.file_meta.ImplementationClassUID = generate_uid()
ds.SOPClassUID = ds.file_meta.MediaStorageSOPClassUID
ds.SOPInstanceUID = ds.file_meta.MediaStorageSOPInstanceUID
ds.PatientName = "ANON^PATIENT"
ds.PatientID = "000000"
ds.StudyDate = "20260602"
ds.Modality = "CT"
ds.InstanceNumber = str(z_index)
ds.Rows, ds.Columns = slice_data.shape
ds.PixelSpacing = [1.0, 1.0]
ds.SliceThickness = 1.0
ds.BitsAllocated = 16
ds.BitsStored = 16
ds.HighBit = 15
ds.PixelRepresentation = 1
ds.ImagePositionPatient = [0.0, 0.0, float(z_index)]
ds.ImageOrientationPatient = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]
ds.SamplesPerPixel = 1
ds.PhotometricInterpretation = "MONOCHROME2"
ds.PixelData = slice_data.tobytes()
return ds
def extract_polygon(mask_uint8):
contours, _ = cv2.findContours(mask_uint8, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
if not contours:
return None
largest_contour = max(contours, key=cv2.contourArea)
epsilon = POLY_EPSILON * cv2.arcLength(largest_contour, True)
approx = cv2.approxPolyDP(largest_contour, epsilon, True)
if len(approx) < 3:
return None
points = [(int(pt[0][0]), int(pt[0][1])) for pt in approx]
return points
def save_png_safe(path, image):
success, buffer = cv2.imencode('.png', image)
if not success:
raise RuntimeError("cv2.imencode failed")
path.write_bytes(buffer.tobytes())
def main():
base_path = Path(BASE_DIR)
if not base_path.exists():
print("Error: directory not found")
return
print("Working directory:", base_path.absolute())
for d in ["images", "dcm", "images_labels", "images_labels/polygons",
"images_result", "images_result/masks_sam"]:
(base_path / d).mkdir(exist_ok=True)
print("Loading NRRD...")
img_data, _ = nrrd.read(base_path / IMG_NRRD)
seg_data, seg_header = nrrd.read(base_path / SEG_NRRD)
if img_data.shape != seg_data.shape:
raise ValueError("Shape mismatch")
SEGMENT_NAMES, SEGMENT_COLORS = extract_segment_metadata(seg_header)
if not SEGMENT_NAMES:
print("No metadata, using placeholders")
unique = np.unique(seg_data[seg_data > 0])
SEGMENT_NAMES = {int(l): f"Class_{int(l)}" for l in unique}
SEGMENT_COLORS = {int(l): f"#{np.random.randint(0, 0xFFFFFF):06x}"
for l in unique}
sorted_labels = sorted(SEGMENT_NAMES.keys())
class_names = [SEGMENT_NAMES[l] for l in sorted_labels]
max_label = max(sorted_labels)
color_map_list = ['#000000'] * (max_label + 1)
for lbl, col in SEGMENT_COLORS.items():
if lbl <= max_label:
color_map_list[lbl] = col
custom_cmap = ListedColormap(color_map_list)
print("Found classes:", class_names)
(base_path / "predefined_classes.txt").write_text("\n".join(class_names))
W, H, D = img_data.shape
count = 0
mask_count = 0
for z in range(D):
img_slice = img_data[:, :, z].T
seg_slice = seg_data[:, :, z].T
create_dicom_from_slice(img_slice.astype(np.int16), z).save_as(
base_path / "dcm" / f"slice_{z:04d}.dcm")
p_min, p_max = np.percentile(img_slice, [0.5, 99.5])
img_uint8 = (np.clip(img_slice, p_min, p_max) - p_min) / (p_max - p_min + 1e-8) * 255
img_uint8 = img_uint8.astype(np.uint8)
slice_labels = np.unique(seg_slice[seg_slice > 0])
slice_labels = [int(l) for l in slice_labels]
if len(slice_labels) > 0:
bbox_lines = []
polygon_lines = []
fig, ax = plt.subplots(figsize=(img_slice.shape[1]/100,
img_slice.shape[0]/100))
ax.imshow(img_uint8, cmap=CT_CMAP)
ax.imshow(seg_slice, cmap=custom_cmap, alpha=0.4)
H_curr, W_curr = img_slice.shape
for label in slice_labels:
mask_class = (seg_slice == label)
labeled_objs, num_objs = ndimage.label(mask_class,
structure=np.ones((3,3)))
for obj_idx in range(1, num_objs + 1):
obj_mask = (labeled_objs == obj_idx)
ys, xs = np.where(obj_mask)
if len(ys) == 0:
continue
x_min, y_min, x_max, y_max = xs.min(), ys.min(), xs.max(), ys.max()
xc = (x_min + x_max) / 2.0
yc = (y_min + y_max) / 2.0
bw = x_max - x_min
bh = y_max - y_min
yolo_class_id = sorted_labels.index(label)
bbox_lines.append(
f"{yolo_class_id} {xc/W_curr:.6f} {yc/H_curr:.6f} "
f"{bw/W_curr:.6f} {bh/H_curr:.6f}")
obj_mask_uint8 = (obj_mask * 255).astype(np.uint8)
polygon = extract_polygon(obj_mask_uint8)
if polygon:
norm_points = []
for (px, py) in polygon:
norm_points.append(f"{px/W_curr:.6f}")
norm_points.append(f"{py/H_curr:.6f}")
polygon_lines.append(f"{yolo_class_id} " + " ".join(norm_points))
single_mask = np.zeros((H_curr, W_curr), dtype=np.uint8)
single_mask[obj_mask] = 255
mask_filename = (f"slice_{z:04d}_class_{yolo_class_id}_"
f"obj_{(obj_idx-1):04d}.png")
mask_path = base_path / "images_result" / "masks_sam" / mask_filename
try:
save_png_safe(mask_path, single_mask)
mask_count += 1
except Exception as e:
print("Error saving mask:", e)
color_hex = color_map_list[label]
rect = Rectangle((x_min, y_min), bw, bh, linewidth=2,
edgecolor=color_hex, facecolor='none',
linestyle='-', alpha=0.7)
ax.add_patch(rect)
if polygon:
poly_pts = np.array(polygon)
closed_pts = np.vstack([poly_pts, poly_pts[0:1]])
ax.plot(closed_pts[:, 0], closed_pts[:, 1],
color=color_hex, linewidth=2.5,
linestyle='--', alpha=0.9)
(base_path / "images_labels" / f"slice_{z:04d}.txt").write_text(
"\n".join(bbox_lines) + "\n")
if polygon_lines:
(base_path / "images_labels" / "polygons" / f"slice_{z:04d}.txt").write_text(
"\n".join(polygon_lines) + "\n")
plt.imsave(base_path / "images" / f"slice_{z:04d}.png",
img_uint8, cmap=CT_CMAP, format='png')
ax.set_title(f"Slice {z}")
ax.axis('off')
plt.tight_layout(pad=0)
plt.savefig(base_path / "images_result" / f"slice_{z:04d}_preview.png",
dpi=150, bbox_inches='tight')
plt.close(fig)
count += 1
(base_path / "data.yaml").write_text(
f"path: {base_path.absolute()}\n"
f"train: images\n"
f"val: images\n"
f"test: images\n"
f"nc: {len(class_names)}\n"
f"names: {class_names}\n")
print("Done! Processed slices:", count)
print("Created binary masks:", mask_count)
if __name__ == "__main__":
try:
main()
except Exception as e:
print("Error:", e)
import traceback
traceback.print_exc()
\end{lstlisting}
\end{document}
BIN
View File
Binary file not shown.
+660
View File
@@ -0,0 +1,660 @@
% ============================================================================
% ЛАБОРАТОРНАЯ РАБОТА №2
% АНАЛИЗ РАДИОМЕТРИЧЕСКИХ ПРИЗНАКОВ
% ============================================================================
% КОМПИЛЯЦИЯ: pdflatex (ДВА ПРОХОДА)
% ============================================================================
\documentclass[12pt,a4paper]{article}
% ============================================================================
% ПРЕАМБУЛА
% ============================================================================
\usepackage[utf8]{inputenc}
\usepackage[T2A]{fontenc}
\usepackage[russian]{babel}
\usepackage{amsmath,amssymb,amsfonts}
\usepackage{graphicx}
\usepackage{hyperref}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{geometry}
\usepackage{booktabs}
\usepackage{longtable}
\usepackage{caption}
\usepackage{subcaption}
\usepackage{titlesec}
\usepackage{float}
\usepackage{array}
\usepackage{multirow}
\usepackage{enumitem}
\geometry{left=2.5cm,right=2cm,top=2cm,bottom=2cm}
\hypersetup{
colorlinks=true,
linkcolor=blue,
urlcolor=blue,
pdfauthor={Студент},
pdftitle={Лабораторная работа N2}
}
\lstset{
basicstyle=\ttfamily\footnotesize,
keywordstyle=\color{blue},
commentstyle=\color{green!60!black},
stringstyle=\color{red},
breaklines=true,
frame=single,
numbers=left,
numberstyle=\tiny,
tabsize=4,
language=Python,
extendedchars=true,
inputencoding=utf8,
}
\titleformat{\section}{\Large\bfseries\centering}{}{0pt}{}
\titleformat{\subsection}{\large\bfseries}{}{0pt}{}
\titleformat{\subsubsection}{\normalsize\bfseries}{}{0pt}{}
\renewcommand{\refname}{Список использованной литературы}
\begin{document}
% ============================================================================
% ТИТУЛЬНЫЙ ЛИСТ
% ============================================================================
\begin{titlepage}
\begin{center}
\vspace*{0.5cm}
{\Large \textbf{ФГБУ <<НМИЦ онкологии им. Н.Н. Блохина>> Минздрава России}}
\vspace{0.3cm}
{\large Кафедра медицинской информатики и искусственного интеллекта}
\vspace{2.5cm}
{\Huge \textbf{ЛАБОРАТОРНАЯ РАБОТА №2}}
\vspace{0.5cm}
{\huge \textbf{Анализ радиометрических признаков}}
\vspace{0.5cm}
{\Large и построение модели обнаружения областей интереса}
\vspace{2.5cm}
\begin{tabular}{p{0.4\textwidth}p{0.4\textwidth}}
\textbf{Выполнил:} & \textbf{Руководитель:} \\
\underline{\hspace{4cm}} & \underline{\hspace{4cm}} \\
\textit{ФИО студента} & \textit{ФИО преподавателя} \\
Группа: \underline{\hspace{2cm}} & Учёная степень: \underline{\hspace{2cm}} \\
Вариант: \underline{\hspace{2cm}} & \\
\end{tabular}
\vspace{2.5cm}
{\large Москва, 2026}
\end{center}
\end{titlepage}
% ============================================================================
% СОДЕРЖАНИЕ
% ============================================================================
\tableofcontents
\newpage
% ============================================================================
% 1. ВВЕДЕНИЕ
% ============================================================================
\section{Введение}
\subsection{Цель работы}
Сформировать у слушателей практические навыки количественного анализа медицинских изображений методами радиомики и построения прогностических моделей машинного обучения для дифференциальной диагностики патологических образований.
\textbf{Задачи работы:}
\begin{enumerate}
\item Провести разведочный анализ данных (EDA) радиометрических признаков.
\item Выполнить предобработку данных (импутация пропусков, масштабирование).
\item Обучить модель бинарной классификации для дифференцировки патологических состояний.
\item Оценить качество модели и интерпретировать результаты.
\item Проанализировать важность радиометрических признаков для клинической задачи.
\end{enumerate}
\subsection{Клинический контекст}
В рамках лабораторной работы используются данные радиомического анализа пациентов с новообразованиями почек из датасета \texttt{1\_2236040\_zno\_cyst\_AF}. Задача состоит в построении модели, способной дифференцировать патологические образования (злокачественные опухоли, доброкачественные опухоли, кисты, абсцессы) на основе количественных признаков, извлечённых из медицинских изображений.
\textbf{Объекты дифференцировки:}
\begin{itemize}
\item Злокачественные опухоли (агрессивные новообразования)
\item Доброкачественные опухоли и кисты (неагрессивные образования)
\item Абсцессы (воспалительные процессы)
\end{itemize}
\textbf{Клиническая значимость:} Неинвазивная дифференцировка типов патологических образований почек на основе радиомических признаков позволяет:
\begin{itemize}
\item Снизить количество инвазивных диагностических процедур (биопсий).
\item Ускорить процесс принятия клинических решений.
\item Повысить точность дифференциальной диагностики.
\item Персонализировать тактику лечения.
\end{itemize}
\subsection{Концепция радиомики}
Радиомика — это высокопроизводительный метод извлечения количественных признаков из медицинских изображений (КТ, МРТ, ПЭТ). Её ключевая идея заключается в том, что визуальная информация содержит скрытые паттерны, неразличимые для человеческого глаза, но поддающиеся математическому описанию. Эти паттерны могут служить неинвазивными биомаркерами, отражающими гистологию, генетику и агрессивность заболевания.
\textbf{Пайплайн радиомического анализа:}
\begin{enumerate}
\item Получение медицинских изображений (КТ, МРТ).
\item Сегментация областей интереса (ROI).
\item Извлечение количественных признаков.
\item Предобработка и отбор признаков.
\item Построение прогностических моделей.
\item Клиническая валидация и интерпретация.
\end{enumerate}
% ============================================================================
% 2. ТЕОРЕТИЧЕСКАЯ ЧАСТЬ
% ============================================================================
\section{Теоретическая часть}
\subsection{Классификация радиометрических признаков}
Современные библиотеки (например, PyRadiomics) извлекают более 100 признаков. Они делятся на несколько основных групп.
\subsubsection{Геометрические признаки (Shape)}
Описывают форму и размер области интереса (ROI).
\textbf{Элонгация (вытянутость):}
\begin{equation}
E = \frac{L_{minor}}{L_{major}}
\end{equation}
где:
\begin{itemize}
\item $E$ -- элонгация (от 0 до 1)
\item $L_{minor}$ и $L_{major}$ -- малая и большая оси аппроксимирующего эллипса
\end{itemize}
Значение, близкое к 1, указывает на округлую (часто доброкачественную) форму.
\textbf{Сферичность:}
\begin{equation}
S = \frac{36\pi V^2}{A^3}
\end{equation}
где:
\begin{itemize}
\item $V$ -- объём ROI
\item $A$ -- площадь поверхности
\end{itemize}
Низкая сферичность указывает на инфильтративный, нерегулярный рост.
\subsubsection{Статистики первого порядка (First-order)}
Описывают распределение интенсивности пикселей без учёта их расположения.
\textbf{Среднее значение:}
\begin{equation}
\mu = \frac{1}{N} \sum_{i=1}^{N} X_i
\end{equation}
где:
\begin{itemize}
\item $\mu$ -- средняя интенсивность
\item $N$ -- число пикселей в маске
\item $X_i$ -- интенсивность $i$-го пикселя
\end{itemize}
\textbf{Дисперсия:}
\begin{equation}
\sigma^2 = \frac{1}{N-1} \sum_{i=1}^{N} (X_i - \mu)^2
\end{equation}
где $\sigma^2$ -- мера разброса значений (неоднородности ткани).
\textbf{Энтропия (мера хаотичности):}
\begin{equation}
H = -\sum_{k} p_k \cdot \log_2(p_k)
\end{equation}
где:
\begin{itemize}
\item $H$ -- энтропия Шеннона
\item $p_k$ -- вероятность появления интенсивности $k$
\end{itemize}
Высокая энтропия часто коррелирует с некрозом или фиброзом внутри опухоли.
\subsubsection{Текстурные признаки}
\textbf{Матрица пространственной смежности (GLCM)} оценивает, как часто пиксели с определёнными уровнями яркости встречаются рядом.
\textbf{Контраст:}
\begin{equation}
Contrast = \sum_{i} \sum_{j} |i - j|^2 \cdot P(i, j)
\end{equation}
где $P(i, j)$ -- вероятность совместного появления пикселей с интенсивностями $i$ и $j$. Высокий контраст указывает на резкие перепады яркости.
\textbf{Энергия (однородность):}
\begin{equation}
Energy = \sum_{i} \sum_{j} P(i, j)^2
\end{equation}
Высокая энергия указывает на однородность текстуры.
\textbf{Корреляция:}
\begin{equation}
Correlation = \frac{\sum_{i} \sum_{j} (i - \mu_i)(j - \mu_j) P(i, j)}{\sigma_i \sigma_j}
\end{equation}
Характеризует линейную зависимость между соседними пикселями.
\subsection{Метрики оценки моделей в клиническом контексте}
При бинарной классификации ($0$ -- норма/доброкачественное, $1$ -- патология/злокачественное) критически важен выбор метрики.
\textbf{Полнота (Recall):}
\begin{equation}
Recall = \frac{TP}{TP + FN}
\end{equation}
Приоритетна для скрининга: минимизирует пропуск патологии (ложноотрицательные результаты).
\textbf{Точность (Precision):}
\begin{equation}
Precision = \frac{TP}{TP + FP}
\end{equation}
Приоритетна для подтверждающей диагностики: минимизирует ложные тревоги, ведущие к ненужным биопсиям.
\textbf{F1-мера (баланс):}
\begin{equation}
F1 = 2 \cdot \frac{Precision \cdot Recall}{Precision + Recall}
\end{equation}
\textbf{ROC-AUC:} Площадь под ROC-кривой. Значение 0.5 соответствует случайному угадыванию, 1.0 -- идеальному классификатору.
\subsection{Предобработка данных}
\textbf{Стандартизация (Z-score):}
\begin{equation}
z = \frac{x - \mu}{\sigma}
\end{equation}
где:
\begin{itemize}
\item $z$ -- нормализованное значение
\item $x$ -- исходное значение
\item $\mu$ и $\sigma$ -- среднее и стандартное отклонение признака по обучающей выборке
\end{itemize}
Обязательна для линейных моделей и нейросетей.
\textbf{Импутация пропусков:}
\begin{equation}
x_{imputed} = \text{median}(X)
\end{equation}
Медианная импутация устойчива к выбросам.
% ============================================================================
% 3. ПРАКТИЧЕСКАЯ ЧАСТЬ
% ============================================================================
\section{Практическая часть}
\subsection{Загрузка и разведочный анализ данных (EDA)}
\subsubsection{Загрузка данных}
Радиометрические характеристики сохраняются в CSV-формате после обработки на платформе <<БиоМедАналитика>>.
\begin{lstlisting}
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
df = pd.read_csv('result.csv')
print(df.shape)
print(df.columns.tolist()[:10])
print(df.info())
print(df.describe())
\end{lstlisting}
\subsubsection{Анализ распределения классов}
\begin{lstlisting}
print("Distribution of classes:")
print(df['label'].value_counts())
print(df['label'].value_counts(normalize=True))
sns.countplot(data=df, x='label')
plt.title('Distribution of classes')
plt.show()
\end{lstlisting}
\subsubsection{Визуализация распределений признаков}
\begin{lstlisting}
features = ['original_firstorder_Mean', 'original_firstorder_Entropy',
'original_glcm_Contrast', 'original_glcm_Correlation']
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
for i, feat in enumerate(features):
ax = axes[i//2, i%2]
sns.histplot(data=df, x=feat, hue='label', kde=True, ax=ax)
ax.set_title(feat)
plt.tight_layout()
plt.show()
\end{lstlisting}
\subsection{Предобработка данных}
\subsubsection{Выделение признаков и целевой переменной}
\begin{lstlisting}
feature_cols = [col for col in df.columns if col not in ['label', 'path_to_cell', 'source_file']]
X = df[feature_cols]
y = df['label']
print("Features shape:", X.shape)
print("Target shape:", y.shape)
print("Classes:", y.unique())
\end{lstlisting}
\subsubsection{Разделение на обучающую и тестовую выборки}
\begin{lstlisting}
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
print("Train size:", X_train.shape[0])
print("Test size:", X_test.shape[0])
print("Train class distribution:\n", y_train.value_counts(normalize=True))
print("Test class distribution:\n", y_test.value_counts(normalize=True))
\end{lstlisting}
\subsubsection{Импутация пропусков}
\begin{lstlisting}
from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy='median')
X_train_imputed = imputer.fit_transform(X_train)
X_test_imputed = imputer.transform(X_test)
print("Missing values after imputation:",
np.isnan(X_train_imputed).sum(), np.isnan(X_test_imputed).sum())
\end{lstlisting}
\subsubsection{Стандартизация}
\begin{lstlisting}
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train_imputed)
X_test_scaled = scaler.transform(X_test_imputed)
print("Mean after scaling:", X_train_scaled.mean(axis=0)[:5])
print("Std after scaling:", X_train_scaled.std(axis=0)[:5])
\end{lstlisting}
\subsection{Обучение модели}
\subsubsection{Случайный лес (Random Forest)}
Случайный лес устойчив к выбросам, не требует строгой нормализации и позволяет оценить важность признаков.
\begin{lstlisting}
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.metrics import roc_auc_score, RocCurveDisplay
model = RandomForestClassifier(
n_estimators=100,
random_state=42,
class_weight='balanced'
)
model.fit(X_train_scaled, y_train)
\end{lstlisting}
\subsubsection{Оценка качества}
\begin{lstlisting}
y_pred = model.predict(X_test_scaled)
y_prob = model.predict_proba(X_test_scaled)[:, 1]
print("Classification Report:")
print(classification_report(y_test, y_pred, target_names=['Benign', 'Malignant']))
cm = confusion_matrix(y_test, y_pred)
print("Confusion Matrix:")
print(cm)
auc = roc_auc_score(y_test, y_prob)
print("ROC-AUC:", auc)
\end{lstlisting}
\subsubsection{Визуализация результатов}
\begin{lstlisting}
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=axes[0])
axes[0].set_title('Confusion Matrix')
axes[0].set_xlabel('Predicted')
axes[0].set_ylabel('True')
RocCurveDisplay.from_estimator(model, X_test_scaled, y_test, ax=axes[1])
axes[1].set_title('ROC Curve')
plt.tight_layout()
plt.show()
\end{lstlisting}
\subsection{Анализ важности признаков}
\begin{lstlisting}
importance = pd.DataFrame({
'feature': feature_cols,
'importance': model.feature_importances_
}).sort_values('importance', ascending=False)
print("Top 10 features:")
print(importance.head(10))
plt.figure(figsize=(10, 8))
sns.barplot(data=importance.head(15), x='importance', y='feature')
plt.title('Feature Importance')
plt.xlabel('Importance')
plt.tight_layout()
plt.show()
\end{lstlisting}
% ============================================================================
% 4. ВАРИАНТЫ ЗАДАНИЙ
% ============================================================================
\section{Варианты заданий}
Выберите один из вариантов в соответствии с указанием преподавателя. Каждый вариант акцентирует внимание на специфике определённой анатомической зоны.
\begin{table}[H]
\centering
\caption{Варианты заданий}
\begin{tabular}{p{1.5cm}|p{5cm}|p{6cm}}
\toprule
\textbf{Вариант} & \textbf{Анатомическая зона} & \textbf{Акцент} \\
\midrule
1 & Почки & Геометрические признаки и статистики первого порядка \\
2 & Молочная железа & Текстурные признаки (GLCM, GLRLM) \\
3 & Головной мозг & Спектральные признаки и NGTDM \\
4 & Брюшная полость & Комбинированное использование всех групп признаков \\
5 & Мультимодальный анализ & Сравнительный анализ и формулировка гипотез \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Задание повышенной сложности (со звёздочкой):}
Реализуйте отбор признаков методом рекурсивного исключения (RFE) или через \texttt{SelectFromModel}. Сравните метрики модели до и после уменьшения размерности.
\begin{lstlisting}
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
selector = RFE(LogisticRegression(), n_features_to_select=20)
selector.fit(X_train_scaled, y_train)
selected_features = selector.get_support()
print("Selected features:", np.sum(selected_features))
X_train_selected = X_train_scaled[:, selected_features]
X_test_selected = X_test_scaled[:, selected_features]
model_selected = RandomForestClassifier(random_state=42)
model_selected.fit(X_train_selected, y_train)
y_pred_selected = model_selected.predict(X_test_selected)
print("Accuracy with RFE:", accuracy_score(y_test, y_pred_selected))
\end{lstlisting}
% ============================================================================
% 5. РЕЗУЛЬТАТЫ
% ============================================================================
\section{Результаты}
\subsection{Пример результатов}
Ниже приведены типичные результаты, которые можно получить при выполнении работы.
\begin{table}[H]
\centering
\caption{Метрики качества модели (пример)}
\begin{tabular}{l|r|r|r}
\toprule
\textbf{Метрика} & \textbf{Значение} & \textbf{95\% CI} & \textbf{Интерпретация} \\
\midrule
Accuracy & 0.89 & 0.85--0.93 & Хорошее качество \\
Precision (Malignant) & 0.87 & 0.82--0.92 & Низкий уровень ЛП \\
Recall (Malignant) & 0.92 & 0.88--0.96 & Высокая чувствительность \\
F1 (Malignant) & 0.89 & 0.85--0.93 & Хороший баланс \\
ROC-AUC & 0.94 & 0.91--0.97 & Отличная дискриминация \\
\bottomrule
\end{tabular}
\end{table}
\subsection{Важность признаков (пример)}
\begin{table}[H]
\centering
\caption{Топ-10 важнейших признаков}
\begin{tabular}{l|r}
\toprule
\textbf{Признак} & \textbf{Важность} \\
\midrule
original\_firstorder\_Entropy & 0.142 \\
original\_glcm\_Contrast & 0.118 \\
original\_firstorder\_Variance & 0.095 \\
original\_shape\_Sphericity & 0.087 \\
original\_glcm\_Correlation & 0.076 \\
original\_firstorder\_Mean & 0.068 \\
original\_glrlm\_RunEntropy & 0.062 \\
original\_glszm\_ZoneEntropy & 0.055 \\
original\_gldm\_DependenceEntropy & 0.048 \\
original\_shape\_Elongation & 0.041 \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Интерпретация:}
\begin{enumerate}
\item \textbf{Энтропия} (original\_firstorder\_Entropy) -- отражает гетерогенность ткани. Высокая энтропия указывает на неоднородность, характерную для злокачественных образований.
\item \textbf{Контраст} (original\_glcm\_Contrast) -- характеризует резкие перепады интенсивности, связанные с неоваскуляризацией и некрозом.
\item \textbf{Дисперсия} (original\_firstorder\_Variance) -- мера разброса значений интенсивности, отражающая неоднородность ткани.
\item \textbf{Сферичность} (original\_shape\_Sphericity) -- низкая сферичность характерна для инфильтративного роста злокачественных опухолей.
\end{enumerate}
% ============================================================================
% 6. ВЫВОДЫ
% ============================================================================
\section{Выводы}
В ходе выполнения лабораторной работы были получены следующие результаты:
\begin{enumerate}
\item \textbf{Проведён разведочный анализ данных}: выявлены закономерности в распределении признаков, определены классы и их соотношение.
\item \textbf{Выполнена предобработка данных}: реализована импутация пропусков и стандартизация признаков.
\item \textbf{Обучена модель случайного леса}: модель демонстрирует высокое качество классификации (ROC-AUC $> 0.9$).
\item \textbf{Проведён анализ важности признаков}: выявлены ключевые радиометрические характеристики (энтропия, контраст, дисперсия, сферичность) для дифференцировки патологических состояний.
\item \textbf{Результаты интерпретированы с клинической точки зрения}: подтверждена диагностическая ценность текстурных признаков и статистик первого порядка.
\end{enumerate}
\textbf{Клинические рекомендации:}
\begin{enumerate}
\item Для скрининга рекомендуется использовать метрику Recall (минимизация пропуска патологии).
\item Для подтверждающей диагностики рекомендуется использовать метрику Precision (минимизация ложных тревог).
\item Текстурные признаки (GLCM\_Contrast, GLCM\_Entropy) являются наиболее информативными для дифференциальной диагностики.
\end{enumerate}
% ============================================================================
% 7. КОНТРОЛЬНЫЕ ВОПРОСЫ
% ============================================================================
\section{Контрольные вопросы}
\begin{enumerate}
\item В чём заключается принципиальное различие между статистиками первого порядка и текстурными признаками? Приведите пример медицинской задачи, в которой каждый из этих типов признаков будет обладать максимальной диагностической ценностью.
\item Почему метрика Accuracy может давать искажённое представление о качестве модели при работе с медицинскими данными? Какие альтернативные метрики предпочтительнее использовать в задачах скрининга и по какой причине?
\item Какую информацию несёт коэффициент корреляции Пирсона между двумя признаками? Почему наличие сильной корреляции (мультиколлинеарности) может создавать проблемы при обучении линейных моделей?
\item Дайте содержательное объяснение метрики ROC-AUC. В каких ситуациях её использование более оправдано по сравнению с F1-мерой?
\item Перечислите обязательные этапы предобработки радиометрических данных перед обучением модели. Обоснуйте необходимость каждого из них с точки зрения статистики и машинного обучения.
\item Проанализируйте матрицу ошибок вашей модели: какое количество ложноположительных и ложноотрицательных прогнозов было допущено? Какой из этих типов ошибок представляет большую опасность в контексте вашей задачи и почему?
\item Назовите три признака, оказавшие наибольшее влияние на прогноз вашей модели. Как вы интерпретируете их вклад с медицинской точки зрения?
\item Если бы у вас появилась возможность дополнить набор данных одним новым типом информации (помимо радиометрических признаков), что бы вы выбрали: клинические метаданные, результаты лабораторных исследований, геномные данные? Обоснуйте свой выбор.
\item Как изменится ваша стратегия моделирования при увеличении объёма обучающей выборки в десять раз? Какие корректировки потребуются в случае сокращения данных вдвое?
\item Какие шаги вы предпримете, если модель демонстрирует высокую полноту при низкой точности прогноза положительных случаев (или наоборот)? Как такой дисбаланс повлияет на возможности клинического применения модели?
\end{enumerate}
% ============================================================================
% 8. СПИСОК ЛИТЕРАТУРЫ
% ============================================================================
\section{Список литературы}
\begin{enumerate}
\item Владзимирский, А. В. Компьютерное зрение в лучевой диагностике: первый этап Московского эксперимента / А. В. Владзимирский, Ю. А. Васильев, К. М. Арзамасов [и др.]. -- 2-е изд. -- Москва : ООО <<Издательские решения>>, 2023. -- 388 с. -- ISBN 978-5-0059-3043-9.
\item Гавриченков, Ю. Д. Системы искусственного интеллекта : учебно-методическое пособие / Ю. Д. Гавриченков, В. Ю. Переверзев. -- Самара : Медицинский университет <<Реавиз>>, 2024. -- 55 с.
\item Поляков, Е. В. Цифровая обработка медицинских изображений : учебное пособие / Е. В. Поляков, В. В. Дмитриева, А. А. Фроня. -- Москва : НИЯУ МИФИ, 2024. -- 100 с.
\item Савченко, Е. Ю. Основы и применение нейронных сетей: теория, практика, инновации : учебное пособие / Е. Ю. Савченко. -- Бишкек : Нео Принт, 2024. -- 116 с.
\item van Griethuysen, J. J. M. Computational Radiomics System to Decode the Radiographic Phenotype / J. J. M. van Griethuysen, A. Fedorov, C. Parmar [et al.] // Cancer Research. -- 2017. -- Vol. 77, No. 21. -- P. e104--e107.
\item Lambin, P. Radiomics: the bridge between medical imaging and personalized medicine / P. Lambin, E. Rios-Velazquez, R. Leijenaar [et al.] // Nature Reviews Clinical Oncology. -- 2017. -- Vol. 14, No. 12. -- P. 749--762.
\item Breiman, L. Random Forests / L. Breiman // Machine Learning. -- 2001. -- Vol. 45, No. 1. -- P. 5--32.
\end{enumerate}
\end{document}
BIN
View File
Binary file not shown.
+1064
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+868
View File
@@ -0,0 +1,868 @@
% ============================================================================
% ЛАБОРАТОРНАЯ РАБОТА №4
% ПОЛНЫЙ ПАЙПЛАЙН ПОДГОТОВКИ ДАННЫХ И ОБУЧЕНИЯ МОДЕЛЕЙ ИИ
% ============================================================================
\documentclass[12pt,a4paper]{article}
% ============================================================================
% ПРЕАМБУЛА
% ============================================================================
\usepackage[utf8]{inputenc}
\usepackage[T2A]{fontenc}
\usepackage[russian]{babel}
\usepackage{amsmath,amssymb,amsfonts}
\usepackage{graphicx}
\usepackage{hyperref}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{geometry}
\usepackage{booktabs}
\usepackage{longtable}
\usepackage{caption}
\usepackage{subcaption}
\usepackage{titlesec}
\usepackage{float}
\usepackage{array}
\usepackage{multirow}
\usepackage{enumitem}
\usepackage{setspace}
\geometry{left=2.5cm,right=2cm,top=2cm,bottom=2cm}
\hypersetup{
colorlinks=true,
linkcolor=blue,
urlcolor=blue,
pdfauthor={Student},
pdftitle={Lab 4}
}
% --- НАСТРОЙКА ЛИСТИНГОВ С ПОДДЕРЖКОЙ КИРИЛЛИЦЫ ---
\lstset{
breaklines=true,
breakatwhitespace=false,
basicstyle=\scriptsize\ttfamily,
keywordstyle=\bfseries\color{blue!70},
commentstyle=\itshape\color{gray!70},
stringstyle=\color{red!60!black},
frame=single,
rulecolor=\color{gray!50},
numbers=left,
numberstyle=\tiny\color{gray},
numbersep=5pt,
tabsize=4,
showstringspaces=false,
captionpos=b,
extendedchars=true,
literate=
{А}{{\selectfont\char192}}1 {Б}{{\selectfont\char193}}1 {В}{{\selectfont\char194}}1 {Г}{{\selectfont\char195}}1 {Д}{{\selectfont\char196}}1 {Е}{{\selectfont\char197}}1 {Ё}{{\selectfont\char168}}1 {Ж}{{\selectfont\char198}}1 {З}{{\selectfont\char199}}1 {И}{{\selectfont\char200}}1 {Й}{{\selectfont\char201}}1 {К}{{\selectfont\char202}}1 {Л}{{\selectfont\char203}}1 {М}{{\selectfont\char204}}1 {Н}{{\selectfont\char205}}1 {О}{{\selectfont\char206}}1 {П}{{\selectfont\char207}}1 {Р}{{\selectfont\char208}}1 {С}{{\selectfont\char209}}1 {Т}{{\selectfont\char210}}1 {У}{{\selectfont\char211}}1 {Ф}{{\selectfont\char212}}1 {Х}{{\selectfont\char213}}1 {Ц}{{\selectfont\char214}}1 {Ч}{{\selectfont\char215}}1 {Ш}{{\selectfont\char216}}1 {Щ}{{\selectfont\char217}}1 {Ъ}{{\selectfont\char218}}1 {Ы}{{\selectfont\char219}}1 {Ь}{{\selectfont\char220}}1 {Э}{{\selectfont\char221}}1 {Ю}{{\selectfont\char222}}1 {Я}{{\selectfont\char223}}1
{а}{{\selectfont\char224}}1 {б}{{\selectfont\char225}}1 {в}{{\selectfont\char226}}1 {г}{{\selectfont\char227}}1 {д}{{\selectfont\char228}}1 {е}{{\selectfont\char229}}1 {ё}{{\selectfont\char184}}1 {ж}{{\selectfont\char230}}1 {з}{{\selectfont\char231}}1 {и}{{\selectfont\char232}}1 {й}{{\selectfont\char233}}1 {к}{{\selectfont\char234}}1 {л}{{\selectfont\char235}}1 {м}{{\selectfont\char236}}1 {н}{{\selectfont\char237}}1 {о}{{\selectfont\char238}}1 {п}{{\selectfont\char239}}1 {р}{{\selectfont\char240}}1 {с}{{\selectfont\char241}}1 {т}{{\selectfont\char242}}1 {у}{{\selectfont\char243}}1 {ф}{{\selectfont\char244}}1 {х}{{\selectfont\char245}}1 {ц}{{\selectfont\char246}}1 {ч}{{\selectfont\char247}}1 {ш}{{\selectfont\char248}}1 {щ}{{\selectfont\char249}}1 {ъ}{{\selectfont\char250}}1 {ы}{{\selectfont\char251}}1 {ь}{{\selectfont\char252}}1 {э}{{\selectfont\char253}}1 {ю}{{\selectfont\char254}}1 {я}{{\selectfont\char255}}1
}
\titleformat{\section}{\Large\bfseries\centering}{}{0pt}{}
\titleformat{\subsection}{\large\bfseries}{}{0pt}{}
\titleformat{\subsubsection}{\normalsize\bfseries}{}{0pt}{}
\renewcommand{\refname}{References}
\begin{document}
% ============================================================================
% ТИТУЛЬНЫЙ ЛИСТ
% ============================================================================
\begin{titlepage}
\begin{center}
\vspace*{0.5cm}
{\Large \textbf{ФГБУ <<НМИЦ онкологии им. Н.Н. Блохина>> Минздрава России}}
\vspace{0.3cm}
{\large Кафедра медицинской информатики и искусственного интеллекта}
\vspace{2.5cm}
{\Huge \textbf{ЛАБОРАТОРНАЯ РАБОТА №4}}
\vspace{0.5cm}
{\huge \textbf{Полный пайплайн подготовки данных}}
\vspace{0.5cm}
{\Large и обучения моделей искусственного интеллекта в онкологии}
\vspace{0.5cm}
{\Large \textbf{От сырых медицинских данных до готовой модели}}
\vspace{2.5cm}
\begin{tabular}{p{0.4\textwidth} p{0.4\textwidth}}
\textbf{Выполнил:} & \textbf{Руководитель:} \\
\underline{\hspace{4cm}} & \underline{\hspace{4cm}} \\
\textit{ФИО студента} & \textit{ФИО преподавателя} \\
Группа: \underline{\hspace{2cm}} & Учёная степень: \underline{\hspace{2cm}} \\
Вариант: \underline{\hspace{2cm}} & \\
\end{tabular}
\vspace{2.5cm}
{\large Москва, 2026}
\end{center}
\end{titlepage}
% ============================================================================
% СОДЕРЖАНИЕ
% ============================================================================
\tableofcontents
\newpage
% ============================================================================
% ВВЕДЕНИЕ
% ============================================================================
\section{Введение}
\subsection{Цель работы}
Систематизировать и интегрировать знания, полученные в ходе выполнения предыдущих лабораторных работ, в единый производственный пайплайн обработки медицинских данных для задач искусственного интеллекта в онкологии. Сформировать целостное понимание всех этапов: от получения сырых медицинских данных до развёртывания обученной модели в клинической практике.
\textbf{Задачи работы:}
\begin{enumerate}
\item Проанализировать полный жизненный цикл данных в проекте медицинского ИИ.
\item Рассмотреть все этапы пайплайна: сбор, аудит, предобработка, разметка, обучение, валидация, внедрение.
\item Изучить работу скриптов и инструментов, обеспечивающих автоматизацию процессов.
\item Разобрать практические примеры и типичные ошибки на каждом этапе.
\item Сформулировать рекомендации по организации производственного процесса.
\end{enumerate}
\subsection{Место работы в общем курсе}
Лабораторная работа №4 является \textbf{интегрирующей} и \textbf{обобщающей}. Она объединяет знания из трёх предыдущих работ:
\begin{itemize}
\item \textbf{ЛР №1}: Аудит и подготовка медицинских данных (форматы DICOM, NRRD, конвертация).
\item \textbf{ЛР №2}: Анализ радиометрических признаков (извлечение, предобработка, моделирование).
\item \textbf{ЛР №3}: Радиомический анализ опухолей мозга (BRATS, мультипараметрическая МРТ).
\end{itemize}
В четвёртой работе рассматриваются:
\begin{itemize}
\item Организация проекта и работа с репозиторием.
\item Автоматизация пайплайна с помощью скриптов.
\item Интеграция всех этапов в единый процесс.
\item Клиническая валидация и внедрение моделей.
\item Поддержка и мониторинг моделей в эксплуатации.
\end{itemize}
% ============================================================================
% ГЛАВА 1. ОРГАНИЗАЦИЯ ПРОЕКТА
% ============================================================================
\section{Организация проекта}
\subsection{Структура репозитория}
Правильная организация проекта — основа успешной работы. Репозиторий имеет следующую структуру:
\begin{verbatim}
ai-med-edu/
|
|-- README.md # Документация проекта
|-- ai-use-program.doc # Учебная программа
|-- references.txt # Список литературы
|
|-- data/ # Экспериментальные данные
| |-- 1_2236040_zno_cyst_AF/ # Эксперимент 1 (ZNO Cyst AF)
| | |-- predefined_classes.txt # Схема классов (12 классов)
| | |-- data.yaml # Конфиг YOLO
| | |-- *.nrrd # Исходные 3D данные
| | |-- dcm/ # DICOM-срезы (1042 файла)
| | |-- images/ # PNG-срезы (300 файлов)
| | |-- images_labels/ # Bbox-разметка
| | | |-- polygons/ # Полигоны для сегментации
| | |-- images_result/ # Визуализация результатов
| | |-- masks_sam/ # Бинарные маски
| |
| |-- prep/ # Подготовительные данные
| | |-- predefined_classes.txt
| | |-- csv/ # CSV-файлы с разметкой
| | |-- images/ # DCM и JPG файлы
| | |-- images_labels/ # TXT-лейблы
| | |-- images_results/ # Результаты
| |
| |-- example/ # Шаблон структуры
|
|-- scripts/ # Скрипты
| |-- ttv_freq2.py # Стратифицированный сплиттер
| |-- train_yolo.py # Обучение детекции
| |-- train_yolo_seg.py # Обучение сегментации
| |-- check_PyTorch_gpu.py # Проверка GPU
| |-- check_tf_gpu.py # Проверка TensorFlow GPU
| |-- config/ # Конфигурации
| | |-- data.yaml
| |-- Тема 2.1/ # 3D радиомика
| | |-- analysis.py
| | |-- config_3d.py
| | |-- feature_3d.py
| | |-- segmentation.py
| |-- Тема 2.2/ # Конвертация NRRD
| |-- nrrd_to_yolo.py
|
|-- env/ # Виртуальное окружение
| |-- Include/
| |-- Lib/
| | |-- site-packages/ # Установленные пакеты
| |-- Scripts/ # Исполняемые файлы
| |-- pyvenv.cfg
|
|-- lectures/ # Лекционные материалы
| |-- К26_1_1.pdf
| |-- К26_2_1.pdf
| |-- К26_2_2.pdf
|
|-- seminars/ # Семинарские занятия
| |-- FeatureExtractionClient.ipynb
| |-- Тема 1.1/
| | |-- Dataset.ipynb
| | |-- Noise.ipynb
| | |-- Resource1.ipynb
| | |-- resource2.ipynb
| | |-- segm2.ipynb
| |-- Тема 1.2/
| | |-- Практикум1.ipynb
| | |-- Т1.ipynb
| | |-- Т2.ipynb
| | |-- best_spleen_model.pth
| |-- Тема 2.1/
| | |-- radiomics_report.html
| | |-- segmentation_full_report.html
| | |-- requirements_3d.txt
| | |-- Лабораторная работа.docx
|
|-- labs/ # Лабораторные работы
|-- Тема 1.1/
|-- Тема 1.2/
|-- Тема 2.1/
| |-- Лабораторная работа.docx
|-- Тема 2.2/
\end{verbatim}
\subsection{Принципы организации}
\textbf{1. Разделение данных и кода:}
\begin{itemize}
\item \texttt{data/} — только данные (не в Git)
\item \texttt{scripts/} — только код (в Git)
\item \texttt{env/} — виртуальное окружение (игнорируется)
\end{itemize}
\textbf{2. Единая точка конфигурации:}
\begin{itemize}
\item \texttt{predefined\_classes.txt} — схема классов для каждого эксперимента
\item \texttt{data.yaml} — конфигурация для YOLO
\item Конфигурационные файлы — единственный источник истины
\end{itemize}
\textbf{3. Воспроизводимость:}
\begin{itemize}
\item Фиксация версий пакетов (\texttt{requirements.txt})
\item Использование \texttt{seed} для случайных процессов
\item Логирование всех параметров обучения
\end{itemize}
\subsection{Виртуальное окружение}
Виртуальное окружение изолирует зависимости проекта и обеспечивает воспроизводимость.
\textbf{Активация окружения:}
\begin{lstlisting}
# Windows
env\Scripts\activate
# Linux / macOS
source env/bin/activate
\end{lstlisting}
\textbf{Ключевые пакеты:}
\begin{table}[H]
\centering
\caption{Основные зависимости проекта}
\begin{tabular}{l|p{8cm}}
\toprule
\textbf{Пакет} & \textbf{Назначение} \\
\midrule
ultralytics & YOLOv8 (детекция, сегментация, классификация) \\
torch, torchvision & PyTorch (глубокое обучение) \\
pydicom & Работа с DICOM-файлами \\
nrrd & Работа с NRRD-файлами \\
pyradiomics & Извлечение радиометрических признаков \\
pandas, numpy & Работа с данными \\
scikit-learn & Машинное обучение \\
matplotlib, seaborn & Визуализация \\
opencv-python & Обработка изображений \\
scipy & Научные вычисления \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Установка зависимостей:}
\begin{lstlisting}
pip install -r requirements.txt
# Или вручную:
pip install ultralytics torch torchvision pyyaml pydicom nrrd
pip install scipy pandas opencv-python matplotlib seaborn
pip install scikit-learn pyradiomics
\end{lstlisting}
% ============================================================================
% ГЛАВА 2. ЖИЗНЕННЫЙ ЦИКЛ ДАННЫХ
% ============================================================================
\section{Жизненный цикл данных}
\subsection{Общая схема пайплайна}
Полный пайплайн обработки медицинских данных для задач ИИ включает следующие этапы:
\begin{enumerate}
\item \textbf{Сбор данных}: получение медицинских изображений (DICOM, NRRD).
\item \textbf{Аудит данных}: проверка целостности, структуры, соответствия.
\item \textbf{Предобработка}: конвертация форматов, нормализация, извлечение срезов.
\item \textbf{Разметка}: создание обучающих меток (экспертная разметка).
\item \textbf{Извлечение признаков}: радиомический анализ.
\item \textbf{Обучение модели}: выбор архитектуры, тренировка, валидация.
\item \textbf{Оценка}: анализ метрик, интерпретация результатов.
\item \textbf{Внедрение}: интеграция в клиническую практику.
\item \textbf{Мониторинг}: отслеживание качества в эксплуатации.
\end{enumerate}
\subsection{Сбор данных}
\subsubsection{Источники медицинских данных}
Медицинские данные могут поступать из различных источников:
\begin{itemize}
\item \textbf{Медицинские организации}: клинические данные (КТ, МРТ, ПЭТ/КТ).
\item \textbf{Открытые датасеты}: TCIA, BRATS, MedMNIST, ChestX-ray14.
\item \textbf{Научные исследования}: специализированные наборы данных.
\item \textbf{Клинические испытания}: данные из исследований.
\end{itemize}
\subsubsection{Форматы данных}
Основные форматы медицинских изображений:
\begin{table}[H]
\centering
\caption{Сравнение форматов медицинских данных}
\begin{tabular}{p{3cm}|p{4.5cm}|p{4.5cm}}
\toprule
\textbf{Характеристика} & \textbf{DICOM} & \textbf{NRRD} \\
\midrule
Применение & Клиническая практика & Научные исследования \\
Метаданные & Богатые (медицинский контекст) & Минимальные (геометрия) \\
3D поддержка & Через серии & Родная \\
Разметка & Отдельные файлы (RT Structure) & В том же файле или .seg \\
Читаемость & Требуется спец. ПО & Многие библиотеки \\
\bottomrule
\end{tabular}
\end{table}
\subsection{Аудит данных}
\subsubsection{Цели аудита}
Аудит данных — первый критический этап, определяющий успех всего проекта.
\textbf{Задачи аудита:}
\begin{enumerate}
\item Проверка целостности файлов.
\item Выявление форматов и структуры данных.
\item Оценка качества разметки.
\item Выявление аномалий и артефактов.
\item Формирование клинической <<легенды>> датасета.
\end{enumerate}
\subsubsection{Практический аудит}
\textbf{Скрипт для аудита структуры:}
\begin{lstlisting}
from pathlib import Path
import numpy as np
import nrrd
import pydicom
def audit_dataset(dataset_path):
"""Проверяет структуру датасета"""
path = Path(dataset_path)
# Проверка наличия необходимых папок
required = ['images', 'images_labels']
for folder in required:
if not (path / folder).exists():
print(f"Missing: {folder}")
# Проверка соответствия файлов
images = list((path / 'images').glob('*.png'))
labels = list((path / 'images_labels').glob('*.txt'))
print(f"Images: {len(images)}")
print(f"Labels: {len(labels)}")
# Проверка классов
classes_file = path / 'predefined_classes.txt'
if classes_file.exists():
classes = classes_file.read_text().strip().split('\n')
print(f"Classes: {len(classes)}")
print(f"Class names: {classes[:5]}...")
return {'images': len(images), 'labels': len(labels)}
\end{lstlisting}
\subsubsection{Клиническая <<легенда>> датасета}
Клиническая <<легенда>> — это полное описание датасета, включающее:
\begin{itemize}
\item Модальность исследования (КТ, МРТ, ПЭТ и др.)
\item Протокол сканирования
\item Подтверждённый диагноз
\item Критерии включения/исключения
\item Правила разметки областей интереса
\item Источник данных
\item Версия программного обеспечения сканера
\item Дата создания и обновления
\end{itemize}
\textbf{Пример <<легенды>>:}
\begin{verbatim}
Датасет: 1_2236040_zno_cyst_AF
Модальность: КТ (компьютерная томография)
Протокол: брюшная полость с контрастным усилением
Источник: Клинический архив
Количество пациентов: 1
Количество срезов: 300 (из 1042 исходных)
Классы: 12 (почки, сегменты, опухоли, кисты, абсцесс)
Разметка: Экспертная (врач-рентгенолог)
Дата создания: 2026
\end{verbatim}
\subsection{Предобработка данных}
\subsubsection{Конвертация форматов}
Переход от сырых данных к структурированному формату:
\textbf{DICOM → PNG + YOLO:}
\begin{lstlisting}
def convert_dicom_to_yolo(dicom_dir, output_dir):
"""Конвертирует DICOM-серию в формат YOLO"""
# 1. Загрузка DICOM-серии
# 2. Извлечение пиксельных данных
# 3. Нормализация изображения
# 4. Сохранение в PNG
# 5. Конвертация разметки в YOLO формат
pass
\end{lstlisting}
\textbf{NRRD → 2D-срезы:}
\begin{lstlisting}
def convert_nrrd_to_slices(nrrd_path, output_dir):
"""Распаковывает NRRD в 2D-срезы"""
import nrrd
# Загрузка данных
data, header = nrrd.read(nrrd_path)
# Извлечение срезов по оси Z
for z in range(data.shape[2]):
slice_data = data[:, :, z]
# Сохранение среза
# ...
\end{lstlisting}
\subsubsection{Нормализация изображений}
Нормализация приводит интенсивности пикселей к единому диапазону.
\textbf{Методы нормализации:}
\begin{enumerate}
\item \textbf{Min-Max нормализация:}
\begin{equation}
x_{norm} = \frac{x - x_{min}}{x_{max} - x_{min}}
\end{equation}
\item \textbf{Z-score нормализация:}
\begin{equation}
x_{norm} = \frac{x - \mu}{\sigma}
\end{equation}
\item \textbf{Перцентильная нормализация (гистограммное выравнивание):}
\begin{equation}
x_{norm} = \frac{x - P_{0.5}}{P_{99.5} - P_{0.5}}
\end{equation}
\end{enumerate}
\textbf{Реализация в Python:}
\begin{lstlisting}
def normalize_image(image, method='percentile'):
if method == 'minmax':
return (image - image.min()) / (image.max() - image.min())
elif method == 'zscore':
return (image - image.mean()) / image.std()
elif method == 'percentile':
p_min, p_max = np.percentile(image, [0.5, 99.5])
return (image - p_min) / (p_max - p_min)
return image
\end{lstlisting}
\subsection{Разметка данных}
\subsubsection{Типы разметки}
Для задач компьютерного зрения используются различные типы разметки:
\begin{table}[H]
\centering
\caption{Типы разметки медицинских изображений}
\begin{tabular}{p{3cm}|p{4.5cm}|p{4.5cm}}
\toprule
\textbf{Тип} & \textbf{Описание} & \textbf{Применение} \\
\midrule
Bounding Box (bbox) & Прямоугольник вокруг объекта & Детекция объектов \\
Полигоны & Контур объекта & Сегментация \\
Бинарные маски & Попиксельная разметка & Сегментация \\
Классификация & Метка всего изображения & Классификация \\
Landmarks & Анатомические точки & Регистрация \\
\bottomrule
\end{tabular}
\end{table}
\subsubsection{Формат YOLO для детекции}
Каждая строка в файле разметки:
\begin{verbatim}
<class_id> <x_center> <y_center> <width> <height>
\end{verbatim}
где все координаты нормализованы от 0 до 1 относительно размеров изображения.
\textbf{Пример:}
\begin{verbatim}
8 0.354492 0.541992 0.017578 0.021484
\end{verbatim}
\subsubsection{Формат YOLO для сегментации}
Каждая строка в файле полигонов:
\begin{verbatim}
<class_id> <x1> <y1> <x2> <y2> ... <xn> <yn>
\end{verbatim}
\textbf{Пример:}
\begin{verbatim}
8 0.349609 0.531250 0.345703 0.535156 0.345703 0.542969 ...
\end{verbatim}
\subsubsection{Роль врача-эксперта в разметке}
Врач-эксперт выполняет критически важные функции:
\begin{enumerate}
\item \textbf{Верификация разметки}: подтверждение корректности областей интереса.
\item \textbf{Клиническая интерпретация}: определение клинической значимости находок.
\item \textbf{Формирование <<легенды>>}: документирование данных.
\item \textbf{Решение спорных случаев}: экспертный анализ сложных примеров.
\end{enumerate}
\textbf{Риски отсутствия экспертизы:}
\begin{itemize}
\item Формирование ложных паттернов (shortcut learning)
\item Ошибки в неструктурированных данных
\item Потеря клинического контекста
\end{itemize}
% ============================================================================
% ГЛАВА 3. АВТОМАТИЗАЦИЯ ПАЙПЛАЙНА
% ============================================================================
\section{Автоматизация пайплайна}
\subsection{Скрипты и их роль}
\subsubsection{ttv\_freq2.py — Стратифицированный сплиттер}
Назначение: автоматическое разбиение датасета на обучающую, валидационную и тестовую выборки с учётом частоты классов.
\textbf{Ключевая особенность:} разбивает датасет, начиная с самых редких классов. Это гарантирует присутствие каждого класса во всех наборах.
\textbf{Параметры:}
\begin{table}[H]
\centering
\caption{Параметры скрипта ttv\_freq2.py}
\begin{tabular}{p{3.5cm}|p{3cm}|p{5cm}}
\toprule
\textbf{Параметр} & \textbf{Обязательность} & \textbf{Описание} \\
\midrule
--task & Да & detect / segment / both \\
--images-dir & Да & Папка с изображениями \\
--labels-dir & Да & Папка с разметкой \\
--output-dir & Да & Папка для результатов \\
--predefined-classes & Нет & Путь к файлу классов \\
--train-ratio & Нет (0.8) & Доля обучающей выборки \\
--val-ratio & Нет (0.1) & Доля валидационной \\
--seed & Нет (42) & Seed для воспроизводимости \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Пример запуска:}
\begin{lstlisting}
python scripts/ttv_freq2.py --task detect \
--images-dir data/1_2236040_zno_cyst_AF/images \
--labels-dir data/1_2236040_zno_cyst_AF/images_labels \
--output-dir data/1_2236040_zno_cyst_AF
\end{lstlisting}
\subsubsection{train\_yolo.py — Обучение детекции}
Назначение: обучение модели YOLOv8 на задачу детекции (bounding boxes).
\textbf{Параметры:}
\begin{table}[H]
\centering
\caption{Параметры скрипта train\_yolo.py}
\begin{tabular}{p{3.5cm}|p{3cm}|p{5cm}}
\toprule
\textbf{Параметр} & \textbf{По умолчанию} & \textbf{Описание} \\
\midrule
--data & Обязательный & Путь к data.yaml \\
--model & yolov8s.pt & Предобученная модель \\
--epochs & 100 & Количество эпох \\
--batch & 16 & Размер батча \\
--imgsz & 512 & Размер изображения \\
--device & 0 & Устройство (0, cpu) \\
--workers & 0 & DataLoader workers \\
--project & runs/detect & Директория проектов \\
--name & experiment & Имя эксперимента \\
\bottomrule
\end{tabular}
\end{table}
\textbf{Пример запуска:}
\begin{lstlisting}
python scripts/train_yolo.py \
--data data/1_2236040_zno_cyst_AF/data_detect.yaml \
--model yolov8s.pt \
--epochs 100 \
--batch 16 \
--name kidney_det_exp1
\end{lstlisting}
\subsubsection{train\_yolo\_seg.py — Обучение сегментации}
Назначение: обучение модели YOLOv8 на задачу сегментации (полигоны/маски).
\textbf{Пример запуска:}
\begin{lstlisting}
python scripts/train_yolo_seg.py \
--data data/1_2236040_zno_cyst_AF/data_segment.yaml \
--model yolov8s-seg.pt \
--epochs 100 \
--batch 16 \
--name kidney_seg_exp1
\end{lstlisting}
\subsubsection{nrrd\_to\_yolo.py — Конвертация NRRD}
Назначение: конвертация 3D NRRD-файлов в 2D-срезы формата YOLO.
\textbf{Основные функции:}
\begin{itemize}
\item Чтение .nrrd и .seg.nrrd файлов
\item Извлечение 2D-срезов по оси Z
\item Сохранение в PNG и DICOM
\item Создание разметки в формате YOLO (bbox и полигоны)
\item Генерация \texttt{predefined\_classes.txt} и \texttt{data.yaml}
\end{itemize}
\subsection{Интеграция этапов}
\subsubsection{Полный пайплайн для нового эксперимента}
\begin{lstlisting}
# 1. Создание структуры
python scripts/create_experiment.py new_experiment
# 2. Конвертация NRRD в YOLO
python scripts/nrrd_to_yolo.py
# 3. Стратифицированный сплит
python scripts/ttv_freq2.py --task both \
--images-dir data/new_experiment/images \
--labels-dir data/new_experiment/images_labels \
--polygons-dir data/new_experiment/images_labels/polygons \
--output-dir data/new_experiment
# 4. Обучение детекции
python scripts/train_yolo.py \
--data data/new_experiment/data_detect.yaml \
--epochs 100 \
--name new_experiment_detect
# 5. Обучение сегментации
python scripts/train_yolo_seg.py \
--data data/new_experiment/data_segment.yaml \
--epochs 100 \
--name new_experiment_seg
\end{lstlisting}
\subsubsection{Автоматизация с помощью Makefile}
\begin{lstlisting}
# Makefile для автоматизации пайплайна
.PHONY: all prepare split train-detect train-seg
EXPERIMENT = 1_2236040_zno_cyst_AF
all: prepare split train-detect train-seg
prepare:
python scripts/nrrd_to_yolo.py
split:
python scripts/ttv_freq2.py --task both \
--images-dir data/$(EXPERIMENT)/images \
--labels-dir data/$(EXPERIMENT)/images_labels \
--polygons-dir data/$(EXPERIMENT)/images_labels/polygons \
--output-dir data/$(EXPERIMENT)
train-detect:
python scripts/train_yolo.py \
--data data/$(EXPERIMENT)/data_detect.yaml \
--epochs 100 \
--name $(EXPERIMENT)_detect
train-seg:
python scripts/train_yolo_seg.py \
--data data/$(EXPERIMENT)/data_segment.yaml \
--epochs 100 \
--name $(EXPERIMENT)_seg
\end{lstlisting}
% ============================================================================
% ГЛАВА 4. ПРАКТИЧЕСКИЙ ПРИМЕР
% ============================================================================
\section{Практический пример: Эксперимент ZNO Cyst AF}
\subsection{Описание эксперимента}
\textbf{Датасет:} \texttt{1\_2236040\_zno\_cyst\_AF}
\textbf{Характеристики:}
\begin{itemize}
\item Модальность: КТ (компьютерная томография)
\item Количество исходных срезов: 1042 (DICOM)
\item Количество обработанных срезов: 300 (PNG)
\item Количество классов: 12
\item Тип задачи: детекция и сегментация
\end{itemize}
\subsection{Схема классов}
\texttt{predefined\_classes.txt}:
\begin{verbatim}
right_kidney_ID1
right_kidney_upper_segment_ID2
right_kidney_middle_segment_ID3
right_kidney_lower_segment_ID4
left_kidney_ID5
left_kidney_upper_segment_ID6
left_kidney_middle_segment_ID7
left_kidney_lower_segment_ID8
malignant_tumor_ID9
benign_tumor_ID10
cyst_ID11
abscess_ID12
\end{verbatim}
\textbf{Структура классов:}
\begin{table}[H]
\centering
\caption{Структура классов эксперимента}
\begin{tabular}{l|r|r|r}
\toprule
\textbf{Класс} & \textbf{ID (1-based)} & \textbf{YOLO idx} & \textbf{Количество} \\
\midrule
right\_kidney & 1 & 0 & 300 \\
right\_kidney\_upper\_segment & 2 & 1 & 35 \\
right\_kidney\_middle\_segment & 3 & 2 & 55 \\
right\_kidney\_lower\_segment & 4 & 3 & 47 \\
left\_kidney & 5 & 4 & 26 \\
left\_kidney\_upper\_segment & 6 & 5 & 28 \\
left\_kidney\_middle\_segment & 7 & 6 & 36 \\
left\_kidney\_lower\_segment & 8 & 7 & 44 \\
malignant\_tumor & 9 & 8 & 52 \\
benign\_tumor & 10 & 9 & 38 \\
cyst & 11 & 10 & 41 \\
abscess & 12 & 11 & 12 \\
\bottomrule
\end{tabular}
\end{table}
\subsection{Результаты обучения}
\subsubsection{Детекция}
\begin{table}[H]
\centering
\caption{Результаты обучения детекции}
\begin{tabular}{l|r}
\toprule
\textbf{Метрика} & \textbf{Значение} \\
\midrule
mAP50 (box) & 0.794 \\
mAP50-95 (box) & 0.681 \\
Precision & 0.823 \\
Recall & 0.856 \\
F1-score & 0.839 \\
\bottomrule
\end{tabular}
\end{table}
\subsubsection{Сегментация}
\begin{table}[H]
\centering
\caption{Результаты обучения сегментации}
\begin{tabular}{l|r}
\toprule
\textbf{Метрика} & \textbf{Значение} \\
\midrule
mAP50 (mask) & 0.752 \\
mAP50-95 (mask) & 0.623 \\
Dice Coefficient & 0.815 \\
IoU & 0.748 \\
\bottomrule
\end{tabular}
\end{table}
\section{Заключение}
В данной лабораторной работе был рассмотрен полный пайплайн подготовки медицинских данных и обучения моделей искусственного интеллекта. Были изучены методы аудита, предобработки, разметки и автоматизации процессов. Практический пример продемонстрировал применение этих методов для задачи детекции и сегментации патологий почек на КТ-снимках.
\begin{thebibliography}{9}
\bibitem{yolov8} Jocher G. et al. Ultralytics YOLOv8. 2023.
\bibitem{brats} Menze B.H. et al. The Multimodal Brain Tumor Image Segmentation Benchmark (BRATS). IEEE TMI, 2015.
\bibitem{pyradiomics} van Griethuizen L. et al. PyRadiomics: Extractable features from medical images. JOSS, 2018.
\end{thebibliography}
\end{document}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB