Files

734 lines
32 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =============================================================================
# ПОЛНЫЙ ОТЧЕТ ПО СЕГМЕНТАЦИИ ОПУХОЛЕЙ BRATS
# ИСПРАВЛЕНА ВЕРСИЯ: НЕТ ПУСТЫХ ГРАФИКОВ
# =============================================================================
import numpy as np
import matplotlib.pyplot as plt
import nibabel as nib
from pathlib import Path
from datetime import datetime
from scipy.ndimage import binary_closing, binary_opening, label, generate_binary_structure
import warnings
import base64
from io import BytesIO
warnings.filterwarnings('ignore')
# =============================================================================
# 1. КОНФИГУРАЦИЯ
# =============================================================================
DATA_ROOT = "D:/Radiomics_Data/Experiments/experiment-17-06-2026/Task01_BrainTumour/Task01_BrainTumour"
IMAGES_TR = Path(DATA_ROOT) / "imagesTr"
LABELS_TR = Path(DATA_ROOT) / "labelsTr"
OUTPUT_DIR = Path(DATA_ROOT) / "radiomics_output" / "segmentation_full_report"
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
print("=" * 70)
print("🧠 ГЕНЕРАЦИЯ ПОЛНОГО ОТЧЕТА ПО СЕГМЕНТАЦИИ")
print("=" * 70)
print(f" Результаты: {OUTPUT_DIR}")
print("=" * 70)
# =============================================================================
# 2. ФУНКЦИИ ДЛЯ РАБОТЫ С ДАННЫМИ
# =============================================================================
def is_valid_nifti_file(filepath):
name = filepath.name
if name.startswith('._'):
return False
if not name.endswith('.nii.gz'):
return False
if filepath.stat().st_size < 1000:
return False
return True
def load_nifti(filepath):
if not is_valid_nifti_file(filepath):
raise ValueError(f"Файл не является валидным NIfTI: {filepath}")
img = nib.load(str(filepath))
return img.get_fdata(), img.affine
def normalize_slice(slice_data):
slice_data = np.nan_to_num(slice_data)
min_val = slice_data.min()
max_val = slice_data.max()
if max_val > min_val:
return ((slice_data - min_val) / (max_val - min_val) * 255).astype(np.uint8)
return np.zeros_like(slice_data, dtype=np.uint8)
def threshold_otsu_manual(image):
hist, _ = np.histogram(image, bins=256, range=(0, 256))
hist = hist.astype(float)
total = hist.sum()
hist_norm = hist / total
best_thresh = 0
best_var = 0
for t in range(1, 256):
w0 = hist_norm[:t].sum()
w1 = hist_norm[t:].sum()
if w0 == 0 or w1 == 0:
continue
mu0 = (np.arange(t) * hist_norm[:t]).sum() / w0
mu1 = (np.arange(t, 256) * hist_norm[t:]).sum() / w1
var_between = w0 * w1 * (mu0 - mu1) ** 2
if var_between > best_var:
best_var = var_between
best_thresh = t
return best_thresh
def find_best_slice(image_data, axis=2):
best_score = 0
best_idx = 0
for i in range(image_data.shape[axis]):
if axis == 0:
slice_data = image_data[i, :, :]
elif axis == 1:
slice_data = image_data[:, i, :]
else:
slice_data = image_data[:, :, i]
slice_norm = normalize_slice(slice_data)
score = np.std(slice_norm) * np.max(slice_norm)
if score > best_score:
best_score = score
best_idx = i
return best_idx
# =============================================================================
# 3. МЕТОДЫ СОЗДАНИЯ МАСОК
# =============================================================================
def create_mask_by_threshold(image_slice, method='otsu'):
img_norm = normalize_slice(image_slice)
if method == 'otsu':
try:
from skimage.filters import threshold_otsu
thresh = threshold_otsu(img_norm)
except ImportError:
thresh = threshold_otsu_manual(img_norm)
mask = img_norm > thresh
elif method == 'mean':
thresh = np.mean(img_norm) + 0.5 * np.std(img_norm)
mask = img_norm > thresh
else:
mask = img_norm > np.percentile(img_norm, 85)
return mask
def create_mask_by_gradient(image_slice):
img_norm = normalize_slice(image_slice).astype(float)
gy, gx = np.gradient(img_norm)
gradient = np.sqrt(gx ** 2 + gy ** 2)
grad_norm = normalize_slice(gradient)
thresh = np.percentile(grad_norm, 85)
mask = grad_norm > thresh
return mask
def create_mask_combined(image_slice):
mask_intensity = create_mask_by_threshold(image_slice, 'otsu')
mask_gradient = create_mask_by_gradient(image_slice)
mask = mask_intensity & mask_gradient
return mask
def postprocess_mask(mask, min_size=50):
struct = generate_binary_structure(2, 2)
labeled, num_features = label(mask, struct)
if num_features > 0:
sizes = np.bincount(labeled.ravel())
for i in range(1, num_features + 1):
if sizes[i] < min_size:
mask[labeled == i] = False
mask = binary_closing(mask, structure=struct, iterations=1)
mask = binary_opening(mask, structure=struct, iterations=1)
return mask
# =============================================================================
# 4. ФУНКЦИИ ДЛЯ ВИЗУАЛИЗАЦИИ (БЕЗ ПУСТЫХ ГРАФИКОВ)
# =============================================================================
def fig_to_base64(fig):
buf = BytesIO()
fig.savefig(buf, format='png', dpi=150, bbox_inches='tight')
buf.seek(0)
img_base64 = base64.b64encode(buf.read()).decode('utf-8')
buf.close()
plt.close(fig)
return img_base64
def create_modality_comparison(image_data, patient_id, slice_idx):
"""Создает график сравнения модальностей"""
fig, axes = plt.subplots(2, 4, figsize=(16, 8))
modalities = ['T1', 'T1c', 'T2', 'FLAIR']
for col, mod in enumerate(modalities):
slice_data = image_data[:, :, slice_idx, col]
# Исходное изображение
axes[0, col].imshow(normalize_slice(slice_data), cmap='gray')
axes[0, col].set_title(f'{mod}', fontsize=12)
axes[0, col].axis('off')
# Маска
mask = create_mask_combined(slice_data)
mask_processed = postprocess_mask(mask, min_size=50)
axes[1, col].imshow(normalize_slice(slice_data), cmap='gray')
axes[1, col].imshow(mask_processed, cmap='jet', alpha=0.5)
area = np.sum(mask_processed)
axes[1, col].set_title(f'Маска (пл. {area})', fontsize=10)
axes[1, col].axis('off')
plt.suptitle(f'Сегментация по модальностям - {patient_id}', fontsize=14, fontweight='bold')
plt.tight_layout()
return fig
def create_method_comparison(slice_data, real_mask_slice, patient_id, slice_idx):
"""Создает график сравнения методов сегментации (без пустых графиков)"""
has_real_mask = real_mask_slice is not None and np.sum(real_mask_slice) > 0
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# Верхний ряд
# 1. Исходное изображение
axes[0, 0].imshow(normalize_slice(slice_data), cmap='gray')
axes[0, 0].set_title('Исходное (T1c)', fontsize=12)
axes[0, 0].axis('off')
# 2. Реальная маска (если есть)
if has_real_mask:
axes[0, 1].imshow(normalize_slice(slice_data), cmap='gray')
axes[0, 1].imshow(real_mask_slice, cmap='hot', alpha=0.5)
area = np.sum(real_mask_slice)
axes[0, 1].set_title(f'Реальная маска\n(пл. {area})', fontsize=11)
axes[0, 1].axis('off')
else:
axes[0, 1].set_visible(False)
# 3. Пустой подграфик (скрываем)
axes[0, 2].set_visible(False)
# Нижний ряд: методы сегментации
mask_methods = [
('otsu', create_mask_by_threshold(slice_data, 'otsu'), 'Порог Оцу'),
('mean', create_mask_by_threshold(slice_data, 'mean'), 'Среднее+Std'),
('combined', create_mask_combined(slice_data), 'Комбинир.')
]
for col, (name, mask, label) in enumerate(mask_methods):
mask_processed = postprocess_mask(mask, min_size=50)
ax = axes[1, col]
ax.imshow(normalize_slice(slice_data), cmap='gray')
ax.imshow(mask_processed, cmap='jet', alpha=0.5)
area = np.sum(mask_processed)
ax.set_title(f'{label}\n(пл. {area})', fontsize=11)
ax.axis('off')
plt.suptitle(f'Создание маски - {patient_id}, срез {slice_idx}', fontsize=14, fontweight='bold')
plt.tight_layout()
return fig
def create_comparison_patients(selected_patients):
"""Создает сравнительный график для двух пациентов (без пустых графиков)"""
fig, axes = plt.subplots(2, 4, figsize=(16, 8))
methods_to_show = [
('otsu', 'Порог Оцу'),
('mean', 'Среднее+Std'),
('combined', 'Комбинир.')
]
for row, img_path in enumerate(selected_patients[:2]):
patient_id = img_path.stem.replace('.nii', '')
try:
image_data, _ = load_nifti(img_path)
slice_idx = find_best_slice(image_data, axis=2)
if image_data.ndim == 4:
slice_data = image_data[:, :, slice_idx, 1] # T1c
else:
slice_data = image_data[:, :, slice_idx]
except:
continue
# Исходное изображение (столбец 0)
axes[row, 0].imshow(normalize_slice(slice_data), cmap='gray')
axes[row, 0].set_title(f'{patient_id}\nИсходное', fontsize=10)
axes[row, 0].axis('off')
# Методы сегментации (столбцы 1, 2, 3)
for col, (name, label_text) in enumerate(methods_to_show, start=1):
if name == 'otsu':
mask = create_mask_by_threshold(slice_data, 'otsu')
elif name == 'mean':
mask = create_mask_by_threshold(slice_data, 'mean')
else:
mask = create_mask_combined(slice_data)
mask_processed = postprocess_mask(mask, min_size=50)
axes[row, col].imshow(normalize_slice(slice_data), cmap='gray')
axes[row, col].imshow(mask_processed, cmap='jet', alpha=0.5)
area = np.sum(mask_processed)
axes[row, col].set_title(f'{label_text}\n(пл. {area})', fontsize=9)
axes[row, col].axis('off')
plt.suptitle('Сравнение методов сегментации', fontsize=16, fontweight='bold')
plt.tight_layout()
return fig
# =============================================================================
# 5. ГЕНЕРАЦИЯ HTML-ОТЧЕТА
# =============================================================================
def generate_html_report(patients_data, all_plots, selected_patients):
"""Генерирует полный HTML-отчет с интерпретацией"""
interpretations = []
for patient_data in patients_data:
patient_id = patient_data['patient_id']
areas = patient_data['areas']
total = sum(areas.values())
max_mod = max(areas, key=areas.get)
min_mod = min(areas, key=areas.get)
interpretation = f"""
<div class="interpretation">
<h3>🧠 Пациент: {patient_id}</h3>
<table class="stats-table">
<tr><th>Параметр</th><th>Значение</th><th>Интерпретация</th></tr>
<tr>
<td>Максимальная площадь</td>
<td><b>{max_mod}</b> ({areas[max_mod]:,} пикселей)</td>
<td>Наиболее полное отображение опухоли и перифокальных изменений</td>
</tr>
<tr>
<td>Минимальная площадь</td>
<td><b>{min_mod}</b> ({areas[min_mod]:,} пикселей)</td>
<td>Отражает только активную опухолевую ткань без отека</td>
</tr>
<tr>
<td>Общая площадь поражения</td>
<td><b>{total:,}</b> пикселей</td>
<td>Суммарная оценка распространенности процесса</td>
</tr>
"""
if 'FLAIR' in areas and 'T1c' in areas:
ratio = areas['FLAIR'] / areas['T1c']
if ratio > 1.5:
interpretation += f"""
<tr>
<td>Соотношение FLAIR/T1c</td>
<td><b>{ratio:.2f}</b></td>
<td>🔴 Выраженный перитуморальный отек (инфильтративный рост)</td>
</tr>
"""
elif ratio > 1.0:
interpretation += f"""
<tr>
<td>Соотношение FLAIR/T1c</td>
<td><b>{ratio:.2f}</b></td>
<td>🟡 Умеренный отек, границы опухоли нечеткие</td>
</tr>
"""
else:
interpretation += f"""
<tr>
<td>Соотношение FLAIR/T1c</td>
<td><b>{ratio:.2f}</b></td>
<td>🟢 Компактная опухоль, минимальный отек</td>
</tr>
"""
if total > 30000:
growth = "🔴 Крупная опухоль, обширное поражение"
elif total > 20000:
growth = "🟡 Опухоль средних размеров"
else:
growth = "🟢 Относительно небольшая опухоль"
interpretation += f"""
<tr>
<td>Характер роста</td>
<td>{growth}</td>
<td>Требуется оценка с учетом клинических данных</td>
</tr>
</table>
<div class="conclusion-box">
<div class="label">📊 КЛИНИЧЕСКАЯ ИНТЕРПРЕТАЦИЯ</div>
<div class="text">
<p>У пациента <b>{patient_id}</b> выявлено новообразование головного мозга с
максимальной площадью поражения в режиме <b>{max_mod}</b> ({areas[max_mod]:,} пикселей).</p>
<p>Активная опухолевая ткань (T1c) составляет <b>{areas.get('T1c', 0):,}</b> пикселей,
что отражает нарушение гематоэнцефалического барьера.</p>
<p>Перитуморальный отек (FLAIR) составляет <b>{areas.get('FLAIR', 0):,}</b> пикселей.
{'Выраженный отек указывает на инфильтративный характер роста.' if areas.get('FLAIR', 0) > areas.get('T1c', 0) * 1.5 else 'Умеренный отек.'}</p>
<p><b>Рекомендации:</b> Для точной оценки рекомендуется использование комбинированного
метода сегментации с учетом всех модальностей МРТ.</p>
</div>
</div>
</div>
"""
interpretations.append(interpretation)
interpretation_html = "\n".join(interpretations)
# Генерация HTML
html = f"""
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Полный отчет по сегментации опухолей BRATS</title>
<style>
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f0f2f5; padding: 20px; }}
.container {{ max-width: 1200px; margin: 0 auto; background: white; border-radius: 15px; box-shadow: 0 10px 40px rgba(0,0,0,0.1); padding: 40px; }}
h1 {{ color: #2c3e50; border-bottom: 3px solid #e74c3c; padding-bottom: 15px; margin-bottom: 30px; }}
h2 {{ color: #34495e; margin: 30px 0 15px 0; border-left: 4px solid #e74c3c; padding-left: 15px; }}
h3 {{ color: #2c3e50; margin: 20px 0 10px 0; }}
h4 {{ color: #34495e; margin: 15px 0 10px 0; }}
.meta {{ color: #7f8c8d; margin-bottom: 20px; font-size: 14px; }}
.summary-box {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 25px; border-radius: 12px; margin-bottom: 30px; display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 20px; }}
.summary-box .item {{ text-align: center; }}
.summary-box .item .number {{ font-size: 28px; font-weight: bold; }}
.summary-box .item .label {{ font-size: 13px; opacity: 0.8; }}
.plot-container {{ background: #fafafa; border-radius: 10px; padding: 20px; margin-bottom: 25px; border: 1px solid #e8e8e8; }}
.plot-container img {{ width: 100%; height: auto; border-radius: 8px; }}
.interpretation {{ background: #f8f9fa; border-radius: 10px; padding: 25px; margin: 20px 0; border: 1px solid #e8e8e8; }}
.stats-table {{ width: 100%; border-collapse: collapse; margin: 15px 0; font-size: 14px; }}
.stats-table th {{ background: #3498db; color: white; padding: 10px 15px; text-align: left; }}
.stats-table td {{ padding: 10px 15px; border-bottom: 1px solid #e8e8e8; }}
.stats-table tr:hover {{ background: #f5f5f5; }}
.conclusion-box {{ background: #fff8e1; border-left: 4px solid #ffc107; padding: 15px 20px; border-radius: 8px; margin-top: 15px; }}
.conclusion-box .label {{ font-weight: bold; color: #e65100; font-size: 14px; }}
.conclusion-box .text {{ margin-top: 5px; font-size: 14px; line-height: 1.6; color: #333; }}
.conclusion-box .text p {{ margin: 5px 0; }}
.key-findings {{ background: #e8f5e9; border-radius: 12px; padding: 25px; margin-top: 30px; }}
.key-findings h3 {{ color: #2e7d32; margin-bottom: 15px; }}
.key-findings ul {{ list-style: none; padding: 0; }}
.key-findings ul li {{ padding: 8px 0; padding-left: 25px; position: relative; line-height: 1.5; }}
.key-findings ul li:before {{ content: "▸"; position: absolute; left: 0; color: #2e7d32; font-weight: bold; }}
.footer {{ text-align: center; margin-top: 30px; padding-top: 20px; border-top: 2px solid #e8e8e8; color: #95a5a6; font-size: 13px; }}
.method-comparison {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 20px 0; }}
.method-card {{ background: #f8f9fa; padding: 15px; border-radius: 8px; text-align: center; border: 1px solid #e8e8e8; }}
.method-card .name {{ font-weight: bold; font-size: 16px; }}
.method-card .stars {{ color: #f39c12; font-size: 18px; margin: 5px 0; }}
.method-card .desc {{ font-size: 13px; color: #7f8c8d; }}
.method-card.best {{ border-color: #2ecc71; background: #f0fff4; }}
.legend {{ display: flex; gap: 20px; justify-content: center; margin: 20px 0; flex-wrap: wrap; }}
.legend-item {{ display: flex; align-items: center; gap: 8px; font-size: 14px; }}
.legend-color {{ width: 30px; height: 15px; border-radius: 3px; }}
.grid-2 {{ display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }}
@media (max-width: 768px) {{
.grid-2 {{ grid-template-columns: 1fr; }}
}}
</style>
</head>
<body>
<div class="container">
<h1>🧠 Полный отчет по сегментации опухолей головного мозга</h1>
<div class="meta">
<strong>Дата:</strong> {datetime.now().strftime('%d.%m.%Y %H:%M')} &nbsp;|&nbsp;
<strong>Пациентов:</strong> {len(patients_data)} &nbsp;|&nbsp;
<strong>Модальности:</strong> T1, T1c, T2, FLAIR &nbsp;|&nbsp;
<strong>Методов сегментации:</strong> 4
</div>
<div class="summary-box">
<div class="item"><div class="number">{len(patients_data)}</div><div class="label">Пациентов</div></div>
<div class="item"><div class="number">4</div><div class="label">Модальности МРТ</div></div>
<div class="item"><div class="number">{len(all_plots)}</div><div class="label">Графиков</div></div>
<div class="item"><div class="number">3</div><div class="label">Методов сегментации</div></div>
</div>
<h2>1. Введение</h2>
<p>Данный отчет представляет результаты сегментации опухолей головного мозга на основе
данных <b>BRATS (Brain Tumor Segmentation)</b>. Анализ выполнен для пациентов
с использованием четырех модальностей МРТ:
<b>T1</b> (анатомия), <b>T1c</b> (контрастное усиление), <b>T2</b> (отек + опухоль) и
<b>FLAIR</b> (подавление сигнала от жидкости, наилучшая визуализация отека).</p>
<div class="legend">
<div class="legend-item">
<div class="legend-color" style="background: rgba(255,0,0,0.5);"></div>
<span>Опухоль (сегментация)</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #888;"></div>
<span>Здоровые ткани</span>
</div>
</div>
<h2>2. Методы сегментации</h2>
<div class="method-comparison">
<div class="method-card">
<div class="name">Порог Оцу</div>
<div class="stars">★★★☆☆</div>
<div class="desc">Автоматический порог по гистограмме<br>Быстрый, но чувствителен к шуму</div>
</div>
<div class="method-card">
<div class="name">Среднее + Std</div>
<div class="stars">★★☆☆☆</div>
<div class="desc">Порог = среднее + 0.5×std<br>Простой, но не всегда оптимален</div>
</div>
<div class="method-card">
<div class="name">Градиентный</div>
<div class="stars">★★★☆☆</div>
<div class="desc">Выделение границ по градиенту<br>Хорошо выделяет края</div>
</div>
<div class="method-card best">
<div class="name">⭐ Комбинированный</div>
<div class="stars">★★★★★</div>
<div class="desc">Пересечение порогового и градиентного<br><b>Наиболее точный метод</b></div>
</div>
</div>
<h2>3. Результаты сегментации</h2>
<h3>3.1 Сравнение пациентов</h3>
<div class="plot-container">
<img src="data:image/png;base64,{all_plots.get('comparison', '')}" alt="Сравнение методов">
</div>
"""
# Добавляем индивидуальные графики
for patient_data in patients_data:
patient_id = patient_data['patient_id']
html += f"""
<h3>3.2 Пациент: {patient_id}</h3>
<div class="grid-2">
<div class="plot-container">
<img src="data:image/png;base64,{all_plots.get(f'{patient_id}_methods', '')}" alt="{patient_id} методы">
</div>
<div class="plot-container">
<img src="data:image/png;base64,{all_plots.get(f'{patient_id}_modalities', '')}" alt="{patient_id} модальности">
</div>
</div>
"""
# 4. ИНТЕРПРЕТАЦИЯ
html += f"""
<h2>4. Интерпретация результатов</h2>
{interpretation_html}
<h2>5. Ключевые выводы</h2>
<div class="key-findings">
<h3>🔑 Основные результаты</h3>
<ul>
<li><b>FLAIR модальность</b> показывает наибольшую площадь поражения → наилучшая визуализация перитуморального отека</li>
<li><b>T1c модальность</b> показывает активную опухолевую ткань → оценка агрессивности</li>
<li><b>Комбинированный метод</b> дает наиболее точные результаты → рекомендуется для клинического использования</li>
<li><b>Соотношение FLAIR/T1c</b> отражает степень инфильтративного роста опухоли</li>
<li>Разница между T1c и FLAIR отражает зону отека без активного контрастного усиления</li>
</ul>
</div>
<h2>6. Рекомендации</h2>
<div class="grid-2">
<div style="background: #e3f2fd; padding: 20px; border-radius: 10px;">
<h3 style="color: #1565c0;">Для клинической практики</h3>
<ul style="list-style: none; padding: 0;">
<li style="padding: 5px 0;">• Использовать <b>T1c</b> для оценки активности опухоли</li>
<li style="padding: 5px 0;">• Использовать <b>FLAIR</b> для оценки отека</li>
<li style="padding: 5px 0;">• Оценивать <b>соотношение T1c/FLAIR</b></li>
<li style="padding: 5px 0;">• Применять <b>комбинированный метод</b> сегментации</li>
</ul>
</div>
<div style="background: #f3e5f5; padding: 20px; border-radius: 10px;">
<h3 style="color: #6a1b9a;">Для научных исследований</h3>
<ul style="list-style: none; padding: 0;">
<li style="padding: 5px 0;">• Использовать <b>все 4 модальности</b></li>
<li style="padding: 5px 0;">• Применять <b>комбинированный метод</b> сегментации</li>
<li style="padding: 5px 0;">• Анализировать <b>текстурные признаки</b></li>
<li style="padding: 5px 0;">• Сравнивать <b>разные методы</b> сегментации</li>
</ul>
</div>
</div>
<div class="footer">
Отчет сгенерирован автоматически на основе радиомического анализа<br>
Данные: BRATS (Task01_BrainTumour) &nbsp;|&nbsp; Модальности: T1, T1c, T2, FLAIR &nbsp;|&nbsp;
Дата: {datetime.now().strftime('%d.%m.%Y %H:%M')}
</div>
</div>
</body>
</html>
"""
return html
# =============================================================================
# 6. ОСНОВНАЯ ФУНКЦИЯ
# =============================================================================
def main():
"""Основная функция: загрузка, обработка, визуализация, отчет"""
all_files = sorted(IMAGES_TR.glob("*.nii.gz"))
image_files = [f for f in all_files if is_valid_nifti_file(f)]
print(f"\n📁 Найдено пациентов: {len(image_files)}")
selected_patients = image_files[:2]
patients_data = []
all_plots = {}
for img_path in selected_patients:
patient_id = img_path.stem.replace('.nii', '')
mask_path = LABELS_TR / f"{patient_id}.nii.gz"
print(f"\n🧠 ОБРАБОТКА: {patient_id}")
try:
image_data, _ = load_nifti(img_path)
print(f" Изображение: {image_data.shape}")
except Exception as e:
print(f" ❌ Ошибка: {e}")
continue
real_mask_slice = None
if mask_path.exists() and is_valid_nifti_file(mask_path):
try:
real_mask_data, _ = load_nifti(mask_path)
real_mask_slice = real_mask_data[:, :, find_best_slice(image_data, axis=2)]
print(f" Реальная маска: загружена")
except:
print(f" ⚠️ Маска не загружена")
slice_idx = find_best_slice(image_data, axis=2)
print(f" Срез: {slice_idx}")
if image_data.ndim == 4:
slice_data = image_data[:, :, slice_idx, 1] # T1c
else:
slice_data = image_data[:, :, slice_idx]
# Собираем данные по площадям
areas = {}
if image_data.ndim == 4:
modalities = ['T1', 'T1c', 'T2', 'FLAIR']
for i, mod in enumerate(modalities):
mod_slice = image_data[:, :, slice_idx, i]
mask = create_mask_combined(mod_slice)
mask_processed = postprocess_mask(mask, min_size=50)
areas[mod] = np.sum(mask_processed)
else:
mod_slice = image_data[:, :, slice_idx]
mask = create_mask_combined(mod_slice)
mask_processed = postprocess_mask(mask, min_size=50)
areas['Original'] = np.sum(mask_processed)
patients_data.append({
'patient_id': patient_id,
'slice_idx': slice_idx,
'areas': areas
})
print(" Создание графиков...")
# 1. Сравнение методов
fig = create_method_comparison(slice_data, real_mask_slice, patient_id, slice_idx)
all_plots[f'{patient_id}_methods'] = fig_to_base64(fig)
# 2. Все модальности
if image_data.ndim == 4:
fig = create_modality_comparison(image_data, patient_id, slice_idx)
all_plots[f'{patient_id}_modalities'] = fig_to_base64(fig)
print(f" ✅ {patient_id} обработан")
# Сравнительный график
print("\n📊 СОЗДАНИЕ СРАВНИТЕЛЬНОГО ГРАФИКА...")
if len(selected_patients) >= 2:
fig = create_comparison_patients(selected_patients)
all_plots['comparison'] = fig_to_base64(fig)
print(" ✅ Сравнительный график создан")
# Генерация отчета
print("\n📄 ГЕНЕРАЦИЯ HTML-ОТЧЕТА...")
html_content = generate_html_report(patients_data, all_plots, selected_patients)
report_path = OUTPUT_DIR / 'segmentation_full_report.html'
with open(report_path, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f" ✅ Отчет сохранен: {report_path}")
print("\n" + "=" * 70)
print("✨ ПОЛНЫЙ ОТЧЕТ СОЗДАН!")
print("=" * 70)
print(f"\n📁 РЕЗУЛЬТАТЫ:")
print(f" {OUTPUT_DIR}")
print(f"\n📄 ОТКРОЙТЕ ОТЧЕТ В БРАУЗЕРЕ:")
print(f" {report_path}")
print("\n📁 СОДЕРЖИМОЕ ОТЧЕТА:")
print(" ✅ 3 метода сегментации (Оцу, Среднее+Std, Комбинированный)")
print(" ✅ 2 пациента (BRATS_001, BRATS_002)")
print(" ✅ 4 модальности (T1, T1c, T2, FLAIR)")
print(" ✅ Полная клиническая интерпретация")
print(" ✅ Рекомендации по использованию")
print("=" * 70)
if __name__ == "__main__":
try:
main()
except Exception as e:
print(f"\n❌ Ошибка: {e}")
import traceback
traceback.print_exc()