Skip to content

Commit 5d38a2a

Browse files
refactor v1.0.0
1 parent e75b527 commit 5d38a2a

6 files changed

Lines changed: 261 additions & 101 deletions

File tree

src/recon/acf_forwardprojector.par

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Forward Projector parameters:=
2+
type := Matrix
3+
Forward projector Using Matrix Parameters :=
4+
Matrix type := Ray Tracing
5+
Ray tracing matrix parameters :=
6+
number of rays in tangential direction to trace for each bin := 5
7+
End Ray tracing matrix parameters :=
8+
End Forward Projector Using Matrix Parameters :=
9+
End:=

src/recon/ct_to_acf.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import stir
22
import os, subprocess
3+
import logging
4+
5+
log = logging.getLogger('recon')
36
import nibabel as nib
47
import numpy as np
58
from 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):
121124
def 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

128140
def 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

Comments
 (0)