mosamatic2 2.0.2__py3-none-any.whl → 2.0.4__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.
Potentially problematic release.
This version of mosamatic2 might be problematic. Click here for more details.
- mosamatic2/app.py +4 -0
- mosamatic2/cli.py +34 -0
- mosamatic2/commands/__init__.py +0 -0
- mosamatic2/commands/calculatescores.py +73 -0
- mosamatic2/commands/createpngsfromsegmentations.py +65 -0
- mosamatic2/commands/dicom2nifti.py +46 -0
- mosamatic2/commands/rescaledicomimages.py +54 -0
- mosamatic2/commands/segmentmusclefatl3tensorflow.py +55 -0
- mosamatic2/constants.py +6 -3
- mosamatic2/core/data/__init__.py +5 -0
- mosamatic2/core/data/dicomimage.py +18 -0
- mosamatic2/core/data/dicomimageseries.py +26 -0
- mosamatic2/core/data/dixonseries.py +22 -0
- mosamatic2/core/data/filedata.py +26 -0
- mosamatic2/core/data/multidicomimage.py +30 -0
- mosamatic2/core/managers/logmanager.py +0 -2
- mosamatic2/core/pipelines/__init__.py +1 -0
- mosamatic2/core/pipelines/defaultpipeline.py +79 -0
- mosamatic2/core/pipelines/pipeline.py +14 -0
- mosamatic2/core/tasks/__init__.py +5 -0
- mosamatic2/core/tasks/calculatescorestask/__init__.py +0 -0
- mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py +149 -0
- mosamatic2/core/tasks/createpngsfromsegmentationstask/__init__.py +0 -0
- mosamatic2/core/tasks/createpngsfromsegmentationstask/createpngsfromsegmentationstask.py +52 -0
- mosamatic2/core/tasks/dicom2niftitask/__init__.py +0 -0
- mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py +24 -0
- mosamatic2/core/tasks/rescaledicomimagestask/__init__.py +0 -0
- mosamatic2/core/tasks/rescaledicomimagestask/rescaledicomimagestask.py +64 -0
- mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/__init__.py +0 -0
- mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/paramloader.py +39 -0
- mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorflowtask.py +121 -0
- mosamatic2/core/tasks/task.py +50 -0
- mosamatic2/core/utils.py +316 -0
- mosamatic2/server.py +112 -1
- mosamatic2/ui/mainwindow.py +150 -1
- mosamatic2/ui/resources/VERSION +1 -1
- mosamatic2/ui/widgets/dialogs/__init__.py +0 -0
- mosamatic2/ui/widgets/dialogs/dialog.py +16 -0
- mosamatic2/ui/widgets/dialogs/helpdialog.py +9 -0
- mosamatic2/ui/widgets/panels/__init__.py +0 -0
- mosamatic2/ui/widgets/panels/defaultpanel.py +31 -0
- mosamatic2/ui/widgets/panels/logpanel.py +65 -0
- mosamatic2/ui/widgets/panels/mainpanel.py +82 -0
- mosamatic2/ui/widgets/panels/pipelines/__init__.py +0 -0
- mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py +299 -0
- mosamatic2/ui/widgets/panels/stackedpanel.py +22 -0
- mosamatic2/ui/widgets/panels/taskpanel.py +6 -0
- mosamatic2/ui/widgets/panels/tasks/__init__.py +0 -0
- mosamatic2/ui/widgets/panels/tasks/calculatescorestaskpanel.py +215 -0
- mosamatic2/ui/widgets/panels/tasks/createpngsfromsegmentationstaskpanel.py +186 -0
- mosamatic2/ui/widgets/panels/tasks/dicom2niftitaskpanel.py +183 -0
- mosamatic2/ui/widgets/panels/tasks/rescaledicomimagestaskpanel.py +184 -0
- mosamatic2/ui/widgets/panels/tasks/segmentmusclefatl3tensorflowtaskpanel.py +216 -0
- mosamatic2/ui/widgets/panels/tasks/selectslicefromscantaskpanel.py +184 -0
- mosamatic2/ui/worker.py +29 -0
- {mosamatic2-2.0.2.dist-info → mosamatic2-2.0.4.dist-info}/METADATA +6 -2
- mosamatic2-2.0.4.dist-info/RECORD +74 -0
- {mosamatic2-2.0.2.dist-info → mosamatic2-2.0.4.dist-info}/entry_points.txt +1 -0
- mosamatic2-2.0.2.dist-info/RECORD +0 -26
- {mosamatic2-2.0.2.dist-info → mosamatic2-2.0.4.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
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.utils import (
|
|
8
|
+
is_dicom,
|
|
9
|
+
load_dicom,
|
|
10
|
+
is_jpeg2000_compressed,
|
|
11
|
+
get_pixels_from_dicom_object,
|
|
12
|
+
calculate_area,
|
|
13
|
+
calculate_mean_radiation_attenuation,
|
|
14
|
+
get_pixels_from_tag_file,
|
|
15
|
+
MUSCLE,
|
|
16
|
+
SAT,
|
|
17
|
+
VAT,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
LOG = LogManager()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CalculateScoresTask(Task):
|
|
24
|
+
INPUTS = [
|
|
25
|
+
'images',
|
|
26
|
+
'segmentations'
|
|
27
|
+
]
|
|
28
|
+
PARAMS = ['file_type']
|
|
29
|
+
|
|
30
|
+
def __init__(self, inputs, params, output, overwrite=True):
|
|
31
|
+
super(CalculateScoresTask, self).__init__(inputs, params, output, overwrite)
|
|
32
|
+
|
|
33
|
+
def collect_img_seg_pairs(self, images, segmentations, file_type='npy'):
|
|
34
|
+
file_type = '.tag' if file_type == 'tag' else '.seg.npy'
|
|
35
|
+
img_seg_pairs = []
|
|
36
|
+
for image in images:
|
|
37
|
+
f_img_path = image.path()
|
|
38
|
+
f_img_name = os.path.split(f_img_path)[1]
|
|
39
|
+
for f_seg_path in segmentations:
|
|
40
|
+
f_seg_name = os.path.split(f_seg_path)[1]
|
|
41
|
+
if file_type == '.seg.npy':
|
|
42
|
+
f_seg_name = f_seg_name.removesuffix(file_type)
|
|
43
|
+
if f_seg_name == f_img_name:
|
|
44
|
+
# img_seg_pairs.append((f_img_path, f_seg_path))
|
|
45
|
+
img_seg_pairs.append((image, f_seg_path))
|
|
46
|
+
elif file_type == '.tag':
|
|
47
|
+
f_seg_name = f_seg_name.removesuffix(file_type).removesuffix('.dcm')
|
|
48
|
+
f_img_name = f_img_name.removesuffix('.dcm')
|
|
49
|
+
if f_seg_name == f_img_name:
|
|
50
|
+
# img_seg_pairs.append((f_img_path, f_seg_path))
|
|
51
|
+
img_seg_pairs.append((image, f_seg_path))
|
|
52
|
+
else:
|
|
53
|
+
raise RuntimeError('Unknown file type')
|
|
54
|
+
return img_seg_pairs
|
|
55
|
+
|
|
56
|
+
def load_images(self):
|
|
57
|
+
image_data = MultiDicomImage()
|
|
58
|
+
image_data.set_path(self.input('images'))
|
|
59
|
+
if image_data.load():
|
|
60
|
+
return image_data
|
|
61
|
+
raise RuntimeError('Could not load images')
|
|
62
|
+
|
|
63
|
+
def load_pixels_and_spacing(self, image):
|
|
64
|
+
p = image.object()
|
|
65
|
+
pixels = get_pixels_from_dicom_object(p, normalize=True)
|
|
66
|
+
return pixels, p.PixelSpacing
|
|
67
|
+
|
|
68
|
+
def load_segmentations(self, file_type='npy'):
|
|
69
|
+
file_type = '.tag' if file_type == 'tag' else '.seg.npy'
|
|
70
|
+
segmentations = []
|
|
71
|
+
for f in os.listdir(self.input('segmentations')):
|
|
72
|
+
f_path = os.path.join(self.input('segmentations'), f)
|
|
73
|
+
if f.endswith(file_type):
|
|
74
|
+
segmentations.append(f_path)
|
|
75
|
+
if len(segmentations) == 0:
|
|
76
|
+
raise RuntimeError('Input directory has no segmentation files')
|
|
77
|
+
return segmentations
|
|
78
|
+
|
|
79
|
+
def load_segmentation(self, f, file_type='npy'):
|
|
80
|
+
if file_type == 'npy':
|
|
81
|
+
return np.load(f)
|
|
82
|
+
if file_type == 'tag':
|
|
83
|
+
pixels = get_pixels_from_tag_file(f)
|
|
84
|
+
try:
|
|
85
|
+
pixels = pixels.reshape(512, 512)
|
|
86
|
+
return pixels
|
|
87
|
+
except Exception:
|
|
88
|
+
LOG.warning(f'Could not reshape TAG pixels to (512, 512), skipping...')
|
|
89
|
+
return None
|
|
90
|
+
raise RuntimeError('Unknown file type')
|
|
91
|
+
|
|
92
|
+
def run(self):
|
|
93
|
+
image_data = self.load_images()
|
|
94
|
+
images = image_data.images()
|
|
95
|
+
file_type = self.param('file_type')
|
|
96
|
+
segmentations = self.load_segmentations(file_type)
|
|
97
|
+
img_seg_pairs = self.collect_img_seg_pairs(images, segmentations, file_type)
|
|
98
|
+
# Create empty data dictionary
|
|
99
|
+
data = {
|
|
100
|
+
'file': [],
|
|
101
|
+
'muscle_area': [], 'muscle_idx': [], 'muscle_ra': [],
|
|
102
|
+
'vat_area': [], 'vat_idx': [], 'vat_ra': [],
|
|
103
|
+
'sat_area': [], 'sat_idx': [], 'sat_ra': []
|
|
104
|
+
}
|
|
105
|
+
nr_steps = len(img_seg_pairs)
|
|
106
|
+
for step in range(nr_steps):
|
|
107
|
+
# Get image and its pixel spacing
|
|
108
|
+
image, pixel_spacing = self.load_pixels_and_spacing(img_seg_pairs[step][0])
|
|
109
|
+
if image is None:
|
|
110
|
+
raise RuntimeError(f'Could not load DICOM image for file {img_seg_pairs[step][0]}')
|
|
111
|
+
# Get segmentation for this image
|
|
112
|
+
segmentation = self.load_segmentation(img_seg_pairs[step][1], file_type)
|
|
113
|
+
if segmentation is None:
|
|
114
|
+
LOG.warning(f'Could not load segmentation for file {img_seg_pairs[step][1]}')
|
|
115
|
+
continue
|
|
116
|
+
# Calculate metrics
|
|
117
|
+
file_name = os.path.split(img_seg_pairs[step][0].path())[1]
|
|
118
|
+
muscle_area = calculate_area(segmentation, MUSCLE, pixel_spacing)
|
|
119
|
+
muscle_idx = 0
|
|
120
|
+
muscle_ra = calculate_mean_radiation_attenuation(image, segmentation, MUSCLE)
|
|
121
|
+
vat_area = calculate_area(segmentation, VAT, pixel_spacing)
|
|
122
|
+
vat_idx = 0
|
|
123
|
+
vat_ra = calculate_mean_radiation_attenuation(image, segmentation, VAT)
|
|
124
|
+
sat_area = calculate_area(segmentation, SAT, pixel_spacing)
|
|
125
|
+
sat_idx = 0
|
|
126
|
+
sat_ra = calculate_mean_radiation_attenuation(image, segmentation, SAT)
|
|
127
|
+
LOG.info(f'file: {file_name}, ' +
|
|
128
|
+
f'muscle_area: {muscle_area}, muscle_idx: {muscle_idx}, muscle_ra: {muscle_ra}, ' +
|
|
129
|
+
f'vat_area: {vat_area}, vat_idx: {vat_idx}, vat_ra: {vat_ra}, ' +
|
|
130
|
+
f'sat_area: {sat_area}, sat_idx: {sat_idx}, sat_ra: {sat_ra}')
|
|
131
|
+
# Update dataframe data
|
|
132
|
+
data['file'].append(file_name)
|
|
133
|
+
data['muscle_area'].append(muscle_area)
|
|
134
|
+
data['muscle_idx'].append(muscle_idx)
|
|
135
|
+
data['muscle_ra'].append(muscle_ra)
|
|
136
|
+
data['vat_area'].append(vat_area)
|
|
137
|
+
data['vat_idx'].append(vat_idx)
|
|
138
|
+
data['vat_ra'].append(vat_ra)
|
|
139
|
+
data['sat_area'].append(sat_area)
|
|
140
|
+
data['sat_idx'].append(sat_idx)
|
|
141
|
+
data['sat_ra'].append(sat_ra)
|
|
142
|
+
# Update progress
|
|
143
|
+
self.set_progress(step, nr_steps)
|
|
144
|
+
# Build dataframe and return the CSV file as output
|
|
145
|
+
csv_file_path = os.path.join(self.output(), 'bc_scores.csv')
|
|
146
|
+
xls_file_path = os.path.join(self.output(), 'bc_scores.xlsx')
|
|
147
|
+
df = pd.DataFrame(data=data)
|
|
148
|
+
df.to_csv(csv_file_path, index=False, sep=';')
|
|
149
|
+
df.to_excel(xls_file_path, index=False, engine='openpyxl')
|
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from mosamatic2.core.tasks.task import Task
|
|
3
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
4
|
+
from mosamatic2.core.utils import (
|
|
5
|
+
convert_numpy_array_to_png_image,
|
|
6
|
+
AlbertaColorMap,
|
|
7
|
+
load_numpy_array,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
LOG = LogManager()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CreatePngsFromSegmentationsTask(Task):
|
|
14
|
+
INPUTS = ['segmentations']
|
|
15
|
+
PARAMS = [
|
|
16
|
+
'fig_width',
|
|
17
|
+
'fig_height'
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
def __init__(self, inputs, params, output, overwrite=True):
|
|
21
|
+
super(CreatePngsFromSegmentationsTask, self).__init__(inputs, params, output, overwrite)
|
|
22
|
+
|
|
23
|
+
def load_segmentations(self):
|
|
24
|
+
segmentations = []
|
|
25
|
+
for f in os.listdir(self.input('segmentations')):
|
|
26
|
+
f_path = os.path.join(self.input('segmentations'), f)
|
|
27
|
+
if f.endswith('.seg.npy'):
|
|
28
|
+
segmentations.append(f_path)
|
|
29
|
+
if len(segmentations) == 0:
|
|
30
|
+
raise RuntimeError('Input directory has no segmentation files')
|
|
31
|
+
return segmentations
|
|
32
|
+
|
|
33
|
+
def run(self):
|
|
34
|
+
segmnentations = self.load_segmentations()
|
|
35
|
+
nr_steps = len(segmnentations)
|
|
36
|
+
for step in range(nr_steps):
|
|
37
|
+
source = segmnentations[step]
|
|
38
|
+
source_name = os.path.split(source)[1]
|
|
39
|
+
source_image = load_numpy_array(source)
|
|
40
|
+
if source_image is not None:
|
|
41
|
+
png_file_name = source_name + '.png'
|
|
42
|
+
convert_numpy_array_to_png_image(
|
|
43
|
+
source_image,
|
|
44
|
+
self.output(),
|
|
45
|
+
AlbertaColorMap(),
|
|
46
|
+
png_file_name,
|
|
47
|
+
fig_width=int(self.param('fig_width')),
|
|
48
|
+
fig_height=int(self.param('fig_height')),
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
LOG.warning(f'File {source} is not a valid NumPy array file')
|
|
52
|
+
self.set_progress(step, nr_steps)
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import dicom2nifti
|
|
3
|
+
from mosamatic2.core.tasks.task import Task
|
|
4
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
5
|
+
|
|
6
|
+
LOG = LogManager()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Dicom2NiftiTask(Task):
|
|
10
|
+
INPUTS = ['images']
|
|
11
|
+
PARAMS = []
|
|
12
|
+
|
|
13
|
+
def __init__(self, inputs, params, output, overwrite):
|
|
14
|
+
super(Dicom2NiftiTask, self).__init__(inputs, params, output, overwrite)
|
|
15
|
+
|
|
16
|
+
def run(self):
|
|
17
|
+
nifti_file_name = os.path.split(self.input('images'))[1] + '.nii.gz'
|
|
18
|
+
LOG.info(f'Converting DICOM directory to {nifti_file_name}')
|
|
19
|
+
dicom2nifti.dicom_series_to_nifti(
|
|
20
|
+
self.input('images'),
|
|
21
|
+
os.path.join(self.output(), nifti_file_name),
|
|
22
|
+
reorient_nifti=True,
|
|
23
|
+
)
|
|
24
|
+
self.set_progress(0, 1)
|
|
File without changes
|
|
@@ -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')
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ParamLoader:
|
|
5
|
+
def __init__(self, json_path):
|
|
6
|
+
self.update(json_path)
|
|
7
|
+
|
|
8
|
+
def save(self, json_path):
|
|
9
|
+
""""
|
|
10
|
+
Save dict to json file
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
json_path : string
|
|
15
|
+
Path to save location
|
|
16
|
+
"""
|
|
17
|
+
with open(json_path, 'w') as f:
|
|
18
|
+
json.dump(self.__dict__, f, indent=4)
|
|
19
|
+
|
|
20
|
+
def update(self, json_path):
|
|
21
|
+
"""
|
|
22
|
+
Load parameters from json file
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
json_path : string
|
|
27
|
+
Path to json file
|
|
28
|
+
"""
|
|
29
|
+
with open(json_path) as f:
|
|
30
|
+
params = json.load(f)
|
|
31
|
+
self.__dict__.update(params)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def dict(self):
|
|
35
|
+
""""
|
|
36
|
+
Give dict-like access to Params instance
|
|
37
|
+
by: 'params.dict['learning_rate']'
|
|
38
|
+
"""
|
|
39
|
+
return self.__dict__
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import zipfile
|
|
3
|
+
import tempfile
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
import models
|
|
7
|
+
|
|
8
|
+
from mosamatic2.core.tasks.task import Task
|
|
9
|
+
from mosamatic2.core.tasks.segmentmusclefatl3tensorflowtask.paramloader import ParamLoader
|
|
10
|
+
from mosamatic2.core.data.multidicomimage import MultiDicomImage
|
|
11
|
+
from mosamatic2.core.data.dicomimage import DicomImage
|
|
12
|
+
from mosamatic2.core.utils import (
|
|
13
|
+
normalize_between,
|
|
14
|
+
get_pixels_from_dicom_object,
|
|
15
|
+
convert_labels_to_157,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
DEVICE = 'cpu'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SegmentMuscleFatL3TensorFlowTask(Task):
|
|
22
|
+
INPUTS = [
|
|
23
|
+
'images',
|
|
24
|
+
'model_files'
|
|
25
|
+
]
|
|
26
|
+
PARAMS = ['model_version']
|
|
27
|
+
|
|
28
|
+
def __init__(self, inputs, params, output, overwrite=True):
|
|
29
|
+
super(SegmentMuscleFatL3TensorFlowTask, self).__init__(inputs, params, output, overwrite)
|
|
30
|
+
|
|
31
|
+
def load_images(self):
|
|
32
|
+
image_data = MultiDicomImage()
|
|
33
|
+
image_data.set_path(self.input('images'))
|
|
34
|
+
if image_data.load():
|
|
35
|
+
return image_data
|
|
36
|
+
raise RuntimeError('Could not load images')
|
|
37
|
+
|
|
38
|
+
def load_model_files(self):
|
|
39
|
+
model_files = []
|
|
40
|
+
for f in os.listdir(self.input('model_files')):
|
|
41
|
+
f_path = os.path.join(self.input('model_files'), f)
|
|
42
|
+
if f_path.endswith('.zip') or f_path.endswith('.json'):
|
|
43
|
+
model_files.append(f_path)
|
|
44
|
+
if len(model_files) != 3:
|
|
45
|
+
raise RuntimeError(f'Found {len(model_files)} model files. This should be 3!')
|
|
46
|
+
return model_files
|
|
47
|
+
|
|
48
|
+
def load_models_and_params(self, model_files, model_version):
|
|
49
|
+
tfLoaded = False
|
|
50
|
+
model, contour_model, params = None, None, None
|
|
51
|
+
for f_path in model_files:
|
|
52
|
+
f_name = os.path.split(f_path)[1]
|
|
53
|
+
if f_name == f'model-{str(model_version)}.zip':
|
|
54
|
+
if not tfLoaded:
|
|
55
|
+
import tensorflow as tf
|
|
56
|
+
tfLoaded = True
|
|
57
|
+
with tempfile.TemporaryDirectory() as model_dir_unzipped:
|
|
58
|
+
# model_dir_unzipped = os.path.join(os.path.split(f_path)[0], 'model_unzipped')
|
|
59
|
+
os.makedirs(model_dir_unzipped, exist_ok=True)
|
|
60
|
+
with zipfile.ZipFile(f_path) as zipObj:
|
|
61
|
+
zipObj.extractall(path=model_dir_unzipped)
|
|
62
|
+
model = tf.keras.models.load_model(model_dir_unzipped, compile=False)
|
|
63
|
+
elif f_name == f'contour_model-{str(model_version)}.zip':
|
|
64
|
+
if not tfLoaded:
|
|
65
|
+
import tensorflow as tf
|
|
66
|
+
tfLoaded = True
|
|
67
|
+
with tempfile.TemporaryDirectory() as contour_model_dir_unzipped:
|
|
68
|
+
# contour_model_dir_unzipped = os.path.join(os.path.split(f_path)[0], 'contour_model_unzipped')
|
|
69
|
+
os.makedirs(contour_model_dir_unzipped, exist_ok=True)
|
|
70
|
+
with zipfile.ZipFile(f_path) as zipObj:
|
|
71
|
+
zipObj.extractall(path=contour_model_dir_unzipped)
|
|
72
|
+
contour_model = tf.keras.models.load_model(contour_model_dir_unzipped, compile=False)
|
|
73
|
+
elif f_name == f'params-{model_version}.json':
|
|
74
|
+
params = ParamLoader(f_path)
|
|
75
|
+
else:
|
|
76
|
+
pass
|
|
77
|
+
return model, contour_model, params
|
|
78
|
+
|
|
79
|
+
def extract_contour(self, image, contour_model, params):
|
|
80
|
+
ct = np.copy(image)
|
|
81
|
+
ct = normalize_between(ct, params.dict['min_bound_contour'], params.dict['max_bound_contour'])
|
|
82
|
+
img2 = np.expand_dims(ct, 0)
|
|
83
|
+
img2 = np.expand_dims(img2, -1)
|
|
84
|
+
pred = contour_model.predict([img2])
|
|
85
|
+
pred_squeeze = np.squeeze(pred)
|
|
86
|
+
pred_max = pred_squeeze.argmax(axis=-1)
|
|
87
|
+
mask = np.uint8(pred_max)
|
|
88
|
+
return mask
|
|
89
|
+
|
|
90
|
+
def segment_muscle_and_fat(self, image, model):
|
|
91
|
+
img2 = np.expand_dims(image, 0)
|
|
92
|
+
img2 = np.expand_dims(img2, -1)
|
|
93
|
+
pred = model.predict([img2])
|
|
94
|
+
pred_squeeze = np.squeeze(pred)
|
|
95
|
+
pred_max = pred_squeeze.argmax(axis=-1)
|
|
96
|
+
return pred_max
|
|
97
|
+
|
|
98
|
+
def process_file(self, image, output_dir, model, contour_model, params):
|
|
99
|
+
assert isinstance(image, DicomImage)
|
|
100
|
+
pixels = get_pixels_from_dicom_object(image.object(), normalize=True)
|
|
101
|
+
if contour_model:
|
|
102
|
+
mask = self.extract_contour(pixels, contour_model, params)
|
|
103
|
+
pixels = normalize_between(pixels, params.dict['min_bound'], params.dict['max_bound'])
|
|
104
|
+
pixels = pixels * mask
|
|
105
|
+
pixels = pixels.astype(np.float32)
|
|
106
|
+
segmentation = self.segment_muscle_and_fat(pixels, model)
|
|
107
|
+
segmentation = convert_labels_to_157(segmentation)
|
|
108
|
+
segmentation_file_name = os.path.split(image.path())[1]
|
|
109
|
+
segmentation_file_path = os.path.join(output_dir, f'{segmentation_file_name}.seg.npy')
|
|
110
|
+
np.save(segmentation_file_path, segmentation)
|
|
111
|
+
|
|
112
|
+
def run(self):
|
|
113
|
+
image_data = self.load_images()
|
|
114
|
+
model_files = self.load_model_files()
|
|
115
|
+
model_version = self.param('model_version')
|
|
116
|
+
model, contour_model, params = self.load_models_and_params(model_files, model_version)
|
|
117
|
+
images = image_data.images()
|
|
118
|
+
nr_steps = len(images)
|
|
119
|
+
for step in range(nr_steps):
|
|
120
|
+
self.process_file(images[step], self.output(), model, contour_model, params)
|
|
121
|
+
self.set_progress(step, nr_steps)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
4
|
+
from mosamatic2.core.utils import create_name_with_timestamp, mosamatic_output_dir
|
|
5
|
+
|
|
6
|
+
LOG = LogManager()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Task:
|
|
10
|
+
INPUTS = []
|
|
11
|
+
PARAMS = []
|
|
12
|
+
OUTPUT = 'output'
|
|
13
|
+
|
|
14
|
+
def __init__(self, inputs, params, output, overwrite=True):
|
|
15
|
+
self._inputs = inputs
|
|
16
|
+
self._params = params
|
|
17
|
+
self._output = os.path.join(output, self.__class__.__name__.lower())
|
|
18
|
+
self._overwrite = overwrite
|
|
19
|
+
if self._overwrite and os.path.isdir(self._output):
|
|
20
|
+
shutil.rmtree(self._output)
|
|
21
|
+
os.makedirs(self._output, exist_ok=self._overwrite)
|
|
22
|
+
# Check that the inputs match specification and type
|
|
23
|
+
assert isinstance(self._inputs, dict)
|
|
24
|
+
assert len(self._inputs.keys()) == len(self.__class__.INPUTS)
|
|
25
|
+
for k, v in self._inputs.items():
|
|
26
|
+
assert k in self.__class__.INPUTS
|
|
27
|
+
assert isinstance(v, str)
|
|
28
|
+
# Check that param names match specification (if not None)
|
|
29
|
+
if self._params:
|
|
30
|
+
assert len(self._params.keys()) == len(self.__class__.PARAMS)
|
|
31
|
+
for k in self._params.keys():
|
|
32
|
+
assert k in self.__class__.PARAMS
|
|
33
|
+
|
|
34
|
+
def input(self, name):
|
|
35
|
+
return self._inputs[name]
|
|
36
|
+
|
|
37
|
+
def param(self, name):
|
|
38
|
+
return self._params[name]
|
|
39
|
+
|
|
40
|
+
def output(self):
|
|
41
|
+
return self._output
|
|
42
|
+
|
|
43
|
+
def overwrite(self):
|
|
44
|
+
return self._overwrite
|
|
45
|
+
|
|
46
|
+
def set_progress(self, step, nr_steps):
|
|
47
|
+
LOG.info(f'[{self.__class__.__name__}] step {step} from {nr_steps}')
|
|
48
|
+
|
|
49
|
+
def run(self):
|
|
50
|
+
raise NotImplementedError()
|