mosamatic2 2.0.24__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. models.py +259 -0
  2. mosamatic2/__init__.py +0 -0
  3. mosamatic2/app.py +32 -0
  4. mosamatic2/cli.py +50 -0
  5. mosamatic2/commands/__init__.py +0 -0
  6. mosamatic2/commands/boadockerpipeline.py +48 -0
  7. mosamatic2/commands/calculatemaskstatistics.py +59 -0
  8. mosamatic2/commands/calculatescores.py +73 -0
  9. mosamatic2/commands/createdicomsummary.py +61 -0
  10. mosamatic2/commands/createpngsfromsegmentations.py +65 -0
  11. mosamatic2/commands/defaultdockerpipeline.py +84 -0
  12. mosamatic2/commands/defaultpipeline.py +70 -0
  13. mosamatic2/commands/dicom2nifti.py +55 -0
  14. mosamatic2/commands/liveranalysispipeline.py +61 -0
  15. mosamatic2/commands/rescaledicomimages.py +54 -0
  16. mosamatic2/commands/segmentmusclefatl3tensorflow.py +55 -0
  17. mosamatic2/commands/selectslicefromscans.py +66 -0
  18. mosamatic2/commands/totalsegmentator.py +77 -0
  19. mosamatic2/constants.py +27 -0
  20. mosamatic2/core/__init__.py +0 -0
  21. mosamatic2/core/data/__init__.py +5 -0
  22. mosamatic2/core/data/dicomimage.py +27 -0
  23. mosamatic2/core/data/dicomimageseries.py +26 -0
  24. mosamatic2/core/data/dixonseries.py +22 -0
  25. mosamatic2/core/data/filedata.py +26 -0
  26. mosamatic2/core/data/multidicomimage.py +30 -0
  27. mosamatic2/core/data/multiniftiimage.py +26 -0
  28. mosamatic2/core/data/multinumpyimage.py +26 -0
  29. mosamatic2/core/data/niftiimage.py +13 -0
  30. mosamatic2/core/data/numpyimage.py +13 -0
  31. mosamatic2/core/managers/__init__.py +0 -0
  32. mosamatic2/core/managers/logmanager.py +45 -0
  33. mosamatic2/core/managers/logmanagerlistener.py +3 -0
  34. mosamatic2/core/pipelines/__init__.py +4 -0
  35. mosamatic2/core/pipelines/boadockerpipeline/__init__.py +0 -0
  36. mosamatic2/core/pipelines/boadockerpipeline/boadockerpipeline.py +70 -0
  37. mosamatic2/core/pipelines/defaultdockerpipeline/__init__.py +0 -0
  38. mosamatic2/core/pipelines/defaultdockerpipeline/defaultdockerpipeline.py +28 -0
  39. mosamatic2/core/pipelines/defaultpipeline/__init__.py +0 -0
  40. mosamatic2/core/pipelines/defaultpipeline/defaultpipeline.py +90 -0
  41. mosamatic2/core/pipelines/liveranalysispipeline/__init__.py +0 -0
  42. mosamatic2/core/pipelines/liveranalysispipeline/liveranalysispipeline.py +48 -0
  43. mosamatic2/core/pipelines/pipeline.py +14 -0
  44. mosamatic2/core/singleton.py +9 -0
  45. mosamatic2/core/tasks/__init__.py +13 -0
  46. mosamatic2/core/tasks/applythresholdtosegmentationstask/__init__.py +0 -0
  47. mosamatic2/core/tasks/applythresholdtosegmentationstask/applythresholdtosegmentationstask.py +117 -0
  48. mosamatic2/core/tasks/calculatemaskstatisticstask/__init__.py +0 -0
  49. mosamatic2/core/tasks/calculatemaskstatisticstask/calculatemaskstatisticstask.py +104 -0
  50. mosamatic2/core/tasks/calculatescorestask/__init__.py +0 -0
  51. mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py +152 -0
  52. mosamatic2/core/tasks/createdicomsummarytask/__init__.py +0 -0
  53. mosamatic2/core/tasks/createdicomsummarytask/createdicomsummarytask.py +88 -0
  54. mosamatic2/core/tasks/createpngsfromsegmentationstask/__init__.py +0 -0
  55. mosamatic2/core/tasks/createpngsfromsegmentationstask/createpngsfromsegmentationstask.py +101 -0
  56. mosamatic2/core/tasks/dicom2niftitask/__init__.py +0 -0
  57. mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py +45 -0
  58. mosamatic2/core/tasks/rescaledicomimagestask/__init__.py +0 -0
  59. mosamatic2/core/tasks/rescaledicomimagestask/rescaledicomimagestask.py +64 -0
  60. mosamatic2/core/tasks/segmentationnifti2numpytask/__init__.py +0 -0
  61. mosamatic2/core/tasks/segmentationnifti2numpytask/segmentationnifti2numpytask.py +57 -0
  62. mosamatic2/core/tasks/segmentationnumpy2niftitask/__init__.py +0 -0
  63. mosamatic2/core/tasks/segmentationnumpy2niftitask/segmentationnumpy2niftitask.py +86 -0
  64. mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/__init__.py +0 -0
  65. mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/paramloader.py +39 -0
  66. mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorflowtask.py +122 -0
  67. mosamatic2/core/tasks/segmentmusclefatt4pytorchtask/__init__.py +0 -0
  68. mosamatic2/core/tasks/segmentmusclefatt4pytorchtask/paramloader.py +39 -0
  69. mosamatic2/core/tasks/segmentmusclefatt4pytorchtask/segmentmusclefatt4pytorchtask.py +128 -0
  70. mosamatic2/core/tasks/selectslicefromscanstask/__init__.py +0 -0
  71. mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py +249 -0
  72. mosamatic2/core/tasks/task.py +50 -0
  73. mosamatic2/core/tasks/totalsegmentatortask/__init__.py +0 -0
  74. mosamatic2/core/tasks/totalsegmentatortask/totalsegmentatortask.py +75 -0
  75. mosamatic2/core/utils.py +405 -0
  76. mosamatic2/server.py +146 -0
  77. mosamatic2/ui/__init__.py +0 -0
  78. mosamatic2/ui/mainwindow.py +426 -0
  79. mosamatic2/ui/resources/VERSION +1 -0
  80. mosamatic2/ui/resources/icons/mosamatic2.icns +0 -0
  81. mosamatic2/ui/resources/icons/mosamatic2.ico +0 -0
  82. mosamatic2/ui/resources/icons/spinner.gif +0 -0
  83. mosamatic2/ui/resources/images/body-composition.jpg +0 -0
  84. mosamatic2/ui/settings.py +62 -0
  85. mosamatic2/ui/utils.py +36 -0
  86. mosamatic2/ui/widgets/__init__.py +0 -0
  87. mosamatic2/ui/widgets/dialogs/__init__.py +0 -0
  88. mosamatic2/ui/widgets/dialogs/dialog.py +16 -0
  89. mosamatic2/ui/widgets/dialogs/helpdialog.py +9 -0
  90. mosamatic2/ui/widgets/panels/__init__.py +0 -0
  91. mosamatic2/ui/widgets/panels/defaultpanel.py +31 -0
  92. mosamatic2/ui/widgets/panels/logpanel.py +65 -0
  93. mosamatic2/ui/widgets/panels/mainpanel.py +82 -0
  94. mosamatic2/ui/widgets/panels/pipelines/__init__.py +0 -0
  95. mosamatic2/ui/widgets/panels/pipelines/boadockerpipelinepanel.py +195 -0
  96. mosamatic2/ui/widgets/panels/pipelines/defaultdockerpipelinepanel.py +314 -0
  97. mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py +302 -0
  98. mosamatic2/ui/widgets/panels/pipelines/liveranalysispipelinepanel.py +187 -0
  99. mosamatic2/ui/widgets/panels/pipelines/pipelinepanel.py +6 -0
  100. mosamatic2/ui/widgets/panels/settingspanel.py +16 -0
  101. mosamatic2/ui/widgets/panels/stackedpanel.py +22 -0
  102. mosamatic2/ui/widgets/panels/tasks/__init__.py +0 -0
  103. mosamatic2/ui/widgets/panels/tasks/applythresholdtosegmentationstaskpanel.py +271 -0
  104. mosamatic2/ui/widgets/panels/tasks/calculatemaskstatisticstaskpanel.py +215 -0
  105. mosamatic2/ui/widgets/panels/tasks/calculatescorestaskpanel.py +238 -0
  106. mosamatic2/ui/widgets/panels/tasks/createdicomsummarytaskpanel.py +206 -0
  107. mosamatic2/ui/widgets/panels/tasks/createpngsfromsegmentationstaskpanel.py +247 -0
  108. mosamatic2/ui/widgets/panels/tasks/dicom2niftitaskpanel.py +183 -0
  109. mosamatic2/ui/widgets/panels/tasks/rescaledicomimagestaskpanel.py +184 -0
  110. mosamatic2/ui/widgets/panels/tasks/segmentationnifti2numpytaskpanel.py +192 -0
  111. mosamatic2/ui/widgets/panels/tasks/segmentationnumpy2niftitaskpanel.py +213 -0
  112. mosamatic2/ui/widgets/panels/tasks/segmentmusclefatl3tensorflowtaskpanel.py +216 -0
  113. mosamatic2/ui/widgets/panels/tasks/segmentmusclefatt4pytorchtaskpanel.py +217 -0
  114. mosamatic2/ui/widgets/panels/tasks/selectslicefromscanstaskpanel.py +193 -0
  115. mosamatic2/ui/widgets/panels/tasks/taskpanel.py +6 -0
  116. mosamatic2/ui/widgets/panels/tasks/totalsegmentatortaskpanel.py +195 -0
  117. mosamatic2/ui/widgets/panels/visualizations/__init__.py +0 -0
  118. mosamatic2/ui/widgets/panels/visualizations/liversegmentvisualization/__init__.py +0 -0
  119. mosamatic2/ui/widgets/panels/visualizations/liversegmentvisualization/liversegmentpicker.py +96 -0
  120. mosamatic2/ui/widgets/panels/visualizations/liversegmentvisualization/liversegmentviewer.py +130 -0
  121. mosamatic2/ui/widgets/panels/visualizations/liversegmentvisualization/liversegmentvisualization.py +120 -0
  122. mosamatic2/ui/widgets/panels/visualizations/sliceselectionvisualization/__init__.py +0 -0
  123. mosamatic2/ui/widgets/panels/visualizations/sliceselectionvisualization/sliceselectionviewer.py +61 -0
  124. mosamatic2/ui/widgets/panels/visualizations/sliceselectionvisualization/sliceselectionvisualization.py +133 -0
  125. mosamatic2/ui/widgets/panels/visualizations/sliceselectionvisualization/slicetile.py +63 -0
  126. mosamatic2/ui/widgets/panels/visualizations/slicevisualization/__init__.py +0 -0
  127. mosamatic2/ui/widgets/panels/visualizations/slicevisualization/custominteractorstyle.py +80 -0
  128. mosamatic2/ui/widgets/panels/visualizations/slicevisualization/sliceviewer.py +116 -0
  129. mosamatic2/ui/widgets/panels/visualizations/slicevisualization/slicevisualization.py +141 -0
  130. mosamatic2/ui/widgets/panels/visualizations/visualization.py +6 -0
  131. mosamatic2/ui/widgets/splashscreen.py +101 -0
  132. mosamatic2/ui/worker.py +29 -0
  133. mosamatic2-2.0.24.dist-info/METADATA +43 -0
  134. mosamatic2-2.0.24.dist-info/RECORD +136 -0
  135. mosamatic2-2.0.24.dist-info/WHEEL +4 -0
  136. mosamatic2-2.0.24.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,104 @@
1
+ import os
2
+ import numpy as np
3
+ import nibabel as nb
4
+ import pandas as pd
5
+ import matplotlib.pyplot as plt
6
+ from mosamatic2.core.tasks.task import Task
7
+ from mosamatic2.core.managers.logmanager import LogManager
8
+
9
+ LOG = LogManager()
10
+
11
+
12
+ class CalculateMaskStatisticsTask(Task):
13
+ INPUTS = ['scans', 'masks']
14
+ PARAMS = []
15
+
16
+ def __init__(self, inputs, params, output, overwrite):
17
+ super(CalculateMaskStatisticsTask, self).__init__(inputs, params, output, overwrite)
18
+
19
+ def load_scans(self):
20
+ scans = []
21
+ for f in os.listdir(self.input('scans')):
22
+ if f.endswith('.nii.gz'):
23
+ scan = os.path.join(self.input('scans'), f)
24
+ if os.path.isfile(scan):
25
+ scans.append(scan)
26
+ return scans
27
+
28
+ def load_mask_files(self):
29
+ mask_files = []
30
+ for f in os.listdir(self.input('masks')):
31
+ if f.endswith('.nii.gz') and 'liver_segment' in f:
32
+ mask_file = os.path.join(self.input('masks'), f)
33
+ if os.path.isfile(mask_file):
34
+ mask_files.append(mask_file)
35
+ return mask_files
36
+
37
+ def collect_scan_mask_file_pairs(self, scans, mask_files):
38
+ scan_mask_file_pairs = []
39
+ for scan in scans:
40
+ scan_name = os.path.split(scan)[1][:-7]
41
+ for mask_file in mask_files:
42
+ mask_file_name = os.path.split(mask_file)[1][:-7]
43
+ if scan_name in mask_file_name:
44
+ scan_mask_file_pairs.append((scan, mask_file))
45
+ return scan_mask_file_pairs
46
+
47
+ def get_masked_voxels(self, scan_mask_file_pair):
48
+ scan_image_file = scan_mask_file_pair[0]
49
+ scan_image = nb.load(scan_image_file)
50
+ scan_image_data = scan_image.get_fdata()
51
+ mask_file_image_file = scan_mask_file_pair[1]
52
+ mask_file_image_file_name = os.path.split(mask_file_image_file)[1]
53
+ mask_file_image = nb.load(mask_file_image_file)
54
+ mask_file_image_data = mask_file_image.get_fdata()
55
+ mask = mask_file_image_data > 0
56
+ masked_voxels = scan_image_data[mask]
57
+ return masked_voxels, mask_file_image_file_name
58
+
59
+ def calculate_volume_in_mL(self, mask_file):
60
+ nifti_image = nb.load(mask_file)
61
+ nifti_image_data = nifti_image.get_fdata()
62
+ voxel_dims = nifti_image.header.get_zooms()[:3]
63
+ voxel_volume = np.prod(voxel_dims)
64
+ num_voxels = np.sum(nifti_image_data > 0.5)
65
+ mask_volume = num_voxels * voxel_volume / 1000.0
66
+ return mask_volume
67
+
68
+ def create_histogram_png(self, masked_voxels, file_name):
69
+ histogram_png_file = os.path.join(self.output(), f'{file_name}.png')
70
+ plt.figure(figsize=(8,6))
71
+ plt.hist(masked_voxels, bins=100, color='steelblue', edgecolor='black')
72
+ plt.title(f'Histogram of HU values inside {file_name}')
73
+ plt.xlabel('HU')
74
+ plt.ylabel('Frequency')
75
+ plt.savefig(histogram_png_file, dpi=300, bbox_inches='tight')
76
+ plt.close()
77
+
78
+ def save_data_to_file(self, data):
79
+ csv_file_path = os.path.join(self.output(), 'statistics.csv')
80
+ xls_file_path = os.path.join(self.output(), 'statistics.xlsx')
81
+ df = pd.DataFrame(data=data)
82
+ df.to_csv(csv_file_path, index=False, sep=';')
83
+ df.to_excel(xls_file_path, index=False, engine='openpyxl')
84
+
85
+ def run(self):
86
+ scans = self.load_scans()
87
+ mask_files = self.load_mask_files()
88
+ scan_mask_file_pairs = self.collect_scan_mask_file_pairs(scans, mask_files)
89
+ nr_steps = len(scan_mask_file_pairs)
90
+ data = {
91
+ 'file': [],
92
+ 'mean_HU': [],
93
+ 'std_HU': [],
94
+ 'volume_mL': [],
95
+ }
96
+ for step in range(nr_steps):
97
+ masked_voxels, file_name = self.get_masked_voxels(scan_mask_file_pairs[step])
98
+ data['file'].append(file_name)
99
+ data['mean_HU'].append(round(np.mean(masked_voxels)))
100
+ data['std_HU'].append(round(np.std(masked_voxels)))
101
+ data['volume_mL'].append(round(self.calculate_volume_in_mL(scan_mask_file_pairs[step][1])))
102
+ self.create_histogram_png(masked_voxels, file_name)
103
+ self.set_progress(step, nr_steps)
104
+ self.save_data_to_file(data)
File without changes
@@ -0,0 +1,152 @@
1
+ import os
2
+ import numpy as np
3
+ import pandas as pd
4
+ from mosamatic2.core.tasks.task import Task
5
+ from mosamatic2.core.managers.logmanager import LogManager
6
+ from mosamatic2.core.data.multidicomimage import MultiDicomImage
7
+ from mosamatic2.core.data.numpyimage import NumpyImage
8
+ from mosamatic2.core.utils import (
9
+ get_pixels_from_dicom_object,
10
+ calculate_area,
11
+ calculate_mean_radiation_attenuation,
12
+ calculate_lama_percentage,
13
+ get_pixels_from_tag_file,
14
+ MUSCLE,
15
+ SAT,
16
+ VAT,
17
+ )
18
+
19
+ LOG = LogManager()
20
+
21
+
22
+ class CalculateScoresTask(Task):
23
+ INPUTS = [
24
+ 'images',
25
+ 'segmentations'
26
+ ]
27
+ PARAMS = ['file_type']
28
+
29
+ def __init__(self, inputs, params, output, overwrite=True):
30
+ super(CalculateScoresTask, self).__init__(inputs, params, output, overwrite)
31
+
32
+ def collect_img_seg_pairs(self, images, segmentations, file_type='npy'):
33
+ file_type = '.tag' if file_type == 'tag' else '.seg.npy'
34
+ img_seg_pairs = []
35
+ for image in images:
36
+ f_img_path = image.path()
37
+ f_img_name = os.path.split(f_img_path)[1]
38
+ for f_seg_path in segmentations:
39
+ f_seg_name = os.path.split(f_seg_path)[1]
40
+ if file_type == '.seg.npy':
41
+ f_seg_name = f_seg_name.removesuffix(file_type)
42
+ if f_seg_name == f_img_name:
43
+ img_seg_pairs.append((image, f_seg_path))
44
+ elif file_type == '.tag':
45
+ f_seg_name = f_seg_name.removesuffix(file_type).removesuffix('.dcm')
46
+ f_img_name = f_img_name.removesuffix('.dcm')
47
+ if f_seg_name == f_img_name:
48
+ img_seg_pairs.append((image, f_seg_path))
49
+ else:
50
+ raise RuntimeError('Unknown file type')
51
+ return img_seg_pairs
52
+
53
+ def load_images(self):
54
+ image_data = MultiDicomImage()
55
+ image_data.set_path(self.input('images'))
56
+ if image_data.load():
57
+ return image_data
58
+ raise RuntimeError('Could not load images')
59
+
60
+ def load_pixels_and_spacing(self, image):
61
+ p = image.object()
62
+ pixels = get_pixels_from_dicom_object(p, normalize=True)
63
+ return pixels, p.PixelSpacing
64
+
65
+ def load_segmentations(self, file_type='npy'):
66
+ file_type = '.tag' if file_type == 'tag' else '.seg.npy'
67
+ segmentations = []
68
+ for f in os.listdir(self.input('segmentations')):
69
+ f_path = os.path.join(self.input('segmentations'), f)
70
+ if f.endswith(file_type):
71
+ segmentations.append(f_path)
72
+ if len(segmentations) == 0:
73
+ raise RuntimeError('Input directory has no segmentation files')
74
+ return segmentations
75
+
76
+ def load_segmentation(self, f, file_type='npy'):
77
+ if file_type == 'npy':
78
+ segmentation = NumpyImage()
79
+ segmentation.set_path(f)
80
+ if segmentation.load():
81
+ return segmentation.object()
82
+ LOG.error(f'Could not load segmentation file {f}')
83
+ if file_type == 'tag':
84
+ pixels = get_pixels_from_tag_file(f)
85
+ try:
86
+ pixels = pixels.reshape(512, 512)
87
+ return pixels
88
+ except Exception:
89
+ LOG.warning(f'Could not reshape TAG pixels to (512, 512), skipping...')
90
+ return None
91
+ raise RuntimeError('Unknown file type')
92
+
93
+ def run(self):
94
+ image_data = self.load_images()
95
+ images = image_data.images()
96
+ file_type = self.param('file_type')
97
+ segmentations = self.load_segmentations(file_type)
98
+ img_seg_pairs = self.collect_img_seg_pairs(images, segmentations, file_type)
99
+ # Create empty data dictionary
100
+ data = {
101
+ 'file': [],
102
+ 'muscle_area': [], 'muscle_idx': [], 'muscle_ra': [], 'muscle_lama_perc': [],
103
+ 'vat_area': [], 'vat_idx': [], 'vat_ra': [],
104
+ 'sat_area': [], 'sat_idx': [], 'sat_ra': []
105
+ }
106
+ nr_steps = len(img_seg_pairs)
107
+ for step in range(nr_steps):
108
+ # Get image and its pixel spacing
109
+ image, pixel_spacing = self.load_pixels_and_spacing(img_seg_pairs[step][0])
110
+ if image is None:
111
+ raise RuntimeError(f'Could not load DICOM image for file {img_seg_pairs[step][0]}')
112
+ # Get segmentation for this image
113
+ segmentation = self.load_segmentation(img_seg_pairs[step][1], file_type)
114
+ if segmentation is None:
115
+ LOG.warning(f'Could not load segmentation for file {img_seg_pairs[step][1]}')
116
+ continue
117
+ # Calculate metrics
118
+ file_name = os.path.split(img_seg_pairs[step][0].path())[1]
119
+ muscle_area = calculate_area(segmentation, MUSCLE, pixel_spacing)
120
+ muscle_idx = 0
121
+ muscle_ra = calculate_mean_radiation_attenuation(image, segmentation, MUSCLE)
122
+ muscle_lama_perc = calculate_lama_percentage(image, segmentation, MUSCLE)
123
+ vat_area = calculate_area(segmentation, VAT, pixel_spacing)
124
+ vat_idx = 0
125
+ vat_ra = calculate_mean_radiation_attenuation(image, segmentation, VAT)
126
+ sat_area = calculate_area(segmentation, SAT, pixel_spacing)
127
+ sat_idx = 0
128
+ sat_ra = calculate_mean_radiation_attenuation(image, segmentation, SAT)
129
+ LOG.info(f'file: {file_name}, ' +
130
+ f'muscle_area: {muscle_area}, muscle_idx: {muscle_idx}, muscle_ra: {muscle_ra}, ' +
131
+ f'vat_area: {vat_area}, vat_idx: {vat_idx}, vat_ra: {vat_ra}, ' +
132
+ f'sat_area: {sat_area}, sat_idx: {sat_idx}, sat_ra: {sat_ra}')
133
+ # Update dataframe data
134
+ data['file'].append(file_name)
135
+ data['muscle_area'].append(muscle_area)
136
+ data['muscle_idx'].append(muscle_idx)
137
+ data['muscle_ra'].append(muscle_ra)
138
+ data['muscle_lama_perc'].append(muscle_lama_perc)
139
+ data['vat_area'].append(vat_area)
140
+ data['vat_idx'].append(vat_idx)
141
+ data['vat_ra'].append(vat_ra)
142
+ data['sat_area'].append(sat_area)
143
+ data['sat_idx'].append(sat_idx)
144
+ data['sat_ra'].append(sat_ra)
145
+ # Update progress
146
+ self.set_progress(step, nr_steps)
147
+ # Build dataframe and return the CSV file as output
148
+ csv_file_path = os.path.join(self.output(), 'bc_scores.csv')
149
+ xls_file_path = os.path.join(self.output(), 'bc_scores.xlsx')
150
+ df = pd.DataFrame(data=data)
151
+ df.to_csv(csv_file_path, index=False, sep=';')
152
+ df.to_excel(xls_file_path, index=False, engine='openpyxl')
@@ -0,0 +1,88 @@
1
+ import os
2
+ import pandas as pd
3
+ from mosamatic2.core.tasks.task import Task
4
+ from mosamatic2.core.managers.logmanager import LogManager
5
+ from mosamatic2.core.utils import (
6
+ is_dicom,
7
+ load_dicom,
8
+ current_time_in_seconds,
9
+ elapsed_time_in_seconds,
10
+ duration,
11
+ )
12
+
13
+ LOG = LogManager()
14
+
15
+
16
+ class CreateDicomSummaryTask(Task):
17
+ INPUTS = ['directory']
18
+ PARAMS = []
19
+
20
+ def __init__(self, inputs, params, output, overwrite):
21
+ super(CreateDicomSummaryTask, self).__init__(inputs, params, output, overwrite)
22
+
23
+ def get_values(self, files, tag_name):
24
+ values = []
25
+ for f in files:
26
+ if tag_name in f.keys():
27
+ if f[tag_name] not in values:
28
+ values.append(f[tag_name])
29
+ return values
30
+
31
+ def get_tag_value(self, p, tag_name):
32
+ return p.data_element(tag_name).value
33
+
34
+ def run(self):
35
+ series = {}
36
+ start_time = current_time_in_seconds()
37
+ for root, dirs, files in os.walk(self.input('directory')):
38
+ for f in files:
39
+ f_path = os.path.join(root, f)
40
+ if is_dicom(f_path):
41
+ p = load_dicom(f_path, stop_before_pixels=True)
42
+ if p:
43
+ patient_id = self.get_tag_value(p, 'PatientID')
44
+ if patient_id:
45
+ if patient_id not in series.keys():
46
+ series[patient_id] = {}
47
+ series_instance_uid = self.get_tag_value(p, 'SeriesInstanceUID')
48
+ if series_instance_uid:
49
+ if series_instance_uid not in series[patient_id].keys():
50
+ series[patient_id][series_instance_uid] = []
51
+ series[patient_id][series_instance_uid].append({
52
+ 'series_description': self.get_tag_value(p, 'SeriesDescription'),
53
+ 'slice_thickness': self.get_tag_value(p, 'SliceThickness'),
54
+ 'rows': self.get_tag_value(p, 'Rows'),
55
+ 'columns': self.get_tag_value(p, 'Columns'),
56
+ 'pixel_spacing': self.get_tag_value(p, 'PixelSpacing'),
57
+ 'modality': self.get_tag_value(p, 'Modality'),
58
+ 'image_type': self.get_tag_value(p, 'ImageType'),
59
+ })
60
+ data = {
61
+ 'patient_id': [],
62
+ 'series_description': [],
63
+ 'nr_images': [],
64
+ 'slice_thickness': [],
65
+ 'rows': [],
66
+ 'columns': [],
67
+ 'pixel_spacing': [],
68
+ 'modality': [],
69
+ 'image_type': [],
70
+ 'series_instance_uid': [],
71
+ }
72
+ for patient_id, v1 in series.items():
73
+ for series_instance_uid, v2 in v1.items():
74
+ data['patient_id'].append(patient_id)
75
+ data['series_description'].append(self.get_values(v2, tag_name='series_description'))
76
+ data['nr_images'].append(len(v2))
77
+ data['slice_thickness'].append(self.get_values(v2, tag_name='slice_thickness'))
78
+ data['rows'].append(self.get_values(v2, tag_name='rows'))
79
+ data['columns'].append(self.get_values(v2, tag_name='columns'))
80
+ data['pixel_spacing'].append(self.get_values(v2, tag_name='pixel_spacing'))
81
+ data['modality'].append(self.get_values(v2, tag_name='modality'))
82
+ data['image_type'].append(self.get_values(v2, tag_name='image_type'))
83
+ data['series_instance_uid'].append(series_instance_uid)
84
+ df = pd.DataFrame(data=data)
85
+ df.to_csv(os.path.join(self.output(), 'summary.csv'), index=False, sep=';')
86
+ df.to_excel(os.path.join(self.output(), 'summary.xlsx'), index=False, engine='openpyxl')
87
+ LOG.info(df)
88
+ LOG.info(f'Elapsed time: {duration(elapsed_time_in_seconds(start_time))}')
@@ -0,0 +1,101 @@
1
+ import os
2
+ from mosamatic2.core.tasks.task import Task
3
+ from mosamatic2.core.managers.logmanager import LogManager
4
+ from mosamatic2.core.data.multidicomimage import MultiDicomImage
5
+ from mosamatic2.core.utils import (
6
+ convert_numpy_array_to_png_image,
7
+ convert_muscle_mask_to_myosteatosis_map,
8
+ get_pixels_from_dicom_object,
9
+ AlbertaColorMap,
10
+ load_numpy_array,
11
+ )
12
+
13
+ LOG = LogManager()
14
+
15
+
16
+ class CreatePngsFromSegmentationsTask(Task):
17
+ INPUTS = ['images', 'segmentations']
18
+ PARAMS = [
19
+ 'fig_width',
20
+ 'fig_height',
21
+ 'hu_low',
22
+ 'hu_high',
23
+ 'alpha',
24
+ ]
25
+
26
+ def __init__(self, inputs, params, output, overwrite=True):
27
+ super(CreatePngsFromSegmentationsTask, self).__init__(inputs, params, output, overwrite)
28
+
29
+ def load_images(self):
30
+ image_data = MultiDicomImage()
31
+ image_data.set_path(self.input('images'))
32
+ if image_data.load():
33
+ return image_data
34
+ raise RuntimeError('Could not load images')
35
+
36
+ def load_segmentations(self):
37
+ segmentations = []
38
+ for f in os.listdir(self.input('segmentations')):
39
+ f_path = os.path.join(self.input('segmentations'), f)
40
+ if f.endswith('.seg.npy'):
41
+ segmentations.append(f_path)
42
+ if len(segmentations) == 0:
43
+ raise RuntimeError('Input directory has no segmentation files')
44
+ return segmentations
45
+
46
+ def collect_img_seg_pairs(self, images, segmentations, file_type='npy'):
47
+ file_type = '.tag' if file_type == 'tag' else '.seg.npy'
48
+ img_seg_pairs = []
49
+ for image in images:
50
+ f_img_path = image.path()
51
+ f_img_name = os.path.split(f_img_path)[1]
52
+ for f_seg_path in segmentations:
53
+ f_seg_name = os.path.split(f_seg_path)[1]
54
+ if file_type == '.seg.npy':
55
+ f_seg_name = f_seg_name.removesuffix(file_type)
56
+ if f_seg_name == f_img_name:
57
+ img_seg_pairs.append((image, f_seg_path))
58
+ elif file_type == '.tag':
59
+ f_seg_name = f_seg_name.removesuffix(file_type).removesuffix('.dcm')
60
+ f_img_name = f_img_name.removesuffix('.dcm')
61
+ if f_seg_name == f_img_name:
62
+ img_seg_pairs.append((image, f_seg_path))
63
+ else:
64
+ raise RuntimeError('Unknown file type')
65
+ return img_seg_pairs
66
+
67
+ def run(self):
68
+ images = self.load_images()
69
+ segmentations = self.load_segmentations()
70
+ img_seg_pairs = self.collect_img_seg_pairs(images.images(), segmentations, 'npy')
71
+ # nr_steps = len(segmentations)
72
+ nr_steps = len(img_seg_pairs)
73
+ for step in range(nr_steps):
74
+ segmentation = img_seg_pairs[step][1]
75
+ segmentation_name = os.path.split(segmentation)[1]
76
+ segmentation_image = load_numpy_array(segmentation)
77
+ if segmentation_image is not None:
78
+ png_file_name = segmentation_name + '.png'
79
+ convert_numpy_array_to_png_image(
80
+ segmentation_image,
81
+ self.output(),
82
+ AlbertaColorMap(),
83
+ png_file_name,
84
+ fig_width=int(self.param('fig_width')),
85
+ fig_height=int(self.param('fig_height')),
86
+ )
87
+ image = img_seg_pairs[step][0]
88
+ image = get_pixels_from_dicom_object(image.object(), normalize=True)
89
+ png_file_name = segmentation_name + '_myosteatosis.png'
90
+ convert_muscle_mask_to_myosteatosis_map(
91
+ image,
92
+ segmentation_image,
93
+ self.output(),
94
+ png_file_name,
95
+ hu_low=self.param('hu_low'),
96
+ hu_high=self.param('hu_high'),
97
+ alpha=self.param('alpha'),
98
+ )
99
+ else:
100
+ LOG.warning(f'File {segmentation} is not a valid NumPy array file')
101
+ self.set_progress(step, nr_steps)
File without changes
@@ -0,0 +1,45 @@
1
+ import os
2
+ import dicom2nifti
3
+ from mosamatic2.core.tasks.task import Task
4
+ from mosamatic2.core.managers.logmanager import LogManager
5
+ from mosamatic2.core.utils import is_dicom
6
+
7
+ LOG = LogManager()
8
+
9
+
10
+ class Dicom2NiftiTask(Task):
11
+ """
12
+ This task converts DICOM scans to NIFTI. Each scan should be stored in a separate
13
+ directory (scan_dir). This task does not convert a single DICOM image!!
14
+ """
15
+ INPUTS = ['scans']
16
+ PARAMS = ['compressed']
17
+
18
+ def __init__(self, inputs, params, output, overwrite):
19
+ super(Dicom2NiftiTask, self).__init__(inputs, params, output, overwrite)
20
+
21
+ def load_scan_dirs(self):
22
+ scan_dirs = []
23
+ for d in os.listdir(self.input('scans')):
24
+ scan_dir = os.path.join(self.input('scans'), d)
25
+ if os.path.isdir(scan_dir):
26
+ scan_dirs.append(scan_dir)
27
+ return scan_dirs
28
+
29
+ def run(self):
30
+ scan_dirs = self.load_scan_dirs()
31
+ nr_steps = len(scan_dirs)
32
+ for step in range(nr_steps):
33
+ scan_dir = scan_dirs[step]
34
+ scan_name = os.path.split(scan_dir)[1]
35
+ if self.param('compressed'):
36
+ nifti_file_name = scan_name + '.nii.gz'
37
+ else:
38
+ nifti_file_name = scan_name + '.nii'
39
+ LOG.info(f'Converting DICOM series in {scan_dir} to {nifti_file_name}')
40
+ dicom2nifti.dicom_series_to_nifti(
41
+ scan_dir,
42
+ os.path.join(self.output(), nifti_file_name),
43
+ reorient_nifti=True,
44
+ )
45
+ self.set_progress(step, nr_steps)
@@ -0,0 +1,64 @@
1
+ import os
2
+ import shutil
3
+ import numpy as np
4
+ from mosamatic2.core.tasks.task import Task
5
+ from mosamatic2.core.managers.logmanager import LogManager
6
+ from mosamatic2.core.data.multidicomimage import MultiDicomImage
7
+ from mosamatic2.core.data.dicomimage import DicomImage
8
+ from mosamatic2.core.data.multidicomimage import MultiDicomImage
9
+ from scipy.ndimage import zoom
10
+
11
+ LOG = LogManager()
12
+
13
+
14
+ class RescaleDicomImagesTask(Task):
15
+ INPUTS = ['images']
16
+ PARAMS = ['target_size']
17
+
18
+ def __init__(self, inputs, params, output=None, overwrite=True):
19
+ super(RescaleDicomImagesTask, self).__init__(inputs, params, output, overwrite)
20
+
21
+ def rescale_image(self, p, target_size):
22
+ pixel_array = p.pixel_array
23
+ hu_array = pixel_array * p.RescaleSlope + p.RescaleIntercept
24
+ hu_air = -1000
25
+ new_rows = max(p.Rows, p.Columns)
26
+ new_cols = max(p.Rows, p.Columns)
27
+ padded_hu_array = np.full((new_rows, new_cols), hu_air, dtype=hu_array.dtype)
28
+ padded_hu_array[:pixel_array.shape[0], :pixel_array.shape[1]] = hu_array
29
+ pixel_array_padded = (padded_hu_array - p.RescaleIntercept) / p.RescaleSlope
30
+ pixel_array_padded = pixel_array_padded.astype(pixel_array.dtype) # Image now has largest dimensions
31
+ pixel_array_rescaled = zoom(pixel_array_padded, zoom=(target_size / new_rows), order=3) # Cubic interpolation
32
+ pixel_array_rescaled = pixel_array_rescaled.astype(pixel_array.dtype)
33
+ original_pixel_spacing = p.PixelSpacing
34
+ new_pixel_spacing = [ps * (new_rows / target_size) for ps in original_pixel_spacing]
35
+ p.PixelSpacing = new_pixel_spacing
36
+ p.PixelData = pixel_array_rescaled.tobytes()
37
+ p.Rows = target_size
38
+ p.Columns = target_size
39
+ return p
40
+
41
+ def run(self):
42
+ image_data = MultiDicomImage()
43
+ image_data.set_path(self.input('images'))
44
+ if image_data.load():
45
+ images = image_data.images()
46
+ nr_steps = len(images)
47
+ for step in range(nr_steps):
48
+ source = images[step]
49
+ assert isinstance(source, DicomImage)
50
+ p = source.object()
51
+ if len(p.pixel_array.shape) == 2:
52
+ source_name = os.path.split(source.path())[1]
53
+ if p.Rows != self.param('target_size') or p.Columns != self.param('target_size'):
54
+ p = self.rescale_image(p, self.param('target_size'))
55
+ target = os.path.join(self.output(), source_name)
56
+ p.save_as(target)
57
+ else:
58
+ target = os.path.join(self.output(), source_name)
59
+ shutil.copy(source.path(), target)
60
+ else:
61
+ LOG.warning(f'Shape of pixel data in file {source.path()} should be 2D but is {len(p.pixel_array.shape)}D')
62
+ self.set_progress(step, nr_steps)
63
+ else:
64
+ LOG.error('Error loading multi-DICOM image data')
@@ -0,0 +1,57 @@
1
+ import os
2
+ import numpy as np
3
+ import nibabel as nib
4
+ import matplotlib.pyplot as plt
5
+ from mosamatic2.core.tasks.task import Task
6
+ from mosamatic2.core.managers.logmanager import LogManager
7
+ from mosamatic2.core.utils import (
8
+ convert_numpy_array_to_png_image,
9
+ AlbertaColorMap,
10
+ )
11
+ LOG = LogManager()
12
+
13
+
14
+ class SegmentationNifti2NumpyTask(Task):
15
+ INPUTS = ['segmentations']
16
+ PARAMS = ['png']
17
+
18
+ def __init__(self, inputs, params, output, overwrite):
19
+ super(SegmentationNifti2NumpyTask, self).__init__(inputs, params, output, overwrite)
20
+
21
+ def load_segmentations(self):
22
+ segmentations = []
23
+ for f in os.listdir(self.input('segmentations')):
24
+ if f.endswith('.seg.npy.nii.gz'):
25
+ f_path = os.path.join(self.input('segmentations'), f)
26
+ segmentations.append(f_path)
27
+ return segmentations
28
+
29
+ def load_segmentation_as_nifti(self, segmentation):
30
+ nifti = nib.load(segmentation)
31
+ narray = np.asanyarray(nifti.dataobj)
32
+ narray = narray[..., 0]
33
+ narray = np.rot90(narray, k=3, axes=(0, 1))
34
+ return narray
35
+
36
+ def create_png_from_array(self, data, file_path):
37
+ png_file_name = os.path.split(file_path)[1] + '.png'
38
+ convert_numpy_array_to_png_image(
39
+ data,
40
+ self.output(),
41
+ AlbertaColorMap(),
42
+ png_file_name,
43
+ fig_width=10, fig_height=10,
44
+ )
45
+
46
+ def run(self):
47
+ segmentations = self.load_segmentations()
48
+ nr_steps = len(segmentations)
49
+ for step in range(nr_steps):
50
+ segmentation = segmentations[step]
51
+ segmentation_narray = self.load_segmentation_as_nifti(segmentation)
52
+ segmentation_narray_name = os.path.split(segmentation)[1][:-7]
53
+ segmentation_narray_path = os.path.join(self.output(), segmentation_narray_name)
54
+ np.save(segmentation_narray_path, segmentation_narray)
55
+ if self.param('png'):
56
+ self.create_png_from_array(segmentation_narray, segmentation_narray_path)
57
+ self.set_progress(step, nr_steps)