11import stir
22import os , subprocess
3+ import logging
4+
5+ log = logging .getLogger ('recon' )
36import nibabel as nib
47import numpy as np
58from scipy .ndimage import gaussian_filter
69
710
8- def validate_ct (pred_ct_path , ct_face_path , hu_min_expected = - 1024 , hu_min_tolerance = 20 ):
11+ def validate_ct (pred_ct_path , ct_face_and_bed_path , hu_min_expected = - 1024 , hu_min_tolerance = 20 ):
912 """
1013 Validate the predicted CT against the ground-truth CT.
1114
@@ -17,15 +20,15 @@ def validate_ct(pred_ct_path, ct_face_path, hu_min_expected=-1024, hu_min_tolera
1720 ----------
1821 pred_ct_path : str
1922 Path to the predicted CT NIfTI file.
20- ct_face_path : str
23+ ct_face_and_bed_path : str
2124 Path to the ground-truth CT NIfTI file.
2225 hu_min_expected : float
2326 Expected minimum HU value (air/background), typically -1024.
2427 hu_min_tolerance : float
2528 How far the actual minimum may deviate from hu_min_expected before warning.
2629 """
2730 pred_img = nib .load (pred_ct_path )
28- gt_img = nib .load (ct_face_path )
31+ gt_img = nib .load (ct_face_and_bed_path )
2932
3033 if pred_img .shape != gt_img .shape :
3134 raise ValueError (
@@ -42,14 +45,14 @@ def validate_ct(pred_ct_path, ct_face_path, hu_min_expected=-1024, hu_min_tolera
4245 pred_data = pred_img .get_fdata (dtype = np .float32 )
4346 hu_min = pred_data .min ()
4447 if hu_min > hu_min_expected + hu_min_tolerance :
45- print (
46- f"WARNING: Predicted CT minimum HU is { hu_min :.1f} . "
48+ log . warning (
49+ f"Predicted CT minimum HU is { hu_min :.1f} . "
4750 f"Expected around { hu_min_expected } (air). "
4851 "The image may not be in correct HU units."
4952 )
5053
5154
52- def swap_face_from_gt (pred_ct_path , ct_face_path , face_mask_path , output_path = None ):
55+ def swap_face_from_gt (pred_ct_path , ct_face_and_bed_path , face_mask_path , output_path = None ):
5356 """
5457 Replace the face region of a predicted CT with the ground-truth CT face.
5558
@@ -60,7 +63,7 @@ def swap_face_from_gt(pred_ct_path, ct_face_path, face_mask_path, output_path=No
6063 ----------
6164 pred_ct_path : str
6265 Path to the predicted CT NIfTI file.
63- ct_face_path : str
66+ ct_face_and_bed_path : str
6467 Path to the ground-truth CT NIfTI file (ct.nii.gz).
6568 face_mask_path : str
6669 Path to the binary face mask NIfTI file.
@@ -73,7 +76,7 @@ def swap_face_from_gt(pred_ct_path, ct_face_path, face_mask_path, output_path=No
7376 CT with the face region swapped in from the ground truth.
7477 """
7578 pred_img = nib .load (pred_ct_path )
76- gt_img = nib .load (ct_face_path )
79+ gt_img = nib .load (ct_face_and_bed_path )
7780 mask_img = nib .load (face_mask_path )
7881
7982 pred_data = pred_img .get_fdata (dtype = np .float32 )
@@ -93,7 +96,7 @@ def swap_face_from_gt(pred_ct_path, ct_face_path, face_mask_path, output_path=No
9396
9497 if output_path is not None :
9598 result_img .to_filename (output_path )
96- print (f"Face-swapped CT saved to { output_path } " )
99+ log . debug (f"Face- and bed -swapped CT saved to { output_path } " )
97100
98101 return result_img
99102
@@ -121,8 +124,17 @@ def smooth_image(img, fwhm_mm=4.0):
121124def save_stir_to_nifti (stir_img , output_path ):
122125 stir .ITKOutputFileFormat ().write_to_file (output_path , stir_img )
123126
124- def calculate_acf (mumap_hv , reference_sinogram , output_hs ):
125- subprocess .run (['calculate_attenuation_coefficients' , '--ACF' , output_hs , mumap_hv , reference_sinogram ], check = True )
127+ def calculate_acf (mumap_hv , reference_sinogram , output_hs , forwardprojector_par ):
128+ with subprocess .Popen (
129+ ['stdbuf' , '-oL' , 'calculate_attenuation_coefficients' , '--ACF' , output_hs , mumap_hv , reference_sinogram , forwardprojector_par ],
130+ stdout = subprocess .PIPE , stderr = subprocess .STDOUT , text = True ,
131+ ) as proc :
132+ for line in proc .stdout :
133+ line = line .rstrip ()
134+ log .debug (line )
135+ proc .wait ()
136+ if proc .returncode != 0 :
137+ raise subprocess .CalledProcessError (proc .returncode , proc .args )
126138
127139
128140def mumap_to_stir (input_path , output_path , ring_spacing_mm = 3.29114 ):
@@ -153,7 +165,7 @@ def mumap_to_stir(input_path, output_path, ring_spacing_mm=3.29114):
153165 img_z .set_origin (stir .FloatCartesianCoordinate3D (snapped_z , 0.0 , 0.0 ))
154166
155167 stir .InterfileOutputFileFormat ().write_to_file (output_path , img_z )
156- print (f"z-origin snapped: { o2 .z ():.4f} -> { snapped_z :.4f} mm, plane_sep={ plane_sep :.5f} mm" )
168+ log . debug (f"z-origin snapped: { o2 .z ():.4f} -> { snapped_z :.4f} mm, plane_sep={ plane_sep :.5f} mm" )
157169
158170
159171# def convert_ct_to_acf(ct_path, reference_sinogram, output_hs, ring_spacing_mm=3.29114,fwhm_mm=4.0,kvp=120):
0 commit comments