Skip to content

Commit f6bc81c

Browse files
authored
Merge pull request #145 from StanfordMIMI/fda_bmd
Incorporated the FDA BunkerHill version of the https://github.com/akshaysc/comp2comp_bmd into the main Comp2Comp library
2 parents da8e7de + 8e768a9 commit f6bc81c

16 files changed

Lines changed: 2756 additions & 344 deletions

bin/C2C

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,61 @@ from comp2comp.aortic_calcium import (
1313
from comp2comp.contrast_phase.contrast_phase import ContrastPhaseDetection
1414
from comp2comp.hip import hip
1515
from comp2comp.inference_pipeline import InferencePipeline
16-
from comp2comp.io import io
16+
from comp2comp.io import fda_io, io
1717
from comp2comp.liver_spleen_pancreas import (
1818
liver_spleen_pancreas,
1919
liver_spleen_pancreas_visualization,
2020
)
2121
from comp2comp.muscle_adipose_tissue import (
22+
fda_muscle_adipose_tissue,
2223
muscle_adipose_tissue,
2324
muscle_adipose_tissue_visualization,
2425
)
25-
from comp2comp.spine import spine
26+
from comp2comp.spine import fda_spine, spine
2627
from comp2comp.utils import orientation
2728
from comp2comp.utils.process import process_3d
2829

2930
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
3031

32+
# FDA approved BunkeHill BMD algorithm
33+
def FdaBmdPipelineBuilder(path, args):
34+
pipeline = InferencePipeline(
35+
[
36+
FdaSpinePipelineBuilder(path, args),
37+
fda_spine.SpineFindDicoms(),
38+
FdaMuscleAdiposeTissuePipelineBuilder(args),
39+
]
40+
)
41+
return pipeline
42+
43+
def FdaSpinePipelineBuilder(path, args):
44+
pipeline = InferencePipeline(
45+
[
46+
fda_io.DicomToNifti(path),
47+
fda_spine.SpineSegmentation(args.spine_model, save=True),
48+
orientation.ToCanonical(),
49+
fda_spine.SpineComputeROIs(args.spine_model),
50+
fda_spine.SpineMetricsSaver(),
51+
]
52+
)
53+
return pipeline
54+
55+
def FdaMuscleAdiposeTissuePipelineBuilder(args):
56+
pipeline = InferencePipeline(
57+
[
58+
fda_muscle_adipose_tissue.MuscleAdiposeTissueSegmentation(
59+
16, args.muscle_fat_model
60+
),
61+
fda_muscle_adipose_tissue.MuscleAdiposeTissuePostProcessing(),
62+
fda_muscle_adipose_tissue.MuscleAdiposeTissueComputeMetrics(),
63+
# fda_muscle_adipose_tissue_visualization.MuscleAdiposeTissueVisualizer(),
64+
fda_muscle_adipose_tissue.MuscleAdiposeTissueH5Saver(),
65+
fda_muscle_adipose_tissue.MuscleAdiposeTissueMetricsSaver(),
66+
]
67+
)
68+
return pipeline
69+
70+
3171
### AAA Pipeline
3272
def AAAPipelineBuilder(path, args):
3373
pipeline = InferencePipeline(
@@ -197,6 +237,17 @@ def argument_parser():
197237
"--spine_model", default="stanford_spine_v0.0.1", type=str
198238
)
199239

240+
# FDA approved BMD algorithm (Spine + muscle + fat)
241+
fda_spine_muscle_adipose_tissue_parser = subparsers.add_parser(
242+
"fda_bmd", parents=[base_parser]
243+
)
244+
fda_spine_muscle_adipose_tissue_parser.add_argument(
245+
"--muscle_fat_model", default="stanford_v0.0.2", type=str
246+
)
247+
fda_spine_muscle_adipose_tissue_parser.add_argument(
248+
"--spine_model", default="ts_spine", type=str
249+
)
250+
200251
# Liver spleen pancreas
201252
liver_spleen_pancreas = subparsers.add_parser(
202253
"liver_spleen_pancreas", parents=[base_parser]
@@ -266,7 +317,9 @@ def argument_parser():
266317

267318
def main():
268319
args = argument_parser().parse_args()
269-
if args.pipeline == "spine_muscle_adipose_tissue":
320+
if args.pipeline == "fda_bmd":
321+
process_3d(args, FdaBmdPipelineBuilder)
322+
elif args.pipeline == "spine_muscle_adipose_tissue":
270323
process_3d(args, SpineMuscleAdiposeTissuePipelineBuilder)
271324
elif args.pipeline == "spine":
272325
process_3d(args, SpinePipelineBuilder)
@@ -283,7 +336,8 @@ def main():
283336
elif args.pipeline == "all":
284337
process_3d(args, AllPipelineBuilder)
285338
else:
286-
raise AssertionError("{} command not supported".format(args.action))
339+
# raise AssertionError("{} command not supported".format(args.action))
340+
raise AssertionError("{} command not supported".format(args.pipeline))
287341

288342

289343
if __name__ == "__main__":

comp2comp/aaa/aaa.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import math
22
import operator
33
import os
4-
import traceback
4+
import traceback
55
import zipfile
66
from pathlib import Path
77
from time import time
@@ -397,10 +397,10 @@ def __call__(self, inference_pipeline):
397397
)
398398
try:
399399
clip.write_videofile(output_dir_summary + "aaa.mp4")
400-
except Exception as e:
401-
print('Error encountered in video generation:\n')
400+
except Exception:
401+
print("Error encountered in video generation:\n")
402402
traceback.print_exc()
403-
403+
404404
return {}
405405

406406

comp2comp/aortic_calcium/aortic_calcium.py

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
import matplotlib.pyplot as plt
1414
import numpy as np
15+
import pydicom
1516
from scipy import ndimage
16-
import pydicom
1717

1818
# from totalsegmentator.libs import (
1919
# download_pretrained_weights,
@@ -34,9 +34,9 @@ def __init__(self):
3434

3535
def __call__(self, inference_pipeline):
3636
# check if kernels are allowed if agatston is used
37-
if inference_pipeline.args.threshold == 'agatston':
37+
if inference_pipeline.args.threshold == "agatston":
3838
self.reconKernelChecker(inference_pipeline.dcm)
39-
39+
4040
# inference_pipeline.dicom_series_path = self.input_path
4141
self.output_dir = inference_pipeline.output_dir
4242
self.output_dir_segmentations = os.path.join(self.output_dir, "segmentations/")
@@ -115,27 +115,63 @@ def reconKernelChecker(self, dcm):
115115
ge_kernels = ["standard", "md stnd"]
116116
philips_kernels = ["a", "b", "c", "sa", "sb"]
117117
canon_kernels = ["fc08", "fc18"]
118-
siemens_kernels = ["b20s", "b20f", "b30f", "b31s", "b31f", "br34f", "b35f", "bf37f", "br38f", "b41f",
119-
"qr40", "qr40d", "br36f", "br40", "b40f", "br40d", "i30f", "i31f", "i26f", "i31s",
120-
"i40f", "b30s", "br36d", "bf39f", "b41s", "br40f"]
118+
siemens_kernels = [
119+
"b20s",
120+
"b20f",
121+
"b30f",
122+
"b31s",
123+
"b31f",
124+
"br34f",
125+
"b35f",
126+
"bf37f",
127+
"br38f",
128+
"b41f",
129+
"qr40",
130+
"qr40d",
131+
"br36f",
132+
"br40",
133+
"b40f",
134+
"br40d",
135+
"i30f",
136+
"i31f",
137+
"i26f",
138+
"i31s",
139+
"i40f",
140+
"b30s",
141+
"br36d",
142+
"bf39f",
143+
"b41s",
144+
"br40f",
145+
]
121146
toshiba_kernels = ["fc01", "fc02", "fc07", "fc08", "fc13", "fc18"]
122147

123-
all_kernels = ge_kernels+philips_kernels+canon_kernels+siemens_kernels+toshiba_kernels
124-
125-
conv_kernel_raw = dcm['ConvolutionKernel'].value
126-
148+
all_kernels = (
149+
ge_kernels
150+
+ philips_kernels
151+
+ canon_kernels
152+
+ siemens_kernels
153+
+ toshiba_kernels
154+
)
155+
156+
conv_kernel_raw = dcm["ConvolutionKernel"].value
157+
127158
if isinstance(conv_kernel_raw, pydicom.multival.MultiValue):
128159
conv_kernel = conv_kernel_raw[0].lower()
129-
recon_kernel_extra = str(conv_kernel_raw)
160+
str(conv_kernel_raw)
130161
else:
131162
conv_kernel = conv_kernel_raw.lower()
132-
recon_kernel_extra = 'n/a'
133-
163+
134164
if conv_kernel in all_kernels:
135165
return True
136-
else:
137-
raise ValueError('Reconstruction kernel not allowed, found: ' + conv_kernel +'\n'
138-
+ 'Allowed kernels are: ' + str(all_kernels))
166+
else:
167+
raise ValueError(
168+
"Reconstruction kernel not allowed, found: "
169+
+ conv_kernel
170+
+ "\n"
171+
+ "Allowed kernels are: "
172+
+ str(all_kernels)
173+
)
174+
139175

140176
class AorticCalciumSegmentation(InferenceClass):
141177
"""Segmentaiton of aortic calcium"""
@@ -168,10 +204,10 @@ def __init__(self):
168204
47: "vertebrae_C4",
169205
48: "vertebrae_C3",
170206
49: "vertebrae_C2",
171-
50: "vertebrae_C1"}
172-
173-
self.vertebrae_name = {v: k for k, v in self.vertebrae_num.items()}
207+
50: "vertebrae_C1",
208+
}
174209

210+
self.vertebrae_name = {v: k for k, v in self.vertebrae_num.items()}
175211

176212
def __call__(self, inference_pipeline):
177213

@@ -194,15 +230,21 @@ def __call__(self, inference_pipeline):
194230
os.makedirs(os.path.join(self.output_dir, "metrics/"))
195231

196232
inference_pipeline.ct = inference_pipeline.medical_volume.get_fdata()
197-
inference_pipeline.aorta_mask = (inference_pipeline.segmentation.get_fdata().round().astype(np.int8) == 52)
198-
inference_pipeline.spine_mask = inference_pipeline.spine_segmentation.get_fdata().round().astype(np.uint8)
199-
233+
inference_pipeline.aorta_mask = (
234+
inference_pipeline.segmentation.get_fdata().round().astype(np.int8) == 52
235+
)
236+
inference_pipeline.spine_mask = (
237+
inference_pipeline.spine_segmentation.get_fdata().round().astype(np.uint8)
238+
)
239+
200240
# convert to the index of TotalSegmentator
201-
if inference_pipeline.spine_model_name == 'stanford_spine_v0.0.1':
241+
if inference_pipeline.spine_model_name == "stanford_spine_v0.0.1":
202242
tmp_mask = inference_pipeline.spine_mask > 0
203-
inference_pipeline.spine_mask[tmp_mask] = inference_pipeline.spine_mask[tmp_mask] + 11
243+
inference_pipeline.spine_mask[tmp_mask] = (
244+
inference_pipeline.spine_mask[tmp_mask] + 11
245+
)
204246
del tmp_mask
205-
247+
206248
spine_mask_bin = inference_pipeline.spine_mask > 0
207249

208250
# Determine the target number of pixels
@@ -220,7 +262,7 @@ def __call__(self, inference_pipeline):
220262
inference_pipeline.aorta_mask,
221263
exclude_mask=spine_mask_bin,
222264
remove_size=3,
223-
return_dilated_mask=True,
265+
return_dilated_mask=True,
224266
return_eroded_aorta=True,
225267
threshold=inference_pipeline.args.threshold,
226268
dilation_iteration=target_aorta_dil,
@@ -240,15 +282,15 @@ def __call__(self, inference_pipeline):
240282
"calcium_segmentations.nii.gz",
241283
),
242284
)
243-
285+
244286
inference_pipeline.saveArrToNifti(
245287
calcification_results["dilated_mask"],
246288
os.path.join(
247289
inference_pipeline.output_dir_segmentation_masks,
248290
"dilated_aorta_mask.nii.gz",
249291
),
250292
)
251-
293+
252294
inference_pipeline.saveArrToNifti(
253295
calcification_results["aorta_eroded"],
254296
os.path.join(
@@ -263,7 +305,7 @@ def __call__(self, inference_pipeline):
263305
inference_pipeline.output_dir_segmentation_masks, "spine_mask.nii.gz"
264306
),
265307
)
266-
308+
267309
inference_pipeline.saveArrToNifti(
268310
inference_pipeline.aorta_mask,
269311
os.path.join(
@@ -647,7 +689,7 @@ def getSmallestArraySlice(self, input_mask, margin=0):
647689
)
648690

649691
return (slice(x_start, x_end), slice(y_start, y_end), slice(z_start, z_end))
650-
692+
651693

652694
class AorticCalciumMetrics(InferenceClass):
653695
"""Calculate metrics for the aortic calcifications"""
@@ -659,7 +701,7 @@ def __call__(self, inference_pipeline):
659701
calc_mask = inference_pipeline.calc_mask
660702
spine_mask = inference_pipeline.spine_mask
661703
aorta_mask = inference_pipeline.aorta_mask
662-
704+
663705
t12_level = np.where((spine_mask == 32).sum(axis=(0, 1)))[0]
664706
l1_level = np.where((spine_mask == 31).sum(axis=(0, 1)))[0]
665707

@@ -686,7 +728,7 @@ def __call__(self, inference_pipeline):
686728
),
687729
)
688730
inference_pipeline.t12_plane = planes
689-
731+
690732
inference_pipeline.pix_dims = inference_pipeline.medical_volume.header[
691733
"pixdim"
692734
][1:4]
@@ -741,10 +783,11 @@ def __call__(self, inference_pipeline):
741783
metrics["volume_total"] = calc_vol
742784

743785
metrics["num_calc"] = num_lesions
744-
786+
745787
# percent of the aorta calcificed
746-
metrics['perc_calcified'] = (calc_mask_region.sum() / aorta_mask_region.sum()) * 100
747-
788+
metrics["perc_calcified"] = (
789+
calc_mask_region.sum() / aorta_mask_region.sum()
790+
) * 100
748791

749792
if inference_pipeline.args.threshold == "agatston":
750793
if num_lesions == 0:

0 commit comments

Comments
 (0)