開春版本

This commit is contained in:
Furen 2025-02-03 07:41:32 +08:00
parent 7cfd15c6c3
commit 8802d67cda

View file

@ -126,6 +126,9 @@ LABEL_MAPPING: Dict[int, str] = {
6: "Left_Optic_Nerve" 6: "Left_Optic_Nerve"
} }
# MAX_OAR = max(LABEL_MAPPING.keys())
max_existing_label = max(LABEL_MAPPING.keys())
# 設定日誌 # 設定日誌
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
@ -346,7 +349,7 @@ def process_multiple_tumors(image_array):
return result_img return result_img
# 為每個腫瘤生成唯一的標籤值,從最大現有標籤值開始 # 為每個腫瘤生成唯一的標籤值,從最大現有標籤值開始
max_existing_label = max(LABEL_MAPPING.keys()) # max_existing_label = max(LABEL_MAPPING.keys())
tumor_colors = generate_distinct_colors(num_tumors) tumor_colors = generate_distinct_colors(num_tumors)
# 更新標籤映射和顏色映射 # 更新標籤映射和顏色映射
@ -374,37 +377,49 @@ def create_structure_masks(image: sitk.Image) -> Dict[str, np.ndarray]:
return masks return masks
def register(ct_fixed, moving, out_moving): MODELS = (
'rigid',
'affine',
'joint',
)
with tempfile.TemporaryDirectory() as tmp: def register(ct_fixed, moving):
fixed = os.path.join(tmp, 'clipped.nii.gz')
out_dir = tempfile.mkdtemp(dir=Path(__file__).resolve().parent/'0')
fixed = os.path.join(out_dir, 'clipped.nii.gz')
ct = sf.load_volume(ct_fixed) ct = sf.load_volume(ct_fixed)
clipped = ct.clip(0, 80) clipped = ct.clip(0, 80)
clipped.save(fixed) clipped.save(fixed)
FREESURFER_HOME = str(Path(__file__).resolve().parent/'mri_synthmorph') FREESURFER_HOME = Path(__file__).resolve().parent/'mri_synthmorph'
my_env = os.environ.copy()
my_env["FREESURFER_HOME"] = str(FREESURFER_HOME)
mri_synthmorph = str(FREESURFER_HOME/'mri_synthmorph')
for m in MODELS:
transform_extension = 'lta' if m in ('affine','rigid') else 'nii.gz'
args = [ args = [
str(Path(__file__).resolve().parent/'mri_synthmorph/mri_synthmorph'), mri_synthmorph,
# 'register', '-m', m,
'-m', 'affine', '-o', os.path.join(out_dir, '%s.nii.gz'%m),
# '-m', 'rigid',
'-o', out_moving, '-O', os.path.join(out_dir, 'out_fixed-%s.nii.gz'%m),
'-t', os.path.join(out_dir, 'moving_to_fixed-%s.%s'% (m, transform_extension)),
'-T', os.path.join(out_dir, 'fixed_to_moving-%s.%s'% (m, transform_extension)),
'-g', '-g',
moving, moving,
fixed, fixed,
] ]
logger.info(f" 執行registration 指令:{' '.join(args)}")
result = subprocess.run(args, env=my_env)
my_env = os.environ.copy() return out_dir
my_env["FREESURFER_HOME"] = str(Path(__file__).resolve().parent/'mri_synthmorph')
# logger.info(f"執行推論指令:{' '.join(predict_cmd)}")
result = subprocess.run(
args,
env=my_env,
)
''' '''
Namespace(command='register', extent=256, fixed='0/clipped.nii.gz', gpu=True, header_only=False, hyper=0.5, init=None, inverse=None, mid_space=False, model='affine', moving='0/t1c.nii.gz', out_dir=None, out_fixed=None, out_moving='0/out-aff.nii.gz', steps=7, threads=None, trans='0/trans.lta', verbose=False, weights=None) Namespace(command='register', extent=256, fixed='0/clipped.nii.gz', gpu=True, header_only=False, hyper=0.5, init=None, inverse=None, mid_space=False, model='affine', moving='0/t1c.nii.gz', out_dir=None, out_fixed=None, out_moving='0/out-aff.nii.gz', steps=7, threads=None, trans='0/trans.lta', verbose=False, weights=None)
@ -455,8 +470,8 @@ def register_synthmorph(ct_fixed, moving, out_moving):
arg=argparse.Namespace(**default) arg=argparse.Namespace(**default)
FREESURFER_HOME = str(Path(__file__).resolve().parent/'mri_synthmorph') FREESURFER_HOME = str(Path(__file__).resolve().parent/'mri_synthmorph')
print(arg) # print(arg)
print(FREESURFER_HOME) # print(FREESURFER_HOME)
os.environ["FREESURFER_HOME"] = str(Path(__file__).resolve().parent/'mri_synthmorph') os.environ["FREESURFER_HOME"] = str(Path(__file__).resolve().parent/'mri_synthmorph')
registration.register(arg) registration.register(arg)
@ -478,10 +493,6 @@ def inference(DCM_CT, DCM_MR):
INPUT_DIR = os.path.join(ROOT_DIR, 'input') INPUT_DIR = os.path.join(ROOT_DIR, 'input')
OUTPUT_DIR = os.path.join(ROOT_DIR, 'output') OUTPUT_DIR = os.path.join(ROOT_DIR, 'output')
# 設定 RTSTRUCT 輸出路徑
head, tail = os.path.split(DCM_CT)
rtss_file = os.path.join(head, tail+'-rtss.dcm')
# 清理並建立工作目錄 # 清理並建立工作目錄
for dir_path in [NII_DIR, INPUT_DIR, OUTPUT_DIR]: for dir_path in [NII_DIR, INPUT_DIR, OUTPUT_DIR]:
shutil.rmtree(dir_path, ignore_errors=True) shutil.rmtree(dir_path, ignore_errors=True)
@ -520,25 +531,25 @@ def inference(DCM_CT, DCM_MR):
if not NII_MR: if not NII_MR:
raise FileNotFoundError(f"找不到 MR 的 NIfTI 檔案,目錄內容:{os.listdir(NII_DIR)}") raise FileNotFoundError(f"找不到 MR 的 NIfTI 檔案,目錄內容:{os.listdir(NII_DIR)}")
logger.info("Registration of %s and %s..."%(NII_CT, NII_MR))
out_dir = register(NII_CT, NII_MR)
# 準備輸入檔案 # 準備輸入檔案
basename = os.path.basename(NII_MR) basename = os.path.basename(NII_MR)
old = '_'+basename.split('_')[-1] old = '_'+basename.split('_')[-1]
input_ct = os.path.join(INPUT_DIR, basename.replace(old, '_0000.nii.gz')) for m in MODELS:
input_ct = os.path.join(INPUT_DIR, basename.replace(old, '_%s_0000.nii.gz'%m))
shutil.copy(NII_CT, input_ct) shutil.copy(NII_CT, input_ct)
input_mr = os.path.join(INPUT_DIR, basename.replace(old, '_0001.nii.gz')) input_mr = os.path.join(INPUT_DIR, basename.replace(old, '_%s_0001.nii.gz'%m))
shutil.copy(os.path.join(out_dir, '%s.nii.gz'%m), input_mr)
logger.info("Registration of %s and %s..."%(NII_CT, NII_MR)) # shutil.rmtree(out_dir)
register(NII_CT, NII_MR, input_mr)
output_file = os.path.join(OUTPUT_DIR, basename.replace(old, '.nii.gz'))
basename_ct = os.path.basename(NII_CT)
old_ct = '_'+basename_ct.split('_')[-1]
label_file = os.path.join(NII_DIR, basename_ct.replace(old_ct, '.label.nii.gz'))
# basename = os.path.basename(NII_MR) # basename = os.path.basename(NII_MR)
# old = '_'+basename.split('_')[-1] # old = '_'+basename.split('_')[-1]
@ -598,6 +609,16 @@ def inference(DCM_CT, DCM_MR):
logger.error(f"模型推論失敗:\n輸出:{e.output}\n錯誤:{e.stderr}") logger.error(f"模型推論失敗:\n輸出:{e.output}\n錯誤:{e.stderr}")
raise RuntimeError(f"模型推論失敗:{e.stderr}") raise RuntimeError(f"模型推論失敗:{e.stderr}")
rtss_combination = []
for m in MODELS:
basename_ct = os.path.basename(NII_CT)
old_ct = '_'+basename_ct.split('_')[-1]
label_file = os.path.join(NII_DIR, basename_ct.replace(old_ct, '_%s.label.nii.gz'%m))
output_file = os.path.join(OUTPUT_DIR, basename.replace(old, '_%s.nii.gz'%m))
if not os.path.exists(output_file): if not os.path.exists(output_file):
raise FileNotFoundError(f"找不到模型輸出檔案:{output_file}") raise FileNotFoundError(f"找不到模型輸出檔案:{output_file}")
@ -622,7 +643,7 @@ def inference(DCM_CT, DCM_MR):
logger.info(f"後處理結果已保存至:{processed_output}") logger.info(f"後處理結果已保存至:{processed_output}")
logger.info("開始執行影像配準...") logger.info("開始執行影像配準...")
# reg_transform(NII_CT, NII_MR, processed_output, label_file) # reg_transform(NII_CT, NII_MR, processed_output, label_file)
shutil.copy(processed_output, label_file) shutil.copy(processed_output, label_file)
except Exception as e: except Exception as e:
@ -664,7 +685,7 @@ def inference(DCM_CT, DCM_MR):
images = [] images = []
images.append(IntensityImage(image, Modality('CT'))) images.append(IntensityImage(image, Modality('CT')))
# images.append(IntensityImage(sitk.ReadImage(input_mr), Modality('MR'))) # images.append(IntensityImage(sitk.ReadImage(input_mr), Modality('MR')))
for k, v in LABEL_MAPPING.items(): for k, v in LABEL_MAPPING.items():
images.append(SegmentationImage(final_image==k, v)) images.append(SegmentationImage(final_image==k, v))
@ -684,7 +705,8 @@ def inference(DCM_CT, DCM_MR):
else: else:
conv_conf = ps_io.RTSSConverter2DConfiguration() conv_conf = ps_io.RTSSConverter2DConfiguration()
meta_data = ps_io.RTSSMetaData( meta_data = ps_io.RTSSMetaData(
series_description = '%s by onlylian'%type(conv_conf).__name__) series_description = '%s by %s'%(m, type(conv_conf).__name__)
)
converter = ps_io.SubjectToRTSSConverter( converter = ps_io.SubjectToRTSSConverter(
subject, subject,
@ -692,11 +714,14 @@ def inference(DCM_CT, DCM_MR):
reference_modality, reference_modality,
conv_conf, conv_conf,
meta_data, meta_data,
# colors = tuple(LABEL_COLORS.values()), # colors = tuple(LABEL_COLORS.values()),
) )
rtss = converter.convert() rtss = converter.convert()
# 設定 RTSTRUCT 輸出路徑
head, tail = os.path.split(DCM_CT)
rtss_file = os.path.join(head, tail+'_%s-rtss.dcm'%m)
# Write the DICOM-RTSS to a separate subject directory # Write the DICOM-RTSS to a separate subject directory
# and include the DICOM files crawled before # and include the DICOM files crawled before
@ -705,21 +730,11 @@ def inference(DCM_CT, DCM_MR):
output_dir_path = os.path.join(ROOT_DIR, 'pyradise') output_dir_path = os.path.join(ROOT_DIR, 'pyradise')
rtss_filename = os.path.basename(rtss_file) rtss_filename = os.path.basename(rtss_file)
rtss_combination = ((rtss_filename, rtss),)
print(output_dir_path)
print(rtss_combination)
# shutil.rmtree(output_dir_path, ignore_errors=True) rtss_combination.append((rtss_filename, rtss))
os.makedirs(output_dir_path, exist_ok=True)
writer = ps_io.DicomSeriesSubjectWriter()
writer.write(rtss_combination,
output_dir_path,
# subject.get_name(),
None,
dicom_series_info,)
shutil.copy(os.path.join(output_dir_path, rtss_filename), rtss_file) # rtss_combination = ((rtss_filename, rtss),)
# rtstruct = RTStructBuilder.create_new(dicom_series_path=DCM_CT) # rtstruct = RTStructBuilder.create_new(dicom_series_path=DCM_CT)
@ -752,12 +767,26 @@ def inference(DCM_CT, DCM_MR):
logger.error(f"RTSTRUCT 生成失敗:{str(e)}") logger.error(f"RTSTRUCT 生成失敗:{str(e)}")
raise RuntimeError("無法生成或儲存 RTSTRUCT") raise RuntimeError("無法生成或儲存 RTSTRUCT")
return rtss_file # print(output_dir_path)
# print(rtss_combination)
# shutil.rmtree(output_dir_path, ignore_errors=True)
os.makedirs(output_dir_path, exist_ok=True)
writer = ps_io.DicomSeriesSubjectWriter()
writer.write(rtss_combination,
output_dir_path,
# subject.get_name(),
None,
dicom_series_info,)
except Exception as e: except Exception as e:
logger.error(f"推論過程發生錯誤:{str(e)}", exc_info=True) logger.error(f"推論過程發生錯誤:{str(e)}", exc_info=True)
raise raise
return rtss_combination, output_dir_path
def SendDCM(fp): def SendDCM(fp):
"""傳送 DICOM 檔案到 PACS""" """傳送 DICOM 檔案到 PACS"""
logger.info(f"準備傳送 DICOM 檔案:{fp}") logger.info(f"準備傳送 DICOM 檔案:{fp}")
@ -803,6 +832,62 @@ def SendDCM(fp):
logger.error(f"DICOM 傳送過程發生錯誤:{str(e)}", exc_info=True) logger.error(f"DICOM 傳送過程發生錯誤:{str(e)}", exc_info=True)
raise raise
def SendDCMs(filelist):
"""傳送 DICOM 檔案到 PACS"""
logger.info(f"準備傳送 DICOM 檔案:{' '.join(filelist)}")
debug_logger()
dslist = []
for fp in filelist:
if not os.path.exists(fp):
raise FileNotFoundError(f"找不到 DICOM 檔案:{fp}")
try:
ds = dcmread(fp)
dslist.append(ds)
except Exception as e:
logger.error(f"讀取 DICOM 檔案失敗:{str(e)}")
raise RuntimeError("無法讀取 DICOM 檔案")
try:
ae = AE()
ae.ae_title = 'OUR_STORE_SCP'
ae.add_requested_context(RTStructureSetStorage)
logger.info("正在連接 PACS 伺服器...")
try:
assoc = ae.associate("172.16.40.36", 104, ae_title='N1000_STORAGE')
except Exception as e:
logger.error(f"PACS 連接失敗:{str(e)}")
raise RuntimeError("無法連接 PACS 伺服器")
if assoc.is_established:
try:
for ds in dslist:
status = assoc.send_c_store(ds)
if status:
logger.info(f'DICOM 傳送成功,狀態碼: 0x{status.Status:04x}')
else:
raise RuntimeError('傳送失敗:連接逾時或收到無效回應')
except Exception as e:
logger.error(f"DICOM 傳送失敗:{str(e)}")
raise
finally:
assoc.release()
else:
raise RuntimeError('傳送失敗:無法建立連接')
except Exception as e:
logger.error(f"DICOM 傳送過程發生錯誤:{str(e)}", exc_info=True)
raise
def main(): def main():
"""主程式進入點""" """主程式進入點"""
if len(sys.argv) < 3: if len(sys.argv) < 3:
@ -817,8 +902,18 @@ def main():
start_time = time.time() start_time = time.time()
try: try:
rtss_file = inference(sys.argv[1], sys.argv[2])
SendDCM(rtss_file) rtss_combination, output_dir_path = inference(sys.argv[1], sys.argv[2])
RTSS = []
for rtss_filename, rtss in rtss_combination:
rtss_file = os.path.join(output_dir_path, rtss_filename)
shutil.copy(rtss_file, Path(sys.argv[1]).resolve().parent)
RTSS.append(rtss_file)
# SendDCM(rtss_file)
SendDCMs(RTSS)
logger.info(f"處理完成,總耗時:{time.time() - start_time:.2f}") logger.info(f"處理完成,總耗時:{time.time() - start_time:.2f}")
except Exception as e: except Exception as e:
logger.error(f"處理過程發生錯誤:{str(e)}", exc_info=True) logger.error(f"處理過程發生錯誤:{str(e)}", exc_info=True)