mosamatic2 2.0.13__py3-none-any.whl → 2.0.15__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/commands/totalsegmentator.py +68 -0
- mosamatic2/core/tasks/__init__.py +2 -1
- mosamatic2/core/tasks/totalsegmentatortask/__init__.py +0 -0
- mosamatic2/core/tasks/totalsegmentatortask/totalsegmentatortask.py +50 -0
- mosamatic2/core/utils.py +4 -0
- mosamatic2/ui/mainwindow.py +15 -0
- mosamatic2/ui/resources/VERSION +1 -1
- mosamatic2/ui/widgets/panels/pipelines/boadockerpipelinepanel.py +10 -0
- mosamatic2/ui/widgets/panels/pipelines/defaultdockerpipelinepanel.py +13 -0
- mosamatic2/ui/widgets/panels/tasks/totalsegmentatortaskpanel.py +195 -0
- mosamatic2/ui/widgets/panels/visualizations/slicevisualization/sliceviewer.py +2 -2
- {mosamatic2-2.0.13.dist-info → mosamatic2-2.0.15.dist-info}/METADATA +2 -1
- {mosamatic2-2.0.13.dist-info → mosamatic2-2.0.15.dist-info}/RECORD +15 -11
- {mosamatic2-2.0.13.dist-info → mosamatic2-2.0.15.dist-info}/WHEEL +0 -0
- {mosamatic2-2.0.13.dist-info → mosamatic2-2.0.15.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from mosamatic2.core.tasks import TotalSegmentatorTask
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command(help='Run Total Segmentator on CT scans')
|
|
7
|
+
@click.option(
|
|
8
|
+
'--scans',
|
|
9
|
+
required=True,
|
|
10
|
+
type=click.Path(exists=True),
|
|
11
|
+
help='Directory with scans (each patient separate subdirectory)',
|
|
12
|
+
)
|
|
13
|
+
@click.option(
|
|
14
|
+
'--output',
|
|
15
|
+
required=True,
|
|
16
|
+
type=click.Path(),
|
|
17
|
+
help='Output directory'
|
|
18
|
+
)
|
|
19
|
+
@click.option(
|
|
20
|
+
'--tasks',
|
|
21
|
+
default='total',
|
|
22
|
+
help='Comma-separated list of Total Segmentator tasks to run (no spaces!)'
|
|
23
|
+
)
|
|
24
|
+
@click.option(
|
|
25
|
+
'--overwrite',
|
|
26
|
+
type=click.BOOL,
|
|
27
|
+
default=False,
|
|
28
|
+
help='Overwrite [true|false]'
|
|
29
|
+
)
|
|
30
|
+
def totalsegmentator(scans, tasks, output, overwrite):
|
|
31
|
+
"""
|
|
32
|
+
Run Total Segmentator on CT scans. If you want to run specialized tasks
|
|
33
|
+
like "liver_segments" or "liver_vessels" you need an educational license.
|
|
34
|
+
Check out https://github.com/wasserth/TotalSegmentator?tab=readme-ov-file
|
|
35
|
+
to find out how to get such a license.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
--scans : str
|
|
40
|
+
Directory to scans. Each patient's scan should be in a separate
|
|
41
|
+
subdirectory. For example:
|
|
42
|
+
|
|
43
|
+
/scans
|
|
44
|
+
/scans/patient1
|
|
45
|
+
/scans/patient1/file1.dcm
|
|
46
|
+
/scans/patient1/file2.dcm
|
|
47
|
+
...
|
|
48
|
+
/scans/patient2
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
--output : str
|
|
52
|
+
Path to output directory where selected slices will be placed. Each
|
|
53
|
+
slice's file name will be the same as the scan directory name, so in
|
|
54
|
+
the example above that would be "patient1", "patient2", etc.
|
|
55
|
+
|
|
56
|
+
--tasks : str
|
|
57
|
+
Comma-separated list of Total Segmentator tasks to run (no spaces!)
|
|
58
|
+
|
|
59
|
+
--overwrite : bool
|
|
60
|
+
Overwrite contents output directory [true|false]
|
|
61
|
+
"""
|
|
62
|
+
task = TotalSegmentatorTask(
|
|
63
|
+
inputs={'scans': scans},
|
|
64
|
+
params={'tasks': tasks},
|
|
65
|
+
output=output,
|
|
66
|
+
overwrite=overwrite,
|
|
67
|
+
)
|
|
68
|
+
task.run()
|
|
@@ -4,4 +4,5 @@ from mosamatic2.core.tasks.calculatescorestask.calculatescorestask import Calcul
|
|
|
4
4
|
from mosamatic2.core.tasks.createpngsfromsegmentationstask.createpngsfromsegmentationstask import CreatePngsFromSegmentationsTask
|
|
5
5
|
from mosamatic2.core.tasks.dicom2niftitask.dicom2niftitask import Dicom2NiftiTask
|
|
6
6
|
from mosamatic2.core.tasks.selectslicefromscanstask.selectslicefromscanstask import SelectSliceFromScansTask
|
|
7
|
-
from mosamatic2.core.tasks.createdicomsummarytask.createdicomsummarytask import CreateDicomSummaryTask
|
|
7
|
+
from mosamatic2.core.tasks.createdicomsummarytask.createdicomsummarytask import CreateDicomSummaryTask
|
|
8
|
+
from mosamatic2.core.tasks.totalsegmentatortask.totalsegmentatortask import TotalSegmentatorTask
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import tempfile
|
|
4
|
+
from totalsegmentator.python_api import totalsegmentator
|
|
5
|
+
from mosamatic2.core.tasks.task import Task
|
|
6
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
7
|
+
|
|
8
|
+
LOG = LogManager()
|
|
9
|
+
TOTAL_SEGMENTATOR_OUTPUT_DIR = os.path.join(tempfile.gettempdir(), 'total_segmentator_output')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TotalSegmentatorTask(Task):
|
|
13
|
+
INPUTS = ['scans']
|
|
14
|
+
PARAMS = ['tasks']
|
|
15
|
+
|
|
16
|
+
def __init__(self, inputs, params, output, overwrite):
|
|
17
|
+
super(TotalSegmentatorTask, self).__init__(inputs, params, output, overwrite)
|
|
18
|
+
LOG.info(f'Using temporary output directory: {TOTAL_SEGMENTATOR_OUTPUT_DIR}')
|
|
19
|
+
|
|
20
|
+
def load_scan_dirs(self):
|
|
21
|
+
scan_dirs = []
|
|
22
|
+
for d in os.listdir(self.input('scans')):
|
|
23
|
+
scan_dir = os.path.join(self.input('scans'), d)
|
|
24
|
+
if os.path.isdir(scan_dir):
|
|
25
|
+
scan_dirs.append(scan_dir)
|
|
26
|
+
return scan_dirs
|
|
27
|
+
|
|
28
|
+
def extract_masks(self, scan_dir):
|
|
29
|
+
os.makedirs(TOTAL_SEGMENTATOR_OUTPUT_DIR, exist_ok=True)
|
|
30
|
+
tasks = self.param('tasks').split(",") if self.param('tasks') else []
|
|
31
|
+
for task in tasks:
|
|
32
|
+
LOG.info(f'Running task {task}...')
|
|
33
|
+
totalsegmentator(input=scan_dir, output=TOTAL_SEGMENTATOR_OUTPUT_DIR, task=task)
|
|
34
|
+
|
|
35
|
+
def run(self):
|
|
36
|
+
scan_dirs = self.load_scan_dirs()
|
|
37
|
+
nr_steps = len(scan_dirs)
|
|
38
|
+
for step in range(nr_steps):
|
|
39
|
+
scan_dir = scan_dirs[step]
|
|
40
|
+
try:
|
|
41
|
+
self.extract_masks(scan_dir)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
LOG.error(f'{scan_dir}: Could not extract masks [{str(e)}]. Skipping scan...')
|
|
44
|
+
self.set_progress(step, nr_steps)
|
|
45
|
+
LOG.info(f'Copying temporary output to final output directory...')
|
|
46
|
+
for f in os.listdir(TOTAL_SEGMENTATOR_OUTPUT_DIR):
|
|
47
|
+
if f.endswith('.nii') or f.endswith('.nii.gz'):
|
|
48
|
+
f_path = os.path.join(TOTAL_SEGMENTATOR_OUTPUT_DIR, f)
|
|
49
|
+
shutil.move(f_path, self.output())
|
|
50
|
+
LOG.info(f'Copied {f}')
|
mosamatic2/core/utils.py
CHANGED
mosamatic2/ui/mainwindow.py
CHANGED
|
@@ -21,6 +21,7 @@ from mosamatic2.ui.widgets.panels.tasks.calculatescorestaskpanel import Calculat
|
|
|
21
21
|
from mosamatic2.ui.widgets.panels.tasks.dicom2niftitaskpanel import Dicom2NiftiTaskPanel
|
|
22
22
|
from mosamatic2.ui.widgets.panels.tasks.createdicomsummarytaskpanel import CreateDicomSummaryTaskPanel
|
|
23
23
|
from mosamatic2.ui.widgets.panels.tasks.selectslicefromscanstaskpanel import SelectSliceFromScansTaskPanel
|
|
24
|
+
from mosamatic2.ui.widgets.panels.tasks.totalsegmentatortaskpanel import TotalSegmentatorTaskPanel
|
|
24
25
|
from mosamatic2.ui.widgets.panels.pipelines.defaultpipelinepanel import DefaultPipelinePanel
|
|
25
26
|
from mosamatic2.ui.widgets.panels.pipelines.defaultdockerpipelinepanel import DefaultDockerPipelinePanel
|
|
26
27
|
from mosamatic2.ui.widgets.panels.pipelines.boadockerpipelinepanel import BoaDockerPipelinePanel
|
|
@@ -43,6 +44,7 @@ class MainWindow(QMainWindow):
|
|
|
43
44
|
self._dicom2nifti_task_panel = None
|
|
44
45
|
self._create_dicom_summary_task_panel = None
|
|
45
46
|
self._select_slice_from_scans_task_panel = None
|
|
47
|
+
self._total_segmentator_task_panel = None
|
|
46
48
|
self._default_pipeline_panel = None
|
|
47
49
|
self._default_docker_pipeline_panel = None
|
|
48
50
|
self._boa_docker_pipeline_panel = None
|
|
@@ -90,6 +92,8 @@ class MainWindow(QMainWindow):
|
|
|
90
92
|
create_dicom_summary_task_action.triggered.connect(self.handle_create_dicom_summary_task_action)
|
|
91
93
|
select_slice_from_scans_task_action = QAction('SelectSliceFromScansTask', self)
|
|
92
94
|
select_slice_from_scans_task_action.triggered.connect(self.handle_select_slice_from_scans_task_action)
|
|
95
|
+
total_segmentator_task_action = QAction('TotalSegmentatorTask', self)
|
|
96
|
+
total_segmentator_task_action.triggered.connect(self.handle_total_segmentator_task_action)
|
|
93
97
|
tasks_menu = self.menuBar().addMenu('Tasks')
|
|
94
98
|
tasks_menu.addAction(rescale_dicom_images_task_action)
|
|
95
99
|
tasks_menu.addAction(segment_muscle_fat_l3_tensorflow_task_action)
|
|
@@ -98,6 +102,7 @@ class MainWindow(QMainWindow):
|
|
|
98
102
|
tasks_menu.addAction(dicom2nifti_task_action)
|
|
99
103
|
tasks_menu.addAction(create_dicom_summary_task_action)
|
|
100
104
|
tasks_menu.addAction(select_slice_from_scans_task_action)
|
|
105
|
+
tasks_menu.addAction(total_segmentator_task_action)
|
|
101
106
|
|
|
102
107
|
def init_pipelines_menu(self):
|
|
103
108
|
default_pipeline_action = QAction('DefaultPipeline', self)
|
|
@@ -137,6 +142,7 @@ class MainWindow(QMainWindow):
|
|
|
137
142
|
self._main_panel.add_panel(self.dicom2nifti_task_panel(), 'dicom2niftitaskpanel')
|
|
138
143
|
self._main_panel.add_panel(self.create_dicom_summary_task_panel(), 'createdicomsummarytaskpanel')
|
|
139
144
|
self._main_panel.add_panel(self.select_slice_from_scans_task_panel(), 'selectslicefromscanstaskpanel')
|
|
145
|
+
self._main_panel.add_panel(self.total_segmentator_task_panel(), 'totalsegmentatortaskpanel')
|
|
140
146
|
self._main_panel.add_panel(self.default_pipeline_panel(), 'defaultpipelinepanel')
|
|
141
147
|
self._main_panel.add_panel(self.default_docker_pipeline_panel(), 'defaultdockerpipelinepanel')
|
|
142
148
|
self._main_panel.add_panel(self.boa_docker_pipeline_panel(), 'boadockerpipelinepanel')
|
|
@@ -186,6 +192,11 @@ class MainWindow(QMainWindow):
|
|
|
186
192
|
if not self._select_slice_from_scans_task_panel:
|
|
187
193
|
self._select_slice_from_scans_task_panel = SelectSliceFromScansTaskPanel()
|
|
188
194
|
return self._select_slice_from_scans_task_panel
|
|
195
|
+
|
|
196
|
+
def total_segmentator_task_panel(self):
|
|
197
|
+
if not self._total_segmentator_task_panel:
|
|
198
|
+
self._total_segmentator_task_panel = TotalSegmentatorTaskPanel()
|
|
199
|
+
return self._total_segmentator_task_panel
|
|
189
200
|
|
|
190
201
|
def default_pipeline_panel(self):
|
|
191
202
|
if not self._default_pipeline_panel:
|
|
@@ -235,6 +246,9 @@ class MainWindow(QMainWindow):
|
|
|
235
246
|
def handle_select_slice_from_scans_task_action(self):
|
|
236
247
|
self.main_panel().select_panel('selectslicefromscanstaskpanel')
|
|
237
248
|
|
|
249
|
+
def handle_total_segmentator_task_action(self):
|
|
250
|
+
self.main_panel().select_panel('totalsegmentatortaskpanel')
|
|
251
|
+
|
|
238
252
|
def handle_default_pipeline_action(self):
|
|
239
253
|
self.main_panel().select_panel('defaultpipelinepanel')
|
|
240
254
|
|
|
@@ -260,6 +274,7 @@ class MainWindow(QMainWindow):
|
|
|
260
274
|
self.dicom2nifti_task_panel().save_inputs_and_parameters()
|
|
261
275
|
self.create_dicom_summary_task_panel().save_inputs_and_parameters()
|
|
262
276
|
self.select_slice_from_scans_task_panel().save_inputs_and_parameters()
|
|
277
|
+
self.total_segmentator_task_panel().save_inputs_and_parameters()
|
|
263
278
|
self.default_pipeline_panel().save_inputs_and_parameters()
|
|
264
279
|
self.default_docker_pipeline_panel().save_inputs_and_parameters()
|
|
265
280
|
self.boa_docker_pipeline_panel().save_inputs_and_parameters()
|
mosamatic2/ui/resources/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.0.
|
|
1
|
+
2.0.15
|
|
@@ -16,6 +16,7 @@ from PySide6.QtCore import (
|
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
from mosamatic2.core.managers.logmanager import LogManager
|
|
19
|
+
from mosamatic2.core.utils import is_docker_running, is_path_docker_compatible
|
|
19
20
|
from mosamatic2.ui.widgets.panels.pipelines.pipelinepanel import PipelinePanel
|
|
20
21
|
from mosamatic2.ui.settings import Settings
|
|
21
22
|
from mosamatic2.ui.utils import is_macos
|
|
@@ -127,6 +128,15 @@ class BoaDockerPipelinePanel(PipelinePanel):
|
|
|
127
128
|
self.settings().set('last_directory', directory)
|
|
128
129
|
|
|
129
130
|
def handle_run_pipeline_button(self):
|
|
131
|
+
if not is_docker_running():
|
|
132
|
+
QMessageBox.information(self, 'Error', 'Docker is not running. Please start Docker Desktop first')
|
|
133
|
+
return
|
|
134
|
+
if not is_path_docker_compatible(self.scans_dir_line_edit().text()):
|
|
135
|
+
QMessageBox.information(self, 'Error', 'Path to scans directory contains spaces and is not Docker compatible')
|
|
136
|
+
return
|
|
137
|
+
if not is_path_docker_compatible(self.output_dir_line_edit().text()):
|
|
138
|
+
QMessageBox.information(self, 'Error', 'Path to output directory contains spaces and is not Docker compatible')
|
|
139
|
+
return
|
|
130
140
|
errors = self.check_inputs_and_parameters()
|
|
131
141
|
if len(errors) > 0:
|
|
132
142
|
error_message = 'Following errors were encountered:\n'
|
|
@@ -18,6 +18,7 @@ from PySide6.QtCore import (
|
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
from mosamatic2.core.managers.logmanager import LogManager
|
|
21
|
+
from mosamatic2.core.utils import is_docker_running, is_path_docker_compatible
|
|
21
22
|
from mosamatic2.ui.widgets.panels.pipelines.pipelinepanel import PipelinePanel
|
|
22
23
|
from mosamatic2.ui.settings import Settings
|
|
23
24
|
from mosamatic2.ui.utils import is_macos
|
|
@@ -223,6 +224,18 @@ class DefaultDockerPipelinePanel(PipelinePanel):
|
|
|
223
224
|
self.model_type_combobox().setCurrentText('pytorch')
|
|
224
225
|
|
|
225
226
|
def handle_run_pipeline_button(self):
|
|
227
|
+
if not is_docker_running():
|
|
228
|
+
QMessageBox.information(self, 'Error', 'Docker is not running. Please start Docker Desktop first')
|
|
229
|
+
return
|
|
230
|
+
if not is_path_docker_compatible(self.images_dir_line_edit().text()):
|
|
231
|
+
QMessageBox.information(self, 'Error', 'Path to images directory contains spaces and is not Docker compatible')
|
|
232
|
+
return
|
|
233
|
+
if not is_path_docker_compatible(self.model_files_dir_line_edit().text()):
|
|
234
|
+
QMessageBox.information(self, 'Error', 'Path to model files directory contains spaces and is not Docker compatible')
|
|
235
|
+
return
|
|
236
|
+
if not is_path_docker_compatible(self.output_dir_line_edit().text()):
|
|
237
|
+
QMessageBox.information(self, 'Error', 'Path to output directory contains spaces and is not Docker compatible')
|
|
238
|
+
return
|
|
226
239
|
errors = self.check_inputs_and_parameters()
|
|
227
240
|
if len(errors) > 0:
|
|
228
241
|
error_message = 'Following errors were encountered:\n'
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from PySide6.QtWidgets import (
|
|
4
|
+
QLineEdit,
|
|
5
|
+
QCheckBox,
|
|
6
|
+
QComboBox,
|
|
7
|
+
QHBoxLayout,
|
|
8
|
+
QVBoxLayout,
|
|
9
|
+
QFormLayout,
|
|
10
|
+
QPushButton,
|
|
11
|
+
QFileDialog,
|
|
12
|
+
QMessageBox,
|
|
13
|
+
)
|
|
14
|
+
from PySide6.QtCore import (
|
|
15
|
+
QThread,
|
|
16
|
+
Slot,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
20
|
+
from mosamatic2.ui.widgets.panels.tasks.taskpanel import TaskPanel
|
|
21
|
+
from mosamatic2.ui.settings import Settings
|
|
22
|
+
from mosamatic2.ui.utils import is_macos
|
|
23
|
+
from mosamatic2.ui.worker import Worker
|
|
24
|
+
from mosamatic2.core.tasks import TotalSegmentatorTask
|
|
25
|
+
|
|
26
|
+
LOG = LogManager()
|
|
27
|
+
|
|
28
|
+
PANEL_TITLE = 'TotalSegmentatorTaskPanel'
|
|
29
|
+
PANEL_NAME = 'totalsegmentatortaskpanel'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TotalSegmentatorTaskPanel(TaskPanel):
|
|
33
|
+
def __init__(self):
|
|
34
|
+
super(TotalSegmentatorTaskPanel, self).__init__()
|
|
35
|
+
self.set_title(PANEL_TITLE)
|
|
36
|
+
self._scans_dir_line_edit = None
|
|
37
|
+
self._scans_dir_select_button = None
|
|
38
|
+
self._tasks_line_edit = None
|
|
39
|
+
self._output_dir_line_edit = None
|
|
40
|
+
self._output_dir_select_button = None
|
|
41
|
+
self._overwrite_checkbox = None
|
|
42
|
+
self._form_layout = None
|
|
43
|
+
self._run_task_button = None
|
|
44
|
+
self._settings = None
|
|
45
|
+
self._task = None
|
|
46
|
+
self._worker = None
|
|
47
|
+
self._thread = None
|
|
48
|
+
self.init_layout()
|
|
49
|
+
|
|
50
|
+
def scans_dir_line_edit(self):
|
|
51
|
+
if not self._scans_dir_line_edit:
|
|
52
|
+
self._scans_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/scans_dir'))
|
|
53
|
+
return self._scans_dir_line_edit
|
|
54
|
+
|
|
55
|
+
def scans_dir_select_button(self):
|
|
56
|
+
if not self._scans_dir_select_button:
|
|
57
|
+
self._scans_dir_select_button = QPushButton('Select')
|
|
58
|
+
self._scans_dir_select_button.clicked.connect(self.handle_scans_dir_select_button)
|
|
59
|
+
return self._scans_dir_select_button
|
|
60
|
+
|
|
61
|
+
def tasks_line_edit(self):
|
|
62
|
+
if not self._tasks_line_edit:
|
|
63
|
+
self._tasks_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/tasks'))
|
|
64
|
+
if self._tasks_line_edit.text() == '':
|
|
65
|
+
self._tasks_line_edit.setText('total')
|
|
66
|
+
return self._tasks_line_edit
|
|
67
|
+
|
|
68
|
+
def output_dir_line_edit(self):
|
|
69
|
+
if not self._output_dir_line_edit:
|
|
70
|
+
self._output_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/output_dir'))
|
|
71
|
+
return self._output_dir_line_edit
|
|
72
|
+
|
|
73
|
+
def output_dir_select_button(self):
|
|
74
|
+
if not self._output_dir_select_button:
|
|
75
|
+
self._output_dir_select_button = QPushButton('Select')
|
|
76
|
+
self._output_dir_select_button.clicked.connect(self.handle_output_dir_select_button)
|
|
77
|
+
return self._output_dir_select_button
|
|
78
|
+
|
|
79
|
+
def overwrite_checkbox(self):
|
|
80
|
+
if not self._overwrite_checkbox:
|
|
81
|
+
self._overwrite_checkbox = QCheckBox('')
|
|
82
|
+
self._overwrite_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/overwrite', True))
|
|
83
|
+
return self._overwrite_checkbox
|
|
84
|
+
|
|
85
|
+
def form_layout(self):
|
|
86
|
+
if not self._form_layout:
|
|
87
|
+
self._form_layout = QFormLayout()
|
|
88
|
+
if is_macos():
|
|
89
|
+
self._form_layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
|
|
90
|
+
return self._form_layout
|
|
91
|
+
|
|
92
|
+
def run_task_button(self):
|
|
93
|
+
if not self._run_task_button:
|
|
94
|
+
self._run_task_button = QPushButton('Run task')
|
|
95
|
+
self._run_task_button.clicked.connect(self.handle_run_task_button)
|
|
96
|
+
return self._run_task_button
|
|
97
|
+
|
|
98
|
+
def settings(self):
|
|
99
|
+
if not self._settings:
|
|
100
|
+
self._settings = Settings()
|
|
101
|
+
return self._settings
|
|
102
|
+
|
|
103
|
+
def init_layout(self):
|
|
104
|
+
scans_dir_layout = QHBoxLayout()
|
|
105
|
+
scans_dir_layout.addWidget(self.scans_dir_line_edit())
|
|
106
|
+
scans_dir_layout.addWidget(self.scans_dir_select_button())
|
|
107
|
+
output_dir_layout = QHBoxLayout()
|
|
108
|
+
output_dir_layout.addWidget(self.output_dir_line_edit())
|
|
109
|
+
output_dir_layout.addWidget(self.output_dir_select_button())
|
|
110
|
+
self.form_layout().addRow('Scans directory', scans_dir_layout)
|
|
111
|
+
self.form_layout().addRow('Tasks', self.tasks_line_edit())
|
|
112
|
+
self.form_layout().addRow('Output directory', output_dir_layout)
|
|
113
|
+
self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
|
|
114
|
+
layout = QVBoxLayout()
|
|
115
|
+
layout.addLayout(self.form_layout())
|
|
116
|
+
layout.addWidget(self.run_task_button())
|
|
117
|
+
self.setLayout(layout)
|
|
118
|
+
self.setObjectName(PANEL_NAME)
|
|
119
|
+
|
|
120
|
+
def handle_scans_dir_select_button(self):
|
|
121
|
+
last_directory = self.settings().get('last_directory')
|
|
122
|
+
directory = QFileDialog.getExistingDirectory(dir=last_directory)
|
|
123
|
+
if directory:
|
|
124
|
+
self.scans_dir_line_edit().setText(directory)
|
|
125
|
+
self.settings().set('last_directory', directory)
|
|
126
|
+
|
|
127
|
+
def handle_output_dir_select_button(self):
|
|
128
|
+
last_directory = self.settings().get('last_directory')
|
|
129
|
+
directory = QFileDialog.getExistingDirectory(dir=last_directory)
|
|
130
|
+
if directory:
|
|
131
|
+
self.output_dir_line_edit().setText(directory)
|
|
132
|
+
self.settings().set('last_directory', directory)
|
|
133
|
+
|
|
134
|
+
def handle_run_task_button(self):
|
|
135
|
+
errors = self.check_inputs_and_parameters()
|
|
136
|
+
if len(errors) > 0:
|
|
137
|
+
error_message = 'Following errors were encountered:\n'
|
|
138
|
+
for error in errors:
|
|
139
|
+
error_message += f' - {error}\n'
|
|
140
|
+
QMessageBox.information(self, 'Error', error_message)
|
|
141
|
+
else:
|
|
142
|
+
LOG.info('Running task...')
|
|
143
|
+
self.run_task_button().setEnabled(False)
|
|
144
|
+
self.save_inputs_and_parameters()
|
|
145
|
+
self._task = TotalSegmentatorTask(
|
|
146
|
+
inputs={'scans': self.scans_dir_line_edit().text()},
|
|
147
|
+
params={'tasks': self.tasks_line_edit().text()},
|
|
148
|
+
output=self.output_dir_line_edit().text(),
|
|
149
|
+
overwrite=self.overwrite_checkbox().isChecked(),
|
|
150
|
+
)
|
|
151
|
+
self._worker = Worker(self._task)
|
|
152
|
+
self._thread = QThread()
|
|
153
|
+
self._worker.moveToThread(self._thread)
|
|
154
|
+
self._thread.started.connect(self._worker.run)
|
|
155
|
+
self._worker.progress.connect(self.handle_progress)
|
|
156
|
+
self._worker.status.connect(self.handle_status)
|
|
157
|
+
self._worker.finished.connect(self.handle_finished)
|
|
158
|
+
self._worker.finished.connect(self._thread.quit)
|
|
159
|
+
self._worker.finished.connect(self._worker.deleteLater)
|
|
160
|
+
self._thread.finished.connect(self._thread.deleteLater)
|
|
161
|
+
self._thread.start()
|
|
162
|
+
|
|
163
|
+
@Slot(int)
|
|
164
|
+
def handle_progress(self, progress):
|
|
165
|
+
LOG.info(f'Progress: {progress} / 100%')
|
|
166
|
+
|
|
167
|
+
@Slot(str)
|
|
168
|
+
def handle_status(self, status):
|
|
169
|
+
LOG.info(f'Status: {status}')
|
|
170
|
+
|
|
171
|
+
@Slot()
|
|
172
|
+
def handle_finished(self):
|
|
173
|
+
self.run_task_button().setEnabled(True)
|
|
174
|
+
|
|
175
|
+
# HELPERS
|
|
176
|
+
|
|
177
|
+
def check_inputs_and_parameters(self):
|
|
178
|
+
errors = []
|
|
179
|
+
if self.scans_dir_line_edit().text() == '':
|
|
180
|
+
errors.append('Empty scans directory path')
|
|
181
|
+
if not os.path.isdir(self.scans_dir_line_edit().text()):
|
|
182
|
+
errors.append('Scans directory does not exist')
|
|
183
|
+
if self.output_dir_line_edit().text() == '':
|
|
184
|
+
errors.append('Empty output directory path')
|
|
185
|
+
if os.path.isdir(self.output_dir_line_edit().text()) and not self.overwrite_checkbox().isChecked():
|
|
186
|
+
errors.append('Output directory exists but overwrite=False. Please remove output directory first')
|
|
187
|
+
if self.tasks_line_edit().text() == '':
|
|
188
|
+
errors.append('Tasks cannot be empty and must be comma-separated list of values')
|
|
189
|
+
return errors
|
|
190
|
+
|
|
191
|
+
def save_inputs_and_parameters(self):
|
|
192
|
+
self.settings().set(f'{PANEL_NAME}/scans_dir', self.scans_dir_line_edit().text())
|
|
193
|
+
self.settings().set(f'{PANEL_NAME}/tasks', self.tasks_line_edit().text())
|
|
194
|
+
self.settings().set(f'{PANEL_NAME}/output_dir', self.output_dir_line_edit().text())
|
|
195
|
+
self.settings().set(f'{PANEL_NAME}/overwrite', self.overwrite_checkbox().isChecked())
|
|
@@ -80,9 +80,9 @@ class SliceViewer(QWidget):
|
|
|
80
80
|
slice.SetMapper(slice_mapper)
|
|
81
81
|
slice_text_actor = self.create_text_actor("", 0.01, 0.01, 12, align_bottom=True, normalized=True)
|
|
82
82
|
usage_text_actor = self.create_text_actor(
|
|
83
|
-
"- Slice with mouse wheel or Up/Down
|
|
83
|
+
"- Slice with mouse wheel or Up/Down keys (first click inside viewer)\n"
|
|
84
84
|
"- Zoom with pressed right mouse button while dragging\n"
|
|
85
|
-
"- Pan with
|
|
85
|
+
"- Pan with Shift key and left mouse button while dragging\n"
|
|
86
86
|
"- Change contrast/brightness with pressed left mouse while dragging",
|
|
87
87
|
0.01, 0.99, 12, normalized=True)
|
|
88
88
|
ren = vtk.vtkRenderer()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: mosamatic2
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.15
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Ralph Brecheisen
|
|
6
6
|
Author-email: r.brecheisen@maastrichtuniversity.nl
|
|
@@ -23,6 +23,7 @@ Requires-Dist: pyqtgraph (>=0.13.7)
|
|
|
23
23
|
Requires-Dist: pyside6-essentials (>=6.9)
|
|
24
24
|
Requires-Dist: python-gdcm (>=3.0.26)
|
|
25
25
|
Requires-Dist: scipy (>=1.15.3)
|
|
26
|
+
Requires-Dist: slicer
|
|
26
27
|
Requires-Dist: tensorboard (==2.15.2)
|
|
27
28
|
Requires-Dist: tensorboard-data-server (==0.7.2)
|
|
28
29
|
Requires-Dist: tensorflow (==2.15.*) ; platform_system == "Linux"
|
|
@@ -13,6 +13,7 @@ mosamatic2/commands/dicom2nifti.py,sha256=uzI3-QT8_HtwXKkX5kRJzMKssM5dvJGUjZ1Z-X
|
|
|
13
13
|
mosamatic2/commands/rescaledicomimages.py,sha256=25QdCzB5s0sRwkTb3o5zco2bIwy6LttNf7i97kGBDYQ,1280
|
|
14
14
|
mosamatic2/commands/segmentmusclefatl3tensorflow.py,sha256=CdScmA_EQicaN4GY5bBUOYwfhDPqy9om2sxY3WrtmM0,1424
|
|
15
15
|
mosamatic2/commands/selectslicefromscans.py,sha256=3398PM2uBcxF6wpb0-c-Itp_qxoAxBf0SE2nDDI3Ct4,1715
|
|
16
|
+
mosamatic2/commands/totalsegmentator.py,sha256=GY8wAFDHJW-dMWa3ZskHJnaYd71bKWUjpwV2-Wf38W4,1956
|
|
16
17
|
mosamatic2/constants.py,sha256=MVYMwO-x2jQSN37o3zNkRseVQ1nYRA3mLv3v_Q0Mlds,1284
|
|
17
18
|
mosamatic2/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
19
|
mosamatic2/core/data/__init__.py,sha256=j9iGqUTJlGF0N0gPrzzpe_Dhv0Bj9c6FdQ1g7U-_j2g,298
|
|
@@ -37,7 +38,7 @@ mosamatic2/core/pipelines/defaultpipeline/__init__.py,sha256=47DEQpj8HBSa-_TImW-
|
|
|
37
38
|
mosamatic2/core/pipelines/defaultpipeline/defaultpipeline.py,sha256=Bme0r_shnrllWYCYDNc6cLM2fQC2yD8RJKpRdoh_6Uc,3077
|
|
38
39
|
mosamatic2/core/pipelines/pipeline.py,sha256=mRxKXLKwgKDpc8R9mCI6gDKGJ2lKVxRQ__Sf0Mfn_Qc,384
|
|
39
40
|
mosamatic2/core/singleton.py,sha256=FV0k_LlOCmFhlWN6gf1c2x7YXWyd8-7DsIMvOKrI6NY,224
|
|
40
|
-
mosamatic2/core/tasks/__init__.py,sha256=
|
|
41
|
+
mosamatic2/core/tasks/__init__.py,sha256=iJ4uMgLtN-PmZ43FAw8Y_QLh03ctiTaCGZxAUsVFXPM,857
|
|
41
42
|
mosamatic2/core/tasks/calculatescorestask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
43
|
mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py,sha256=cwGVedJR_BGSYzXq6ouTgCbSC6s2VtyD8FzRC-QBXUI,6617
|
|
43
44
|
mosamatic2/core/tasks/createdicomsummarytask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -54,15 +55,17 @@ mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorf
|
|
|
54
55
|
mosamatic2/core/tasks/selectslicefromscanstask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
55
56
|
mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py,sha256=EIEHhWoGL30rcz8qckFl465rU40P-pIkvhMOfSud7Yw,7253
|
|
56
57
|
mosamatic2/core/tasks/task.py,sha256=APPnid6dpSGkPuDqU1vm2RIMR5vkpvbP1CPHUMjympg,1691
|
|
57
|
-
mosamatic2/core/
|
|
58
|
+
mosamatic2/core/tasks/totalsegmentatortask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
|
+
mosamatic2/core/tasks/totalsegmentatortask/totalsegmentatortask.py,sha256=Yzy-V6psjhmt-idvLxBoUmbyQGnCBb0OiD0s9lXLmWk,2096
|
|
60
|
+
mosamatic2/core/utils.py,sha256=4duREimHWkUfZIjSy16fsxF8-3ZxukfOi8eSNjAdxF8,12240
|
|
58
61
|
mosamatic2/server.py,sha256=-cZ9BPsZUXoINKqwhCHN8c59mlvzzDXzTVxsYt9au70,4644
|
|
59
62
|
mosamatic2/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
|
-
mosamatic2/ui/mainwindow.py,sha256=
|
|
63
|
+
mosamatic2/ui/mainwindow.py,sha256=DrX-6Hu4-3wJJy6rZkexYuTQ-pO_uliyEQVkYc4GuCM,15848
|
|
61
64
|
mosamatic2/ui/resources/icons/mosamatic2.icns,sha256=OfhC-diJTIgaNMOezxKKilGsY7mRkaGdU5dGr0MOjIA,2994125
|
|
62
65
|
mosamatic2/ui/resources/icons/mosamatic2.ico,sha256=ySD3RYluHK3pgS0Eas7eKrVk_AskdLQ4qs_IT-wNhq4,12229
|
|
63
66
|
mosamatic2/ui/resources/icons/spinner.gif,sha256=rvaac6GUZauHSPFSOLWr0RmLfjmtZih2Q8knQ2WP3Po,16240
|
|
64
67
|
mosamatic2/ui/resources/images/body-composition.jpg,sha256=KD-BudbXwThB4lJOZZN-ad5-TZRaaZ5cKTH0Ar1TOZs,21227
|
|
65
|
-
mosamatic2/ui/resources/VERSION,sha256=
|
|
68
|
+
mosamatic2/ui/resources/VERSION,sha256=Ot4hy-wRNaoAwSuWw00gYSDe184QrE6GG0RezRKfkr0,9
|
|
66
69
|
mosamatic2/ui/settings.py,sha256=YEVHYJIfNsqMO3v1pjzgh7Pih9GGoUX7S9s8S-sBNUk,2121
|
|
67
70
|
mosamatic2/ui/utils.py,sha256=6bbPIrh4RJ_yhQKNZrgPbL4XeUEogjIjbk_e5c3QS5g,853
|
|
68
71
|
mosamatic2/ui/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -74,8 +77,8 @@ mosamatic2/ui/widgets/panels/defaultpanel.py,sha256=Ry32Xv6ibxm6NdZ7eBOCcisWNcmn
|
|
|
74
77
|
mosamatic2/ui/widgets/panels/logpanel.py,sha256=ogswJ6_ryb6u7JeVnOsh2Ez8KWg6jtCFZwij8s87xO4,1861
|
|
75
78
|
mosamatic2/ui/widgets/panels/mainpanel.py,sha256=KqI8dA7GpLFd2unqVRTBkNxdnh6AWGpVPwQuaEg8PmI,2431
|
|
76
79
|
mosamatic2/ui/widgets/panels/pipelines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
77
|
-
mosamatic2/ui/widgets/panels/pipelines/boadockerpipelinepanel.py,sha256=
|
|
78
|
-
mosamatic2/ui/widgets/panels/pipelines/defaultdockerpipelinepanel.py,sha256=
|
|
80
|
+
mosamatic2/ui/widgets/panels/pipelines/boadockerpipelinepanel.py,sha256=f1VKob6SRi8dO2m1HF4JXwV_EkrzYWgTdkIOILDD5y4,8406
|
|
81
|
+
mosamatic2/ui/widgets/panels/pipelines/defaultdockerpipelinepanel.py,sha256=VAYx8Ksb9yVaA2GngB5h-sJNfALXeQMmWqOEXQNCjKY,14215
|
|
79
82
|
mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py,sha256=qkfI4BnLIXqE_YvSQj4sO_FjnK0eVdIMqAZ8sktgI-8,13727
|
|
80
83
|
mosamatic2/ui/widgets/panels/pipelines/pipelinepanel.py,sha256=SlkKme8Wv2Bvp2Alen98mFjv3F5eZCwJylj294gd5uU,178
|
|
81
84
|
mosamatic2/ui/widgets/panels/stackedpanel.py,sha256=dK1YWuHUzxRhVb5gP0Lu9rAiW4XagjcHmGF__5Lpufk,657
|
|
@@ -88,15 +91,16 @@ mosamatic2/ui/widgets/panels/tasks/rescaledicomimagestaskpanel.py,sha256=ds7Jxyn
|
|
|
88
91
|
mosamatic2/ui/widgets/panels/tasks/segmentmusclefatl3tensorflowtaskpanel.py,sha256=QCEZs9lqaE-XAJuyyrfZVnFkNRyjMw6Cfa-6qP9WaV8,9630
|
|
89
92
|
mosamatic2/ui/widgets/panels/tasks/selectslicefromscanstaskpanel.py,sha256=meKltgxPReZ9HioSop6jW_2CFm18URBy3LX11U8tbtc,8059
|
|
90
93
|
mosamatic2/ui/widgets/panels/tasks/taskpanel.py,sha256=t8lIx1P8sS1Fa-aNm6eEha6297pJQNbBRizkobBexz8,170
|
|
94
|
+
mosamatic2/ui/widgets/panels/tasks/totalsegmentatortaskpanel.py,sha256=6jXjHSlnyaWiei5LF08cR8vnEc5-OtbMpAlqpxPQZ-0,8169
|
|
91
95
|
mosamatic2/ui/widgets/panels/visualizations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
92
96
|
mosamatic2/ui/widgets/panels/visualizations/slicevisualization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
93
97
|
mosamatic2/ui/widgets/panels/visualizations/slicevisualization/custominteractorstyle.py,sha256=GaXmO5SvXwLAnrVtAGglTXgPx6mD0Q6f8gYrGN5WRO4,2644
|
|
94
|
-
mosamatic2/ui/widgets/panels/visualizations/slicevisualization/sliceviewer.py,sha256=
|
|
98
|
+
mosamatic2/ui/widgets/panels/visualizations/slicevisualization/sliceviewer.py,sha256=yMjKzV_r6oPDMtz4-bLSWqTUBv42Jl4uHV-T3H51Xuo,4613
|
|
95
99
|
mosamatic2/ui/widgets/panels/visualizations/slicevisualization/slicevisualization.py,sha256=2cLTHsGrdMMt_dbnq0EUAFSSW7naR6B1neB7RnUOmjQ,4214
|
|
96
100
|
mosamatic2/ui/widgets/panels/visualizations/visualization.py,sha256=JvqTJi7cCGYK1-wrN2oURdCOBoPS2clVUyYglhkoVJg,178
|
|
97
101
|
mosamatic2/ui/widgets/splashscreen.py,sha256=MS-OczOWfwwEQNQd-JWe9_Mh57css0cSQgbu973rwQo,4056
|
|
98
102
|
mosamatic2/ui/worker.py,sha256=v7e3gq7MUudgpB1BJW-P7j5wurzu6-HG5m7I6WHgJp0,699
|
|
99
|
-
mosamatic2-2.0.
|
|
100
|
-
mosamatic2-2.0.
|
|
101
|
-
mosamatic2-2.0.
|
|
102
|
-
mosamatic2-2.0.
|
|
103
|
+
mosamatic2-2.0.15.dist-info/entry_points.txt,sha256=MCUpKkgbej1clgp8EqlLQGs0BIKwGPcBPiVWLfGz9Gw,126
|
|
104
|
+
mosamatic2-2.0.15.dist-info/METADATA,sha256=AJb3ASnhH6ZN0w6cdvvJThNJcaJCuC3thrcAf234dQM,1534
|
|
105
|
+
mosamatic2-2.0.15.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
106
|
+
mosamatic2-2.0.15.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|