From c792f5eb0b23b4d5acf702fddc6336cfffc8301a Mon Sep 17 00:00:00 2001 From: Furen Date: Wed, 22 Jul 2026 14:50:09 +0800 Subject: [PATCH] Initial commit --- CT/Dice/test/compare_test_dice_scores.py | 143 ++++ CT/Dice/test/compute_test_dice.py | 306 +++++++++ .../compare_validation_dice_scores.py | 142 ++++ CT/Dice/validation/compute_validation_dice.py | 622 ++++++++++++++++++ CT/visualize_medical_compare.py | 141 ++++ CTC/Dice/test/compare_test_dice_scores.py | 287 ++++++++ CTC/Dice/test/compute_test_dice.py | 306 +++++++++ .../compare_validation_dice_scores.py | 142 ++++ .../validation/compute_validation_dice.py | 288 ++++++++ CTC/visualize_medical_compare_chiasm.py | 180 +++++ CTC/visualize_medical_compare_eyes.py | 180 +++++ .../Dice/test/compare_test_dice_scores.py | 170 +++++ CT_CTC_T1_OAR/Dice/test/compute_test_dice.py | 306 +++++++++ .../compare_validation_dice_scores.py | 286 ++++++++ .../validation/compute_validation_dice.py | 602 +++++++++++++++++ CT_CTC_T1_OAR/Left_optic_nerve.py | 161 +++++ CT_CTC_T1_OAR/chiasm.py | 165 +++++ .../Dice/test/compare_test_dice_scores.py | 144 ++++ .../Dice/test/compute_test_dice.py | 286 ++++++++ .../compare_validation_dice_scores.py | 111 ++++ .../validation/compute_validation_dice.py | 325 +++++++++ CT_CTC_T1_OAR_TV/visualize_medical_compare.py | 151 +++++ FLAIR/Dice/test/compare_test_dice_scores.py | 287 ++++++++ FLAIR/Dice/test/compute_test_dice.py | 614 +++++++++++++++++ .../compare_validation_dice_scores.py | 142 ++++ .../validation/compute_validation_dice.py | 288 ++++++++ T1/Dice/test/compare_test_dice_scores.py | 143 ++++ T1/Dice/test/compute_test_dice.py | 282 ++++++++ .../compare_validation_dice_scores.py | 142 ++++ T1/Dice/validation/compute_validation_dice.py | 288 ++++++++ T1/visualize_medical_compare.py | 331 ++++++++++ T1C/Dice/test/compare_test_dice_scores.py | 143 ++++ T1C/Dice/test/compute_test_dice.py | 282 ++++++++ .../compare_validation_dice_scores.py | 142 ++++ .../validation/compute_validation_dice.py | 288 ++++++++ T1C/low_chiasm.py | 149 +++++ T1C/visualize_medical_compare.py | 303 +++++++++ .../Dice/test/compare_test_dice_scores.py | 144 ++++ T1C_OAR_TV/Dice/test/compute_test_dice.py | 318 +++++++++ .../compare_validation_dice_scores.py | 111 ++++ .../validation/compute_validation_dice.py | 325 +++++++++ T1C_OAR_TV/lpre_ow_chiasm.py | 151 +++++ T1C_OAR_TV/visualize_medical_compare.py | 496 ++++++++++++++ T2/Dice/test/compare_test_dice_scores.py | 143 ++++ T2/Dice/test/compute_test_dice.py | 306 +++++++++ .../compare_validation_dice_scores.py | 142 ++++ T2/Dice/validation/compute_validation_dice.py | 300 +++++++++ T2/visualize_medical_compare.py | 151 +++++ .../copy_and_classify_modalities.py | 112 ++++ .../Image_files/merge_H4_N6_directories.py | 47 ++ .../Image_files/multi_modality_image_copy.py | 52 ++ .../Image_files/patient_modalities_files.py | 51 ++ .../OAR_Files/copy_patient_OAR_files.py | 46 ++ .../OAR_Files/merge_G4_M6_directories.py | 47 ++ .../OAR_Files/merge_patient_folders.py | 46 ++ .../OAR_Files/multi_organ_label_combiner.py | 89 +++ .../OAR_Files/organ_file_processor.py | 138 ++++ .../copy_patient_OAR_Target_files.py | 62 ++ .../merge_G4_M6_directories.py | 47 ++ .../merge_tv_patient_folders.py | 46 ++ .../organ_tv_file_processor.py | 156 +++++ .../tv_and_oar_label_combiner.py | 107 +++ .../advanced_multimodal_image_resampler.py | 108 +++ .../nnunet_Dataset012_OAR_TV_preparation.py | 119 ++++ .../nnunet_Dataset021_OAR_preparation.py | 128 ++++ .../nnunet_Dataset022_OAR_TV_preparation.py | 130 ++++ preprocessing/final/tv_t1c_ctctc_resampler.py | 110 ++++ preprocessing/rename_files.py | 147 +++++ .../copy_and_classify_modalities.py | 112 ++++ test/Image_files/merge_H4_N6_directories.py | 47 ++ test/Image_files/multi_modality_image_copy.py | 96 +++ test/Image_files/patient_modalities_files.py | 165 +++++ test/OAR_Files/copy_patient_OAR_files.py | 46 ++ test/OAR_Files/merge_G4_M6_directories.py | 47 ++ test/OAR_Files/merge_patient_folders.py | 46 ++ test/OAR_Files/multi_organ_label_combiner.py | 172 +++++ test/OAR_Files/organ_file_processor.py | 138 ++++ .../copy_patient_OAR_Target_files.py | 62 ++ .../merge_G4_M6_directories.py | 47 ++ .../merge_tv_patient_folders.py | 46 ++ .../organ_tv_file_processor.py | 156 +++++ .../tv_and_oar_label_combiner.py | 107 +++ .../advanced_multimodal_image_resampler.py | 227 +++++++ .../nnunet_Dataset012_OAR_TV_preparation.py | 119 ++++ .../nnunet_Dataset021_OAR_preparation.py | 128 ++++ .../nnunet_Dataset022_OAR_TV_preparation.py | 130 ++++ test/final/tv_t1c_ctctc_resampler.py | 110 ++++ test/process_T1C_for_nnUNet.py | 147 +++++ 88 files changed, 15791 insertions(+) create mode 100644 CT/Dice/test/compare_test_dice_scores.py create mode 100644 CT/Dice/test/compute_test_dice.py create mode 100644 CT/Dice/validation/compare_validation_dice_scores.py create mode 100644 CT/Dice/validation/compute_validation_dice.py create mode 100644 CT/visualize_medical_compare.py create mode 100644 CTC/Dice/test/compare_test_dice_scores.py create mode 100644 CTC/Dice/test/compute_test_dice.py create mode 100644 CTC/Dice/validation/compare_validation_dice_scores.py create mode 100644 CTC/Dice/validation/compute_validation_dice.py create mode 100644 CTC/visualize_medical_compare_chiasm.py create mode 100644 CTC/visualize_medical_compare_eyes.py create mode 100644 CT_CTC_T1_OAR/Dice/test/compare_test_dice_scores.py create mode 100644 CT_CTC_T1_OAR/Dice/test/compute_test_dice.py create mode 100644 CT_CTC_T1_OAR/Dice/validation/compare_validation_dice_scores.py create mode 100644 CT_CTC_T1_OAR/Dice/validation/compute_validation_dice.py create mode 100644 CT_CTC_T1_OAR/Left_optic_nerve.py create mode 100644 CT_CTC_T1_OAR/chiasm.py create mode 100644 CT_CTC_T1_OAR_TV/Dice/test/compare_test_dice_scores.py create mode 100644 CT_CTC_T1_OAR_TV/Dice/test/compute_test_dice.py create mode 100644 CT_CTC_T1_OAR_TV/Dice/validation/compare_validation_dice_scores.py create mode 100644 CT_CTC_T1_OAR_TV/Dice/validation/compute_validation_dice.py create mode 100644 CT_CTC_T1_OAR_TV/visualize_medical_compare.py create mode 100644 FLAIR/Dice/test/compare_test_dice_scores.py create mode 100644 FLAIR/Dice/test/compute_test_dice.py create mode 100644 FLAIR/Dice/validation/compare_validation_dice_scores.py create mode 100644 FLAIR/Dice/validation/compute_validation_dice.py create mode 100644 T1/Dice/test/compare_test_dice_scores.py create mode 100644 T1/Dice/test/compute_test_dice.py create mode 100644 T1/Dice/validation/compare_validation_dice_scores.py create mode 100644 T1/Dice/validation/compute_validation_dice.py create mode 100644 T1/visualize_medical_compare.py create mode 100644 T1C/Dice/test/compare_test_dice_scores.py create mode 100644 T1C/Dice/test/compute_test_dice.py create mode 100644 T1C/Dice/validation/compare_validation_dice_scores.py create mode 100644 T1C/Dice/validation/compute_validation_dice.py create mode 100644 T1C/low_chiasm.py create mode 100644 T1C/visualize_medical_compare.py create mode 100644 T1C_OAR_TV/Dice/test/compare_test_dice_scores.py create mode 100644 T1C_OAR_TV/Dice/test/compute_test_dice.py create mode 100644 T1C_OAR_TV/Dice/validation/compare_validation_dice_scores.py create mode 100644 T1C_OAR_TV/Dice/validation/compute_validation_dice.py create mode 100644 T1C_OAR_TV/lpre_ow_chiasm.py create mode 100644 T1C_OAR_TV/visualize_medical_compare.py create mode 100644 T2/Dice/test/compare_test_dice_scores.py create mode 100644 T2/Dice/test/compute_test_dice.py create mode 100644 T2/Dice/validation/compare_validation_dice_scores.py create mode 100644 T2/Dice/validation/compute_validation_dice.py create mode 100644 T2/visualize_medical_compare.py create mode 100644 preprocessing/Image_files/copy_and_classify_modalities.py create mode 100644 preprocessing/Image_files/merge_H4_N6_directories.py create mode 100644 preprocessing/Image_files/multi_modality_image_copy.py create mode 100644 preprocessing/Image_files/patient_modalities_files.py create mode 100644 preprocessing/OAR_Files/copy_patient_OAR_files.py create mode 100644 preprocessing/OAR_Files/merge_G4_M6_directories.py create mode 100644 preprocessing/OAR_Files/merge_patient_folders.py create mode 100644 preprocessing/OAR_Files/multi_organ_label_combiner.py create mode 100644 preprocessing/OAR_Files/organ_file_processor.py create mode 100644 preprocessing/OAR_Target_Files/copy_patient_OAR_Target_files.py create mode 100644 preprocessing/OAR_Target_Files/merge_G4_M6_directories.py create mode 100644 preprocessing/OAR_Target_Files/merge_tv_patient_folders.py create mode 100644 preprocessing/OAR_Target_Files/organ_tv_file_processor.py create mode 100644 preprocessing/OAR_Target_Files/tv_and_oar_label_combiner.py create mode 100644 preprocessing/final/advanced_multimodal_image_resampler.py create mode 100644 preprocessing/final/nnunet_Dataset012_OAR_TV_preparation.py create mode 100644 preprocessing/final/nnunet_Dataset021_OAR_preparation.py create mode 100644 preprocessing/final/nnunet_Dataset022_OAR_TV_preparation.py create mode 100644 preprocessing/final/tv_t1c_ctctc_resampler.py create mode 100644 preprocessing/rename_files.py create mode 100644 test/Image_files/copy_and_classify_modalities.py create mode 100644 test/Image_files/merge_H4_N6_directories.py create mode 100644 test/Image_files/multi_modality_image_copy.py create mode 100644 test/Image_files/patient_modalities_files.py create mode 100644 test/OAR_Files/copy_patient_OAR_files.py create mode 100644 test/OAR_Files/merge_G4_M6_directories.py create mode 100644 test/OAR_Files/merge_patient_folders.py create mode 100644 test/OAR_Files/multi_organ_label_combiner.py create mode 100644 test/OAR_Files/organ_file_processor.py create mode 100644 test/OAR_Target_Files/copy_patient_OAR_Target_files.py create mode 100644 test/OAR_Target_Files/merge_G4_M6_directories.py create mode 100644 test/OAR_Target_Files/merge_tv_patient_folders.py create mode 100644 test/OAR_Target_Files/organ_tv_file_processor.py create mode 100644 test/OAR_Target_Files/tv_and_oar_label_combiner.py create mode 100644 test/final/advanced_multimodal_image_resampler.py create mode 100644 test/final/nnunet_Dataset012_OAR_TV_preparation.py create mode 100644 test/final/nnunet_Dataset021_OAR_preparation.py create mode 100644 test/final/nnunet_Dataset022_OAR_TV_preparation.py create mode 100644 test/final/tv_t1c_ctctc_resampler.py create mode 100644 test/process_T1C_for_nnUNet.py diff --git a/CT/Dice/test/compare_test_dice_scores.py b/CT/Dice/test/compare_test_dice_scores.py new file mode 100644 index 0000000..15c3816 --- /dev/null +++ b/CT/Dice/test/compare_test_dice_scores.py @@ -0,0 +1,143 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# 設置中文字體支持 +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """讀取JSON數據文件""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"文件未找到: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON解析錯誤: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """從數據中提取特定器官的分數""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """創建和保存圖表""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('CT', fontsize=12, pad=10) # Changed title + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # 調整數值標籤位置和大小 + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6, # Reduced font size + rotation=0) # Optional: rotate text if still crowded + + plt.ylim(0, 100) + plt.ylabel('DSC (%)', fontsize=10) + plt.xlabel('OAR', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """打印數值結果""" + print("\n平均 Dice 分數 (百分比):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # 定義路徑和器官映射 + base_path = '/mnt/1248/onlylian/CT/Dice/test' + paths = { + '2D': os.path.join(base_path, 'CT_test_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'CT_test_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'CT_test_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve" + } + + # 初始化數據存儲 + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # 讀取和處理數據 + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # 計算平均值和標準差 + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # 創建圖表並保存 + output_path = os.path.join(base_path, 'CT_test_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # 打印結果 + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/CT/Dice/test/compute_test_dice.py b/CT/Dice/test/compute_test_dice.py new file mode 100644 index 0000000..3654542 --- /dev/null +++ b/CT/Dice/test/compute_test_dice.py @@ -0,0 +1,306 @@ +import json +import os +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import time + +def load_nifti(file_path): + """加載 NIfTI 文件""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """計算Dice係數""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """分離左右結構""" + # 使用SimpleITK的ConnectedComponent + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + # 計算連通區域數量和大小 + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: # 背景算一個,所以至少需要3個 + print(f"警告:{structure_name} 未找到足夠的連通區域") + return None, None + + # 排除背景(標籤0)並按大小排序 + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"警告:{structure_name} 未找到兩個主要區域") + return None, None + + # 獲取兩個最大區域 + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + # 計算質心 + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) # 使用x軸(第三維度)的平均值 + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + # 根據x軸位置確定左右 + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """評估單個案例""" + print(f"\n測試文件: {os.path.basename(pred_path)}") + + # 加載圖像 + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"圖像大小: {gt_img.shape}") + print(f"加載時間: {load_time:.2f}秒") + + results = {} + total_time = 0 + + # 處理眼睛 + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + # 形態學處理眼睛 + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + # 計算眼睛的Dice + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + + # 分離眼睛 + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + + # 處理視神經 + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + # 形態學處理視神經 + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + # 計算視神經的Dice + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + + # 分離視神經 + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + + # 計算各個標籤的Dice + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") + except Exception as e: + print(f"處理標籤 {label} 時出錯:{str(e)}") + results[label] = np.nan + continue + + print(f"單個案例總計算時間: {total_time:.2f}秒") + return results + +def evaluate_fold(gt_folder, pred_folder, labels): + """評估單個fold的所有案例""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(pred_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(pred_folder)}"): + gt_path = os.path.join(gt_folder, pred_file) + pred_path = os.path.join(pred_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"找不到對應的真實標籤文件:{gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + + # 收集結果 + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, base_pred_folder, model_name, labels, organ_names): + """評估單個模型的所有fold""" + print(f"\n開始評估模型:{model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + # 處理 fold_0 到 fold_4 + for fold in range(5): + print(f"\n處理 {model_name} - Fold {fold}") + pred_folder = os.path.join(base_pred_folder, f"fold_{fold}") + if not os.path.exists(pred_folder): + print(f"找不到預測文件夾:{pred_folder}") + continue + + results = evaluate_fold(gt_folder, pred_folder, labels) + fold_results[f"fold_{fold}"] = results + + # 合併結果 + for key in results: + all_results[key].extend(results[key]) + + # 計算總平均值和標準差 + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + # 保存結果到指定路径 + save_dir = "/mnt/1248/onlylian/CT/Dice/test" + os.makedirs(save_dir, exist_ok=True) + filename = os.path.join(save_dir, f'CT_test_dice_results_{model_name}.json') + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\n{model_name}結果已保存到 {filename}") + + # 打印結果 + print(f"\n{model_name} 模型的 Dice 結果(所有fold的平均值):") + + # 打印器官結果 + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: 無法計算 (n={n})") + + # 打印合併結果 + print("\n合併結構的結果:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "合併眼睛" if key == 'combined_eye' else "合併視神經" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: 無法計算 (n={n})") + + return summary + +if __name__ == "__main__": + # 設置路徑和標籤 + gt_folder = "/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset555/labelsTs" + models = { + '2D': '/mnt/1248/onlylian/CT/output_predictions/2d', + '3D_fullres': '/mnt/1248/onlylian/CT/output_predictions/3d_fullres', + '3D_lowres': '/mnt/1248/onlylian/CT/output_predictions/3d_lowres' + } + labels = [1, 2, 3, 4, 5, 6] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + + # 讓用戶選擇要運行哪個模型 + print("可用的模型:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names) + except (ValueError, IndexError): + print("無效的選擇!請輸入1-3之間的數字。") \ No newline at end of file diff --git a/CT/Dice/validation/compare_validation_dice_scores.py b/CT/Dice/validation/compare_validation_dice_scores.py new file mode 100644 index 0000000..6e44e50 --- /dev/null +++ b/CT/Dice/validation/compare_validation_dice_scores.py @@ -0,0 +1,142 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# 設置中文字體支持 +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """讀取JSON數據文件""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"文件未找到: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON解析錯誤: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """從數據中提取特定器官的分數""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """創建和保存圖表""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('CT', fontsize=12, pad=10) # Changed title + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # 添加數值標籤 + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height + 0.5, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6) # Reduced font size + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """打印數值結果""" + print("\n平均 Dice 分數 (百分比):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # 定義路徑和器官映射 + base_path = '/mnt/1248/onlylian/CT/Dice/validation' + paths = { + '2D': os.path.join(base_path, 'CT_validation_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'CT_validation_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'CT_validation_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve" + } + + # 初始化數據存儲 + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # 讀取和處理數據 + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # 計算平均值和標準差 + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # 創建圖表並保存 + output_path = os.path.join(base_path, 'CT_validation_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # 打印結果 + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/CT/Dice/validation/compute_validation_dice.py b/CT/Dice/validation/compute_validation_dice.py new file mode 100644 index 0000000..4f48b14 --- /dev/null +++ b/CT/Dice/validation/compute_validation_dice.py @@ -0,0 +1,622 @@ +# import json +# import os +# import numpy as np +# import SimpleITK as sitk +# from tqdm import tqdm +# import time + +# def load_nifti(file_path): +# """加載 NIfTI 文件""" +# return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +# def compute_dice(pred, gt): +# """計算Dice係數""" +# intersection = np.sum(pred & gt) +# sum_pred = np.sum(pred) +# sum_gt = np.sum(gt) + +# if sum_pred == 0 and sum_gt == 0: +# return 1.0 +# elif sum_pred == 0 or sum_gt == 0: +# return 0.0 + +# return 2.0 * intersection / (sum_pred + sum_gt) + +# def separate_structures(combined_mask, structure_name=""): +# """分離左右結構""" +# sitk_image = sitk.GetImageFromArray(combined_mask) +# labeled_components = sitk.ConnectedComponent(sitk_image) +# labeled_array = sitk.GetArrayFromImage(labeled_components) + +# unique, counts = np.unique(labeled_array, return_counts=True) +# if len(unique) < 3: +# print(f"警告:{structure_name} 未找到足夠的連通區域") +# return None, None + +# region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] +# sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + +# if len(sorted_regions) < 2: +# print(f"警告:{structure_name} 未找到兩個主要區域") +# return None, None + +# region1_mask = (labeled_array == sorted_regions[0][0]) +# region2_mask = (labeled_array == sorted_regions[1][0]) + +# def get_centroid(mask): +# indices = np.where(mask) +# return np.mean(indices[2]) + +# centroid1 = get_centroid(region1_mask) +# centroid2 = get_centroid(region2_mask) + +# if centroid1 > centroid2: +# right = region1_mask +# left = region2_mask +# else: +# right = region2_mask +# left = region1_mask + +# return right.astype(np.uint8), left.astype(np.uint8) + +# def evaluate_case(gt_path, pred_path, labels): +# """評估單個案例""" +# print(f"\n測試文件: {os.path.basename(pred_path)}") + +# start_time = time.time() +# gt_img = load_nifti(gt_path) +# pred_img = load_nifti(pred_path) +# load_time = time.time() - start_time + +# print(f"圖像大小: {gt_img.shape}") +# print(f"加載時間: {load_time:.2f}秒") + +# results = {} +# total_time = 0 + +# # 處理眼睛 +# start_time = time.time() +# combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) +# combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + +# combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) +# closing_filter = sitk.BinaryMorphologicalClosingImageFilter() +# closing_filter.SetKernelRadius(2) +# processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + +# eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) +# eye_time = time.time() - start_time +# total_time += eye_time +# results['combined_eye'] = eye_dice +# print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + +# right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") +# right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + +# # 處理視神經 +# start_time = time.time() +# combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) +# combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + +# combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) +# processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + +# nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) +# nerve_time = time.time() - start_time +# total_time += nerve_time +# results['combined_nerve'] = nerve_dice +# print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + +# right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") +# right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + +# for label in labels: +# start_time = time.time() + +# if label == 1: # Brainstem +# gt_binary = (gt_img == label).astype(np.uint8) +# pred_binary = (pred_img == label).astype(np.uint8) +# elif label == 2 and right_pred_eye is not None: # Right Eye +# gt_binary = right_gt_eye +# pred_binary = right_pred_eye +# elif label == 3 and left_pred_eye is not None: # Left Eye +# gt_binary = left_gt_eye +# pred_binary = left_pred_eye +# elif label == 4: # Optic Chiasm +# gt_binary = (gt_img == label).astype(np.uint8) +# pred_binary = (pred_img == label).astype(np.uint8) +# elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve +# gt_binary = right_gt_nerve +# pred_binary = right_pred_nerve +# elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve +# gt_binary = left_gt_nerve +# pred_binary = left_pred_nerve +# else: +# continue + +# try: +# dice = compute_dice(pred_binary, gt_binary) +# compute_time = time.time() - start_time +# total_time += compute_time +# results[label] = dice +# print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") +# except Exception as e: +# print(f"處理標籤 {label} 時出錯:{str(e)}") +# results[label] = np.nan +# continue + +# print(f"單個案例總計算時間: {total_time:.2f}秒") +# return results + +# def evaluate_validation_fold(gt_base_folder, validation_folder, labels): +# """評估單個fold的驗證集""" +# fold_results = {label: [] for label in labels} +# fold_results['combined_eye'] = [] +# fold_results['combined_nerve'] = [] + +# pred_files = sorted([f for f in os.listdir(validation_folder) if f.endswith('.nii.gz')]) + +# for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(validation_folder)}"): +# patient_id = pred_file.split('.nii.gz')[0] +# gt_path = os.path.join(gt_base_folder, f"{patient_id}.nii.gz") +# pred_path = os.path.join(validation_folder, pred_file) + +# if not os.path.exists(gt_path): +# print(f"找不到對應的真實標籤文件:{gt_path}") +# continue + +# case_results = evaluate_case(gt_path, pred_path, labels) + +# for key in case_results: +# if not np.isnan(case_results[key]): +# if key in fold_results: +# fold_results[key].append(case_results[key]) + +# return fold_results + +# def evaluate_model(gt_folder, model_path, model_name, labels, organ_names, output_dir): +# """評估模型所有fold的驗證集""" +# print(f"\n開始評估模型:{model_name}") +# all_results = {label: [] for label in labels} +# all_results['combined_eye'] = [] +# all_results['combined_nerve'] = [] +# fold_results = {} + +# for fold in range(5): +# validation_folder = os.path.join(model_path, f"fold_{fold}", "validation") + +# if not os.path.exists(validation_folder): +# print(f"找不到fold_{fold}的驗證資料夾:{validation_folder}") +# continue + +# print(f"\n處理 {model_name} - Fold {fold} 驗證集") +# results = evaluate_validation_fold(gt_folder, validation_folder, labels) +# fold_results[f"fold_{fold}"] = results + +# for key in results: +# all_results[key].extend(results[key]) + +# summary = {} +# for key in all_results: +# if all_results[key]: +# mean_dice = np.mean(all_results[key]) +# std_dice = np.std(all_results[key]) +# summary[key] = { +# 'mean_dice': float(mean_dice), +# 'std_dice': float(std_dice), +# 'n_samples': len(all_results[key]) +# } +# else: +# summary[key] = { +# 'mean_dice': np.nan, +# 'std_dice': np.nan, +# 'n_samples': 0 +# } + +# model_results = { +# 'summary': summary, +# 'fold_results': fold_results +# } + +# output_path = os.path.join(output_dir, 'CT', 'Dice', 'validation') +# os.makedirs(output_path, exist_ok=True) + +# filename = os.path.join(output_path, f'dice_results_{model_name}.json') +# with open(filename, 'w') as f: +# json.dump(model_results, f, indent=4) +# print(f"\n{model_name}驗證集結果已保存到 {filename}") + +# print(f"\n{model_name} 模型的驗證集 Dice 結果(所有fold的平均值):") + +# for label in labels: +# if label in summary: +# mean = summary[label]['mean_dice'] +# std = summary[label]['std_dice'] +# n = summary[label]['n_samples'] +# organ_name = organ_names.get(label, f"Label_{label}") +# if not np.isnan(mean): +# print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") +# else: +# print(f"{organ_name}: 無法計算 (n={n})") + +# print("\n合併結構的結果:") +# for key in ['combined_eye', 'combined_nerve']: +# if key in summary: +# mean = summary[key]['mean_dice'] +# std = summary[key]['std_dice'] +# n = summary[key]['n_samples'] +# name = "合併眼睛" if key == 'combined_eye' else "合併視神經" +# if not np.isnan(mean): +# print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") +# else: +# print(f"{name}: 無法計算 (n={n})") + +# return summary + +# if __name__ == "__main__": +# base_dir = "/mnt/1248/onlylian" +# gt_folder = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset555/labelsTr") +# output_dir = base_dir + +# models = { +# '2D': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset555/nnUNetTrainer__nnUNetPlans__2d"), +# '3D_fullres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset555/nnUNetTrainer__nnUNetPlans__3d_fullres"), +# '3D_lowres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset555/nnUNetTrainer__nnUNetPlans__3d_lowres") +# } + +# labels = [1, 2, 3, 4, 5, 6] +# organ_names = { +# 1: "Brainstem", +# 2: "Right_Eye", +# 3: "Left_Eye", +# 4: "Optic_Chiasm", +# 5: "Right_Optic_Nerve", +# 6: "Left_Optic_Nerve" +# } + +# print("可用的模型:") +# for i, model_name in enumerate(models.keys(), 1): +# print(f"{i}. {model_name}") + +# choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") +# try: +# model_index = int(choice) - 1 +# model_names = list(models.keys()) +# selected_model = model_names[model_index] +# evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names, output_dir) +# except (ValueError, IndexError): +# print("無效的選擇!請輸入1-3之間的數字。") + +import json +import os +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import time +import pandas as pd + +def load_nifti(file_path): + """加載 NIfTI 文件""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """計算Dice係數""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """分離左右結構""" + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: + print(f"警告:{structure_name} 未找到足夠的連通區域") + return None, None + + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"警告:{structure_name} 未找到兩個主要區域") + return None, None + + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def collect_image_stats(gt_path): + """收集圖像大小信息""" + gt_img = load_nifti(gt_path) + return gt_img.shape + +def evaluate_case(gt_path, pred_path, labels): + """評估單個案例""" + print(f"\n測試文件: {os.path.basename(pred_path)}") + + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"圖像大小: {gt_img.shape}") + print(f"加載時間: {load_time:.2f}秒") + + results = {} + total_time = 0 + + # 處理眼睛 + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + + # 處理視神經 + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") + except Exception as e: + print(f"處理標籤 {label} 時出錯:{str(e)}") + results[label] = np.nan + continue + + print(f"單個案例總計算時間: {total_time:.2f}秒") + return results + +def evaluate_validation_fold(gt_base_folder, validation_folder, labels): + """評估單個fold的驗證集""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + image_sizes = [] + + pred_files = sorted([f for f in os.listdir(validation_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(validation_folder)}"): + patient_id = pred_file.split('.nii.gz')[0] + gt_path = os.path.join(gt_base_folder, f"{patient_id}.nii.gz") + pred_path = os.path.join(validation_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"找不到對應的真實標籤文件:{gt_path}") + continue + + # 收集圖像大小信息 + img_size = collect_image_stats(gt_path) + image_sizes.append({ + 'patient_id': patient_id, + 'depth': img_size[0], + 'height': img_size[1], + 'width': img_size[2] + }) + + case_results = evaluate_case(gt_path, pred_path, labels) + + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results, image_sizes + +def evaluate_model(gt_folder, model_path, model_name, labels, organ_names, output_dir): + """評估模型所有fold的驗證集""" + print(f"\n開始評估模型:{model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + all_image_sizes = [] + + for fold in range(5): + validation_folder = os.path.join(model_path, f"fold_{fold}", "validation") + + if not os.path.exists(validation_folder): + print(f"找不到fold_{fold}的驗證資料夾:{validation_folder}") + continue + + print(f"\n處理 {model_name} - Fold {fold} 驗證集") + results, image_sizes = evaluate_validation_fold(gt_folder, validation_folder, labels) + fold_results[f"fold_{fold}"] = results + all_image_sizes.extend(image_sizes) + + for key in results: + all_results[key].extend(results[key]) + + # 保存圖像大小統計信息 + df = pd.DataFrame(all_image_sizes) + stats_output_path = os.path.join(output_dir, 'CT', 'Stats') + os.makedirs(stats_output_path, exist_ok=True) + + # 保存為Excel文件 + excel_path = os.path.join(stats_output_path, f'image_sizes_{model_name}.xlsx') + df.to_excel(excel_path, index=False) + print(f"\n圖像大小統計已保存到 {excel_path}") + + # 同時保存為txt文件 + txt_path = os.path.join(stats_output_path, f'image_sizes_{model_name}.txt') + with open(txt_path, 'w') as f: + f.write("Patient ID, Depth, Height, Width\n") + for size in all_image_sizes: + f.write(f"{size['patient_id']}, {size['depth']}, {size['height']}, {size['width']}\n") + print(f"圖像大小統計已保存到 {txt_path}") + + # 計算並打印統計摘要 + print("\n圖像大小統計摘要:") + print(f"總樣本數: {len(df)}") + print("\n尺寸統計 (depth, height, width):") + print(f"最小值: ({df['depth'].min()}, {df['height'].min()}, {df['width'].min()})") + print(f"最大值: ({df['depth'].max()}, {df['height'].max()}, {df['width'].max()})") + print(f"平均值: ({df['depth'].mean():.1f}, {df['height'].mean():.1f}, {df['width'].mean():.1f})") + print(f"中位數: ({df['depth'].median()}, {df['height'].median()}, {df['width'].median()})") + + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + output_path = os.path.join(output_dir, 'CT', 'Dice', 'validation') + os.makedirs(output_path, exist_ok=True) + + filename = os.path.join(output_path, f'dice_results_{model_name}.json') + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\n{model_name}驗證集結果已保存到 {filename}") + + print(f"\n{model_name} 模型的驗證集 Dice 結果(所有fold的平均值):") + + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: 無法計算 (n={n})") + + print("\n合併結構的結果:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "合併眼睛" if key == 'combined_eye' else "合併視神經" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: 無法計算 (n={n})") + + return summary + +if __name__ == "__main__": + base_dir = "/mnt/1248/onlylian" + gt_folder = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset555/labelsTr") + output_dir = base_dir + + models = { + '2D': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset555/nnUNetTrainer__nnUNetPlans__2d"), + '3D_fullres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset555/nnUNetTrainer__nnUNetPlans__3d_fullres"), + '3D_lowres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset555/nnUNetTrainer__nnUNetPlans__3d_lowres") + } + + labels = [1, 2, 3, 4, 5, 6] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + + print("可用的模型:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names, output_dir) + except (ValueError, IndexError): + print("無效的選擇!請輸入1-3之間的數字。") diff --git a/CT/visualize_medical_compare.py b/CT/visualize_medical_compare.py new file mode 100644 index 0000000..f270493 --- /dev/null +++ b/CT/visualize_medical_compare.py @@ -0,0 +1,141 @@ +import nibabel as nib +import matplotlib.pyplot as plt +import numpy as np +import os + +def load_nifti(file_path): + """載入NIfTI檔案""" + return nib.load(file_path).get_fdata() + +def create_colored_mask(data): + """創建彩色遮罩""" + color_dict = { + 1: [144/255, 238/255, 144/255], # Brainstem - 綠色 + 2: [255/255, 218/255, 150/255], # Right_Eye - 淺黃色 + 3: [205/255, 170/255, 125/255], # Left_Eye - 棕色 + 4: [135/255, 206/255, 235/255], # Optic_Chiasm - 藍色 + 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - 亮珊瑚紅 + 6: [255/255, 160/255, 122/255] # Left_Optic_Nerve - 淺珊瑚紅 + } + + mask = np.zeros((*data.shape, 3)) + + for label, color in color_dict.items(): + mask_3d = np.dstack([data == label] * 3) + mask = np.where(mask_3d, color, mask) + + return mask + +def get_brain_bounds(image): + """獲取腦部區域的邊界""" + mask = image > np.percentile(image, 1) + rows = np.any(mask, axis=1) + cols = np.any(mask, axis=0) + rmin, rmax = np.where(rows)[0][[0, -1]] + cmin, cmax = np.where(cols)[0][[0, -1]] + + center_r = (rmin + rmax) // 2 + center_c = (cmin + cmax) // 2 + + # 減小裁剪半徑以放大顯示區域 + radius = int(max(rmax - rmin, cmax - cmin) * 0.55) + + # 調整邊距 + margin = int(radius * 0.15) + + # 根據中心點和半徑設定新的邊界 + rmin = max(center_r - radius - margin, 0) + rmax = min(center_r + radius + margin, image.shape[0]) + cmin = max(center_c - radius - margin, 0) + cmax = min(center_c + radius + margin, image.shape[1]) + + return rmin, rmax, cmin, cmax + +def check_organs_in_slice(slice_data): + """檢查切片中存在的器官標籤""" + unique_labels = set(np.unique(slice_data)) + if 0 in unique_labels: + unique_labels.remove(0) + organ_dict = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + return {organ_dict[label] for label in unique_labels if label in organ_dict} + +def save_slice(image, ground_truth, prediction, slice_num, save_path): + """保存指定切片的比較圖""" + plt.clf() + fig, axes = plt.subplots(1, 2, figsize=(25, 11)) + plt.subplots_adjust(wspace=0.01) + + img_slice = np.rot90(image[:, :, slice_num]) + rmin, rmax, cmin, cmax = get_brain_bounds(img_slice) + + # Ground Truth + gt_slice = np.rot90(ground_truth[:, :, slice_num]) + gt_mask = create_colored_mask(gt_slice) + + axes[0].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray') + axes[0].imshow(gt_mask[rmin:rmax, cmin:cmax], alpha=0.5) + axes[0].set_title('Ground Truth', fontsize=32, pad=20, weight='bold') + axes[0].axis('off') + + # Prediction + pred_slice = np.rot90(prediction[:, :, slice_num]) + pred_mask = create_colored_mask(pred_slice) + + axes[1].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray') + axes[1].imshow(pred_mask[rmin:rmax, cmin:cmax], alpha=0.5) + axes[1].set_title('Prediction', fontsize=32, pad=20, weight='bold') + axes[1].axis('off') + + plt.savefig(save_path, bbox_inches='tight', dpi=300, pad_inches=0.05) + plt.close() + print(f"已保存切片 {slice_num} 至: {save_path}") + +if __name__ == "__main__": + # 載入影像檔案 + image_path = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset555/imagesTs/CT_081_0000.nii.gz' + gt_path = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset555/labelsTs/CT_081.nii.gz' + pred_path = '/mnt/1248/onlylian/CT/output_predictions/2d/fold_3/CT_081.nii.gz' + + # 設定保存路徑 + save_dir = '/mnt/1248/onlylian/CT' + + # 讀取影像 + image = load_nifti(image_path) + ground_truth = load_nifti(gt_path) + prediction = load_nifti(pred_path) + + # 檢查每個切片包含的器官 + total_slices = image.shape[2] + for slice_num in range(total_slices): + gt_slice = ground_truth[:, :, slice_num] + pred_slice = prediction[:, :, slice_num] + + # 獲取器官信息 + gt_organs = check_organs_in_slice(gt_slice) + pred_organs = check_organs_in_slice(pred_slice) + + if gt_organs or pred_organs: + print(f"\n切片 {slice_num}:") + print(f"Ground Truth 包含: {', '.join(sorted(gt_organs))}") + print(f"Prediction 包含: {', '.join(sorted(pred_organs))}") + + # 讓用戶選擇要保存的切片 + while True: + try: + slice_num = int(input("\n請輸入要保存的切片編號 (-1 退出): ")) + if slice_num == -1: + break + if 0 <= slice_num < total_slices: + save_path = os.path.join(save_dir, f'comparison_CT_057.png') + save_slice(image, ground_truth, prediction, slice_num, save_path) + else: + print(f"切片編號必須在 0 到 {total_slices-1} 之間") + except ValueError: + print("請輸入有效的數字") \ No newline at end of file diff --git a/CTC/Dice/test/compare_test_dice_scores.py b/CTC/Dice/test/compare_test_dice_scores.py new file mode 100644 index 0000000..c209181 --- /dev/null +++ b/CTC/Dice/test/compare_test_dice_scores.py @@ -0,0 +1,287 @@ +# import json +# import os +# import numpy as np +# import matplotlib.pyplot as plt +# from typing import Dict, Tuple + +# # 設置中文字體支持 +# plt.rcParams['font.sans-serif'] = ['SimHei'] +# plt.rcParams['axes.unicode_minus'] = False + +# def load_data(file_path: str) -> Dict: +# """讀取JSON數據文件""" +# try: +# with open(file_path, 'r') as file: +# return json.load(file) +# except FileNotFoundError: +# print(f"文件未找到: {file_path}") +# return {} +# except json.JSONDecodeError: +# print(f"JSON解析錯誤: {file_path}") +# return {} + +# def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: +# """從數據中提取特定器官的分數""" +# if "summary" not in data or organ_id not in data["summary"]: +# return (0, 0) + +# scores = data["summary"][organ_id] +# if model == '3D_fullres': +# mean_dice = scores.get("mean_dice", 0) +# std_dice = scores.get("std_dice", 0) +# else: +# mean_dice = scores.get("mean_dice", scores.get("mean", 0)) +# std_dice = scores.get("std_dice", scores.get("std", 0)) +# return (mean_dice, std_dice) + +# def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): +# """創建和保存圖表""" +# plt.figure(figsize=(16, 5)) # Reduced figure size +# plt.title('CT_CTC_T1_OAR', fontsize=12, pad=10) # Reduced title size and padding +# plt.grid(True, axis='y', linestyle='--', alpha=0.7) + +# x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups +# width = 0.35 # Reduced bar width + +# colors = { +# '2D': 'royalblue', +# '3D_fullres': 'limegreen', +# '3D_lowres': 'orange' +# } + +# positions = { +# '2D': x - width, +# '3D_fullres': x, +# '3D_lowres': x + width +# } + +# for model, averages in model_averages.items(): +# bars = plt.bar(positions[model], +# [averages[organ] * 100 for organ in organs.keys()], +# width, +# label=model, +# color=colors[model]) + +# # 調整數值標籤位置和大小 +# for j, bar in enumerate(bars): +# height = bar.get_height() +# organ = list(organs.keys())[j] +# std = model_stds[model][organ] * 100 +# plt.text(bar.get_x() + bar.get_width()/2., height, +# f'{height:.1f}±{std:.1f}%', +# ha='center', +# va='bottom', +# fontsize=6, # Reduced font size +# rotation=0) # Optional: rotate text if still crowded + +# plt.ylim(0, 100) +# plt.ylabel('DC (%)', fontsize=10) +# plt.xlabel('OAR', fontsize=10) +# plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) +# plt.legend(loc='upper right', fontsize=8) +# plt.tight_layout() + +# os.makedirs(os.path.dirname(output_path), exist_ok=True) +# plt.savefig(output_path, dpi=300, bbox_inches='tight') +# plt.close() + +# def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): +# """打印數值結果""" +# print("\n平均 Dice 分數 (百分比):") +# for model in model_averages.keys(): +# print(f"\n{model}:") +# for organ in organs.keys(): +# avg = model_averages[model][organ] * 100 +# std = model_stds[model][organ] * 100 +# print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +# def main(): +# # 定義路徑和器官映射 +# base_path = '/mnt/1248/onlylian/CT_CTC_T1_OAR/Dice/test' +# paths = { +# '2D': os.path.join(base_path, 'CT_CTC_T1_OAR_test_dice_results_2D.json'), +# '3D_fullres': os.path.join(base_path, 'CT_CTC_T1_OAR_test_dice_results_3D_fullres.json'), +# '3D_lowres': os.path.join(base_path, 'CT_CTC_T1_OAR_test_dice_results_3D_lowres.json') +# } + +# organs = { +# "1": "Brainstem", +# "2": "Right_Eye", +# "3": "Left_Eye", +# "4": "Optic_Chiasm", +# "5": "Right_Optic_Nerve", +# "6": "Left_Optic_Nerve" +# } + +# # 初始化數據存儲 +# model_dice_scores = { +# model: {organ: None for organ in organs.keys()} +# for model in paths.keys() +# } + +# # 讀取和處理數據 +# for model, path in paths.items(): +# data = load_data(path) +# for organ_id in organs.keys(): +# model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + +# # 計算平均值和標準差 +# model_averages = {} +# model_stds = {} +# for model, scores in model_dice_scores.items(): +# model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} +# model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + +# # 創建圖表並保存 +# output_path = os.path.join(base_path, 'CT_CTC_T1_OAR_test_dice_scores_comparison.png') +# create_plot(model_averages, model_stds, organs, output_path) + +# # 打印結果 +# print_results(model_averages, model_stds, organs) + +# if __name__ == "__main__": +# main() + +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# 設置中文字體支持 +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """讀取JSON數據文件""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"文件未找到: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON解析錯誤: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """從數據中提取特定器官的分數""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """創建和保存圖表""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('CTC', fontsize=12, pad=10) # Changed title + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # 調整數值標籤位置和大小 + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6, # Reduced font size + rotation=0) # Optional: rotate text if still crowded + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """打印數值結果""" + print("\n平均 Dice 分數 (百分比):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # 定義路徑和器官映射 + base_path = '/mnt/1248/onlylian/CTC/Dice/test' + paths = { + '2D': os.path.join(base_path, 'CTC_test_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'CTC_test_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'CTC_test_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve" + } + + # 初始化數據存儲 + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # 讀取和處理數據 + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # 計算平均值和標準差 + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # 創建圖表並保存 + output_path = os.path.join(base_path, 'CTC_test_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # 打印結果 + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/CTC/Dice/test/compute_test_dice.py b/CTC/Dice/test/compute_test_dice.py new file mode 100644 index 0000000..b103ac2 --- /dev/null +++ b/CTC/Dice/test/compute_test_dice.py @@ -0,0 +1,306 @@ +import json +import os +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import time + +def load_nifti(file_path): + """加載 NIfTI 文件""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """計算Dice係數""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """分離左右結構""" + # 使用SimpleITK的ConnectedComponent + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + # 計算連通區域數量和大小 + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: # 背景算一個,所以至少需要3個 + print(f"警告:{structure_name} 未找到足夠的連通區域") + return None, None + + # 排除背景(標籤0)並按大小排序 + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"警告:{structure_name} 未找到兩個主要區域") + return None, None + + # 獲取兩個最大區域 + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + # 計算質心 + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) # 使用x軸(第三維度)的平均值 + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + # 根據x軸位置確定左右 + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """評估單個案例""" + print(f"\n測試文件: {os.path.basename(pred_path)}") + + # 加載圖像 + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"圖像大小: {gt_img.shape}") + print(f"加載時間: {load_time:.2f}秒") + + results = {} + total_time = 0 + + # 處理眼睛 + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + # 形態學處理眼睛 + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + # 計算眼睛的Dice + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + + # 分離眼睛 + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + + # 處理視神經 + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + # 形態學處理視神經 + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + # 計算視神經的Dice + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + + # 分離視神經 + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + + # 計算各個標籤的Dice + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") + except Exception as e: + print(f"處理標籤 {label} 時出錯:{str(e)}") + results[label] = np.nan + continue + + print(f"單個案例總計算時間: {total_time:.2f}秒") + return results + +def evaluate_fold(gt_folder, pred_folder, labels): + """評估單個fold的所有案例""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(pred_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(pred_folder)}"): + gt_path = os.path.join(gt_folder, pred_file) + pred_path = os.path.join(pred_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"找不到對應的真實標籤文件:{gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + + # 收集結果 + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, base_pred_folder, model_name, labels, organ_names): + """評估單個模型的所有fold""" + print(f"\n開始評估模型:{model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + # 處理 fold_0 到 fold_4 + for fold in range(5): + print(f"\n處理 {model_name} - Fold {fold}") + pred_folder = os.path.join(base_pred_folder, f"fold_{fold}") + if not os.path.exists(pred_folder): + print(f"找不到預測文件夾:{pred_folder}") + continue + + results = evaluate_fold(gt_folder, pred_folder, labels) + fold_results[f"fold_{fold}"] = results + + # 合併結果 + for key in results: + all_results[key].extend(results[key]) + + # 計算總平均值和標準差 + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + # 保存結果到指定路径 + save_dir = "/mnt/1248/onlylian/CTC/Dice/test" + os.makedirs(save_dir, exist_ok=True) + filename = os.path.join(save_dir, f'CTC_test_dice_results_{model_name}.json') + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\n{model_name}結果已保存到 {filename}") + + # 打印結果 + print(f"\n{model_name} 模型的 Dice 結果(所有fold的平均值):") + + # 打印器官結果 + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: 無法計算 (n={n})") + + # 打印合併結果 + print("\n合併結構的結果:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "合併眼睛" if key == 'combined_eye' else "合併視神經" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: 無法計算 (n={n})") + + return summary + +if __name__ == "__main__": + # 設置路徑和標籤 + gt_folder = "/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset666/labelsTs" + models = { + '2D': '/mnt/1248/onlylian/CTC/output_predictions/2d', + '3D_fullres': '/mnt/1248/onlylian/CTC/output_predictions/3d_fullres', + '3D_lowres': '/mnt/1248/onlylian/CTC/output_predictions/3d_lowres' + } + labels = [1, 2, 3, 4, 5, 6] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + + # 讓用戶選擇要運行哪個模型 + print("可用的模型:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names) + except (ValueError, IndexError): + print("無效的選擇!請輸入1-3之間的數字。") \ No newline at end of file diff --git a/CTC/Dice/validation/compare_validation_dice_scores.py b/CTC/Dice/validation/compare_validation_dice_scores.py new file mode 100644 index 0000000..08614b9 --- /dev/null +++ b/CTC/Dice/validation/compare_validation_dice_scores.py @@ -0,0 +1,142 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# 設置中文字體支持 +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """讀取JSON數據文件""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"文件未找到: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON解析錯誤: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """從數據中提取特定器官的分數""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """創建和保存圖表""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('CTC', fontsize=12, pad=10) # Reduced title size and padding + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # 添加數值標籤 + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height + 0.5, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6) # Reduced font size + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """打印數值結果""" + print("\n平均 Dice 分數 (百分比):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # 定義路徑和器官映射 + base_path = '/mnt/1248/onlylian/CTC/Dice/validation' + paths = { + '2D': os.path.join(base_path, 'CTC_validation_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'CTC_validation_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'CTC_validation_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve" + } + + # 初始化數據存儲 + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # 讀取和處理數據 + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # 計算平均值和標準差 + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # 創建圖表並保存 + output_path = os.path.join(base_path, 'CTC_validation_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # 打印結果 + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/CTC/Dice/validation/compute_validation_dice.py b/CTC/Dice/validation/compute_validation_dice.py new file mode 100644 index 0000000..351bd8b --- /dev/null +++ b/CTC/Dice/validation/compute_validation_dice.py @@ -0,0 +1,288 @@ +import os +import time +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import json + +def load_nifti(file_path): + """加載 NIfTI 文件""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """計算Dice係數""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """分離左右結構""" + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: + print(f"警告:{structure_name} 未找到足夠的連通區域") + return None, None + + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"警告:{structure_name} 未找到兩個主要區域") + return None, None + + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """評估單個案例""" + print(f"\n測試文件: {os.path.basename(pred_path)}") + + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"圖像大小: {gt_img.shape}") + print(f"加載時間: {load_time:.2f}秒") + + results = {} + total_time = 0 + + # 處理眼睛 + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + + # 處理視神經 + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") + except Exception as e: + print(f"處理標籤 {label} 時出錯:{str(e)}") + results[label] = np.nan + continue + + print(f"單個案例總計算時間: {total_time:.2f}秒") + return results + +def evaluate_validation_fold(gt_base_folder, validation_folder, labels): + """評估單個fold的驗證集""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(validation_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(validation_folder)}"): + patient_id = pred_file.split('.nii.gz')[0] + gt_path = os.path.join(gt_base_folder, f"{patient_id}.nii.gz") + pred_path = os.path.join(validation_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"找不到對應的真實標籤文件:{gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, model_path, model_name, labels, organ_names, output_dir): + """評估模型所有fold的驗證集""" + print(f"\n開始評估模型:{model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + for fold in range(5): + validation_folder = os.path.join(model_path, f"fold_{fold}", "validation") + + if not os.path.exists(validation_folder): + print(f"找不到fold_{fold}的驗證資料夾:{validation_folder}") + continue + + print(f"\n處理 {model_name} - Fold {fold} 驗證集") + results = evaluate_validation_fold(gt_folder, validation_folder, labels) + fold_results[f"fold_{fold}"] = results + + for key in results: + all_results[key].extend(results[key]) + + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + output_path = os.path.join(output_dir, 'CTC', 'Dice', 'validation') + os.makedirs(output_path, exist_ok=True) + + filename = os.path.join(output_path, f'dice_results_{model_name}.json') + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\n{model_name}驗證集結果已保存到 {filename}") + + print(f"\n{model_name} 模型的驗證集 Dice 結果(所有fold的平均值):") + + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: 無法計算 (n={n})") + + print("\n合併結構的結果:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "合併眼睛" if key == 'combined_eye' else "合併視神經" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: 無法計算 (n={n})") + + return summary + +if __name__ == "__main__": + base_dir = "/mnt/1248/onlylian" + gt_folder = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset666/labelsTr") + output_dir = base_dir + + models = { + '2D': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset666/nnUNetTrainer__nnUNetPlans__2d"), + '3D_fullres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset666/nnUNetTrainer__nnUNetPlans__3d_fullres"), + '3D_lowres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset666/nnUNetTrainer__nnUNetPlans__3d_lowres") + } + + labels = [1, 2, 3, 4, 5, 6] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + + print("可用的模型:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names, output_dir) + except (ValueError, IndexError): + print("無效的選擇!請輸入1-3之間的數字。") \ No newline at end of file diff --git a/CTC/visualize_medical_compare_chiasm.py b/CTC/visualize_medical_compare_chiasm.py new file mode 100644 index 0000000..0834e2e --- /dev/null +++ b/CTC/visualize_medical_compare_chiasm.py @@ -0,0 +1,180 @@ +import nibabel as nib +import matplotlib.pyplot as plt +import numpy as np +import os + +def load_nifti(file_path): + """載入NIfTI檔案""" + return nib.load(file_path).get_fdata() + +def create_colored_mask(data): + """創建彩色遮罩""" + color_dict = { + 1: [144/255, 238/255, 144/255], # Brainstem - 綠色 + 2: [255/255, 218/255, 150/255], # Right_Eye - 淺黃色 + 3: [205/255, 170/255, 125/255], # Left_Eye - 棕色 + 4: [135/255, 206/255, 235/255], # Optic_Chiasm - 藍色 + 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - 亮珊瑚紅 + 6: [255/255, 160/255, 122/255] # Left_Optic_Nerve - 淺珊瑚紅 + } + + mask = np.zeros((*data.shape, 4)) + + for label, color in color_dict.items(): + organ_mask = (data == label) + if np.any(organ_mask): + mask[organ_mask] = [*color, 1.0] + + return mask + +def get_brain_bounds(image): + """獲取腦部區域的邊界,並調整顯示位置以避開底部基架""" + # 基本閾值處理 + mask = image > np.percentile(image, 1) + + # 獲取非零區域的邊界 + rows = np.any(mask, axis=1) + cols = np.any(mask, axis=0) + + # 確保有足夠的點 + if not np.any(rows) or not np.any(cols): + return 0, image.shape[0], 0, image.shape[1] + + row_indices = np.where(rows)[0] + col_indices = np.where(cols)[0] + + # 獲取初始邊界 + rmin, rmax = row_indices[[0, -1]] + cmin, cmax = col_indices[[0, -1]] + + # 計算中心點,但略微上移以避開基架 + center_r = (rmin + rmax) // 2 - int(image.shape[0] * 0.15) # 上移15% + center_c = (cmin + cmax) // 2 + + # 計算顯示範圍 + height = image.shape[0] + width = image.shape[1] + display_size = int(min(height, width) * 0.7) # 使用70%的圖像大小 + + # 設置新的邊界,確保不會超出圖像範圍 + rmin = max(center_r - display_size//2, 0) + rmax = min(center_r + display_size//2, height - int(height * 0.2)) # 留出底部空間 + cmin = max(center_c - display_size//2, 0) + cmax = min(center_c + display_size//2, width) + + # 如果上邊界太小,適當下移整個顯示區域 + if rmin < height * 0.1: + shift = int(height * 0.1) - rmin + rmin += shift + rmax += shift + + return rmin, rmax, cmin, cmax + +def apply_brain_window(image, window_width=80, window_level=40): + """應用brain window設置""" + window_min = window_level - window_width/2 + window_max = window_level + window_width/2 + windowed = np.clip(image, window_min, window_max) + windowed = (windowed - window_min) / (window_max - window_min) + return windowed + +def check_organs_in_slice(slice_data): + """檢查切片中存在的器官標籤""" + unique_labels = set(np.unique(slice_data)) + if 0 in unique_labels: + unique_labels.remove(0) + organ_dict = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + return {organ_dict[label] for label in unique_labels if label in organ_dict} + +def save_slice(image, ground_truth, prediction, slice_num, save_path): + """保存指定切片的比較圖""" + plt.clf() + # Create figure with 1 row and 3 columns + fig, axes = plt.subplots(1, 3, figsize=(24, 8)) + plt.subplots_adjust(wspace=0.01) + + img_slice = np.rot90(image[:, :, slice_num]) + rmin, rmax, cmin, cmax = get_brain_bounds(img_slice) + + # 使用brain window設置 + img_slice = apply_brain_window(img_slice) + + # Original Image + axes[0].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray') + axes[0].set_title('Original Image', fontsize=32, pad=20, weight='bold') + axes[0].axis('off') + + # Ground Truth + gt_slice = np.rot90(ground_truth[:, :, slice_num]) + gt_mask = create_colored_mask(gt_slice) + + axes[1].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[1].imshow(gt_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[1].set_title('Ground Truth', fontsize=32, pad=20, weight='bold') + axes[1].axis('off') + + # Prediction + pred_slice = np.rot90(prediction[:, :, slice_num]) + pred_mask = create_colored_mask(pred_slice) + + axes[2].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[2].imshow(pred_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[2].set_title('Prediction', fontsize=32, pad=20, weight='bold') + axes[2].axis('off') + + plt.savefig(save_path, bbox_inches='tight', dpi=300, pad_inches=0.05) + plt.close() + print(f"已保存切片 {slice_num} 至: {save_path}") + +if __name__ == "__main__": + base_dir = "/mnt/1248/onlylian" + image_path = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset666/imagesTs/CTC_081_0000.nii.gz' + gt_path = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset666/labelsTs/CTC_081.nii.gz' + pred_path = '/mnt/1248/onlylian/CTC/output_predictions/3d_fullres/fold_4/CTC_081.nii.gz' + save_dir = os.path.join(base_dir, "CTC") + + os.makedirs(save_dir, exist_ok=True) + + print("正在載入影像...") + image = load_nifti(image_path) + ground_truth = load_nifti(gt_path) + prediction = load_nifti(pred_path) + print("影像載入完成") + + print("\n分析切片中的器官...") + total_slices = image.shape[2] + for slice_num in range(total_slices): + gt_slice = ground_truth[:, :, slice_num] + pred_slice = prediction[:, :, slice_num] + + gt_organs = check_organs_in_slice(gt_slice) + pred_organs = check_organs_in_slice(pred_slice) + + if gt_organs or pred_organs: + print(f"\n切片 {slice_num}:") + print(f"Ground Truth 包含: {', '.join(sorted(gt_organs))}") + print(f"Prediction 包含: {', '.join(sorted(pred_organs))}") + + print("\n開始切片選擇和保存過程...") + while True: + try: + slice_num = int(input("\n請輸入要保存的切片編號 (-1 退出): ")) + if slice_num == -1: + print("程序結束") + break + if 0 <= slice_num < total_slices: + save_path = os.path.join(save_dir, f'comparison_CTC_081_{slice_num:03d}.png') + save_slice(image, ground_truth, prediction, slice_num, save_path) + else: + print(f"切片編號必須在 0 到 {total_slices-1} 之間") + except ValueError: + print("請輸入有效的數字") + except Exception as e: + print(f"發生錯誤: {str(e)}") \ No newline at end of file diff --git a/CTC/visualize_medical_compare_eyes.py b/CTC/visualize_medical_compare_eyes.py new file mode 100644 index 0000000..101cd8c --- /dev/null +++ b/CTC/visualize_medical_compare_eyes.py @@ -0,0 +1,180 @@ +import nibabel as nib +import matplotlib.pyplot as plt +import numpy as np +import os + +def load_nifti(file_path): + """載入NIfTI檔案""" + return nib.load(file_path).get_fdata() + +def create_colored_mask(data): + """創建彩色遮罩""" + color_dict = { + 1: [144/255, 238/255, 144/255], # Brainstem - 綠色 + 2: [255/255, 218/255, 150/255], # Right_Eye - 淺黃色 + 3: [205/255, 170/255, 125/255], # Left_Eye - 棕色 + 4: [135/255, 206/255, 235/255], # Optic_Chiasm - 藍色 + 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - 亮珊瑚紅 + 6: [255/255, 160/255, 122/255] # Left_Optic_Nerve - 淺珊瑚紅 + } + + mask = np.zeros((*data.shape, 4)) + + for label, color in color_dict.items(): + organ_mask = (data == label) + if np.any(organ_mask): + mask[organ_mask] = [*color, 1.0] + + return mask + +def get_brain_bounds(image): + """獲取腦部區域的邊界,並調整顯示位置以避開底部基架""" + # 基本閾值處理 + mask = image > np.percentile(image, 1) + + # 獲取非零區域的邊界 + rows = np.any(mask, axis=1) + cols = np.any(mask, axis=0) + + # 確保有足夠的點 + if not np.any(rows) or not np.any(cols): + return 0, image.shape[0], 0, image.shape[1] + + row_indices = np.where(rows)[0] + col_indices = np.where(cols)[0] + + # 獲取初始邊界 + rmin, rmax = row_indices[[0, -1]] + cmin, cmax = col_indices[[0, -1]] + + # 計算中心點,但略微上移以避開基架 + center_r = (rmin + rmax) // 2 - int(image.shape[0] * 0.15) # 上移15% + center_c = (cmin + cmax) // 2 + + # 計算顯示範圍 + height = image.shape[0] + width = image.shape[1] + display_size = int(min(height, width) * 0.7) # 使用70%的圖像大小 + + # 設置新的邊界,確保不會超出圖像範圍 + rmin = max(center_r - display_size//2, 0) + rmax = min(center_r + display_size//2, height - int(height * 0.2)) # 留出底部空間 + cmin = max(center_c - display_size//2, 0) + cmax = min(center_c + display_size//2, width) + + # 如果上邊界太小,適當下移整個顯示區域 + if rmin < height * 0.1: + shift = int(height * 0.1) - rmin + rmin += shift + rmax += shift + + return rmin, rmax, cmin, cmax + +def apply_brain_window(image, window_width=80, window_level=40): + """應用brain window設置""" + window_min = window_level - window_width/2 + window_max = window_level + window_width/2 + windowed = np.clip(image, window_min, window_max) + windowed = (windowed - window_min) / (window_max - window_min) + return windowed + +def check_organs_in_slice(slice_data): + """檢查切片中存在的器官標籤""" + unique_labels = set(np.unique(slice_data)) + if 0 in unique_labels: + unique_labels.remove(0) + organ_dict = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + return {organ_dict[label] for label in unique_labels if label in organ_dict} + +def save_slice(image, ground_truth, prediction, slice_num, save_path): + """保存指定切片的比較圖""" + plt.clf() + # Create figure with 1 row and 3 columns + fig, axes = plt.subplots(1, 3, figsize=(24, 8)) + plt.subplots_adjust(wspace=0.01) + + img_slice = np.rot90(image[:, :, slice_num]) + rmin, rmax, cmin, cmax = get_brain_bounds(img_slice) + + # 使用brain window設置 + img_slice = apply_brain_window(img_slice) + + # Original Image + axes[0].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray') + axes[0].set_title('Original Image', fontsize=32, pad=20, weight='bold') + axes[0].axis('off') + + # Ground Truth + gt_slice = np.rot90(ground_truth[:, :, slice_num]) + gt_mask = create_colored_mask(gt_slice) + + axes[1].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[1].imshow(gt_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[1].set_title('Ground Truth', fontsize=32, pad=20, weight='bold') + axes[1].axis('off') + + # Prediction + pred_slice = np.rot90(prediction[:, :, slice_num]) + pred_mask = create_colored_mask(pred_slice) + + axes[2].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[2].imshow(pred_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[2].set_title('Prediction', fontsize=32, pad=20, weight='bold') + axes[2].axis('off') + + plt.savefig(save_path, bbox_inches='tight', dpi=300, pad_inches=0.05) + plt.close() + print(f"已保存切片 {slice_num} 至: {save_path}") + +if __name__ == "__main__": + base_dir = "/mnt/1248/onlylian" + image_path = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset666/imagesTs/CTC_001_0000.nii.gz' + gt_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset666/labelsTs/CTC_001.nii.gz") + pred_path = '/mnt/1248/onlylian/CTC/output_predictions/2d/fold_0/CTC_001.nii.gz' + save_dir = os.path.join(base_dir, "CTC") + + os.makedirs(save_dir, exist_ok=True) + + print("正在載入影像...") + image = load_nifti(image_path) + ground_truth = load_nifti(gt_path) + prediction = load_nifti(pred_path) + print("影像載入完成") + + print("\n分析切片中的器官...") + total_slices = image.shape[2] + for slice_num in range(total_slices): + gt_slice = ground_truth[:, :, slice_num] + pred_slice = prediction[:, :, slice_num] + + gt_organs = check_organs_in_slice(gt_slice) + pred_organs = check_organs_in_slice(pred_slice) + + if gt_organs or pred_organs: + print(f"\n切片 {slice_num}:") + print(f"Ground Truth 包含: {', '.join(sorted(gt_organs))}") + print(f"Prediction 包含: {', '.join(sorted(pred_organs))}") + + print("\n開始切片選擇和保存過程...") + while True: + try: + slice_num = int(input("\n請輸入要保存的切片編號 (-1 退出): ")) + if slice_num == -1: + print("程序結束") + break + if 0 <= slice_num < total_slices: + save_path = os.path.join(save_dir, f'comparison_CTC_001_{slice_num:03d}.png') + save_slice(image, ground_truth, prediction, slice_num, save_path) + else: + print(f"切片編號必須在 0 到 {total_slices-1} 之間") + except ValueError: + print("請輸入有效的數字") + except Exception as e: + print(f"發生錯誤: {str(e)}") \ No newline at end of file diff --git a/CT_CTC_T1_OAR/Dice/test/compare_test_dice_scores.py b/CT_CTC_T1_OAR/Dice/test/compare_test_dice_scores.py new file mode 100644 index 0000000..235deec --- /dev/null +++ b/CT_CTC_T1_OAR/Dice/test/compare_test_dice_scores.py @@ -0,0 +1,170 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple, List +import pandas as pd + +# 使用更通用的字體設置 +plt.rcParams['font.family'] = 'DejaVu Sans' +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """Load JSON data file""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"File not found: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON decoding error: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """Extract scores for a specific organ from the data""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_subplot(ax, model_averages: Dict, model_stds: Dict, organs: Dict, modality: str): + """Create a subplot for each modality""" + ax.set_title(modality, fontsize=12, pad=10) + ax.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 + width = 0.35 + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + ax.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + ax.set_ylim(0, 100) + ax.set_ylabel('DSC (%)', fontsize=10) + ax.set_xlabel('OAR', fontsize=10) + ax.set_xticks(x) + ax.set_xticklabels([organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + if modality == 'NC-CT': + ax.legend(loc='upper right', fontsize=8) + +def create_results_table(all_results: Dict[str, Dict], organs: Dict, output_path: str): + """Create and save results table""" + for model_type in ['2D', '3D_fullres', '3D_lowres']: + data = [] + for modality, results in all_results.items(): + for organ_id in organs.keys(): + mean, std = results[model_type][organ_id] + data.append({ + 'Modality': modality, + 'OAR': organs[organ_id], + 'DSC': f"{mean*100:.2f} ± {std*100:.2f}" + }) + + df = pd.DataFrame(data) + pivot_df = df.pivot(index='OAR', columns='Modality', values='DSC') + + filename = f"{output_path}_{model_type}_results.csv" + pivot_df.to_csv(filename) + +def process_modality(base_path: str, modality: str, organs: Dict) -> Tuple[Dict, Dict]: + """Process data for a single modality""" + paths = { + '2D': os.path.join(base_path, f'{modality}_test_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, f'{modality}_test_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, f'{modality}_test_dice_results_3D_lowres.json') + } + + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + return model_averages, model_stds + +def main(): + base_dir = '/mnt/1248/onlylian' + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve" + } + + modalities = { + 'NC-CT': 'CT', + 'CE-CT': 'CTC', + 'T1W': 'T1', + 'CE-T1W': 'T1C', + 'T2W': 'T2', + 'T2-FLAIR': 'FLAIR', + 'CT/CE-T1W': 'CT_CTC_T1_OAR' + } + + all_results = {} + + # Process all modalities including CT_CTC_T1_OAR + fig, axes = plt.subplots(4, 2, figsize=(16, 24)) + axes = axes.ravel() + + for i, (display_name, folder_name) in enumerate(modalities.items()): + if display_name == 'CT/CE-T1W': + base_path = os.path.join(base_dir, folder_name, 'Dice/test') + else: + base_path = os.path.join(base_dir, folder_name, 'Dice/test') + + model_averages, model_stds = process_modality(base_path, folder_name, organs) + create_subplot(axes[i], model_averages, model_stds, organs, display_name) + + all_results[display_name] = { + model: {organ_id: (model_averages[model][organ_id], model_stds[model][organ_id]) + for organ_id in organs.keys()} + for model in model_averages.keys() + } + + plt.tight_layout() + plot_output_path = os.path.join(base_dir, 'multimodal_comparison.png') + plt.savefig(plot_output_path, dpi=300, bbox_inches='tight') + plt.close() + + # Save results table + table_output_path = os.path.join(base_dir, 'Dice_scores') + create_results_table(all_results, organs, output_path=table_output_path) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/CT_CTC_T1_OAR/Dice/test/compute_test_dice.py b/CT_CTC_T1_OAR/Dice/test/compute_test_dice.py new file mode 100644 index 0000000..a79a3d6 --- /dev/null +++ b/CT_CTC_T1_OAR/Dice/test/compute_test_dice.py @@ -0,0 +1,306 @@ +import os +import time +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import json + +def load_nifti(file_path): + """加載 NIfTI 文件""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """計算Dice係數""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """分離左右結構""" + # 使用SimpleITK的ConnectedComponent + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + # 計算連通區域數量和大小 + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: # 背景算一個,所以至少需要3個 + print(f"警告:{structure_name} 未找到足夠的連通區域") + return None, None + + # 排除背景(標籤0)並按大小排序 + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"警告:{structure_name} 未找到兩個主要區域") + return None, None + + # 獲取兩個最大區域 + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + # 計算質心 + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) # 使用x軸(第三維度)的平均值 + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + # 根據x軸位置確定左右 + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """評估單個案例""" + print(f"\n測試文件: {os.path.basename(pred_path)}") + + # 加載圖像 + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"圖像大小: {gt_img.shape}") + print(f"加載時間: {load_time:.2f}秒") + + results = {} + total_time = 0 + + # 處理眼睛 + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + # 形態學處理眼睛 + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + # 計算眼睛的Dice + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + + # 分離眼睛 + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + + # 處理視神經 + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + # 形態學處理視神經 + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + # 計算視神經的Dice + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + + # 分離視神經 + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + + # 計算各個標籤的Dice + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") + except Exception as e: + print(f"處理標籤 {label} 時出錯:{str(e)}") + results[label] = np.nan + continue + + print(f"單個案例總計算時間: {total_time:.2f}秒") + return results + +def evaluate_fold(gt_folder, pred_folder, labels): + """評估單個fold的所有案例""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(pred_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(pred_folder)}"): + gt_path = os.path.join(gt_folder, pred_file) + pred_path = os.path.join(pred_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"找不到對應的真實標籤文件:{gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + + # 收集結果 + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, base_pred_folder, model_name, labels, organ_names): + """評估單個模型的所有fold""" + print(f"\n開始評估模型:{model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + # 處理 fold_0 到 fold_4 + for fold in range(5): + print(f"\n處理 {model_name} - Fold {fold}") + pred_folder = os.path.join(base_pred_folder, f"fold_{fold}") + if not os.path.exists(pred_folder): + print(f"找不到預測文件夾:{pred_folder}") + continue + + results = evaluate_fold(gt_folder, pred_folder, labels) + fold_results[f"fold_{fold}"] = results + + # 合併結果 + for key in results: + all_results[key].extend(results[key]) + + # 計算總平均值和標準差 + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + # 保存結果到指定路径 + save_dir = "/mnt/1248/onlylian/CT_CTC_T1_OAR/Dice/test" + os.makedirs(save_dir, exist_ok=True) + filename = os.path.join(save_dir, f'CT_CTC_T1_OAR_test_dice_results_{model_name}.json') + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\n{model_name}結果已保存到 {filename}") + + # 打印結果 + print(f"\n{model_name} 模型的 Dice 結果(所有fold的平均值):") + + # 打印器官結果 + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: 無法計算 (n={n})") + + # 打印合併結果 + print("\n合併結構的結果:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "合併眼睛" if key == 'combined_eye' else "合併視神經" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: 無法計算 (n={n})") + + return summary + +if __name__ == "__main__": + # 設置路徑和標籤 + gt_folder = "/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset221_OAR/labelsTs" + models = { + '2D': '/mnt/1248/onlylian/CT_CTC_T1_OAR/output_predictions/2d', + '3D_fullres': '/mnt/1248/onlylian/CT_CTC_T1_OAR/output_predictions/3d_fullres', + '3D_lowres': '/mnt/1248/onlylian/CT_CTC_T1_OAR/output_predictions/3d_lowres' + } + labels = [1, 2, 3, 4, 5, 6] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + + # 讓用戶選擇要運行哪個模型 + print("可用的模型:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names) + except (ValueError, IndexError): + print("無效的選擇!請輸入1-3之間的數字。") \ No newline at end of file diff --git a/CT_CTC_T1_OAR/Dice/validation/compare_validation_dice_scores.py b/CT_CTC_T1_OAR/Dice/validation/compare_validation_dice_scores.py new file mode 100644 index 0000000..89c05ea --- /dev/null +++ b/CT_CTC_T1_OAR/Dice/validation/compare_validation_dice_scores.py @@ -0,0 +1,286 @@ +# import json +# import os +# import numpy as np +# import matplotlib.pyplot as plt +# from typing import Dict, Tuple + +# # 設置中文字體支持 +# plt.rcParams['font.sans-serif'] = ['SimHei'] +# plt.rcParams['axes.unicode_minus'] = False + +# def load_data(file_path: str) -> Dict: +# """讀取JSON數據文件""" +# try: +# with open(file_path, 'r') as file: +# return json.load(file) +# except FileNotFoundError: +# print(f"文件未找到: {file_path}") +# return {} +# except json.JSONDecodeError: +# print(f"JSON解析錯誤: {file_path}") +# return {} + +# def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: +# """從數據中提取特定器官的分數""" +# if "summary" not in data or organ_id not in data["summary"]: +# return (0, 0) + +# scores = data["summary"][organ_id] +# if model == '3D_fullres': +# mean_dice = scores.get("mean_dice", 0) +# std_dice = scores.get("std_dice", 0) +# else: +# mean_dice = scores.get("mean_dice", scores.get("mean", 0)) +# std_dice = scores.get("std_dice", scores.get("std", 0)) +# return (mean_dice, std_dice) + +# def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): +# """創建和保存圖表""" +# plt.figure(figsize=(18, 6)) +# plt.title('CT_CTC_T1_OAR', fontsize=14, pad=15) +# plt.grid(True, axis='y', linestyle='--', alpha=0.7) + +# x = np.arange(len(organs)) * 2.0 +# width = 0.5 + +# colors = { +# '2D': 'royalblue', +# '3D_fullres': 'limegreen', +# '3D_lowres': 'orange' +# } + +# positions = { +# '2D': x - width, +# '3D_fullres': x, +# '3D_lowres': x + width +# } + +# for model, averages in model_averages.items(): +# bars = plt.bar(positions[model], +# [averages[organ] * 100 for organ in organs.keys()], +# width, +# label=model, +# color=colors[model]) + +# # 添加數值標籤 +# for j, bar in enumerate(bars): +# height = bar.get_height() +# organ = list(organs.keys())[j] +# std = model_stds[model][organ] * 100 +# plt.text(bar.get_x() + bar.get_width()/2., height + 0.5, +# f'{height:.1f}±{std:.1f}%', +# ha='center', +# va='bottom', +# fontsize=8) + +# plt.ylim(0, 100) +# plt.ylabel('DC (%)', fontsize=12) +# plt.xlabel('OAR', fontsize=12) +# plt.xticks(x, [organs[k] for k in organs.keys()], rotation=45, ha='right') +# plt.legend(loc='upper right') +# plt.tight_layout() + +# # 確保輸出目錄存在 +# os.makedirs(os.path.dirname(output_path), exist_ok=True) +# plt.savefig(output_path, dpi=300, bbox_inches='tight') +# plt.close() + +# def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): +# """打印數值結果""" +# print("\n平均 Dice 分數 (百分比):") +# for model in model_averages.keys(): +# print(f"\n{model}:") +# for organ in organs.keys(): +# avg = model_averages[model][organ] * 100 +# std = model_stds[model][organ] * 100 +# print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +# def main(): +# # 定義路徑和器官映射 +# base_path = '/mnt/1248/onlylian/CT_CTC_T1_OAR/Dice/validation' +# paths = { +# '2D': os.path.join(base_path, 'CT_CTC_T1_OAR_validation_dice_results_2D.json'), +# '3D_fullres': os.path.join(base_path, 'CT_CTC_T1_OAR_validation_dice_results_3D_fullres.json'), +# '3D_lowres': os.path.join(base_path, 'CT_CTC_T1_OAR_validation_dice_results_3D_lowres.json') +# } + +# organs = { +# "1": "Brainstem", +# "2": "Right_Eye", +# "3": "Left_Eye", +# "4": "Optic_Chiasm", +# "5": "Right_Optic_Nerve", +# "6": "Left_Optic_Nerve" +# } + +# # 初始化數據存儲 +# model_dice_scores = { +# model: {organ: None for organ in organs.keys()} +# for model in paths.keys() +# } + +# # 讀取和處理數據 +# for model, path in paths.items(): +# data = load_data(path) +# for organ_id in organs.keys(): +# model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + +# # 計算平均值和標準差 +# model_averages = {} +# model_stds = {} +# for model, scores in model_dice_scores.items(): +# model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} +# model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + +# # 創建圖表並保存 +# output_path = os.path.join(base_path, 'CT_CTC_T1_OAR_validation_dice_scores_comparison.png') +# create_plot(model_averages, model_stds, organs, output_path) + +# # 打印結果 +# print_results(model_averages, model_stds, organs) + +# if __name__ == "__main__": +# main() + +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# 設置中文字體支持 +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """讀取JSON數據文件""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"文件未找到: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON解析錯誤: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """從數據中提取特定器官的分數""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """創建和保存圖表""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('CT_CTC_T1_OAR', fontsize=12, pad=10) # Reduced title size and padding + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # 添加數值標籤 + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height + 0.5, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6) # Reduced font size + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """打印數值結果""" + print("\n平均 Dice 分數 (百分比):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # 定義路徑和器官映射 + base_path = '/mnt/1248/onlylian/CT_CTC_T1_OAR/Dice/validation' + paths = { + '2D': os.path.join(base_path, 'CT_CTC_T1_OAR_validation_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'CT_CTC_T1_OAR_validation_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'CT_CTC_T1_OAR_validation_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve" + } + + # 初始化數據存儲 + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # 讀取和處理數據 + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # 計算平均值和標準差 + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # 創建圖表並保存 + output_path = os.path.join(base_path, 'CT_CTC_T1_OAR_validation_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # 打印結果 + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/CT_CTC_T1_OAR/Dice/validation/compute_validation_dice.py b/CT_CTC_T1_OAR/Dice/validation/compute_validation_dice.py new file mode 100644 index 0000000..81fa68c --- /dev/null +++ b/CT_CTC_T1_OAR/Dice/validation/compute_validation_dice.py @@ -0,0 +1,602 @@ +# import os +# import time +# import numpy as np +# import SimpleITK as sitk +# from tqdm import tqdm +# import json + +# def load_nifti(file_path): +# """加載 NIfTI 文件""" +# return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +# def compute_dice(pred, gt): +# """計算Dice係數""" +# intersection = np.sum(pred & gt) +# sum_pred = np.sum(pred) +# sum_gt = np.sum(gt) + +# if sum_pred == 0 and sum_gt == 0: +# return 1.0 +# elif sum_pred == 0 or sum_gt == 0: +# return 0.0 + +# return 2.0 * intersection / (sum_pred + sum_gt) + +# def separate_structures(combined_mask, structure_name=""): +# """分離左右結構""" +# sitk_image = sitk.GetImageFromArray(combined_mask) +# labeled_components = sitk.ConnectedComponent(sitk_image) +# labeled_array = sitk.GetArrayFromImage(labeled_components) + +# unique, counts = np.unique(labeled_array, return_counts=True) +# if len(unique) < 3: +# print(f"警告:{structure_name} 未找到足夠的連通區域") +# return None, None + +# region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] +# sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + +# if len(sorted_regions) < 2: +# print(f"警告:{structure_name} 未找到兩個主要區域") +# return None, None + +# region1_mask = (labeled_array == sorted_regions[0][0]) +# region2_mask = (labeled_array == sorted_regions[1][0]) + +# def get_centroid(mask): +# indices = np.where(mask) +# return np.mean(indices[2]) + +# centroid1 = get_centroid(region1_mask) +# centroid2 = get_centroid(region2_mask) + +# if centroid1 > centroid2: +# right = region1_mask +# left = region2_mask +# else: +# right = region2_mask +# left = region1_mask + +# return right.astype(np.uint8), left.astype(np.uint8) + +# def evaluate_case(gt_path, pred_path, labels): +# """評估單個案例""" +# print(f"\n測試文件: {os.path.basename(pred_path)}") + +# start_time = time.time() +# gt_img = load_nifti(gt_path) +# pred_img = load_nifti(pred_path) +# load_time = time.time() - start_time + +# print(f"圖像大小: {gt_img.shape}") +# print(f"加載時間: {load_time:.2f}秒") + +# results = {} +# total_time = 0 + +# # 處理眼睛 +# start_time = time.time() +# combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) +# combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + +# combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) +# closing_filter = sitk.BinaryMorphologicalClosingImageFilter() +# closing_filter.SetKernelRadius(2) +# processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + +# eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) +# eye_time = time.time() - start_time +# total_time += eye_time +# results['combined_eye'] = eye_dice +# print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + +# right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") +# right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + +# # 處理視神經 +# start_time = time.time() +# combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) +# combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + +# combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) +# processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + +# nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) +# nerve_time = time.time() - start_time +# total_time += nerve_time +# results['combined_nerve'] = nerve_dice +# print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + +# right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") +# right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + +# for label in labels: +# start_time = time.time() + +# if label == 1: # Brainstem +# gt_binary = (gt_img == label).astype(np.uint8) +# pred_binary = (pred_img == label).astype(np.uint8) +# elif label == 2 and right_pred_eye is not None: # Right Eye +# gt_binary = right_gt_eye +# pred_binary = right_pred_eye +# elif label == 3 and left_pred_eye is not None: # Left Eye +# gt_binary = left_gt_eye +# pred_binary = left_pred_eye +# elif label == 4: # Optic Chiasm +# gt_binary = (gt_img == label).astype(np.uint8) +# pred_binary = (pred_img == label).astype(np.uint8) +# elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve +# gt_binary = right_gt_nerve +# pred_binary = right_pred_nerve +# elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve +# gt_binary = left_gt_nerve +# pred_binary = left_pred_nerve +# else: +# continue + +# try: +# dice = compute_dice(pred_binary, gt_binary) +# compute_time = time.time() - start_time +# total_time += compute_time +# results[label] = dice +# print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") +# except Exception as e: +# print(f"處理標籤 {label} 時出錯:{str(e)}") +# results[label] = np.nan +# continue + +# print(f"單個案例總計算時間: {total_time:.2f}秒") +# return results + +# def evaluate_validation_fold(gt_base_folder, validation_folder, labels): +# """評估單個fold的驗證集""" +# fold_results = {label: [] for label in labels} +# fold_results['combined_eye'] = [] +# fold_results['combined_nerve'] = [] + +# pred_files = sorted([f for f in os.listdir(validation_folder) if f.endswith('.nii.gz')]) + +# for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(validation_folder)}"): +# # 從預測文件名中提取病人ID +# patient_id = pred_file.split('.nii.gz')[0] + +# # 構建對應的ground truth路徑 +# gt_path = os.path.join(gt_base_folder, f"{patient_id}.nii.gz") +# pred_path = os.path.join(validation_folder, pred_file) + +# if not os.path.exists(gt_path): +# print(f"找不到對應的真實標籤文件:{gt_path}") +# continue + +# case_results = evaluate_case(gt_path, pred_path, labels) + +# for key in case_results: +# if not np.isnan(case_results[key]): +# if key in fold_results: +# fold_results[key].append(case_results[key]) + +# return fold_results + +# def evaluate_model(gt_folder, model_path, model_name, labels, organ_names, output_dir): +# """評估模型所有fold的驗證集""" +# print(f"\n開始評估模型:{model_name}") +# all_results = {label: [] for label in labels} +# all_results['combined_eye'] = [] +# all_results['combined_nerve'] = [] +# fold_results = {} + +# # 遍歷每個fold +# for fold in range(5): +# validation_folder = os.path.join(model_path, f"fold_{fold}", "validation") + +# if not os.path.exists(validation_folder): +# print(f"找不到fold_{fold}的驗證資料夾:{validation_folder}") +# continue + +# print(f"\n處理 {model_name} - Fold {fold} 驗證集") +# results = evaluate_validation_fold(gt_folder, validation_folder, labels) +# fold_results[f"fold_{fold}"] = results + +# for key in results: +# all_results[key].extend(results[key]) + +# # 計算總平均值和標準差 +# summary = {} +# for key in all_results: +# if all_results[key]: +# mean_dice = np.mean(all_results[key]) +# std_dice = np.std(all_results[key]) +# summary[key] = { +# 'mean_dice': float(mean_dice), +# 'std_dice': float(std_dice), +# 'n_samples': len(all_results[key]) +# } +# else: +# summary[key] = { +# 'mean_dice': np.nan, +# 'std_dice': np.nan, +# 'n_samples': 0 +# } + +# # 保存結果 +# model_results = { +# 'summary': summary, +# 'fold_results': fold_results +# } + +# # 確保輸出目錄存在 +# os.makedirs(output_dir, exist_ok=True) + +# # 使用完整路徑保存結果 +# filename = os.path.join(output_dir, f'CT_CTC_T1_OAR_validation_dice_results_{model_name}.json') +# with open(filename, 'w') as f: +# json.dump(model_results, f, indent=4) +# print(f"\n{model_name}驗證集結果已保存到 {filename}") + +# # 打印結果 +# print(f"\n{model_name} 模型的驗證集 Dice 結果(所有fold的平均值):") + +# # 打印器官結果 +# for label in labels: +# if label in summary: +# mean = summary[label]['mean_dice'] +# std = summary[label]['std_dice'] +# n = summary[label]['n_samples'] +# organ_name = organ_names.get(label, f"Label_{label}") +# if not np.isnan(mean): +# print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") +# else: +# print(f"{organ_name}: 無法計算 (n={n})") + +# # 打印合併結果 +# print("\n合併結構的結果:") +# for key in ['combined_eye', 'combined_nerve']: +# if key in summary: +# mean = summary[key]['mean_dice'] +# std = summary[key]['std_dice'] +# n = summary[key]['n_samples'] +# name = "合併眼睛" if key == 'combined_eye' else "合併視神經" +# if not np.isnan(mean): +# print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") +# else: +# print(f"{name}: 無法計算 (n={n})") + +# return summary + +# if __name__ == "__main__": +# # 設置路徑 +# base_dir = "/mnt/1248/onlylian" +# gt_folder = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset221_OAR/labelsTr") +# output_dir = base_dir # 設置輸出目錄為 /mnt/1248/onlylian + +# models = { +# '2D': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset221_OAR/nnUNetTrainer__nnUNetPlans__2d"), +# '3D_fullres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset221_OAR/nnUNetTrainer__nnUNetPlans__3d_fullres"), +# '3D_lowres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset221_OAR/nnUNetTrainer__nnUNetPlans__3d_lowres") +# } + +# labels = [1, 2, 3, 4, 5, 6] +# organ_names = { +# 1: "Brainstem", +# 2: "Right_Eye", +# 3: "Left_Eye", +# 4: "Optic_Chiasm", +# 5: "Right_Optic_Nerve", +# 6: "Left_Optic_Nerve" +# } + +# # 讓用戶選擇要運行哪個模型 +# print("可用的模型:") +# for i, model_name in enumerate(models.keys(), 1): +# print(f"{i}. {model_name}") + +# choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") +# try: +# model_index = int(choice) - 1 +# model_names = list(models.keys()) +# selected_model = model_names[model_index] +# evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names, output_dir) +# except (ValueError, IndexError): +# print("無效的選擇!請輸入1-3之間的數字。") + +import os +import time +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import json + +def load_nifti(file_path): + """加載 NIfTI 文件""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """計算Dice係數""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """分離左右結構""" + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: + print(f"警告:{structure_name} 未找到足夠的連通區域") + return None, None + + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"警告:{structure_name} 未找到兩個主要區域") + return None, None + + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """評估單個案例""" + print(f"\n測試文件: {os.path.basename(pred_path)}") + + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"圖像大小: {gt_img.shape}") + print(f"加載時間: {load_time:.2f}秒") + + results = {} + total_time = 0 + + # 處理眼睛 + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + + # 處理視神經 + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") + except Exception as e: + print(f"處理標籤 {label} 時出錯:{str(e)}") + results[label] = np.nan + continue + + print(f"單個案例總計算時間: {total_time:.2f}秒") + return results + +def evaluate_validation_fold(gt_base_folder, validation_folder, labels): + """評估單個fold的驗證集""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(validation_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(validation_folder)}"): + # 從預測文件名中提取病人ID + patient_id = pred_file.split('.nii.gz')[0] + + # 構建對應的ground truth路徑 + gt_path = os.path.join(gt_base_folder, f"{patient_id}.nii.gz") + pred_path = os.path.join(validation_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"找不到對應的真實標籤文件:{gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, model_path, model_name, labels, organ_names, output_dir): + """評估模型所有fold的驗證集""" + print(f"\n開始評估模型:{model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + # 遍歷每個fold + for fold in range(5): + validation_folder = os.path.join(model_path, f"fold_{fold}", "validation") + + if not os.path.exists(validation_folder): + print(f"找不到fold_{fold}的驗證資料夾:{validation_folder}") + continue + + print(f"\n處理 {model_name} - Fold {fold} 驗證集") + results = evaluate_validation_fold(gt_folder, validation_folder, labels) + fold_results[f"fold_{fold}"] = results + + for key in results: + all_results[key].extend(results[key]) + + # 計算總平均值和標準差 + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + # 保存結果 + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + # 建立完整的輸出路徑 + output_path = os.path.join(output_dir, 'CT_CTC_T1_OAR', 'Dice', 'validation') + os.makedirs(output_path, exist_ok=True) + + # 使用完整路徑保存結果 + filename = os.path.join(output_path, f'dice_results_{model_name}.json') + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\n{model_name}驗證集結果已保存到 {filename}") + + # 打印結果 + print(f"\n{model_name} 模型的驗證集 Dice 結果(所有fold的平均值):") + + # 打印器官結果 + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: 無法計算 (n={n})") + + # 打印合併結果 + print("\n合併結構的結果:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "合併眼睛" if key == 'combined_eye' else "合併視神經" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: 無法計算 (n={n})") + + return summary + +if __name__ == "__main__": + # 設置路徑 + base_dir = "/mnt/1248/onlylian" + gt_folder = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset221_OAR/labelsTr") + output_dir = base_dir # 設置基礎輸出目錄 + + models = { + '2D': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset221_OAR/nnUNetTrainer__nnUNetPlans__2d"), + '3D_fullres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset221_OAR/nnUNetTrainer__nnUNetPlans__3d_fullres"), + '3D_lowres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset221_OAR/nnUNetTrainer__nnUNetPlans__3d_lowres") + } + + labels = [1, 2, 3, 4, 5, 6] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + + # 讓用戶選擇要運行哪個模型 + print("可用的模型:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names, output_dir) + except (ValueError, IndexError): + print("無效的選擇!請輸入1-3之間的數字。") \ No newline at end of file diff --git a/CT_CTC_T1_OAR/Left_optic_nerve.py b/CT_CTC_T1_OAR/Left_optic_nerve.py new file mode 100644 index 0000000..c00e154 --- /dev/null +++ b/CT_CTC_T1_OAR/Left_optic_nerve.py @@ -0,0 +1,161 @@ +import nibabel as nib +import matplotlib.pyplot as plt +import numpy as np +import os + +def load_nifti(file_path): + """載入NIfTI檔案""" + return nib.load(file_path).get_fdata() + +def create_colored_mask(data): + """創建彩色遮罩""" + color_dict = { + 1: [144/255, 238/255, 144/255], # Brainstem - 綠色 + 2: [255/255, 218/255, 150/255], # Right_Eye - 淺黃色 + 3: [205/255, 170/255, 125/255], # Left_Eye - 棕色 + 4: [135/255, 206/255, 235/255], # Optic_Chiasm - 藍色 + 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - 亮珊瑚紅 + 6: [255/255, 160/255, 122/255] # Left_Optic_Nerve - 淺珊瑚紅 + } + + mask = np.zeros((*data.shape, 4)) + + for label, color in color_dict.items(): + organ_mask = (data == label) + if np.any(organ_mask): + mask[organ_mask] = [*color, 1.0] + + return mask + +def get_brain_bounds(image): + """獲取腦部區域的邊界""" + mask = image > np.percentile(image, 1) + rows = np.any(mask, axis=1) + cols = np.any(mask, axis=0) + rmin, rmax = np.where(rows)[0][[0, -1]] + cmin, cmax = np.where(cols)[0][[0, -1]] + + center_r = (rmin + rmax) // 2 + center_c = (cmin + cmax) // 2 + + radius = int(max(rmax - rmin, cmax - cmin) * 0.55) + margin = int(radius * 0.15) + + rmin = max(center_r - radius - margin, 0) + rmax = min(center_r + radius + margin, image.shape[0]) + cmin = max(center_c - radius - margin, 0) + cmax = min(center_c + radius + margin, image.shape[1]) + + return rmin, rmax, cmin, cmax + +def check_organs_in_slice(slice_data): + """檢查切片中存在的器官標籤""" + unique_labels = set(np.unique(slice_data)) + if 0 in unique_labels: + unique_labels.remove(0) + organ_dict = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + return {organ_dict[label] for label in unique_labels if label in organ_dict} + +def adjust_contrast(img): + """調整圖像對比度""" + p2, p98 = np.percentile(img, (2, 98)) + return np.clip((img - p2) / (p98 - p2), 0, 1) + +def save_slice(image_ct, image_mri, ground_truth, prediction, slice_num, save_path): + """保存指定切片的比較圖""" + plt.clf() + fig, axes = plt.subplots(1, 3, figsize=(30, 10)) + plt.subplots_adjust(wspace=0.01) + + # Process MRI image + img_slice_mri = np.rot90(image_mri[:, :, slice_num]) + rmin, rmax, cmin, cmax = get_brain_bounds(img_slice_mri) + + # Adjust contrast for MRI + mri_norm = adjust_contrast(img_slice_mri) + + # Original MRI Image + axes[0].imshow(mri_norm[rmin:rmax, cmin:cmax], cmap='gray') + axes[0].set_title('Original Image', fontsize=32, pad=20, weight='bold') + axes[0].axis('off') + + # Ground Truth with MRI background + gt_slice = np.rot90(ground_truth[:, :, slice_num]) + gt_mask = create_colored_mask(gt_slice) + + axes[1].imshow(mri_norm[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[1].imshow(gt_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[1].set_title('Ground Truth', fontsize=32, pad=20, weight='bold') + axes[1].axis('off') + + # Prediction with MRI background + pred_slice = np.rot90(prediction[:, :, slice_num]) + pred_mask = create_colored_mask(pred_slice) + + axes[2].imshow(mri_norm[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[2].imshow(pred_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[2].set_title('Prediction', fontsize=32, pad=20, weight='bold') + axes[2].axis('off') + + plt.savefig(save_path, bbox_inches='tight', dpi=300, pad_inches=0.05) + plt.close() + print(f"已保存切片 {slice_num} 至: {save_path}") + +if __name__ == "__main__": + # 載入影像檔案 + image_path_1 = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset221_OAR/imagesTs/OAR_051_0000.nii.gz' #CT + image_path_2 = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset221_OAR/imagesTs/OAR_051_0001.nii.gz' #MRI + gt_path = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset221_OAR/labelsTs/OAR_051.nii.gz' + pred_path = '/mnt/1248/onlylian/CT_CTC_T1_OAR/output_predictions/3d_fullres/fold_4/OAR_051.nii.gz' + + # 設定保存路徑 + save_dir = '/mnt/1248/onlylian/CT_CTC_T1_OAR' + os.makedirs(save_dir, exist_ok=True) + + # 讀取影像 + print("正在載入影像...") + image_ct = load_nifti(image_path_1) + image_mri = load_nifti(image_path_2) + ground_truth = load_nifti(gt_path) + prediction = load_nifti(pred_path) + print("影像載入完成") + + print("\n分析切片中的器官...") + total_slices = image_ct.shape[2] + for slice_num in range(total_slices): + gt_slice = ground_truth[:, :, slice_num] + pred_slice = prediction[:, :, slice_num] + + gt_organs = check_organs_in_slice(gt_slice) + pred_organs = check_organs_in_slice(pred_slice) + + if gt_organs or pred_organs: + print(f"\n切片 {slice_num}:") + print(f"Ground Truth 包含: {', '.join(sorted(gt_organs))}") + print(f"Prediction 包含: {', '.join(sorted(pred_organs))}") + + print("\n開始切片選擇和保存過程...") + while True: + try: + slice_num = int(input("\n請輸入要保存的切片編號 (-1 退出): ")) + if slice_num == -1: + print("程序結束") + break + if 0 <= slice_num < total_slices: + save_path = os.path.join(save_dir, f'comparison_L_optic_nerve_051_{slice_num:03d}.png') + save_slice(image_ct, image_mri, ground_truth, prediction, slice_num, save_path) + else: + print(f"切片編號必須在 0 到 {total_slices-1} 之間") + except ValueError: + print("請輸入有效的數字") + except Exception as e: + print(f"發生錯誤: {str(e)}") + + diff --git a/CT_CTC_T1_OAR/chiasm.py b/CT_CTC_T1_OAR/chiasm.py new file mode 100644 index 0000000..1a11631 --- /dev/null +++ b/CT_CTC_T1_OAR/chiasm.py @@ -0,0 +1,165 @@ +import nibabel as nib +import matplotlib.pyplot as plt +import numpy as np +import os + +def load_nifti(file_path): + """載入NIfTI檔案""" + return nib.load(file_path).get_fdata() + +def create_colored_mask(data): + """創建彩色遮罩""" + color_dict = { + 1: [144/255, 238/255, 144/255], # Brainstem - 綠色 + 2: [255/255, 218/255, 150/255], # Right_Eye - 淺黃色 + 3: [205/255, 170/255, 125/255], # Left_Eye - 棕色 + 4: [135/255, 206/255, 235/255], # Optic_Chiasm - 藍色 + 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - 亮珊瑚紅 + 6: [255/255, 160/255, 122/255] # Left_Optic_Nerve - 淺珊瑚紅 + } + + # 創建一個具有透明背景的遮罩 + mask = np.zeros((*data.shape, 4)) # 使用4通道(RGBA) + + for label, color in color_dict.items(): + # 為每個器官設置顏色,包括完全不透明的 alpha 通道 + organ_mask = (data == label) + if np.any(organ_mask): # 只處理存在的器官 + mask[organ_mask] = [*color, 1.0] # RGB + alpha + + return mask + +def get_brain_bounds(image): + """獲取腦部區域的邊界""" + mask = image > np.percentile(image, 1) + rows = np.any(mask, axis=1) + cols = np.any(mask, axis=0) + rmin, rmax = np.where(rows)[0][[0, -1]] + cmin, cmax = np.where(cols)[0][[0, -1]] + + center_r = (rmin + rmax) // 2 + center_c = (cmin + cmax) // 2 + + radius = int(max(rmax - rmin, cmax - cmin) * 0.55) + margin = int(radius * 0.15) + + rmin = max(center_r - radius - margin, 0) + rmax = min(center_r + radius + margin, image.shape[0]) + cmin = max(center_c - radius - margin, 0) + cmax = min(center_c + radius + margin, image.shape[1]) + + return rmin, rmax, cmin, cmax + +def check_organs_in_slice(slice_data): + """檢查切片中存在的器官標籤""" + unique_labels = set(np.unique(slice_data)) + if 0 in unique_labels: + unique_labels.remove(0) + organ_dict = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + return {organ_dict[label] for label in unique_labels if label in organ_dict} + +def save_slice(image_ct, image_mri, ground_truth, prediction, slice_num, save_path): + """保存指定切片的比較圖""" + plt.clf() + fig, axes = plt.subplots(1, 2, figsize=(25, 11)) + plt.subplots_adjust(wspace=0.01) + + # Process CT and MRI images + img_slice_ct = np.rot90(image_ct[:, :, slice_num]) + img_slice_mri = np.rot90(image_mri[:, :, slice_num]) + + # Get bounds from CT image + rmin, rmax, cmin, cmax = get_brain_bounds(img_slice_ct) + + # 調整CT和MRI圖像的對比度 + def adjust_contrast(img): + p2, p98 = np.percentile(img, (2, 98)) + return np.clip((img - p2) / (p98 - p2), 0, 1) + + ct_norm = adjust_contrast(img_slice_ct) + mri_norm = adjust_contrast(img_slice_mri) + + # Blend images with equal weights + blended_img = 0.5 * ct_norm + 0.5 * mri_norm + + # Ground Truth + gt_slice = np.rot90(ground_truth[:, :, slice_num]) + gt_mask = create_colored_mask(gt_slice) + + # 使用 zorder 參數來控制圖層順序 + axes[0].imshow(blended_img[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[0].imshow(gt_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[0].set_title('Ground Truth', fontsize=32, pad=20, weight='bold') + axes[0].axis('off') + + # Prediction + pred_slice = np.rot90(prediction[:, :, slice_num]) + pred_mask = create_colored_mask(pred_slice) + + axes[1].imshow(blended_img[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[1].imshow(pred_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[1].set_title('Prediction', fontsize=32, pad=20, weight='bold') + axes[1].axis('off') + + plt.savefig(save_path, bbox_inches='tight', dpi=300, pad_inches=0.05) + plt.close() + print(f"已保存切片 {slice_num} 至: {save_path}") + +if __name__ == "__main__": + # 載入影像檔案 + image_path_1 = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset221_OAR/imagesTs/OAR_082_0000.nii.gz' #CT + image_path_2 = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset221_OAR/imagesTs/OAR_082_0001.nii.gz' #MRI + gt_path = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset221_OAR/labelsTs/OAR_082.nii.gz' + pred_path = '/mnt/1248/onlylian/CT_CTC_T1_OAR/output_predictions/3d_fullres/fold_4/OAR_082.nii.gz' + + # 設定保存路徑 + save_dir = '/mnt/1248/onlylian/CT_CTC_T1_OAR' + os.makedirs(save_dir, exist_ok=True) + + # 讀取影像 + print("正在載入影像...") + image_ct = load_nifti(image_path_1) + image_mri = load_nifti(image_path_2) + ground_truth = load_nifti(gt_path) + prediction = load_nifti(pred_path) + print("影像載入完成") + + print("\n分析切片中的器官...") + total_slices = image_ct.shape[2] + for slice_num in range(total_slices): + gt_slice = ground_truth[:, :, slice_num] + pred_slice = prediction[:, :, slice_num] + + gt_organs = check_organs_in_slice(gt_slice) + pred_organs = check_organs_in_slice(pred_slice) + + if gt_organs or pred_organs: + print(f"\n切片 {slice_num}:") + print(f"Ground Truth 包含: {', '.join(sorted(gt_organs))}") + print(f"Prediction 包含: {', '.join(sorted(pred_organs))}") + + print("\n開始切片選擇和保存過程...") + while True: + try: + slice_num = int(input("\n請輸入要保存的切片編號 (-1 退出): ")) + if slice_num == -1: + print("程序結束") + break + if 0 <= slice_num < total_slices: + save_path = os.path.join(save_dir, f'comparison_optic_chiasm_082_{slice_num:03d}.png') + save_slice(image_ct, image_mri, ground_truth, prediction, slice_num, save_path) + else: + print(f"切片編號必須在 0 到 {total_slices-1} 之間") + except ValueError: + print("請輸入有效的數字") + except Exception as e: + print(f"發生錯誤: {str(e)}") + + diff --git a/CT_CTC_T1_OAR_TV/Dice/test/compare_test_dice_scores.py b/CT_CTC_T1_OAR_TV/Dice/test/compare_test_dice_scores.py new file mode 100644 index 0000000..833ba4c --- /dev/null +++ b/CT_CTC_T1_OAR_TV/Dice/test/compare_test_dice_scores.py @@ -0,0 +1,144 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# Set up Chinese font support +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """Load JSON data file""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"File not found: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON decoding error: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """Extract scores for a specific organ from the data""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """Create and save the plot""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('T1C_OAR_TV', fontsize=12, pad=10) # Changed title + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # Adjust data label position and size + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6, # Reduced font size + rotation=0) # Optional: rotate text if still crowded + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR_TV', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """Print numerical results""" + print("\nMean Dice Scores (percentage):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # Define paths and organ mapping + base_path = '/mnt/1248/onlylian/T1C_OAR_TV/Dice/test' + paths = { + '2D': os.path.join(base_path, 'T1C_OAR_TV_test_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'T1C_OAR_TV_test_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'T1C_OAR_TV_test_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve", + "7": "TV" + } + + # Initialize data storage + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # Load and process data + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # Calculate averages and standard deviations + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # Create and save the plot + output_path = os.path.join(base_path, 'T1C_OAR_TV_test_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # Print the results + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/CT_CTC_T1_OAR_TV/Dice/test/compute_test_dice.py b/CT_CTC_T1_OAR_TV/Dice/test/compute_test_dice.py new file mode 100644 index 0000000..34e6500 --- /dev/null +++ b/CT_CTC_T1_OAR_TV/Dice/test/compute_test_dice.py @@ -0,0 +1,286 @@ +import json +import os +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import time + +def load_nifti(file_path): + """Load a NIfTI file""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """Compute Dice coefficient""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """Separate left and right structures""" + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: + print(f"Warning: Not enough connected regions found for {structure_name}") + return None, None + + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"Warning: Only one major region found for {structure_name}") + return None, None + + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """Evaluate a single case""" + print(f"\nTest file: {os.path.basename(pred_path)}") + + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"Image size: {gt_img.shape}") + print(f"Loading time: {load_time:.2f}s") + + results = {} + total_time = 0 + + # Process eyes + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"Eyes combined computation time: {eye_time:.2f}s, Dice: {eye_dice:.4f}") + + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "Eyes") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "Eyes") + + # Process optic nerves + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"Optic nerves combined computation time: {nerve_time:.2f}s, Dice: {nerve_dice:.4f}") + + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "Optic Nerves") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "Optic Nerves") + + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + elif label == 7: # TV + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"Label {label} computation time: {compute_time:.2f}s, Dice: {dice:.4f}") + except Exception as e: + print(f"Error processing label {label}: {str(e)}") + results[label] = np.nan + continue + + print(f"Total computation time for this case: {total_time:.2f}s") + return results + +def evaluate_fold(gt_folder, pred_folder, labels): + """Evaluate all cases in a single fold""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(pred_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(pred_folder)}"): + gt_path = os.path.join(gt_folder, pred_file) + pred_path = os.path.join(pred_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"Could not find corresponding ground truth file: {gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, base_pred_folder, model_name, labels, organ_names): + """Evaluate all folds for a single model""" + print(f"\nStarting evaluation of model: {model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + for fold in range(5): + print(f"\nProcessing {model_name} - Fold {fold}") + pred_folder = os.path.join(base_pred_folder, f"fold_{fold}") + if not os.path.exists(pred_folder): + print(f"Could not find prediction folder: {pred_folder}") + continue + + results = evaluate_fold(gt_folder, pred_folder, labels) + fold_results[f"fold_{fold}"] = results + + for key in results: + all_results[key].extend(results[key]) + + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + save_dir = "/mnt/1248/onlylian/CT_CTC_T1_OAR_TV/Dice/test" + os.makedirs(save_dir, exist_ok=True) + filename = os.path.join(save_dir, f'CT_CTC_T1_OAR_TV_test_dice_results_{model_name}.json') + + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\nResults for {model_name} saved to {filename}") + + print(f"\nDice results for {model_name} (average across all folds):") + + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: Unable to compute (n={n})") + + print("\nResults for combined structures:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "Combined Eyes" if key == 'combined_eye' else "Combined Optic Nerves" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: Unable to compute (n={n})") + + return summary + +if __name__ == "__main__": + gt_folder = "/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset222_OAR_TV/labelsTs" + models = { + '2D': '/mnt/1248/onlylian/CT_CTC_T1_OAR_TV/output_predictions/2d', + '3D_fullres': '/mnt/1248/onlylian/CT_CTC_T1_OAR_TV/output_predictions/3d_fullres', + '3D_lowres': '/mnt/1248/onlylian/CT_CTC_T1_OAR_TV/output_predictions/3d_lowres' + } + labels = [1, 2, 3, 4, 5, 6, 7] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve", + 7: "TV" + } + + print("Available models:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\nPlease select a model to run (enter 1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names) + except (ValueError, IndexError): + print("Invalid choice! Please enter a number between 1 and 3.") \ No newline at end of file diff --git a/CT_CTC_T1_OAR_TV/Dice/validation/compare_validation_dice_scores.py b/CT_CTC_T1_OAR_TV/Dice/validation/compare_validation_dice_scores.py new file mode 100644 index 0000000..cce4eb1 --- /dev/null +++ b/CT_CTC_T1_OAR_TV/Dice/validation/compare_validation_dice_scores.py @@ -0,0 +1,111 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# Set up Chinese font support +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +# Functions up to create_plot remain the same + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """Create and save the plot""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('T1C_OAR_TV', fontsize=12, pad=10) # Changed title + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # Adjust data label position and size + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height + 0.5, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6) + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR_TV', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +# print_results function remains the same + +def main(): + # Define paths and organ mapping + base_path = '/mnt/1248/onlylian/T1C_OAR_TV/Dice/validation' + paths = { + '2D': os.path.join(base_path, 'T1C_OAR_TV_validation_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'T1C_OAR_TV_validation_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'T1C_OAR_TV_validation_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve", + "7": "TV" + } + + # Initialize data storage + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # Load and process data + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # Calculate averages and standard deviations + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # Create and save the plot + output_path = os.path.join(base_path, 'T1C_OAR_TV_validation_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # Print the results + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/CT_CTC_T1_OAR_TV/Dice/validation/compute_validation_dice.py b/CT_CTC_T1_OAR_TV/Dice/validation/compute_validation_dice.py new file mode 100644 index 0000000..d6db76b --- /dev/null +++ b/CT_CTC_T1_OAR_TV/Dice/validation/compute_validation_dice.py @@ -0,0 +1,325 @@ +import os +import time +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import json + +def load_nifti(file_path): + """Load a NIfTI file""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """Compute Dice coefficient""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def preprocess_binary_mask(mask, apply_closing=True): + """Preprocess binary mask with morphological operations""" + mask_sitk = sitk.GetImageFromArray(mask) + + if apply_closing: + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + mask = sitk.GetArrayFromImage(closing_filter.Execute(mask_sitk)) + + return mask + +def separate_structures(combined_mask, structure_name=""): + """Separate left and right structures""" + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: + print(f"Warning: Not enough connected regions found for {structure_name}") + return None, None + + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"Warning: Only one major region found for {structure_name}") + return None, None + + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """Evaluate a single case""" + print(f"\nTest file: {os.path.basename(pred_path)}") + + try: + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"Image size: {gt_img.shape}") + print(f"Loading time: {load_time:.2f}s") + + results = {} + total_time = 0 + + # Process eyes + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + processed_pred_eye = preprocess_binary_mask(combined_pred_eye) + processed_gt_eye = preprocess_binary_mask(combined_gt_eye) + + eye_dice = compute_dice(processed_pred_eye, processed_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"Eyes combined computation time: {eye_time:.2f}s, Dice: {eye_dice:.4f}") + + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "Eyes") + right_gt_eye, left_gt_eye = separate_structures(processed_gt_eye, "Eyes") + + # Process optic nerves + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + processed_pred_nerve = preprocess_binary_mask(combined_pred_nerve) + processed_gt_nerve = preprocess_binary_mask(combined_gt_nerve) + + nerve_dice = compute_dice(processed_pred_nerve, processed_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"Optic nerves combined computation time: {nerve_time:.2f}s, Dice: {nerve_dice:.4f}") + + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "Optic Nerves") + right_gt_nerve, left_gt_nerve = separate_structures(processed_gt_nerve, "Optic Nerves") + + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + apply_closing = True + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + apply_closing = False # Already processed + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + apply_closing = False # Already processed + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + apply_closing = True + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + apply_closing = False # Already processed + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + apply_closing = False # Already processed + elif label == 7: # TV + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + apply_closing = True + else: + continue + + try: + if apply_closing: + pred_binary = preprocess_binary_mask(pred_binary) + gt_binary = preprocess_binary_mask(gt_binary) + + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"Label {label} computation time: {compute_time:.2f}s, Dice: {dice:.4f}") + except Exception as e: + print(f"Error processing label {label}: {str(e)}") + results[label] = np.nan + continue + + print(f"Total computation time for this case: {total_time:.2f}s") + return results + except Exception as e: + print(f"Error evaluating case: {str(e)}") + return None + +def evaluate_validation_fold(gt_base_folder, validation_folder, labels): + """Evaluate the validation set for a single fold""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(validation_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(validation_folder)}"): + patient_id = pred_file.split('.nii.gz')[0] + gt_path = os.path.join(gt_base_folder, f"{patient_id}.nii.gz") + pred_path = os.path.join(validation_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"Could not find corresponding ground truth file: {gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + if case_results is not None: + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, model_path, model_name, labels, organ_names, output_dir): + """Evaluate the validation set for all folds of a model""" + print(f"\nStarting evaluation of model: {model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + for fold in range(5): + validation_folder = os.path.join(model_path, f"fold_{fold}", "validation") + + if not os.path.exists(validation_folder): + print(f"Could not find fold_{fold} validation folder: {validation_folder}") + continue + + print(f"\nProcessing {model_name} - Fold {fold} validation set") + results = evaluate_validation_fold(gt_folder, validation_folder, labels) + fold_results[f"fold_{fold}"] = results + + for key in results: + all_results[key].extend(results[key]) + + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + output_path = os.path.join(output_dir, 'CT_CTC_T1_OAR_TV', 'Dice', 'validation') + os.makedirs(output_path, exist_ok=True) + + filename = os.path.join(output_path, f'dice_results_{model_name}.json') + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\nValidation set results for {model_name} saved to {filename}") + + print(f"\nValidation set Dice results for {model_name} (average across all folds):") + + # Print individual organ results + print("\nOrgan-specific results:") + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: Unable to compute (n={n})") + + # Print combined structure results + print("\nCombined structure results:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "Combined Eyes" if key == 'combined_eye' else "Combined Optic Nerves" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: Unable to compute (n={n})") + + return summary + +if __name__ == "__main__": + base_dir = "/mnt/1248/onlylian" + gt_folder = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset222_OAR_TV/labelsTr") + output_dir = base_dir + + models = { + '2D': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset222_OAR_TV/nnUNetTrainer__nnUNetPlans__2d"), + '3D_fullres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset222_OAR_TV/nnUNetTrainer__nnUNetPlans__3d_fullres"), + '3D_lowres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset222_OAR_TV/nnUNetTrainer__nnUNetPlans__3d_lowres") + } + + labels = [1, 2, 3, 4, 5, 6, 7] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve", + 7: "TV" + } + + print("Available models:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + try: + choice = input("\nPlease select a model to run (enter 1-3): ") + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names, output_dir) + except (ValueError, IndexError): + print("Invalid choice! Please enter a number between 1 and 3.") + except KeyboardInterrupt: + print("\nProgram interrupted by user") + print("You can run the program again to continue processing") + except Exception as e: + print(f"\nAn unexpected error occurred: {str(e)}") + print("Please check the error message and try again") \ No newline at end of file diff --git a/CT_CTC_T1_OAR_TV/visualize_medical_compare.py b/CT_CTC_T1_OAR_TV/visualize_medical_compare.py new file mode 100644 index 0000000..fb9e147 --- /dev/null +++ b/CT_CTC_T1_OAR_TV/visualize_medical_compare.py @@ -0,0 +1,151 @@ +import nibabel as nib +import matplotlib.pyplot as plt +import numpy as np +import os + +def load_nifti(file_path): + """載入NIfTI檔案""" + return nib.load(file_path).get_fdata() + +def create_colored_mask(data): + """創建彩色遮罩""" + color_dict = { + 1: [144/255, 238/255, 144/255], # Brainstem - 綠色 + 2: [255/255, 218/255, 150/255], # Right_Eye - 淺黃色 + 3: [205/255, 170/255, 125/255], # Left_Eye - 棕色 + 4: [135/255, 206/255, 235/255], # Optic_Chiasm - 藍色 + 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - 亮珊瑚紅 + 6: [255/255, 160/255, 122/255], # Left_Optic_Nerve - 淺珊瑚紅 + 7: [255/255, 0/255, 0/255] # TV - 亮紅色 + } + + mask = np.zeros((*data.shape, 4)) + for label, color in color_dict.items(): + organ_mask = (data == label) + if np.any(organ_mask): + mask[organ_mask] = [*color, 1.0] + return mask + +def get_display_bounds(image): + """直接返回較大的顯示區域""" + h, w = image.shape + # 使用圖像中心點 + center_h = h // 2 + center_w = w // 2 + # 取圖像大小的 85% 作為顯示範圍 + radius_h = int(h * 0.425) + radius_w = int(w * 0.425) + + return ( + max(center_h - radius_h, 0), + min(center_h + radius_h, h), + max(center_w - radius_w, 0), + min(center_w + radius_w, w) + ) + +def check_tv_in_slice(slice_data): + """檢查切片中是否包含TV(label 7)""" + return 7 in np.unique(slice_data) + +def find_tv_slices(ground_truth): + """找出所有包含TV的切片""" + tv_slices = [] + tv_pixels = [] + total_slices = ground_truth.shape[2] + + for slice_num in range(total_slices): + slice_data = ground_truth[:, :, slice_num] + if check_tv_in_slice(slice_data): + tv_pixel_count = np.sum(slice_data == 7) + tv_slices.append(slice_num) + tv_pixels.append(tv_pixel_count) + + sorted_indices = np.argsort(tv_pixels)[::-1] + sorted_slices = [tv_slices[i] for i in sorted_indices] + sorted_pixels = [tv_pixels[i] for i in sorted_indices] + + return sorted_slices, sorted_pixels + +def save_slice(image, ground_truth, prediction, slice_num, save_path): + """保存指定切片的三列比較圖(原始影像、Ground Truth和預測結果)""" + plt.clf() + fig, axes = plt.subplots(1, 3, figsize=(36, 11)) + plt.subplots_adjust(wspace=0.01) + + img_slice = np.rot90(image[:, :, slice_num]) + rmin, rmax, cmin, cmax = get_display_bounds(img_slice) + + # 調整圖像對比度 + p2, p98 = np.percentile(img_slice, (2, 98)) + img_slice = np.clip(img_slice, p2, p98) + img_slice = (img_slice - p2) / (p98 - p2) + + # 1. Original Image + axes[0].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[0].set_title('Original Image', fontsize=32, pad=20, weight='bold') + axes[0].axis('off') + + # 2. Ground Truth + gt_slice = np.rot90(ground_truth[:, :, slice_num]) + gt_mask = create_colored_mask(gt_slice) + + axes[1].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[1].imshow(gt_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[1].set_title('Ground Truth', fontsize=32, pad=20, weight='bold') + axes[1].axis('off') + + # 3. Prediction + pred_slice = np.rot90(prediction[:, :, slice_num]) + pred_mask = create_colored_mask(pred_slice) + + axes[2].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[2].imshow(pred_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[2].set_title('Prediction', fontsize=32, pad=20, weight='bold') + axes[2].axis('off') + + plt.suptitle('') + plt.savefig(save_path, bbox_inches='tight', dpi=300, pad_inches=0.05) + plt.close() + print(f"已保存切片 {slice_num} 至: {save_path}") + +if __name__ == "__main__": + # 設定路徑 + image_path = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset222_OAR_TV/imagesTs/OAR_TV_098_0001.nii.gz' + gt_path = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset222_OAR_TV/labelsTs/OAR_TV_098.nii.gz' + pred_path = '/mnt/1248/onlylian/CT_CTC_T1_OAR_TV/output_predictions/3d_fullres/fold_4/OAR_TV_098.nii.gz' + save_dir = '/mnt/1248/onlylian/CT_CTC_T1_OAR_TV' + + os.makedirs(save_dir, exist_ok=True) + + print("正在載入影像...") + image = load_nifti(image_path) + ground_truth = load_nifti(gt_path) + prediction = load_nifti(pred_path) + print("影像載入完成") + + print("\n尋找包含TV的切片...") + tv_slices, tv_pixels = find_tv_slices(ground_truth) + + if tv_slices: + print(f"\n找到 {len(tv_slices)} 個包含TV的切片:") + for slice_num, pixel_count in zip(tv_slices, tv_pixels): + print(f"切片 {slice_num}: {pixel_count} 像素") + else: + print("未找到包含TV的切片") + + print("\n開始切片選擇和保存過程...") + while True: + try: + slice_num = int(input("\n請輸入要保存的切片編號 (-1 退出): ")) + if slice_num == -1: + print("程序結束") + break + if 0 <= slice_num < ground_truth.shape[2]: + save_path = os.path.join(save_dir, f'comparison_OAR_TV_098_4_{slice_num:03d}.png') + save_slice(image, ground_truth, prediction, slice_num, save_path) + else: + print(f"切片編號必須在 0 到 {ground_truth.shape[2]-1} 之間") + except ValueError: + print("請輸入有效的數字") + except Exception as e: + print(f"發生錯誤: {str(e)}") \ No newline at end of file diff --git a/FLAIR/Dice/test/compare_test_dice_scores.py b/FLAIR/Dice/test/compare_test_dice_scores.py new file mode 100644 index 0000000..5feb29c --- /dev/null +++ b/FLAIR/Dice/test/compare_test_dice_scores.py @@ -0,0 +1,287 @@ +# import json +# import os +# import numpy as np +# import matplotlib.pyplot as plt +# from typing import Dict, Tuple + +# # 設置中文字體支持 +# plt.rcParams['font.sans-serif'] = ['SimHei'] +# plt.rcParams['axes.unicode_minus'] = False + +# def load_data(file_path: str) -> Dict: +# """讀取JSON數據文件""" +# try: +# with open(file_path, 'r') as file: +# return json.load(file) +# except FileNotFoundError: +# print(f"文件未找到: {file_path}") +# return {} +# except json.JSONDecodeError: +# print(f"JSON解析錯誤: {file_path}") +# return {} + +# def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: +# """從數據中提取特定器官的分數""" +# if "summary" not in data or organ_id not in data["summary"]: +# return (0, 0) + +# scores = data["summary"][organ_id] +# if model == '3D_fullres': +# mean_dice = scores.get("mean_dice", 0) +# std_dice = scores.get("std_dice", 0) +# else: +# mean_dice = scores.get("mean_dice", scores.get("mean", 0)) +# std_dice = scores.get("std_dice", scores.get("std", 0)) +# return (mean_dice, std_dice) + +# def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): +# """創建和保存圖表""" +# plt.figure(figsize=(16, 5)) # Reduced figure size +# plt.title('CTC', fontsize=12, pad=10) # Changed title +# plt.grid(True, axis='y', linestyle='--', alpha=0.7) + +# x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups +# width = 0.35 # Reduced bar width + +# colors = { +# '2D': 'royalblue', +# '3D_fullres': 'limegreen', +# '3D_lowres': 'orange' +# } + +# positions = { +# '2D': x - width, +# '3D_fullres': x, +# '3D_lowres': x + width +# } + +# for model, averages in model_averages.items(): +# bars = plt.bar(positions[model], +# [averages[organ] * 100 for organ in organs.keys()], +# width, +# label=model, +# color=colors[model]) + +# # 調整數值標籤位置和大小 +# for j, bar in enumerate(bars): +# height = bar.get_height() +# organ = list(organs.keys())[j] +# std = model_stds[model][organ] * 100 +# plt.text(bar.get_x() + bar.get_width()/2., height, +# f'{height:.1f}±{std:.1f}%', +# ha='center', +# va='bottom', +# fontsize=6, # Reduced font size +# rotation=0) # Optional: rotate text if still crowded + +# plt.ylim(0, 100) +# plt.ylabel('DC (%)', fontsize=10) +# plt.xlabel('OAR', fontsize=10) +# plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) +# plt.legend(loc='upper right', fontsize=8) +# plt.tight_layout() + +# os.makedirs(os.path.dirname(output_path), exist_ok=True) +# plt.savefig(output_path, dpi=300, bbox_inches='tight') +# plt.close() + +# def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): +# """打印數值結果""" +# print("\n平均 Dice 分數 (百分比):") +# for model in model_averages.keys(): +# print(f"\n{model}:") +# for organ in organs.keys(): +# avg = model_averages[model][organ] * 100 +# std = model_stds[model][organ] * 100 +# print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +# def main(): +# # 定義路徑和器官映射 +# base_path = '/mnt/1248/onlylian/CTC/Dice/test' +# paths = { +# '2D': os.path.join(base_path, 'CTC_test_dice_results_2D.json'), +# '3D_fullres': os.path.join(base_path, 'CTC_test_dice_results_3D_fullres.json'), +# '3D_lowres': os.path.join(base_path, 'CTC_test_dice_results_3D_lowres.json') +# } + +# organs = { +# "1": "Brainstem", +# "2": "Right_Eye", +# "3": "Left_Eye", +# "4": "Optic_Chiasm", +# "5": "Right_Optic_Nerve", +# "6": "Left_Optic_Nerve" +# } + +# # 初始化數據存儲 +# model_dice_scores = { +# model: {organ: None for organ in organs.keys()} +# for model in paths.keys() +# } + +# # 讀取和處理數據 +# for model, path in paths.items(): +# data = load_data(path) +# for organ_id in organs.keys(): +# model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + +# # 計算平均值和標準差 +# model_averages = {} +# model_stds = {} +# for model, scores in model_dice_scores.items(): +# model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} +# model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + +# # 創建圖表並保存 +# output_path = os.path.join(base_path, 'CTC_test_dice_scores_comparison.png') +# create_plot(model_averages, model_stds, organs, output_path) + +# # 打印結果 +# print_results(model_averages, model_stds, organs) + +# if __name__ == "__main__": +# main() + +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# 設置中文字體支持 +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """讀取JSON數據文件""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"文件未找到: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON解析錯誤: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """從數據中提取特定器官的分數""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """創建和保存圖表""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('FLAIR', fontsize=12, pad=10) # Changed title + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # 調整數值標籤位置和大小 + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6, # Reduced font size + rotation=0) # Optional: rotate text if still crowded + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """打印數值結果""" + print("\n平均 Dice 分數 (百分比):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # 定義路徑和器官映射 + base_path = '/mnt/1248/onlylian/FLAIR/Dice/test' + paths = { + '2D': os.path.join(base_path, 'FLAIR_test_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'FLAIR_test_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'FLAIR_test_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve" + } + + # 初始化數據存儲 + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # 讀取和處理數據 + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # 計算平均值和標準差 + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # 創建圖表並保存 + output_path = os.path.join(base_path, 'FLAIR_test_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # 打印結果 + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/FLAIR/Dice/test/compute_test_dice.py b/FLAIR/Dice/test/compute_test_dice.py new file mode 100644 index 0000000..458415f --- /dev/null +++ b/FLAIR/Dice/test/compute_test_dice.py @@ -0,0 +1,614 @@ +# import json +# import os +# import numpy as np +# import SimpleITK as sitk +# from tqdm import tqdm +# import time + +# def load_nifti(file_path): +# """加載 NIfTI 文件""" +# return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +# def compute_dice(pred, gt): +# """計算Dice係數""" +# intersection = np.sum(pred & gt) +# sum_pred = np.sum(pred) +# sum_gt = np.sum(gt) + +# if sum_pred == 0 and sum_gt == 0: +# return 1.0 +# elif sum_pred == 0 or sum_gt == 0: +# return 0.0 + +# return 2.0 * intersection / (sum_pred + sum_gt) + +# def separate_structures(combined_mask, structure_name=""): +# """分離左右結構""" +# # 使用SimpleITK的ConnectedComponent +# sitk_image = sitk.GetImageFromArray(combined_mask) +# labeled_components = sitk.ConnectedComponent(sitk_image) +# labeled_array = sitk.GetArrayFromImage(labeled_components) + +# # 計算連通區域數量和大小 +# unique, counts = np.unique(labeled_array, return_counts=True) +# if len(unique) < 3: # 背景算一個,所以至少需要3個 +# print(f"警告:{structure_name} 未找到足夠的連通區域") +# return None, None + +# # 排除背景(標籤0)並按大小排序 +# region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] +# sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + +# if len(sorted_regions) < 2: +# print(f"警告:{structure_name} 未找到兩個主要區域") +# return None, None + +# # 獲取兩個最大區域 +# region1_mask = (labeled_array == sorted_regions[0][0]) +# region2_mask = (labeled_array == sorted_regions[1][0]) + +# # 計算質心 +# def get_centroid(mask): +# indices = np.where(mask) +# return np.mean(indices[2]) # 使用x軸(第三維度)的平均值 + +# centroid1 = get_centroid(region1_mask) +# centroid2 = get_centroid(region2_mask) + +# # 根據x軸位置確定左右 +# if centroid1 > centroid2: +# right = region1_mask +# left = region2_mask +# else: +# right = region2_mask +# left = region1_mask + +# return right.astype(np.uint8), left.astype(np.uint8) + +# def evaluate_case(gt_path, pred_path, labels): +# """評估單個案例""" +# print(f"\n測試文件: {os.path.basename(pred_path)}") + +# # 加載圖像 +# start_time = time.time() +# gt_img = load_nifti(gt_path) +# pred_img = load_nifti(pred_path) +# load_time = time.time() - start_time + +# print(f"圖像大小: {gt_img.shape}") +# print(f"加載時間: {load_time:.2f}秒") + +# results = {} +# total_time = 0 + +# # 處理眼睛 +# start_time = time.time() +# combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) +# combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + +# # 形態學處理眼睛 +# combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) +# closing_filter = sitk.BinaryMorphologicalClosingImageFilter() +# closing_filter.SetKernelRadius(2) +# processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + +# # 計算眼睛的Dice +# eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) +# eye_time = time.time() - start_time +# total_time += eye_time +# results['combined_eye'] = eye_dice +# print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + +# # 分離眼睛 +# right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") +# right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + +# # 處理視神經 +# start_time = time.time() +# combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) +# combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + +# # 形態學處理視神經 +# combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) +# processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + +# # 計算視神經的Dice +# nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) +# nerve_time = time.time() - start_time +# total_time += nerve_time +# results['combined_nerve'] = nerve_dice +# print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + +# # 分離視神經 +# right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") +# right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + +# # 計算各個標籤的Dice +# for label in labels: +# start_time = time.time() + +# if label == 1: # Brainstem +# gt_binary = (gt_img == label).astype(np.uint8) +# pred_binary = (pred_img == label).astype(np.uint8) +# elif label == 2 and right_pred_eye is not None: # Right Eye +# gt_binary = right_gt_eye +# pred_binary = right_pred_eye +# elif label == 3 and left_pred_eye is not None: # Left Eye +# gt_binary = left_gt_eye +# pred_binary = left_pred_eye +# elif label == 4: # Optic Chiasm +# gt_binary = (gt_img == label).astype(np.uint8) +# pred_binary = (pred_img == label).astype(np.uint8) +# elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve +# gt_binary = right_gt_nerve +# pred_binary = right_pred_nerve +# elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve +# gt_binary = left_gt_nerve +# pred_binary = left_pred_nerve +# else: +# continue + +# try: +# dice = compute_dice(pred_binary, gt_binary) +# compute_time = time.time() - start_time +# total_time += compute_time +# results[label] = dice +# print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") +# except Exception as e: +# print(f"處理標籤 {label} 時出錯:{str(e)}") +# results[label] = np.nan +# continue + +# print(f"單個案例總計算時間: {total_time:.2f}秒") +# return results + +# def evaluate_fold(gt_folder, pred_folder, labels): +# """評估單個fold的所有案例""" +# fold_results = {label: [] for label in labels} +# fold_results['combined_eye'] = [] +# fold_results['combined_nerve'] = [] + +# pred_files = sorted([f for f in os.listdir(pred_folder) if f.endswith('.nii.gz')]) + +# for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(pred_folder)}"): +# gt_path = os.path.join(gt_folder, pred_file) +# pred_path = os.path.join(pred_folder, pred_file) + +# if not os.path.exists(gt_path): +# print(f"找不到對應的真實標籤文件:{gt_path}") +# continue + +# case_results = evaluate_case(gt_path, pred_path, labels) + +# # 收集結果 +# for key in case_results: +# if not np.isnan(case_results[key]): +# if key in fold_results: +# fold_results[key].append(case_results[key]) + +# return fold_results + +# def evaluate_model(gt_folder, base_pred_folder, model_name, labels, organ_names): +# """評估單個模型的所有fold""" +# print(f"\n開始評估模型:{model_name}") +# all_results = {label: [] for label in labels} +# all_results['combined_eye'] = [] +# all_results['combined_nerve'] = [] +# fold_results = {} + +# # 處理 fold_0 到 fold_4 +# for fold in range(5): +# print(f"\n處理 {model_name} - Fold {fold}") +# pred_folder = os.path.join(base_pred_folder, f"fold_{fold}") +# if not os.path.exists(pred_folder): +# print(f"找不到預測文件夾:{pred_folder}") +# continue + +# results = evaluate_fold(gt_folder, pred_folder, labels) +# fold_results[f"fold_{fold}"] = results + +# # 合併結果 +# for key in results: +# all_results[key].extend(results[key]) + +# # 計算總平均值和標準差 +# summary = {} +# for key in all_results: +# if all_results[key]: +# mean_dice = np.mean(all_results[key]) +# std_dice = np.std(all_results[key]) +# summary[key] = { +# 'mean_dice': float(mean_dice), +# 'std_dice': float(std_dice), +# 'n_samples': len(all_results[key]) +# } +# else: +# summary[key] = { +# 'mean_dice': np.nan, +# 'std_dice': np.nan, +# 'n_samples': 0 +# } + +# # 保存結果到指定路径 +# save_dir = "/mnt/1248/onlylian/CTC/Dice/test" +# os.makedirs(save_dir, exist_ok=True) +# filename = os.path.join(save_dir, f'CTC_test_dice_results_{model_name}.json') + +# model_results = { +# 'summary': summary, +# 'fold_results': fold_results +# } + +# with open(filename, 'w') as f: +# json.dump(model_results, f, indent=4) +# print(f"\n{model_name}結果已保存到 {filename}") + +# # 打印結果 +# print(f"\n{model_name} 模型的 Dice 結果(所有fold的平均值):") + +# # 打印器官結果 +# for label in labels: +# if label in summary: +# mean = summary[label]['mean_dice'] +# std = summary[label]['std_dice'] +# n = summary[label]['n_samples'] +# organ_name = organ_names.get(label, f"Label_{label}") +# if not np.isnan(mean): +# print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") +# else: +# print(f"{organ_name}: 無法計算 (n={n})") + +# # 打印合併結果 +# print("\n合併結構的結果:") +# for key in ['combined_eye', 'combined_nerve']: +# if key in summary: +# mean = summary[key]['mean_dice'] +# std = summary[key]['std_dice'] +# n = summary[key]['n_samples'] +# name = "合併眼睛" if key == 'combined_eye' else "合併視神經" +# if not np.isnan(mean): +# print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") +# else: +# print(f"{name}: 無法計算 (n={n})") + +# return summary + +# if __name__ == "__main__": +# # 設置路徑和標籤 +# gt_folder = "/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset666/labelsTs" +# models = { +# '2D': '/mnt/1248/onlylian/CTC/output_predictions/2d', +# '3D_fullres': '/mnt/1248/onlylian/CTC/output_predictions/3d_fullres', +# '3D_lowres': '/mnt/1248/onlylian/CTC/output_predictions/3d_lowres' +# } +# labels = [1, 2, 3, 4, 5, 6] +# organ_names = { +# 1: "Brainstem", +# 2: "Right_Eye", +# 3: "Left_Eye", +# 4: "Optic_Chiasm", +# 5: "Right_Optic_Nerve", +# 6: "Left_Optic_Nerve" +# } + +# # 讓用戶選擇要運行哪個模型 +# print("可用的模型:") +# for i, model_name in enumerate(models.keys(), 1): +# print(f"{i}. {model_name}") + +# choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") +# try: +# model_index = int(choice) - 1 +# model_names = list(models.keys()) +# selected_model = model_names[model_index] +# evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names) +# except (ValueError, IndexError): +# print("無效的選擇!請輸入1-3之間的數字。") + +import json +import os +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import time + +def load_nifti(file_path): + """加載 NIfTI 文件""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """計算Dice係數""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """分離左右結構""" + # 使用SimpleITK的ConnectedComponent + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + # 計算連通區域數量和大小 + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: # 背景算一個,所以至少需要3個 + print(f"警告:{structure_name} 未找到足夠的連通區域") + return None, None + + # 排除背景(標籤0)並按大小排序 + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"警告:{structure_name} 未找到兩個主要區域") + return None, None + + # 獲取兩個最大區域 + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + # 計算質心 + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) # 使用x軸(第三維度)的平均值 + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + # 根據x軸位置確定左右 + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """評估單個案例""" + print(f"\n測試文件: {os.path.basename(pred_path)}") + + # 加載圖像 + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"圖像大小: {gt_img.shape}") + print(f"加載時間: {load_time:.2f}秒") + + results = {} + total_time = 0 + + # 處理眼睛 + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + # 形態學處理眼睛 + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + # 計算眼睛的Dice + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + + # 分離眼睛 + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + + # 處理視神經 + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + # 形態學處理視神經 + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + # 計算視神經的Dice + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + + # 分離視神經 + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + + # 計算各個標籤的Dice + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") + except Exception as e: + print(f"處理標籤 {label} 時出錯:{str(e)}") + results[label] = np.nan + continue + + print(f"單個案例總計算時間: {total_time:.2f}秒") + return results + +def evaluate_fold(gt_folder, pred_folder, labels): + """評估單個fold的所有案例""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(pred_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(pred_folder)}"): + gt_path = os.path.join(gt_folder, pred_file) + pred_path = os.path.join(pred_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"找不到對應的真實標籤文件:{gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + + # 收集結果 + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, base_pred_folder, model_name, labels, organ_names): + """評估單個模型的所有fold""" + print(f"\n開始評估模型:{model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + # 處理 fold_0 到 fold_4 + for fold in range(5): + print(f"\n處理 {model_name} - Fold {fold}") + pred_folder = os.path.join(base_pred_folder, f"fold_{fold}") + if not os.path.exists(pred_folder): + print(f"找不到預測文件夾:{pred_folder}") + continue + + results = evaluate_fold(gt_folder, pred_folder, labels) + fold_results[f"fold_{fold}"] = results + + # 合併結果 + for key in results: + all_results[key].extend(results[key]) + + # 計算總平均值和標準差 + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + # 保存結果到指定路径 + save_dir = "/mnt/1248/onlylian/FLAIR/Dice/test" + os.makedirs(save_dir, exist_ok=True) + filename = os.path.join(save_dir, f'FLAIR_test_dice_results_{model_name}.json') + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\n{model_name}結果已保存到 {filename}") + + # 打印結果 + print(f"\n{model_name} 模型的 Dice 結果(所有fold的平均值):") + + # 打印器官結果 + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: 無法計算 (n={n})") + + # 打印合併結果 + print("\n合併結構的結果:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "合併眼睛" if key == 'combined_eye' else "合併視神經" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: 無法計算 (n={n})") + + return summary + +if __name__ == "__main__": + # 設置路徑和標籤 + gt_folder = "/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset777/labelsTs" + models = { + '2D': '/mnt/1248/onlylian/FLAIR/output_predictions/2d', + '3D_fullres': '/mnt/1248/onlylian/FLAIR/output_predictions/3d_fullres', + '3D_lowres': '/mnt/1248/onlylian/FLAIR/output_predictions/3d_lowres' + } + labels = [1, 2, 3, 4, 5, 6] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + + # 讓用戶選擇要運行哪個模型 + print("可用的模型:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names) + except (ValueError, IndexError): + print("無效的選擇!請輸入1-3之間的數字。") + diff --git a/FLAIR/Dice/validation/compare_validation_dice_scores.py b/FLAIR/Dice/validation/compare_validation_dice_scores.py new file mode 100644 index 0000000..b760d71 --- /dev/null +++ b/FLAIR/Dice/validation/compare_validation_dice_scores.py @@ -0,0 +1,142 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# 設置中文字體支持 +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """讀取JSON數據文件""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"文件未找到: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON解析錯誤: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """從數據中提取特定器官的分數""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """創建和保存圖表""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('FLAIR', fontsize=12, pad=10) # Changed title + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # 添加數值標籤 + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height + 0.5, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6) # Reduced font size + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """打印數值結果""" + print("\n平均 Dice 分數 (百分比):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # 定義路徑和器官映射 + base_path = '/mnt/1248/onlylian/FLAIR/Dice/validation' + paths = { + '2D': os.path.join(base_path, 'FLAIR_validation_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'FLAIR_validation_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'FLAIR_validation_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve" + } + + # 初始化數據存儲 + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # 讀取和處理數據 + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # 計算平均值和標準差 + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # 創建圖表並保存 + output_path = os.path.join(base_path, 'FLAIR_validation_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # 打印結果 + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/FLAIR/Dice/validation/compute_validation_dice.py b/FLAIR/Dice/validation/compute_validation_dice.py new file mode 100644 index 0000000..f6a5780 --- /dev/null +++ b/FLAIR/Dice/validation/compute_validation_dice.py @@ -0,0 +1,288 @@ +import os +import time +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import json + +def load_nifti(file_path): + """加載 NIfTI 文件""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """計算Dice係數""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """分離左右結構""" + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: + print(f"警告:{structure_name} 未找到足夠的連通區域") + return None, None + + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"警告:{structure_name} 未找到兩個主要區域") + return None, None + + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """評估單個案例""" + print(f"\n測試文件: {os.path.basename(pred_path)}") + + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"圖像大小: {gt_img.shape}") + print(f"加載時間: {load_time:.2f}秒") + + results = {} + total_time = 0 + + # 處理眼睛 + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + + # 處理視神經 + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") + except Exception as e: + print(f"處理標籤 {label} 時出錯:{str(e)}") + results[label] = np.nan + continue + + print(f"單個案例總計算時間: {total_time:.2f}秒") + return results + +def evaluate_validation_fold(gt_base_folder, validation_folder, labels): + """評估單個fold的驗證集""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(validation_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(validation_folder)}"): + patient_id = pred_file.split('.nii.gz')[0] + gt_path = os.path.join(gt_base_folder, f"{patient_id}.nii.gz") + pred_path = os.path.join(validation_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"找不到對應的真實標籤文件:{gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, model_path, model_name, labels, organ_names, output_dir): + """評估模型所有fold的驗證集""" + print(f"\n開始評估模型:{model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + for fold in range(5): + validation_folder = os.path.join(model_path, f"fold_{fold}", "validation") + + if not os.path.exists(validation_folder): + print(f"找不到fold_{fold}的驗證資料夾:{validation_folder}") + continue + + print(f"\n處理 {model_name} - Fold {fold} 驗證集") + results = evaluate_validation_fold(gt_folder, validation_folder, labels) + fold_results[f"fold_{fold}"] = results + + for key in results: + all_results[key].extend(results[key]) + + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + output_path = os.path.join(output_dir, 'FLAIR', 'Dice', 'validation') + os.makedirs(output_path, exist_ok=True) + + filename = os.path.join(output_path, f'dice_results_{model_name}.json') + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\n{model_name}驗證集結果已保存到 {filename}") + + print(f"\n{model_name} 模型的驗證集 Dice 結果(所有fold的平均值):") + + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: 無法計算 (n={n})") + + print("\n合併結構的結果:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "合併眼睛" if key == 'combined_eye' else "合併視神經" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: 無法計算 (n={n})") + + return summary + +if __name__ == "__main__": + base_dir = "/mnt/1248/onlylian" + gt_folder = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset777/labelsTr") + output_dir = base_dir + + models = { + '2D': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset777/nnUNetTrainer__nnUNetPlans__2d"), + '3D_fullres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset777/nnUNetTrainer__nnUNetPlans__3d_fullres"), + '3D_lowres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset777/nnUNetTrainer__nnUNetPlans__3d_lowres") + } + + labels = [1, 2, 3, 4, 5, 6] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + + print("可用的模型:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names, output_dir) + except (ValueError, IndexError): + print("無效的選擇!請輸入1-3之間的數字。") \ No newline at end of file diff --git a/T1/Dice/test/compare_test_dice_scores.py b/T1/Dice/test/compare_test_dice_scores.py new file mode 100644 index 0000000..9d6d855 --- /dev/null +++ b/T1/Dice/test/compare_test_dice_scores.py @@ -0,0 +1,143 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# 設置中文字體支持 +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """讀取JSON數據文件""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"文件未找到: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON解析錯誤: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """從數據中提取特定器官的分數""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """創建和保存圖表""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('T1', fontsize=12, pad=10) # Changed title + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # 調整數值標籤位置和大小 + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6, # Reduced font size + rotation=0) # Optional: rotate text if still crowded + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """打印數值結果""" + print("\n平均 Dice 分數 (百分比):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # 定義路徑和器官映射 + base_path = '/mnt/1248/onlylian/T1/Dice/test' + paths = { + '2D': os.path.join(base_path, 'T1_test_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'T1_test_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'T1_test_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve" + } + + # 初始化數據存儲 + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # 讀取和處理數據 + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # 計算平均值和標準差 + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # 創建圖表並保存 + output_path = os.path.join(base_path, 'T1_test_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # 打印結果 + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/T1/Dice/test/compute_test_dice.py b/T1/Dice/test/compute_test_dice.py new file mode 100644 index 0000000..125de14 --- /dev/null +++ b/T1/Dice/test/compute_test_dice.py @@ -0,0 +1,282 @@ +import json +import os +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import time + +def load_nifti(file_path): + """加載 NIfTI 文件""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """計算Dice係數""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """分離左右結構""" + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: + print(f"警告:{structure_name} 未找到足夠的連通區域") + return None, None + + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"警告:{structure_name} 未找到兩個主要區域") + return None, None + + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """評估單個案例""" + print(f"\n測試文件: {os.path.basename(pred_path)}") + + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"圖像大小: {gt_img.shape}") + print(f"加載時間: {load_time:.2f}秒") + + results = {} + total_time = 0 + + # 處理眼睛 + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + + # 處理視神經 + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") + except Exception as e: + print(f"處理標籤 {label} 時出錯:{str(e)}") + results[label] = np.nan + continue + + print(f"單個案例總計算時間: {total_time:.2f}秒") + return results + +def evaluate_fold(gt_folder, pred_folder, labels): + """評估單個fold的所有案例""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(pred_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(pred_folder)}"): + gt_path = os.path.join(gt_folder, pred_file) + pred_path = os.path.join(pred_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"找不到對應的真實標籤文件:{gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, base_pred_folder, model_name, labels, organ_names): + """評估單個模型的所有fold""" + print(f"\n開始評估模型:{model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + for fold in range(5): + print(f"\n處理 {model_name} - Fold {fold}") + pred_folder = os.path.join(base_pred_folder, f"fold_{fold}") + if not os.path.exists(pred_folder): + print(f"找不到預測文件夾:{pred_folder}") + continue + + results = evaluate_fold(gt_folder, pred_folder, labels) + fold_results[f"fold_{fold}"] = results + + for key in results: + all_results[key].extend(results[key]) + + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + save_dir = "/mnt/1248/onlylian/T1/Dice/test" + os.makedirs(save_dir, exist_ok=True) + filename = os.path.join(save_dir, f'T1_test_dice_results_{model_name}.json') + + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\n{model_name}結果已保存到 {filename}") + + print(f"\n{model_name} 模型的 Dice 結果(所有fold的平均值):") + + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: 無法計算 (n={n})") + + print("\n合併結構的結果:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "合併眼睛" if key == 'combined_eye' else "合併視神經" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: 無法計算 (n={n})") + + return summary + +if __name__ == "__main__": + gt_folder = "/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset888/labelsTs" + models = { + '2D': '/mnt/1248/onlylian/T1/output_predictions/2d', + '3D_fullres': '/mnt/1248/onlylian/T1/output_predictions/3d_fullres', + '3D_lowres': '/mnt/1248/onlylian/T1/output_predictions/3d_lowres' + } + labels = [1, 2, 3, 4, 5, 6] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + + print("可用的模型:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names) + except (ValueError, IndexError): + print("無效的選擇!請輸入1-3之間的數字。") \ No newline at end of file diff --git a/T1/Dice/validation/compare_validation_dice_scores.py b/T1/Dice/validation/compare_validation_dice_scores.py new file mode 100644 index 0000000..6deac44 --- /dev/null +++ b/T1/Dice/validation/compare_validation_dice_scores.py @@ -0,0 +1,142 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# 設置中文字體支持 +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """讀取JSON數據文件""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"文件未找到: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON解析錯誤: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """從數據中提取特定器官的分數""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """創建和保存圖表""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('T1', fontsize=12, pad=10) # Changed title from FLAIR to T1 + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # 添加數值標籤 + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height + 0.5, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6) # Reduced font size + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """打印數值結果""" + print("\n平均 Dice 分數 (百分比):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # 定義路徑和器官映射 + base_path = '/mnt/1248/onlylian/T1/Dice/validation' # Changed from FLAIR to T1 + paths = { + '2D': os.path.join(base_path, 'T1_validation_dice_results_2D.json'), # Changed FLAIR to T1 + '3D_fullres': os.path.join(base_path, 'T1_validation_dice_results_3D_fullres.json'), # Changed FLAIR to T1 + '3D_lowres': os.path.join(base_path, 'T1_validation_dice_results_3D_lowres.json') # Changed FLAIR to T1 + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve" + } + + # 初始化數據存儲 + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # 讀取和處理數據 + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # 計算平均值和標準差 + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # 創建圖表並保存 + output_path = os.path.join(base_path, 'T1_validation_dice_scores_comparison.png') # Changed FLAIR to T1 + create_plot(model_averages, model_stds, organs, output_path) + + # 打印結果 + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/T1/Dice/validation/compute_validation_dice.py b/T1/Dice/validation/compute_validation_dice.py new file mode 100644 index 0000000..45d25b2 --- /dev/null +++ b/T1/Dice/validation/compute_validation_dice.py @@ -0,0 +1,288 @@ +import os +import time +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import json + +def load_nifti(file_path): + """Load a NIfTI file""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """Compute Dice coefficient""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """Separate left and right structures""" + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: + print(f"Warning: Not enough connected regions found for {structure_name}") + return None, None + + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"Warning: Only one major region found for {structure_name}") + return None, None + + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """Evaluate a single case""" + print(f"\nTest file: {os.path.basename(pred_path)}") + + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"Image size: {gt_img.shape}") + print(f"Loading time: {load_time:.2f}s") + + results = {} + total_time = 0 + + # Process eyes + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"Eyes combined computation time: {eye_time:.2f}s, Dice: {eye_dice:.4f}") + + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "Eyes") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "Eyes") + + # Process optic nerves + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"Optic nerves combined computation time: {nerve_time:.2f}s, Dice: {nerve_dice:.4f}") + + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "Optic Nerves") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "Optic Nerves") + + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"Label {label} computation time: {compute_time:.2f}s, Dice: {dice:.4f}") + except Exception as e: + print(f"Error processing label {label}: {str(e)}") + results[label] = np.nan + continue + + print(f"Total computation time for this case: {total_time:.2f}s") + return results + +def evaluate_validation_fold(gt_base_folder, validation_folder, labels): + """Evaluate the validation set for a single fold""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(validation_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(validation_folder)}"): + patient_id = pred_file.split('.nii.gz')[0] + gt_path = os.path.join(gt_base_folder, f"{patient_id}.nii.gz") + pred_path = os.path.join(validation_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"Could not find corresponding ground truth file: {gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, model_path, model_name, labels, organ_names, output_dir): + """Evaluate the validation set for all folds of a model""" + print(f"\nStarting evaluation of model: {model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + for fold in range(5): + validation_folder = os.path.join(model_path, f"fold_{fold}", "validation") + + if not os.path.exists(validation_folder): + print(f"Could not find fold_{fold} validation folder: {validation_folder}") + continue + + print(f"\nProcessing {model_name} - Fold {fold} validation set") + results = evaluate_validation_fold(gt_folder, validation_folder, labels) + fold_results[f"fold_{fold}"] = results + + for key in results: + all_results[key].extend(results[key]) + + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + output_path = os.path.join(output_dir, 'T1', 'Dice', 'validation') + os.makedirs(output_path, exist_ok=True) + + filename = os.path.join(output_path, f'dice_results_{model_name}.json') + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\nValidation set results for {model_name} saved to {filename}") + + print(f"\nValidation set Dice results for {model_name} (average across all folds):") + + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: Unable to compute (n={n})") + + print("\nResults for combined structures:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "Combined Eyes" if key == 'combined_eye' else "Combined Optic Nerves" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: Unable to compute (n={n})") + + return summary + +if __name__ == "__main__": + base_dir = "/mnt/1248/onlylian" + gt_folder = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset888/labelsTr") + output_dir = base_dir + + models = { + '2D': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset888/nnUNetTrainer__nnUNetPlans__2d"), + '3D_fullres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset888/nnUNetTrainer__nnUNetPlans__3d_fullres"), + '3D_lowres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset888/nnUNetTrainer__nnUNetPlans__3d_lowres") + } + + labels = [1, 2, 3, 4, 5, 6] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + + print("Available models:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\nPlease select a model to run (enter 1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names, output_dir) + except (ValueError, IndexError): + print("Invalid choice! Please enter a number between 1 and 3.") \ No newline at end of file diff --git a/T1/visualize_medical_compare.py b/T1/visualize_medical_compare.py new file mode 100644 index 0000000..e7c7ede --- /dev/null +++ b/T1/visualize_medical_compare.py @@ -0,0 +1,331 @@ +# import nibabel as nib +# import matplotlib.pyplot as plt +# import numpy as np +# import os + +# def load_nifti(file_path): +# """載入NIfTI檔案""" +# return nib.load(file_path).get_fdata() + +# def create_colored_mask(data): +# """創建彩色遮罩""" +# color_dict = { +# 1: [144/255, 238/255, 144/255], # Brainstem - 綠色 +# 2: [255/255, 218/255, 150/255], # Right_Eye - 淺黃色 +# 3: [205/255, 170/255, 125/255], # Left_Eye - 棕色 +# 4: [135/255, 206/255, 235/255], # Optic_Chiasm - 藍色 +# 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - 亮珊瑚紅 +# 6: [255/255, 160/255, 122/255] # Left_Optic_Nerve - 淺珊瑚紅 +# } + +# mask = np.zeros((*data.shape, 4)) + +# for label, color in color_dict.items(): +# organ_mask = (data == label) +# if np.any(organ_mask): +# mask[organ_mask] = [*color, 1.0] + +# return mask + +# def get_brain_bounds(image): +# """獲取腦部區域的邊界""" +# mask = image > np.percentile(image, 1) +# rows = np.any(mask, axis=1) +# cols = np.any(mask, axis=0) +# rmin, rmax = np.where(rows)[0][[0, -1]] +# cmin, cmax = np.where(cols)[0][[0, -1]] + +# center_r = (rmin + rmax) // 2 +# center_c = (cmin + cmax) // 2 + +# radius = int(max(rmax - rmin, cmax - cmin) * 0.55) +# margin = int(radius * 0.15) + +# rmin = max(center_r - radius - margin, 0) +# rmax = min(center_r + radius + margin, image.shape[0]) +# cmin = max(center_c - radius - margin, 0) +# cmax = min(center_c + radius + margin, image.shape[1]) + +# return rmin, rmax, cmin, cmax + +# def check_organs_in_slice(slice_data): +# """檢查切片中存在的器官標籤""" +# unique_labels = set(np.unique(slice_data)) +# if 0 in unique_labels: +# unique_labels.remove(0) +# organ_dict = { +# 1: "Brainstem", +# 2: "Right_Eye", +# 3: "Left_Eye", +# 4: "Optic_Chiasm", +# 5: "Right_Optic_Nerve", +# 6: "Left_Optic_Nerve" +# } +# return {organ_dict[label] for label in unique_labels if label in organ_dict} + +# def save_slice(image, ground_truth, prediction, slice_num, save_path): +# """保存指定切片的比較圖""" +# plt.clf() +# fig, axes = plt.subplots(1, 3, figsize=(30, 10)) +# plt.subplots_adjust(wspace=0.01) + +# for ax in axes: +# ax.axis('off') + +# img_slice = np.rot90(image[:, :, slice_num]) +# rmin, rmax, cmin, cmax = get_brain_bounds(img_slice) + +# # 調整圖像對比度 +# p2, p98 = np.percentile(img_slice, (2, 98)) +# img_slice = np.clip(img_slice, p2, p98) +# img_slice = (img_slice - p2) / (p98 - p2) + +# # 原始圖像 +# axes[0].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray') +# axes[0].set_title('Original Image', fontsize=32, pad=20, weight='bold') + +# # Ground Truth +# gt_slice = np.rot90(ground_truth[:, :, slice_num]) +# gt_mask = create_colored_mask(gt_slice) + +# axes[1].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) +# axes[1].imshow(gt_mask[rmin:rmax, cmin:cmax], zorder=2) +# axes[1].set_title('Ground Truth', fontsize=32, pad=20, weight='bold') + +# # Prediction +# pred_slice = np.rot90(prediction[:, :, slice_num]) +# pred_mask = create_colored_mask(pred_slice) + +# axes[2].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) +# axes[2].imshow(pred_mask[rmin:rmax, cmin:cmax], zorder=2) +# axes[2].set_title('Prediction', fontsize=32, pad=20, weight='bold') + +# plt.savefig(save_path, bbox_inches='tight', dpi=300, pad_inches=0.05) +# plt.close() +# print(f"已保存切片 {slice_num} 至: {save_path}") + +# if __name__ == "__main__": +# base_dir = "/mnt/1248/onlylian" +# image_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset888/imagesTs/T1_069_0000.nii.gz") +# gt_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset888/labelsTs/T1_069.nii.gz") +# pred_path = os.path.join(base_dir, "T1/output_predictions/T1_069.nii.gz") +# save_dir = os.path.join(base_dir, "T1") + +# os.makedirs(save_dir, exist_ok=True) + +# print("正在載入影像...") +# image = load_nifti(image_path) +# ground_truth = load_nifti(gt_path) +# prediction = load_nifti(pred_path) +# print("影像載入完成") + +# print("\n分析切片中的器官...") +# total_slices = image.shape[2] +# for slice_num in range(total_slices): +# gt_slice = ground_truth[:, :, slice_num] +# pred_slice = prediction[:, :, slice_num] + +# gt_organs = check_organs_in_slice(gt_slice) +# pred_organs = check_organs_in_slice(pred_slice) + +# if gt_organs or pred_organs: +# print(f"\n切片 {slice_num}:") +# print(f"Ground Truth 包含: {', '.join(sorted(gt_organs))}") +# print(f"Prediction 包含: {', '.join(sorted(pred_organs))}") + +# print("\n開始切片選擇和保存過程...") +# while True: +# try: +# slice_num = int(input("\n請輸入要保存的切片編號 (-1 退出): ")) +# if slice_num == -1: +# print("程序結束") +# break +# if 0 <= slice_num < total_slices: +# save_path = os.path.join(save_dir, f'comparison_T1_069_{slice_num:03d}.png') +# save_slice(image, ground_truth, prediction, slice_num, save_path) +# else: +# print(f"切片編號必須在 0 到 {total_slices-1} 之間") +# except ValueError: +# print("請輸入有效的數字") +# except Exception as e: +# print(f"發生錯誤: {str(e)}") + +import nibabel as nib +import matplotlib.pyplot as plt +import numpy as np +import os + +def load_nifti(file_path): + """載入NIfTI檔案""" + return nib.load(file_path).get_fdata() + +def create_colored_mask(data): + """創建彩色遮罩""" + color_dict = { + 1: [144/255, 238/255, 144/255], # Brainstem - 綠色 + 2: [255/255, 218/255, 150/255], # Right_Eye - 淺黃色 + 3: [205/255, 170/255, 125/255], # Left_Eye - 棕色 + 4: [135/255, 206/255, 235/255], # Optic_Chiasm - 藍色 + 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - 亮珊瑚紅 + 6: [255/255, 160/255, 122/255] # Left_Optic_Nerve - 淺珊瑚紅 + } + + mask = np.zeros((*data.shape, 4)) + + for label, color in color_dict.items(): + organ_mask = (data == label) + if np.any(organ_mask): + mask[organ_mask] = [*color, 1.0] + + return mask + +def get_brain_bounds(image): + """獲取腦部區域的邊界,並調整顯示位置以避開底部基架""" + # 基本閾值處理 + mask = image > np.percentile(image, 1) + + # 獲取非零區域的邊界 + rows = np.any(mask, axis=1) + cols = np.any(mask, axis=0) + + # 確保有足夠的點 + if not np.any(rows) or not np.any(cols): + return 0, image.shape[0], 0, image.shape[1] + + row_indices = np.where(rows)[0] + col_indices = np.where(cols)[0] + + # 獲取初始邊界 + rmin, rmax = row_indices[[0, -1]] + cmin, cmax = col_indices[[0, -1]] + + # 計算中心點,但略微上移以避開基架 + center_r = (rmin + rmax) // 2 - int(image.shape[0] * 0.15) # 上移15% + center_c = (cmin + cmax) // 2 + + # 計算顯示範圍 + height = image.shape[0] + width = image.shape[1] + display_size = int(min(height, width) * 0.7) # 使用70%的圖像大小 + + # 設置新的邊界,確保不會超出圖像範圍 + rmin = max(center_r - display_size//2, 0) + rmax = min(center_r + display_size//2, height - int(height * 0.2)) # 留出底部空間 + cmin = max(center_c - display_size//2, 0) + cmax = min(center_c + display_size//2, width) + + # 如果上邊界太小,適當下移整個顯示區域 + if rmin < height * 0.1: + shift = int(height * 0.1) - rmin + rmin += shift + rmax += shift + + return rmin, rmax, cmin, cmax + +def apply_brain_window(image, window_width=80, window_level=40): + """應用brain window設置""" + window_min = window_level - window_width/2 + window_max = window_level + window_width/2 + windowed = np.clip(image, window_min, window_max) + windowed = (windowed - window_min) / (window_max - window_min) + return windowed + +def check_organs_in_slice(slice_data): + """檢查切片中存在的器官標籤""" + unique_labels = set(np.unique(slice_data)) + if 0 in unique_labels: + unique_labels.remove(0) + organ_dict = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + return {organ_dict[label] for label in unique_labels if label in organ_dict} + +def save_slice(image, ground_truth, prediction, slice_num, save_path): + """保存指定切片的比較圖""" + plt.clf() + fig, axes = plt.subplots(1, 3, figsize=(24, 8)) + plt.subplots_adjust(wspace=0.01) + + for ax in axes: + ax.axis('off') + + img_slice = np.rot90(image[:, :, slice_num]) + rmin, rmax, cmin, cmax = get_brain_bounds(img_slice) + + # 使用brain window設置 + img_slice = apply_brain_window(img_slice) + + # 原始圖像 + axes[0].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray') + axes[0].set_title('Original Image', fontsize=32, pad=20, weight='bold') + + # Ground Truth + gt_slice = np.rot90(ground_truth[:, :, slice_num]) + gt_mask = create_colored_mask(gt_slice) + + axes[1].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[1].imshow(gt_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[1].set_title('Ground Truth', fontsize=32, pad=20, weight='bold') + + # Prediction + pred_slice = np.rot90(prediction[:, :, slice_num]) + pred_mask = create_colored_mask(pred_slice) + + axes[2].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[2].imshow(pred_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[2].set_title('Prediction', fontsize=32, pad=20, weight='bold') + + plt.savefig(save_path, bbox_inches='tight', dpi=300, pad_inches=0.05) + plt.close() + print(f"已保存切片 {slice_num} 至: {save_path}") + +if __name__ == "__main__": + base_dir = "/mnt/1248/onlylian" + image_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset888/imagesTs/T1_069_0000.nii.gz") + gt_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset888/labelsTs/T1_069.nii.gz") + pred_path = os.path.join(base_dir, "T1/output_predictions/T1_069.nii.gz") + save_dir = os.path.join(base_dir, "T1") + + os.makedirs(save_dir, exist_ok=True) + + print("正在載入影像...") + image = load_nifti(image_path) + ground_truth = load_nifti(gt_path) + prediction = load_nifti(pred_path) + print("影像載入完成") + + print("\n分析切片中的器官...") + total_slices = image.shape[2] + for slice_num in range(total_slices): + gt_slice = ground_truth[:, :, slice_num] + pred_slice = prediction[:, :, slice_num] + + gt_organs = check_organs_in_slice(gt_slice) + pred_organs = check_organs_in_slice(pred_slice) + + if gt_organs or pred_organs: + print(f"\n切片 {slice_num}:") + print(f"Ground Truth 包含: {', '.join(sorted(gt_organs))}") + print(f"Prediction 包含: {', '.join(sorted(pred_organs))}") + + print("\n開始切片選擇和保存過程...") + while True: + try: + slice_num = int(input("\n請輸入要保存的切片編號 (-1 退出): ")) + if slice_num == -1: + print("程序結束") + break + if 0 <= slice_num < total_slices: + save_path = os.path.join(save_dir, f'comparison_T1_069_{slice_num:03d}.png') + save_slice(image, ground_truth, prediction, slice_num, save_path) + else: + print(f"切片編號必須在 0 到 {total_slices-1} 之間") + except ValueError: + print("請輸入有效的數字") + except Exception as e: + print(f"發生錯誤: {str(e)}") \ No newline at end of file diff --git a/T1C/Dice/test/compare_test_dice_scores.py b/T1C/Dice/test/compare_test_dice_scores.py new file mode 100644 index 0000000..5789dba --- /dev/null +++ b/T1C/Dice/test/compare_test_dice_scores.py @@ -0,0 +1,143 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# 設置中文字體支持 +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """讀取JSON數據文件""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"文件未找到: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON解析錯誤: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """從數據中提取特定器官的分數""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """創建和保存圖表""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('T1C', fontsize=12, pad=10) # Changed title + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # 調整數值標籤位置和大小 + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6, # Reduced font size + rotation=0) # Optional: rotate text if still crowded + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """打印數值結果""" + print("\n平均 Dice 分數 (百分比):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # 定義路徑和器官映射 + base_path = '/mnt/1248/onlylian/T1C/Dice/test' + paths = { + '2D': os.path.join(base_path, 'T1C_test_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'T1C_test_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'T1C_test_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve" + } + + # 初始化數據存儲 + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # 讀取和處理數據 + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # 計算平均值和標準差 + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # 創建圖表並保存 + output_path = os.path.join(base_path, 'T1C_test_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # 打印結果 + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/T1C/Dice/test/compute_test_dice.py b/T1C/Dice/test/compute_test_dice.py new file mode 100644 index 0000000..ad17af4 --- /dev/null +++ b/T1C/Dice/test/compute_test_dice.py @@ -0,0 +1,282 @@ +import json +import os +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import time + +def load_nifti(file_path): + """加載 NIfTI 文件""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """計算Dice係數""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """分離左右結構""" + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: + print(f"警告:{structure_name} 未找到足夠的連通區域") + return None, None + + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"警告:{structure_name} 未找到兩個主要區域") + return None, None + + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """評估單個案例""" + print(f"\n測試文件: {os.path.basename(pred_path)}") + + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"圖像大小: {gt_img.shape}") + print(f"加載時間: {load_time:.2f}秒") + + results = {} + total_time = 0 + + # 處理眼睛 + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + + # 處理視神經 + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") + except Exception as e: + print(f"處理標籤 {label} 時出錯:{str(e)}") + results[label] = np.nan + continue + + print(f"單個案例總計算時間: {total_time:.2f}秒") + return results + +def evaluate_fold(gt_folder, pred_folder, labels): + """評估單個fold的所有案例""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(pred_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(pred_folder)}"): + gt_path = os.path.join(gt_folder, pred_file) + pred_path = os.path.join(pred_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"找不到對應的真實標籤文件:{gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, base_pred_folder, model_name, labels, organ_names): + """評估單個模型的所有fold""" + print(f"\n開始評估模型:{model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + for fold in range(5): + print(f"\n處理 {model_name} - Fold {fold}") + pred_folder = os.path.join(base_pred_folder, f"fold_{fold}") + if not os.path.exists(pred_folder): + print(f"找不到預測文件夾:{pred_folder}") + continue + + results = evaluate_fold(gt_folder, pred_folder, labels) + fold_results[f"fold_{fold}"] = results + + for key in results: + all_results[key].extend(results[key]) + + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + save_dir = "/mnt/1248/onlylian/T1C/Dice/test" + os.makedirs(save_dir, exist_ok=True) + filename = os.path.join(save_dir, f'T1C_test_dice_results_{model_name}.json') + + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\n{model_name}結果已保存到 {filename}") + + print(f"\n{model_name} 模型的 Dice 結果(所有fold的平均值):") + + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: 無法計算 (n={n})") + + print("\n合併結構的結果:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "合併眼睛" if key == 'combined_eye' else "合併視神經" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: 無法計算 (n={n})") + + return summary + +if __name__ == "__main__": + gt_folder = "/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset999/labelsTs" + models = { + '2D': '/mnt/1248/onlylian/T1C/output_predictions/2d', + '3D_fullres': '/mnt/1248/onlylian/T1C/output_predictions/3d_fullres', + '3D_lowres': '/mnt/1248/onlylian/T1C/output_predictions/3d_lowres' + } + labels = [1, 2, 3, 4, 5, 6] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + + print("可用的模型:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names) + except (ValueError, IndexError): + print("無效的選擇!請輸入1-3之間的數字。") \ No newline at end of file diff --git a/T1C/Dice/validation/compare_validation_dice_scores.py b/T1C/Dice/validation/compare_validation_dice_scores.py new file mode 100644 index 0000000..12bd280 --- /dev/null +++ b/T1C/Dice/validation/compare_validation_dice_scores.py @@ -0,0 +1,142 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# 設置中文字體支持 +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """讀取JSON數據文件""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"文件未找到: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON解析錯誤: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """從數據中提取特定器官的分數""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """創建和保存圖表""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('T1C', fontsize=12, pad=10) # Changed title from T1 to T1C + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # 添加數值標籤 + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height + 0.5, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6) # Reduced font size + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """打印數值結果""" + print("\n平均 Dice 分數 (百分比):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # 定義路徑和器官映射 + base_path = '/mnt/1248/onlylian/T1C/Dice/validation' # Changed from T1 to T1C + paths = { + '2D': os.path.join(base_path, 'T1C_validation_dice_results_2D.json'), # Changed T1 to T1C + '3D_fullres': os.path.join(base_path, 'T1C_validation_dice_results_3D_fullres.json'), # Changed T1 to T1C + '3D_lowres': os.path.join(base_path, 'T1C_validation_dice_results_3D_lowres.json') # Changed T1 to T1C + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve" + } + + # 初始化數據存儲 + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # 讀取和處理數據 + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # 計算平均值和標準差 + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # 創建圖表並保存 + output_path = os.path.join(base_path, 'T1C_validation_dice_scores_comparison.png') # Changed T1 to T1C + create_plot(model_averages, model_stds, organs, output_path) + + # 打印結果 + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/T1C/Dice/validation/compute_validation_dice.py b/T1C/Dice/validation/compute_validation_dice.py new file mode 100644 index 0000000..b461429 --- /dev/null +++ b/T1C/Dice/validation/compute_validation_dice.py @@ -0,0 +1,288 @@ +import os +import time +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import json + +def load_nifti(file_path): + """加載 NIfTI 文件""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """計算Dice係數""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """分離左右結構""" + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: + print(f"警告:{structure_name} 未找到足夠的連通區域") + return None, None + + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"警告:{structure_name} 未找到兩個主要區域") + return None, None + + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """評估單個案例""" + print(f"\n測試文件: {os.path.basename(pred_path)}") + + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"圖像大小: {gt_img.shape}") + print(f"加載時間: {load_time:.2f}秒") + + results = {} + total_time = 0 + + # 處理眼睛 + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + + # 處理視神經 + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") + except Exception as e: + print(f"處理標籤 {label} 時出錯:{str(e)}") + results[label] = np.nan + continue + + print(f"單個案例總計算時間: {total_time:.2f}秒") + return results + +def evaluate_validation_fold(gt_base_folder, validation_folder, labels): + """評估單個fold的驗證集""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(validation_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(validation_folder)}"): + patient_id = pred_file.split('.nii.gz')[0] + gt_path = os.path.join(gt_base_folder, f"{patient_id}.nii.gz") + pred_path = os.path.join(validation_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"找不到對應的真實標籤文件:{gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, model_path, model_name, labels, organ_names, output_dir): + """評估模型所有fold的驗證集""" + print(f"\n開始評估模型:{model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + for fold in range(5): + validation_folder = os.path.join(model_path, f"fold_{fold}", "validation") + + if not os.path.exists(validation_folder): + print(f"找不到fold_{fold}的驗證資料夾:{validation_folder}") + continue + + print(f"\n處理 {model_name} - Fold {fold} 驗證集") + results = evaluate_validation_fold(gt_folder, validation_folder, labels) + fold_results[f"fold_{fold}"] = results + + for key in results: + all_results[key].extend(results[key]) + + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + output_path = os.path.join(output_dir, 'T1C', 'Dice', 'validation') + os.makedirs(output_path, exist_ok=True) + + filename = os.path.join(output_path, f'dice_results_{model_name}.json') + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\n{model_name}驗證集結果已保存到 {filename}") + + print(f"\n{model_name} 模型的驗證集 Dice 結果(所有fold的平均值):") + + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: 無法計算 (n={n})") + + print("\n合併結構的結果:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "合併眼睛" if key == 'combined_eye' else "合併視神經" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: 無法計算 (n={n})") + + return summary + +if __name__ == "__main__": + base_dir = "/mnt/1248/onlylian" + gt_folder = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset999/labelsTr") + output_dir = base_dir + + models = { + '2D': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset999/nnUNetTrainer__nnUNetPlans__2d"), + '3D_fullres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset999/nnUNetTrainer__nnUNetPlans__3d_fullres"), + '3D_lowres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset999/nnUNetTrainer__nnUNetPlans__3d_lowres") + } + + labels = [1, 2, 3, 4, 5, 6] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + + print("可用的模型:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names, output_dir) + except (ValueError, IndexError): + print("無效的選擇!請輸入1-3之間的數字。") \ No newline at end of file diff --git a/T1C/low_chiasm.py b/T1C/low_chiasm.py new file mode 100644 index 0000000..27178d4 --- /dev/null +++ b/T1C/low_chiasm.py @@ -0,0 +1,149 @@ +import nibabel as nib +import matplotlib.pyplot as plt +import numpy as np +import os + +def load_nifti(file_path): + """載入NIfTI檔案""" + return nib.load(file_path).get_fdata() + +def create_colored_mask(data): + """創建彩色遮罩""" + color_dict = { + 1: [144/255, 238/255, 144/255], # Brainstem - 綠色 + 2: [255/255, 218/255, 150/255], # Right_Eye - 淺黃色 + 3: [205/255, 170/255, 125/255], # Left_Eye - 棕色 + 4: [135/255, 206/255, 235/255], # Optic_Chiasm - 藍色 + 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - 亮珊瑚紅 + 6: [255/255, 160/255, 122/255] # Left_Optic_Nerve - 淺珊瑚紅 + } + + # 創建一個具有透明背景的遮罩 + mask = np.zeros((*data.shape, 4)) # 使用4通道(RGBA) + + for label, color in color_dict.items(): + # 為每個器官設置顏色,包括完全不透明的 alpha 通道 + organ_mask = (data == label) + if np.any(organ_mask): # 只處理存在的器官 + mask[organ_mask] = [*color, 1.0] # RGB + alpha + + return mask + +def get_brain_bounds(image): + """獲取腦部區域的邊界""" + mask = image > np.percentile(image, 1) + rows = np.any(mask, axis=1) + cols = np.any(mask, axis=0) + rmin, rmax = np.where(rows)[0][[0, -1]] + cmin, cmax = np.where(cols)[0][[0, -1]] + + center_r = (rmin + rmax) // 2 + center_c = (cmin + cmax) // 2 + + radius = int(max(rmax - rmin, cmax - cmin) * 0.55) + margin = int(radius * 0.15) + + rmin = max(center_r - radius - margin, 0) + rmax = min(center_r + radius + margin, image.shape[0]) + cmin = max(center_c - radius - margin, 0) + cmax = min(center_c + radius + margin, image.shape[1]) + + return rmin, rmax, cmin, cmax + +def check_organs_in_slice(slice_data): + """檢查切片中存在的器官標籤""" + unique_labels = set(np.unique(slice_data)) + if 0 in unique_labels: + unique_labels.remove(0) + organ_dict = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + return {organ_dict[label] for label in unique_labels if label in organ_dict} + +def save_slice(image, ground_truth, prediction, slice_num, save_path): + """保存指定切片的比較圖""" + plt.clf() + fig, axes = plt.subplots(1, 2, figsize=(25, 11)) + plt.subplots_adjust(wspace=0.01) + + img_slice = np.rot90(image[:, :, slice_num]) + rmin, rmax, cmin, cmax = get_brain_bounds(img_slice) + + # 調整圖像對比度以提高可見度 + p2, p98 = np.percentile(img_slice, (2, 98)) + img_slice = np.clip(img_slice, p2, p98) + img_slice = (img_slice - p2) / (p98 - p2) + + # Ground Truth + gt_slice = np.rot90(ground_truth[:, :, slice_num]) + gt_mask = create_colored_mask(gt_slice) + + # 使用 imshow 的 zorder 參數來控制圖層順序 + axes[0].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[0].imshow(gt_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[0].set_title('Ground Truth', fontsize=32, pad=20, weight='bold') + axes[0].axis('off') + + # Prediction + pred_slice = np.rot90(prediction[:, :, slice_num]) + pred_mask = create_colored_mask(pred_slice) + + axes[1].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[1].imshow(pred_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[1].set_title('Prediction', fontsize=32, pad=20, weight='bold') + axes[1].axis('off') + + plt.savefig(save_path, bbox_inches='tight', dpi=300, pad_inches=0.05) + plt.close() + print(f"已保存切片 {slice_num} 至: {save_path}") + +if __name__ == "__main__": + base_dir = "/mnt/1248/onlylian" + image_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset999/imagesTs/T1C_043_0000.nii.gz") + gt_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset999/labelsTs/T1C_043.nii.gz") + pred_path = os.path.join(base_dir, "T1C/output_predictions/2d/fold_2/T1C_043.nii.gz") # Updated path + save_dir = os.path.join(base_dir, "T1C") + + os.makedirs(save_dir, exist_ok=True) + + print("正在載入影像...") + image = load_nifti(image_path) + ground_truth = load_nifti(gt_path) + prediction = load_nifti(pred_path) + print("影像載入完成") + + print("\n分析切片中的器官...") + total_slices = image.shape[2] + for slice_num in range(total_slices): + gt_slice = ground_truth[:, :, slice_num] + pred_slice = prediction[:, :, slice_num] + + gt_organs = check_organs_in_slice(gt_slice) + pred_organs = check_organs_in_slice(pred_slice) + + if gt_organs or pred_organs: + print(f"\n切片 {slice_num}:") + print(f"Ground Truth 包含: {', '.join(sorted(gt_organs))}") + print(f"Prediction 包含: {', '.join(sorted(pred_organs))}") + + print("\n開始切片選擇和保存過程...") + while True: + try: + slice_num = int(input("\n請輸入要保存的切片編號 (-1 退出): ")) + if slice_num == -1: + print("程序結束") + break + if 0 <= slice_num < total_slices: + save_path = os.path.join(save_dir, f'comparison_T1C_043_{slice_num:03d}.png') + save_slice(image, ground_truth, prediction, slice_num, save_path) + else: + print(f"切片編號必須在 0 到 {total_slices-1} 之間") + except ValueError: + print("請輸入有效的數字") + except Exception as e: + print(f"發生錯誤: {str(e)}") \ No newline at end of file diff --git a/T1C/visualize_medical_compare.py b/T1C/visualize_medical_compare.py new file mode 100644 index 0000000..1e6393c --- /dev/null +++ b/T1C/visualize_medical_compare.py @@ -0,0 +1,303 @@ +# import nibabel as nib +# import matplotlib.pyplot as plt +# import numpy as np +# import os +# from typing import Tuple, Set, Dict, Optional + +# def load_nifti(file_path: str) -> np.ndarray: +# """Load a NIfTI file and return its data array.""" +# return nib.load(file_path).get_fdata() + +# def create_colored_mask(data: np.ndarray) -> np.ndarray: +# """Create a colored mask for segmentation visualization. + +# Args: +# data: Input segmentation array +# Returns: +# RGB mask array +# """ +# color_dict = { +# 1: [144/255, 238/255, 144/255], # Brainstem - Light green +# 2: [255/255, 218/255, 150/255], # Right_Eye - Light yellow +# 3: [205/255, 170/255, 125/255], # Left_Eye - Brown +# 4: [135/255, 206/255, 235/255], # Optic_Chiasm - Sky blue +# 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - Bright coral +# 6: [255/255, 160/255, 122/255], # Left_Optic_Nerve - Light coral +# 7: [147/255, 112/255, 219/255] # TV - Medium purple +# } + +# mask = np.zeros((*data.shape, 3)) + +# for label, color in color_dict.items(): +# mask_3d = np.dstack([data == label] * 3) +# mask = np.where(mask_3d, color, mask) + +# return mask + +# def get_brain_bounds(image: np.ndarray, padding_factor: float = 0.15) -> Tuple[int, int, int, int]: +# """Get the boundaries of the brain region with adjustable padding. + +# Args: +# image: Input image array +# padding_factor: Factor to determine padding around the region of interest +# Returns: +# Tuple of (rmin, rmax, cmin, cmax) coordinates +# """ +# mask = image > np.percentile(image, 1) +# rows = np.any(mask, axis=1) +# cols = np.any(mask, axis=0) +# rmin, rmax = np.where(rows)[0][[0, -1]] +# cmin, cmax = np.where(cols)[0][[0, -1]] + +# center_r = (rmin + rmax) // 2 +# center_c = (cmin + cmax) // 2 + +# radius = int(max(rmax - rmin, cmax - cmin) * 0.55) +# margin = int(radius * padding_factor) + +# return ( +# max(center_r - radius - margin, 0), +# min(center_r + radius + margin, image.shape[0]), +# max(center_c - radius - margin, 0), +# min(center_c + radius + margin, image.shape[1]) +# ) + +# def check_organs_in_slice(slice_data: np.ndarray) -> Set[str]: +# """Check which organs are present in a given slice. + +# Args: +# slice_data: 2D array of the slice +# Returns: +# Set of organ names present in the slice +# """ +# organ_dict = { +# 1: "Brainstem", +# 2: "Right_Eye", +# 3: "Left_Eye", +# 4: "Optic_Chiasm", +# 5: "Right_Optic_Nerve", +# 6: "Left_Optic_Nerve", +# 7: "TV" +# } + +# unique_labels = set(np.unique(slice_data)) - {0} +# return {organ_dict[label] for label in unique_labels if label in organ_dict} + +# def save_slice(image: np.ndarray, ground_truth: np.ndarray, +# prediction: np.ndarray, slice_num: int, +# save_path: str) -> None: +# """Save comparison visualization of a specific slice. + +# Args: +# image: MRI volume +# ground_truth: Ground truth segmentation volume +# prediction: Predicted segmentation volume +# slice_num: Slice number to visualize +# save_path: Path to save the visualization +# """ +# plt.clf() +# fig, axes = plt.subplots(1, 2, figsize=(25, 11)) +# plt.subplots_adjust(wspace=0.01) + +# # Process image +# img_slice = np.rot90(image[:, :, slice_num]) + +# # Get bounds +# rmin, rmax, cmin, cmax = get_brain_bounds(img_slice) + +# # Normalize image for display +# img_norm = (img_slice - np.min(img_slice)) / (np.max(img_slice) - np.min(img_slice)) + +# # Ground Truth +# gt_slice = np.rot90(ground_truth[:, :, slice_num]) +# gt_mask = create_colored_mask(gt_slice) + +# axes[0].imshow(img_norm[rmin:rmax, cmin:cmax], cmap='gray') +# axes[0].imshow(gt_mask[rmin:rmax, cmin:cmax], alpha=0.5) +# axes[0].set_title('Ground Truth', fontsize=32, pad=20, weight='bold') +# axes[0].axis('off') + +# # Prediction +# pred_slice = np.rot90(prediction[:, :, slice_num]) +# pred_mask = create_colored_mask(pred_slice) + +# axes[1].imshow(img_norm[rmin:rmax, cmin:cmax], cmap='gray') +# axes[1].imshow(pred_mask[rmin:rmax, cmin:cmax], alpha=0.5) +# axes[1].set_title('Prediction', fontsize=32, pad=20, weight='bold') +# axes[1].axis('off') + +# plt.savefig(save_path, bbox_inches='tight', dpi=300, pad_inches=0.05) +# plt.close() +# print(f"Saved slice {slice_num} to: {save_path}") + +# def main(): +# """Main function to run the visualization pipeline.""" +# # Load images +# base_path = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset999' +# image_mri = load_nifti(os.path.join(base_path, 'imagesTs/T1C_043_0000.nii.gz')) +# ground_truth = load_nifti(os.path.join(base_path, 'labelsTs/T1C_043.nii.gz')) +# prediction = load_nifti('/mnt/1248/onlylian/T1C/output_predictions/2d/fold_2/T1C_043.nii.gz') + +# save_dir = '/mnt/1248/onlylian/T1C' + +# # Analyze slices +# total_slices = image_mri.shape[2] +# for slice_num in range(total_slices): +# gt_organs = check_organs_in_slice(ground_truth[:, :, slice_num]) +# pred_organs = check_organs_in_slice(prediction[:, :, slice_num]) + +# if gt_organs or pred_organs: +# print(f"\nSlice {slice_num}:") +# print(f"Ground Truth contains: {', '.join(sorted(gt_organs))}") +# print(f"Prediction contains: {', '.join(sorted(pred_organs))}") + +# # Interactive slice selection +# while True: +# try: +# slice_num = int(input("\nEnter slice number to save (-1 to exit): ")) +# if slice_num == -1: +# break +# if 0 <= slice_num < total_slices: +# save_path = os.path.join(save_dir, f'comparison_{slice_num:03d}.png') +# save_slice(image_mri, ground_truth, prediction, +# slice_num, save_path) +# else: +# print(f"Slice number must be between 0 and {total_slices-1}") +# except ValueError: +# print("Please enter a valid number") +# except Exception as e: +# print(f"Error occurred: {str(e)}") + +# if __name__ == "__main__": +# main() + +import nibabel as nib +import matplotlib.pyplot as plt +import numpy as np +import os +from PIL import Image +from typing import Tuple, Set, Dict, Optional +import io + +def load_nifti(file_path: str) -> np.ndarray: + """Load a NIfTI file and return its data array.""" + return nib.load(file_path).get_fdata() + +def create_colored_mask(data: np.ndarray) -> np.ndarray: + """Create a colored mask for segmentation visualization.""" + color_dict = { + 1: [144/255, 238/255, 144/255], # Brainstem - Light green + 2: [255/255, 218/255, 150/255], # Right_Eye - Light yellow + 3: [205/255, 170/255, 125/255], # Left_Eye - Brown + 4: [135/255, 206/255, 235/255], # Optic_Chiasm - Sky blue + 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - Bright coral + 6: [255/255, 160/255, 122/255], # Left_Optic_Nerve - Light coral + 7: [147/255, 112/255, 219/255] # TV - Medium purple + } + + mask = np.zeros((*data.shape, 3)) + for label, color in color_dict.items(): + mask_3d = np.dstack([data == label] * 3) + mask = np.where(mask_3d, color, mask) + return mask + +def get_brain_bounds(image: np.ndarray, padding_factor: float = 0.15) -> Tuple[int, int, int, int]: + """Get the boundaries of the brain region with adjustable padding.""" + mask = image > np.percentile(image, 1) + rows = np.any(mask, axis=1) + cols = np.any(mask, axis=0) + rmin, rmax = np.where(rows)[0][[0, -1]] + cmin, cmax = np.where(cols)[0][[0, -1]] + + center_r = (rmin + rmax) // 2 + center_c = (cmin + cmax) // 2 + radius = int(max(rmax - rmin, cmax - cmin) * 0.55) + margin = int(radius * padding_factor) + + return ( + max(center_r - radius - margin, 0), + min(center_r + radius + margin, image.shape[0]), + max(center_c - radius - margin, 0), + min(center_c + radius + margin, image.shape[1]) + ) + +def create_slice_image(image: np.ndarray, ground_truth: np.ndarray, + prediction: np.ndarray, slice_num: int) -> Image.Image: + """Create image for a specific slice.""" + plt.clf() + fig, axes = plt.subplots(1, 2, figsize=(25, 11)) + plt.subplots_adjust(wspace=0.01) + + img_slice = np.rot90(image[:, :, slice_num]) + rmin, rmax, cmin, cmax = get_brain_bounds(img_slice) + img_norm = (img_slice - np.min(img_slice)) / (np.max(img_slice) - np.min(img_slice)) + + # Ground Truth + gt_slice = np.rot90(ground_truth[:, :, slice_num]) + gt_mask = create_colored_mask(gt_slice) + + axes[0].imshow(img_norm[rmin:rmax, cmin:cmax], cmap='gray') + axes[0].imshow(gt_mask[rmin:rmax, cmin:cmax], alpha=0.5) + axes[0].set_title('Ground Truth', fontsize=32, pad=20, weight='bold') + axes[0].axis('off') + + # Prediction + pred_slice = np.rot90(prediction[:, :, slice_num]) + pred_mask = create_colored_mask(pred_slice) + + axes[1].imshow(img_norm[rmin:rmax, cmin:cmax], cmap='gray') + axes[1].imshow(pred_mask[rmin:rmax, cmin:cmax], alpha=0.5) + axes[1].set_title('Prediction', fontsize=32, pad=20, weight='bold') + axes[1].axis('off') + + # Convert plot to PIL Image + buf = io.BytesIO() + plt.savefig(buf, format='png', bbox_inches='tight', dpi=300, pad_inches=0.05) + plt.close() + buf.seek(0) + return Image.open(buf) + +def create_animated_gif(image: np.ndarray, ground_truth: np.ndarray, + prediction: np.ndarray, start_slice: int, + end_slice: int, output_path: str, + duration: int = 500) -> None: + """Create an animated GIF from a range of slices.""" + frames = [] + + for slice_num in range(start_slice, end_slice + 1): + print(f"Processing slice {slice_num}...") + frame = create_slice_image(image, ground_truth, prediction, slice_num) + frames.append(frame) + + # Save the animated GIF + frames[0].save( + output_path, + save_all=True, + append_images=frames[1:], + duration=duration, + loop=0 + ) + print(f"Saved animated GIF to: {output_path}") + +def main(): + """Main function to create the animated visualization.""" + # Load images with new paths + base_path = '/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset999' + image_mri = load_nifti(os.path.join(base_path, 'imagesTs/T1C_043_0000.nii.gz')) + ground_truth = load_nifti(os.path.join(base_path, 'labelsTs/T1C_043.nii.gz')) + prediction = load_nifti('/mnt/1248/onlylian/T1C/output_predictions/2d/fold_2/T1C_043.nii.gz') + + # Create animated GIF for slices 111-121 + output_path = '/mnt/1248/onlylian/T1C/slice_animation.gif' + create_animated_gif( + image_mri, + ground_truth, + prediction, + start_slice=111, + end_slice=121, + output_path=output_path, + duration=500 # 500ms per frame + ) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/T1C_OAR_TV/Dice/test/compare_test_dice_scores.py b/T1C_OAR_TV/Dice/test/compare_test_dice_scores.py new file mode 100644 index 0000000..833ba4c --- /dev/null +++ b/T1C_OAR_TV/Dice/test/compare_test_dice_scores.py @@ -0,0 +1,144 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# Set up Chinese font support +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """Load JSON data file""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"File not found: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON decoding error: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """Extract scores for a specific organ from the data""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """Create and save the plot""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('T1C_OAR_TV', fontsize=12, pad=10) # Changed title + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # Adjust data label position and size + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6, # Reduced font size + rotation=0) # Optional: rotate text if still crowded + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR_TV', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """Print numerical results""" + print("\nMean Dice Scores (percentage):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # Define paths and organ mapping + base_path = '/mnt/1248/onlylian/T1C_OAR_TV/Dice/test' + paths = { + '2D': os.path.join(base_path, 'T1C_OAR_TV_test_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'T1C_OAR_TV_test_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'T1C_OAR_TV_test_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve", + "7": "TV" + } + + # Initialize data storage + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # Load and process data + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # Calculate averages and standard deviations + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # Create and save the plot + output_path = os.path.join(base_path, 'T1C_OAR_TV_test_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # Print the results + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/T1C_OAR_TV/Dice/test/compute_test_dice.py b/T1C_OAR_TV/Dice/test/compute_test_dice.py new file mode 100644 index 0000000..3032bab --- /dev/null +++ b/T1C_OAR_TV/Dice/test/compute_test_dice.py @@ -0,0 +1,318 @@ +import json +import os +import numpy as np +import SimpleITK as sitk +import pandas as pd +import matplotlib.pyplot as plt +import seaborn as sns +from tqdm import tqdm +import time +from datetime import datetime +from scipy.ndimage import distance_transform_edt +import multiprocessing +from joblib import Parallel, delayed + +class IntegratedAnalyzer: + def __init__(self, base_path): + """初始化分析器""" + self.base_path = base_path + self.organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve", + 7: "TV" + } + self.configurations = { + 'Dataset012_OAR_TV': { + '2d': { + 'model': 'nnUNetTrainer__nnUNetPlans__2d', + 'path': '/mnt/1248/onlylian/T1C_OAR_TV/output_predictions/2d', + 'folds': range(5) + }, + '3d_fullres': { + 'model': 'nnUNetTrainer__nnUNetPlans__3d_fullres', + 'path': '/mnt/1248/onlylian/T1C_OAR_TV/output_predictions/3d_fullres', + 'folds': range(5) + }, + '3d_lowres': { + 'model': 'nnUNetTrainer__nnUNetPlans__3d_lowres', + 'path': '/mnt/1248/onlylian/T1C_OAR_TV/output_predictions/3d_lowres', + 'folds': range(5) + } + } + } + # 設定並行處理的核心數 + self.n_jobs = max(1, multiprocessing.cpu_count() - 1) + self.setup_paths() + self.create_output_folders() + + def setup_paths(self): + """設置所有需要的路徑""" + self.dataset_paths = {} + for dataset, model_configs in self.configurations.items(): + self.dataset_paths[dataset] = { + 'ground_truth': os.path.join( + "/mnt/1248/onlylian/nnUNet/nnUNet_raw", + dataset, + "labelsTs" + ), + 'predictions': {} + } + for model_type, config in model_configs.items(): + self.dataset_paths[dataset]['predictions'][model_type] = { + fold: os.path.join(config['path'], f"fold_{fold}") + for fold in config['folds'] + } + + self.output_path = os.path.join( + self.base_path, + "analysis_results", + datetime.now().strftime("%Y%m%d_%H%M%S") + ) + + def create_output_folders(self): + """建立輸出資料夾""" + os.makedirs(self.output_path, exist_ok=True) + + def setup_font_configuration(self): + """設置基本字型配置""" + plt.rcParams.update(plt.rcParamsDefault) + plt.rcParams['font.family'] = 'DejaVu Sans' + plt.rcParams['axes.unicode_minus'] = False + plt.rcParams['font.size'] = 12 + plt.rcParams['axes.titlesize'] = 14 + plt.rcParams['axes.labelsize'] = 12 + plt.rcParams['xtick.labelsize'] = 10 + plt.rcParams['ytick.labelsize'] = 10 + return True + + def load_nifti(self, file_path): + """載入 NIfTI 檔案""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + + def separate_structures(self, combined_mask, structure_name=""): + """分離左右結構,改進的版本""" + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: # 背景+至少兩個區域 + # 只在實際需要時輸出警告 + if np.sum(combined_mask) > 0: # 只有當mask中確實有內容時才輸出警告 + print(f"Warning: Not enough connected regions found for {structure_name}") + return None, None + + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + if np.sum(combined_mask) > 0: # 只有當mask中確實有內容時才輸出警告 + print(f"Warning: Only one major region found for {structure_name}") + return None, None + + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + + def compute_dice(self, pred, gt): + """計算 Dice 係數""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + + def evaluate_case(self, gt_path, pred_path): + """評估單一案例,包含腫瘤距離分析""" + gt_img = self.load_nifti(gt_path) + pred_img = self.load_nifti(pred_path) + + # 計算腫瘤距離圖 + tumor_mask = (gt_img == 7) # 7 是腫瘤的標籤 + tumor_distance = distance_transform_edt(~tumor_mask) + + results = {} + distance_results = {} + + # Process eyes + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + results['combined_eye'] = self.compute_dice(processed_pred_eye, combined_gt_eye) + eye_distances = tumor_distance[combined_gt_eye > 0] + if len(eye_distances) > 0: + distance_results['combined_eye'] = np.mean(eye_distances) + + right_pred_eye, left_pred_eye = self.separate_structures(processed_pred_eye, "Eyes") + right_gt_eye, left_gt_eye = self.separate_structures(combined_gt_eye, "Eyes") + + # Process optic nerves + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + results['combined_nerve'] = self.compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_distances = tumor_distance[combined_gt_nerve > 0] + if len(nerve_distances) > 0: + distance_results['combined_nerve'] = np.mean(nerve_distances) + + right_pred_nerve, left_pred_nerve = self.separate_structures(processed_pred_nerve, "Optic Nerves") + right_gt_nerve, left_gt_nerve = self.separate_structures(combined_gt_nerve, "Optic Nerves") + + # Process individual structures + for label in self.organ_names.keys(): + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + elif label == 7: # TV (tumor) + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + else: + continue + + try: + dice = self.compute_dice(pred_binary, gt_binary) + results[label] = dice + + # 計算每個器官到腫瘤的距離 + if label != 7: # 不計算腫瘤自身的距離 + organ_distances = tumor_distance[gt_binary > 0] + if len(organ_distances) > 0: + distance_results[label] = np.mean(organ_distances) + except Exception as e: + print(f"Error processing label {label}: {str(e)}") + results[label] = np.nan + distance_results[label] = np.nan + + return results, distance_results + + def process_single_case(self, args): + """處理單一案例的包裝函數""" + gt_path, pred_path = args + return self.evaluate_case(gt_path, pred_path) + + def process_fold(self, model_type, fold, model_config, dataset): + """處理單一fold的函數""" + results = [] + pred_folder = os.path.join(model_config['path'], f"fold_{fold}") + gt_folder = self.dataset_paths[dataset]['ground_truth'] + + if not os.path.exists(pred_folder): + print(f"Warning: Prediction folder does not exist: {pred_folder}") + return results + + pred_files = [f for f in os.listdir(pred_folder) if f.endswith('.nii.gz')] + + # 準備參數 + case_args = [(os.path.join(gt_folder, pred_file), + os.path.join(pred_folder, pred_file)) + for pred_file in pred_files + if os.path.exists(os.path.join(gt_folder, pred_file))] + + # 使用 joblib 進行並行處理 + processed_results = Parallel(n_jobs=self.n_jobs)( + delayed(self.process_single_case)(args) for args in tqdm(case_args, + desc=f"Processing fold {fold}") + ) + + # 整理結果 + for (dice_results, distance_results), (gt_path, pred_path) in zip(processed_results, case_args): + pred_file = os.path.basename(pred_path) + for key, value in dice_results.items(): + if isinstance(key, int): + result_entry = { + 'model_type': model_type, + 'fold': fold, + 'case_id': pred_file, + 'structure': self.organ_names[key], + 'dice_score': value, + 'distance_to_tumor': distance_results.get(key, np.nan) + } + results.append(result_entry) + + return results + + def analyze_and_visualize(self): + """分析並視覺化結果,包含距離分析""" + self.setup_font_configuration() + plots_path = os.path.join(self.output_path, "plots") + os.makedirs(plots_path, exist_ok=True) + + for dataset in self.configurations.keys(): + all_results = [] + + for model_type, model_config in self.configurations[dataset].items(): + print(f"\nProcessing {dataset} - {model_type}") + + # 並行處理每個 fold + fold_results = Parallel(n_jobs=self.n_jobs)( + delayed(self.process_fold)(model_type, fold, model_config, dataset) + for fold in model_config['folds'] + ) + + # 整合所有結果 + for results in fold_results: + all_results.extend(results) + + # 創建DataFrame並視覺化 + if all_results: + df = pd.DataFrame(all_results) + + # 基本的模型比較圖 + plt.figure(figsize=(12, 6)) + sns.boxplot(data=df, x='model_type', y='dice_score') + plt.title(f'{dataset} - Model Comparison') + plt.xlabel('Model Type') + plt.ylabel('Dice Score') + plt.tight_layout() + plt.savefig(os.path.join(plots_path, f'{dataset}_model_comparison.png')) + plt.close() + + # 結構wise比較圖 + plt.figure(figsize=(15, 8)) \ No newline at end of file diff --git a/T1C_OAR_TV/Dice/validation/compare_validation_dice_scores.py b/T1C_OAR_TV/Dice/validation/compare_validation_dice_scores.py new file mode 100644 index 0000000..cce4eb1 --- /dev/null +++ b/T1C_OAR_TV/Dice/validation/compare_validation_dice_scores.py @@ -0,0 +1,111 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# Set up Chinese font support +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +# Functions up to create_plot remain the same + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """Create and save the plot""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('T1C_OAR_TV', fontsize=12, pad=10) # Changed title + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # Adjust data label position and size + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height + 0.5, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6) + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR_TV', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +# print_results function remains the same + +def main(): + # Define paths and organ mapping + base_path = '/mnt/1248/onlylian/T1C_OAR_TV/Dice/validation' + paths = { + '2D': os.path.join(base_path, 'T1C_OAR_TV_validation_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'T1C_OAR_TV_validation_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'T1C_OAR_TV_validation_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve", + "7": "TV" + } + + # Initialize data storage + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # Load and process data + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # Calculate averages and standard deviations + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # Create and save the plot + output_path = os.path.join(base_path, 'T1C_OAR_TV_validation_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # Print the results + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/T1C_OAR_TV/Dice/validation/compute_validation_dice.py b/T1C_OAR_TV/Dice/validation/compute_validation_dice.py new file mode 100644 index 0000000..e3ea708 --- /dev/null +++ b/T1C_OAR_TV/Dice/validation/compute_validation_dice.py @@ -0,0 +1,325 @@ +import os +import time +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import json + +def load_nifti(file_path): + """Load a NIfTI file""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """Compute Dice coefficient""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def preprocess_binary_mask(mask, apply_closing=True): + """Preprocess binary mask with morphological operations""" + mask_sitk = sitk.GetImageFromArray(mask) + + if apply_closing: + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + mask = sitk.GetArrayFromImage(closing_filter.Execute(mask_sitk)) + + return mask + +def separate_structures(combined_mask, structure_name=""): + """Separate left and right structures""" + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: + print(f"Warning: Not enough connected regions found for {structure_name}") + return None, None + + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"Warning: Only one major region found for {structure_name}") + return None, None + + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """Evaluate a single case""" + print(f"\nTest file: {os.path.basename(pred_path)}") + + try: + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"Image size: {gt_img.shape}") + print(f"Loading time: {load_time:.2f}s") + + results = {} + total_time = 0 + + # Process eyes + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + processed_pred_eye = preprocess_binary_mask(combined_pred_eye) + processed_gt_eye = preprocess_binary_mask(combined_gt_eye) + + eye_dice = compute_dice(processed_pred_eye, processed_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"Eyes combined computation time: {eye_time:.2f}s, Dice: {eye_dice:.4f}") + + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "Eyes") + right_gt_eye, left_gt_eye = separate_structures(processed_gt_eye, "Eyes") + + # Process optic nerves + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + processed_pred_nerve = preprocess_binary_mask(combined_pred_nerve) + processed_gt_nerve = preprocess_binary_mask(combined_gt_nerve) + + nerve_dice = compute_dice(processed_pred_nerve, processed_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"Optic nerves combined computation time: {nerve_time:.2f}s, Dice: {nerve_dice:.4f}") + + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "Optic Nerves") + right_gt_nerve, left_gt_nerve = separate_structures(processed_gt_nerve, "Optic Nerves") + + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + apply_closing = True + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + apply_closing = False # Already processed + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + apply_closing = False # Already processed + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + apply_closing = True + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + apply_closing = False # Already processed + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + apply_closing = False # Already processed + elif label == 7: # TV + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + apply_closing = True + else: + continue + + try: + if apply_closing: + pred_binary = preprocess_binary_mask(pred_binary) + gt_binary = preprocess_binary_mask(gt_binary) + + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"Label {label} computation time: {compute_time:.2f}s, Dice: {dice:.4f}") + except Exception as e: + print(f"Error processing label {label}: {str(e)}") + results[label] = np.nan + continue + + print(f"Total computation time for this case: {total_time:.2f}s") + return results + except Exception as e: + print(f"Error evaluating case: {str(e)}") + return None + +def evaluate_validation_fold(gt_base_folder, validation_folder, labels): + """Evaluate the validation set for a single fold""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(validation_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(validation_folder)}"): + patient_id = pred_file.split('.nii.gz')[0] + gt_path = os.path.join(gt_base_folder, f"{patient_id}.nii.gz") + pred_path = os.path.join(validation_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"Could not find corresponding ground truth file: {gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + if case_results is not None: + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, model_path, model_name, labels, organ_names, output_dir): + """Evaluate the validation set for all folds of a model""" + print(f"\nStarting evaluation of model: {model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + for fold in range(5): + validation_folder = os.path.join(model_path, f"fold_{fold}", "validation") + + if not os.path.exists(validation_folder): + print(f"Could not find fold_{fold} validation folder: {validation_folder}") + continue + + print(f"\nProcessing {model_name} - Fold {fold} validation set") + results = evaluate_validation_fold(gt_folder, validation_folder, labels) + fold_results[f"fold_{fold}"] = results + + for key in results: + all_results[key].extend(results[key]) + + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + output_path = os.path.join(output_dir, 'T1C_OAR_TV', 'Dice', 'validation') + os.makedirs(output_path, exist_ok=True) + + filename = os.path.join(output_path, f'dice_results_{model_name}.json') + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\nValidation set results for {model_name} saved to {filename}") + + print(f"\nValidation set Dice results for {model_name} (average across all folds):") + + # Print individual organ results + print("\nOrgan-specific results:") + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: Unable to compute (n={n})") + + # Print combined structure results + print("\nCombined structure results:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "Combined Eyes" if key == 'combined_eye' else "Combined Optic Nerves" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: Unable to compute (n={n})") + + return summary + +if __name__ == "__main__": + base_dir = "/mnt/1248/onlylian" + gt_folder = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset012_OAR_TV/labelsTr") + output_dir = base_dir + + models = { + '2D': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset012_OAR_TV/nnUNetTrainer__nnUNetPlans__2d"), + '3D_fullres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset012_OAR_TV/nnUNetTrainer__nnUNetPlans__3d_fullres"), + '3D_lowres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset012_OAR_TV/nnUNetTrainer__nnUNetPlans__3d_lowres") + } + + labels = [1, 2, 3, 4, 5, 6, 7] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve", + 7: "TV" + } + + print("Available models:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + try: + choice = input("\nPlease select a model to run (enter 1-3): ") + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names, output_dir) + except (ValueError, IndexError): + print("Invalid choice! Please enter a number between 1 and 3.") + except KeyboardInterrupt: + print("\nProgram interrupted by user") + print("You can run the program again to continue processing") + except Exception as e: + print(f"\nAn unexpected error occurred: {str(e)}") + print("Please check the error message and try again") \ No newline at end of file diff --git a/T1C_OAR_TV/lpre_ow_chiasm.py b/T1C_OAR_TV/lpre_ow_chiasm.py new file mode 100644 index 0000000..fb548d1 --- /dev/null +++ b/T1C_OAR_TV/lpre_ow_chiasm.py @@ -0,0 +1,151 @@ +import nibabel as nib +import matplotlib.pyplot as plt +import numpy as np +import os + +def load_nifti(file_path): + """載入NIfTI檔案""" + return nib.load(file_path).get_fdata() + +def create_colored_mask(data): + """創建彩色遮罩""" + color_dict = { + 1: [144/255, 238/255, 144/255], # Brainstem - 綠色 + 2: [255/255, 218/255, 150/255], # Right_Eye - 淺黃色 + 3: [205/255, 170/255, 125/255], # Left_Eye - 棕色 + 4: [135/255, 206/255, 235/255], # Optic_Chiasm - 藍色 + 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - 亮珊瑚紅 + 6: [255/255, 160/255, 122/255], # Left_Optic_Nerve - 淺珊瑚紅 + 7: [255/255, 0/255, 0/255] # TV - 亮紅色 + } + + mask = np.zeros((*data.shape, 4)) + for label, color in color_dict.items(): + organ_mask = (data == label) + if np.any(organ_mask): + mask[organ_mask] = [*color, 1.0] + return mask + +def get_display_bounds(image): + """直接返回較大的顯示區域""" + h, w = image.shape + # 使用圖像中心點 + center_h = h // 2 + center_w = w // 2 + # 取圖像大小的 85% 作為顯示範圍 + radius_h = int(h * 0.425) + radius_w = int(w * 0.425) + + return ( + max(center_h - radius_h, 0), + min(center_h + radius_h, h), + max(center_w - radius_w, 0), + min(center_w + radius_w, w) + ) + +def check_tv_in_slice(slice_data): + """檢查切片中是否包含TV(label 7)""" + return 7 in np.unique(slice_data) + +def find_tv_slices(ground_truth): + """找出所有包含TV的切片""" + tv_slices = [] + tv_pixels = [] + total_slices = ground_truth.shape[2] + + for slice_num in range(total_slices): + slice_data = ground_truth[:, :, slice_num] + if check_tv_in_slice(slice_data): + tv_pixel_count = np.sum(slice_data == 7) + tv_slices.append(slice_num) + tv_pixels.append(tv_pixel_count) + + sorted_indices = np.argsort(tv_pixels)[::-1] + sorted_slices = [tv_slices[i] for i in sorted_indices] + sorted_pixels = [tv_pixels[i] for i in sorted_indices] + + return sorted_slices, sorted_pixels + +def save_slice(image, ground_truth, prediction, slice_num, save_path): + """保存指定切片的三列比較圖(原始影像、Ground Truth和預測結果)""" + plt.clf() + fig, axes = plt.subplots(1, 3, figsize=(36, 11)) + plt.subplots_adjust(wspace=0.01) + + img_slice = np.rot90(image[:, :, slice_num]) + rmin, rmax, cmin, cmax = get_display_bounds(img_slice) + + # 調整圖像對比度 + p2, p98 = np.percentile(img_slice, (2, 98)) + img_slice = np.clip(img_slice, p2, p98) + img_slice = (img_slice - p2) / (p98 - p2) + + # 1. Original Image + axes[0].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[0].set_title('Original Image', fontsize=32, pad=20, weight='bold') + axes[0].axis('off') + + # 2. Ground Truth + gt_slice = np.rot90(ground_truth[:, :, slice_num]) + gt_mask = create_colored_mask(gt_slice) + + axes[1].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[1].imshow(gt_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[1].set_title('Ground Truth', fontsize=32, pad=20, weight='bold') + axes[1].axis('off') + + # 3. Prediction + pred_slice = np.rot90(prediction[:, :, slice_num]) + pred_mask = create_colored_mask(pred_slice) + + axes[2].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) + axes[2].imshow(pred_mask[rmin:rmax, cmin:cmax], zorder=2) + axes[2].set_title('Prediction', fontsize=32, pad=20, weight='bold') + axes[2].axis('off') + + plt.suptitle('') + plt.savefig(save_path, bbox_inches='tight', dpi=300, pad_inches=0.05) + plt.close() + print(f"已保存切片 {slice_num} 至: {save_path}") + +if __name__ == "__main__": + base_dir = "/mnt/1248/onlylian" + image_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset012_OAR_TV/imagesTs/OAR_TV_092_0000.nii.gz") + gt_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset012_OAR_TV/labelsTs/OAR_TV_092.nii.gz") + pred_path = os.path.join(base_dir, "T1C_OAR_TV/output_predictions/2d/fold_2/OAR_TV_092.nii.gz") # 更新預測結果路徑 + save_dir = os.path.join(base_dir, "T1C_OAR_TV") + + os.makedirs(save_dir, exist_ok=True) + + print("正在載入影像...") + image = load_nifti(image_path) + ground_truth = load_nifti(gt_path) + prediction = load_nifti(pred_path) # 載入預測結果 + print("影像載入完成") + + print("\n尋找包含TV的切片...") + tv_slices, tv_pixels = find_tv_slices(ground_truth) + + if tv_slices: + print(f"\n找到 {len(tv_slices)} 個包含TV的切片:") + for slice_num, pixel_count in zip(tv_slices, tv_pixels): + print(f"切片 {slice_num}: {pixel_count} 像素") + else: + print("未找到包含TV的切片") + + print("\n開始切片選擇和保存過程...") + while True: + try: + slice_num = int(input("\n請輸入要保存的切片編號 (-1 退出): ")) + if slice_num == -1: + print("程序結束") + break + if 0 <= slice_num < ground_truth.shape[2]: + save_path = os.path.join(save_dir, f'comparison_OAR_TV_092_3_{slice_num:03d}.png') + save_slice(image, ground_truth, prediction, slice_num, save_path) + else: + print(f"切片編號必須在 0 到 {ground_truth.shape[2]-1} 之間") + except ValueError: + print("請輸入有效的數字") + except Exception as e: + print(f"發生錯誤: {str(e)}") \ No newline at end of file diff --git a/T1C_OAR_TV/visualize_medical_compare.py b/T1C_OAR_TV/visualize_medical_compare.py new file mode 100644 index 0000000..39e71e2 --- /dev/null +++ b/T1C_OAR_TV/visualize_medical_compare.py @@ -0,0 +1,496 @@ +# import nibabel as nib +# import matplotlib.pyplot as plt +# import numpy as np +# import os +# import SimpleITK as sitk + +# def load_nifti(file_path): +# """載入NIfTI檔案""" +# return nib.load(file_path).get_fdata() + +# def process_eyes_segmentation(pred_array): +# """ +# 後處理眼睛的分割結果 + +# Parameters: +# pred_array: numpy array, 預測的分割結果 + +# Returns: +# result: numpy array, 處理後的分割結果 +# """ +# # 轉換為SimpleITK影像以使用形態學操作 +# pred_img = sitk.GetImageFromArray(pred_array) + +# # 提取左右眼的mask (標籤2和3) +# right_eye = (pred_array == 2).astype(np.uint8) +# left_eye = (pred_array == 3).astype(np.uint8) + +# # 合併左右眼 +# combined_eyes = right_eye | left_eye +# combined_eyes_sitk = sitk.GetImageFromArray(combined_eyes) + +# # 應用形態學操作 +# # 1. 閉運算填充小洞 +# closing_filter = sitk.BinaryMorphologicalClosingImageFilter() +# closing_filter.SetKernelRadius(2) +# closed_eyes = closing_filter.Execute(combined_eyes_sitk) + +# # 2. 移除小的孤立區域 +# cc_filter = sitk.ConnectedComponentImageFilter() +# cc_filter.FullyConnectedOn() +# components = cc_filter.Execute(closed_eyes) + +# # 獲取標籤統計信息 +# label_stats = sitk.LabelShapeStatisticsImageFilter() +# label_stats.Execute(components) + +# # 保留最大的兩個連通區域(左右眼) +# n_objects = cc_filter.GetObjectCount() +# if n_objects > 2: +# sizes = [(i, label_stats.GetPhysicalSize(i)) for i in range(1, n_objects + 1)] +# sizes.sort(key=lambda x: x[1], reverse=True) + +# # 創建mask只保留最大的兩個區域 +# binary_mask = sitk.Image(components.GetSize(), sitk.sitkUInt8) +# binary_mask.CopyInformation(components) +# binary_mask = sitk.Mask(components, components) + +# for i in range(n_objects): +# if i >= 2: # 移除小於第二大的區域 +# binary_mask = sitk.BinaryThreshold(binary_mask, +# sizes[i][0], sizes[i][0], +# 0, 1) +# else: +# binary_mask = closed_eyes + +# # 將處理後的mask轉回numpy array +# processed_mask = sitk.GetArrayFromImage(binary_mask) + +# # 基於質心分離左右眼 +# label_img = sitk.GetArrayFromImage(components) +# regions = [] +# for i in range(1, n_objects + 1): +# if label_stats.GetPhysicalSize(i) > 0: +# region_mask = (label_img == i) +# centroid = np.mean(np.where(region_mask), axis=1) +# regions.append((i, centroid[2])) # 使用x座標來判斷左右 + +# # 根據質心x座標排序 +# regions.sort(key=lambda x: x[1]) + +# # 重建左右眼標籤 +# result = np.zeros_like(pred_array) + +# if len(regions) >= 2: +# # 將最右邊的區域標記為右眼(標籤2) +# result[label_img == regions[-1][0]] = 2 +# # 將最左邊的區域標記為左眼(標籤3) +# result[label_img == regions[0][0]] = 3 + +# return result + +# def process_optic_nerves_segmentation(pred_array): +# """ +# 後處理視神經的分割結果 + +# Parameters: +# pred_array: numpy array, 預測的分割結果 + +# Returns: +# result: numpy array, 處理後的分割結果 +# """ +# # 提取視神經的mask (標籤5和6) +# right_nerve = (pred_array == 5).astype(np.uint8) +# left_nerve = (pred_array == 6).astype(np.uint8) + +# # 合併視神經 +# combined_nerves = right_nerve | left_nerve +# combined_nerves_sitk = sitk.GetImageFromArray(combined_nerves) + +# # 應用形態學操作 +# closing_filter = sitk.BinaryMorphologicalClosingImageFilter() +# closing_filter.SetKernelRadius(2) +# closed_nerves = closing_filter.Execute(combined_nerves_sitk) + +# # 連通區域分析 +# cc_filter = sitk.ConnectedComponentImageFilter() +# cc_filter.FullyConnectedOn() +# components = cc_filter.Execute(closed_nerves) + +# # 獲取標籤統計信息 +# label_stats = sitk.LabelShapeStatisticsImageFilter() +# label_stats.Execute(components) + +# # 處理連通區域 +# n_objects = cc_filter.GetObjectCount() +# label_img = sitk.GetArrayFromImage(components) + +# regions = [] +# for i in range(1, n_objects + 1): +# if label_stats.GetPhysicalSize(i) > 0: +# region_mask = (label_img == i) +# centroid = np.mean(np.where(region_mask), axis=1) +# regions.append((i, centroid[2])) + +# # 根據質心x座標排序 +# regions.sort(key=lambda x: x[1]) + +# # 重建左右視神經標籤 +# result = np.zeros_like(pred_array) + +# if len(regions) >= 2: +# # 將最右邊的區域標記為右視神經(標籤5) +# result[label_img == regions[-1][0]] = 5 +# # 將最左邊的區域標記為左視神經(標籤6) +# result[label_img == regions[0][0]] = 6 + +# return result + +# def post_process_symmetrical_organs(prediction): +# """對稱器官後處理""" +# processed_prediction = prediction.copy() + +# # 處理眼睛 +# eyes_result = process_eyes_segmentation(processed_prediction) +# # 更新眼睛的標籤 +# processed_prediction[eyes_result > 0] = eyes_result[eyes_result > 0] + +# # 處理視神經 +# nerves_result = process_optic_nerves_segmentation(processed_prediction) +# # 更新視神經的標籤 +# processed_prediction[nerves_result > 0] = nerves_result[nerves_result > 0] + +# return processed_prediction + +# def create_colored_mask(data): +# """創建彩色遮罩""" +# color_dict = { +# 1: [144/255, 238/255, 144/255], # Brainstem - 綠色 +# 2: [255/255, 218/255, 150/255], # Right_Eye - 淺黃色 +# 3: [205/255, 170/255, 125/255], # Left_Eye - 棕色 +# 4: [135/255, 206/255, 235/255], # Optic_Chiasm - 藍色 +# 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - 亮珊瑚紅 +# 6: [255/255, 160/255, 122/255], # Left_Optic_Nerve - 淺珊瑚紅 +# 7: [255/255, 0/255, 0/255] # TV - 亮紅色 +# } + +# # 創建一個具有透明背景的遮罩 +# mask = np.zeros((*data.shape, 4)) # 使用4通道(RGBA) + +# for label, color in color_dict.items(): +# # 為每個器官設置顏色,包括完全不透明的 alpha 通道 +# organ_mask = (data == label) +# if np.any(organ_mask): # 只處理存在的器官 +# mask[organ_mask] = [*color, 1.0] # RGB + alpha + +# return mask + +# def get_brain_bounds(image): +# """獲取腦部區域的邊界""" +# mask = image > np.percentile(image, 1) +# rows = np.any(mask, axis=1) +# cols = np.any(mask, axis=0) +# rmin, rmax = np.where(rows)[0][[0, -1]] +# cmin, cmax = np.where(cols)[0][[0, -1]] + +# center_r = (rmin + rmax) // 2 +# center_c = (cmin + cmax) // 2 + +# radius = int(max(rmax - rmin, cmax - cmin) * 0.55) +# margin = int(radius * 0.15) + +# rmin = max(center_r - radius - margin, 0) +# rmax = min(center_r + radius + margin, image.shape[0]) +# cmin = max(center_c - radius - margin, 0) +# cmax = min(center_c + radius + margin, image.shape[1]) + +# return rmin, rmax, cmin, cmax + +# def check_organs_in_slice(slice_data): +# """檢查切片中存在的器官標籤""" +# unique_labels = set(np.unique(slice_data)) +# if 0 in unique_labels: +# unique_labels.remove(0) +# organ_dict = { +# 1: "Brainstem", +# 2: "Right_Eye", +# 3: "Left_Eye", +# 4: "Optic_Chiasm", +# 5: "Right_Optic_Nerve", +# 6: "Left_Optic_Nerve", +# 7: "TV" +# } +# return {organ_dict[label] for label in unique_labels if label in organ_dict} + +# def save_slice(image, ground_truth, prediction, slice_num, save_path): +# """保存指定切片的比較圖""" +# plt.clf() +# fig, axes = plt.subplots(1, 2, figsize=(25, 11)) +# plt.subplots_adjust(wspace=0.01) + +# img_slice = np.rot90(image[:, :, slice_num]) +# rmin, rmax, cmin, cmax = get_brain_bounds(img_slice) + +# # 調整圖像對比度以提高可見度 +# p2, p98 = np.percentile(img_slice, (2, 98)) +# img_slice = np.clip(img_slice, p2, p98) +# img_slice = (img_slice - p2) / (p98 - p2) + +# # Ground Truth +# gt_slice = np.rot90(ground_truth[:, :, slice_num]) +# gt_mask = create_colored_mask(gt_slice) + +# # 使用 imshow 的 zorder 參數來控制圖層順序 +# axes[0].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) +# axes[0].imshow(gt_mask[rmin:rmax, cmin:cmax], zorder=2) +# axes[0].set_title('Ground Truth', fontsize=32, pad=20, weight='bold') +# axes[0].axis('off') + +# # 對整個prediction進行後處理 +# processed_prediction = post_process_symmetrical_organs(prediction) + +# # Prediction +# pred_slice = np.rot90(processed_prediction[:, :, slice_num]) +# pred_mask = create_colored_mask(pred_slice) + +# axes[1].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray', zorder=1) +# axes[1].imshow(pred_mask[rmin:rmax, cmin:cmax], zorder=2) +# axes[1].set_title('Prediction', fontsize=32, pad=20, weight='bold') +# axes[1].axis('off') + +# plt.savefig(save_path, bbox_inches='tight', dpi=300, pad_inches=0.05) +# plt.close() +# print(f"已保存切片 {slice_num} 至: {save_path}") + +# if __name__ == "__main__": +# base_dir = "/mnt/1248/onlylian" +# image_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset012_OAR_TV/imagesTs/OAR_TV_092_0000.nii.gz") +# gt_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset012_OAR_TV/labelsTs/OAR_TV_092.nii.gz") +# pred_path = os.path.join(base_dir, "T1C_OAR_TV/output_predictions/2d/fold_2/OAR_TV_092.nii.gz") +# save_dir = os.path.join(base_dir, "T1C_OAR_TV") + +# os.makedirs(save_dir, exist_ok=True) + +# print("正在載入影像...") +# image = load_nifti(image_path) +# ground_truth = load_nifti(gt_path) +# prediction = load_nifti(pred_path) +# print("影像載入完成") + +# print("\n分析切片中的器官...") +# total_slices = image.shape[2] +# for slice_num in range(total_slices): +# gt_slice = ground_truth[:, :, slice_num] +# pred_slice = prediction[:, :, slice_num] + +# gt_organs = check_organs_in_slice(gt_slice) +# pred_organs = check_organs_in_slice(pred_slice) + +# if gt_organs or pred_organs: +# print(f"\n切片 {slice_num}:") +# print(f"Ground Truth 包含: {', '.join(sorted(gt_organs))}") +# print(f"Prediction 包含: {', '.join(sorted(pred_organs))}") + +# print("\n開始切片選擇和保存過程...") +# while True: +# try: +# slice_num = int(input("\n請輸入要保存的切片編號 (-1 退出): ")) +# if slice_num == -1: +# print("程序結束") +# break +# if 0 <= slice_num < total_slices: +# save_path = os.path.join(save_dir, f'comparison_OAR_TV_092_{slice_num:03d}.png') +# save_slice(image, ground_truth, prediction, slice_num, save_path) +# else: +# print(f"切片編號必須在 0 到 {total_slices-1} 之間") +# except ValueError: +# print("請輸入有效的數字") +# except Exception as e: +# print(f"發生錯誤: {str(e)}") + +import nibabel as nib +import matplotlib.pyplot as plt +import numpy as np +import os +import matplotlib.patches as patches + +def load_nifti(file_path): + """載入NIfTI檔案""" + return nib.load(file_path).get_fdata() + +def create_colored_mask(data): + """創建彩色遮罩""" + color_dict = { + 1: [144/255, 238/255, 144/255], # Brainstem - 綠色 + 2: [255/255, 218/255, 150/255], # Right_Eye - 淺黃色 + 3: [205/255, 170/255, 125/255], # Left_Eye - 棕色 + 4: [135/255, 206/255, 235/255], # Optic_Chiasm - 藍色 + 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - 亮珊瑚紅 + 6: [255/255, 160/255, 122/255], # Left_Optic_Nerve - 淺珊瑚紅 + 7: [255/255, 0/255, 0/255] # TV - 亮紅色 + } + + mask = np.zeros((*data.shape, 4)) + for label, color in color_dict.items(): + organ_mask = (data == label) + if np.any(organ_mask): + mask[organ_mask] = [*color, 1.0] + return mask + +def get_brain_bounds(image): + """獲取腦部區域的邊界""" + mask = image > np.percentile(image, 1) + rows = np.any(mask, axis=1) + cols = np.any(mask, axis=0) + rmin, rmax = np.where(rows)[0][[0, -1]] + cmin, cmax = np.where(cols)[0][[0, -1]] + + center_r = (rmin + rmax) // 2 + center_c = (cmin + cmax) // 2 + + radius = int(max(rmax - rmin, cmax - cmin) * 0.55) + margin = int(radius * 0.15) + + return ( + max(center_r - radius - margin, 0), + min(center_r + radius + margin, image.shape[0]), + max(center_c - radius - margin, 0), + min(center_c + radius + margin, image.shape[1]) + ) + +def check_organs_in_slice(slice_data): + """檢查切片中存在的器官標籤""" + unique_labels = set(np.unique(slice_data)) + if 0 in unique_labels: + unique_labels.remove(0) + organ_dict = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve", + 7: "TV" + } + return {organ_dict[label] for label in unique_labels if label in organ_dict} + +def create_circular_mask(shape, center, radius): + """創建圓形遮罩""" + Y, X = np.ogrid[:shape[0], :shape[1]] + dist_from_center = np.sqrt((X - center[0])**2 + (Y - center[1])**2) + mask = dist_from_center <= radius + return mask + +def save_slice(image, ground_truth, prediction, slice_num, save_path): + """保存指定切片的比較圖""" + plt.clf() + fig, axes = plt.subplots(1, 3, figsize=(36, 11)) + plt.subplots_adjust(wspace=0.01) + + img_slice = np.rot90(image[:, :, slice_num]) + rmin, rmax, cmin, cmax = get_brain_bounds(img_slice) + + # 計算圓形遮罩的參數 + height, width = rmax-rmin, cmax-cmin + center = (width//2, height//2) + radius = min(width, height)//2 + mask = create_circular_mask((height, width), center, radius) + + # 調整圖像對比度 + p2, p98 = np.percentile(img_slice, (2, 98)) + img_slice = np.clip(img_slice, p2, p98) + img_slice = (img_slice - p2) / (p98 - p2) + + cropped_slice = img_slice[rmin:rmax, cmin:cmax] + gt_slice = np.rot90(ground_truth[:, :, slice_num])[rmin:rmax, cmin:cmax] + pred_slice = np.rot90(prediction[:, :, slice_num])[rmin:rmax, cmin:cmax] + + # 創建遮罩 + gt_mask = create_colored_mask(gt_slice) + pred_mask = create_colored_mask(pred_slice) + + # 設置背景顏色為黑色 + fig.patch.set_facecolor('black') + + titles = ['Original Image', 'Ground Truth', 'Prediction'] + images = [ + (cropped_slice, None), + (cropped_slice, gt_mask), + (cropped_slice, pred_mask) + ] + + for ax, title, (img, overlay) in zip(axes, titles, images): + ax.set_facecolor('black') + + # 應用圓形遮罩 + masked_img = np.copy(img) + masked_img[~mask] = 0 + + # 顯示基礎圖像 + ax.imshow(masked_img, cmap='gray', zorder=1) + + # 如果有overlay,顯示它 + if overlay is not None: + masked_overlay = overlay.copy() + masked_overlay[~mask] = [0, 0, 0, 0] + ax.imshow(masked_overlay, zorder=2) + + # 設置標題 + ax.set_title(title, fontsize=32, pad=20, weight='bold', color='white') + ax.axis('off') + + plt.savefig(save_path, bbox_inches='tight', dpi=300, pad_inches=0.05, + facecolor='black', edgecolor='none') + plt.close() + print(f"已保存切片 {slice_num} 至: {save_path}") + +if __name__ == "__main__": + base_dir = "/mnt/1248/onlylian" + image_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset012_OAR_TV/imagesTs/OAR_TV_092_0000.nii.gz") + gt_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset012_OAR_TV/labelsTs/OAR_TV_092.nii.gz") + pred_path = os.path.join(base_dir, "T1C_OAR_TV/output_predictions/2d/fold_2/OAR_TV_092.nii.gz") + save_dir = os.path.join(base_dir, "T1C_OAR_TV") + + os.makedirs(save_dir, exist_ok=True) + + print("正在載入影像...") + image = load_nifti(image_path) + ground_truth = load_nifti(gt_path) + prediction = load_nifti(pred_path) + print("影像載入完成") + + print("\n分析切片中的器官...") + total_slices = image.shape[2] + for slice_num in range(total_slices): + gt_slice = ground_truth[:, :, slice_num] + pred_slice = prediction[:, :, slice_num] + + gt_organs = check_organs_in_slice(gt_slice) + pred_organs = check_organs_in_slice(pred_slice) + + if gt_organs or pred_organs: + print(f"\n切片 {slice_num}:") + print(f"Ground Truth 包含: {', '.join(sorted(gt_organs))}") + print(f"Prediction 包含: {', '.join(sorted(pred_organs))}") + + print("\n開始切片選擇和保存過程...") + while True: + try: + slice_num = int(input("\n請輸入要保存的切片編號 (-1 退出): ")) + if slice_num == -1: + print("程序結束") + break + if 0 <= slice_num < total_slices: + save_path = os.path.join(save_dir, f'comparison_OAR_TV_092_{slice_num:03d}.png') + save_slice(image, ground_truth, prediction, slice_num, save_path) + else: + print(f"切片編號必須在 0 到 {total_slices-1} 之間") + except ValueError: + print("請輸入有效的數字") + except Exception as e: + print(f"發生錯誤: {str(e)}") + + + + + diff --git a/T2/Dice/test/compare_test_dice_scores.py b/T2/Dice/test/compare_test_dice_scores.py new file mode 100644 index 0000000..a1cee90 --- /dev/null +++ b/T2/Dice/test/compare_test_dice_scores.py @@ -0,0 +1,143 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# Set up Chinese font support +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """Load JSON data file""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"File not found: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON decoding error: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """Extract scores for a specific organ from the data""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """Create and save the plot""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('T2', fontsize=12, pad=10) # Changed title + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # Adjust data label position and size + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6, # Reduced font size + rotation=0) # Optional: rotate text if still crowded + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """Print numerical results""" + print("\nMean Dice Scores (percentage):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # Define paths and organ mapping + base_path = '/mnt/1248/onlylian/T2/Dice/test' + paths = { + '2D': os.path.join(base_path, 'T2_test_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'T2_test_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'T2_test_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve" + } + + # Initialize data storage + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # Load and process data + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # Calculate averages and standard deviations + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # Create and save the plot + output_path = os.path.join(base_path, 'T2_test_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # Print the results + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/T2/Dice/test/compute_test_dice.py b/T2/Dice/test/compute_test_dice.py new file mode 100644 index 0000000..0ca28c0 --- /dev/null +++ b/T2/Dice/test/compute_test_dice.py @@ -0,0 +1,306 @@ +import os +import time +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import json + +def load_nifti(file_path): + """加載 NIfTI 文件""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """計算Dice係數""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """分離左右結構""" + # 使用SimpleITK的ConnectedComponent + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + # 計算連通區域數量和大小 + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: # 背景算一個,所以至少需要3個 + print(f"警告:{structure_name} 未找到足夠的連通區域") + return None, None + + # 排除背景(標籤0)並按大小排序 + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"警告:{structure_name} 未找到兩個主要區域") + return None, None + + # 獲取兩個最大區域 + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + # 計算質心 + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) # 使用x軸(第三維度)的平均值 + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + # 根據x軸位置確定左右 + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """評估單個案例""" + print(f"\n測試文件: {os.path.basename(pred_path)}") + + # 加載圖像 + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"圖像大小: {gt_img.shape}") + print(f"加載時間: {load_time:.2f}秒") + + results = {} + total_time = 0 + + # 處理眼睛 + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + # 形態學處理眼睛 + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + # 計算眼睛的Dice + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + + # 分離眼睛 + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + + # 處理視神經 + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + # 形態學處理視神經 + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + # 計算視神經的Dice + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + + # 分離視神經 + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + + # 計算各個標籤的Dice + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") + except Exception as e: + print(f"處理標籤 {label} 時出錯:{str(e)}") + results[label] = np.nan + continue + + print(f"單個案例總計算時間: {total_time:.2f}秒") + return results + +def evaluate_fold(gt_folder, pred_folder, labels): + """評估單個fold的所有案例""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(pred_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(pred_folder)}"): + gt_path = os.path.join(gt_folder, pred_file) + pred_path = os.path.join(pred_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"找不到對應的真實標籤文件:{gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + + # 收集結果 + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, base_pred_folder, model_name, labels, organ_names): + """評估單個模型的所有fold""" + print(f"\n開始評估模型:{model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + # 處理 fold_0 到 fold_4 + for fold in range(5): + print(f"\n處理 {model_name} - Fold {fold}") + pred_folder = os.path.join(base_pred_folder, f"fold_{fold}") + if not os.path.exists(pred_folder): + print(f"找不到預測文件夾:{pred_folder}") + continue + + results = evaluate_fold(gt_folder, pred_folder, labels) + fold_results[f"fold_{fold}"] = results + + # 合併結果 + for key in results: + all_results[key].extend(results[key]) + + # 計算總平均值和標準差 + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + # 保存結果到指定路径 + save_dir = "/mnt/1248/onlylian" + os.makedirs(save_dir, exist_ok=True) + filename = os.path.join(save_dir, f'T2_test_dice_results_{model_name}.json') + + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\n{model_name}結果已保存到 {filename}") + + # 打印結果 + print(f"\n{model_name} 模型的 Dice 結果(所有fold的平均值):") + + # 打印器官結果 + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: 無法計算 (n={n})") + + # 打印合併結果 + print("\n合併結構的結果:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "合併眼睛" if key == 'combined_eye' else "合併視神經" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: 無法計算 (n={n})") + + return summary + +if __name__ == "__main__": + # 設置路徑和標籤 + gt_folder = "/mnt/1248/onlylian/nnUNet/nnUNet_raw/Dataset111/labelsTs" + models = { + '2D': '/mnt/1248/onlylian/T2/output_predictions/2d', + '3D_fullres': '/mnt/1248/onlylian/T2/output_predictions/3d_fullres', + '3D_lowres': '/mnt/1248/onlylian/T2/output_predictions/3d_lowres' + } + labels = [1, 2, 3, 4, 5, 6] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + + # 讓用戶選擇要運行哪個模型 + print("可用的模型:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names) + except (ValueError, IndexError): + print("無效的選擇!請輸入1-3之間的數字。") \ No newline at end of file diff --git a/T2/Dice/validation/compare_validation_dice_scores.py b/T2/Dice/validation/compare_validation_dice_scores.py new file mode 100644 index 0000000..908611b --- /dev/null +++ b/T2/Dice/validation/compare_validation_dice_scores.py @@ -0,0 +1,142 @@ +import json +import os +import numpy as np +import matplotlib.pyplot as plt +from typing import Dict, Tuple + +# Set up Chinese font support +plt.rcParams['font.sans-serif'] = ['SimHei'] +plt.rcParams['axes.unicode_minus'] = False + +def load_data(file_path: str) -> Dict: + """Load JSON data file""" + try: + with open(file_path, 'r') as file: + return json.load(file) + except FileNotFoundError: + print(f"File not found: {file_path}") + return {} + except json.JSONDecodeError: + print(f"JSON decoding error: {file_path}") + return {} + +def extract_scores(data: Dict, model: str, organ_id: str) -> Tuple[float, float]: + """Extract scores for a specific organ from the data""" + if "summary" not in data or organ_id not in data["summary"]: + return (0, 0) + + scores = data["summary"][organ_id] + if model == '3D_fullres': + mean_dice = scores.get("mean_dice", 0) + std_dice = scores.get("std_dice", 0) + else: + mean_dice = scores.get("mean_dice", scores.get("mean", 0)) + std_dice = scores.get("std_dice", scores.get("std", 0)) + return (mean_dice, std_dice) + +def create_plot(model_averages: Dict, model_stds: Dict, organs: Dict, output_path: str): + """Create and save the plot""" + plt.figure(figsize=(16, 5)) # Reduced figure size + plt.title('T2', fontsize=12, pad=10) # Reduced title size and padding + plt.grid(True, axis='y', linestyle='--', alpha=0.7) + + x = np.arange(len(organs)) * 1.5 # Reduced spacing between groups + width = 0.35 # Reduced bar width + + colors = { + '2D': 'royalblue', + '3D_fullres': 'limegreen', + '3D_lowres': 'orange' + } + + positions = { + '2D': x - width, + '3D_fullres': x, + '3D_lowres': x + width + } + + for model, averages in model_averages.items(): + bars = plt.bar(positions[model], + [averages[organ] * 100 for organ in organs.keys()], + width, + label=model, + color=colors[model]) + + # Add data labels + for j, bar in enumerate(bars): + height = bar.get_height() + organ = list(organs.keys())[j] + std = model_stds[model][organ] * 100 + plt.text(bar.get_x() + bar.get_width()/2., height + 0.5, + f'{height:.1f}±{std:.1f}%', + ha='center', + va='bottom', + fontsize=6) # Reduced font size + + plt.ylim(0, 100) + plt.ylabel('DC (%)', fontsize=10) + plt.xlabel('OAR', fontsize=10) + plt.xticks(x, [organs[k] for k in organs.keys()], rotation=30, ha='right', fontsize=8) + plt.legend(loc='upper right', fontsize=8) + plt.tight_layout() + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + plt.savefig(output_path, dpi=300, bbox_inches='tight') + plt.close() + +def print_results(model_averages: Dict, model_stds: Dict, organs: Dict): + """Print numerical results""" + print("\nMean Dice Scores (percentage):") + for model in model_averages.keys(): + print(f"\n{model}:") + for organ in organs.keys(): + avg = model_averages[model][organ] * 100 + std = model_stds[model][organ] * 100 + print(f"{organs[organ]}: {avg:.1f}±{std:.1f}%") + +def main(): + # Define paths and organ mapping + base_path = '/mnt/1248/onlylian/T2/Dice/validation' + paths = { + '2D': os.path.join(base_path, 'T2_validation_dice_results_2D.json'), + '3D_fullres': os.path.join(base_path, 'T2_validation_dice_results_3D_fullres.json'), + '3D_lowres': os.path.join(base_path, 'T2_validation_dice_results_3D_lowres.json') + } + + organs = { + "1": "Brainstem", + "2": "Right_Eye", + "3": "Left_Eye", + "4": "Optic_Chiasm", + "5": "Right_Optic_Nerve", + "6": "Left_Optic_Nerve" + } + + # Initialize data storage + model_dice_scores = { + model: {organ: None for organ in organs.keys()} + for model in paths.keys() + } + + # Load and process data + for model, path in paths.items(): + data = load_data(path) + for organ_id in organs.keys(): + model_dice_scores[model][organ_id] = extract_scores(data, model, organ_id) + + # Calculate averages and standard deviations + model_averages = {} + model_stds = {} + for model, scores in model_dice_scores.items(): + model_averages[model] = {organ: scores[organ][0] if scores[organ] else 0 for organ in organs.keys()} + model_stds[model] = {organ: scores[organ][1] if scores[organ] else 0 for organ in organs.keys()} + + # Create and save the plot + output_path = os.path.join(base_path, 'T2_validation_dice_scores_comparison.png') + create_plot(model_averages, model_stds, organs, output_path) + + # Print the results + print_results(model_averages, model_stds, organs) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/T2/Dice/validation/compute_validation_dice.py b/T2/Dice/validation/compute_validation_dice.py new file mode 100644 index 0000000..d402a62 --- /dev/null +++ b/T2/Dice/validation/compute_validation_dice.py @@ -0,0 +1,300 @@ +import os +import time +import numpy as np +import SimpleITK as sitk +from tqdm import tqdm +import json + +def load_nifti(file_path): + """加載 NIfTI 文件""" + return sitk.GetArrayFromImage(sitk.ReadImage(file_path)) + +def compute_dice(pred, gt): + """計算Dice係數""" + intersection = np.sum(pred & gt) + sum_pred = np.sum(pred) + sum_gt = np.sum(gt) + + if sum_pred == 0 and sum_gt == 0: + return 1.0 + elif sum_pred == 0 or sum_gt == 0: + return 0.0 + + return 2.0 * intersection / (sum_pred + sum_gt) + +def separate_structures(combined_mask, structure_name=""): + """分離左右結構""" + sitk_image = sitk.GetImageFromArray(combined_mask) + labeled_components = sitk.ConnectedComponent(sitk_image) + labeled_array = sitk.GetArrayFromImage(labeled_components) + + unique, counts = np.unique(labeled_array, return_counts=True) + if len(unique) < 3: + print(f"警告:{structure_name} 未找到足夠的連通區域") + return None, None + + region_sizes = [(i, count) for i, count in zip(unique[1:], counts[1:])] + sorted_regions = sorted(region_sizes, key=lambda x: x[1], reverse=True) + + if len(sorted_regions) < 2: + print(f"警告:{structure_name} 未找到兩個主要區域") + return None, None + + region1_mask = (labeled_array == sorted_regions[0][0]) + region2_mask = (labeled_array == sorted_regions[1][0]) + + def get_centroid(mask): + indices = np.where(mask) + return np.mean(indices[2]) + + centroid1 = get_centroid(region1_mask) + centroid2 = get_centroid(region2_mask) + + if centroid1 > centroid2: + right = region1_mask + left = region2_mask + else: + right = region2_mask + left = region1_mask + + return right.astype(np.uint8), left.astype(np.uint8) + +def evaluate_case(gt_path, pred_path, labels): + """評估單個案例""" + print(f"\n測試文件: {os.path.basename(pred_path)}") + + start_time = time.time() + gt_img = load_nifti(gt_path) + pred_img = load_nifti(pred_path) + load_time = time.time() - start_time + + print(f"圖像大小: {gt_img.shape}") + print(f"加載時間: {load_time:.2f}秒") + + results = {} + total_time = 0 + + # 處理眼睛 + start_time = time.time() + combined_gt_eye = ((gt_img == 2) | (gt_img == 3)).astype(np.uint8) + combined_pred_eye = ((pred_img == 2) | (pred_img == 3)).astype(np.uint8) + + combined_pred_eye_sitk = sitk.GetImageFromArray(combined_pred_eye) + closing_filter = sitk.BinaryMorphologicalClosingImageFilter() + closing_filter.SetKernelRadius(2) + processed_pred_eye = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_eye_sitk)) + + eye_dice = compute_dice(processed_pred_eye, combined_gt_eye) + eye_time = time.time() - start_time + total_time += eye_time + results['combined_eye'] = eye_dice + print(f"眼睛合併計算時間: {eye_time:.2f}秒, Dice: {eye_dice:.4f}") + + right_pred_eye, left_pred_eye = separate_structures(processed_pred_eye, "眼睛") + right_gt_eye, left_gt_eye = separate_structures(combined_gt_eye, "眼睛") + + # 處理視神經 + start_time = time.time() + combined_gt_nerve = ((gt_img == 5) | (gt_img == 6)).astype(np.uint8) + combined_pred_nerve = ((pred_img == 5) | (pred_img == 6)).astype(np.uint8) + + combined_pred_nerve_sitk = sitk.GetImageFromArray(combined_pred_nerve) + processed_pred_nerve = sitk.GetArrayFromImage(closing_filter.Execute(combined_pred_nerve_sitk)) + + nerve_dice = compute_dice(processed_pred_nerve, combined_gt_nerve) + nerve_time = time.time() - start_time + total_time += nerve_time + results['combined_nerve'] = nerve_dice + print(f"視神經合併計算時間: {nerve_time:.2f}秒, Dice: {nerve_dice:.4f}") + + right_pred_nerve, left_pred_nerve = separate_structures(processed_pred_nerve, "視神經") + right_gt_nerve, left_gt_nerve = separate_structures(combined_gt_nerve, "視神經") + + for label in labels: + start_time = time.time() + + if label == 1: # Brainstem + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 2 and right_pred_eye is not None: # Right Eye + gt_binary = right_gt_eye + pred_binary = right_pred_eye + elif label == 3 and left_pred_eye is not None: # Left Eye + gt_binary = left_gt_eye + pred_binary = left_pred_eye + elif label == 4: # Optic Chiasm + gt_binary = (gt_img == label).astype(np.uint8) + pred_binary = (pred_img == label).astype(np.uint8) + elif label == 5 and right_pred_nerve is not None: # Right Optic Nerve + gt_binary = right_gt_nerve + pred_binary = right_pred_nerve + elif label == 6 and left_pred_nerve is not None: # Left Optic Nerve + gt_binary = left_gt_nerve + pred_binary = left_pred_nerve + else: + continue + + try: + dice = compute_dice(pred_binary, gt_binary) + compute_time = time.time() - start_time + total_time += compute_time + results[label] = dice + print(f"標籤 {label} 計算時間: {compute_time:.2f}秒, Dice: {dice:.4f}") + except Exception as e: + print(f"處理標籤 {label} 時出錯:{str(e)}") + results[label] = np.nan + continue + + print(f"單個案例總計算時間: {total_time:.2f}秒") + return results + +def evaluate_validation_fold(gt_base_folder, validation_folder, labels): + """評估單個fold的驗證集""" + fold_results = {label: [] for label in labels} + fold_results['combined_eye'] = [] + fold_results['combined_nerve'] = [] + + pred_files = sorted([f for f in os.listdir(validation_folder) if f.endswith('.nii.gz')]) + + for pred_file in tqdm(pred_files, desc=f"Processing {os.path.basename(validation_folder)}"): + # 從預測文件名中提取病人ID + patient_id = pred_file.split('.nii.gz')[0] + + # 構建對應的ground truth路徑 + gt_path = os.path.join(gt_base_folder, f"{patient_id}.nii.gz") + pred_path = os.path.join(validation_folder, pred_file) + + if not os.path.exists(gt_path): + print(f"找不到對應的真實標籤文件:{gt_path}") + continue + + case_results = evaluate_case(gt_path, pred_path, labels) + + for key in case_results: + if not np.isnan(case_results[key]): + if key in fold_results: + fold_results[key].append(case_results[key]) + + return fold_results + +def evaluate_model(gt_folder, model_path, model_name, labels, organ_names, output_dir): + """評估模型所有fold的驗證集""" + print(f"\n開始評估模型:{model_name}") + all_results = {label: [] for label in labels} + all_results['combined_eye'] = [] + all_results['combined_nerve'] = [] + fold_results = {} + + # 遍歷每個fold + for fold in range(5): + validation_folder = os.path.join(model_path, f"fold_{fold}", "validation") + + if not os.path.exists(validation_folder): + print(f"找不到fold_{fold}的驗證資料夾:{validation_folder}") + continue + + print(f"\n處理 {model_name} - Fold {fold} 驗證集") + results = evaluate_validation_fold(gt_folder, validation_folder, labels) + fold_results[f"fold_{fold}"] = results + + for key in results: + all_results[key].extend(results[key]) + + # 計算總平均值和標準差 + summary = {} + for key in all_results: + if all_results[key]: + mean_dice = np.mean(all_results[key]) + std_dice = np.std(all_results[key]) + summary[key] = { + 'mean_dice': float(mean_dice), + 'std_dice': float(std_dice), + 'n_samples': len(all_results[key]) + } + else: + summary[key] = { + 'mean_dice': np.nan, + 'std_dice': np.nan, + 'n_samples': 0 + } + + # 保存結果 + model_results = { + 'summary': summary, + 'fold_results': fold_results + } + + # 確保輸出目錄存在 + os.makedirs(output_dir, exist_ok=True) + + # 使用完整路徑保存結果 + filename = os.path.join(output_dir, f'T2_validation_dice_results_{model_name}.json') + with open(filename, 'w') as f: + json.dump(model_results, f, indent=4) + print(f"\n{model_name}驗證集結果已保存到 {filename}") + + # 打印結果 + print(f"\n{model_name} 模型的驗證集 Dice 結果(所有fold的平均值):") + + # 打印器官結果 + for label in labels: + if label in summary: + mean = summary[label]['mean_dice'] + std = summary[label]['std_dice'] + n = summary[label]['n_samples'] + organ_name = organ_names.get(label, f"Label_{label}") + if not np.isnan(mean): + print(f"{organ_name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{organ_name}: 無法計算 (n={n})") + + # 打印合併結果 + print("\n合併結構的結果:") + for key in ['combined_eye', 'combined_nerve']: + if key in summary: + mean = summary[key]['mean_dice'] + std = summary[key]['std_dice'] + n = summary[key]['n_samples'] + name = "合併眼睛" if key == 'combined_eye' else "合併視神經" + if not np.isnan(mean): + print(f"{name}: {mean:.4f} ± {std:.4f} (n={n})") + else: + print(f"{name}: 無法計算 (n={n})") + + return summary + +if __name__ == "__main__": + # 設置路徑 + base_dir = "/mnt/1248/onlylian" + gt_folder = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset111/labelsTr") + output_dir = base_dir # 設置輸出目錄為 /mnt/1248/onlylian + + models = { + '2D': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset111/nnUNetTrainer__nnUNetPlans__2d"), + '3D_fullres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset111/nnUNetTrainer__nnUNetPlans__3d_fullres"), + '3D_lowres': os.path.join(base_dir, "nnUNet/nnUNet_results/Dataset111/nnUNetTrainer__nnUNetPlans__3d_lowres") + } + + labels = [1, 2, 3, 4, 5, 6] + organ_names = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + + # 讓用戶選擇要運行哪個模型 + print("可用的模型:") + for i, model_name in enumerate(models.keys(), 1): + print(f"{i}. {model_name}") + + choice = input("\n請選擇要運行的模型 (輸入數字1-3): ") + try: + model_index = int(choice) - 1 + model_names = list(models.keys()) + selected_model = model_names[model_index] + evaluate_model(gt_folder, models[selected_model], selected_model, labels, organ_names, output_dir) + except (ValueError, IndexError): + print("無效的選擇!請輸入1-3之間的數字。") \ No newline at end of file diff --git a/T2/visualize_medical_compare.py b/T2/visualize_medical_compare.py new file mode 100644 index 0000000..bb27213 --- /dev/null +++ b/T2/visualize_medical_compare.py @@ -0,0 +1,151 @@ +import nibabel as nib +import matplotlib.pyplot as plt +import numpy as np +import os + +def load_nifti(file_path): + """載入NIfTI檔案""" + return nib.load(file_path).get_fdata() + +def create_colored_mask(data): + """創建彩色遮罩""" + color_dict = { + 1: [144/255, 238/255, 144/255], # Brainstem - 綠色 + 2: [255/255, 218/255, 150/255], # Right_Eye - 淺黃色 + 3: [205/255, 170/255, 125/255], # Left_Eye - 棕色 + 4: [135/255, 206/255, 235/255], # Optic_Chiasm - 藍色 + 5: [255/255, 99/255, 71/255], # Right_Optic_Nerve - 亮珊瑚紅 + 6: [255/255, 160/255, 122/255] # Left_Optic_Nerve - 淺珊瑚紅 + } + + mask = np.zeros((*data.shape, 3)) + + for label, color in color_dict.items(): + mask_3d = np.dstack([data == label] * 3) + mask = np.where(mask_3d, color, mask) + + return mask + +def get_brain_bounds(image): + """獲取腦部區域的邊界""" + mask = image > np.percentile(image, 1) + rows = np.any(mask, axis=1) + cols = np.any(mask, axis=0) + rmin, rmax = np.where(rows)[0][[0, -1]] + cmin, cmax = np.where(cols)[0][[0, -1]] + + center_r = (rmin + rmax) // 2 + center_c = (cmin + cmax) // 2 + + # 減小裁剪半徑以放大顯示區域 + radius = int(max(rmax - rmin, cmax - cmin) * 0.55) + + # 調整邊距 + margin = int(radius * 0.15) + + # 根據中心點和半徑設定新的邊界 + rmin = max(center_r - radius - margin, 0) + rmax = min(center_r + radius + margin, image.shape[0]) + cmin = max(center_c - radius - margin, 0) + cmax = min(center_c + radius + margin, image.shape[1]) + + return rmin, rmax, cmin, cmax + +def check_organs_in_slice(slice_data): + """檢查切片中存在的器官標籤""" + unique_labels = set(np.unique(slice_data)) + if 0 in unique_labels: + unique_labels.remove(0) + organ_dict = { + 1: "Brainstem", + 2: "Right_Eye", + 3: "Left_Eye", + 4: "Optic_Chiasm", + 5: "Right_Optic_Nerve", + 6: "Left_Optic_Nerve" + } + return {organ_dict[label] for label in unique_labels if label in organ_dict} + +def save_slice(image, ground_truth, prediction, slice_num, save_path): + """保存指定切片的比較圖""" + plt.clf() + fig, axes = plt.subplots(1, 2, figsize=(25, 11)) + plt.subplots_adjust(wspace=0.01) + + img_slice = np.rot90(image[:, :, slice_num]) + rmin, rmax, cmin, cmax = get_brain_bounds(img_slice) + + # Ground Truth + gt_slice = np.rot90(ground_truth[:, :, slice_num]) + gt_mask = create_colored_mask(gt_slice) + + axes[0].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray') + axes[0].imshow(gt_mask[rmin:rmax, cmin:cmax], alpha=0.5) + axes[0].set_title('Ground Truth', fontsize=32, pad=20, weight='bold') + axes[0].axis('off') + + # Prediction + pred_slice = np.rot90(prediction[:, :, slice_num]) + pred_mask = create_colored_mask(pred_slice) + + axes[1].imshow(img_slice[rmin:rmax, cmin:cmax], cmap='gray') + axes[1].imshow(pred_mask[rmin:rmax, cmin:cmax], alpha=0.5) + axes[1].set_title('Prediction', fontsize=32, pad=20, weight='bold') + axes[1].axis('off') + + plt.savefig(save_path, bbox_inches='tight', dpi=300, pad_inches=0.05) + plt.close() + print(f"已保存切片 {slice_num} 至: {save_path}") + +if __name__ == "__main__": + # 設定路徑 + base_dir = "/mnt/1248/onlylian" + image_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset111/imagesTs/T2_039_0000.nii.gz") + gt_path = os.path.join(base_dir, "nnUNet/nnUNet_raw/Dataset111/labelsTs/T2_039.nii.gz") + pred_path = os.path.join(base_dir, "T2/output_predictions/2d/fold_4/T2_039.nii.gz") + save_dir = os.path.join(base_dir, "T2") + + # 確保保存目錄存在 + os.makedirs(save_dir, exist_ok=True) + + # 載入影像 + print("正在載入影像...") + image = load_nifti(image_path) + ground_truth = load_nifti(gt_path) + prediction = load_nifti(pred_path) + print("影像載入完成") + + # 檢查每個切片包含的器官 + print("\n分析切片中的器官...") + total_slices = image.shape[2] + for slice_num in range(total_slices): + gt_slice = ground_truth[:, :, slice_num] + pred_slice = prediction[:, :, slice_num] + + # 獲取器官信息 + gt_organs = check_organs_in_slice(gt_slice) + pred_organs = check_organs_in_slice(pred_slice) + + if gt_organs or pred_organs: + print(f"\n切片 {slice_num}:") + print(f"Ground Truth 包含: {', '.join(sorted(gt_organs))}") + print(f"Prediction 包含: {', '.join(sorted(pred_organs))}") + + # 讓用戶選擇要保存的切片 + print("\n開始切片選擇和保存過程...") + while True: + try: + slice_num = int(input("\n請輸入要保存的切片編號 (-1 退出): ")) + if slice_num == -1: + print("程序結束") + break + if 0 <= slice_num < total_slices: + save_path = os.path.join(save_dir, f'comparison_T2_069_{slice_num:03d}.png') + save_slice(image, ground_truth, prediction, slice_num, save_path) + else: + print(f"切片編號必須在 0 到 {total_slices-1} 之間") + except ValueError: + print("請輸入有效的數字") + except Exception as e: + print(f"發生錯誤: {str(e)}") + diff --git a/preprocessing/Image_files/copy_and_classify_modalities.py b/preprocessing/Image_files/copy_and_classify_modalities.py new file mode 100644 index 0000000..6168c69 --- /dev/null +++ b/preprocessing/Image_files/copy_and_classify_modalities.py @@ -0,0 +1,112 @@ +import os +import shutil +import pandas as pd + +def load_excel(file_path): + """載入 Excel 文件""" + return pd.read_excel(file_path, sheet_name='Sheet1') + +def ensure_directories_exist(target_dir, modalities): + """確保目標目錄存在""" + for modality in modalities: + os.makedirs(os.path.join(target_dir, modality), exist_ok=True) + +def find_file_recursive(filename, search_path): + """在給定目錄中遞迴搜索文件。""" + for root, dirs, files in os.walk(search_path): + if filename in files: + return os.path.join(root, filename) + return None + +def extract_patient_and_date(file_path): + """從文件路徑中提取病人檔名和日期。""" + parts = file_path.split(os.sep) + if len(parts) >= 4: + return parts[-4], parts[-3] # 假設病人檔名在倒數第四層,日期在倒數第三層 + return None, None + +def is_swi_file(file_name): + """檢查檔案名稱是否包含 'swi'""" + return 'swi' in file_name.lower() + +def copy_files(df, source_dir, target_dir): + """遍歷數據框並將對應的 .warp.nii.gz 文件複製到相應的模態資料夾,若無 .warp.nii.gz 則複製原始的 .nii.gz 文件,保留病人檔名和日期""" + processed_patients = set() + + for index, row in df.iterrows(): + file_name = row['name'] + modality = row['modality_nn'] + + # 檢查是否為 swi 檔案 + if is_swi_file(file_name): + print(f'跳過 swi 檔案: {file_name}') + continue + + # 修改來源檔名:將 .nii.gz 替換為 .warp.nii.gz + warp_file_name = file_name.replace('.nii.gz', '.warp.nii.gz') + + # 在源目錄中遞迴搜索 .warp.nii.gz 文件 + warp_file_path = find_file_recursive(warp_file_name, source_dir) + + if warp_file_path: + # 如果找到 .warp.nii.gz 文件,複製該文件 + patient_id, date = extract_patient_and_date(warp_file_path) + if patient_id and date: + if patient_id not in processed_patients: + processed_patients.add(patient_id) + + # 複製到個別的模態資料夾 + target_dir_path = os.path.join(target_dir, modality, patient_id, date) + os.makedirs(target_dir_path, exist_ok=True) + target_file_path = os.path.join(target_dir_path, warp_file_name) + shutil.copy2(warp_file_path, target_file_path) + print(f'已複製 {warp_file_name} 到 {target_dir_path} 資料夾。') + + # 如果是 CT 或 CTC,也複製到 CT+CTC 資料夾 + if modality in ['CT', 'CTC']: + combined_target_dir_path = os.path.join(target_dir, 'CT+CTC', patient_id, date) + os.makedirs(combined_target_dir_path, exist_ok=True) + combined_target_file_path = os.path.join(combined_target_dir_path, warp_file_name) + shutil.copy2(warp_file_path, combined_target_file_path) + print(f'已複製 {warp_file_name} 到 {combined_target_dir_path} 資料夾。') + else: + # 如果找不到 .warp.nii.gz 文件,則複製原始的 .nii.gz 文件 + original_file_path = find_file_recursive(file_name, source_dir) + if original_file_path: + patient_id, date = extract_patient_and_date(original_file_path) + if patient_id and date: + if patient_id not in processed_patients: + processed_patients.add(patient_id) + + # 複製到個別的模態資料夾 + target_dir_path = os.path.join(target_dir, modality, patient_id, date) + os.makedirs(target_dir_path, exist_ok=True) + target_file_path = os.path.join(target_dir_path, file_name) + shutil.copy2(original_file_path, target_file_path) + print(f'已複製 {file_name} 到 {target_dir_path} 資料夾。') + + # 如果是 CT 或 CTC,也複製到 CT+CTC 資料夾 + if modality in ['CT', 'CTC']: + combined_target_dir_path = os.path.join(target_dir, 'CT+CTC', patient_id, date) + os.makedirs(combined_target_dir_path, exist_ok=True) + combined_target_file_path = os.path.join(combined_target_dir_path, file_name) + shutil.copy2(original_file_path, combined_target_file_path) + print(f'已複製 {file_name} 到 {combined_target_dir_path} 資料夾。') + else: + print(f'{file_name} 和 {warp_file_name} 均在源目錄中不存在。') + +def main(): + file_path = '/home/onlylian/modality_by_nn.xlsx' + source_dir = '/home/onlylian/M6_24Q3' + target_dir = '/home/onlylian/sorted_modality_files_inference' + + modalities = ['CT', 'CTC', 'T1', 'T1C', 'T2', 'FLAIR', 'CT+CTC'] # 加入 CT+CTC + + df = load_excel(file_path) + ensure_directories_exist(target_dir, modalities) + copy_files(df, source_dir, target_dir) + + print('文件分類和複製完成。') + +if __name__ == "__main__": + main() diff --git a/preprocessing/Image_files/merge_H4_N6_directories.py b/preprocessing/Image_files/merge_H4_N6_directories.py new file mode 100644 index 0000000..2ad085d --- /dev/null +++ b/preprocessing/Image_files/merge_H4_N6_directories.py @@ -0,0 +1,47 @@ +import os +import shutil + +def merge_directories(src1, src2, dest): + if not os.path.exists(dest): + os.makedirs(dest) + + def copy_or_merge(src, dest): + for patient in os.listdir(src): + patient_src_path = os.path.join(src, patient) + patient_dest_path = os.path.join(dest, patient) + + if os.path.isdir(patient_src_path): + if os.path.exists(patient_dest_path): + # If destination folder exists, merge the contents + for item in os.listdir(patient_src_path): + s = os.path.join(patient_src_path, item) + d = os.path.join(patient_dest_path, item) + if os.path.isdir(s): + shutil.copytree(s, d, dirs_exist_ok=True) + else: + shutil.copy2(s, d) + else: + # If destination folder does not exist, copy the whole folder + shutil.copytree(patient_src_path, patient_dest_path) + + # Copy or merge from both source directories to destination directory + copy_or_merge(src1, dest) + copy_or_merge(src2, dest) + + print("Directories merged successfully.") + +# Define source and destination paths +src1 = '/mnt/1220/Public/dataset2/H4' +src2 = '/mnt/1220/Public/dataset2/N6' +dest = "/home/onlylian/dataset2_H4_N6" + +# Call the function to merge the directories +merge_directories(src1, src2, dest) + + + + + + + + diff --git a/preprocessing/Image_files/multi_modality_image_copy.py b/preprocessing/Image_files/multi_modality_image_copy.py new file mode 100644 index 0000000..5ff8d63 --- /dev/null +++ b/preprocessing/Image_files/multi_modality_image_copy.py @@ -0,0 +1,52 @@ +import os +import shutil + +def copy_last_file(source_base_dir, target_base_dir, modality): + # 確保目標目錄存在 + os.makedirs(target_base_dir, exist_ok=True) + + # 按照病患資料夾名稱進行排序 + for patient_dir in sorted(os.listdir(source_base_dir)): + patient_path = os.path.join(source_base_dir, patient_dir) + if os.path.isdir(patient_path): + # 按照日期資料夾名稱進行排序 + date_dirs = sorted(os.listdir(patient_path)) + for date_dir in date_dirs: + date_path = os.path.join(patient_path, date_dir) + if os.path.isdir(date_path): + files = os.listdir(date_path) + + # 篩選出 .nii.gz 和 .warp.nii.gz 檔案 + nii_files = [f for f in files if f.endswith('.nii.gz')] + warp_files = [f for f in files if f.endswith('.warp.nii.gz')] + + # 先選擇最後一個 .warp.nii.gz 檔案,如果沒有則選擇 .nii.gz 檔案 + if warp_files: + warp_files.sort() + last_file = warp_files[-1] # 選擇最後一個 .warp.nii.gz 檔案 + elif nii_files: + nii_files.sort() + last_file = nii_files[-1] # 選擇最後一個 .nii.gz 檔案 + else: + print(f"No valid .nii.gz or .warp.nii.gz files found in {date_path}") + continue + + last_file_path = os.path.join(date_path, last_file) + + # 根據模態重新命名文件 + new_file_name = f"{modality}_{patient_dir}.nii.gz" + target_file_path = os.path.join(target_base_dir, new_file_name) + + # 複製文件到目標目錄 + shutil.copy2(last_file_path, target_file_path) + print(f"Copied: {last_file_path} to {target_file_path}") + +# T1C 模態的處理 +source_base_dir_t1c = '/home/onlylian/sorted_modality_files/T1C' +target_base_dir_t1c = '/home/onlylian/T1C_image_folder' +copy_last_file(source_base_dir_t1c, target_base_dir_t1c, 'T1C') + +# CT+CTC 模態的處理 +source_base_dir_ctctc = '/home/onlylian/sorted_modality_files/CT+CTC' +target_base_dir_ctctc = '/home/onlylian/CT+CTC_image_folder' +copy_last_file(source_base_dir_ctctc, target_base_dir_ctctc, 'CT+CTC') \ No newline at end of file diff --git a/preprocessing/Image_files/patient_modalities_files.py b/preprocessing/Image_files/patient_modalities_files.py new file mode 100644 index 0000000..cbdf392 --- /dev/null +++ b/preprocessing/Image_files/patient_modalities_files.py @@ -0,0 +1,51 @@ +import os +import pandas as pd + +# 要扫描的根目录 +root_dir = '/home/onlylian/sorted_modality_files' + +# 为 DataFrame 准备数据 +data = [] + +# 遍历目录 +for subdir, _, files in os.walk(root_dir): + for file in files: + if file.endswith('.nii.gz'): + parts = subdir.split('/') + try: + if len(parts) >= 8: # 标准结构 + modalities = parts[4] # 提取模态类型 + patient_id = parts[5] # 提取患者信息 + date = parts[6] # 提取日期 + name = file # 提取文件名 + elif len(parts) == 7: # 当模态类型与具体模态合并时 + modalities = parts[4] # 提取模态类型 + patient_id = parts[5] # 提取患者信息 + date = parts[6] # 提取日期 + name = file # 提取文件名 + elif len(parts) == 6: # 当只有日期和模态类型时 + modalities = parts[4] # 提取模态类型 + patient_id = parts[5] # 提取患者信息 + date = '' # 没有日期信息 + name = file # 提取文件名 + else: + print(f"Path {subdir} does not have enough parts to extract data") + continue + data.append([modalities, patient_id, date, name]) + print(f"Added: {modalities}/{patient_id}/{date}/{name}") # 调试信息 + except IndexError as e: + print(f"Error processing path {subdir}: {e}") + +# 检查数据是否已经添加 +if not data: + print("No data found. Please check the directory structure and files.") + +# 创建 DataFrame +df = pd.DataFrame(data, columns=['Modalities', 'patient_id', 'date', 'name']) +# 输出到 Excel +output_path = '/home/onlylian/patient_modalities_files.xlsx' +df.to_excel(output_path, index=False) + +print(f"Excel 文件已生成在: {output_path}") + + diff --git a/preprocessing/OAR_Files/copy_patient_OAR_files.py b/preprocessing/OAR_Files/copy_patient_OAR_files.py new file mode 100644 index 0000000..516cfa2 --- /dev/null +++ b/preprocessing/OAR_Files/copy_patient_OAR_files.py @@ -0,0 +1,46 @@ +import os +import shutil +import pandas as pd + +# 讀取 Excel 文件 +file_path = '/home/onlylian/patient_modalities_files.xlsx' +df = pd.read_excel(file_path) + +# 定義來源和目標資料夾 +source_dir = '/home/onlylian/Complete_NEW_OAR_Files' +target_dir = '/home/onlylian/sorted_OAR_files' +modalities = ['CT', 'CTC', 'T1', 'T1C', 'T2', 'FLAIR', 'CT+CTC'] + +# 創建目標資料夾結構 +for modality in modalities: + modality_path = os.path.join(target_dir, modality) + os.makedirs(modality_path, exist_ok=True) + +# 遍歷每個病人ID和日期 +for _, row in df.iterrows(): + modality = row['Modalities'] + patient_id = row['patient_id'] + date = str(row['date']) + + if modality in modalities: + # 構建來源資料夾路徑 + source_path = os.path.join(source_dir, patient_id, date) + print(f"Checking source path: {source_path}") # 檢查路徑 + if os.path.exists(source_path): + # 構建目標資料夾路徑 + modality_path = os.path.join(target_dir, modality, patient_id, date) + os.makedirs(modality_path, exist_ok=True) + + # 遍歷來源資料夾中的所有檔案 + for file_name in os.listdir(source_path): + # 更新條件檢查 + if file_name.endswith('.nii.gz'): # 確保是正確的檔案類型 + source_file = os.path.join(source_path, file_name) + target_file = os.path.join(modality_path, file_name) + print(f"Copying {source_file} to {target_file}") # 跟蹤檔案複製 + # 複製檔案到目標資料夾 + shutil.copy2(source_file, target_file) + else: + print(f"Source path not found: {source_path}") + else: + print(f"Unknown modality {modality} for patient {patient_id} on {date}") diff --git a/preprocessing/OAR_Files/merge_G4_M6_directories.py b/preprocessing/OAR_Files/merge_G4_M6_directories.py new file mode 100644 index 0000000..d228a31 --- /dev/null +++ b/preprocessing/OAR_Files/merge_G4_M6_directories.py @@ -0,0 +1,47 @@ +import os +import shutil + +def merge_directories(src1, src2, dest): + if not os.path.exists(dest): + os.makedirs(dest) + + def copy_or_merge(src, dest): + for patient in os.listdir(src): + patient_src_path = os.path.join(src, patient) + patient_dest_path = os.path.join(dest, patient) + + if os.path.isdir(patient_src_path): + if os.path.exists(patient_dest_path): + # If destination folder exists, merge the contents + for item in os.listdir(patient_src_path): + s = os.path.join(patient_src_path, item) + d = os.path.join(patient_dest_path, item) + if os.path.isdir(s): + shutil.copytree(s, d, dirs_exist_ok=True) + else: + shutil.copy2(s, d) + else: + # If destination folder does not exist, copy the whole folder + shutil.copytree(patient_src_path, patient_dest_path) + + # Copy or merge from both source directories to destination directory + copy_or_merge(src1, dest) + copy_or_merge(src2, dest) + + print("Directories merged successfully.") + +# Define source and destination paths +src1 = '/mnt/1220/Public/dataset2/G4' +src2 = '/mnt/1220/Public/dataset2/M6' +dest = "/home/onlylian/dataset2_G4_M6" + +# Call the function to merge the directories +merge_directories(src1, src2, dest) + + + + + + + + diff --git a/preprocessing/OAR_Files/merge_patient_folders.py b/preprocessing/OAR_Files/merge_patient_folders.py new file mode 100644 index 0000000..aacd05c --- /dev/null +++ b/preprocessing/OAR_Files/merge_patient_folders.py @@ -0,0 +1,46 @@ +import os +import shutil +import re + +def extract_patient_id(filename): + # 假設文件名格式為 "something_PATIENTID.nii.gz" + match = re.search(r'_([A-Z0-9]+)\.nii\.gz$', filename) + if match: + return match.group(1) + return None + +def merge_folders(folder1, folder2, output_folder): + # 確保輸出資料夾存在 + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + # 獲取兩個資料夾中的所有文件 + files1 = set(os.listdir(folder1)) + files2 = set(os.listdir(folder2)) + + # 合併所有唯一的病人ID + all_files = files1.union(files2) + + for file in all_files: + patient_id = extract_patient_id(file) + if patient_id: + new_filename = f"label_{patient_id}.nii.gz" + source_folder = folder1 if file in files1 else folder2 + source_path = os.path.join(source_folder, file) + dest_path = os.path.join(output_folder, new_filename) + + # 複製文件到輸出資料夾並重命名 + shutil.copy2(source_path, dest_path) + print(f"Copied and renamed {file} to {new_filename}") + else: + print(f"Skipped {file} - could not extract patient ID") + +# 設定資料夾路徑 +folder1 = '/home/onlylian/resampled_T1C_label' +folder2 = '/home/onlylian/resampled_CT+CTC_label' +output_folder = '/home/onlylian/merged_label_folder' + +# 執行合併 +merge_folders(folder1, folder2, output_folder) + +print("Merging complete!") \ No newline at end of file diff --git a/preprocessing/OAR_Files/multi_organ_label_combiner.py b/preprocessing/OAR_Files/multi_organ_label_combiner.py new file mode 100644 index 0000000..c074588 --- /dev/null +++ b/preprocessing/OAR_Files/multi_organ_label_combiner.py @@ -0,0 +1,89 @@ +import os +import SimpleITK as sitk +import difflib + +# 定義資料夾路徑 +T1C_folder_path = '/home/onlylian/sorted_OAR_files/T1C' +CTCTC_folder_path = '/home/onlylian/sorted_OAR_files/CT+CTC' +T1C_output_folder_path = '/home/onlylian/T1C_label_folder' +CTCTC_output_folder_path = '/home/onlylian/CT+CTC_label_folder' + +# 定義組織標籤對應關係 +labels = { + "background": "0", + "Brainstem": "1", + "Right_Eye": "2", + "Left_Eye": "3", + "Optic_Chiasm": "4", + "Right_Optic_Nerve": "5", + "Left_Optic_Nerve": "6" +} + +# 確保輸出資料夾存在 +os.makedirs(T1C_output_folder_path, exist_ok=True) +os.makedirs(CTCTC_output_folder_path, exist_ok=True) + +def resample_image(image, reference_image): + resample = sitk.ResampleImageFilter() + resample.SetReferenceImage(reference_image) + resample.SetInterpolator(sitk.sitkNearestNeighbor) + resample.SetDefaultPixelValue(0) + return resample.Execute(image) + +def process_images(input_folder, output_folder, image_type): + # 獲取所有病人的資料夾路徑 + patient_folders = sorted([os.path.join(dp, d) for dp, dn, filenames in os.walk(input_folder) for d in dn]) + + file_counter = 1 + + for patient_folder in patient_folders: + # 創建空的影像,用來合成所有器官 + combined_image = None + # 獲取病人文件夾中的所有文件名,並轉換為小寫形式 + patient_files = {f.lower(): f for f in os.listdir(patient_folder)} + filename = patient_folder.split('/')[-2] + + # 遍歷每個病人的所有影像文件 + for organ_name, label in labels.items(): + organ_filename = f'struct_{organ_name}.nii.gz'.lower() + # 使用 difflib.get_close_matches 查找相似文件名 + possible_matches = difflib.get_close_matches(organ_filename, patient_files.keys(), n=1, cutoff=0.8) + + if possible_matches: + matched_filename = patient_files[possible_matches[0]] + organ_path = os.path.join(patient_folder, matched_filename) + organ_image = sitk.ReadImage(organ_path) + + if combined_image is None: + # 初始化合成影像 + combined_image = sitk.Image(organ_image.GetSize(), sitk.sitkUInt8) + combined_image.CopyInformation(organ_image) + + # 重新取樣器官影像以匹配合成影像的大小 + resampled_organ_image = resample_image(organ_image, combined_image) + + # 將器官影像添加到合成影像中 + organ_array = sitk.GetArrayFromImage(resampled_organ_image) + combined_array = sitk.GetArrayFromImage(combined_image) + combined_array[organ_array > 0] = int(label) + + combined_image = sitk.GetImageFromArray(combined_array) + combined_image.CopyInformation(resampled_organ_image) + + # 保存合成影像 + if combined_image: + output_filename = f'{image_type}_label_{filename}.nii.gz' + output_path = os.path.join(output_folder, output_filename) + sitk.WriteImage(combined_image, output_path) + print(f'Saved combined image to {output_path}') + file_counter += 1 + +# 處理 T1C 影像 +print("Processing T1C images...") +process_images(T1C_folder_path, T1C_output_folder_path, 'T1C') + +# 處理 CT+CTC 影像 +print("Processing CT+CTC images...") +process_images(CTCTC_folder_path, CTCTC_output_folder_path, 'CT+CTC') + +print("All processing completed.") \ No newline at end of file diff --git a/preprocessing/OAR_Files/organ_file_processor.py b/preprocessing/OAR_Files/organ_file_processor.py new file mode 100644 index 0000000..592bf22 --- /dev/null +++ b/preprocessing/OAR_Files/organ_file_processor.py @@ -0,0 +1,138 @@ +import os +import shutil +import difflib + +def copy_organ_files(source_root, target_root): + # 確保目標資料夾存在 + if not os.path.exists(target_root): + os.makedirs(target_root) + print(f"Created directory {target_root}") + + # 遍歷來源的根資料夾和所有子資料夾,尋找名為 ORGAN 的資料夾 + for root, dirs, files in os.walk(source_root): + for dir in dirs: + if dir == 'ORGAN': + source_folder_path = os.path.join(root, dir) + # 提取病例資料夾和日期資料夾 + case_folder = os.path.basename(os.path.dirname(os.path.dirname(os.path.dirname(source_folder_path)))) + date_folder = os.path.basename(os.path.dirname(os.path.dirname(source_folder_path))) + # 組合目標資料夾路徑 + target_folder_path = os.path.join(target_root, case_folder, date_folder) + + # 確保目標資料夾存在 + if not os.path.exists(target_folder_path): + os.makedirs(target_folder_path) + print(f"Created directory {target_folder_path}") + + # 複製資料夾中的所有檔案到目標資料夾 + for file_name in os.listdir(source_folder_path): + source_file_path = os.path.join(source_folder_path, file_name) + target_file_path = os.path.join(target_folder_path, file_name) + + # 如果目標檔案已經存在,重新命名以避免覆蓋 + if os.path.exists(target_file_path): + base, extension = os.path.splitext(file_name) + counter = 1 + new_file_name = f"{base}_{counter}{extension}" + target_file_path = os.path.join(target_folder_path, new_file_name) + while os.path.exists(target_file_path): + counter += 1 + new_file_name = f"{base}_{counter}{extension}" + target_file_path = os.path.join(target_folder_path, new_file_name) + + shutil.copy(source_file_path, target_file_path) + print(f"Copied {source_file_path} to {target_file_path}") + + print("All ORGAN files have been copied.") + +def check_and_copy_files(root_dir, required_file_groups, target_dir, match_cutoff=0.7): + # 確保目標目錄存在 + os.makedirs(target_dir, exist_ok=True) + + # 計算缺少文件的病人數量 + missing_files_patients = 0 + copied_files_patients = 0 + + # 遍歷根目錄下的每個病人文件夾 + for patient_id in os.listdir(root_dir): + patient_dir = os.path.join(root_dir, patient_id) + + # 確保是目錄 + if os.path.isdir(patient_dir): + # 遍歷病人目錄下的日期文件夾 + for date_folder in os.listdir(patient_dir): + date_dir = os.path.join(patient_dir, date_folder) + existing_files = os.listdir(date_dir) + + # 檢查指定的OAR文件是否存在 + missing_files = [] + for file_group in required_file_groups: + # 對於左右眼的文件,進行更精確的匹配 + if 'Left_Eye' in file_group[0]: + matches = [f for f in existing_files if 'Left_Eye' in f] + elif 'Right_Eye' in file_group[0]: + matches = [f for f in existing_files if 'Right_Eye' in f] + else: + # 對其他文件使用模糊匹配來查找接近的文件名稱 + matches = difflib.get_close_matches(file_group[0], existing_files, n=1, cutoff=match_cutoff) + + if not matches: + missing_files.append(file_group[0]) # 使用文件組中的第一個文件名作為缺失文件的表示 + else: + print(f"匹配到的文件: {matches[0]} 對應到 {file_group[0]}") + + if missing_files: + missing_files_patients += 1 + print(f"病人: {patient_id}, 日期: {date_folder}, 缺失的文件: {missing_files}") + else: + # 沒有缺失文件,複製文件到新目錄 + new_patient_dir = os.path.join(target_dir, patient_id) + new_date_dir = os.path.join(new_patient_dir, date_folder) + os.makedirs(new_date_dir, exist_ok=True) + + for file_name in existing_files: + src_file = os.path.join(date_dir, file_name) + dst_file = os.path.join(new_date_dir, file_name) + shutil.copy(src_file, dst_file) + + copied_files_patients += 1 + print(f"病人: {patient_id}, 日期: {date_folder}, 文件已成功複製") + + print(f"缺少所需OAR文件的資料總數: {missing_files_patients}") + print(f"文件完整且已複製的資料總數: {copied_files_patients}") + +def count_patients_and_records(target_dir): + patient_count = 0 + record_count = 0 + + for patient_id in os.listdir(target_dir): + patient_dir = os.path.join(target_dir, patient_id) + + if os.path.isdir(patient_dir): + patient_count += 1 + record_count += len(os.listdir(patient_dir)) + + print(f"完整OAR文件的病人總數: {patient_count}") + print(f"完整OAR文件的資料總數: {record_count}") + +if __name__ == "__main__": + # 第一部分:複製 ORGAN 文件 + source_root = '/home/onlylian/dataset2_G4_M6' + intermediate_target = '/home/onlylian/NEW_ORGAN_Files' + copy_organ_files(source_root, intermediate_target) + + # 第二部分:檢查並複製完整的 OAR 文件 + required_file_groups = [ + ['Struct_Brain_Stem.nii.gz'], + ['Struct_Left_Optic_Nerve.nii.gz'], + ['Struct_Right_Optic_Nerve.nii.gz'], + ['Struct_Left_Eye.nii.gz'], + ['Struct_Right_Eye.nii.gz'], + ['Struct_Optic_Chiasm.nii.gz'] + ] + final_target = '/home/onlylian/Complete_NEW_OAR_Files' + + check_and_copy_files(intermediate_target, required_file_groups, final_target) + + # 計算總共的病人和記錄數量 + count_patients_and_records(final_target) \ No newline at end of file diff --git a/preprocessing/OAR_Target_Files/copy_patient_OAR_Target_files.py b/preprocessing/OAR_Target_Files/copy_patient_OAR_Target_files.py new file mode 100644 index 0000000..be50746 --- /dev/null +++ b/preprocessing/OAR_Target_Files/copy_patient_OAR_Target_files.py @@ -0,0 +1,62 @@ +import os +import shutil +import pandas as pd + +# 讀取 Excel 文件 +file_path = '/home/onlylian/patient_modalities_files.xlsx' +df = pd.read_excel(file_path) + +# 定義來源和目標資料夾 +source_dir = '/home/onlylian/Complete_NEW_ORGAN_Target_Files' +target_dir = '/home/onlylian/sorted_OAR_Target_files' +modalities = ['CT', 'CTC', 'T1', 'T1C', 'T2', 'FLAIR', 'CT+CTC'] + +# 創建目標資料夾結構 +for modality in modalities: + modality_path = os.path.join(target_dir, modality) + os.makedirs(modality_path, exist_ok=True) + +# 遍歷每個病人ID和日期 +for _, row in df.iterrows(): + modality = row['Modalities'] + patient_id = row['patient_id'] + date = str(row['date']) + + if modality in modalities: + # 構建來源資料夾路徑 + source_path = os.path.join(source_dir, patient_id, date) + print(f"Checking source path: {source_path}") # 檢查路徑 + if os.path.exists(source_path): + # 分別處理 ORGAN 和 RT 目錄 + organ_path = os.path.join(source_path, 'ORGAN') + rt_path = os.path.join(source_path, 'RT') + + # 構建目標資料夾路徑 + modality_path = os.path.join(target_dir, modality, patient_id, date) + os.makedirs(modality_path, exist_ok=True) + + # 遍歷 ORGAN 目錄中的所有檔案 + if os.path.exists(organ_path): + for file_name in os.listdir(organ_path): + if file_name.endswith('.nii.gz'): # 檢查檔案格式 + source_file = os.path.join(organ_path, file_name) + target_file = os.path.join(modality_path, file_name) + print(f"Copying {source_file} to {target_file}") + shutil.copy2(source_file, target_file) + else: + print(f"ORGAN folder not found: {organ_path}") + + # 遍歷 RT 目錄中的所有檔案 + if os.path.exists(rt_path): + for file_name in os.listdir(rt_path): + if file_name.endswith('.nii.gz'): # 檢查檔案格式 + source_file = os.path.join(rt_path, file_name) + target_file = os.path.join(modality_path, file_name) + print(f"Copying {source_file} to {target_file}") + shutil.copy2(source_file, target_file) + else: + print(f"RT folder not found: {rt_path}") + else: + print(f"Source path not found: {source_path}") + else: + print(f"Unknown modality {modality} for patient {patient_id} on {date}") diff --git a/preprocessing/OAR_Target_Files/merge_G4_M6_directories.py b/preprocessing/OAR_Target_Files/merge_G4_M6_directories.py new file mode 100644 index 0000000..d228a31 --- /dev/null +++ b/preprocessing/OAR_Target_Files/merge_G4_M6_directories.py @@ -0,0 +1,47 @@ +import os +import shutil + +def merge_directories(src1, src2, dest): + if not os.path.exists(dest): + os.makedirs(dest) + + def copy_or_merge(src, dest): + for patient in os.listdir(src): + patient_src_path = os.path.join(src, patient) + patient_dest_path = os.path.join(dest, patient) + + if os.path.isdir(patient_src_path): + if os.path.exists(patient_dest_path): + # If destination folder exists, merge the contents + for item in os.listdir(patient_src_path): + s = os.path.join(patient_src_path, item) + d = os.path.join(patient_dest_path, item) + if os.path.isdir(s): + shutil.copytree(s, d, dirs_exist_ok=True) + else: + shutil.copy2(s, d) + else: + # If destination folder does not exist, copy the whole folder + shutil.copytree(patient_src_path, patient_dest_path) + + # Copy or merge from both source directories to destination directory + copy_or_merge(src1, dest) + copy_or_merge(src2, dest) + + print("Directories merged successfully.") + +# Define source and destination paths +src1 = '/mnt/1220/Public/dataset2/G4' +src2 = '/mnt/1220/Public/dataset2/M6' +dest = "/home/onlylian/dataset2_G4_M6" + +# Call the function to merge the directories +merge_directories(src1, src2, dest) + + + + + + + + diff --git a/preprocessing/OAR_Target_Files/merge_tv_patient_folders.py b/preprocessing/OAR_Target_Files/merge_tv_patient_folders.py new file mode 100644 index 0000000..79f382f --- /dev/null +++ b/preprocessing/OAR_Target_Files/merge_tv_patient_folders.py @@ -0,0 +1,46 @@ +import os +import shutil +import re + +def extract_patient_id(filename): + # 假設文件名格式為 "something_PATIENTID.nii.gz" + match = re.search(r'_([A-Z0-9]+)\.nii\.gz$', filename) + if match: + return match.group(1) + return None + +def merge_folders(folder1, folder2, output_folder): + # 確保輸出資料夾存在 + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + # 獲取兩個資料夾中的所有文件 + files1 = set(os.listdir(folder1)) + files2 = set(os.listdir(folder2)) + + # 合併所有唯一的病人ID + all_files = files1.union(files2) + + for file in all_files: + patient_id = extract_patient_id(file) + if patient_id: + new_filename = f"label_{patient_id}.nii.gz" + source_folder = folder1 if file in files1 else folder2 + source_path = os.path.join(source_folder, file) + dest_path = os.path.join(output_folder, new_filename) + + # 複製文件到輸出資料夾並重命名 + shutil.copy2(source_path, dest_path) + print(f"Copied and renamed {file} to {new_filename}") + else: + print(f"Skipped {file} - could not extract patient ID") + +# 設定資料夾路徑 +folder1 = '/home/onlylian/resampled_T1C_TV_label' +folder2 = '/home/onlylian/resampled_CT+CTC_TV_label' +output_folder = '/home/onlylian/merged_TV_label_folder' + +# 執行合併 +merge_folders(folder1, folder2, output_folder) + +print("Merging complete!") \ No newline at end of file diff --git a/preprocessing/OAR_Target_Files/organ_tv_file_processor.py b/preprocessing/OAR_Target_Files/organ_tv_file_processor.py new file mode 100644 index 0000000..98c594b --- /dev/null +++ b/preprocessing/OAR_Target_Files/organ_tv_file_processor.py @@ -0,0 +1,156 @@ +import os +import shutil +import difflib +import time + +def copy_organ_and_tv_files(source_root, target_root): + # 確保目標資料夾存在 + if not os.path.exists(target_root): + os.makedirs(target_root) + print(f"Created directory {target_root}") + + # 複製 'ORGAN' 資料夾的檔案 + for root, dirs, files in os.walk(source_root): + for dir in dirs: + if dir == 'ORGAN': + source_folder_path = os.path.join(root, dir) + case_folder = os.path.basename(os.path.dirname(os.path.dirname(os.path.dirname(source_folder_path)))) + date_folder = os.path.basename(os.path.dirname(os.path.dirname(source_folder_path))) + target_folder_path = os.path.join(target_root, case_folder, date_folder, 'ORGAN') + + if not os.path.exists(target_folder_path): + os.makedirs(target_folder_path) + print(f"Created directory {target_folder_path}") + + for file_name in os.listdir(source_folder_path): + source_file_path = os.path.join(source_folder_path, file_name) + target_file_path = os.path.join(target_folder_path, file_name) + copy_with_retry(source_file_path, target_file_path) + + # 複製 TV 檔案 + for root, dirs, files in os.walk(source_root): + for file_name in files: + if file_name.startswith('TV') and file_name.endswith('.nii.gz'): + source_file_path = os.path.join(root, file_name) + case_folder = os.path.basename(os.path.dirname(os.path.dirname(os.path.dirname(source_file_path)))) + date_folder = os.path.basename(os.path.dirname(os.path.dirname(source_file_path))) + target_folder_path = os.path.join(target_root, case_folder, date_folder, 'RT') + + if not os.path.exists(target_folder_path): + os.makedirs(target_folder_path) + print(f"Created directory {target_folder_path}") + + target_file_path = os.path.join(target_folder_path, file_name) + copy_with_retry(source_file_path, target_file_path) + + print("All ORGAN and TV files have been copied.") + +def check_and_copy_files(root_dir, required_oar_files, target_dir): + os.makedirs(target_dir, exist_ok=True) + missing_files_patients = 0 + copied_files_patients = 0 + + for patient_id in os.listdir(root_dir): + patient_dir = os.path.join(root_dir, patient_id) + + if os.path.isdir(patient_dir): + for date_folder in os.listdir(patient_dir): + date_dir = os.path.join(patient_dir, date_folder) + organ_dir = os.path.join(date_dir, 'ORGAN') + rt_dir = os.path.join(date_dir, 'RT') + + if os.path.isdir(organ_dir) and os.path.isdir(rt_dir): + organ_files = os.listdir(organ_dir) + rt_files = os.listdir(rt_dir) + + missing_oar_files = [] + for oar_file in required_oar_files: + if 'Left_Eye' in oar_file: + matches = [f for f in organ_files if 'Left_Eye' in f] + elif 'Right_Eye' in oar_file: + matches = [f for f in organ_files if 'Right_Eye' in f] + else: + matches = difflib.get_close_matches(oar_file, organ_files, n=1, cutoff=0.8) + + if not matches: + missing_oar_files.append(oar_file) + + target_files = [f for f in rt_files if f.endswith('.nii.gz')] + if not target_files: + missing_oar_files.append('Target File') + + if missing_oar_files: + missing_files_patients += 1 + print(f"病人: {patient_id}, 日期: {date_folder}, 缺失的文件: {missing_oar_files}") + else: + new_patient_dir = os.path.join(target_dir, patient_id, date_folder) + os.makedirs(new_patient_dir, exist_ok=True) + + for file_name in organ_files: + src_file = os.path.join(organ_dir, file_name) + dst_file = os.path.join(new_patient_dir, 'ORGAN', file_name) + os.makedirs(os.path.join(new_patient_dir, 'ORGAN'), exist_ok=True) + copy_with_retry(src_file, dst_file) + + for file_name in rt_files: + src_file = os.path.join(rt_dir, file_name) + dst_file = os.path.join(new_patient_dir, 'RT', file_name) + os.makedirs(os.path.join(new_patient_dir, 'RT'), exist_ok=True) + copy_with_retry(src_file, dst_file) + + copied_files_patients += 1 + print(f"病人: {patient_id}, 日期: {date_folder}, 文件已成功複製") + else: + missing_files_patients += 1 + print(f"病人: {patient_id}, 日期: {date_folder}, 缺少 ORGAN 或 RT 目錄") + + print(f"缺少所需文件的資料總數: {missing_files_patients}") + print(f"文件完整且已複製的資料總數: {copied_files_patients}") + +def copy_with_retry(src, dst, retries=3, delay=5): + for attempt in range(retries): + try: + shutil.copy(src, dst) + print(f"Copied {src} to {dst}") + return + except BlockingIOError as e: + if attempt < retries - 1: + print(f"資源暫時不可用,等待 {delay} 秒後重試... (第 {attempt + 1} 次)") + time.sleep(delay) + else: + raise e + +def count_patients_and_records(target_dir): + patient_count = 0 + record_count = 0 + + for patient_id in os.listdir(target_dir): + patient_dir = os.path.join(target_dir, patient_id) + + if os.path.isdir(patient_dir): + patient_count += 1 + record_count += len(os.listdir(patient_dir)) + + print(f"完整OAR和Target文件的病人總數: {patient_count}") + print(f"完整OAR和Target文件的資料總數: {record_count}") + +if __name__ == "__main__": + # 設定路徑 + source_root = '/home/onlylian/dataset2_G4_M6' + intermediate_target = '/home/onlylian/NEW_ORGAN_Target_Files' + final_target = '/home/onlylian/Complete_NEW_ORGAN_Target_Files' + + # 指定要檢查的OAR文件 + required_oar_files = [ + 'Struct_Brain_Stem.nii.gz', + 'Struct_Left_Optic_Nerve.nii.gz', + 'Struct_Right_Optic_Nerve.nii.gz', + 'Struct_Left_Eye.nii.gz', + 'Struct_Right_Eye.nii.gz', + 'Struct_Optic_Chiasm.nii.gz' + ] + + # 執行複製和檢查過程 + copy_organ_and_tv_files(source_root, intermediate_target) + check_and_copy_files(intermediate_target, required_oar_files, final_target) + count_patients_and_records(final_target) \ No newline at end of file diff --git a/preprocessing/OAR_Target_Files/tv_and_oar_label_combiner.py b/preprocessing/OAR_Target_Files/tv_and_oar_label_combiner.py new file mode 100644 index 0000000..39aa69a --- /dev/null +++ b/preprocessing/OAR_Target_Files/tv_and_oar_label_combiner.py @@ -0,0 +1,107 @@ +import os +import glob +import SimpleITK as sitk +from difflib import get_close_matches + +def process_images(input_base_path, output_base_path, output_prefix): + # 定義組織標籤對應關係 + labels = { + "background": "0", + "Struct_Brain_Stem": "1", + "Struct_Right_Eye": "2", + "Struct_Left_Eye": "3", + "Struct_Optic_Chiasm": "4", + "Struct_Right_Optic_Nerve": "5", + "Struct_Left_Optic_Nerve": "6", + "TV": "7" # Tumor + } + + # 確保輸出資料夾存在 + os.makedirs(output_base_path, exist_ok=True) + + # 定義重新取樣函數 + def resample_image(image, reference_image): + resample = sitk.ResampleImageFilter() + resample.SetReferenceImage(reference_image) + resample.SetInterpolator(sitk.sitkNearestNeighbor) # 使用最近鄰插值避免標籤變化 + resample.SetDefaultPixelValue(0) + resampled_image = resample.Execute(image) + return resampled_image + + # 遍歷所有病人的資料夾 + for patient_id in os.listdir(input_base_path): + patient_folder_path = os.path.join(input_base_path, patient_id) + if not os.path.isdir(patient_folder_path): + continue # 跳過非資料夾的項目 + + # 確定病人資料夾下的日期資料夾 + date_folder = os.listdir(patient_folder_path)[0] + ORGAN_folder_path = os.path.join(patient_folder_path, date_folder) + + # 創建一個空的影像來合併所有標籤 + combined_image = None + + # 列出病人資料夾內的所有檔案 + all_files = os.listdir(ORGAN_folder_path) + + # 遍歷每個標籤並合併 + for organ_name, label in labels.items(): + if organ_name == "TV": + # 動態搜尋 TV 檔案 + tv_files = glob.glob(os.path.join(ORGAN_folder_path, "TV-*.nii.gz")) + if len(tv_files) == 0: + print(f"TV file not found for patient {patient_id}, skipping TV...") + continue + organ_path = tv_files[0] # 取第一個找到的檔案 + else: + # 使用模糊比對尋找最接近的檔案名 + matches = get_close_matches(organ_name, all_files, n=1, cutoff=0.6) + if len(matches) == 0: + print(f"No close match found for {organ_name} for patient {patient_id}, skipping...") + continue + organ_filename = matches[0] + organ_path = os.path.join(ORGAN_folder_path, organ_filename) + + if not os.path.exists(organ_path): + print(f"File {organ_path} not found for patient {patient_id}, skipping...") + continue + + organ_image = sitk.ReadImage(organ_path) + + if combined_image is None: + # 初始化合併影像的大小和參考 + combined_image = sitk.Image(organ_image.GetSize(), sitk.sitkUInt8) + combined_image.CopyInformation(organ_image) + else: + # 如果器官影像的尺寸和合併影像不同,進行重新取樣 + if organ_image.GetSize() != combined_image.GetSize(): + print(f"Resampling organ {organ_name} for patient {patient_id} due to size mismatch...") + organ_image = resample_image(organ_image, combined_image) + + # 將器官影像添加到合併影像中 + organ_array = sitk.GetArrayFromImage(organ_image) + combined_array = sitk.GetArrayFromImage(combined_image) + + # 更新合併影像的陣列,根據標籤值來設定非背景區域 + combined_array[organ_array > 0] = int(label) + + # 將更新的陣列轉回 SimpleITK 影像 + combined_image = sitk.GetImageFromArray(combined_array) + combined_image.CopyInformation(organ_image) + + # 保存合併後的影像 + output_filename = f'{output_prefix}_label_{patient_id}.nii.gz' + output_path = os.path.join(output_base_path, output_filename) + sitk.WriteImage(combined_image, output_path) + print(f'Saved combined image for patient {patient_id} to {output_path}') + +if __name__ == "__main__": + # 處理 T1C 影像 + T1C_input_path = '/home/onlylian/sorted_OAR_Target_files/T1C' + T1C_output_path = '/home/onlylian/T1C_OAR_Target_label_folder' + process_images(T1C_input_path, T1C_output_path, "T1C") + + # 處理 CT+CTC 影像 + CT_CTC_input_path = '/home/onlylian/sorted_OAR_Target_files/T1C' # 注意:這裡使用相同的輸入路徑,如果需要不同的路徑請修改 + CT_CTC_output_path = '/home/onlylian/CT+CTC_OAR_Target_label_folder' + process_images(CT_CTC_input_path, CT_CTC_output_path, "CT+CTC") \ No newline at end of file diff --git a/preprocessing/final/advanced_multimodal_image_resampler.py b/preprocessing/final/advanced_multimodal_image_resampler.py new file mode 100644 index 0000000..d391462 --- /dev/null +++ b/preprocessing/final/advanced_multimodal_image_resampler.py @@ -0,0 +1,108 @@ +import SimpleITK as sitk +import os +import numpy as np + +def standardize_origin(image, target_origin): + """將影像的原點標準化為目標原點""" + return sitk.Resample( + image, + image.GetSize(), + sitk.Transform(), + sitk.sitkLinear, + target_origin, + image.GetSpacing(), + image.GetDirection(), + 0, + image.GetPixelID() + ) + +def resample_image(image, reference_image, is_label=False): + resampler = sitk.ResampleImageFilter() + resampler.SetReferenceImage(reference_image) + resampler.SetTransform(sitk.Transform()) + resampler.SetOutputSpacing(reference_image.GetSpacing()) + resampler.SetSize(reference_image.GetSize()) + resampler.SetOutputDirection(reference_image.GetDirection()) + resampler.SetOutputOrigin(reference_image.GetOrigin()) + + if is_label: + resampler.SetInterpolator(sitk.sitkNearestNeighbor) + resampler.SetDefaultPixelValue(0) + else: + resampler.SetInterpolator(sitk.sitkLinear) + resampler.SetDefaultPixelValue(image.GetPixelIDValue()) + + return resampler.Execute(image) + +def process_patient(t1c_image_path, ctctc_image_path, label_path, output_t1c_dir, output_ctctc_dir, output_label_dir, patient_id): + print(f"\nProcessing patient: {patient_id}") + + # 讀取影像 + t1c_image = sitk.ReadImage(t1c_image_path) + ctctc_image = sitk.ReadImage(ctctc_image_path) + label = sitk.ReadImage(label_path) + + print(f" Original T1C image size: {t1c_image.GetSize()}, origin: {t1c_image.GetOrigin()}") + print(f" Original CT+CTC image size: {ctctc_image.GetSize()}, origin: {ctctc_image.GetOrigin()}") + print(f" Original label size: {label.GetSize()}, origin: {label.GetOrigin()}") + + # 使用 T1C 影像作為參考 + reference_image = t1c_image + + # 重採樣 CT+CTC 影像以匹配 T1C 影像 + resampled_ctctc = resample_image(ctctc_image, reference_image) + + # 重採樣 label 以匹配 T1C 影像 + resampled_label = resample_image(label, reference_image, is_label=True) + + print(f" Resampled CT+CTC image size: {resampled_ctctc.GetSize()}, origin: {resampled_ctctc.GetOrigin()}") + print(f" Resampled label size: {resampled_label.GetSize()}, origin: {resampled_label.GetOrigin()}") + + # 保存重採樣後的影像和標籤 + t1c_output_path = os.path.join(output_t1c_dir, f"T1C_{patient_id}.nii.gz") + ctctc_output_path = os.path.join(output_ctctc_dir, f"CT+CTC_{patient_id}.nii.gz") + label_output_path = os.path.join(output_label_dir, f"label_{patient_id}.nii.gz") + + sitk.WriteImage(t1c_image, t1c_output_path) + sitk.WriteImage(resampled_ctctc, ctctc_output_path) + sitk.WriteImage(resampled_label, label_output_path) + + print(f" T1C image saved to: {t1c_output_path}") + print(f" Resampled CT+CTC image saved to: {ctctc_output_path}") + print(f" Resampled label saved to: {label_output_path}") + +def process_directory(t1c_dir, ctctc_dir, label_dir, output_t1c_dir, output_ctctc_dir, output_label_dir): + print("Starting directory processing") + os.makedirs(output_t1c_dir, exist_ok=True) + os.makedirs(output_ctctc_dir, exist_ok=True) + os.makedirs(output_label_dir, exist_ok=True) + + # 獲取所有病人 ID + patient_ids = set() + for filename in os.listdir(t1c_dir): + if filename.startswith("T1C_") and filename.endswith(".nii.gz"): + patient_id = filename[4:-7] # 去掉 "T1C_" 和 ".nii.gz" + patient_ids.add(patient_id) + + for patient_id in patient_ids: + t1c_path = os.path.join(t1c_dir, f"T1C_{patient_id}.nii.gz") + ctctc_path = os.path.join(ctctc_dir, f"CT+CTC_{patient_id}.nii.gz") + label_path = os.path.join(label_dir, f"T1C_label_{patient_id}.nii.gz") # 假設 label 文件名與 T1C 相同 + + if os.path.exists(t1c_path) and os.path.exists(ctctc_path) and os.path.exists(label_path): + process_patient(t1c_path, ctctc_path, label_path, output_t1c_dir, output_ctctc_dir, output_label_dir, patient_id) + else: + print(f" WARNING: Missing files for patient {patient_id}") + +# 主程序 +t1c_dir = '/home/onlylian/T1C_image_folder' +ctctc_dir = '/home/onlylian/CT+CTC_image_folder' +label_dir = '/home/onlylian/T1C_label_folder' # 使用 T1C 的 label +output_t1c_dir = '/home/onlylian/resampled_T1C_image' +output_ctctc_dir = '/home/onlylian/resampled_CT+CTC_image' +output_label_dir = '/home/onlylian/resampled_label' + +print("Starting image preprocessing") +process_directory(t1c_dir, ctctc_dir, label_dir, output_t1c_dir, output_ctctc_dir, output_label_dir) + +print("All processing completed.") \ No newline at end of file diff --git a/preprocessing/final/nnunet_Dataset012_OAR_TV_preparation.py b/preprocessing/final/nnunet_Dataset012_OAR_TV_preparation.py new file mode 100644 index 0000000..336e368 --- /dev/null +++ b/preprocessing/final/nnunet_Dataset012_OAR_TV_preparation.py @@ -0,0 +1,119 @@ +import json +import os +import shutil +import glob +import re +import pandas as pd + +# 創建 dataset.json 內容 +dataset_json = { + "channel_names": { + "0": "T1C" + }, + "labels": { + "background": "0", + "Brainstem": "1", + "Right_Eye": "2", + "Left_Eye": "3", + "Optic_Chiasm": "4", + "Right_Optic_Nerve": "5", + "Left_Optic_Nerve": "6", + "TV": "7" + }, + "numTraining": 0, + "file_ending": ".nii.gz" +} + +# 定義目錄 +source_image_dir = "/home/onlylian/resampled_T1C_TV_image" +source_label_dir = "/home/onlylian/resampled_tv_label" +target_base_dir = "/home/onlylian/nnUNet/nnUNet_raw/Dataset012_OAR_TV" +imagesTr_dir = os.path.join(target_base_dir, "imagesTr") +labelsTr_dir = os.path.join(target_base_dir, "labelsTr") + +# 創建目錄 +os.makedirs(imagesTr_dir, exist_ok=True) +os.makedirs(labelsTr_dir, exist_ok=True) + +def extract_patient_id(filename): + match = re.search(r'(T1C|label)_([^.]+)\.nii\.gz', os.path.basename(filename)) + return match.group(2) if match else None + +# 獲取並排序原始圖像文件 +source_image_files = sorted(glob.glob(os.path.join(source_image_dir, "*.nii.gz"))) + +processed_files = 0 +patient_mapping = [] + +# 創建病歷號到文件名的映射 +label_files = glob.glob(os.path.join(source_label_dir, "*.nii.gz")) +label_map = {extract_patient_id(f): f for f in label_files} + +print(f"找到 {len(label_map)} 個標籤文件") +print(f"找到 {len(source_image_files)} 個圖像文件") + +# 處理所有資料 +for index, source_image_file in enumerate(source_image_files, start=1): + if index % 10 == 0: # 每處理10個文件顯示一次進度 + print(f"\n正在處理檔案 {index}/{len(source_image_files)}:") + + patient_id = extract_patient_id(source_image_file) + + if patient_id is None: + print(f"警告: 無法提取病歷號,跳過文件: {source_image_file}") + continue + + # 使用新的文件名格式 + new_filename = f"OAR_TV_{index:03d}" + + # 複製並重命名圖像文件 + target_image_file = os.path.join(imagesTr_dir, f"{new_filename}_0000.nii.gz") + + shutil.copyfile(source_image_file, target_image_file) + + # 處理標籤文件 + if patient_id in label_map: + source_label_file = label_map[patient_id] + target_label_file = os.path.join(labelsTr_dir, f"{new_filename}.nii.gz") + shutil.copyfile(source_label_file, target_label_file) + processed_files += 1 + + # 添加到映射列表 + patient_mapping.append({ + "編號": f"{index:03d}", + "病歷號": patient_id + }) + + # 輸出處理細節 + print(f"T1C: {source_image_file}") + print(f"複製圖像檔案: {source_image_file} 到 {target_image_file}") + print(f"複製標籤檔案: {source_label_file} 到 {target_label_file}") + print() # 空行分隔每筆記錄 + else: + print(f"警告: 未找到 {patient_id} 的標籤檔案。") + +print("\n所有資料處理完畢。") + +# 更新 dataset.json +dataset_json["numTraining"] = processed_files + +# 寫入 dataset.json 檔案 +dataset_json_path = os.path.join(target_base_dir, "dataset.json") +with open(dataset_json_path, 'w') as f: + json.dump(dataset_json, f, indent=4) +print(f"dataset.json 已寫入到 {dataset_json_path}") + +print(f"總共處理了 {processed_files} 個完整的資料集(圖像加標籤)") + +# 創建 Excel 文件 +df = pd.DataFrame(patient_mapping) +excel_path = os.path.join(target_base_dir, "patient_mapping.xlsx") +df.to_excel(excel_path, index=False) +print(f"病人映射表已保存到 {excel_path}") + +# 輸出處理摘要 +print("\n處理摘要:") +print(f"總共找到的圖像: {len(source_image_files)}") +print(f"總共找到的標籤文件: {len(label_map)}") +print(f"成功處理的完整資料集: {processed_files}") +print(f"跳過的文件數量: {len(source_image_files) - processed_files}") \ No newline at end of file diff --git a/preprocessing/final/nnunet_Dataset021_OAR_preparation.py b/preprocessing/final/nnunet_Dataset021_OAR_preparation.py new file mode 100644 index 0000000..9db8aeb --- /dev/null +++ b/preprocessing/final/nnunet_Dataset021_OAR_preparation.py @@ -0,0 +1,128 @@ +import json +import os +import shutil +import glob +import re +import pandas as pd + +# 創建 dataset.json 內容 +dataset_json = { + "channel_names": { + "0": "CT+CTC", + "1": "T1C" + }, + "labels": { + "background": "0", + "Brainstem": "1", + "Right_Eye": "2", + "Left_Eye": "3", + "Optic_Chiasm": "4", + "Right_Optic_Nerve": "5", + "Left_Optic_Nerve": "6" + }, + "numTraining": 0, + "file_ending": ".nii.gz" +} + +# 定義目錄 +source_image1_dir = "/home/onlylian/resampled_CT+CTC_image" +source_image2_dir = "/home/onlylian/resampled_T1C_image" +source_label_dir = "/home/onlylian/resampled_label" +target_base_dir = "/home/onlylian/nnUNet/nnUNet_raw/Dataset021_OAR" +imagesTr_dir = os.path.join(target_base_dir, "imagesTr") +labelsTr_dir = os.path.join(target_base_dir, "labelsTr") + +# 創建目錄 +os.makedirs(imagesTr_dir, exist_ok=True) +os.makedirs(labelsTr_dir, exist_ok=True) + +def extract_patient_id(filename): + match = re.search(r'(CT\+CTC|T1C|label)_([^.]+)\.nii\.gz', os.path.basename(filename)) + return match.group(2) if match else None + +# 獲取並排序原始圖像文件 +source_image1_files = sorted(glob.glob(os.path.join(source_image1_dir, "*.nii.gz"))) +source_image2_files = sorted(glob.glob(os.path.join(source_image2_dir, "*.nii.gz"))) + +processed_files = 0 +patient_mapping = [] + +# 創建病歷號到文件名的映射 +label_files = glob.glob(os.path.join(source_label_dir, "*.nii.gz")) +label_map = {extract_patient_id(f): f for f in label_files} + +print(f"找到 {len(label_map)} 個標籤文件") +print(f"找到 {len(source_image1_files)} 個 CT+CTC 圖像文件") +print(f"找到 {len(source_image2_files)} 個 T1C 圖像文件") + +# 處理所有資料 +for index, (source_image1_file, source_image2_file) in enumerate(zip(source_image1_files, source_image2_files), start=1): + if index % 10 == 0: # 每處理10個文件顯示一次進度 + print(f"\n正在處理檔案 {index}/{len(source_image1_files)}:") + + patient_id1 = extract_patient_id(source_image1_file) + patient_id2 = extract_patient_id(source_image2_file) + + if patient_id1 != patient_id2 or patient_id1 is None or patient_id2 is None: + print(f"警告: 病歷號不匹配或無法提取,跳過這對文件: {source_image1_file}, {source_image2_file}") + continue + + # 使用新的文件名格式 + new_filename = f"OAR_{index:03d}" + + # 複製並重命名圖像文件 + target_image1_file = os.path.join(imagesTr_dir, f"{new_filename}_0000.nii.gz") + target_image2_file = os.path.join(imagesTr_dir, f"{new_filename}_0001.nii.gz") + + shutil.copyfile(source_image1_file, target_image1_file) + shutil.copyfile(source_image2_file, target_image2_file) + + # 處理標籤文件 + if patient_id1 in label_map: + source_label_file = label_map[patient_id1] + target_label_file = os.path.join(labelsTr_dir, f"{new_filename}.nii.gz") + shutil.copyfile(source_label_file, target_label_file) + processed_files += 1 + + # 添加到映射列表 + patient_mapping.append({ + "編號": f"{index:03d}", + "病歷號": patient_id1 + }) + + # 輸出處理細節 + print(f"CT+CTC: {source_image1_file}") + print(f"T1C: {source_image2_file}") + print(f"複製圖像檔案: {source_image1_file} 到 {target_image1_file}") + print(f"複製圖像檔案: {source_image2_file} 到 {target_image2_file}") + print(f"複製標籤檔案: {source_label_file} 到 {target_label_file}") + print() # 空行分隔每筆記錄 + else: + print(f"警告: 未找到 {patient_id1} 的標籤檔案。") + +print("\n所有資料處理完畢。") + +# 更新 dataset.json +dataset_json["numTraining"] = processed_files + +# 寫入 dataset.json 檔案 +dataset_json_path = os.path.join(target_base_dir, "dataset.json") +with open(dataset_json_path, 'w') as f: + json.dump(dataset_json, f, indent=4) +print(f"dataset.json 已寫入到 {dataset_json_path}") + +print(f"總共處理了 {processed_files} 個完整的資料集(圖像對加標籤)") + +# 創建 Excel 文件 +df = pd.DataFrame(patient_mapping) +excel_path = os.path.join(target_base_dir, "patient_mapping.xlsx") +df.to_excel(excel_path, index=False) +print(f"病人映射表已保存到 {excel_path}") + +# 輸出處理摘要 +print("\n處理摘要:") +print(f"總共找到的 CT+CTC 圖像: {len(source_image1_files)}") +print(f"總共找到的 T1C 圖像: {len(source_image2_files)}") +print(f"總共找到的標籤文件: {len(label_map)}") +print(f"成功處理的完整資料集: {processed_files}") +print(f"跳過的文件對數量: {len(source_image1_files) - processed_files}") \ No newline at end of file diff --git a/preprocessing/final/nnunet_Dataset022_OAR_TV_preparation.py b/preprocessing/final/nnunet_Dataset022_OAR_TV_preparation.py new file mode 100644 index 0000000..270722f --- /dev/null +++ b/preprocessing/final/nnunet_Dataset022_OAR_TV_preparation.py @@ -0,0 +1,130 @@ +import json +import os +import shutil +import glob +import re +import pandas as pd + +# 創建 dataset.json 內容 +dataset_json = { + "channel_names": { + "0": "CT+CTC", + "1": "T1C" + }, + "labels": { + "background": "0", + "Brainstem": "1", + "Right_Eye": "2", + "Left_Eye": "3", + "Optic_Chiasm": "4", + "Right_Optic_Nerve": "5", + "Left_Optic_Nerve": "6", + "TV": "7" + }, + "numTraining": 0, + "file_ending": ".nii.gz" +} + +# 定義目錄 +source_image1_dir = "/home/onlylian/resampled_CT+CTC_TV_image" +source_image2_dir = "/home/onlylian/resampled_T1C_TV_image" +source_label_dir = "/home/onlylian/resampled_tv_label" +target_base_dir = "/home/onlylian/nnUNet/nnUNet_raw/Dataset022_OAR_TV" +imagesTr_dir = os.path.join(target_base_dir, "imagesTr") +labelsTr_dir = os.path.join(target_base_dir, "labelsTr") + +# 創建目錄 +os.makedirs(imagesTr_dir, exist_ok=True) +os.makedirs(labelsTr_dir, exist_ok=True) + +def extract_patient_id(filename): + match = re.search(r'(CT\+CTC|T1C|label)_([^.]+)\.nii\.gz', os.path.basename(filename)) + return match.group(2) if match else None + +# 獲取並排序原始圖像文件 +source_image1_files = sorted(glob.glob(os.path.join(source_image1_dir, "*.nii.gz"))) +source_image2_files = sorted(glob.glob(os.path.join(source_image2_dir, "*.nii.gz"))) + +processed_files = 0 +patient_mapping = [] + +# 創建病歷號到文件名的映射 +label_files = glob.glob(os.path.join(source_label_dir, "*.nii.gz")) +label_map = {extract_patient_id(f): f for f in label_files} + +print(f"找到 {len(label_map)} 個標籤文件") +print(f"找到 {len(source_image1_files)} 個 CT+CTC 圖像文件") +print(f"找到 {len(source_image2_files)} 個 T1C 圖像文件") + +# 處理所有資料 +for index, (source_image1_file, source_image2_file) in enumerate(zip(source_image1_files, source_image2_files), start=1): + if index % 10 == 0: # 每處理10個文件顯示一次進度 + print(f"\n正在處理檔案 {index}/{len(source_image1_files)}:") + + patient_id1 = extract_patient_id(source_image1_file) + patient_id2 = extract_patient_id(source_image2_file) + + if patient_id1 != patient_id2 or patient_id1 is None or patient_id2 is None: + print(f"警告: 病歷號不匹配或無法提取,跳過這對文件: {source_image1_file}, {source_image2_file}") + continue + + # 使用新的文件名格式 + new_filename = f"OAR_TV_{index:03d}" + + # 複製並重命名圖像文件 + target_image1_file = os.path.join(imagesTr_dir, f"{new_filename}_0000.nii.gz") + target_image2_file = os.path.join(imagesTr_dir, f"{new_filename}_0001.nii.gz") + + shutil.copyfile(source_image1_file, target_image1_file) + shutil.copyfile(source_image2_file, target_image2_file) + + # 處理標籤文件 + if patient_id1 in label_map: + source_label_file = label_map[patient_id1] + target_label_file = os.path.join(labelsTr_dir, f"{new_filename}.nii.gz") + shutil.copyfile(source_label_file, target_label_file) + processed_files += 1 + + # 添加到映射列表 + patient_mapping.append({ + "編號": f"{index:03d}", + "病歷號": patient_id1 + }) + + # 輸出處理細節 + print(f"CT+CTC: {source_image1_file}") + print(f"T1C: {source_image2_file}") + print(f"複製圖像檔案: {source_image1_file} 到 {target_image1_file}") + print(f"複製圖像檔案: {source_image2_file} 到 {target_image2_file}") + print(f"複製標籤檔案: {source_label_file} 到 {target_label_file}") + print() # 空行分隔每筆記錄 + else: + print(f"警告: 未找到 {patient_id1} 的標籤檔案。") + +print("\n所有資料處理完畢。") + +# 更新 dataset.json +dataset_json["numTraining"] = processed_files + +# 寫入 dataset.json 檔案 +dataset_json_path = os.path.join(target_base_dir, "dataset.json") +with open(dataset_json_path, 'w') as f: + json.dump(dataset_json, f, indent=4) +print(f"dataset.json 已寫入到 {dataset_json_path}") + +print(f"總共處理了 {processed_files} 個完整的資料集(圖像對加標籤)") + +# 創建 Excel 文件 +df = pd.DataFrame(patient_mapping) +excel_path = os.path.join(target_base_dir, "patient_mapping.xlsx") +df.to_excel(excel_path, index=False) +print(f"病人映射表已保存到 {excel_path}") + +# 輸出處理摘要 +print("\n處理摘要:") +print(f"總共找到的 CT+CTC 圖像: {len(source_image1_files)}") +print(f"總共找到的 T1C 圖像: {len(source_image2_files)}") +print(f"總共找到的標籤文件: {len(label_map)}") +print(f"成功處理的完整資料集: {processed_files}") +print(f"跳過的文件對數量: {len(source_image1_files) - processed_files}") + diff --git a/preprocessing/final/tv_t1c_ctctc_resampler.py b/preprocessing/final/tv_t1c_ctctc_resampler.py new file mode 100644 index 0000000..922f085 --- /dev/null +++ b/preprocessing/final/tv_t1c_ctctc_resampler.py @@ -0,0 +1,110 @@ +import SimpleITK as sitk +import os +import numpy as np + +def standardize_origin(image, target_origin): + """將影像的原點標準化為目標原點""" + return sitk.Resample( + image, + image.GetSize(), + sitk.Transform(), + sitk.sitkLinear, + target_origin, + image.GetSpacing(), + image.GetDirection(), + 0, + image.GetPixelID() + ) + +def resample_image(image, reference_image, is_label=False): + resampler = sitk.ResampleImageFilter() + resampler.SetReferenceImage(reference_image) + resampler.SetTransform(sitk.Transform()) + resampler.SetOutputSpacing(reference_image.GetSpacing()) + resampler.SetSize(reference_image.GetSize()) + resampler.SetOutputDirection(reference_image.GetDirection()) + resampler.SetOutputOrigin(reference_image.GetOrigin()) + + if is_label: + resampler.SetInterpolator(sitk.sitkNearestNeighbor) + resampler.SetDefaultPixelValue(0) + else: + resampler.SetInterpolator(sitk.sitkLinear) + resampler.SetDefaultPixelValue(image.GetPixelIDValue()) + + return resampler.Execute(image) + +def process_patient(t1c_image_path, ctctc_image_path, label_path, output_t1c_dir, output_ctctc_dir, output_label_dir, patient_id): + print(f"\nProcessing patient: {patient_id}") + + # 讀取影像 + t1c_image = sitk.ReadImage(t1c_image_path) + ctctc_image = sitk.ReadImage(ctctc_image_path) + label = sitk.ReadImage(label_path) + + print(f" Original T1C image size: {t1c_image.GetSize()}, origin: {t1c_image.GetOrigin()}") + print(f" Original CT+CTC image size: {ctctc_image.GetSize()}, origin: {ctctc_image.GetOrigin()}") + print(f" Original label size: {label.GetSize()}, origin: {label.GetOrigin()}") + + # 使用 T1C 影像作為參考 + reference_image = t1c_image + + # 重採樣 CT+CTC 影像以匹配 T1C 影像 + resampled_ctctc = resample_image(ctctc_image, reference_image) + + # 重採樣 label 以匹配 T1C 影像 + resampled_label = resample_image(label, reference_image, is_label=True) + + print(f" Resampled CT+CTC image size: {resampled_ctctc.GetSize()}, origin: {resampled_ctctc.GetOrigin()}") + print(f" Resampled label size: {resampled_label.GetSize()}, origin: {resampled_label.GetOrigin()}") + + # 保存重採樣後的影像和標籤 + t1c_output_path = os.path.join(output_t1c_dir, f"T1C_{patient_id}.nii.gz") + ctctc_output_path = os.path.join(output_ctctc_dir, f"CT+CTC_{patient_id}.nii.gz") + label_output_path = os.path.join(output_label_dir, f"label_{patient_id}.nii.gz") + + sitk.WriteImage(t1c_image, t1c_output_path) + sitk.WriteImage(resampled_ctctc, ctctc_output_path) + sitk.WriteImage(resampled_label, label_output_path) + + print(f" T1C image saved to: {t1c_output_path}") + print(f" Resampled CT+CTC image saved to: {ctctc_output_path}") + print(f" Resampled label saved to: {label_output_path}") + +def process_directory(t1c_dir, ctctc_dir, label_dir, output_t1c_dir, output_ctctc_dir, output_label_dir): + print("Starting directory processing") + os.makedirs(output_t1c_dir, exist_ok=True) + os.makedirs(output_ctctc_dir, exist_ok=True) + os.makedirs(output_label_dir, exist_ok=True) + + # 獲取所有病人 ID + patient_ids = set() + for filename in os.listdir(t1c_dir): + if filename.startswith("T1C_") and filename.endswith(".nii.gz"): + patient_id = filename[4:-7] # 去掉 "T1C_" 和 ".nii.gz" + patient_ids.add(patient_id) + + for patient_id in patient_ids: + t1c_path = os.path.join(t1c_dir, f"T1C_{patient_id}.nii.gz") + ctctc_path = os.path.join(ctctc_dir, f"CT+CTC_{patient_id}.nii.gz") + label_path = os.path.join(label_dir, f"T1C_label_{patient_id}.nii.gz") # 假設 label 文件名與 T1C 相同 + + if os.path.exists(t1c_path) and os.path.exists(ctctc_path) and os.path.exists(label_path): + process_patient(t1c_path, ctctc_path, label_path, output_t1c_dir, output_ctctc_dir, output_label_dir, patient_id) + else: + print(f" WARNING: Missing files for patient {patient_id}") + +# 主程序 +t1c_dir = '/home/onlylian/T1C_image_folder' +ctctc_dir = '/home/onlylian/CT+CTC_image_folder' +label_dir = '/home/onlylian/T1C_OAR_Target_label_folder' # 使用 T1C 的 label +output_t1c_dir = '/home/onlylian/resampled_T1C_TV_image_2' +output_ctctc_dir = '/home/onlylian/resampled_CT+CTC_TV_image_2' +output_label_dir = '/home/onlylian/resampled_tv_label' + +print("Starting image preprocessing") +process_directory(t1c_dir, ctctc_dir, label_dir, output_t1c_dir, output_ctctc_dir, output_label_dir) + +print("All processing completed.") + + diff --git a/preprocessing/rename_files.py b/preprocessing/rename_files.py new file mode 100644 index 0000000..35a4b91 --- /dev/null +++ b/preprocessing/rename_files.py @@ -0,0 +1,147 @@ +import os +import shutil + +# 定义所有需要的路径 +final_image_folder = '/mnt/1218/onlylian/resampled_T1_image_test' +final_label_folder = '/mnt/1218/onlylian/resampled_label_test' +mapping_file = '/mnt/1218/onlylian/patient_mapping.txt' + +# nnUNet路径 +base_nnunet_path = '/home/onlylian/nnUNet/nnUNet_raw/Dataset888' +nnunet_image_folder = os.path.join(base_nnunet_path, 'imagesTs') +nnunet_label_folder = os.path.join(base_nnunet_path, 'labelsTs') + +# 打印初始状态 +print("\nChecking directories:") +print(f"Source image folder exists: {os.path.exists(final_image_folder)}") +print(f"Source label folder exists: {os.path.exists(final_label_folder)}") + +# 确保目录存在 +try: + os.makedirs(base_nnunet_path, exist_ok=True) + os.makedirs(nnunet_image_folder, exist_ok=True) + os.makedirs(nnunet_label_folder, exist_ok=True) + print(f"Successfully created/verified nnUNet directories") +except Exception as e: + print(f"Error creating directories: {e}") + +def get_patient_ids(folder_path, prefix='T1_'): + """從檔案名稱中提取病人ID""" + if not os.path.exists(folder_path): + print(f"WARNING: Folder does not exist: {folder_path}") + return set() + + patient_ids = set() + try: + for filename in os.listdir(folder_path): + if filename.endswith('.nii.gz'): + if filename.startswith(prefix): + # 對於T1影像: T1_XXXXX.nii.gz -> XXXXX + patient_id = filename[len(prefix):-7] + patient_ids.add(patient_id) + elif prefix == 'T1_' and filename.startswith('label_'): + # 對於標籤: label_XXXXX.nii.gz -> XXXXX + patient_id = filename[6:-7] + patient_ids.add(patient_id) + print(f"Found {len(patient_ids)} patients in {folder_path}") + except Exception as e: + print(f"Error reading directory {folder_path}: {e}") + return patient_ids + +def create_filtered_folder(image_folder, label_folder, output_image_folder, output_label_folder): + """过滤并重命名文件,同时创建映射文件""" + print("\nStarting file processing...") + + # 获取病人ID + print("\nGetting patient IDs...") + image_patients = get_patient_ids(image_folder, 'T1_') + label_patients = get_patient_ids(label_folder, 'label_') # 修改為正確的前綴 + + # 打印一些檔案範例以供驗證 + print("\nSample files in directories:") + print("Images directory:") + for f in sorted(os.listdir(image_folder))[:3]: + print(f" - {f}") + print("Labels directory:") + for f in sorted(os.listdir(label_folder))[:3]: + print(f" - {f}") + + common_patients = image_patients.intersection(label_patients) + print(f"\nFound {len(common_patients)} common patients") + + mapping_list = [] + processed_files = {"images": 0, "labels": 0} + + for index, patient_id in enumerate(sorted(common_patients), 1): + new_filename = f"T1_{index:03d}" + mapping_list.append(f"Original ID: {patient_id} -> New ID: {new_filename}") + + # 处理图像文件 + image_file = f'T1_{patient_id}.nii.gz' + src_image_path = os.path.join(image_folder, image_file) + dst_image_path = os.path.join(output_image_folder, f"{new_filename}_0000.nii.gz") + + # 处理标签文件 + label_file = f'label_{patient_id}.nii.gz' # 修改為正確的命名格式 + src_label_path = os.path.join(label_folder, label_file) + dst_label_path = os.path.join(output_label_folder, f"{new_filename}.nii.gz") + + # 複製檔案並記錄結果 + if os.path.exists(src_image_path): + try: + shutil.copy2(src_image_path, dst_image_path) + processed_files["images"] += 1 + print(f"Copied image: {os.path.basename(dst_image_path)}") + except Exception as e: + print(f"Error copying image {src_image_path}: {e}") + else: + print(f"Source image not found: {src_image_path}") + + if os.path.exists(src_label_path): + try: + shutil.copy2(src_label_path, dst_label_path) + processed_files["labels"] += 1 + print(f"Copied label: {os.path.basename(dst_label_path)}") + except Exception as e: + print(f"Error copying label {src_label_path}: {e}") + else: + print(f"Source label not found: {src_label_path}") + + # 写入映射文件 + try: + with open(mapping_file, 'w') as f: + f.write("Patient ID Mapping:\n") + f.write("=================\n\n") + for mapping in mapping_list: + f.write(f"{mapping}\n") + f.write(f"\nTotal number of patients: {len(mapping_list)}") + print(f"\nMapping file created: {mapping_file}") + except Exception as e: + print(f"Error writing mapping file: {e}") + + return len(common_patients), processed_files + +print("\nStarting file processing...") +common_count, processed_files = create_filtered_folder( + final_image_folder, final_label_folder, + nnunet_image_folder, nnunet_label_folder +) + +print(f"\nProcessing completed:") +print(f"- Total number of common patients: {common_count}") +print(f"- Files processed: {processed_files['images']} images, {processed_files['labels']} labels") +print(f"- Patient ID mapping has been saved to: {mapping_file}") + +# 验证最终结果 +print("\nFinal verification:") +if os.path.exists(nnunet_label_folder): + label_files = os.listdir(nnunet_label_folder) + print(f"Files in labels directory: {len(label_files)}") + if len(label_files) > 0: + print("Sample files:") + for f in sorted(label_files)[:5]: + print(f" - {f}") + else: + print("WARNING: Labels directory is empty!") +else: + print("WARNING: Labels directory does not exist!") \ No newline at end of file diff --git a/test/Image_files/copy_and_classify_modalities.py b/test/Image_files/copy_and_classify_modalities.py new file mode 100644 index 0000000..f0ceaa9 --- /dev/null +++ b/test/Image_files/copy_and_classify_modalities.py @@ -0,0 +1,112 @@ +import os +import shutil +import pandas as pd + +def load_excel(file_path): + """載入 Excel 文件""" + return pd.read_excel(file_path, sheet_name='Sheet1') + +def ensure_directories_exist(target_dir, modalities): + """確保目標目錄存在""" + for modality in modalities: + os.makedirs(os.path.join(target_dir, modality), exist_ok=True) + +def find_file_recursive(filename, search_path): + """在給定目錄中遞迴搜索文件。""" + for root, dirs, files in os.walk(search_path): + if filename in files: + return os.path.join(root, filename) + return None + +def extract_patient_and_date(file_path): + """從文件路徑中提取病人檔名和日期。""" + parts = file_path.split(os.sep) + if len(parts) >= 4: + return parts[-4], parts[-3] # 假設病人檔名在倒數第四層,日期在倒數第三層 + return None, None + +def is_swi_file(file_name): + """檢查檔案名稱是否包含 'swi'""" + return 'swi' in file_name.lower() + +def copy_files(df, source_dir, target_dir): + """遍歷數據框並將對應的 .warp.nii.gz 文件複製到相應的模態資料夾,若無 .warp.nii.gz 則複製原始的 .nii.gz 文件,保留病人檔名和日期""" + processed_patients = set() + + for index, row in df.iterrows(): + file_name = row['name'] + modality = row['modality_nn'] + + # 檢查是否為 swi 檔案 + if is_swi_file(file_name): + print(f'跳過 swi 檔案: {file_name}') + continue + + # 修改來源檔名:將 .nii.gz 替換為 .warp.nii.gz + warp_file_name = file_name.replace('.nii.gz', '.warp.nii.gz') + + # 在源目錄中遞迴搜索 .warp.nii.gz 文件 + warp_file_path = find_file_recursive(warp_file_name, source_dir) + + if warp_file_path: + # 如果找到 .warp.nii.gz 文件,複製該文件 + patient_id, date = extract_patient_and_date(warp_file_path) + if patient_id and date: + if patient_id not in processed_patients: + processed_patients.add(patient_id) + + # 複製到個別的模態資料夾 + target_dir_path = os.path.join(target_dir, modality, patient_id, date) + os.makedirs(target_dir_path, exist_ok=True) + target_file_path = os.path.join(target_dir_path, warp_file_name) + shutil.copy2(warp_file_path, target_file_path) + print(f'已複製 {warp_file_name} 到 {target_dir_path} 資料夾。') + + # 如果是 CT 或 CTC,也複製到 CT+CTC 資料夾 + if modality in ['CT', 'CTC']: + combined_target_dir_path = os.path.join(target_dir, 'CT+CTC', patient_id, date) + os.makedirs(combined_target_dir_path, exist_ok=True) + combined_target_file_path = os.path.join(combined_target_dir_path, warp_file_name) + shutil.copy2(warp_file_path, combined_target_file_path) + print(f'已複製 {warp_file_name} 到 {combined_target_dir_path} 資料夾。') + else: + # 如果找不到 .warp.nii.gz 文件,則複製原始的 .nii.gz 文件 + original_file_path = find_file_recursive(file_name, source_dir) + if original_file_path: + patient_id, date = extract_patient_and_date(original_file_path) + if patient_id and date: + if patient_id not in processed_patients: + processed_patients.add(patient_id) + + # 複製到個別的模態資料夾 + target_dir_path = os.path.join(target_dir, modality, patient_id, date) + os.makedirs(target_dir_path, exist_ok=True) + target_file_path = os.path.join(target_dir_path, file_name) + shutil.copy2(original_file_path, target_file_path) + print(f'已複製 {file_name} 到 {target_dir_path} 資料夾。') + + # 如果是 CT 或 CTC,也複製到 CT+CTC 資料夾 + if modality in ['CT', 'CTC']: + combined_target_dir_path = os.path.join(target_dir, 'CT+CTC', patient_id, date) + os.makedirs(combined_target_dir_path, exist_ok=True) + combined_target_file_path = os.path.join(combined_target_dir_path, file_name) + shutil.copy2(original_file_path, combined_target_file_path) + print(f'已複製 {file_name} 到 {combined_target_dir_path} 資料夾。') + else: + print(f'{file_name} 和 {warp_file_name} 均在源目錄中不存在。') + +def main(): + file_path = '/mnt/1218/onlylian/modality_by_nn.xlsx' + source_dir = '/mnt/1218/onlylian/N6_24Q3' + target_dir = '/mnt/1218/onlylian/sorted_modality_files' + + modalities = ['CT', 'CTC', 'T1', 'T1C', 'T2', 'FLAIR', 'CT+CTC'] # 加入 CT+CTC + + df = load_excel(file_path) + ensure_directories_exist(target_dir, modalities) + copy_files(df, source_dir, target_dir) + + print('文件分類和複製完成。') + +if __name__ == "__main__": + main() diff --git a/test/Image_files/merge_H4_N6_directories.py b/test/Image_files/merge_H4_N6_directories.py new file mode 100644 index 0000000..2ad085d --- /dev/null +++ b/test/Image_files/merge_H4_N6_directories.py @@ -0,0 +1,47 @@ +import os +import shutil + +def merge_directories(src1, src2, dest): + if not os.path.exists(dest): + os.makedirs(dest) + + def copy_or_merge(src, dest): + for patient in os.listdir(src): + patient_src_path = os.path.join(src, patient) + patient_dest_path = os.path.join(dest, patient) + + if os.path.isdir(patient_src_path): + if os.path.exists(patient_dest_path): + # If destination folder exists, merge the contents + for item in os.listdir(patient_src_path): + s = os.path.join(patient_src_path, item) + d = os.path.join(patient_dest_path, item) + if os.path.isdir(s): + shutil.copytree(s, d, dirs_exist_ok=True) + else: + shutil.copy2(s, d) + else: + # If destination folder does not exist, copy the whole folder + shutil.copytree(patient_src_path, patient_dest_path) + + # Copy or merge from both source directories to destination directory + copy_or_merge(src1, dest) + copy_or_merge(src2, dest) + + print("Directories merged successfully.") + +# Define source and destination paths +src1 = '/mnt/1220/Public/dataset2/H4' +src2 = '/mnt/1220/Public/dataset2/N6' +dest = "/home/onlylian/dataset2_H4_N6" + +# Call the function to merge the directories +merge_directories(src1, src2, dest) + + + + + + + + diff --git a/test/Image_files/multi_modality_image_copy.py b/test/Image_files/multi_modality_image_copy.py new file mode 100644 index 0000000..638a194 --- /dev/null +++ b/test/Image_files/multi_modality_image_copy.py @@ -0,0 +1,96 @@ +import os +import shutil + +def copy_last_file(source_base_dir, target_base_dir, modality): + # 確保目標目錄存在 + os.makedirs(target_base_dir, exist_ok=True) + + # 按照病患資料夾名稱進行排序 + for patient_dir in sorted(os.listdir(source_base_dir)): + patient_path = os.path.join(source_base_dir, patient_dir) + if os.path.isdir(patient_path): + # 按照日期資料夾名稱進行排序 + date_dirs = sorted(os.listdir(patient_path)) + for date_dir in date_dirs: + date_path = os.path.join(patient_path, date_dir) + if os.path.isdir(date_path): + files = os.listdir(date_path) + + # 篩選出 .nii.gz 和 .warp.nii.gz 檔案 + nii_files = [f for f in files if f.endswith('.nii.gz')] + warp_files = [f for f in files if f.endswith('.warp.nii.gz')] + + # 先選擇最後一個 .warp.nii.gz 檔案,如果沒有則選擇 .nii.gz 檔案 + if warp_files: + warp_files.sort() + last_file = warp_files[-1] # 選擇最後一個 .warp.nii.gz 檔案 + elif nii_files: + nii_files.sort() + last_file = nii_files[-1] # 選擇最後一個 .nii.gz 檔案 + else: + print(f"No valid .nii.gz or .warp.nii.gz files found in {date_path}") + continue + + last_file_path = os.path.join(date_path, last_file) + + # 根據模態重新命名文件 + new_file_name = f"{modality}_{patient_dir}.nii.gz" + target_file_path = os.path.join(target_base_dir, new_file_name) + + # 複製文件到目標目錄 + shutil.copy2(last_file_path, target_file_path) + print(f"Copied: {last_file_path} to {target_file_path}") + +# T1C 模態的處理 +source_base_dir_t1c = '/mnt/1218/onlylian/sorted_modality_files_test/T1' +target_base_dir_t1c = '/mnt/1218/onlylian/T1_image_folder_test' +copy_last_file(source_base_dir_t1c, target_base_dir_t1c, 'T1') + + + +# import os +# import shutil + +# def copy_last_file(source_base_dir, target_base_dir, modality): +# # 確保目標目錄存在 +# os.makedirs(target_base_dir, exist_ok=True) + +# # 按照病患資料夾名稱進行排序 +# for patient_dir in sorted(os.listdir(source_base_dir)): +# patient_path = os.path.join(source_base_dir, patient_dir) +# if os.path.isdir(patient_path): +# # 按照日期資料夾名稱進行排序 +# date_dirs = sorted(os.listdir(patient_path)) +# if date_dirs: # 確保有日期資料夾 +# # 取最新的日期資料夾 +# latest_date_dir = date_dirs[-1] +# date_path = os.path.join(patient_path, latest_date_dir) + +# if os.path.isdir(date_path): +# # 獲取該資料夾中的所有檔案 +# files = [f for f in os.listdir(date_path) if f.endswith('.nii.gz')] + +# if files: # 如果有符合條件的檔案 +# # 按名稱排序並取最後一個檔案 +# files.sort() +# last_file = files[-1] + +# # 構建完整的檔案路徑 +# last_file_path = os.path.join(date_path, last_file) + +# # 根據模態重新命名檔案 +# new_file_name = f"{modality}_{patient_dir}.nii.gz" +# target_file_path = os.path.join(target_base_dir, new_file_name) + +# # 複製檔案到目標目錄 +# shutil.copy2(last_file_path, target_file_path) +# print(f"Copied: {last_file_path} to {target_file_path}") +# else: +# print(f"No .nii.gz files found in {date_path}") + +# # 設定實際的路徑 +# source_base_dir = '/mnt/1218/onlylian/sorted_modality_files_test/T1C' # 更新為您的實際T1C檔案路徑 +# target_base_dir = '/mnt/1218/onlylian/T1C_image_folder_test' # 更新為您想要的輸出路徑 + +# # 執行檔案複製 +# copy_last_file(source_base_dir, target_base_dir, 'T1C') \ No newline at end of file diff --git a/test/Image_files/patient_modalities_files.py b/test/Image_files/patient_modalities_files.py new file mode 100644 index 0000000..e684883 --- /dev/null +++ b/test/Image_files/patient_modalities_files.py @@ -0,0 +1,165 @@ +# import os +# import pandas as pd + +# # 要扫描的根目录 +# root_dir = '/mnt/1218/onlylian/sorted_modality_files' + +# # 为 DataFrame 准备数据 +# data = [] + +# # 遍历目录 +# for subdir, _, files in os.walk(root_dir): +# for file in files: +# if file.endswith('.nii.gz'): +# parts = subdir.split('/') +# try: +# if len(parts) >= 8: # 标准结构 +# modalities = parts[4] # 提取模态类型 +# patient_id = parts[5] # 提取患者信息 +# date = parts[6] # 提取日期 +# name = file # 提取文件名 +# elif len(parts) == 7: # 当模态类型与具体模态合并时 +# modalities = parts[4] # 提取模态类型 +# patient_id = parts[5] # 提取患者信息 +# date = parts[6] # 提取日期 +# name = file # 提取文件名 +# elif len(parts) == 6: # 当只有日期和模态类型时 +# modalities = parts[4] # 提取模态类型 +# patient_id = parts[5] # 提取患者信息 +# date = '' # 没有日期信息 +# name = file # 提取文件名 +# else: +# print(f"Path {subdir} does not have enough parts to extract data") +# continue +# data.append([modalities, patient_id, date, name]) +# print(f"Added: {modalities}/{patient_id}/{date}/{name}") # 调试信息 +# except IndexError as e: +# print(f"Error processing path {subdir}: {e}") + +# # 检查数据是否已经添加 +# if not data: +# print("No data found. Please check the directory structure and files.") + +# # 创建 DataFrame +# df = pd.DataFrame(data, columns=['Modalities', 'patient_id', 'date', 'name']) +# # 输出到 Excel +# output_path = '/mnt/1218/onlylian/patient_modalities_files.xlsx' +# df.to_excel(output_path, index=False) + +# print(f"Excel 文件已生成在: {output_path}") + +import os +import pandas as pd +from datetime import datetime + +# 设置根目录 +root_dir = '/mnt/1218/onlylian/sorted_modality_files' + +# 为 DataFrame 准备数据 +data = [] + +# 添加计数器和集合来跟踪统计信息 +total_files = 0 +unique_patients = set() +unique_modalities = set() +processed_dates = set() + +def process_file_path(file_path): + """ + 处理文件路径,提取相关信息 + 示例路径: /mnt/1218/onlylian/sorted_modality_files/CT/2U5BG75W/20240802/1.1_CyberKnife_head(MAR)_20240802155850_6.nii.gz + """ + try: + # 分割路径 + parts = file_path.split('/') + + # 确保文件是 .nii.gz 格式 + if not parts[-1].endswith('.nii.gz'): + return None + + # 从路径中提取所需信息 + modalities = parts[-4] # CT + patient_id = parts[-3] # 2U5BG75W + date = parts[-2] # 20240802 + name = parts[-1] # filename.nii.gz + + return [modalities, patient_id, date, name] + + except IndexError as e: + print(f"Error processing path {file_path}: {e}") + return None + +# 获取当前时间作为运行标记 +current_time = datetime.now().strftime("%Y%m%d_%H%M%S") + +print("开始扫描文件...") + +# 遍历目录 +for subdir, _, files in os.walk(root_dir): + for file in files: + if file.endswith('.nii.gz'): + full_path = os.path.join(subdir, file) + file_info = process_file_path(full_path) + + if file_info: + data.append(file_info) + total_files += 1 + unique_patients.add(file_info[1]) # 添加病人ID到集合 + unique_modalities.add(file_info[0]) # 添加模态类型到集合 + processed_dates.add(file_info[2]) # 添加日期到集合 + + # 每处理100个文件显示一次进度 + if total_files % 100 == 0: + print(f"已处理 {total_files} 个文件...") + +# 检查数据是否已经添加 +if not data: + print("未找到数据。请检查目录结构和文件。") +else: + # 创建 DataFrame + df = pd.DataFrame(data, columns=['Modalities', 'patient_id', 'date', 'name']) + + # 生成带时间戳的输出文件名,以避免覆盖现有文件 + output_path = f'/mnt/1218/onlylian/patient_modalities_files_{current_time}.xlsx' + + # 输出到 Excel + df.to_excel(output_path, index=False) + + # 打印统计信息 + print("\n处理完成!统计信息:") + print(f"总文件数: {total_files}") + print(f"病人数量: {len(unique_patients)}") + print(f"模态类型数: {len(unique_modalities)}") + print(f"检查日期数: {len(processed_dates)}") + print("\n各模态类型及其文件数量:") + modality_counts = df['Modalities'].value_counts() + for modality, count in modality_counts.items(): + print(f"{modality}: {count} 个文件") + + print(f"\nExcel 文件已生成在: {output_path}") + + # 输出病人ID分布统计 + patient_file_counts = df['patient_id'].value_counts() + print("\n病人文件数量分布:") + print(f"最少文件数: {patient_file_counts.min()}") + print(f"最多文件数: {patient_file_counts.max()}") + print(f"平均每个病人文件数: {patient_file_counts.mean():.2f}") + +# 错误处理和日志 +try: + # 同时保存一份日志文件 + log_path = f'/mnt/1218/onlylian/processing_log_{current_time}.txt' + with open(log_path, 'w') as log_file: + log_file.write(f"处理时间: {current_time}\n") + log_file.write(f"总文件数: {total_files}\n") + log_file.write(f"病人数量: {len(unique_patients)}\n") + log_file.write(f"模态类型数: {len(unique_modalities)}\n") + log_file.write(f"检查日期数: {len(processed_dates)}\n") + log_file.write("\n各模态类型及其文件数量:\n") + for modality, count in modality_counts.items(): + log_file.write(f"{modality}: {count} 个文件\n") + + print(f"\n处理日志已保存在: {log_path}") +except Exception as e: + print(f"保存日志文件时出错: {e}") + diff --git a/test/OAR_Files/copy_patient_OAR_files.py b/test/OAR_Files/copy_patient_OAR_files.py new file mode 100644 index 0000000..f27edbf --- /dev/null +++ b/test/OAR_Files/copy_patient_OAR_files.py @@ -0,0 +1,46 @@ +import os +import shutil +import pandas as pd + +# 讀取 Excel 文件 +file_path = '/mnt/1218/onlylian/patient_modalities_files_20241031_181028.xlsx' +df = pd.read_excel(file_path) + +# 定義來源和目標資料夾 +source_dir = '/mnt/1218/onlylian/Complete_NEW_OAR_Files' +target_dir = '/mnt/1218/onlylian/sorted_OAR_files_test' +modalities = ['CT', 'CTC', 'T1', 'T1C', 'T2', 'FLAIR', 'CT+CTC'] + +# 創建目標資料夾結構 +for modality in modalities: + modality_path = os.path.join(target_dir, modality) + os.makedirs(modality_path, exist_ok=True) + +# 遍歷每個病人ID和日期 +for _, row in df.iterrows(): + modality = row['Modalities'] + patient_id = row['patient_id'] + date = str(row['date']) + + if modality in modalities: + # 構建來源資料夾路徑 + source_path = os.path.join(source_dir, patient_id, date) + print(f"Checking source path: {source_path}") # 檢查路徑 + if os.path.exists(source_path): + # 構建目標資料夾路徑 + modality_path = os.path.join(target_dir, modality, patient_id, date) + os.makedirs(modality_path, exist_ok=True) + + # 遍歷來源資料夾中的所有檔案 + for file_name in os.listdir(source_path): + # 更新條件檢查 + if file_name.endswith('.nii.gz'): # 確保是正確的檔案類型 + source_file = os.path.join(source_path, file_name) + target_file = os.path.join(modality_path, file_name) + print(f"Copying {source_file} to {target_file}") # 跟蹤檔案複製 + # 複製檔案到目標資料夾 + shutil.copy2(source_file, target_file) + else: + print(f"Source path not found: {source_path}") + else: + print(f"Unknown modality {modality} for patient {patient_id} on {date}") diff --git a/test/OAR_Files/merge_G4_M6_directories.py b/test/OAR_Files/merge_G4_M6_directories.py new file mode 100644 index 0000000..d228a31 --- /dev/null +++ b/test/OAR_Files/merge_G4_M6_directories.py @@ -0,0 +1,47 @@ +import os +import shutil + +def merge_directories(src1, src2, dest): + if not os.path.exists(dest): + os.makedirs(dest) + + def copy_or_merge(src, dest): + for patient in os.listdir(src): + patient_src_path = os.path.join(src, patient) + patient_dest_path = os.path.join(dest, patient) + + if os.path.isdir(patient_src_path): + if os.path.exists(patient_dest_path): + # If destination folder exists, merge the contents + for item in os.listdir(patient_src_path): + s = os.path.join(patient_src_path, item) + d = os.path.join(patient_dest_path, item) + if os.path.isdir(s): + shutil.copytree(s, d, dirs_exist_ok=True) + else: + shutil.copy2(s, d) + else: + # If destination folder does not exist, copy the whole folder + shutil.copytree(patient_src_path, patient_dest_path) + + # Copy or merge from both source directories to destination directory + copy_or_merge(src1, dest) + copy_or_merge(src2, dest) + + print("Directories merged successfully.") + +# Define source and destination paths +src1 = '/mnt/1220/Public/dataset2/G4' +src2 = '/mnt/1220/Public/dataset2/M6' +dest = "/home/onlylian/dataset2_G4_M6" + +# Call the function to merge the directories +merge_directories(src1, src2, dest) + + + + + + + + diff --git a/test/OAR_Files/merge_patient_folders.py b/test/OAR_Files/merge_patient_folders.py new file mode 100644 index 0000000..aacd05c --- /dev/null +++ b/test/OAR_Files/merge_patient_folders.py @@ -0,0 +1,46 @@ +import os +import shutil +import re + +def extract_patient_id(filename): + # 假設文件名格式為 "something_PATIENTID.nii.gz" + match = re.search(r'_([A-Z0-9]+)\.nii\.gz$', filename) + if match: + return match.group(1) + return None + +def merge_folders(folder1, folder2, output_folder): + # 確保輸出資料夾存在 + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + # 獲取兩個資料夾中的所有文件 + files1 = set(os.listdir(folder1)) + files2 = set(os.listdir(folder2)) + + # 合併所有唯一的病人ID + all_files = files1.union(files2) + + for file in all_files: + patient_id = extract_patient_id(file) + if patient_id: + new_filename = f"label_{patient_id}.nii.gz" + source_folder = folder1 if file in files1 else folder2 + source_path = os.path.join(source_folder, file) + dest_path = os.path.join(output_folder, new_filename) + + # 複製文件到輸出資料夾並重命名 + shutil.copy2(source_path, dest_path) + print(f"Copied and renamed {file} to {new_filename}") + else: + print(f"Skipped {file} - could not extract patient ID") + +# 設定資料夾路徑 +folder1 = '/home/onlylian/resampled_T1C_label' +folder2 = '/home/onlylian/resampled_CT+CTC_label' +output_folder = '/home/onlylian/merged_label_folder' + +# 執行合併 +merge_folders(folder1, folder2, output_folder) + +print("Merging complete!") \ No newline at end of file diff --git a/test/OAR_Files/multi_organ_label_combiner.py b/test/OAR_Files/multi_organ_label_combiner.py new file mode 100644 index 0000000..7d606b2 --- /dev/null +++ b/test/OAR_Files/multi_organ_label_combiner.py @@ -0,0 +1,172 @@ +# import os +# import SimpleITK as sitk +# import difflib + +# # 定義資料夾路徑 +# T1C_folder_path = '/home/onlylian/sorted_OAR_files/T1C' +# CTCTC_folder_path = '/home/onlylian/sorted_OAR_files/CT+CTC' +# T1C_output_folder_path = '/home/onlylian/T1C_label_folder' +# CTCTC_output_folder_path = '/home/onlylian/CT+CTC_label_folder' + +# # 定義組織標籤對應關係 +# labels = { +# "background": "0", +# "Brainstem": "1", +# "Right_Eye": "2", +# "Left_Eye": "3", +# "Optic_Chiasm": "4", +# "Right_Optic_Nerve": "5", +# "Left_Optic_Nerve": "6" +# } + +# # 確保輸出資料夾存在 +# os.makedirs(T1C_output_folder_path, exist_ok=True) +# os.makedirs(CTCTC_output_folder_path, exist_ok=True) + +# def resample_image(image, reference_image): +# resample = sitk.ResampleImageFilter() +# resample.SetReferenceImage(reference_image) +# resample.SetInterpolator(sitk.sitkNearestNeighbor) +# resample.SetDefaultPixelValue(0) +# return resample.Execute(image) + +# def process_images(input_folder, output_folder, image_type): +# # 獲取所有病人的資料夾路徑 +# patient_folders = sorted([os.path.join(dp, d) for dp, dn, filenames in os.walk(input_folder) for d in dn]) + +# file_counter = 1 + +# for patient_folder in patient_folders: +# # 創建空的影像,用來合成所有器官 +# combined_image = None +# # 獲取病人文件夾中的所有文件名,並轉換為小寫形式 +# patient_files = {f.lower(): f for f in os.listdir(patient_folder)} +# filename = patient_folder.split('/')[-2] + +# # 遍歷每個病人的所有影像文件 +# for organ_name, label in labels.items(): +# organ_filename = f'struct_{organ_name}.nii.gz'.lower() +# # 使用 difflib.get_close_matches 查找相似文件名 +# possible_matches = difflib.get_close_matches(organ_filename, patient_files.keys(), n=1, cutoff=0.8) + +# if possible_matches: +# matched_filename = patient_files[possible_matches[0]] +# organ_path = os.path.join(patient_folder, matched_filename) +# organ_image = sitk.ReadImage(organ_path) + +# if combined_image is None: +# # 初始化合成影像 +# combined_image = sitk.Image(organ_image.GetSize(), sitk.sitkUInt8) +# combined_image.CopyInformation(organ_image) + +# # 重新取樣器官影像以匹配合成影像的大小 +# resampled_organ_image = resample_image(organ_image, combined_image) + +# # 將器官影像添加到合成影像中 +# organ_array = sitk.GetArrayFromImage(resampled_organ_image) +# combined_array = sitk.GetArrayFromImage(combined_image) +# combined_array[organ_array > 0] = int(label) + +# combined_image = sitk.GetImageFromArray(combined_array) +# combined_image.CopyInformation(resampled_organ_image) + +# # 保存合成影像 +# if combined_image: +# output_filename = f'{image_type}_label_{filename}.nii.gz' +# output_path = os.path.join(output_folder, output_filename) +# sitk.WriteImage(combined_image, output_path) +# print(f'Saved combined image to {output_path}') +# file_counter += 1 + +# # 處理 T1C 影像 +# print("Processing T1C images...") +# process_images(T1C_folder_path, T1C_output_folder_path, 'T1C') + +# # 處理 CT+CTC 影像 +# print("Processing CT+CTC images...") +# process_images(CTCTC_folder_path, CTCTC_output_folder_path, 'CT+CTC') + +# print("All processing completed.") + +import os +import SimpleITK as sitk +import difflib + +# 定义文件夹路径 +T1_folder_path = '/mnt/1218/onlylian/sorted_OAR_files_test/T1C' +T1_output_folder_path = '/mnt/1218/onlylian/T1C_label_folder_test_2' + +# 定义组织标签对应关系 +labels = { + "background": "0", + "Brainstem": "1", + "Right_Eye": "2", + "Left_Eye": "3", + "Optic_Chiasm": "4", + "Right_Optic_Nerve": "5", + "Left_Optic_Nerve": "6" +} + +# 确保输出文件夹存在 +os.makedirs(T1_output_folder_path, exist_ok=True) + +def resample_image(image, reference_image): + resample = sitk.ResampleImageFilter() + resample.SetReferenceImage(reference_image) + resample.SetInterpolator(sitk.sitkNearestNeighbor) + resample.SetDefaultPixelValue(0) + return resample.Execute(image) + +def process_images(input_folder, output_folder, image_type): + # 获取所有病人的文件夹路径 + patient_folders = sorted([os.path.join(dp, d) for dp, dn, filenames in os.walk(input_folder) for d in dn]) + + file_counter = 1 + + for patient_folder in patient_folders: + # 创建空的图像,用来合成所有器官 + combined_image = None + # 获取病人文件夹中的所有文件名,并转换为小写形式 + patient_files = {f.lower(): f for f in os.listdir(patient_folder)} + filename = patient_folder.split('/')[-2] + + # 遍历每个病人的所有图像文件 + for organ_name, label in labels.items(): + organ_filename = f'struct_{organ_name}.nii.gz'.lower() + # 使用 difflib.get_close_matches 查找相似文件名 + possible_matches = difflib.get_close_matches(organ_filename, patient_files.keys(), n=1, cutoff=0.8) + + if possible_matches: + matched_filename = patient_files[possible_matches[0]] + organ_path = os.path.join(patient_folder, matched_filename) + organ_image = sitk.ReadImage(organ_path) + + if combined_image is None: + # 初始化合成图像 + combined_image = sitk.Image(organ_image.GetSize(), sitk.sitkUInt8) + combined_image.CopyInformation(organ_image) + + # 重新采样器官图像以匹配合成图像的大小 + resampled_organ_image = resample_image(organ_image, combined_image) + + # 将器官图像添加到合成图像中 + organ_array = sitk.GetArrayFromImage(resampled_organ_image) + combined_array = sitk.GetArrayFromImage(combined_image) + combined_array[organ_array > 0] = int(label) + + combined_image = sitk.GetImageFromArray(combined_array) + combined_image.CopyInformation(resampled_organ_image) + + # 保存合成图像 + if combined_image: + output_filename = f'{image_type}_label_{filename}.nii.gz' + output_path = os.path.join(output_folder, output_filename) + sitk.WriteImage(combined_image, output_path) + print(f'Saved combined image to {output_path}') + file_counter += 1 + +# 处理 T1C 图像 +print("Processing T1C images...") +process_images(T1_folder_path, T1_output_folder_path, 'T1C') + +print("Processing completed.") \ No newline at end of file diff --git a/test/OAR_Files/organ_file_processor.py b/test/OAR_Files/organ_file_processor.py new file mode 100644 index 0000000..017eca8 --- /dev/null +++ b/test/OAR_Files/organ_file_processor.py @@ -0,0 +1,138 @@ +import os +import shutil +import difflib + +def copy_organ_files(source_root, target_root): + # 確保目標資料夾存在 + if not os.path.exists(target_root): + os.makedirs(target_root) + print(f"Created directory {target_root}") + + # 遍歷來源的根資料夾和所有子資料夾,尋找名為 ORGAN 的資料夾 + for root, dirs, files in os.walk(source_root): + for dir in dirs: + if dir == 'ORGAN': + source_folder_path = os.path.join(root, dir) + # 提取病例資料夾和日期資料夾 + case_folder = os.path.basename(os.path.dirname(os.path.dirname(os.path.dirname(source_folder_path)))) + date_folder = os.path.basename(os.path.dirname(os.path.dirname(source_folder_path))) + # 組合目標資料夾路徑 + target_folder_path = os.path.join(target_root, case_folder, date_folder) + + # 確保目標資料夾存在 + if not os.path.exists(target_folder_path): + os.makedirs(target_folder_path) + print(f"Created directory {target_folder_path}") + + # 複製資料夾中的所有檔案到目標資料夾 + for file_name in os.listdir(source_folder_path): + source_file_path = os.path.join(source_folder_path, file_name) + target_file_path = os.path.join(target_folder_path, file_name) + + # 如果目標檔案已經存在,重新命名以避免覆蓋 + if os.path.exists(target_file_path): + base, extension = os.path.splitext(file_name) + counter = 1 + new_file_name = f"{base}_{counter}{extension}" + target_file_path = os.path.join(target_folder_path, new_file_name) + while os.path.exists(target_file_path): + counter += 1 + new_file_name = f"{base}_{counter}{extension}" + target_file_path = os.path.join(target_folder_path, new_file_name) + + shutil.copy(source_file_path, target_file_path) + print(f"Copied {source_file_path} to {target_file_path}") + + print("All ORGAN files have been copied.") + +def check_and_copy_files(root_dir, required_file_groups, target_dir, match_cutoff=0.7): + # 確保目標目錄存在 + os.makedirs(target_dir, exist_ok=True) + + # 計算缺少文件的病人數量 + missing_files_patients = 0 + copied_files_patients = 0 + + # 遍歷根目錄下的每個病人文件夾 + for patient_id in os.listdir(root_dir): + patient_dir = os.path.join(root_dir, patient_id) + + # 確保是目錄 + if os.path.isdir(patient_dir): + # 遍歷病人目錄下的日期文件夾 + for date_folder in os.listdir(patient_dir): + date_dir = os.path.join(patient_dir, date_folder) + existing_files = os.listdir(date_dir) + + # 檢查指定的OAR文件是否存在 + missing_files = [] + for file_group in required_file_groups: + # 對於左右眼的文件,進行更精確的匹配 + if 'Left_Eye' in file_group[0]: + matches = [f for f in existing_files if 'Left_Eye' in f] + elif 'Right_Eye' in file_group[0]: + matches = [f for f in existing_files if 'Right_Eye' in f] + else: + # 對其他文件使用模糊匹配來查找接近的文件名稱 + matches = difflib.get_close_matches(file_group[0], existing_files, n=1, cutoff=match_cutoff) + + if not matches: + missing_files.append(file_group[0]) # 使用文件組中的第一個文件名作為缺失文件的表示 + else: + print(f"匹配到的文件: {matches[0]} 對應到 {file_group[0]}") + + if missing_files: + missing_files_patients += 1 + print(f"病人: {patient_id}, 日期: {date_folder}, 缺失的文件: {missing_files}") + else: + # 沒有缺失文件,複製文件到新目錄 + new_patient_dir = os.path.join(target_dir, patient_id) + new_date_dir = os.path.join(new_patient_dir, date_folder) + os.makedirs(new_date_dir, exist_ok=True) + + for file_name in existing_files: + src_file = os.path.join(date_dir, file_name) + dst_file = os.path.join(new_date_dir, file_name) + shutil.copy(src_file, dst_file) + + copied_files_patients += 1 + print(f"病人: {patient_id}, 日期: {date_folder}, 文件已成功複製") + + print(f"缺少所需OAR文件的資料總數: {missing_files_patients}") + print(f"文件完整且已複製的資料總數: {copied_files_patients}") + +def count_patients_and_records(target_dir): + patient_count = 0 + record_count = 0 + + for patient_id in os.listdir(target_dir): + patient_dir = os.path.join(target_dir, patient_id) + + if os.path.isdir(patient_dir): + patient_count += 1 + record_count += len(os.listdir(patient_dir)) + + print(f"完整OAR文件的病人總數: {patient_count}") + print(f"完整OAR文件的資料總數: {record_count}") + +if __name__ == "__main__": + # 第一部分:複製 ORGAN 文件 + source_root = '/mnt/1218/onlylian/M6_24Q3/nii' + intermediate_target = '/mnt/1218/onlylian/NEW_ORGAN_Files' + copy_organ_files(source_root, intermediate_target) + + # 第二部分:檢查並複製完整的 OAR 文件 + required_file_groups = [ + ['Struct_Brain_Stem.nii.gz'], + ['Struct_Left_Optic_Nerve.nii.gz'], + ['Struct_Right_Optic_Nerve.nii.gz'], + ['Struct_Left_Eye.nii.gz'], + ['Struct_Right_Eye.nii.gz'], + ['Struct_Optic_Chiasm.nii.gz'] + ] + final_target = '/mnt/1218/onlylian/Complete_NEW_OAR_Files' + + check_and_copy_files(intermediate_target, required_file_groups, final_target) + + # 計算總共的病人和記錄數量 + count_patients_and_records(final_target) \ No newline at end of file diff --git a/test/OAR_Target_Files/copy_patient_OAR_Target_files.py b/test/OAR_Target_Files/copy_patient_OAR_Target_files.py new file mode 100644 index 0000000..be50746 --- /dev/null +++ b/test/OAR_Target_Files/copy_patient_OAR_Target_files.py @@ -0,0 +1,62 @@ +import os +import shutil +import pandas as pd + +# 讀取 Excel 文件 +file_path = '/home/onlylian/patient_modalities_files.xlsx' +df = pd.read_excel(file_path) + +# 定義來源和目標資料夾 +source_dir = '/home/onlylian/Complete_NEW_ORGAN_Target_Files' +target_dir = '/home/onlylian/sorted_OAR_Target_files' +modalities = ['CT', 'CTC', 'T1', 'T1C', 'T2', 'FLAIR', 'CT+CTC'] + +# 創建目標資料夾結構 +for modality in modalities: + modality_path = os.path.join(target_dir, modality) + os.makedirs(modality_path, exist_ok=True) + +# 遍歷每個病人ID和日期 +for _, row in df.iterrows(): + modality = row['Modalities'] + patient_id = row['patient_id'] + date = str(row['date']) + + if modality in modalities: + # 構建來源資料夾路徑 + source_path = os.path.join(source_dir, patient_id, date) + print(f"Checking source path: {source_path}") # 檢查路徑 + if os.path.exists(source_path): + # 分別處理 ORGAN 和 RT 目錄 + organ_path = os.path.join(source_path, 'ORGAN') + rt_path = os.path.join(source_path, 'RT') + + # 構建目標資料夾路徑 + modality_path = os.path.join(target_dir, modality, patient_id, date) + os.makedirs(modality_path, exist_ok=True) + + # 遍歷 ORGAN 目錄中的所有檔案 + if os.path.exists(organ_path): + for file_name in os.listdir(organ_path): + if file_name.endswith('.nii.gz'): # 檢查檔案格式 + source_file = os.path.join(organ_path, file_name) + target_file = os.path.join(modality_path, file_name) + print(f"Copying {source_file} to {target_file}") + shutil.copy2(source_file, target_file) + else: + print(f"ORGAN folder not found: {organ_path}") + + # 遍歷 RT 目錄中的所有檔案 + if os.path.exists(rt_path): + for file_name in os.listdir(rt_path): + if file_name.endswith('.nii.gz'): # 檢查檔案格式 + source_file = os.path.join(rt_path, file_name) + target_file = os.path.join(modality_path, file_name) + print(f"Copying {source_file} to {target_file}") + shutil.copy2(source_file, target_file) + else: + print(f"RT folder not found: {rt_path}") + else: + print(f"Source path not found: {source_path}") + else: + print(f"Unknown modality {modality} for patient {patient_id} on {date}") diff --git a/test/OAR_Target_Files/merge_G4_M6_directories.py b/test/OAR_Target_Files/merge_G4_M6_directories.py new file mode 100644 index 0000000..d228a31 --- /dev/null +++ b/test/OAR_Target_Files/merge_G4_M6_directories.py @@ -0,0 +1,47 @@ +import os +import shutil + +def merge_directories(src1, src2, dest): + if not os.path.exists(dest): + os.makedirs(dest) + + def copy_or_merge(src, dest): + for patient in os.listdir(src): + patient_src_path = os.path.join(src, patient) + patient_dest_path = os.path.join(dest, patient) + + if os.path.isdir(patient_src_path): + if os.path.exists(patient_dest_path): + # If destination folder exists, merge the contents + for item in os.listdir(patient_src_path): + s = os.path.join(patient_src_path, item) + d = os.path.join(patient_dest_path, item) + if os.path.isdir(s): + shutil.copytree(s, d, dirs_exist_ok=True) + else: + shutil.copy2(s, d) + else: + # If destination folder does not exist, copy the whole folder + shutil.copytree(patient_src_path, patient_dest_path) + + # Copy or merge from both source directories to destination directory + copy_or_merge(src1, dest) + copy_or_merge(src2, dest) + + print("Directories merged successfully.") + +# Define source and destination paths +src1 = '/mnt/1220/Public/dataset2/G4' +src2 = '/mnt/1220/Public/dataset2/M6' +dest = "/home/onlylian/dataset2_G4_M6" + +# Call the function to merge the directories +merge_directories(src1, src2, dest) + + + + + + + + diff --git a/test/OAR_Target_Files/merge_tv_patient_folders.py b/test/OAR_Target_Files/merge_tv_patient_folders.py new file mode 100644 index 0000000..79f382f --- /dev/null +++ b/test/OAR_Target_Files/merge_tv_patient_folders.py @@ -0,0 +1,46 @@ +import os +import shutil +import re + +def extract_patient_id(filename): + # 假設文件名格式為 "something_PATIENTID.nii.gz" + match = re.search(r'_([A-Z0-9]+)\.nii\.gz$', filename) + if match: + return match.group(1) + return None + +def merge_folders(folder1, folder2, output_folder): + # 確保輸出資料夾存在 + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + # 獲取兩個資料夾中的所有文件 + files1 = set(os.listdir(folder1)) + files2 = set(os.listdir(folder2)) + + # 合併所有唯一的病人ID + all_files = files1.union(files2) + + for file in all_files: + patient_id = extract_patient_id(file) + if patient_id: + new_filename = f"label_{patient_id}.nii.gz" + source_folder = folder1 if file in files1 else folder2 + source_path = os.path.join(source_folder, file) + dest_path = os.path.join(output_folder, new_filename) + + # 複製文件到輸出資料夾並重命名 + shutil.copy2(source_path, dest_path) + print(f"Copied and renamed {file} to {new_filename}") + else: + print(f"Skipped {file} - could not extract patient ID") + +# 設定資料夾路徑 +folder1 = '/home/onlylian/resampled_T1C_TV_label' +folder2 = '/home/onlylian/resampled_CT+CTC_TV_label' +output_folder = '/home/onlylian/merged_TV_label_folder' + +# 執行合併 +merge_folders(folder1, folder2, output_folder) + +print("Merging complete!") \ No newline at end of file diff --git a/test/OAR_Target_Files/organ_tv_file_processor.py b/test/OAR_Target_Files/organ_tv_file_processor.py new file mode 100644 index 0000000..98c594b --- /dev/null +++ b/test/OAR_Target_Files/organ_tv_file_processor.py @@ -0,0 +1,156 @@ +import os +import shutil +import difflib +import time + +def copy_organ_and_tv_files(source_root, target_root): + # 確保目標資料夾存在 + if not os.path.exists(target_root): + os.makedirs(target_root) + print(f"Created directory {target_root}") + + # 複製 'ORGAN' 資料夾的檔案 + for root, dirs, files in os.walk(source_root): + for dir in dirs: + if dir == 'ORGAN': + source_folder_path = os.path.join(root, dir) + case_folder = os.path.basename(os.path.dirname(os.path.dirname(os.path.dirname(source_folder_path)))) + date_folder = os.path.basename(os.path.dirname(os.path.dirname(source_folder_path))) + target_folder_path = os.path.join(target_root, case_folder, date_folder, 'ORGAN') + + if not os.path.exists(target_folder_path): + os.makedirs(target_folder_path) + print(f"Created directory {target_folder_path}") + + for file_name in os.listdir(source_folder_path): + source_file_path = os.path.join(source_folder_path, file_name) + target_file_path = os.path.join(target_folder_path, file_name) + copy_with_retry(source_file_path, target_file_path) + + # 複製 TV 檔案 + for root, dirs, files in os.walk(source_root): + for file_name in files: + if file_name.startswith('TV') and file_name.endswith('.nii.gz'): + source_file_path = os.path.join(root, file_name) + case_folder = os.path.basename(os.path.dirname(os.path.dirname(os.path.dirname(source_file_path)))) + date_folder = os.path.basename(os.path.dirname(os.path.dirname(source_file_path))) + target_folder_path = os.path.join(target_root, case_folder, date_folder, 'RT') + + if not os.path.exists(target_folder_path): + os.makedirs(target_folder_path) + print(f"Created directory {target_folder_path}") + + target_file_path = os.path.join(target_folder_path, file_name) + copy_with_retry(source_file_path, target_file_path) + + print("All ORGAN and TV files have been copied.") + +def check_and_copy_files(root_dir, required_oar_files, target_dir): + os.makedirs(target_dir, exist_ok=True) + missing_files_patients = 0 + copied_files_patients = 0 + + for patient_id in os.listdir(root_dir): + patient_dir = os.path.join(root_dir, patient_id) + + if os.path.isdir(patient_dir): + for date_folder in os.listdir(patient_dir): + date_dir = os.path.join(patient_dir, date_folder) + organ_dir = os.path.join(date_dir, 'ORGAN') + rt_dir = os.path.join(date_dir, 'RT') + + if os.path.isdir(organ_dir) and os.path.isdir(rt_dir): + organ_files = os.listdir(organ_dir) + rt_files = os.listdir(rt_dir) + + missing_oar_files = [] + for oar_file in required_oar_files: + if 'Left_Eye' in oar_file: + matches = [f for f in organ_files if 'Left_Eye' in f] + elif 'Right_Eye' in oar_file: + matches = [f for f in organ_files if 'Right_Eye' in f] + else: + matches = difflib.get_close_matches(oar_file, organ_files, n=1, cutoff=0.8) + + if not matches: + missing_oar_files.append(oar_file) + + target_files = [f for f in rt_files if f.endswith('.nii.gz')] + if not target_files: + missing_oar_files.append('Target File') + + if missing_oar_files: + missing_files_patients += 1 + print(f"病人: {patient_id}, 日期: {date_folder}, 缺失的文件: {missing_oar_files}") + else: + new_patient_dir = os.path.join(target_dir, patient_id, date_folder) + os.makedirs(new_patient_dir, exist_ok=True) + + for file_name in organ_files: + src_file = os.path.join(organ_dir, file_name) + dst_file = os.path.join(new_patient_dir, 'ORGAN', file_name) + os.makedirs(os.path.join(new_patient_dir, 'ORGAN'), exist_ok=True) + copy_with_retry(src_file, dst_file) + + for file_name in rt_files: + src_file = os.path.join(rt_dir, file_name) + dst_file = os.path.join(new_patient_dir, 'RT', file_name) + os.makedirs(os.path.join(new_patient_dir, 'RT'), exist_ok=True) + copy_with_retry(src_file, dst_file) + + copied_files_patients += 1 + print(f"病人: {patient_id}, 日期: {date_folder}, 文件已成功複製") + else: + missing_files_patients += 1 + print(f"病人: {patient_id}, 日期: {date_folder}, 缺少 ORGAN 或 RT 目錄") + + print(f"缺少所需文件的資料總數: {missing_files_patients}") + print(f"文件完整且已複製的資料總數: {copied_files_patients}") + +def copy_with_retry(src, dst, retries=3, delay=5): + for attempt in range(retries): + try: + shutil.copy(src, dst) + print(f"Copied {src} to {dst}") + return + except BlockingIOError as e: + if attempt < retries - 1: + print(f"資源暫時不可用,等待 {delay} 秒後重試... (第 {attempt + 1} 次)") + time.sleep(delay) + else: + raise e + +def count_patients_and_records(target_dir): + patient_count = 0 + record_count = 0 + + for patient_id in os.listdir(target_dir): + patient_dir = os.path.join(target_dir, patient_id) + + if os.path.isdir(patient_dir): + patient_count += 1 + record_count += len(os.listdir(patient_dir)) + + print(f"完整OAR和Target文件的病人總數: {patient_count}") + print(f"完整OAR和Target文件的資料總數: {record_count}") + +if __name__ == "__main__": + # 設定路徑 + source_root = '/home/onlylian/dataset2_G4_M6' + intermediate_target = '/home/onlylian/NEW_ORGAN_Target_Files' + final_target = '/home/onlylian/Complete_NEW_ORGAN_Target_Files' + + # 指定要檢查的OAR文件 + required_oar_files = [ + 'Struct_Brain_Stem.nii.gz', + 'Struct_Left_Optic_Nerve.nii.gz', + 'Struct_Right_Optic_Nerve.nii.gz', + 'Struct_Left_Eye.nii.gz', + 'Struct_Right_Eye.nii.gz', + 'Struct_Optic_Chiasm.nii.gz' + ] + + # 執行複製和檢查過程 + copy_organ_and_tv_files(source_root, intermediate_target) + check_and_copy_files(intermediate_target, required_oar_files, final_target) + count_patients_and_records(final_target) \ No newline at end of file diff --git a/test/OAR_Target_Files/tv_and_oar_label_combiner.py b/test/OAR_Target_Files/tv_and_oar_label_combiner.py new file mode 100644 index 0000000..39aa69a --- /dev/null +++ b/test/OAR_Target_Files/tv_and_oar_label_combiner.py @@ -0,0 +1,107 @@ +import os +import glob +import SimpleITK as sitk +from difflib import get_close_matches + +def process_images(input_base_path, output_base_path, output_prefix): + # 定義組織標籤對應關係 + labels = { + "background": "0", + "Struct_Brain_Stem": "1", + "Struct_Right_Eye": "2", + "Struct_Left_Eye": "3", + "Struct_Optic_Chiasm": "4", + "Struct_Right_Optic_Nerve": "5", + "Struct_Left_Optic_Nerve": "6", + "TV": "7" # Tumor + } + + # 確保輸出資料夾存在 + os.makedirs(output_base_path, exist_ok=True) + + # 定義重新取樣函數 + def resample_image(image, reference_image): + resample = sitk.ResampleImageFilter() + resample.SetReferenceImage(reference_image) + resample.SetInterpolator(sitk.sitkNearestNeighbor) # 使用最近鄰插值避免標籤變化 + resample.SetDefaultPixelValue(0) + resampled_image = resample.Execute(image) + return resampled_image + + # 遍歷所有病人的資料夾 + for patient_id in os.listdir(input_base_path): + patient_folder_path = os.path.join(input_base_path, patient_id) + if not os.path.isdir(patient_folder_path): + continue # 跳過非資料夾的項目 + + # 確定病人資料夾下的日期資料夾 + date_folder = os.listdir(patient_folder_path)[0] + ORGAN_folder_path = os.path.join(patient_folder_path, date_folder) + + # 創建一個空的影像來合併所有標籤 + combined_image = None + + # 列出病人資料夾內的所有檔案 + all_files = os.listdir(ORGAN_folder_path) + + # 遍歷每個標籤並合併 + for organ_name, label in labels.items(): + if organ_name == "TV": + # 動態搜尋 TV 檔案 + tv_files = glob.glob(os.path.join(ORGAN_folder_path, "TV-*.nii.gz")) + if len(tv_files) == 0: + print(f"TV file not found for patient {patient_id}, skipping TV...") + continue + organ_path = tv_files[0] # 取第一個找到的檔案 + else: + # 使用模糊比對尋找最接近的檔案名 + matches = get_close_matches(organ_name, all_files, n=1, cutoff=0.6) + if len(matches) == 0: + print(f"No close match found for {organ_name} for patient {patient_id}, skipping...") + continue + organ_filename = matches[0] + organ_path = os.path.join(ORGAN_folder_path, organ_filename) + + if not os.path.exists(organ_path): + print(f"File {organ_path} not found for patient {patient_id}, skipping...") + continue + + organ_image = sitk.ReadImage(organ_path) + + if combined_image is None: + # 初始化合併影像的大小和參考 + combined_image = sitk.Image(organ_image.GetSize(), sitk.sitkUInt8) + combined_image.CopyInformation(organ_image) + else: + # 如果器官影像的尺寸和合併影像不同,進行重新取樣 + if organ_image.GetSize() != combined_image.GetSize(): + print(f"Resampling organ {organ_name} for patient {patient_id} due to size mismatch...") + organ_image = resample_image(organ_image, combined_image) + + # 將器官影像添加到合併影像中 + organ_array = sitk.GetArrayFromImage(organ_image) + combined_array = sitk.GetArrayFromImage(combined_image) + + # 更新合併影像的陣列,根據標籤值來設定非背景區域 + combined_array[organ_array > 0] = int(label) + + # 將更新的陣列轉回 SimpleITK 影像 + combined_image = sitk.GetImageFromArray(combined_array) + combined_image.CopyInformation(organ_image) + + # 保存合併後的影像 + output_filename = f'{output_prefix}_label_{patient_id}.nii.gz' + output_path = os.path.join(output_base_path, output_filename) + sitk.WriteImage(combined_image, output_path) + print(f'Saved combined image for patient {patient_id} to {output_path}') + +if __name__ == "__main__": + # 處理 T1C 影像 + T1C_input_path = '/home/onlylian/sorted_OAR_Target_files/T1C' + T1C_output_path = '/home/onlylian/T1C_OAR_Target_label_folder' + process_images(T1C_input_path, T1C_output_path, "T1C") + + # 處理 CT+CTC 影像 + CT_CTC_input_path = '/home/onlylian/sorted_OAR_Target_files/T1C' # 注意:這裡使用相同的輸入路徑,如果需要不同的路徑請修改 + CT_CTC_output_path = '/home/onlylian/CT+CTC_OAR_Target_label_folder' + process_images(CT_CTC_input_path, CT_CTC_output_path, "CT+CTC") \ No newline at end of file diff --git a/test/final/advanced_multimodal_image_resampler.py b/test/final/advanced_multimodal_image_resampler.py new file mode 100644 index 0000000..efd390a --- /dev/null +++ b/test/final/advanced_multimodal_image_resampler.py @@ -0,0 +1,227 @@ +# import SimpleITK as sitk +# import os +# import numpy as np + +# def standardize_origin(image, target_origin): +# """將影像的原點標準化為目標原點""" +# return sitk.Resample( +# image, +# image.GetSize(), +# sitk.Transform(), +# sitk.sitkLinear, +# target_origin, +# image.GetSpacing(), +# image.GetDirection(), +# 0, +# image.GetPixelID() +# ) + +# def resample_image(image, reference_image, is_label=False): +# resampler = sitk.ResampleImageFilter() +# resampler.SetReferenceImage(reference_image) +# resampler.SetTransform(sitk.Transform()) +# resampler.SetOutputSpacing(reference_image.GetSpacing()) +# resampler.SetSize(reference_image.GetSize()) +# resampler.SetOutputDirection(reference_image.GetDirection()) +# resampler.SetOutputOrigin(reference_image.GetOrigin()) + +# if is_label: +# resampler.SetInterpolator(sitk.sitkNearestNeighbor) +# resampler.SetDefaultPixelValue(0) +# else: +# resampler.SetInterpolator(sitk.sitkLinear) +# resampler.SetDefaultPixelValue(image.GetPixelIDValue()) + +# return resampler.Execute(image) + +# def process_patient(t1c_image_path, ctctc_image_path, label_path, output_t1c_dir, output_ctctc_dir, output_label_dir, patient_id): +# print(f"\nProcessing patient: {patient_id}") + +# # 讀取影像 +# t1c_image = sitk.ReadImage(t1c_image_path) +# ctctc_image = sitk.ReadImage(ctctc_image_path) +# label = sitk.ReadImage(label_path) + +# print(f" Original T1C image size: {t1c_image.GetSize()}, origin: {t1c_image.GetOrigin()}") +# print(f" Original CT+CTC image size: {ctctc_image.GetSize()}, origin: {ctctc_image.GetOrigin()}") +# print(f" Original label size: {label.GetSize()}, origin: {label.GetOrigin()}") + +# # 使用 T1C 影像作為參考 +# reference_image = t1c_image + +# # 重採樣 CT+CTC 影像以匹配 T1C 影像 +# resampled_ctctc = resample_image(ctctc_image, reference_image) + +# # 重採樣 label 以匹配 T1C 影像 +# resampled_label = resample_image(label, reference_image, is_label=True) + +# print(f" Resampled CT+CTC image size: {resampled_ctctc.GetSize()}, origin: {resampled_ctctc.GetOrigin()}") +# print(f" Resampled label size: {resampled_label.GetSize()}, origin: {resampled_label.GetOrigin()}") + +# # 保存重採樣後的影像和標籤 +# t1c_output_path = os.path.join(output_t1c_dir, f"T1C_{patient_id}.nii.gz") +# ctctc_output_path = os.path.join(output_ctctc_dir, f"CT+CTC_{patient_id}.nii.gz") +# label_output_path = os.path.join(output_label_dir, f"label_{patient_id}.nii.gz") + +# sitk.WriteImage(t1c_image, t1c_output_path) +# sitk.WriteImage(resampled_ctctc, ctctc_output_path) +# sitk.WriteImage(resampled_label, label_output_path) + +# print(f" T1C image saved to: {t1c_output_path}") +# print(f" Resampled CT+CTC image saved to: {ctctc_output_path}") +# print(f" Resampled label saved to: {label_output_path}") + +# def process_directory(t1c_dir, ctctc_dir, label_dir, output_t1c_dir, output_ctctc_dir, output_label_dir): +# print("Starting directory processing") +# os.makedirs(output_t1c_dir, exist_ok=True) +# os.makedirs(output_ctctc_dir, exist_ok=True) +# os.makedirs(output_label_dir, exist_ok=True) + +# # 獲取所有病人 ID +# patient_ids = set() +# for filename in os.listdir(t1c_dir): +# if filename.startswith("T1C_") and filename.endswith(".nii.gz"): +# patient_id = filename[4:-7] # 去掉 "T1C_" 和 ".nii.gz" +# patient_ids.add(patient_id) + +# for patient_id in patient_ids: +# t1c_path = os.path.join(t1c_dir, f"T1C_{patient_id}.nii.gz") +# ctctc_path = os.path.join(ctctc_dir, f"CT+CTC_{patient_id}.nii.gz") +# label_path = os.path.join(label_dir, f"T1C_label_{patient_id}.nii.gz") # 假設 label 文件名與 T1C 相同 + +# if os.path.exists(t1c_path) and os.path.exists(ctctc_path) and os.path.exists(label_path): +# process_patient(t1c_path, ctctc_path, label_path, output_t1c_dir, output_ctctc_dir, output_label_dir, patient_id) +# else: +# print(f" WARNING: Missing files for patient {patient_id}") + +# # 主程序 +# t1c_dir = '/home/onlylian/T1C_image_folder' +# ctctc_dir = '/home/onlylian/CT+CTC_image_folder' +# label_dir = '/home/onlylian/T1C_label_folder' # 使用 T1C 的 label +# output_t1c_dir = '/home/onlylian/resampled_T1C_image_2' +# output_ctctc_dir = '/home/onlylian/resampled_CT+CTC_image_2' +# output_label_dir = '/home/onlylian/resampled_label_2' + +# print("Starting image preprocessing") +# process_directory(t1c_dir, ctctc_dir, label_dir, output_t1c_dir, output_ctctc_dir, output_label_dir) + +# print("All processing completed.") + +import SimpleITK as sitk +import os +import numpy as np + +def standardize_origin(image, target_origin): + """將影像的原點標準化為目標原點""" + return sitk.Resample( + image, + image.GetSize(), + sitk.Transform(), + sitk.sitkLinear, + target_origin, + image.GetSpacing(), + image.GetDirection(), + 0, + image.GetPixelID() + ) + +def resample_image(image, reference_image, is_label=False): + """重採樣影像以匹配參考影像""" + resampler = sitk.ResampleImageFilter() + resampler.SetReferenceImage(reference_image) + resampler.SetTransform(sitk.Transform()) + resampler.SetOutputSpacing(reference_image.GetSpacing()) + resampler.SetSize(reference_image.GetSize()) + resampler.SetOutputDirection(reference_image.GetDirection()) + resampler.SetOutputOrigin(reference_image.GetOrigin()) + + if is_label: + resampler.SetInterpolator(sitk.sitkNearestNeighbor) + resampler.SetDefaultPixelValue(0) + else: + resampler.SetInterpolator(sitk.sitkLinear) + resampler.SetDefaultPixelValue(image.GetPixelIDValue()) + + return resampler.Execute(image) + +def process_patient(t1_image_path, label_path, output_t1_dir, output_label_dir, patient_id): + """處理單個病人的影像""" + print(f"\nProcessing patient: {patient_id}") + + # 讀取影像 + t1_image = sitk.ReadImage(t1_image_path) + label = sitk.ReadImage(label_path) + + print(f" Original T1 image size: {t1_image.GetSize()}, origin: {t1_image.GetOrigin()}") + print(f" Original label size: {label.GetSize()}, origin: {label.GetOrigin()}") + + # 使用 T1 影像作為參考 + reference_image = t1_image + + # 重採樣 label 以匹配 T1 影像 + resampled_label = resample_image(label, reference_image, is_label=True) + + print(f" Resampled label size: {resampled_label.GetSize()}, origin: {resampled_label.GetOrigin()}") + + # 保存處理後的影像和標籤 + t1_output_path = os.path.join(output_t1_dir, f"T1_{patient_id}.nii.gz") + label_output_path = os.path.join(output_label_dir, f"label_{patient_id}.nii.gz") + + sitk.WriteImage(t1_image, t1_output_path) + sitk.WriteImage(resampled_label, label_output_path) + + print(f" T1 image saved to: {t1_output_path}") + print(f" Resampled label saved to: {label_output_path}") + +def process_directory(t1_dir, label_dir, output_t1_dir, output_label_dir): + """處理整個目錄的影像""" + print("Starting directory processing") + + # 創建輸出目錄 + os.makedirs(output_t1_dir, exist_ok=True) + os.makedirs(output_label_dir, exist_ok=True) + + # 獲取所有病人 ID + patient_ids = set() + for filename in os.listdir(t1_dir): + if filename.startswith("T1_") and filename.endswith(".nii.gz"): + patient_id = filename[3:-7] # 去掉 "T1_" 和 ".nii.gz" + patient_ids.add(patient_id) + + total_patients = len(patient_ids) + processed_count = 0 + + for patient_id in patient_ids: + processed_count += 1 + print(f"\nProcessing patient {processed_count}/{total_patients}: {patient_id}") + + t1_path = os.path.join(t1_dir, f"T1_{patient_id}.nii.gz") + label_path = os.path.join(label_dir, f"T1_label_{patient_id}.nii.gz") + + if os.path.exists(t1_path) and os.path.exists(label_path): + try: + process_patient(t1_path, label_path, output_t1_dir, output_label_dir, patient_id) + except Exception as e: + print(f" ERROR processing patient {patient_id}: {str(e)}") + else: + print(f" WARNING: Missing files for patient {patient_id}") + if not os.path.exists(t1_path): + print(f" Missing T1 image: {t1_path}") + if not os.path.exists(label_path): + print(f" Missing label: {label_path}") + +# 主程序 +if __name__ == "__main__": + # 設定輸入輸出路徑 + t1_dir = '/mnt/1218/onlylian/T1_image_folder_test' + label_dir = '/mnt/1218/onlylian/T1_label_folder_test' + output_t1_dir = '/mnt/1218/onlylian/resampled_T1_image_test' + output_label_dir = '/mnt/1218/onlylian/resampled_T1_label_test' + + print("Starting image preprocessing") + + try: + process_directory(t1_dir, label_dir, output_t1_dir, output_label_dir) + print("\nAll processing completed successfully.") + except Exception as e: + print(f"\nERROR: Processing failed with error: {str(e)}") \ No newline at end of file diff --git a/test/final/nnunet_Dataset012_OAR_TV_preparation.py b/test/final/nnunet_Dataset012_OAR_TV_preparation.py new file mode 100644 index 0000000..336e368 --- /dev/null +++ b/test/final/nnunet_Dataset012_OAR_TV_preparation.py @@ -0,0 +1,119 @@ +import json +import os +import shutil +import glob +import re +import pandas as pd + +# 創建 dataset.json 內容 +dataset_json = { + "channel_names": { + "0": "T1C" + }, + "labels": { + "background": "0", + "Brainstem": "1", + "Right_Eye": "2", + "Left_Eye": "3", + "Optic_Chiasm": "4", + "Right_Optic_Nerve": "5", + "Left_Optic_Nerve": "6", + "TV": "7" + }, + "numTraining": 0, + "file_ending": ".nii.gz" +} + +# 定義目錄 +source_image_dir = "/home/onlylian/resampled_T1C_TV_image" +source_label_dir = "/home/onlylian/resampled_tv_label" +target_base_dir = "/home/onlylian/nnUNet/nnUNet_raw/Dataset012_OAR_TV" +imagesTr_dir = os.path.join(target_base_dir, "imagesTr") +labelsTr_dir = os.path.join(target_base_dir, "labelsTr") + +# 創建目錄 +os.makedirs(imagesTr_dir, exist_ok=True) +os.makedirs(labelsTr_dir, exist_ok=True) + +def extract_patient_id(filename): + match = re.search(r'(T1C|label)_([^.]+)\.nii\.gz', os.path.basename(filename)) + return match.group(2) if match else None + +# 獲取並排序原始圖像文件 +source_image_files = sorted(glob.glob(os.path.join(source_image_dir, "*.nii.gz"))) + +processed_files = 0 +patient_mapping = [] + +# 創建病歷號到文件名的映射 +label_files = glob.glob(os.path.join(source_label_dir, "*.nii.gz")) +label_map = {extract_patient_id(f): f for f in label_files} + +print(f"找到 {len(label_map)} 個標籤文件") +print(f"找到 {len(source_image_files)} 個圖像文件") + +# 處理所有資料 +for index, source_image_file in enumerate(source_image_files, start=1): + if index % 10 == 0: # 每處理10個文件顯示一次進度 + print(f"\n正在處理檔案 {index}/{len(source_image_files)}:") + + patient_id = extract_patient_id(source_image_file) + + if patient_id is None: + print(f"警告: 無法提取病歷號,跳過文件: {source_image_file}") + continue + + # 使用新的文件名格式 + new_filename = f"OAR_TV_{index:03d}" + + # 複製並重命名圖像文件 + target_image_file = os.path.join(imagesTr_dir, f"{new_filename}_0000.nii.gz") + + shutil.copyfile(source_image_file, target_image_file) + + # 處理標籤文件 + if patient_id in label_map: + source_label_file = label_map[patient_id] + target_label_file = os.path.join(labelsTr_dir, f"{new_filename}.nii.gz") + shutil.copyfile(source_label_file, target_label_file) + processed_files += 1 + + # 添加到映射列表 + patient_mapping.append({ + "編號": f"{index:03d}", + "病歷號": patient_id + }) + + # 輸出處理細節 + print(f"T1C: {source_image_file}") + print(f"複製圖像檔案: {source_image_file} 到 {target_image_file}") + print(f"複製標籤檔案: {source_label_file} 到 {target_label_file}") + print() # 空行分隔每筆記錄 + else: + print(f"警告: 未找到 {patient_id} 的標籤檔案。") + +print("\n所有資料處理完畢。") + +# 更新 dataset.json +dataset_json["numTraining"] = processed_files + +# 寫入 dataset.json 檔案 +dataset_json_path = os.path.join(target_base_dir, "dataset.json") +with open(dataset_json_path, 'w') as f: + json.dump(dataset_json, f, indent=4) +print(f"dataset.json 已寫入到 {dataset_json_path}") + +print(f"總共處理了 {processed_files} 個完整的資料集(圖像加標籤)") + +# 創建 Excel 文件 +df = pd.DataFrame(patient_mapping) +excel_path = os.path.join(target_base_dir, "patient_mapping.xlsx") +df.to_excel(excel_path, index=False) +print(f"病人映射表已保存到 {excel_path}") + +# 輸出處理摘要 +print("\n處理摘要:") +print(f"總共找到的圖像: {len(source_image_files)}") +print(f"總共找到的標籤文件: {len(label_map)}") +print(f"成功處理的完整資料集: {processed_files}") +print(f"跳過的文件數量: {len(source_image_files) - processed_files}") \ No newline at end of file diff --git a/test/final/nnunet_Dataset021_OAR_preparation.py b/test/final/nnunet_Dataset021_OAR_preparation.py new file mode 100644 index 0000000..9db8aeb --- /dev/null +++ b/test/final/nnunet_Dataset021_OAR_preparation.py @@ -0,0 +1,128 @@ +import json +import os +import shutil +import glob +import re +import pandas as pd + +# 創建 dataset.json 內容 +dataset_json = { + "channel_names": { + "0": "CT+CTC", + "1": "T1C" + }, + "labels": { + "background": "0", + "Brainstem": "1", + "Right_Eye": "2", + "Left_Eye": "3", + "Optic_Chiasm": "4", + "Right_Optic_Nerve": "5", + "Left_Optic_Nerve": "6" + }, + "numTraining": 0, + "file_ending": ".nii.gz" +} + +# 定義目錄 +source_image1_dir = "/home/onlylian/resampled_CT+CTC_image" +source_image2_dir = "/home/onlylian/resampled_T1C_image" +source_label_dir = "/home/onlylian/resampled_label" +target_base_dir = "/home/onlylian/nnUNet/nnUNet_raw/Dataset021_OAR" +imagesTr_dir = os.path.join(target_base_dir, "imagesTr") +labelsTr_dir = os.path.join(target_base_dir, "labelsTr") + +# 創建目錄 +os.makedirs(imagesTr_dir, exist_ok=True) +os.makedirs(labelsTr_dir, exist_ok=True) + +def extract_patient_id(filename): + match = re.search(r'(CT\+CTC|T1C|label)_([^.]+)\.nii\.gz', os.path.basename(filename)) + return match.group(2) if match else None + +# 獲取並排序原始圖像文件 +source_image1_files = sorted(glob.glob(os.path.join(source_image1_dir, "*.nii.gz"))) +source_image2_files = sorted(glob.glob(os.path.join(source_image2_dir, "*.nii.gz"))) + +processed_files = 0 +patient_mapping = [] + +# 創建病歷號到文件名的映射 +label_files = glob.glob(os.path.join(source_label_dir, "*.nii.gz")) +label_map = {extract_patient_id(f): f for f in label_files} + +print(f"找到 {len(label_map)} 個標籤文件") +print(f"找到 {len(source_image1_files)} 個 CT+CTC 圖像文件") +print(f"找到 {len(source_image2_files)} 個 T1C 圖像文件") + +# 處理所有資料 +for index, (source_image1_file, source_image2_file) in enumerate(zip(source_image1_files, source_image2_files), start=1): + if index % 10 == 0: # 每處理10個文件顯示一次進度 + print(f"\n正在處理檔案 {index}/{len(source_image1_files)}:") + + patient_id1 = extract_patient_id(source_image1_file) + patient_id2 = extract_patient_id(source_image2_file) + + if patient_id1 != patient_id2 or patient_id1 is None or patient_id2 is None: + print(f"警告: 病歷號不匹配或無法提取,跳過這對文件: {source_image1_file}, {source_image2_file}") + continue + + # 使用新的文件名格式 + new_filename = f"OAR_{index:03d}" + + # 複製並重命名圖像文件 + target_image1_file = os.path.join(imagesTr_dir, f"{new_filename}_0000.nii.gz") + target_image2_file = os.path.join(imagesTr_dir, f"{new_filename}_0001.nii.gz") + + shutil.copyfile(source_image1_file, target_image1_file) + shutil.copyfile(source_image2_file, target_image2_file) + + # 處理標籤文件 + if patient_id1 in label_map: + source_label_file = label_map[patient_id1] + target_label_file = os.path.join(labelsTr_dir, f"{new_filename}.nii.gz") + shutil.copyfile(source_label_file, target_label_file) + processed_files += 1 + + # 添加到映射列表 + patient_mapping.append({ + "編號": f"{index:03d}", + "病歷號": patient_id1 + }) + + # 輸出處理細節 + print(f"CT+CTC: {source_image1_file}") + print(f"T1C: {source_image2_file}") + print(f"複製圖像檔案: {source_image1_file} 到 {target_image1_file}") + print(f"複製圖像檔案: {source_image2_file} 到 {target_image2_file}") + print(f"複製標籤檔案: {source_label_file} 到 {target_label_file}") + print() # 空行分隔每筆記錄 + else: + print(f"警告: 未找到 {patient_id1} 的標籤檔案。") + +print("\n所有資料處理完畢。") + +# 更新 dataset.json +dataset_json["numTraining"] = processed_files + +# 寫入 dataset.json 檔案 +dataset_json_path = os.path.join(target_base_dir, "dataset.json") +with open(dataset_json_path, 'w') as f: + json.dump(dataset_json, f, indent=4) +print(f"dataset.json 已寫入到 {dataset_json_path}") + +print(f"總共處理了 {processed_files} 個完整的資料集(圖像對加標籤)") + +# 創建 Excel 文件 +df = pd.DataFrame(patient_mapping) +excel_path = os.path.join(target_base_dir, "patient_mapping.xlsx") +df.to_excel(excel_path, index=False) +print(f"病人映射表已保存到 {excel_path}") + +# 輸出處理摘要 +print("\n處理摘要:") +print(f"總共找到的 CT+CTC 圖像: {len(source_image1_files)}") +print(f"總共找到的 T1C 圖像: {len(source_image2_files)}") +print(f"總共找到的標籤文件: {len(label_map)}") +print(f"成功處理的完整資料集: {processed_files}") +print(f"跳過的文件對數量: {len(source_image1_files) - processed_files}") \ No newline at end of file diff --git a/test/final/nnunet_Dataset022_OAR_TV_preparation.py b/test/final/nnunet_Dataset022_OAR_TV_preparation.py new file mode 100644 index 0000000..270722f --- /dev/null +++ b/test/final/nnunet_Dataset022_OAR_TV_preparation.py @@ -0,0 +1,130 @@ +import json +import os +import shutil +import glob +import re +import pandas as pd + +# 創建 dataset.json 內容 +dataset_json = { + "channel_names": { + "0": "CT+CTC", + "1": "T1C" + }, + "labels": { + "background": "0", + "Brainstem": "1", + "Right_Eye": "2", + "Left_Eye": "3", + "Optic_Chiasm": "4", + "Right_Optic_Nerve": "5", + "Left_Optic_Nerve": "6", + "TV": "7" + }, + "numTraining": 0, + "file_ending": ".nii.gz" +} + +# 定義目錄 +source_image1_dir = "/home/onlylian/resampled_CT+CTC_TV_image" +source_image2_dir = "/home/onlylian/resampled_T1C_TV_image" +source_label_dir = "/home/onlylian/resampled_tv_label" +target_base_dir = "/home/onlylian/nnUNet/nnUNet_raw/Dataset022_OAR_TV" +imagesTr_dir = os.path.join(target_base_dir, "imagesTr") +labelsTr_dir = os.path.join(target_base_dir, "labelsTr") + +# 創建目錄 +os.makedirs(imagesTr_dir, exist_ok=True) +os.makedirs(labelsTr_dir, exist_ok=True) + +def extract_patient_id(filename): + match = re.search(r'(CT\+CTC|T1C|label)_([^.]+)\.nii\.gz', os.path.basename(filename)) + return match.group(2) if match else None + +# 獲取並排序原始圖像文件 +source_image1_files = sorted(glob.glob(os.path.join(source_image1_dir, "*.nii.gz"))) +source_image2_files = sorted(glob.glob(os.path.join(source_image2_dir, "*.nii.gz"))) + +processed_files = 0 +patient_mapping = [] + +# 創建病歷號到文件名的映射 +label_files = glob.glob(os.path.join(source_label_dir, "*.nii.gz")) +label_map = {extract_patient_id(f): f for f in label_files} + +print(f"找到 {len(label_map)} 個標籤文件") +print(f"找到 {len(source_image1_files)} 個 CT+CTC 圖像文件") +print(f"找到 {len(source_image2_files)} 個 T1C 圖像文件") + +# 處理所有資料 +for index, (source_image1_file, source_image2_file) in enumerate(zip(source_image1_files, source_image2_files), start=1): + if index % 10 == 0: # 每處理10個文件顯示一次進度 + print(f"\n正在處理檔案 {index}/{len(source_image1_files)}:") + + patient_id1 = extract_patient_id(source_image1_file) + patient_id2 = extract_patient_id(source_image2_file) + + if patient_id1 != patient_id2 or patient_id1 is None or patient_id2 is None: + print(f"警告: 病歷號不匹配或無法提取,跳過這對文件: {source_image1_file}, {source_image2_file}") + continue + + # 使用新的文件名格式 + new_filename = f"OAR_TV_{index:03d}" + + # 複製並重命名圖像文件 + target_image1_file = os.path.join(imagesTr_dir, f"{new_filename}_0000.nii.gz") + target_image2_file = os.path.join(imagesTr_dir, f"{new_filename}_0001.nii.gz") + + shutil.copyfile(source_image1_file, target_image1_file) + shutil.copyfile(source_image2_file, target_image2_file) + + # 處理標籤文件 + if patient_id1 in label_map: + source_label_file = label_map[patient_id1] + target_label_file = os.path.join(labelsTr_dir, f"{new_filename}.nii.gz") + shutil.copyfile(source_label_file, target_label_file) + processed_files += 1 + + # 添加到映射列表 + patient_mapping.append({ + "編號": f"{index:03d}", + "病歷號": patient_id1 + }) + + # 輸出處理細節 + print(f"CT+CTC: {source_image1_file}") + print(f"T1C: {source_image2_file}") + print(f"複製圖像檔案: {source_image1_file} 到 {target_image1_file}") + print(f"複製圖像檔案: {source_image2_file} 到 {target_image2_file}") + print(f"複製標籤檔案: {source_label_file} 到 {target_label_file}") + print() # 空行分隔每筆記錄 + else: + print(f"警告: 未找到 {patient_id1} 的標籤檔案。") + +print("\n所有資料處理完畢。") + +# 更新 dataset.json +dataset_json["numTraining"] = processed_files + +# 寫入 dataset.json 檔案 +dataset_json_path = os.path.join(target_base_dir, "dataset.json") +with open(dataset_json_path, 'w') as f: + json.dump(dataset_json, f, indent=4) +print(f"dataset.json 已寫入到 {dataset_json_path}") + +print(f"總共處理了 {processed_files} 個完整的資料集(圖像對加標籤)") + +# 創建 Excel 文件 +df = pd.DataFrame(patient_mapping) +excel_path = os.path.join(target_base_dir, "patient_mapping.xlsx") +df.to_excel(excel_path, index=False) +print(f"病人映射表已保存到 {excel_path}") + +# 輸出處理摘要 +print("\n處理摘要:") +print(f"總共找到的 CT+CTC 圖像: {len(source_image1_files)}") +print(f"總共找到的 T1C 圖像: {len(source_image2_files)}") +print(f"總共找到的標籤文件: {len(label_map)}") +print(f"成功處理的完整資料集: {processed_files}") +print(f"跳過的文件對數量: {len(source_image1_files) - processed_files}") + diff --git a/test/final/tv_t1c_ctctc_resampler.py b/test/final/tv_t1c_ctctc_resampler.py new file mode 100644 index 0000000..922f085 --- /dev/null +++ b/test/final/tv_t1c_ctctc_resampler.py @@ -0,0 +1,110 @@ +import SimpleITK as sitk +import os +import numpy as np + +def standardize_origin(image, target_origin): + """將影像的原點標準化為目標原點""" + return sitk.Resample( + image, + image.GetSize(), + sitk.Transform(), + sitk.sitkLinear, + target_origin, + image.GetSpacing(), + image.GetDirection(), + 0, + image.GetPixelID() + ) + +def resample_image(image, reference_image, is_label=False): + resampler = sitk.ResampleImageFilter() + resampler.SetReferenceImage(reference_image) + resampler.SetTransform(sitk.Transform()) + resampler.SetOutputSpacing(reference_image.GetSpacing()) + resampler.SetSize(reference_image.GetSize()) + resampler.SetOutputDirection(reference_image.GetDirection()) + resampler.SetOutputOrigin(reference_image.GetOrigin()) + + if is_label: + resampler.SetInterpolator(sitk.sitkNearestNeighbor) + resampler.SetDefaultPixelValue(0) + else: + resampler.SetInterpolator(sitk.sitkLinear) + resampler.SetDefaultPixelValue(image.GetPixelIDValue()) + + return resampler.Execute(image) + +def process_patient(t1c_image_path, ctctc_image_path, label_path, output_t1c_dir, output_ctctc_dir, output_label_dir, patient_id): + print(f"\nProcessing patient: {patient_id}") + + # 讀取影像 + t1c_image = sitk.ReadImage(t1c_image_path) + ctctc_image = sitk.ReadImage(ctctc_image_path) + label = sitk.ReadImage(label_path) + + print(f" Original T1C image size: {t1c_image.GetSize()}, origin: {t1c_image.GetOrigin()}") + print(f" Original CT+CTC image size: {ctctc_image.GetSize()}, origin: {ctctc_image.GetOrigin()}") + print(f" Original label size: {label.GetSize()}, origin: {label.GetOrigin()}") + + # 使用 T1C 影像作為參考 + reference_image = t1c_image + + # 重採樣 CT+CTC 影像以匹配 T1C 影像 + resampled_ctctc = resample_image(ctctc_image, reference_image) + + # 重採樣 label 以匹配 T1C 影像 + resampled_label = resample_image(label, reference_image, is_label=True) + + print(f" Resampled CT+CTC image size: {resampled_ctctc.GetSize()}, origin: {resampled_ctctc.GetOrigin()}") + print(f" Resampled label size: {resampled_label.GetSize()}, origin: {resampled_label.GetOrigin()}") + + # 保存重採樣後的影像和標籤 + t1c_output_path = os.path.join(output_t1c_dir, f"T1C_{patient_id}.nii.gz") + ctctc_output_path = os.path.join(output_ctctc_dir, f"CT+CTC_{patient_id}.nii.gz") + label_output_path = os.path.join(output_label_dir, f"label_{patient_id}.nii.gz") + + sitk.WriteImage(t1c_image, t1c_output_path) + sitk.WriteImage(resampled_ctctc, ctctc_output_path) + sitk.WriteImage(resampled_label, label_output_path) + + print(f" T1C image saved to: {t1c_output_path}") + print(f" Resampled CT+CTC image saved to: {ctctc_output_path}") + print(f" Resampled label saved to: {label_output_path}") + +def process_directory(t1c_dir, ctctc_dir, label_dir, output_t1c_dir, output_ctctc_dir, output_label_dir): + print("Starting directory processing") + os.makedirs(output_t1c_dir, exist_ok=True) + os.makedirs(output_ctctc_dir, exist_ok=True) + os.makedirs(output_label_dir, exist_ok=True) + + # 獲取所有病人 ID + patient_ids = set() + for filename in os.listdir(t1c_dir): + if filename.startswith("T1C_") and filename.endswith(".nii.gz"): + patient_id = filename[4:-7] # 去掉 "T1C_" 和 ".nii.gz" + patient_ids.add(patient_id) + + for patient_id in patient_ids: + t1c_path = os.path.join(t1c_dir, f"T1C_{patient_id}.nii.gz") + ctctc_path = os.path.join(ctctc_dir, f"CT+CTC_{patient_id}.nii.gz") + label_path = os.path.join(label_dir, f"T1C_label_{patient_id}.nii.gz") # 假設 label 文件名與 T1C 相同 + + if os.path.exists(t1c_path) and os.path.exists(ctctc_path) and os.path.exists(label_path): + process_patient(t1c_path, ctctc_path, label_path, output_t1c_dir, output_ctctc_dir, output_label_dir, patient_id) + else: + print(f" WARNING: Missing files for patient {patient_id}") + +# 主程序 +t1c_dir = '/home/onlylian/T1C_image_folder' +ctctc_dir = '/home/onlylian/CT+CTC_image_folder' +label_dir = '/home/onlylian/T1C_OAR_Target_label_folder' # 使用 T1C 的 label +output_t1c_dir = '/home/onlylian/resampled_T1C_TV_image_2' +output_ctctc_dir = '/home/onlylian/resampled_CT+CTC_TV_image_2' +output_label_dir = '/home/onlylian/resampled_tv_label' + +print("Starting image preprocessing") +process_directory(t1c_dir, ctctc_dir, label_dir, output_t1c_dir, output_ctctc_dir, output_label_dir) + +print("All processing completed.") + + diff --git a/test/process_T1C_for_nnUNet.py b/test/process_T1C_for_nnUNet.py new file mode 100644 index 0000000..35a4b91 --- /dev/null +++ b/test/process_T1C_for_nnUNet.py @@ -0,0 +1,147 @@ +import os +import shutil + +# 定义所有需要的路径 +final_image_folder = '/mnt/1218/onlylian/resampled_T1_image_test' +final_label_folder = '/mnt/1218/onlylian/resampled_label_test' +mapping_file = '/mnt/1218/onlylian/patient_mapping.txt' + +# nnUNet路径 +base_nnunet_path = '/home/onlylian/nnUNet/nnUNet_raw/Dataset888' +nnunet_image_folder = os.path.join(base_nnunet_path, 'imagesTs') +nnunet_label_folder = os.path.join(base_nnunet_path, 'labelsTs') + +# 打印初始状态 +print("\nChecking directories:") +print(f"Source image folder exists: {os.path.exists(final_image_folder)}") +print(f"Source label folder exists: {os.path.exists(final_label_folder)}") + +# 确保目录存在 +try: + os.makedirs(base_nnunet_path, exist_ok=True) + os.makedirs(nnunet_image_folder, exist_ok=True) + os.makedirs(nnunet_label_folder, exist_ok=True) + print(f"Successfully created/verified nnUNet directories") +except Exception as e: + print(f"Error creating directories: {e}") + +def get_patient_ids(folder_path, prefix='T1_'): + """從檔案名稱中提取病人ID""" + if not os.path.exists(folder_path): + print(f"WARNING: Folder does not exist: {folder_path}") + return set() + + patient_ids = set() + try: + for filename in os.listdir(folder_path): + if filename.endswith('.nii.gz'): + if filename.startswith(prefix): + # 對於T1影像: T1_XXXXX.nii.gz -> XXXXX + patient_id = filename[len(prefix):-7] + patient_ids.add(patient_id) + elif prefix == 'T1_' and filename.startswith('label_'): + # 對於標籤: label_XXXXX.nii.gz -> XXXXX + patient_id = filename[6:-7] + patient_ids.add(patient_id) + print(f"Found {len(patient_ids)} patients in {folder_path}") + except Exception as e: + print(f"Error reading directory {folder_path}: {e}") + return patient_ids + +def create_filtered_folder(image_folder, label_folder, output_image_folder, output_label_folder): + """过滤并重命名文件,同时创建映射文件""" + print("\nStarting file processing...") + + # 获取病人ID + print("\nGetting patient IDs...") + image_patients = get_patient_ids(image_folder, 'T1_') + label_patients = get_patient_ids(label_folder, 'label_') # 修改為正確的前綴 + + # 打印一些檔案範例以供驗證 + print("\nSample files in directories:") + print("Images directory:") + for f in sorted(os.listdir(image_folder))[:3]: + print(f" - {f}") + print("Labels directory:") + for f in sorted(os.listdir(label_folder))[:3]: + print(f" - {f}") + + common_patients = image_patients.intersection(label_patients) + print(f"\nFound {len(common_patients)} common patients") + + mapping_list = [] + processed_files = {"images": 0, "labels": 0} + + for index, patient_id in enumerate(sorted(common_patients), 1): + new_filename = f"T1_{index:03d}" + mapping_list.append(f"Original ID: {patient_id} -> New ID: {new_filename}") + + # 处理图像文件 + image_file = f'T1_{patient_id}.nii.gz' + src_image_path = os.path.join(image_folder, image_file) + dst_image_path = os.path.join(output_image_folder, f"{new_filename}_0000.nii.gz") + + # 处理标签文件 + label_file = f'label_{patient_id}.nii.gz' # 修改為正確的命名格式 + src_label_path = os.path.join(label_folder, label_file) + dst_label_path = os.path.join(output_label_folder, f"{new_filename}.nii.gz") + + # 複製檔案並記錄結果 + if os.path.exists(src_image_path): + try: + shutil.copy2(src_image_path, dst_image_path) + processed_files["images"] += 1 + print(f"Copied image: {os.path.basename(dst_image_path)}") + except Exception as e: + print(f"Error copying image {src_image_path}: {e}") + else: + print(f"Source image not found: {src_image_path}") + + if os.path.exists(src_label_path): + try: + shutil.copy2(src_label_path, dst_label_path) + processed_files["labels"] += 1 + print(f"Copied label: {os.path.basename(dst_label_path)}") + except Exception as e: + print(f"Error copying label {src_label_path}: {e}") + else: + print(f"Source label not found: {src_label_path}") + + # 写入映射文件 + try: + with open(mapping_file, 'w') as f: + f.write("Patient ID Mapping:\n") + f.write("=================\n\n") + for mapping in mapping_list: + f.write(f"{mapping}\n") + f.write(f"\nTotal number of patients: {len(mapping_list)}") + print(f"\nMapping file created: {mapping_file}") + except Exception as e: + print(f"Error writing mapping file: {e}") + + return len(common_patients), processed_files + +print("\nStarting file processing...") +common_count, processed_files = create_filtered_folder( + final_image_folder, final_label_folder, + nnunet_image_folder, nnunet_label_folder +) + +print(f"\nProcessing completed:") +print(f"- Total number of common patients: {common_count}") +print(f"- Files processed: {processed_files['images']} images, {processed_files['labels']} labels") +print(f"- Patient ID mapping has been saved to: {mapping_file}") + +# 验证最终结果 +print("\nFinal verification:") +if os.path.exists(nnunet_label_folder): + label_files = os.listdir(nnunet_label_folder) + print(f"Files in labels directory: {len(label_files)}") + if len(label_files) > 0: + print("Sample files:") + for f in sorted(label_files)[:5]: + print(f" - {f}") + else: + print("WARNING: Labels directory is empty!") +else: + print("WARNING: Labels directory does not exist!") \ No newline at end of file