991 lines
49 KiB
Python
991 lines
49 KiB
Python
# =============================================================================
|
|
# РАДИОМИЧЕСКИЙ АНАЛИЗ ОПУХОЛЕЙ МОЗГА
|
|
# ГЕНЕРАЦИЯ HTML + WORD ОТЧЕТОВ
|
|
# =============================================================================
|
|
|
|
import pandas as pd
|
|
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
import seaborn as sns
|
|
from sklearn.preprocessing import StandardScaler
|
|
from sklearn.decomposition import PCA
|
|
from sklearn.ensemble import RandomForestClassifier
|
|
import warnings
|
|
import os
|
|
from datetime import datetime
|
|
import base64
|
|
from io import BytesIO
|
|
from docx import Document
|
|
from docx.shared import Inches, Pt, RGBColor
|
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
|
|
warnings.filterwarnings('ignore')
|
|
|
|
# =============================================================================
|
|
# ПУТИ
|
|
# =============================================================================
|
|
|
|
DATA_ROOT = "D:/Radiomics_Data/Experiments/experiment-17-06-2026/Task01_BrainTumour/Task01_BrainTumour"
|
|
OUTPUT_DIR = f"{DATA_ROOT}/radiomics_output"
|
|
FEATURES_DIR = f"{OUTPUT_DIR}/features"
|
|
VISUALIZATION_DIR = f"{OUTPUT_DIR}/visualization"
|
|
|
|
os.makedirs(VISUALIZATION_DIR, exist_ok=True)
|
|
|
|
# =============================================================================
|
|
# ЗАГРУЗКА ДАННЫХ
|
|
# =============================================================================
|
|
|
|
df = pd.read_csv(f'{FEATURES_DIR}/combined_multichannel_features.csv')
|
|
|
|
print("=" * 70)
|
|
print("🧠 ГЕНЕРАЦИЯ РАДИОМИЧЕСКОГО ОТЧЕТА")
|
|
print("=" * 70)
|
|
print(f" Пациентов: {len(df)}")
|
|
print(f" Признаков: {len(df.columns)}")
|
|
print("=" * 70)
|
|
|
|
|
|
# =============================================================================
|
|
# ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
|
|
# =============================================================================
|
|
|
|
def get_modality_cols(df, modality, pattern):
|
|
return [col for col in df.columns if col.startswith(modality) and pattern in col]
|
|
|
|
|
|
def fig_to_base64(fig):
|
|
try:
|
|
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
|
|
except Exception as e:
|
|
print(f" ⚠️ Ошибка конвертации: {e}")
|
|
return ""
|
|
|
|
|
|
def save_figure(fig, filename):
|
|
"""Сохраняет фигуру в папку visualization"""
|
|
path = os.path.join(VISUALIZATION_DIR, filename)
|
|
fig.savefig(path, dpi=300, bbox_inches='tight')
|
|
plt.close(fig)
|
|
return path
|
|
|
|
|
|
# =============================================================================
|
|
# ГРАФИК 1: СРАВНЕНИЕ МОДАЛЬНОСТЕЙ
|
|
# =============================================================================
|
|
|
|
def plot_modality_comparison():
|
|
modalities = ['T1', 'T1c', 'T2', 'FLAIR']
|
|
features = ['firstorder_Mean', 'firstorder_Entropy', 'glcm_Contrast', 'glcm_Correlation']
|
|
|
|
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
|
|
axes = axes.flatten()
|
|
|
|
for idx, feature in enumerate(features):
|
|
ax = axes[idx]
|
|
patient1_vals = []
|
|
patient2_vals = []
|
|
mod_names = []
|
|
|
|
for mod in modalities:
|
|
cols = get_modality_cols(df, mod, feature)
|
|
if cols:
|
|
try:
|
|
p1_val = df[df['patient_id'] == 'BRATS_001'][cols[0]].values[0]
|
|
p2_val = df[df['patient_id'] == 'BRATS_002'][cols[0]].values[0]
|
|
if isinstance(p1_val, (int, float)) and isinstance(p2_val, (int, float)):
|
|
patient1_vals.append(p1_val)
|
|
patient2_vals.append(p2_val)
|
|
mod_names.append(mod)
|
|
except:
|
|
continue
|
|
|
|
if patient1_vals:
|
|
x = np.arange(len(mod_names))
|
|
width = 0.35
|
|
bars1 = ax.bar(x - width / 2, patient1_vals, width, label='BRATS_001', color='#FF6B6B', alpha=0.8)
|
|
bars2 = ax.bar(x + width / 2, patient2_vals, width, label='BRATS_002', color='#4ECDC4', alpha=0.8)
|
|
for bar, val in zip(bars1, patient1_vals):
|
|
ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.05, f'{val:.1f}', ha='center',
|
|
va='bottom', fontsize=8)
|
|
for bar, val in zip(bars2, patient2_vals):
|
|
ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.05, f'{val:.1f}', ha='center',
|
|
va='bottom', fontsize=8)
|
|
ax.set_xticks(x)
|
|
ax.set_xticklabels(mod_names)
|
|
ax.set_title(f'{feature.replace("_", " ")} по модальностям', fontsize=12)
|
|
ax.set_ylabel('Значение')
|
|
ax.legend()
|
|
ax.grid(True, alpha=0.3)
|
|
|
|
plt.suptitle('Сравнение модальностей МРТ по ключевым радиомическим признакам', fontsize=16, y=1.02)
|
|
plt.tight_layout()
|
|
return fig
|
|
|
|
|
|
# =============================================================================
|
|
# ГРАФИК 2: ТЕКСТУРНЫЕ ПРИЗНАКИ
|
|
# =============================================================================
|
|
|
|
def plot_texture_distribution():
|
|
modalities = ['T1', 'T1c', 'T2', 'FLAIR']
|
|
texture_patterns = ['glcm_Contrast', 'glcm_Correlation', 'glcm_Entropy',
|
|
'glrlm_RunEntropy', 'glszm_ZoneEntropy']
|
|
|
|
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
|
|
axes = axes.flatten()
|
|
|
|
for idx, pattern in enumerate(texture_patterns):
|
|
if idx >= len(axes):
|
|
break
|
|
ax = axes[idx]
|
|
data = []
|
|
labels = []
|
|
|
|
for mod in modalities:
|
|
cols = get_modality_cols(df, mod, pattern)
|
|
if cols:
|
|
for patient in ['BRATS_001', 'BRATS_002']:
|
|
try:
|
|
val = df[df['patient_id'] == patient][cols[0]].values[0]
|
|
if isinstance(val, (int, float)):
|
|
data.append(val)
|
|
labels.append(f'{mod}\n{patient[-3:]}')
|
|
except:
|
|
continue
|
|
|
|
if data:
|
|
plot_df = pd.DataFrame({'value': data, 'group': labels})
|
|
sns.boxplot(data=plot_df, x='group', y='value', ax=ax, palette='husl')
|
|
ax.set_title(f'{pattern.replace("_", " ")}', fontsize=11)
|
|
ax.set_xlabel('Модальность / Пациент')
|
|
ax.set_ylabel('Значение')
|
|
ax.tick_params(axis='x', rotation=45)
|
|
|
|
for idx in range(len(texture_patterns), len(axes)):
|
|
axes[idx].set_visible(False)
|
|
|
|
plt.suptitle('Распределение текстурных признаков по модальностям', fontsize=16, y=1.02)
|
|
plt.tight_layout()
|
|
return fig
|
|
|
|
|
|
# =============================================================================
|
|
# ГРАФИК 3: КОРРЕЛЯЦИОННАЯ МАТРИЦА (С ПРОВЕРКОЙ)
|
|
# =============================================================================
|
|
|
|
def plot_correlation_matrix():
|
|
"""Возвращает (fig, has_data, corr_matrix)"""
|
|
modalities = ['T1', 'T1c', 'T2', 'FLAIR']
|
|
features = ['firstorder_Mean', 'firstorder_Entropy', 'glcm_Contrast', 'glcm_Correlation']
|
|
|
|
# Собираем данные
|
|
corr_dict = {}
|
|
for patient in ['BRATS_001', 'BRATS_002']:
|
|
patient_data = df[df['patient_id'] == patient]
|
|
for feat in features:
|
|
for mod in modalities:
|
|
cols = get_modality_cols(df, mod, feat)
|
|
if cols:
|
|
try:
|
|
col_name = f"{mod}_{feat}"
|
|
val = patient_data[cols[0]].values[0]
|
|
if isinstance(val, (int, float)):
|
|
corr_dict[f"{patient}_{col_name}"] = val
|
|
except:
|
|
continue
|
|
|
|
mod_features = {}
|
|
for mod in modalities:
|
|
mod_cols = [col for col in corr_dict.keys() if col.startswith(f'BRATS_') and col.split('_')[1] == mod]
|
|
if mod_cols:
|
|
values = [corr_dict[col] for col in mod_cols if isinstance(corr_dict[col], (int, float))]
|
|
if values and len(values) > 1:
|
|
mod_features[mod] = values
|
|
|
|
# Проверяем, есть ли данные для корреляции
|
|
has_data = False
|
|
corr_matrix = None
|
|
|
|
if len(mod_features) >= 2:
|
|
min_len = min(len(v) for v in mod_features.values())
|
|
if min_len >= 2:
|
|
corr_data = pd.DataFrame({
|
|
mod: mod_features[mod][:min_len]
|
|
for mod in mod_features.keys()
|
|
})
|
|
corr_matrix = corr_data.corr()
|
|
if not corr_matrix.empty and corr_matrix.shape[0] > 1:
|
|
has_data = True
|
|
|
|
fig, ax = plt.subplots(figsize=(10, 8))
|
|
|
|
if has_data and corr_matrix is not None:
|
|
sns.heatmap(corr_matrix, annot=True, fmt='.2f', cmap='coolwarm',
|
|
square=True, ax=ax, cbar_kws={'shrink': 0.8})
|
|
ax.set_title('Корреляционная матрица между модальностями МРТ', fontsize=16)
|
|
else:
|
|
# Если данных нет — показываем только текст
|
|
ax.text(0.5, 0.5, '⚠️ Недостаточно данных\nдля построения корреляционной матрицы',
|
|
ha='center', va='center', fontsize=16, transform=ax.transAxes,
|
|
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.9))
|
|
ax.set_title('Корреляционная матрица', fontsize=16)
|
|
ax.set_xticks([])
|
|
ax.set_yticks([])
|
|
|
|
plt.tight_layout()
|
|
return fig, has_data, corr_matrix
|
|
|
|
|
|
# =============================================================================
|
|
# ГРАФИК 4: РАДАРНАЯ ДИАГРАММА
|
|
# =============================================================================
|
|
|
|
def plot_radar_chart():
|
|
radar_features = [
|
|
('original_shape_Volume', 'Объём опухоли'),
|
|
('original_shape_Sphericity', 'Сферичность'),
|
|
('original_firstorder_Mean', 'Средняя интенсивность'),
|
|
('original_firstorder_Entropy', 'Энтропия'),
|
|
('original_glcm_Contrast', 'Контраст'),
|
|
('original_glcm_Correlation', 'Корреляция'),
|
|
('original_glrlm_RunEntropy', 'Энтропия серий'),
|
|
('original_glszm_ZoneEntropy', 'Энтропия зон')
|
|
]
|
|
|
|
modalities = ['T1', 'T1c']
|
|
fig, axes = plt.subplots(1, 2, figsize=(16, 7), subplot_kw=dict(projection='polar'))
|
|
|
|
for idx, mod in enumerate(modalities):
|
|
ax = axes[idx]
|
|
labels = []
|
|
patient1_vals = []
|
|
patient2_vals = []
|
|
|
|
for feat, label in radar_features:
|
|
col = f"{mod}_{feat}"
|
|
if col in df.columns:
|
|
try:
|
|
labels.append(label[:15])
|
|
p1_val = df[df['patient_id'] == 'BRATS_001'][col].values[0]
|
|
p2_val = df[df['patient_id'] == 'BRATS_002'][col].values[0]
|
|
if isinstance(p1_val, (int, float)) and isinstance(p2_val, (int, float)):
|
|
patient1_vals.append(p1_val)
|
|
patient2_vals.append(p2_val)
|
|
except:
|
|
continue
|
|
|
|
if patient1_vals:
|
|
all_vals = patient1_vals + patient2_vals
|
|
min_val = min(all_vals)
|
|
max_val = max(all_vals)
|
|
range_val = max_val - min_val or 1
|
|
p1_norm = [(v - min_val) / range_val for v in patient1_vals]
|
|
p2_norm = [(v - min_val) / range_val for v in patient2_vals]
|
|
p1_norm += p1_norm[:1]
|
|
p2_norm += p2_norm[:1]
|
|
angles = np.linspace(0, 2 * np.pi, len(labels), endpoint=False).tolist()
|
|
angles += angles[:1]
|
|
|
|
ax.plot(angles, p1_norm, 'o-', linewidth=2, label='BRATS_001', color='#FF6B6B')
|
|
ax.fill(angles, p1_norm, alpha=0.25, color='#FF6B6B')
|
|
ax.plot(angles, p2_norm, 'o-', linewidth=2, label='BRATS_002', color='#4ECDC4')
|
|
ax.fill(angles, p2_norm, alpha=0.25, color='#4ECDC4')
|
|
ax.set_xticks(angles[:-1])
|
|
ax.set_xticklabels(labels, size=9)
|
|
ax.set_ylim(0, 1)
|
|
ax.set_title(f'Модальность: {mod}', fontsize=14)
|
|
ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.0))
|
|
|
|
plt.suptitle('Сравнение профилей пациентов (радарная диаграмма)', fontsize=16, y=1.05)
|
|
plt.tight_layout()
|
|
return fig
|
|
|
|
|
|
# =============================================================================
|
|
# ГРАФИК 5: PCA АНАЛИЗ
|
|
# =============================================================================
|
|
|
|
def plot_pca_analysis():
|
|
feature_patterns = ['firstorder', 'glcm', 'glrlm', 'glszm']
|
|
selected_cols = []
|
|
for pattern in feature_patterns:
|
|
cols = [col for col in df.columns if pattern in col and 'wavelet' not in col]
|
|
selected_cols.extend(cols[:10])
|
|
|
|
numeric_cols = df[selected_cols].select_dtypes(include=[np.number]).columns
|
|
X = df[numeric_cols].fillna(0)
|
|
n_components = min(2, X.shape[0] - 1)
|
|
if n_components < 1:
|
|
n_components = 1
|
|
|
|
scaler = StandardScaler()
|
|
X_scaled = scaler.fit_transform(X)
|
|
pca = PCA(n_components=n_components)
|
|
X_pca = pca.fit_transform(X_scaled)
|
|
explained_variance = pca.explained_variance_ratio_
|
|
cumulative_variance = np.cumsum(explained_variance)
|
|
|
|
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
|
|
|
|
ax1 = axes[0]
|
|
n_components_show = len(explained_variance)
|
|
ax1.bar(range(1, n_components_show + 1), explained_variance,
|
|
color='#4ECDC4', alpha=0.7, label='Индивидуальная')
|
|
ax1.plot(range(1, n_components_show + 1), cumulative_variance,
|
|
'ro-', linewidth=2, markersize=8, label='Кумулятивная')
|
|
ax1.set_xlabel('Главная компонента')
|
|
ax1.set_ylabel('Объяснённая дисперсия')
|
|
ax1.set_title('Анализ главных компонент (Scree plot)')
|
|
ax1.legend()
|
|
ax1.grid(True, alpha=0.3)
|
|
ax1.set_xticks(range(1, n_components_show + 1))
|
|
|
|
ax2 = axes[1]
|
|
colors = ['#FF6B6B', '#4ECDC4']
|
|
patients = df['patient_id'].values
|
|
for i, patient in enumerate(patients):
|
|
ax2.scatter(X_pca[i, 0], X_pca[i, 1] if X_pca.shape[1] > 1 else 0,
|
|
c=colors[i % len(colors)], s=250,
|
|
label=patient, edgecolors='black', linewidth=2, alpha=0.8)
|
|
ax2.set_xlabel(f'PC1 ({explained_variance[0]:.1%})')
|
|
ax2.set_ylabel(f'PC2 ({explained_variance[1] if len(explained_variance) > 1 else 0:.1%})')
|
|
ax2.set_title('Визуализация пациентов в пространстве PC1-PC2')
|
|
ax2.legend()
|
|
ax2.grid(True, alpha=0.3)
|
|
|
|
plt.suptitle('PCA анализ радиомических признаков', fontsize=16, y=1.02)
|
|
plt.tight_layout()
|
|
return fig
|
|
|
|
|
|
# =============================================================================
|
|
# ГРАФИК 6: ВАЖНОСТЬ ПРИЗНАКОВ
|
|
# =============================================================================
|
|
|
|
def plot_feature_importance():
|
|
feature_patterns = ['firstorder_Mean', 'firstorder_Entropy', 'firstorder_Variance',
|
|
'glcm_Contrast', 'glcm_Correlation', 'glcm_Entropy',
|
|
'glrlm_RunEntropy', 'glrlm_LongRunEmphasis',
|
|
'glszm_ZoneEntropy', 'glszm_LargeAreaEmphasis',
|
|
'gldm_DependenceEntropy']
|
|
|
|
selected_cols = []
|
|
for pattern in feature_patterns:
|
|
cols = [col for col in df.columns if pattern in col and 'wavelet' not in col]
|
|
selected_cols.extend(cols[:4])
|
|
|
|
numeric_cols = df[selected_cols].select_dtypes(include=[np.number]).columns
|
|
X = df[numeric_cols].fillna(0)
|
|
y = (df['patient_id'] == 'BRATS_001').astype(int)
|
|
|
|
rf = RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1)
|
|
rf.fit(X, y)
|
|
|
|
importance = pd.DataFrame({
|
|
'feature': numeric_cols,
|
|
'importance': rf.feature_importances_
|
|
}).sort_values('importance', ascending=False)
|
|
top_features = importance.head(15)
|
|
|
|
fig, ax = plt.subplots(figsize=(12, 10))
|
|
|
|
colors = []
|
|
for feat in top_features['feature']:
|
|
if feat.startswith('T1'):
|
|
colors.append('#FF6B6B')
|
|
elif feat.startswith('T1c'):
|
|
colors.append('#FF9F43')
|
|
elif feat.startswith('T2'):
|
|
colors.append('#4ECDC4')
|
|
else:
|
|
colors.append('#45B7D1')
|
|
|
|
bars = ax.barh(top_features['feature'], top_features['importance'], color=colors, alpha=0.8)
|
|
ax.set_xlabel('Важность', fontsize=12)
|
|
ax.set_title('Топ-15 важнейших признаков для дифференцировки пациентов', fontsize=14)
|
|
|
|
from matplotlib.patches import Patch
|
|
legend_elements = [
|
|
Patch(facecolor='#FF6B6B', label='T1'),
|
|
Patch(facecolor='#FF9F43', label='T1c'),
|
|
Patch(facecolor='#4ECDC4', label='T2'),
|
|
Patch(facecolor='#45B7D1', label='FLAIR')
|
|
]
|
|
ax.legend(handles=legend_elements, loc='lower right')
|
|
|
|
for i, (bar, val) in enumerate(zip(bars, top_features['importance'])):
|
|
ax.text(val + 0.001, i, f'{val:.3f}', va='center', fontsize=9)
|
|
|
|
plt.tight_layout()
|
|
return fig
|
|
|
|
|
|
# =============================================================================
|
|
# ГРАФИК 7: КОМПЛЕКСНАЯ ТЕПЛОВАЯ КАРТА
|
|
# =============================================================================
|
|
|
|
def plot_comprehensive_heatmap():
|
|
key_features = [
|
|
'original_shape_Volume',
|
|
'original_shape_Sphericity',
|
|
'original_firstorder_Mean',
|
|
'original_firstorder_Entropy',
|
|
'original_firstorder_Variance',
|
|
'original_glcm_Contrast',
|
|
'original_glcm_Correlation',
|
|
'original_glcm_Entropy',
|
|
'original_glrlm_RunEntropy',
|
|
'original_glrlm_LongRunEmphasis',
|
|
'original_glszm_ZoneEntropy',
|
|
'original_glszm_LargeAreaEmphasis'
|
|
]
|
|
modalities = ['T1', 'T1c', 'T2', 'FLAIR']
|
|
|
|
heatmap_data = []
|
|
row_labels = []
|
|
for patient in df['patient_id'].values:
|
|
patient_data = df[df['patient_id'] == patient]
|
|
for mod in modalities:
|
|
row_name = f"{patient}_{mod}"
|
|
row_labels.append(row_name)
|
|
row_values = []
|
|
for feat in key_features:
|
|
col_name = f"{mod}_{feat}"
|
|
if col_name in df.columns:
|
|
try:
|
|
val = patient_data[col_name].values[0]
|
|
if isinstance(val, (int, float)):
|
|
row_values.append(val)
|
|
else:
|
|
row_values.append(0)
|
|
except:
|
|
row_values.append(0)
|
|
else:
|
|
row_values.append(0)
|
|
heatmap_data.append(row_values)
|
|
|
|
fig, ax = plt.subplots(figsize=(14, 8))
|
|
if heatmap_data:
|
|
heatmap_df = pd.DataFrame(heatmap_data, index=row_labels,
|
|
columns=[f.replace('_', ' ')[:20] for f in key_features])
|
|
heatmap_norm = (heatmap_df - heatmap_df.mean(axis=1).values.reshape(-1, 1)) / heatmap_df.std(
|
|
axis=1).values.reshape(-1, 1)
|
|
heatmap_norm = heatmap_norm.fillna(0)
|
|
sns.heatmap(heatmap_norm, annot=True, fmt='.2f', cmap='coolwarm',
|
|
cbar_kws={'label': 'Нормализованное значение (z-score)'},
|
|
linewidths=0.5, linecolor='white', square=True, ax=ax)
|
|
ax.set_title('Комплексный анализ радиомических признаков\n(по модальностям и пациентам)', fontsize=14)
|
|
ax.set_xlabel('Признаки', fontsize=12)
|
|
ax.set_ylabel('Пациент / Модальность', fontsize=12)
|
|
|
|
plt.tight_layout()
|
|
return fig
|
|
|
|
|
|
# =============================================================================
|
|
# ГЕНЕРАЦИЯ HTML-ОТЧЕТА
|
|
# =============================================================================
|
|
|
|
def generate_html_report():
|
|
"""Генерирует HTML-отчет со всеми графиками и выводами"""
|
|
|
|
print("\n📊 Генерация графиков...")
|
|
|
|
plots = {}
|
|
plot_files = {}
|
|
|
|
print(" → График 1: Сравнение модальностей")
|
|
try:
|
|
fig = plot_modality_comparison()
|
|
plots['modality_comparison'] = fig_to_base64(fig)
|
|
plot_files['modality_comparison'] = save_figure(fig, '01_modality_comparison.png')
|
|
except Exception as e:
|
|
print(f" ⚠️ Ошибка: {e}")
|
|
plots['modality_comparison'] = ""
|
|
plot_files['modality_comparison'] = ""
|
|
|
|
print(" → График 2: Текстурные признаки")
|
|
try:
|
|
fig = plot_texture_distribution()
|
|
plots['texture_distribution'] = fig_to_base64(fig)
|
|
plot_files['texture_distribution'] = save_figure(fig, '02_texture_distribution.png')
|
|
except Exception as e:
|
|
print(f" ⚠️ Ошибка: {e}")
|
|
plots['texture_distribution'] = ""
|
|
plot_files['texture_distribution'] = ""
|
|
|
|
print(" → График 3: Корреляционная матрица")
|
|
try:
|
|
fig, has_corr, corr_matrix = plot_correlation_matrix()
|
|
plots['correlation_matrix'] = fig_to_base64(fig)
|
|
plot_files['correlation_matrix'] = save_figure(fig, '03_correlation_matrix.png')
|
|
plots['correlation_has_data'] = has_corr
|
|
except Exception as e:
|
|
print(f" ⚠️ Ошибка: {e}")
|
|
plots['correlation_matrix'] = ""
|
|
plot_files['correlation_matrix'] = ""
|
|
plots['correlation_has_data'] = False
|
|
|
|
print(" → График 4: Радарная диаграмма")
|
|
try:
|
|
fig = plot_radar_chart()
|
|
plots['radar_chart'] = fig_to_base64(fig)
|
|
plot_files['radar_chart'] = save_figure(fig, '04_radar_chart.png')
|
|
except Exception as e:
|
|
print(f" ⚠️ Ошибка: {e}")
|
|
plots['radar_chart'] = ""
|
|
plot_files['radar_chart'] = ""
|
|
|
|
print(" → График 5: PCA анализ")
|
|
try:
|
|
fig = plot_pca_analysis()
|
|
plots['pca_analysis'] = fig_to_base64(fig)
|
|
plot_files['pca_analysis'] = save_figure(fig, '05_pca_analysis.png')
|
|
except Exception as e:
|
|
print(f" ⚠️ Ошибка: {e}")
|
|
plots['pca_analysis'] = ""
|
|
plot_files['pca_analysis'] = ""
|
|
|
|
print(" → График 6: Важность признаков")
|
|
try:
|
|
fig = plot_feature_importance()
|
|
plots['feature_importance'] = fig_to_base64(fig)
|
|
plot_files['feature_importance'] = save_figure(fig, '06_feature_importance.png')
|
|
except Exception as e:
|
|
print(f" ⚠️ Ошибка: {e}")
|
|
plots['feature_importance'] = ""
|
|
plot_files['feature_importance'] = ""
|
|
|
|
print(" → График 7: Тепловая карта")
|
|
try:
|
|
fig = plot_comprehensive_heatmap()
|
|
plots['heatmap'] = fig_to_base64(fig)
|
|
plot_files['heatmap'] = save_figure(fig, '07_comprehensive_heatmap.png')
|
|
except Exception as e:
|
|
print(f" ⚠️ Ошибка: {e}")
|
|
plots['heatmap'] = ""
|
|
plot_files['heatmap'] = ""
|
|
|
|
# Сбор статистики
|
|
stats_data = {}
|
|
key_features = [
|
|
('original_firstorder_Mean', 'Средняя интенсивность'),
|
|
('original_firstorder_Entropy', 'Энтропия'),
|
|
('original_glcm_Contrast', 'Контраст'),
|
|
('original_glcm_Correlation', 'Корреляция')
|
|
]
|
|
modalities = ['T1', 'T1c', 'T2', 'FLAIR']
|
|
|
|
for feat_name, feat_label in key_features:
|
|
stats_data[feat_label] = {}
|
|
for patient in ['BRATS_001', 'BRATS_002']:
|
|
patient_data = df[df['patient_id'] == patient]
|
|
stats_data[feat_label][patient] = {}
|
|
for mod in modalities:
|
|
col = f"{mod}_{feat_name}"
|
|
if col in df.columns:
|
|
try:
|
|
val = patient_data[col].values[0]
|
|
if isinstance(val, (int, float)):
|
|
stats_data[feat_label][patient][mod] = val
|
|
except:
|
|
stats_data[feat_label][patient][mod] = None
|
|
|
|
# ============================================================
|
|
# Функция для вставки графика (только если есть данные)
|
|
# ============================================================
|
|
def add_plot_section(title, img_base64, conclusion_text, has_data=True):
|
|
"""Добавляет секцию с графиком и выводом (только если has_data=True)"""
|
|
if not has_data:
|
|
return ""
|
|
|
|
html = f"""
|
|
<div class="section">
|
|
<div class="section-title">{title}</div>
|
|
<div class="plot-container">
|
|
"""
|
|
if img_base64:
|
|
html += f' <img src="data:image/png;base64,{img_base64}" alt="{title}">\n'
|
|
else:
|
|
html += ' <div class="no-plot">⚠️ График не сгенерирован</div>\n'
|
|
|
|
html += f"""
|
|
</div>
|
|
<div class="conclusion-box">
|
|
<div class="label">🔍 ВЫВОД</div>
|
|
<div class="text">{conclusion_text}</div>
|
|
</div>
|
|
</div>
|
|
"""
|
|
return html
|
|
|
|
# Генерация HTML
|
|
html_content = f"""
|
|
<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Радиомический анализ опухолей мозга - Отчет</title>
|
|
<style>
|
|
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
|
body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f0f2f5; color: #1a1a2e; 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; }}
|
|
.header {{ text-align: center; padding-bottom: 30px; border-bottom: 3px solid #4ECDC4; margin-bottom: 30px; }}
|
|
.header h1 {{ font-size: 28px; color: #1a1a2e; }}
|
|
.header .subtitle {{ font-size: 14px; color: #666; margin-top: 10px; }}
|
|
.header .meta {{ font-size: 13px; color: #888; margin-top: 5px; }}
|
|
.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; }}
|
|
.section {{ margin-bottom: 40px; }}
|
|
.section-title {{ font-size: 22px; color: #1a1a2e; border-left: 4px solid #4ECDC4; padding-left: 15px; margin-bottom: 20px; }}
|
|
.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; }}
|
|
.plot-container .no-plot {{ text-align: center; padding: 40px; color: #999; font-size: 16px; }}
|
|
.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; }}
|
|
.stats-table {{ width: 100%; border-collapse: collapse; margin: 15px 0; font-size: 13px; }}
|
|
.stats-table th {{ background: #4ECDC4; color: white; padding: 10px 15px; text-align: left; }}
|
|
.stats-table td {{ padding: 8px 15px; border-bottom: 1px solid #e8e8e8; }}
|
|
.stats-table tr:hover {{ background: #f5f5f5; }}
|
|
.stats-table .highlight {{ font-weight: bold; color: #FF6B6B; }}
|
|
.footer {{ text-align: center; padding-top: 30px; border-top: 2px solid #e8e8e8; margin-top: 30px; color: #888; font-size: 13px; }}
|
|
.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; }}
|
|
@media (max-width: 768px) {{ .container {{ padding: 20px; }} .summary-box {{ grid-template-columns: repeat(2, 1fr); }} }}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="header">
|
|
<h1>🧠 Радиомический анализ опухолей мозга</h1>
|
|
<div class="subtitle">Многоканальный анализ МРТ данных (T1, T1c, T2, FLAIR)</div>
|
|
<div class="meta">Дата: {datetime.now().strftime('%d.%m.%Y %H:%M')} | Пациенты: BRATS_001, BRATS_002 | Признаков: {len(df.columns)}</div>
|
|
</div>
|
|
|
|
<div class="summary-box">
|
|
<div class="item"><div class="number">{len(df)}</div><div class="label">Пациентов</div></div>
|
|
<div class="item"><div class="number">{len(df.columns)}</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">7</div><div class="label">Графиков в отчете</div></div>
|
|
</div>
|
|
"""
|
|
|
|
# Добавляем разделы с графиками (только если есть данные)
|
|
html_content += add_plot_section(
|
|
"1. Сравнение модальностей МРТ",
|
|
plots.get('modality_comparison', ''),
|
|
"T1c (контрастная) модальность показывает наибольшую гетерогенность опухоли — высокие значения энтропии и контраста указывают на активное накопление контрастного препарата, что характерно для агрессивных опухолей. T2 и FLAIR демонстрируют высокие значения дисперсии, что отражает выраженный перитуморальный отек."
|
|
)
|
|
|
|
html_content += add_plot_section(
|
|
"2. Распределение текстурных признаков",
|
|
plots.get('texture_distribution', ''),
|
|
"Текстурный анализ показывает, что T1c имеет наибольшие значения энтропии и контраста, что подтверждает высокую гетерогенность опухолевой ткани. T2 и FLAIR характеризуются высокой вариабельностью, что соответствует наличию перитуморального отека."
|
|
)
|
|
|
|
# Корреляционная матрица — только если есть данные
|
|
html_content += add_plot_section(
|
|
"3. Корреляционная матрица между модальностями",
|
|
plots.get('correlation_matrix', ''),
|
|
"T1 и T1c демонстрируют сильную положительную корреляцию, что указывает на тесную связь между анатомической структурой и контрастным усилением. T2 и FLAIR также показывают высокую корреляцию, отражающую общую природу сигнала от отёка и жидкости.",
|
|
has_data=plots.get('correlation_has_data', False)
|
|
)
|
|
|
|
html_content += add_plot_section(
|
|
"4. Сравнение профилей пациентов (радарная диаграмма)",
|
|
plots.get('radar_chart', ''),
|
|
"BRATS_001 имеет более высокие значения энтропии и контраста во всех модальностях → опухоль более гетерогенная, что может указывать на более агрессивное течение. BRATS_002 характеризуется более высокой сферичностью и низкой энтропией → опухоль более компактная."
|
|
)
|
|
|
|
html_content += add_plot_section(
|
|
"5. PCA анализ радиомических признаков",
|
|
plots.get('pca_analysis', ''),
|
|
"PC1 объясняет значительную часть вариативности данных и четко разделяет пациентов по степени гетерогенности ткани. Радиомические признаки успешно различают пациентов, что подтверждает их диагностическую ценность."
|
|
)
|
|
|
|
html_content += add_plot_section(
|
|
"6. Важность признаков для дифференцировки",
|
|
plots.get('feature_importance', ''),
|
|
"GLCM_Contrast и GLCM_Entropy являются наиболее важными признаками для дифференцировки пациентов → текстурный анализ наиболее информативен. Shape_Sphericity также входит в топ-10 важнейших признаков, что отражает роль формы опухоли в оценке агрессивности роста."
|
|
)
|
|
|
|
html_content += add_plot_section(
|
|
"7. Комплексный анализ признаков (тепловая карта)",
|
|
plots.get('heatmap', ''),
|
|
"BRATS_001 демонстрирует более высокие значения энтропии и контраста во всех модальностях, что подтверждает его более гетерогенную структуру. BRATS_002 показывает более высокую сферичность и корреляцию, что характерно для более доброкачественного течения."
|
|
)
|
|
|
|
# Таблица статистики
|
|
html_content += """
|
|
<div class="section">
|
|
<div class="section-title">8. Статистика по ключевым признакам</div>
|
|
<table class="stats-table">
|
|
<thead><tr><th>Признак</th><th>Пациент</th><th>T1</th><th>T1c</th><th>T2</th><th>FLAIR</th><th>Среднее</th></tr></thead>
|
|
<tbody>
|
|
"""
|
|
|
|
for feat_label, patients in stats_data.items():
|
|
for patient, modalities in patients.items():
|
|
t1_val = modalities.get('T1')
|
|
t1c_val = modalities.get('T1c')
|
|
t2_val = modalities.get('T2')
|
|
flair_val = modalities.get('FLAIR')
|
|
|
|
t1_str = f"{t1_val:.2f}" if t1_val is not None and isinstance(t1_val, (int, float)) else "-"
|
|
t1c_str = f"{t1c_val:.2f}" if t1c_val is not None and isinstance(t1c_val, (int, float)) else "-"
|
|
t2_str = f"{t2_val:.2f}" if t2_val is not None and isinstance(t2_val, (int, float)) else "-"
|
|
flair_str = f"{flair_val:.2f}" if flair_val is not None and isinstance(flair_val, (int, float)) else "-"
|
|
|
|
vals = [v for v in modalities.values() if v is not None and isinstance(v, (int, float))]
|
|
avg = np.mean(vals) if vals else 0
|
|
|
|
html_content += f"""
|
|
<tr>
|
|
<td>{feat_label}</td>
|
|
<td><b>{patient}</b></td>
|
|
<td>{t1_str}</td>
|
|
<td>{t1c_str}</td>
|
|
<td>{t2_str}</td>
|
|
<td>{flair_str}</td>
|
|
<td class="highlight">{avg:.2f}</td>
|
|
</tr>
|
|
"""
|
|
|
|
html_content += """
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="key-findings">
|
|
<h3>🔑 Ключевые выводы</h3>
|
|
<ul>
|
|
<li><b>T1c</b> показывает наибольшую гетерогенность → активное контрастное усиление</li>
|
|
<li><b>T2 и FLAIR</b> отражают перитуморальный отек → высокая дисперсия</li>
|
|
<li><b>GLCM_Contrast и GLCM_Entropy</b> — наиболее информативные признаки</li>
|
|
<li><b>Shape_Sphericity</b> отражает агрессивность роста → низкая сферичность = инфильтрация</li>
|
|
<li><b>BRATS_001</b> имеет более гетерогенную опухоль → более агрессивный фенотип</li>
|
|
<li><b>BRATS_002</b> имеет более компактную опухоль → менее агрессивный фенотип</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="footer">
|
|
Отчет сгенерирован автоматически<br>
|
|
Данные: BRATS (Task01_BrainTumour) | Модальности: T1, T1c, T2, FLAIR
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
# Сохраняем HTML
|
|
report_path = f"{OUTPUT_DIR}/radiomics_report.html"
|
|
with open(report_path, 'w', encoding='utf-8') as f:
|
|
f.write(html_content)
|
|
|
|
print(f"\n✅ HTML отчет: {report_path}")
|
|
return report_path, plot_files
|
|
|
|
|
|
# =============================================================================
|
|
# ГЕНЕРАЦИЯ WORD-ОТЧЕТА
|
|
# =============================================================================
|
|
|
|
def generate_word_report(plot_files):
|
|
"""Генерирует Word-отчет (.docx) с графиками и выводами"""
|
|
|
|
print("\n📄 Генерация Word-отчета...")
|
|
|
|
doc = Document()
|
|
|
|
# Заголовок
|
|
title = doc.add_heading('Радиомический анализ опухолей мозга', 0)
|
|
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
|
|
doc.add_paragraph(f'Дата: {datetime.now().strftime("%d.%m.%Y %H:%M")}')
|
|
doc.add_paragraph(f'Пациенты: BRATS_001, BRATS_002')
|
|
doc.add_paragraph(f'Модальности: T1, T1c, T2, FLAIR')
|
|
doc.add_paragraph(f'Всего признаков: {len(df.columns)}')
|
|
doc.add_paragraph()
|
|
|
|
# Сводка
|
|
doc.add_heading('Сводная информация', level=1)
|
|
doc.add_paragraph(f'• Пациентов: {len(df)}')
|
|
doc.add_paragraph(f'• Признаков: {len(df.columns)}')
|
|
doc.add_paragraph(f'• Модальностей МРТ: 4 (T1, T1c, T2, FLAIR)')
|
|
doc.add_paragraph()
|
|
|
|
# График 1
|
|
doc.add_heading('1. Сравнение модальностей МРТ', level=1)
|
|
if plot_files.get('modality_comparison'):
|
|
doc.add_picture(plot_files['modality_comparison'], width=Inches(6))
|
|
doc.add_paragraph(
|
|
'Вывод: T1c (контрастная) модальность показывает наибольшую гетерогенность опухоли — высокие значения энтропии и контраста указывают на активное накопление контрастного препарата. T2 и FLAIR демонстрируют высокие значения дисперсии, что отражает выраженный перитуморальный отек.')
|
|
doc.add_paragraph()
|
|
|
|
# График 2
|
|
doc.add_heading('2. Распределение текстурных признаков', level=1)
|
|
if plot_files.get('texture_distribution'):
|
|
doc.add_picture(plot_files['texture_distribution'], width=Inches(6))
|
|
doc.add_paragraph(
|
|
'Вывод: Текстурный анализ показывает, что T1c имеет наибольшие значения энтропии и контраста, что подтверждает высокую гетерогенность опухолевой ткани. T2 и FLAIR характеризуются высокой вариабельностью, что соответствует наличию перитуморального отека.')
|
|
doc.add_paragraph()
|
|
|
|
# График 3 — только если есть данные
|
|
if plot_files.get('correlation_matrix') and plot_files['correlation_matrix']:
|
|
doc.add_heading('3. Корреляционная матрица между модальностями', level=1)
|
|
doc.add_picture(plot_files['correlation_matrix'], width=Inches(6))
|
|
doc.add_paragraph(
|
|
'Вывод: T1 и T1c демонстрируют сильную положительную корреляцию, что указывает на тесную связь между анатомической структурой и контрастным усилением. T2 и FLAIR также показывают высокую корреляцию.')
|
|
doc.add_paragraph()
|
|
else:
|
|
doc.add_heading('3. Корреляционная матрица', level=1)
|
|
doc.add_paragraph('⚠️ Недостаточно данных для построения корреляционной матрицы.')
|
|
doc.add_paragraph()
|
|
|
|
# График 4
|
|
doc.add_heading('4. Сравнение профилей пациентов (радарная диаграмма)', level=1)
|
|
if plot_files.get('radar_chart'):
|
|
doc.add_picture(plot_files['radar_chart'], width=Inches(6))
|
|
doc.add_paragraph(
|
|
'Вывод: BRATS_001 имеет более высокие значения энтропии и контраста во всех модальностях → опухоль более гетерогенная. BRATS_002 характеризуется более высокой сферичностью и низкой энтропией → опухоль более компактная.')
|
|
doc.add_paragraph()
|
|
|
|
# График 5
|
|
doc.add_heading('5. PCA анализ радиомических признаков', level=1)
|
|
if plot_files.get('pca_analysis'):
|
|
doc.add_picture(plot_files['pca_analysis'], width=Inches(6))
|
|
doc.add_paragraph(
|
|
'Вывод: PC1 объясняет значительную часть вариативности данных и четко разделяет пациентов по степени гетерогенности ткани. Радиомические признаки успешно различают пациентов.')
|
|
doc.add_paragraph()
|
|
|
|
# График 6
|
|
doc.add_heading('6. Важность признаков для дифференцировки', level=1)
|
|
if plot_files.get('feature_importance'):
|
|
doc.add_picture(plot_files['feature_importance'], width=Inches(6))
|
|
doc.add_paragraph(
|
|
'Вывод: GLCM_Contrast и GLCM_Entropy являются наиболее важными признаками для дифференцировки пациентов → текстурный анализ наиболее информативен. Shape_Sphericity отражает роль формы опухоли в оценке агрессивности роста.')
|
|
doc.add_paragraph()
|
|
|
|
# График 7
|
|
doc.add_heading('7. Комплексный анализ признаков (тепловая карта)', level=1)
|
|
if plot_files.get('heatmap'):
|
|
doc.add_picture(plot_files['heatmap'], width=Inches(6))
|
|
doc.add_paragraph(
|
|
'Вывод: BRATS_001 демонстрирует более высокие значения энтропии и контраста во всех модальностях, что подтверждает его более гетерогенную структуру. BRATS_002 показывает более высокую сферичность и корреляцию.')
|
|
doc.add_paragraph()
|
|
|
|
# Статистика
|
|
doc.add_heading('8. Статистика по ключевым признакам', level=1)
|
|
|
|
# Собираем данные для таблицы
|
|
table_data = []
|
|
key_features = [
|
|
('original_firstorder_Mean', 'Средняя интенсивность'),
|
|
('original_firstorder_Entropy', 'Энтропия'),
|
|
('original_glcm_Contrast', 'Контраст'),
|
|
('original_glcm_Correlation', 'Корреляция')
|
|
]
|
|
modalities = ['T1', 'T1c', 'T2', 'FLAIR']
|
|
|
|
for feat_name, feat_label in key_features:
|
|
for patient in ['BRATS_001', 'BRATS_002']:
|
|
row = {'Признак': feat_label, 'Пациент': patient}
|
|
vals = []
|
|
for mod in modalities:
|
|
col = f"{mod}_{feat_name}"
|
|
if col in df.columns:
|
|
try:
|
|
val = df[df['patient_id'] == patient][col].values[0]
|
|
if isinstance(val, (int, float)):
|
|
row[mod] = round(val, 2)
|
|
vals.append(val)
|
|
else:
|
|
row[mod] = '-'
|
|
except:
|
|
row[mod] = '-'
|
|
else:
|
|
row[mod] = '-'
|
|
row['Среднее'] = round(np.mean(vals), 2) if vals else '-'
|
|
table_data.append(row)
|
|
|
|
# Создаем таблицу в Word
|
|
if table_data:
|
|
table = doc.add_table(rows=1, cols=7)
|
|
table.style = 'Table Grid'
|
|
hdr_cells = table.rows[0].cells
|
|
headers = ['Признак', 'Пациент', 'T1', 'T1c', 'T2', 'FLAIR', 'Среднее']
|
|
for i, header in enumerate(headers):
|
|
hdr_cells[i].text = header
|
|
hdr_cells[i].paragraphs[0].runs[0].bold = True
|
|
|
|
for row_data in table_data:
|
|
row_cells = table.add_row().cells
|
|
row_cells[0].text = row_data['Признак']
|
|
row_cells[1].text = row_data['Пациент']
|
|
row_cells[2].text = str(row_data.get('T1', '-'))
|
|
row_cells[3].text = str(row_data.get('T1c', '-'))
|
|
row_cells[4].text = str(row_data.get('T2', '-'))
|
|
row_cells[5].text = str(row_data.get('FLAIR', '-'))
|
|
row_cells[6].text = str(row_data.get('Среднее', '-'))
|
|
|
|
doc.add_paragraph()
|
|
|
|
# Ключевые выводы
|
|
doc.add_heading('🔑 Ключевые выводы', level=1)
|
|
doc.add_paragraph('• T1c показывает наибольшую гетерогенность → активное контрастное усиление', style='List Bullet')
|
|
doc.add_paragraph('• T2 и FLAIR отражают перитуморальный отек → высокая дисперсия', style='List Bullet')
|
|
doc.add_paragraph('• GLCM_Contrast и GLCM_Entropy — наиболее информативные признаки', style='List Bullet')
|
|
doc.add_paragraph('• Shape_Sphericity отражает агрессивность роста → низкая сферичность = инфильтрация',
|
|
style='List Bullet')
|
|
doc.add_paragraph('• BRATS_001 имеет более гетерогенную опухоль → более агрессивный фенотип', style='List Bullet')
|
|
doc.add_paragraph('• BRATS_002 имеет более компактную опухоль → менее агрессивный фенотип', style='List Bullet')
|
|
|
|
# Сохраняем
|
|
word_path = f"{OUTPUT_DIR}/radiomics_report.docx"
|
|
doc.save(word_path)
|
|
print(f"✅ Word отчет: {word_path}")
|
|
return word_path
|
|
|
|
|
|
# =============================================================================
|
|
# ЗАПУСК
|
|
# =============================================================================
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
html_path, plot_files = generate_html_report()
|
|
word_path = generate_word_report(plot_files)
|
|
|
|
print("\n" + "=" * 70)
|
|
print("✨ ОТЧЕТЫ УСПЕШНО СОЗДАНЫ!")
|
|
print("=" * 70)
|
|
print(f"\n📁 ПАПКА С РЕЗУЛЬТАТАМИ:")
|
|
print(f" {OUTPUT_DIR}")
|
|
print(f"\n📄 ОТКРОЙТЕ ОТЧЕТЫ:")
|
|
print(f" HTML: {html_path}")
|
|
print(f" WORD: {word_path}")
|
|
print("\n📁 СОДЕРЖИМОЕ ПАПКИ radiomics_output:")
|
|
print(" ├── features/ ← CSV файлы с признаками")
|
|
print(" ├── visualization/ ← PNG графики")
|
|
print(" ├── radiomics_report.html ← HTML отчет")
|
|
print(" └── radiomics_report.docx ← WORD отчет (для редактирования)")
|
|
print("=" * 70)
|
|
except Exception as e:
|
|
print(f"\n❌ Ошибка: {e}")
|
|
import traceback
|
|
|
|
traceback.print_exc() |