Initial commit
This commit is contained in:
commit
c792f5eb0b
88 changed files with 15791 additions and 0 deletions
143
CT/Dice/test/compare_test_dice_scores.py
Normal file
143
CT/Dice/test/compare_test_dice_scores.py
Normal file
|
|
@ -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()
|
||||
306
CT/Dice/test/compute_test_dice.py
Normal file
306
CT/Dice/test/compute_test_dice.py
Normal file
|
|
@ -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之間的數字。")
|
||||
142
CT/Dice/validation/compare_validation_dice_scores.py
Normal file
142
CT/Dice/validation/compare_validation_dice_scores.py
Normal file
|
|
@ -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()
|
||||
622
CT/Dice/validation/compute_validation_dice.py
Normal file
622
CT/Dice/validation/compute_validation_dice.py
Normal file
|
|
@ -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之間的數字。")
|
||||
141
CT/visualize_medical_compare.py
Normal file
141
CT/visualize_medical_compare.py
Normal file
|
|
@ -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("請輸入有效的數字")
|
||||
287
CTC/Dice/test/compare_test_dice_scores.py
Normal file
287
CTC/Dice/test/compare_test_dice_scores.py
Normal file
|
|
@ -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()
|
||||
306
CTC/Dice/test/compute_test_dice.py
Normal file
306
CTC/Dice/test/compute_test_dice.py
Normal file
|
|
@ -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之間的數字。")
|
||||
142
CTC/Dice/validation/compare_validation_dice_scores.py
Normal file
142
CTC/Dice/validation/compare_validation_dice_scores.py
Normal file
|
|
@ -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()
|
||||
288
CTC/Dice/validation/compute_validation_dice.py
Normal file
288
CTC/Dice/validation/compute_validation_dice.py
Normal file
|
|
@ -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之間的數字。")
|
||||
180
CTC/visualize_medical_compare_chiasm.py
Normal file
180
CTC/visualize_medical_compare_chiasm.py
Normal file
|
|
@ -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)}")
|
||||
180
CTC/visualize_medical_compare_eyes.py
Normal file
180
CTC/visualize_medical_compare_eyes.py
Normal file
|
|
@ -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)}")
|
||||
170
CT_CTC_T1_OAR/Dice/test/compare_test_dice_scores.py
Normal file
170
CT_CTC_T1_OAR/Dice/test/compare_test_dice_scores.py
Normal file
|
|
@ -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()
|
||||
306
CT_CTC_T1_OAR/Dice/test/compute_test_dice.py
Normal file
306
CT_CTC_T1_OAR/Dice/test/compute_test_dice.py
Normal file
|
|
@ -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之間的數字。")
|
||||
286
CT_CTC_T1_OAR/Dice/validation/compare_validation_dice_scores.py
Normal file
286
CT_CTC_T1_OAR/Dice/validation/compare_validation_dice_scores.py
Normal file
|
|
@ -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()
|
||||
602
CT_CTC_T1_OAR/Dice/validation/compute_validation_dice.py
Normal file
602
CT_CTC_T1_OAR/Dice/validation/compute_validation_dice.py
Normal file
|
|
@ -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之間的數字。")
|
||||
161
CT_CTC_T1_OAR/Left_optic_nerve.py
Normal file
161
CT_CTC_T1_OAR/Left_optic_nerve.py
Normal file
|
|
@ -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)}")
|
||||
|
||||
|
||||
165
CT_CTC_T1_OAR/chiasm.py
Normal file
165
CT_CTC_T1_OAR/chiasm.py
Normal file
|
|
@ -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)}")
|
||||
|
||||
|
||||
144
CT_CTC_T1_OAR_TV/Dice/test/compare_test_dice_scores.py
Normal file
144
CT_CTC_T1_OAR_TV/Dice/test/compare_test_dice_scores.py
Normal file
|
|
@ -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()
|
||||
286
CT_CTC_T1_OAR_TV/Dice/test/compute_test_dice.py
Normal file
286
CT_CTC_T1_OAR_TV/Dice/test/compute_test_dice.py
Normal file
|
|
@ -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.")
|
||||
|
|
@ -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()
|
||||
325
CT_CTC_T1_OAR_TV/Dice/validation/compute_validation_dice.py
Normal file
325
CT_CTC_T1_OAR_TV/Dice/validation/compute_validation_dice.py
Normal file
|
|
@ -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")
|
||||
151
CT_CTC_T1_OAR_TV/visualize_medical_compare.py
Normal file
151
CT_CTC_T1_OAR_TV/visualize_medical_compare.py
Normal file
|
|
@ -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)}")
|
||||
287
FLAIR/Dice/test/compare_test_dice_scores.py
Normal file
287
FLAIR/Dice/test/compare_test_dice_scores.py
Normal file
|
|
@ -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()
|
||||
614
FLAIR/Dice/test/compute_test_dice.py
Normal file
614
FLAIR/Dice/test/compute_test_dice.py
Normal file
|
|
@ -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之間的數字。")
|
||||
|
||||
142
FLAIR/Dice/validation/compare_validation_dice_scores.py
Normal file
142
FLAIR/Dice/validation/compare_validation_dice_scores.py
Normal file
|
|
@ -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()
|
||||
288
FLAIR/Dice/validation/compute_validation_dice.py
Normal file
288
FLAIR/Dice/validation/compute_validation_dice.py
Normal file
|
|
@ -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之間的數字。")
|
||||
143
T1/Dice/test/compare_test_dice_scores.py
Normal file
143
T1/Dice/test/compare_test_dice_scores.py
Normal file
|
|
@ -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()
|
||||
282
T1/Dice/test/compute_test_dice.py
Normal file
282
T1/Dice/test/compute_test_dice.py
Normal file
|
|
@ -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之間的數字。")
|
||||
142
T1/Dice/validation/compare_validation_dice_scores.py
Normal file
142
T1/Dice/validation/compare_validation_dice_scores.py
Normal file
|
|
@ -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()
|
||||
288
T1/Dice/validation/compute_validation_dice.py
Normal file
288
T1/Dice/validation/compute_validation_dice.py
Normal file
|
|
@ -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.")
|
||||
331
T1/visualize_medical_compare.py
Normal file
331
T1/visualize_medical_compare.py
Normal file
|
|
@ -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)}")
|
||||
143
T1C/Dice/test/compare_test_dice_scores.py
Normal file
143
T1C/Dice/test/compare_test_dice_scores.py
Normal file
|
|
@ -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()
|
||||
282
T1C/Dice/test/compute_test_dice.py
Normal file
282
T1C/Dice/test/compute_test_dice.py
Normal file
|
|
@ -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之間的數字。")
|
||||
142
T1C/Dice/validation/compare_validation_dice_scores.py
Normal file
142
T1C/Dice/validation/compare_validation_dice_scores.py
Normal file
|
|
@ -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()
|
||||
288
T1C/Dice/validation/compute_validation_dice.py
Normal file
288
T1C/Dice/validation/compute_validation_dice.py
Normal file
|
|
@ -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之間的數字。")
|
||||
149
T1C/low_chiasm.py
Normal file
149
T1C/low_chiasm.py
Normal file
|
|
@ -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)}")
|
||||
303
T1C/visualize_medical_compare.py
Normal file
303
T1C/visualize_medical_compare.py
Normal file
|
|
@ -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()
|
||||
144
T1C_OAR_TV/Dice/test/compare_test_dice_scores.py
Normal file
144
T1C_OAR_TV/Dice/test/compare_test_dice_scores.py
Normal file
|
|
@ -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()
|
||||
318
T1C_OAR_TV/Dice/test/compute_test_dice.py
Normal file
318
T1C_OAR_TV/Dice/test/compute_test_dice.py
Normal file
|
|
@ -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))
|
||||
111
T1C_OAR_TV/Dice/validation/compare_validation_dice_scores.py
Normal file
111
T1C_OAR_TV/Dice/validation/compare_validation_dice_scores.py
Normal file
|
|
@ -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()
|
||||
325
T1C_OAR_TV/Dice/validation/compute_validation_dice.py
Normal file
325
T1C_OAR_TV/Dice/validation/compute_validation_dice.py
Normal file
|
|
@ -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")
|
||||
151
T1C_OAR_TV/lpre_ow_chiasm.py
Normal file
151
T1C_OAR_TV/lpre_ow_chiasm.py
Normal file
|
|
@ -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)}")
|
||||
496
T1C_OAR_TV/visualize_medical_compare.py
Normal file
496
T1C_OAR_TV/visualize_medical_compare.py
Normal file
|
|
@ -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)}")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
143
T2/Dice/test/compare_test_dice_scores.py
Normal file
143
T2/Dice/test/compare_test_dice_scores.py
Normal file
|
|
@ -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()
|
||||
306
T2/Dice/test/compute_test_dice.py
Normal file
306
T2/Dice/test/compute_test_dice.py
Normal file
|
|
@ -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之間的數字。")
|
||||
142
T2/Dice/validation/compare_validation_dice_scores.py
Normal file
142
T2/Dice/validation/compare_validation_dice_scores.py
Normal file
|
|
@ -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()
|
||||
300
T2/Dice/validation/compute_validation_dice.py
Normal file
300
T2/Dice/validation/compute_validation_dice.py
Normal file
|
|
@ -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之間的數字。")
|
||||
151
T2/visualize_medical_compare.py
Normal file
151
T2/visualize_medical_compare.py
Normal file
|
|
@ -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)}")
|
||||
|
||||
112
preprocessing/Image_files/copy_and_classify_modalities.py
Normal file
112
preprocessing/Image_files/copy_and_classify_modalities.py
Normal file
|
|
@ -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()
|
||||
47
preprocessing/Image_files/merge_H4_N6_directories.py
Normal file
47
preprocessing/Image_files/merge_H4_N6_directories.py
Normal file
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
52
preprocessing/Image_files/multi_modality_image_copy.py
Normal file
52
preprocessing/Image_files/multi_modality_image_copy.py
Normal file
|
|
@ -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')
|
||||
51
preprocessing/Image_files/patient_modalities_files.py
Normal file
51
preprocessing/Image_files/patient_modalities_files.py
Normal file
|
|
@ -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}")
|
||||
|
||||
|
||||
46
preprocessing/OAR_Files/copy_patient_OAR_files.py
Normal file
46
preprocessing/OAR_Files/copy_patient_OAR_files.py
Normal file
|
|
@ -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}")
|
||||
47
preprocessing/OAR_Files/merge_G4_M6_directories.py
Normal file
47
preprocessing/OAR_Files/merge_G4_M6_directories.py
Normal file
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
46
preprocessing/OAR_Files/merge_patient_folders.py
Normal file
46
preprocessing/OAR_Files/merge_patient_folders.py
Normal file
|
|
@ -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!")
|
||||
89
preprocessing/OAR_Files/multi_organ_label_combiner.py
Normal file
89
preprocessing/OAR_Files/multi_organ_label_combiner.py
Normal file
|
|
@ -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.")
|
||||
138
preprocessing/OAR_Files/organ_file_processor.py
Normal file
138
preprocessing/OAR_Files/organ_file_processor.py
Normal file
|
|
@ -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)
|
||||
|
|
@ -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}")
|
||||
47
preprocessing/OAR_Target_Files/merge_G4_M6_directories.py
Normal file
47
preprocessing/OAR_Target_Files/merge_G4_M6_directories.py
Normal file
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
46
preprocessing/OAR_Target_Files/merge_tv_patient_folders.py
Normal file
46
preprocessing/OAR_Target_Files/merge_tv_patient_folders.py
Normal file
|
|
@ -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!")
|
||||
156
preprocessing/OAR_Target_Files/organ_tv_file_processor.py
Normal file
156
preprocessing/OAR_Target_Files/organ_tv_file_processor.py
Normal file
|
|
@ -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)
|
||||
107
preprocessing/OAR_Target_Files/tv_and_oar_label_combiner.py
Normal file
107
preprocessing/OAR_Target_Files/tv_and_oar_label_combiner.py
Normal file
|
|
@ -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")
|
||||
108
preprocessing/final/advanced_multimodal_image_resampler.py
Normal file
108
preprocessing/final/advanced_multimodal_image_resampler.py
Normal file
|
|
@ -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.")
|
||||
119
preprocessing/final/nnunet_Dataset012_OAR_TV_preparation.py
Normal file
119
preprocessing/final/nnunet_Dataset012_OAR_TV_preparation.py
Normal file
|
|
@ -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}")
|
||||
128
preprocessing/final/nnunet_Dataset021_OAR_preparation.py
Normal file
128
preprocessing/final/nnunet_Dataset021_OAR_preparation.py
Normal file
|
|
@ -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}")
|
||||
130
preprocessing/final/nnunet_Dataset022_OAR_TV_preparation.py
Normal file
130
preprocessing/final/nnunet_Dataset022_OAR_TV_preparation.py
Normal file
|
|
@ -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}")
|
||||
|
||||
110
preprocessing/final/tv_t1c_ctctc_resampler.py
Normal file
110
preprocessing/final/tv_t1c_ctctc_resampler.py
Normal file
|
|
@ -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.")
|
||||
|
||||
|
||||
147
preprocessing/rename_files.py
Normal file
147
preprocessing/rename_files.py
Normal file
|
|
@ -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!")
|
||||
112
test/Image_files/copy_and_classify_modalities.py
Normal file
112
test/Image_files/copy_and_classify_modalities.py
Normal file
|
|
@ -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()
|
||||
47
test/Image_files/merge_H4_N6_directories.py
Normal file
47
test/Image_files/merge_H4_N6_directories.py
Normal file
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
96
test/Image_files/multi_modality_image_copy.py
Normal file
96
test/Image_files/multi_modality_image_copy.py
Normal file
|
|
@ -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')
|
||||
165
test/Image_files/patient_modalities_files.py
Normal file
165
test/Image_files/patient_modalities_files.py
Normal file
|
|
@ -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}")
|
||||
|
||||
46
test/OAR_Files/copy_patient_OAR_files.py
Normal file
46
test/OAR_Files/copy_patient_OAR_files.py
Normal file
|
|
@ -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}")
|
||||
47
test/OAR_Files/merge_G4_M6_directories.py
Normal file
47
test/OAR_Files/merge_G4_M6_directories.py
Normal file
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
46
test/OAR_Files/merge_patient_folders.py
Normal file
46
test/OAR_Files/merge_patient_folders.py
Normal file
|
|
@ -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!")
|
||||
172
test/OAR_Files/multi_organ_label_combiner.py
Normal file
172
test/OAR_Files/multi_organ_label_combiner.py
Normal file
|
|
@ -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.")
|
||||
138
test/OAR_Files/organ_file_processor.py
Normal file
138
test/OAR_Files/organ_file_processor.py
Normal file
|
|
@ -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)
|
||||
62
test/OAR_Target_Files/copy_patient_OAR_Target_files.py
Normal file
62
test/OAR_Target_Files/copy_patient_OAR_Target_files.py
Normal file
|
|
@ -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}")
|
||||
47
test/OAR_Target_Files/merge_G4_M6_directories.py
Normal file
47
test/OAR_Target_Files/merge_G4_M6_directories.py
Normal file
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
46
test/OAR_Target_Files/merge_tv_patient_folders.py
Normal file
46
test/OAR_Target_Files/merge_tv_patient_folders.py
Normal file
|
|
@ -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!")
|
||||
156
test/OAR_Target_Files/organ_tv_file_processor.py
Normal file
156
test/OAR_Target_Files/organ_tv_file_processor.py
Normal file
|
|
@ -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)
|
||||
107
test/OAR_Target_Files/tv_and_oar_label_combiner.py
Normal file
107
test/OAR_Target_Files/tv_and_oar_label_combiner.py
Normal file
|
|
@ -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")
|
||||
227
test/final/advanced_multimodal_image_resampler.py
Normal file
227
test/final/advanced_multimodal_image_resampler.py
Normal file
|
|
@ -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)}")
|
||||
119
test/final/nnunet_Dataset012_OAR_TV_preparation.py
Normal file
119
test/final/nnunet_Dataset012_OAR_TV_preparation.py
Normal file
|
|
@ -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}")
|
||||
128
test/final/nnunet_Dataset021_OAR_preparation.py
Normal file
128
test/final/nnunet_Dataset021_OAR_preparation.py
Normal file
|
|
@ -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}")
|
||||
130
test/final/nnunet_Dataset022_OAR_TV_preparation.py
Normal file
130
test/final/nnunet_Dataset022_OAR_TV_preparation.py
Normal file
|
|
@ -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}")
|
||||
|
||||
110
test/final/tv_t1c_ctctc_resampler.py
Normal file
110
test/final/tv_t1c_ctctc_resampler.py
Normal file
|
|
@ -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.")
|
||||
|
||||
|
||||
147
test/process_T1C_for_nnUNet.py
Normal file
147
test/process_T1C_for_nnUNet.py
Normal file
|
|
@ -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!")
|
||||
Loading…
Reference in a new issue