mosamatic2 2.0.10__tar.gz → 2.0.11__tar.gz
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-2.0.10 → mosamatic2-2.0.11}/PKG-INFO +1 -1
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/pyproject.toml +1 -1
- mosamatic2-2.0.11/src/mosamatic2/core/pipelines/__init__.py +2 -0
- mosamatic2-2.0.11/src/mosamatic2/core/pipelines/defaultdockerpipeline/defaultdockerpipeline.py +42 -0
- mosamatic2-2.0.11/src/mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py +162 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/mainwindow.py +15 -0
- mosamatic2-2.0.11/src/mosamatic2/ui/resources/VERSION +1 -0
- mosamatic2-2.0.11/src/mosamatic2/ui/widgets/panels/pipelines/defaultdockerpipelinepanel.py +311 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/tasks/selectslicefromscanstaskpanel.py +1 -1
- mosamatic2-2.0.11/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization/__init__.py +0 -0
- mosamatic2-2.0.10/src/mosamatic2/core/pipelines/__init__.py +0 -1
- mosamatic2-2.0.10/src/mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py +0 -114
- mosamatic2-2.0.10/src/mosamatic2/ui/resources/VERSION +0 -1
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/README.md +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/models.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/app.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/cli.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/commands/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/commands/calculatescores.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/commands/createdicomsummary.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/commands/createpngsfromsegmentations.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/commands/defaultpipeline.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/commands/dicom2nifti.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/commands/rescaledicomimages.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/commands/segmentmusclefatl3tensorflow.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/commands/selectslicefromscans.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/constants.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/data/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/data/dicomimage.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/data/dicomimageseries.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/data/dixonseries.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/data/filedata.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/data/multidicomimage.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/data/multinumpyimage.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/data/numpyimage.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/managers/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/managers/logmanager.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/managers/logmanagerlistener.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/core/pipelines/defaultpipeline → mosamatic2-2.0.11/src/mosamatic2/core/pipelines/defaultdockerpipeline}/__init__.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/core/tasks/calculatescorestask → mosamatic2-2.0.11/src/mosamatic2/core/pipelines/defaultpipeline}/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/pipelines/defaultpipeline/defaultpipeline.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/pipelines/pipeline.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/singleton.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/tasks/__init__.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/core/tasks/createdicomsummarytask → mosamatic2-2.0.11/src/mosamatic2/core/tasks/calculatescorestask}/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/core/tasks/createpngsfromsegmentationstask → mosamatic2-2.0.11/src/mosamatic2/core/tasks/createdicomsummarytask}/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/tasks/createdicomsummarytask/createdicomsummarytask.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/core/tasks/dicom2niftitask → mosamatic2-2.0.11/src/mosamatic2/core/tasks/createpngsfromsegmentationstask}/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/tasks/createpngsfromsegmentationstask/createpngsfromsegmentationstask.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/core/tasks/rescaledicomimagestask → mosamatic2-2.0.11/src/mosamatic2/core/tasks/dicom2niftitask}/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask → mosamatic2-2.0.11/src/mosamatic2/core/tasks/rescaledicomimagestask}/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/tasks/rescaledicomimagestask/rescaledicomimagestask.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/core/tasks/selectslicefromscanstask → mosamatic2-2.0.11/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask}/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/paramloader.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorflowtask.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/ui → mosamatic2-2.0.11/src/mosamatic2/core/tasks/selectslicefromscanstask}/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/tasks/task.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/utils.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/server.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/ui/widgets → mosamatic2-2.0.11/src/mosamatic2/ui}/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/resources/icons/mosamatic2.icns +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/resources/icons/mosamatic2.ico +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/resources/icons/spinner.gif +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/resources/images/body-composition.jpg +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/settings.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/utils.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/ui/widgets/dialogs → mosamatic2-2.0.11/src/mosamatic2/ui/widgets}/__init__.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/ui/widgets/panels → mosamatic2-2.0.11/src/mosamatic2/ui/widgets/dialogs}/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/dialogs/dialog.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/dialogs/helpdialog.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/ui/widgets/panels/pipelines → mosamatic2-2.0.11/src/mosamatic2/ui/widgets/panels}/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/defaultpanel.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/logpanel.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/mainpanel.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/ui/widgets/panels/tasks → mosamatic2-2.0.11/src/mosamatic2/ui/widgets/panels/pipelines}/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/pipelines/pipelinepanel.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/stackedpanel.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/ui/widgets/panels/visualizations → mosamatic2-2.0.11/src/mosamatic2/ui/widgets/panels/tasks}/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/tasks/calculatescorestaskpanel.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/tasks/createdicomsummarytaskpanel.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/tasks/createpngsfromsegmentationstaskpanel.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/tasks/dicom2niftitaskpanel.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/tasks/rescaledicomimagestaskpanel.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/tasks/segmentmusclefatl3tensorflowtaskpanel.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/tasks/taskpanel.py +0 -0
- {mosamatic2-2.0.10/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization → mosamatic2-2.0.11/src/mosamatic2/ui/widgets/panels/visualizations}/__init__.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization/custominteractorstyle.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization/sliceviewer.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization/slicevisualization.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/visualizations/visualization.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/splashscreen.py +0 -0
- {mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/worker.py +0 -0
mosamatic2-2.0.11/src/mosamatic2/core/pipelines/defaultdockerpipeline/defaultdockerpipeline.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from mosamatic2.core.pipelines.pipeline import Pipeline
|
|
4
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
5
|
+
|
|
6
|
+
LOG = LogManager()
|
|
7
|
+
DOCKER_SCRIPT = """
|
|
8
|
+
docker run --rm ^
|
|
9
|
+
-v "%IMAGES%":/data/images ^
|
|
10
|
+
-v "%MODEL_FILES%":/data/model_files ^
|
|
11
|
+
-v "%OUTPUT%":/data/output ^
|
|
12
|
+
brecheisen/mosamatic2-cli:%VERSION% defaultpipeline ^
|
|
13
|
+
--images /data/images --model_files /data/model_files --output /data/output --overwrite true
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DefaultDockerPipeline(Pipeline):
|
|
18
|
+
INPUTS = [
|
|
19
|
+
'images',
|
|
20
|
+
'model_files',
|
|
21
|
+
]
|
|
22
|
+
PARAMS = [
|
|
23
|
+
'target_size',
|
|
24
|
+
'file_type',
|
|
25
|
+
'fig_width',
|
|
26
|
+
'fig_height',
|
|
27
|
+
'model_type',
|
|
28
|
+
'model_version',
|
|
29
|
+
'version',
|
|
30
|
+
]
|
|
31
|
+
def __init__(self, inputs, params, output, overwrite):
|
|
32
|
+
super(DefaultDockerPipeline, self).__init__(inputs, params, output, overwrite)
|
|
33
|
+
|
|
34
|
+
def run(self):
|
|
35
|
+
docker_script = 'docker run --rm ' + \
|
|
36
|
+
'-v "{}":/data/images '.format(self.input('images')) + \
|
|
37
|
+
'-v "{}":/data/model_files '.format(self.input('model_files')) + \
|
|
38
|
+
'-v "{}":/data/output '.format(self.output()) + \
|
|
39
|
+
'brecheisen/mosamatic2-cli:{} defaultpipeline '.format(self.param('version')) + \
|
|
40
|
+
'--images /data/images --model_files /data/model_files --output /data/output --overwrite true'
|
|
41
|
+
LOG.info(f'Running Docker script: {docker_script}')
|
|
42
|
+
os.system(docker_script)
|
mosamatic2-2.0.11/src/mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import math
|
|
3
|
+
import tempfile
|
|
4
|
+
import shutil
|
|
5
|
+
import nibabel as nib
|
|
6
|
+
import numpy as np
|
|
7
|
+
from totalsegmentator.python_api import totalsegmentator
|
|
8
|
+
from mosamatic2.core.tasks.task import Task
|
|
9
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
10
|
+
from mosamatic2.core.utils import load_dicom
|
|
11
|
+
|
|
12
|
+
LOG = LogManager()
|
|
13
|
+
|
|
14
|
+
TOTAL_SEGMENTATOR_OUTPUT_DIR = os.path.join(tempfile.gettempdir(), 'total_segmentator_output')
|
|
15
|
+
TOTAL_SEGMENTATOR_TASK = 'total'
|
|
16
|
+
Z_DELTA_OFFSETS = {
|
|
17
|
+
'vertebrae_L3': 0.333,
|
|
18
|
+
'vertebrae_T4': 0.5,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SelectSliceFromScansTask(Task):
|
|
23
|
+
INPUTS = ['scans']
|
|
24
|
+
PARAMS = ['vertebra']
|
|
25
|
+
|
|
26
|
+
def __init__(self, inputs, params, output, overwrite):
|
|
27
|
+
super(SelectSliceFromScansTask, self).__init__(inputs, params, output, overwrite)
|
|
28
|
+
self._error_dir = os.path.split(self.output())[0]
|
|
29
|
+
self._error_dir = os.path.join(self._error_dir, 'selectslicefromscanstask_errors')
|
|
30
|
+
os.makedirs(self._error_dir, exist_ok=True)
|
|
31
|
+
self._error_file = os.path.join(self._error_dir, 'errors.txt')
|
|
32
|
+
with open(self._error_file, 'w') as f:
|
|
33
|
+
f.write('Errors:\n')
|
|
34
|
+
LOG.info(f'Error directory: {self._error_dir}')
|
|
35
|
+
|
|
36
|
+
def write_error(self, message):
|
|
37
|
+
LOG.error(message)
|
|
38
|
+
with open(self._error_file, 'a') as f:
|
|
39
|
+
f.write(message + '\n')
|
|
40
|
+
|
|
41
|
+
def load_scan_dirs(self):
|
|
42
|
+
scan_dirs = []
|
|
43
|
+
for d in os.listdir(self.input('scans')):
|
|
44
|
+
scan_dir = os.path.join(self.input('scans'), d)
|
|
45
|
+
if os.path.isdir(scan_dir):
|
|
46
|
+
scan_dirs.append(scan_dir)
|
|
47
|
+
return scan_dirs
|
|
48
|
+
|
|
49
|
+
def extract_masks(self, scan_dir):
|
|
50
|
+
os.makedirs(TOTAL_SEGMENTATOR_OUTPUT_DIR, exist_ok=True)
|
|
51
|
+
totalsegmentator(input=scan_dir, output=TOTAL_SEGMENTATOR_OUTPUT_DIR, fast=True)
|
|
52
|
+
if not os.path.isfile(os.path.join(TOTAL_SEGMENTATOR_OUTPUT_DIR, 'vertebrae_L3.nii.gz')):
|
|
53
|
+
raise Exception(f'{scan_dir}: vertebrae_L3.nii.gz does not exist')
|
|
54
|
+
# os.system(f'TotalSegmentator -i {scan_dir} -o {TOTAL_SEGMENTATOR_OUTPUT_DIR} --fast')
|
|
55
|
+
|
|
56
|
+
def delete_total_segmentator_output(self):
|
|
57
|
+
if os.path.exists(TOTAL_SEGMENTATOR_OUTPUT_DIR):
|
|
58
|
+
shutil.rmtree(TOTAL_SEGMENTATOR_OUTPUT_DIR)
|
|
59
|
+
|
|
60
|
+
def get_z_delta_offset_for_mask(self, mask_name):
|
|
61
|
+
if mask_name not in Z_DELTA_OFFSETS.keys():
|
|
62
|
+
return None
|
|
63
|
+
return Z_DELTA_OFFSETS[mask_name]
|
|
64
|
+
|
|
65
|
+
def find_slice(self, scan_dir, vertebra):
|
|
66
|
+
if vertebra == 'L3':
|
|
67
|
+
vertebral_level = 'vertebrae_L3'
|
|
68
|
+
elif vertebra == 'T4':
|
|
69
|
+
vertebral_level = 'vertebrae_T4'
|
|
70
|
+
else:
|
|
71
|
+
self.write_error(f'{scan_dir}: Unknown vertbra {vertebra}. Options are "L3" and "T4"')
|
|
72
|
+
return None
|
|
73
|
+
# Find Z-positions DICOM images
|
|
74
|
+
z_positions = {}
|
|
75
|
+
for f in os.listdir(scan_dir):
|
|
76
|
+
f_path = os.path.join(scan_dir, f)
|
|
77
|
+
try:
|
|
78
|
+
p = load_dicom(f_path, stop_before_pixels=True)
|
|
79
|
+
if p is not None and hasattr(p, "ImagePositionPatient"):
|
|
80
|
+
z_positions[p.ImagePositionPatient[2]] = f_path
|
|
81
|
+
except Exception as e:
|
|
82
|
+
self.write_error(f"{scan_dir}: Failed to load DICOM {f_path}: {e}")
|
|
83
|
+
break
|
|
84
|
+
if not z_positions:
|
|
85
|
+
self.write_error(f"{scan_dir}: No valid DICOM z-positions found.")
|
|
86
|
+
return None
|
|
87
|
+
# Find Z-position L3 image
|
|
88
|
+
mask_file = os.path.join(TOTAL_SEGMENTATOR_OUTPUT_DIR, f'{vertebral_level}.nii.gz')
|
|
89
|
+
if not os.path.exists(mask_file):
|
|
90
|
+
self.write_error(f"{scan_dir}: Mask file not found: {mask_file}")
|
|
91
|
+
return None
|
|
92
|
+
try:
|
|
93
|
+
mask_obj = nib.load(mask_file)
|
|
94
|
+
mask = mask_obj.get_fdata()
|
|
95
|
+
affine_transform = mask_obj.affine
|
|
96
|
+
except Exception as e:
|
|
97
|
+
self.write_error(f"{scan_dir}: Failed to load mask {mask_file}: {e}")
|
|
98
|
+
return None
|
|
99
|
+
indexes = np.array(np.where(mask == 1))
|
|
100
|
+
if indexes.size == 0:
|
|
101
|
+
self.write_error(f"{scan_dir}: No voxels found in mask {mask_file} for {vertebral_level}")
|
|
102
|
+
return None
|
|
103
|
+
try:
|
|
104
|
+
index_min = indexes.min(axis=1)
|
|
105
|
+
index_max = indexes.max(axis=1)
|
|
106
|
+
except ValueError as e:
|
|
107
|
+
self.write_error(f"{scan_dir}: Invalid indexes array for {vertebral_level}: {e}")
|
|
108
|
+
return None
|
|
109
|
+
world_min = nib.affines.apply_affine(affine_transform, index_min)
|
|
110
|
+
world_max = nib.affines.apply_affine(affine_transform, index_max)
|
|
111
|
+
z_direction = affine_transform[:3, 2][2]
|
|
112
|
+
if z_direction == 0:
|
|
113
|
+
self.write_error(f"{scan_dir}: Affine z-direction is zero.")
|
|
114
|
+
return None
|
|
115
|
+
z_sign = math.copysign(1, z_direction)
|
|
116
|
+
z_delta_offset = self.get_z_delta_offset_for_mask(vertebral_level)
|
|
117
|
+
if z_delta_offset is None:
|
|
118
|
+
return None
|
|
119
|
+
z_delta = 0.333 * abs(world_max[2] - world_min[2]) # This needs to be vertebra-specific perhaps
|
|
120
|
+
z_l3 = world_max[2] - z_sign * z_delta
|
|
121
|
+
# Find closest L3 image in DICOM set
|
|
122
|
+
positions = sorted(z_positions.keys())
|
|
123
|
+
closest_file = None
|
|
124
|
+
for z1, z2 in zip(positions[:-1], positions[1:]):
|
|
125
|
+
if min(z1, z2) <= z_l3 <= max(z1, z2):
|
|
126
|
+
closest_z = min(z_positions.keys(), key=lambda z: abs(z - z_l3))
|
|
127
|
+
closest_file = z_positions[closest_z]
|
|
128
|
+
LOG.info(f'Closest image: {closest_file}')
|
|
129
|
+
break
|
|
130
|
+
if closest_file is None:
|
|
131
|
+
self.write_error(f"{scan_dir}: No matching slice found.")
|
|
132
|
+
return closest_file
|
|
133
|
+
|
|
134
|
+
def run(self):
|
|
135
|
+
scan_dirs = self.load_scan_dirs()
|
|
136
|
+
vertebra = self.param('vertebra')
|
|
137
|
+
nr_steps = len(scan_dirs)
|
|
138
|
+
for step in range(nr_steps):
|
|
139
|
+
scan_dir = scan_dirs[step]
|
|
140
|
+
scan_name = os.path.split(scan_dir)[1]
|
|
141
|
+
errors = False
|
|
142
|
+
LOG.info(f'Processing {scan_dir}...')
|
|
143
|
+
try:
|
|
144
|
+
self.extract_masks(scan_dir)
|
|
145
|
+
except Exception as e:
|
|
146
|
+
self.write_error(f'{scan_dir}: Could not extract masks [{str(e)}]. Skipping scan...')
|
|
147
|
+
errors = True
|
|
148
|
+
if not errors:
|
|
149
|
+
file_path = self.find_slice(scan_dir, vertebra)
|
|
150
|
+
if file_path is not None:
|
|
151
|
+
extension = '' if file_path.endswith('.dcm') else '.dcm'
|
|
152
|
+
target_file_path = os.path.join(self.output(), vertebra + '_' + scan_name + extension)
|
|
153
|
+
shutil.copyfile(file_path, target_file_path)
|
|
154
|
+
else:
|
|
155
|
+
self.write_error(f'{scan_dir}: Could not find slice for vertebral level: {vertebra}')
|
|
156
|
+
errors = True
|
|
157
|
+
self.delete_total_segmentator_output()
|
|
158
|
+
if errors:
|
|
159
|
+
LOG.info(f'Copying problematic scan {scan_dir} to error directory: {self._error_dir}')
|
|
160
|
+
scan_error_dir = os.path.join(self._error_dir, scan_name)
|
|
161
|
+
shutil.copytree(scan_dir, scan_error_dir)
|
|
162
|
+
self.set_progress(step, nr_steps)
|
|
@@ -22,6 +22,7 @@ from mosamatic2.ui.widgets.panels.tasks.dicom2niftitaskpanel import Dicom2NiftiT
|
|
|
22
22
|
from mosamatic2.ui.widgets.panels.tasks.createdicomsummarytaskpanel import CreateDicomSummaryTaskPanel
|
|
23
23
|
from mosamatic2.ui.widgets.panels.tasks.selectslicefromscanstaskpanel import SelectSliceFromScansTaskPanel
|
|
24
24
|
from mosamatic2.ui.widgets.panels.pipelines.defaultpipelinepanel import DefaultPipelinePanel
|
|
25
|
+
from mosamatic2.ui.widgets.panels.pipelines.defaultdockerpipelinepanel import DefaultDockerPipelinePanel
|
|
25
26
|
from mosamatic2.ui.widgets.panels.visualizations.slicevisualization.slicevisualization import SliceVisualization
|
|
26
27
|
|
|
27
28
|
LOG = LogManager()
|
|
@@ -42,6 +43,7 @@ class MainWindow(QMainWindow):
|
|
|
42
43
|
self._create_dicom_summary_task_panel = None
|
|
43
44
|
self._select_slice_from_scans_task_panel = None
|
|
44
45
|
self._default_pipeline_panel = None
|
|
46
|
+
self._default_docker_pipeline_panel = None
|
|
45
47
|
self._slice_visualization = None
|
|
46
48
|
self.init_window()
|
|
47
49
|
|
|
@@ -98,8 +100,11 @@ class MainWindow(QMainWindow):
|
|
|
98
100
|
def init_pipelines_menu(self):
|
|
99
101
|
default_pipeline_action = QAction('DefaultPipeline', self)
|
|
100
102
|
default_pipeline_action.triggered.connect(self.handle_default_pipeline_action)
|
|
103
|
+
default_docker_pipeline_action = QAction('DefaultDockerPipeline', self)
|
|
104
|
+
default_docker_pipeline_action.triggered.connect(self.handle_default_docker_pipeline_action)
|
|
101
105
|
pipelines_menu = self.menuBar().addMenu('Pipelines')
|
|
102
106
|
pipelines_menu.addAction(default_pipeline_action)
|
|
107
|
+
pipelines_menu.addAction(default_docker_pipeline_action)
|
|
103
108
|
|
|
104
109
|
def init_visualizations_menu(self):
|
|
105
110
|
slice_visualization_action = QAction('SliceVisualization', self)
|
|
@@ -128,6 +133,7 @@ class MainWindow(QMainWindow):
|
|
|
128
133
|
self._main_panel.add_panel(self.create_dicom_summary_task_panel(), 'createdicomsummarytaskpanel')
|
|
129
134
|
self._main_panel.add_panel(self.select_slice_from_scans_task_panel(), 'selectslicefromscanstaskpanel')
|
|
130
135
|
self._main_panel.add_panel(self.default_pipeline_panel(), 'defaultpipelinepanel')
|
|
136
|
+
self._main_panel.add_panel(self.default_docker_pipeline_panel(), 'defaultdockerpipelinepanel')
|
|
131
137
|
self._main_panel.add_panel(self.slice_visualization(), 'slicevisualization')
|
|
132
138
|
self._main_panel.select_panel('defaultpipelinepanel')
|
|
133
139
|
return self._main_panel
|
|
@@ -179,6 +185,11 @@ class MainWindow(QMainWindow):
|
|
|
179
185
|
if not self._default_pipeline_panel:
|
|
180
186
|
self._default_pipeline_panel = DefaultPipelinePanel()
|
|
181
187
|
return self._default_pipeline_panel
|
|
188
|
+
|
|
189
|
+
def default_docker_pipeline_panel(self):
|
|
190
|
+
if not self._default_docker_pipeline_panel:
|
|
191
|
+
self._default_docker_pipeline_panel = DefaultDockerPipelinePanel()
|
|
192
|
+
return self._default_docker_pipeline_panel
|
|
182
193
|
|
|
183
194
|
def slice_visualization(self):
|
|
184
195
|
if not self._slice_visualization:
|
|
@@ -216,6 +227,9 @@ class MainWindow(QMainWindow):
|
|
|
216
227
|
def handle_default_pipeline_action(self):
|
|
217
228
|
self.main_panel().select_panel('defaultpipelinepanel')
|
|
218
229
|
|
|
230
|
+
def handle_default_docker_pipeline_action(self):
|
|
231
|
+
self.main_panel().select_panel('defaultdockerpipelinepanel')
|
|
232
|
+
|
|
219
233
|
def handle_slice_visualization_action(self):
|
|
220
234
|
self.main_panel().select_panel('slicevisualization')
|
|
221
235
|
|
|
@@ -233,6 +247,7 @@ class MainWindow(QMainWindow):
|
|
|
233
247
|
self.create_dicom_summary_task_panel().save_inputs_and_parameters()
|
|
234
248
|
self.select_slice_from_scans_task_panel().save_inputs_and_parameters()
|
|
235
249
|
self.default_pipeline_panel().save_inputs_and_parameters()
|
|
250
|
+
self.default_docker_pipeline_panel().save_inputs_and_parameters()
|
|
236
251
|
self.slice_visualization().save_inputs_and_parameters()
|
|
237
252
|
return super().closeEvent(event)
|
|
238
253
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.0.11
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from PySide6.QtWidgets import (
|
|
4
|
+
QLineEdit,
|
|
5
|
+
QSpinBox,
|
|
6
|
+
QComboBox,
|
|
7
|
+
QCheckBox,
|
|
8
|
+
QHBoxLayout,
|
|
9
|
+
QVBoxLayout,
|
|
10
|
+
QFormLayout,
|
|
11
|
+
QPushButton,
|
|
12
|
+
QFileDialog,
|
|
13
|
+
QMessageBox,
|
|
14
|
+
)
|
|
15
|
+
from PySide6.QtCore import (
|
|
16
|
+
QThread,
|
|
17
|
+
Slot,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
21
|
+
from mosamatic2.ui.widgets.panels.pipelines.pipelinepanel import PipelinePanel
|
|
22
|
+
from mosamatic2.ui.settings import Settings
|
|
23
|
+
from mosamatic2.ui.utils import is_macos
|
|
24
|
+
from mosamatic2.ui.worker import Worker
|
|
25
|
+
from mosamatic2.core.pipelines import DefaultDockerPipeline
|
|
26
|
+
|
|
27
|
+
LOG = LogManager()
|
|
28
|
+
|
|
29
|
+
PANEL_TITLE = 'DefaultDockerPipeline'
|
|
30
|
+
PANEL_NAME = 'defaultdockerpipeline'
|
|
31
|
+
MODEL_TYPE_ITEM_NAMES = ['tensorflow', 'pytorch']
|
|
32
|
+
MODEL_VERSION_ITEM_NAMES = ['1.0', '2.2']
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DefaultDockerPipelinePanel(PipelinePanel):
|
|
36
|
+
def __init__(self):
|
|
37
|
+
super(DefaultDockerPipelinePanel, self).__init__()
|
|
38
|
+
self.set_title(PANEL_TITLE)
|
|
39
|
+
self._images_dir_line_edit = None
|
|
40
|
+
self._images_dir_select_button = None
|
|
41
|
+
self._model_files_dir_line_edit = None
|
|
42
|
+
self._model_files_dir_select_button = None
|
|
43
|
+
self._output_dir_line_edit = None
|
|
44
|
+
self._output_dir_select_button = None
|
|
45
|
+
self._target_size_spinbox = None
|
|
46
|
+
self._model_type_combobox = None
|
|
47
|
+
self._model_version_combobox = None
|
|
48
|
+
self._fig_width_spinbox = None
|
|
49
|
+
self._fig_height_spinbox = None
|
|
50
|
+
self._full_scan_checkbox = None
|
|
51
|
+
self._overwrite_checkbox = None
|
|
52
|
+
self._version_line_edit = None
|
|
53
|
+
self._form_layout = None
|
|
54
|
+
self._run_pipeline_button = None
|
|
55
|
+
self._settings = None
|
|
56
|
+
self._task = None
|
|
57
|
+
self._worker = None
|
|
58
|
+
self._thread = None
|
|
59
|
+
self.init_layout()
|
|
60
|
+
|
|
61
|
+
def images_dir_line_edit(self):
|
|
62
|
+
if not self._images_dir_line_edit:
|
|
63
|
+
self._images_dir_line_edit = QLineEdit()
|
|
64
|
+
self._images_dir_line_edit.setText(self.settings().get(f'{PANEL_NAME}/images_dir'))
|
|
65
|
+
return self._images_dir_line_edit
|
|
66
|
+
|
|
67
|
+
def images_dir_select_button(self):
|
|
68
|
+
if not self._images_dir_select_button:
|
|
69
|
+
self._images_dir_select_button = QPushButton('Select')
|
|
70
|
+
self._images_dir_select_button.clicked.connect(self.handle_images_dir_select_button)
|
|
71
|
+
return self._images_dir_select_button
|
|
72
|
+
|
|
73
|
+
def model_files_dir_line_edit(self):
|
|
74
|
+
if not self._model_files_dir_line_edit:
|
|
75
|
+
self._model_files_dir_line_edit = QLineEdit()
|
|
76
|
+
self._model_files_dir_line_edit.setText(self.settings().get(f'{PANEL_NAME}/model_files_dir'))
|
|
77
|
+
return self._model_files_dir_line_edit
|
|
78
|
+
|
|
79
|
+
def model_files_dir_select_button(self):
|
|
80
|
+
if not self._model_files_dir_select_button:
|
|
81
|
+
self._model_files_dir_select_button = QPushButton('Select')
|
|
82
|
+
self._model_files_dir_select_button.clicked.connect(self.handle_model_files_dir_select_button)
|
|
83
|
+
return self._model_files_dir_select_button
|
|
84
|
+
|
|
85
|
+
def output_dir_line_edit(self):
|
|
86
|
+
if not self._output_dir_line_edit:
|
|
87
|
+
self._output_dir_line_edit = QLineEdit()
|
|
88
|
+
self._output_dir_line_edit.setText(self.settings().get(f'{PANEL_NAME}/output_dir'))
|
|
89
|
+
return self._output_dir_line_edit
|
|
90
|
+
|
|
91
|
+
def output_dir_select_button(self):
|
|
92
|
+
if not self._output_dir_select_button:
|
|
93
|
+
self._output_dir_select_button = QPushButton('Select')
|
|
94
|
+
self._output_dir_select_button.clicked.connect(self.handle_output_dir_select_button)
|
|
95
|
+
return self._output_dir_select_button
|
|
96
|
+
|
|
97
|
+
def target_size_spinbox(self):
|
|
98
|
+
if not self._target_size_spinbox:
|
|
99
|
+
self._target_size_spinbox = QSpinBox()
|
|
100
|
+
self._target_size_spinbox.setMinimum(0)
|
|
101
|
+
self._target_size_spinbox.setMaximum(1024)
|
|
102
|
+
self._target_size_spinbox.setValue(self.settings().get_int(f'{PANEL_NAME}/target_size', 512))
|
|
103
|
+
return self._target_size_spinbox
|
|
104
|
+
|
|
105
|
+
def model_type_combobox(self):
|
|
106
|
+
if not self._model_type_combobox:
|
|
107
|
+
self._model_type_combobox = QComboBox()
|
|
108
|
+
self._model_type_combobox.addItems(MODEL_TYPE_ITEM_NAMES)
|
|
109
|
+
self._model_type_combobox.setCurrentText(self.settings().get(f'{PANEL_NAME}/model_type'))
|
|
110
|
+
self._model_type_combobox.currentTextChanged.connect(self.handle_model_type_combobox)
|
|
111
|
+
return self._model_type_combobox
|
|
112
|
+
|
|
113
|
+
def model_version_combobox(self):
|
|
114
|
+
if not self._model_version_combobox:
|
|
115
|
+
self._model_version_combobox = QComboBox()
|
|
116
|
+
self._model_version_combobox.addItems(MODEL_VERSION_ITEM_NAMES)
|
|
117
|
+
self._model_version_combobox.setCurrentText(self.settings().get(f'{PANEL_NAME}/model_version'))
|
|
118
|
+
self._model_version_combobox.currentTextChanged.connect(self.handle_model_version_combobox)
|
|
119
|
+
return self._model_version_combobox
|
|
120
|
+
|
|
121
|
+
def fig_width_spinbox(self):
|
|
122
|
+
if not self._fig_width_spinbox:
|
|
123
|
+
self._fig_width_spinbox = QSpinBox()
|
|
124
|
+
self._fig_width_spinbox.setValue(self.settings().get_int(f'{PANEL_NAME}/fig_width', default=10))
|
|
125
|
+
return self._fig_width_spinbox
|
|
126
|
+
|
|
127
|
+
def fig_height_spinbox(self):
|
|
128
|
+
if not self._fig_height_spinbox:
|
|
129
|
+
self._fig_height_spinbox = QSpinBox()
|
|
130
|
+
self._fig_height_spinbox.setValue(self.settings().get_int(f'{PANEL_NAME}/fig_height', default=10))
|
|
131
|
+
return self._fig_height_spinbox
|
|
132
|
+
|
|
133
|
+
def full_scan_checkbox(self):
|
|
134
|
+
if not self._full_scan_checkbox:
|
|
135
|
+
self._full_scan_checkbox = QCheckBox('')
|
|
136
|
+
self._full_scan_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/full_scan', False))
|
|
137
|
+
return self._full_scan_checkbox
|
|
138
|
+
|
|
139
|
+
def overwrite_checkbox(self):
|
|
140
|
+
if not self._overwrite_checkbox:
|
|
141
|
+
self._overwrite_checkbox = QCheckBox('')
|
|
142
|
+
self._overwrite_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/overwrite', True))
|
|
143
|
+
return self._overwrite_checkbox
|
|
144
|
+
|
|
145
|
+
def version_line_edit(self):
|
|
146
|
+
if not self._version_line_edit:
|
|
147
|
+
self._version_line_edit = QLineEdit()
|
|
148
|
+
self._version_line_edit.setText(self.settings().get(f'{PANEL_NAME}/version'))
|
|
149
|
+
return self._version_line_edit
|
|
150
|
+
|
|
151
|
+
def form_layout(self):
|
|
152
|
+
if not self._form_layout:
|
|
153
|
+
self._form_layout = QFormLayout()
|
|
154
|
+
if is_macos():
|
|
155
|
+
self._form_layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
|
|
156
|
+
return self._form_layout
|
|
157
|
+
|
|
158
|
+
def run_pipeline_button(self):
|
|
159
|
+
if not self._run_pipeline_button:
|
|
160
|
+
self._run_pipeline_button = QPushButton('Run pipeline')
|
|
161
|
+
self._run_pipeline_button.clicked.connect(self.handle_run_pipeline_button)
|
|
162
|
+
return self._run_pipeline_button
|
|
163
|
+
|
|
164
|
+
def settings(self):
|
|
165
|
+
if not self._settings:
|
|
166
|
+
self._settings = Settings()
|
|
167
|
+
return self._settings
|
|
168
|
+
|
|
169
|
+
def init_help_dialog(self):
|
|
170
|
+
self.help_dialog().set_text('Show some help information')
|
|
171
|
+
|
|
172
|
+
def init_layout(self):
|
|
173
|
+
images_dir_layout = QHBoxLayout()
|
|
174
|
+
images_dir_layout.addWidget(self.images_dir_line_edit())
|
|
175
|
+
images_dir_layout.addWidget(self.images_dir_select_button())
|
|
176
|
+
model_files_dir_layout = QHBoxLayout()
|
|
177
|
+
model_files_dir_layout.addWidget(self.model_files_dir_line_edit())
|
|
178
|
+
model_files_dir_layout.addWidget(self.model_files_dir_select_button())
|
|
179
|
+
output_dir_layout = QHBoxLayout()
|
|
180
|
+
output_dir_layout.addWidget(self.output_dir_line_edit())
|
|
181
|
+
output_dir_layout.addWidget(self.output_dir_select_button())
|
|
182
|
+
self.form_layout().addRow('Images directory', images_dir_layout)
|
|
183
|
+
self.form_layout().addRow('Model files directory', model_files_dir_layout)
|
|
184
|
+
self.form_layout().addRow('Output directory', output_dir_layout)
|
|
185
|
+
# self.form_layout().addRow('Version', self.version_line_edit())
|
|
186
|
+
self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
|
|
187
|
+
layout = QVBoxLayout()
|
|
188
|
+
layout.addLayout(self.form_layout())
|
|
189
|
+
layout.addWidget(self.run_pipeline_button())
|
|
190
|
+
self.setLayout(layout)
|
|
191
|
+
self.setObjectName(PANEL_NAME)
|
|
192
|
+
|
|
193
|
+
def handle_images_dir_select_button(self):
|
|
194
|
+
last_directory = self.settings().get('last_directory')
|
|
195
|
+
directory = QFileDialog.getExistingDirectory(dir=last_directory)
|
|
196
|
+
if directory:
|
|
197
|
+
self.images_dir_line_edit().setText(directory)
|
|
198
|
+
self.settings().set('last_directory', directory)
|
|
199
|
+
|
|
200
|
+
def handle_model_files_dir_select_button(self):
|
|
201
|
+
last_directory = self.settings().get('last_directory')
|
|
202
|
+
directory = QFileDialog.getExistingDirectory(dir=last_directory)
|
|
203
|
+
if directory:
|
|
204
|
+
self.model_files_dir_line_edit().setText(directory)
|
|
205
|
+
self.settings().set('last_directory', directory)
|
|
206
|
+
|
|
207
|
+
def handle_output_dir_select_button(self):
|
|
208
|
+
last_directory = self.settings().get('last_directory')
|
|
209
|
+
directory = QFileDialog.getExistingDirectory(dir=last_directory)
|
|
210
|
+
if directory:
|
|
211
|
+
self.output_dir_line_edit().setText(directory)
|
|
212
|
+
self.settings().set('last_directory', directory)
|
|
213
|
+
|
|
214
|
+
def handle_model_type_combobox(self, text):
|
|
215
|
+
if text == 'tensorflow':
|
|
216
|
+
self.model_version_combobox().setCurrentText('1.0')
|
|
217
|
+
if text == 'pytorch':
|
|
218
|
+
self.model_version_combobox().setCurrentText('2.2')
|
|
219
|
+
|
|
220
|
+
def handle_model_version_combobox(self, text):
|
|
221
|
+
if text == '1.0':
|
|
222
|
+
self.model_type_combobox().setCurrentText('tensorflow')
|
|
223
|
+
if text == '2.2':
|
|
224
|
+
self.model_type_combobox().setCurrentText('pytorch')
|
|
225
|
+
|
|
226
|
+
def handle_run_pipeline_button(self):
|
|
227
|
+
errors = self.check_inputs_and_parameters()
|
|
228
|
+
if len(errors) > 0:
|
|
229
|
+
error_message = 'Following errors were encountered:\n'
|
|
230
|
+
for error in errors:
|
|
231
|
+
error_message += f' - {error}\n'
|
|
232
|
+
QMessageBox.information(self, 'Error', error_message)
|
|
233
|
+
else:
|
|
234
|
+
LOG.info('Running pipeline...')
|
|
235
|
+
self.run_pipeline_button().setEnabled(False)
|
|
236
|
+
self.save_inputs_and_parameters()
|
|
237
|
+
self._task = DefaultDockerPipeline(
|
|
238
|
+
inputs={
|
|
239
|
+
'images': self.images_dir_line_edit().text(),
|
|
240
|
+
'model_files': self.model_files_dir_line_edit().text(),
|
|
241
|
+
},
|
|
242
|
+
params={
|
|
243
|
+
'file_type': 'npy',
|
|
244
|
+
'model_type': 'tensorflow',
|
|
245
|
+
'model_version': 1.0,
|
|
246
|
+
'target_size': 512,
|
|
247
|
+
'fig_width': 10,
|
|
248
|
+
'fig_height': 10,
|
|
249
|
+
'version': '2.0.10',
|
|
250
|
+
},
|
|
251
|
+
output=self.output_dir_line_edit().text(),
|
|
252
|
+
overwrite=self.overwrite_checkbox().isChecked(),
|
|
253
|
+
)
|
|
254
|
+
self._worker = Worker(self._task)
|
|
255
|
+
self._thread = QThread()
|
|
256
|
+
self._worker.moveToThread(self._thread)
|
|
257
|
+
self._thread.started.connect(self._worker.run)
|
|
258
|
+
self._worker.progress.connect(self.handle_progress)
|
|
259
|
+
self._worker.status.connect(self.handle_status)
|
|
260
|
+
self._worker.finished.connect(self.handle_finished)
|
|
261
|
+
self._worker.finished.connect(self._thread.quit)
|
|
262
|
+
self._worker.finished.connect(self._worker.deleteLater)
|
|
263
|
+
self._thread.finished.connect(self._thread.deleteLater)
|
|
264
|
+
self._thread.start()
|
|
265
|
+
|
|
266
|
+
@Slot(int)
|
|
267
|
+
def handle_progress(self, progress):
|
|
268
|
+
LOG.info(f'Progress: {progress} / 100%')
|
|
269
|
+
|
|
270
|
+
@Slot(str)
|
|
271
|
+
def handle_status(self, status):
|
|
272
|
+
LOG.info(f'Status: {status}')
|
|
273
|
+
|
|
274
|
+
@Slot()
|
|
275
|
+
def handle_finished(self):
|
|
276
|
+
self.run_pipeline_button().setEnabled(True)
|
|
277
|
+
|
|
278
|
+
def check_inputs_and_parameters(self):
|
|
279
|
+
errors = []
|
|
280
|
+
if self.images_dir_line_edit().text() == '':
|
|
281
|
+
errors.append('Empty images directory path')
|
|
282
|
+
elif not os.path.isdir(self.images_dir_line_edit().text()):
|
|
283
|
+
errors.append('Images directory does not exist')
|
|
284
|
+
if self.model_files_dir_line_edit().text() == '':
|
|
285
|
+
errors.append('Empty model files directory path')
|
|
286
|
+
elif not os.path.isdir(self.model_files_dir_line_edit().text()):
|
|
287
|
+
errors.append('Model files directory does not exist')
|
|
288
|
+
if self.output_dir_line_edit().text() == '':
|
|
289
|
+
errors.append('Empty output directory path')
|
|
290
|
+
elif os.path.isdir(self.output_dir_line_edit().text()) and not self.overwrite_checkbox().isChecked():
|
|
291
|
+
errors.append('Output directory exists but overwrite=False. Please remove output directory first')
|
|
292
|
+
# if self.version_line_edit().text() == '':
|
|
293
|
+
# errors.append('Empty version. Should be same as current Mosamatic version')
|
|
294
|
+
# if self.target_size_spinbox().value() != 512:
|
|
295
|
+
# errors.append('Target size must be 512')
|
|
296
|
+
# if self.full_scan_checkbox().isChecked():
|
|
297
|
+
# errors.append('Full scan support is not available yet')
|
|
298
|
+
return errors
|
|
299
|
+
|
|
300
|
+
def save_inputs_and_parameters(self):
|
|
301
|
+
self.settings().set(f'{PANEL_NAME}/images_dir', self.images_dir_line_edit().text())
|
|
302
|
+
self.settings().set(f'{PANEL_NAME}/model_files_dir', self.model_files_dir_line_edit().text())
|
|
303
|
+
self.settings().set(f'{PANEL_NAME}/output_dir', self.output_dir_line_edit().text())
|
|
304
|
+
# self.settings().set(f'{PANEL_NAME}/version', self.version_line_edit().text())
|
|
305
|
+
# self.settings().set(f'{PANEL_NAME}/target_size', self.target_size_spinbox().value())
|
|
306
|
+
# self.settings().set(f'{PANEL_NAME}/model_type', self.model_type_combobox().currentText())
|
|
307
|
+
# self.settings().set(f'{PANEL_NAME}/model_version', self.model_version_combobox().currentText())
|
|
308
|
+
# self.settings().set(f'{PANEL_NAME}/fig_width', self.fig_width_spinbox().value())
|
|
309
|
+
# self.settings().set(f'{PANEL_NAME}/fig_height', self.fig_height_spinbox().value())
|
|
310
|
+
# self.settings().set(f'{PANEL_NAME}/full_scan', self.full_scan_checkbox().isChecked())
|
|
311
|
+
self.settings().set(f'{PANEL_NAME}/overwrite', self.overwrite_checkbox().isChecked())
|
mosamatic2-2.0.11/src/mosamatic2/ui/widgets/panels/visualizations/slicevisualization/__init__.py
ADDED
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from mosamatic2.core.pipelines.defaultpipeline.defaultpipeline import DefaultPipeline
|
mosamatic2-2.0.10/src/mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import math
|
|
3
|
-
import tempfile
|
|
4
|
-
import shutil
|
|
5
|
-
import nibabel as nib
|
|
6
|
-
import numpy as np
|
|
7
|
-
from totalsegmentator.python_api import totalsegmentator
|
|
8
|
-
from mosamatic2.core.tasks.task import Task
|
|
9
|
-
from mosamatic2.core.managers.logmanager import LogManager
|
|
10
|
-
from mosamatic2.core.utils import load_dicom
|
|
11
|
-
|
|
12
|
-
LOG = LogManager()
|
|
13
|
-
|
|
14
|
-
TOTAL_SEGMENTATOR_OUTPUT_DIR = os.path.join(tempfile.gettempdir(), 'total_segmentator_output')
|
|
15
|
-
TOTAL_SEGMENTATOR_TASK = 'total'
|
|
16
|
-
Z_DELTA_OFFSETS = {
|
|
17
|
-
'vertebrae_L3': 0.333,
|
|
18
|
-
'vertebrae_T4': 0.5,
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class SelectSliceFromScansTask(Task):
|
|
23
|
-
INPUTS = ['scans']
|
|
24
|
-
PARAMS = ['vertebra']
|
|
25
|
-
|
|
26
|
-
def __init__(self, inputs, params, output, overwrite):
|
|
27
|
-
super(SelectSliceFromScansTask, self).__init__(inputs, params, output, overwrite)
|
|
28
|
-
|
|
29
|
-
def load_scan_dirs(self):
|
|
30
|
-
scan_dirs = []
|
|
31
|
-
for d in os.listdir(self.input('scans')):
|
|
32
|
-
scan_dir = os.path.join(self.input('scans'), d)
|
|
33
|
-
if os.path.isdir(scan_dir):
|
|
34
|
-
scan_dirs.append(scan_dir)
|
|
35
|
-
return scan_dirs
|
|
36
|
-
|
|
37
|
-
def extract_masks(self, scan_dir):
|
|
38
|
-
os.makedirs(TOTAL_SEGMENTATOR_OUTPUT_DIR, exist_ok=True)
|
|
39
|
-
totalsegmentator(input=scan_dir, output=TOTAL_SEGMENTATOR_OUTPUT_DIR, fast=True)
|
|
40
|
-
# os.system(f'TotalSegmentator -i {scan_dir} -o {TOTAL_SEGMENTATOR_OUTPUT_DIR} --fast')
|
|
41
|
-
|
|
42
|
-
def delete_total_segmentator_output(self):
|
|
43
|
-
if os.path.exists(TOTAL_SEGMENTATOR_OUTPUT_DIR):
|
|
44
|
-
shutil.rmtree(TOTAL_SEGMENTATOR_OUTPUT_DIR)
|
|
45
|
-
|
|
46
|
-
def get_z_delta_offset_for_mask(self, mask_name):
|
|
47
|
-
if mask_name not in Z_DELTA_OFFSETS.keys():
|
|
48
|
-
return None
|
|
49
|
-
return Z_DELTA_OFFSETS[mask_name]
|
|
50
|
-
|
|
51
|
-
def find_slice(self, scan_dir, vertebra):
|
|
52
|
-
if vertebra == 'L3':
|
|
53
|
-
vertebral_level = 'vertebrae_L3'
|
|
54
|
-
elif vertebra == 'T4':
|
|
55
|
-
vertebral_level = 'vertebrae_T4'
|
|
56
|
-
else:
|
|
57
|
-
raise RuntimeError(f'Unknown vertbra {vertebra}. Options are "L3" and "T4"')
|
|
58
|
-
# Find Z-positions DICOM images
|
|
59
|
-
z_positions = {}
|
|
60
|
-
for f in os.listdir(scan_dir):
|
|
61
|
-
f_path = os.path.join(scan_dir, f)
|
|
62
|
-
p = load_dicom(f_path, stop_before_pixels=True)
|
|
63
|
-
if p is not None:
|
|
64
|
-
z_positions[p.ImagePositionPatient[2]] = f_path
|
|
65
|
-
# Find Z-position L3 image
|
|
66
|
-
mask_file = os.path.join(TOTAL_SEGMENTATOR_OUTPUT_DIR, f'{vertebral_level}.nii.gz')
|
|
67
|
-
mask_obj = nib.load(mask_file)
|
|
68
|
-
mask = mask_obj.get_fdata()
|
|
69
|
-
affine_transform = mask_obj.affine
|
|
70
|
-
indexes = np.array(np.where(mask == 1))
|
|
71
|
-
index_min = indexes.min(axis=1)
|
|
72
|
-
index_max = indexes.max(axis=1)
|
|
73
|
-
world_min = nib.affines.apply_affine(affine_transform, index_min)
|
|
74
|
-
world_max = nib.affines.apply_affine(affine_transform, index_max)
|
|
75
|
-
z_direction = affine_transform[:3, 2][2]
|
|
76
|
-
z_sign = math.copysign(1, z_direction)
|
|
77
|
-
z_delta_offset = self.get_z_delta_offset_for_mask(vertebral_level)
|
|
78
|
-
if z_delta_offset is None:
|
|
79
|
-
return None
|
|
80
|
-
z_delta = 0.333 * abs(world_max[2] - world_min[2]) # This needs to be vertebra-specific perhaps
|
|
81
|
-
z_l3 = world_max[2] - z_sign * z_delta
|
|
82
|
-
# Find closest L3 image in DICOM set
|
|
83
|
-
positions = sorted(z_positions.keys())
|
|
84
|
-
closest_file = None
|
|
85
|
-
for z1, z2 in zip(positions[:-1], positions[1:]):
|
|
86
|
-
if min(z1, z2) <= z_l3 <= max(z1, z2):
|
|
87
|
-
closest_z = min(z_positions.keys(), key=lambda z: abs(z - z_l3))
|
|
88
|
-
closest_file = z_positions[closest_z]
|
|
89
|
-
LOG.info(f'Closest image: {closest_file}')
|
|
90
|
-
break
|
|
91
|
-
return closest_file
|
|
92
|
-
|
|
93
|
-
def run(self):
|
|
94
|
-
scan_dirs = self.load_scan_dirs()
|
|
95
|
-
vertebra = self.param('vertebra')
|
|
96
|
-
nr_steps = len(scan_dirs)
|
|
97
|
-
for step in range(nr_steps):
|
|
98
|
-
scan_dir = scan_dirs[step]
|
|
99
|
-
scan_name = os.path.split(scan_dir)[1]
|
|
100
|
-
try:
|
|
101
|
-
self.extract_masks(scan_dir)
|
|
102
|
-
except Exception as e:
|
|
103
|
-
LOG.warning(f'Could not extract masks from {scan_dir} [{str(e)}]')
|
|
104
|
-
self.set_progress(step, nr_steps)
|
|
105
|
-
continue
|
|
106
|
-
file_path = self.find_slice(scan_dir, vertebra)
|
|
107
|
-
if file_path is not None:
|
|
108
|
-
extension = '' if file_path.endswith('.dcm') else '.dcm'
|
|
109
|
-
target_file_path = os.path.join(self.output(), vertebra + '_' + scan_name + extension)
|
|
110
|
-
shutil.copyfile(file_path, target_file_path)
|
|
111
|
-
else:
|
|
112
|
-
LOG.error(f'Could not find slice for vertebral level: {vertebra}')
|
|
113
|
-
self.delete_total_segmentator_output()
|
|
114
|
-
self.set_progress(step, nr_steps)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2.0.10
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/commands/createpngsfromsegmentations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/commands/segmentmusclefatl3tensorflow.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mosamatic2-2.0.10/src/mosamatic2/ui/widgets → mosamatic2-2.0.11/src/mosamatic2/ui}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/resources/images/body-composition.jpg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mosamatic2-2.0.10 → mosamatic2-2.0.11}/src/mosamatic2/ui/widgets/panels/pipelines/pipelinepanel.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|