mosamatic2 2.0.12__py3-none-any.whl → 2.0.14__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/cli.py +4 -0
- mosamatic2/commands/boadockerpipeline.py +48 -0
- mosamatic2/commands/defaultdockerpipeline.py +73 -0
- mosamatic2/commands/dicom2nifti.py +11 -2
- mosamatic2/commands/totalsegmentator.py +68 -0
- mosamatic2/core/pipelines/__init__.py +2 -1
- mosamatic2/core/pipelines/boadockerpipeline/__init__.py +0 -0
- mosamatic2/core/pipelines/boadockerpipeline/boadockerpipeline.py +70 -0
- mosamatic2/core/pipelines/defaultdockerpipeline/defaultdockerpipeline.py +5 -12
- mosamatic2/core/tasks/__init__.py +2 -1
- mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py +5 -2
- mosamatic2/core/tasks/totalsegmentatortask/__init__.py +0 -0
- mosamatic2/core/tasks/totalsegmentatortask/totalsegmentatortask.py +50 -0
- mosamatic2/core/utils.py +15 -1
- mosamatic2/ui/mainwindow.py +30 -0
- mosamatic2/ui/resources/VERSION +1 -1
- mosamatic2/ui/widgets/panels/pipelines/boadockerpipelinepanel.py +185 -0
- mosamatic2/ui/widgets/panels/pipelines/defaultdockerpipelinepanel.py +0 -14
- mosamatic2/ui/widgets/panels/tasks/totalsegmentatortaskpanel.py +195 -0
- mosamatic2/ui/widgets/panels/visualizations/slicevisualization/sliceviewer.py +2 -2
- {mosamatic2-2.0.12.dist-info → mosamatic2-2.0.14.dist-info}/METADATA +3 -1
- {mosamatic2-2.0.12.dist-info → mosamatic2-2.0.14.dist-info}/RECORD +24 -15
- {mosamatic2-2.0.12.dist-info → mosamatic2-2.0.14.dist-info}/WHEEL +0 -0
- {mosamatic2-2.0.12.dist-info → mosamatic2-2.0.14.dist-info}/entry_points.txt +0 -0
mosamatic2/cli.py
CHANGED
|
@@ -8,6 +8,8 @@ from mosamatic2.commands import (
|
|
|
8
8
|
selectslicefromscans,
|
|
9
9
|
createdicomsummary,
|
|
10
10
|
defaultpipeline,
|
|
11
|
+
defaultdockerpipeline,
|
|
12
|
+
boadockerpipeline,
|
|
11
13
|
)
|
|
12
14
|
from mosamatic2.core.utils import show_doc_command
|
|
13
15
|
|
|
@@ -37,4 +39,6 @@ main.add_command(dicom2nifti.dicom2nifti)
|
|
|
37
39
|
main.add_command(selectslicefromscans.selectslicefromscans)
|
|
38
40
|
main.add_command(createdicomsummary.createdicomsummary)
|
|
39
41
|
main.add_command(defaultpipeline.defaultpipeline)
|
|
42
|
+
main.add_command(defaultdockerpipeline.defaultdockerpipeline)
|
|
43
|
+
main.add_command(boadockerpipeline.boadockerpipeline)
|
|
40
44
|
main.add_command(show_doc_command(main)) # Special command to show long description for command
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from mosamatic2.core.pipelines import BoaDockerPipeline
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command(help='Runs 3D body composition analysis pipeline using BOA algorithm')
|
|
7
|
+
@click.option(
|
|
8
|
+
'--scans',
|
|
9
|
+
required=True,
|
|
10
|
+
type=click.Path(exists=True),
|
|
11
|
+
help='Root directory with scan directories for each patient (no spaces allowed)',
|
|
12
|
+
)
|
|
13
|
+
@click.option(
|
|
14
|
+
'--output',
|
|
15
|
+
required=True,
|
|
16
|
+
type=click.Path(),
|
|
17
|
+
help='Output directory (no spaces allowed)'
|
|
18
|
+
)
|
|
19
|
+
@click.option(
|
|
20
|
+
'--overwrite',
|
|
21
|
+
type=click.BOOL,
|
|
22
|
+
default=False,
|
|
23
|
+
help='Overwrite [true|false]'
|
|
24
|
+
)
|
|
25
|
+
def boadockerpipeline(scans, output, overwrite):
|
|
26
|
+
"""
|
|
27
|
+
Runs 3D body composition analysis pipeline using BOA algorithm.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
--scans : str
|
|
32
|
+
Root directory with scan directories for each patient. Each scan directory should
|
|
33
|
+
contain DICOM images for a single scan only and nothing else. Also, the directory
|
|
34
|
+
paths cannot contain any spaces.
|
|
35
|
+
|
|
36
|
+
--output : str
|
|
37
|
+
Path to output directory. No spaces allowed.
|
|
38
|
+
|
|
39
|
+
--overwrite : bool
|
|
40
|
+
Overwrite contents output directory [true|false]
|
|
41
|
+
"""
|
|
42
|
+
pipeline = BoaDockerPipeline(
|
|
43
|
+
inputs={'scans': scans},
|
|
44
|
+
params=None,
|
|
45
|
+
output=output,
|
|
46
|
+
overwrite=overwrite,
|
|
47
|
+
)
|
|
48
|
+
pipeline.run()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from mosamatic2.core.pipelines import DefaultDockerPipeline
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command(help='Runs default L3 analysis pipeline through Docker')
|
|
7
|
+
@click.option(
|
|
8
|
+
'--images',
|
|
9
|
+
required=True,
|
|
10
|
+
type=click.Path(exists=True),
|
|
11
|
+
help='Directory with L3 images (no spaces allowed)',
|
|
12
|
+
)
|
|
13
|
+
@click.option(
|
|
14
|
+
'--model_files',
|
|
15
|
+
required=True,
|
|
16
|
+
type=click.Path(),
|
|
17
|
+
help='Input directory with AI model files (no spaces allowed)'
|
|
18
|
+
)
|
|
19
|
+
@click.option(
|
|
20
|
+
'--output',
|
|
21
|
+
required=True,
|
|
22
|
+
type=click.Path(),
|
|
23
|
+
help='Output directory (no spaces allowed)'
|
|
24
|
+
)
|
|
25
|
+
@click.option(
|
|
26
|
+
'--overwrite',
|
|
27
|
+
type=click.BOOL,
|
|
28
|
+
default=False,
|
|
29
|
+
help='Overwrite [true|false]'
|
|
30
|
+
)
|
|
31
|
+
def defaultdockerpipeline(images, model_files, output, overwrite):
|
|
32
|
+
"""
|
|
33
|
+
Runs default L3 analysis pipeline through Docker
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
--images : str
|
|
38
|
+
Directory with L3 images. Warning: This directory path cannot contain any spaces!
|
|
39
|
+
Docker has issues working with that.
|
|
40
|
+
|
|
41
|
+
--model_files : str
|
|
42
|
+
Directory with AI model files. This directory can ONLY contain the following
|
|
43
|
+
files:
|
|
44
|
+
|
|
45
|
+
(1) contour_model-1.0.zip
|
|
46
|
+
(2) model-1.0.zip
|
|
47
|
+
(3) params-1.0.json
|
|
48
|
+
|
|
49
|
+
These files can be downloaded from:
|
|
50
|
+
https://mosamatic.rbeesoft.nl/wordpress/mosamatic/installation/
|
|
51
|
+
|
|
52
|
+
Warning: This directory path cannot contain any spaces!
|
|
53
|
+
|
|
54
|
+
--output : str
|
|
55
|
+
Path to output directory (no spaces!)
|
|
56
|
+
|
|
57
|
+
--overwrite : bool
|
|
58
|
+
Overwrite contents output directory [true|false]
|
|
59
|
+
"""
|
|
60
|
+
pipeline = DefaultDockerPipeline(
|
|
61
|
+
inputs={'images': images, 'model_files': model_files},
|
|
62
|
+
params={
|
|
63
|
+
'target_size': 512,
|
|
64
|
+
'model_type': 'tensorflow',
|
|
65
|
+
'model_version': 1.0,
|
|
66
|
+
'file_type': 'npy',
|
|
67
|
+
'fig_width': 10,
|
|
68
|
+
'fig_height': 10,
|
|
69
|
+
},
|
|
70
|
+
output=output,
|
|
71
|
+
overwrite=overwrite,
|
|
72
|
+
)
|
|
73
|
+
pipeline.run()
|
|
@@ -10,6 +10,12 @@ from mosamatic2.core.tasks import Dicom2NiftiTask
|
|
|
10
10
|
type=click.Path(exists=True),
|
|
11
11
|
help='Root directory with DICOM scans (one for each patient)',
|
|
12
12
|
)
|
|
13
|
+
@click.option(
|
|
14
|
+
'--compressed',
|
|
15
|
+
type=click.BOOL,
|
|
16
|
+
default=True,
|
|
17
|
+
help='Compress with gzip [true|false] (default: True)'
|
|
18
|
+
)
|
|
13
19
|
@click.option(
|
|
14
20
|
'--output',
|
|
15
21
|
required=True,
|
|
@@ -22,7 +28,7 @@ from mosamatic2.core.tasks import Dicom2NiftiTask
|
|
|
22
28
|
default=False,
|
|
23
29
|
help='Overwrite [true|false]'
|
|
24
30
|
)
|
|
25
|
-
def dicom2nifti(scans, output, overwrite):
|
|
31
|
+
def dicom2nifti(scans, compressed, output, overwrite):
|
|
26
32
|
"""
|
|
27
33
|
Converts DICOM scans to NIFTI format.
|
|
28
34
|
|
|
@@ -31,6 +37,9 @@ def dicom2nifti(scans, output, overwrite):
|
|
|
31
37
|
--scans : str
|
|
32
38
|
Root directory with DICOM scans (one subdirectory for each patient)
|
|
33
39
|
|
|
40
|
+
--compressed : bool
|
|
41
|
+
Whether to compress the NIFTI file with gzip or not (default: True)
|
|
42
|
+
|
|
34
43
|
--output : str
|
|
35
44
|
Path to output directory
|
|
36
45
|
|
|
@@ -39,7 +48,7 @@ def dicom2nifti(scans, output, overwrite):
|
|
|
39
48
|
"""
|
|
40
49
|
task = Dicom2NiftiTask(
|
|
41
50
|
inputs={'scans': scans},
|
|
42
|
-
params=
|
|
51
|
+
params={'compressed': compressed},
|
|
43
52
|
output=output,
|
|
44
53
|
overwrite=overwrite,
|
|
45
54
|
)
|
|
@@ -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()
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
from mosamatic2.core.pipelines.defaultpipeline.defaultpipeline import DefaultPipeline
|
|
2
|
-
from mosamatic2.core.pipelines.defaultdockerpipeline.defaultdockerpipeline import DefaultDockerPipeline
|
|
2
|
+
from mosamatic2.core.pipelines.defaultdockerpipeline.defaultdockerpipeline import DefaultDockerPipeline
|
|
3
|
+
from mosamatic2.core.pipelines.boadockerpipeline.boadockerpipeline import BoaDockerPipeline
|
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from mosamatic2.core.pipelines.pipeline import Pipeline
|
|
3
|
+
from mosamatic2.core.tasks import Dicom2NiftiTask
|
|
4
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
5
|
+
from mosamatic2.core.utils import (
|
|
6
|
+
is_docker_running,
|
|
7
|
+
to_unix_path,
|
|
8
|
+
current_time_in_seconds,
|
|
9
|
+
elapsed_time_in_seconds,
|
|
10
|
+
duration,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
LOG = LogManager()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BoaDockerPipeline(Pipeline):
|
|
17
|
+
INPUTS = ['scans']
|
|
18
|
+
PARAMS = []
|
|
19
|
+
|
|
20
|
+
def __init__(self, inputs, params, output, overwrite):
|
|
21
|
+
super(BoaDockerPipeline, self).__init__(inputs, params, output, overwrite)
|
|
22
|
+
|
|
23
|
+
def load_nifti_files(self):
|
|
24
|
+
nifti_files_dir = os.path.join(self.output(), 'dicom2niftitask')
|
|
25
|
+
nifti_files = []
|
|
26
|
+
for f in os.listdir(nifti_files_dir):
|
|
27
|
+
f_path = os.path.join(nifti_files_dir, f)
|
|
28
|
+
if f.endswith('.nii') or f.endswith('.nii.gz'):
|
|
29
|
+
nifti_files.append(f_path)
|
|
30
|
+
return nifti_files
|
|
31
|
+
|
|
32
|
+
def run(self):
|
|
33
|
+
assert is_docker_running()
|
|
34
|
+
# First convert DICOM series to NIFTI format
|
|
35
|
+
dicom2nifti_task = Dicom2NiftiTask(
|
|
36
|
+
inputs={'scans': self.input('scans')},
|
|
37
|
+
params={'compressed': True},
|
|
38
|
+
output=self.output(),
|
|
39
|
+
overwrite=self.overwrite(),
|
|
40
|
+
)
|
|
41
|
+
dicom2nifti_task.run()
|
|
42
|
+
# Load NIFTI file paths
|
|
43
|
+
nifti_files = self.load_nifti_files()
|
|
44
|
+
workspaces_dir = to_unix_path(os.path.join(self.output(), 'workspaces'))
|
|
45
|
+
os.makedirs(workspaces_dir, exist_ok=True)
|
|
46
|
+
weights_dir = to_unix_path(os.path.join(self.output(), 'weights'))
|
|
47
|
+
start_time = current_time_in_seconds()
|
|
48
|
+
for f in nifti_files:
|
|
49
|
+
start_time_f = current_time_in_seconds()
|
|
50
|
+
f = to_unix_path(f)
|
|
51
|
+
dir_name = os.path.split(f)[1][:-7]
|
|
52
|
+
workspace = to_unix_path(os.path.join(workspaces_dir, dir_name))
|
|
53
|
+
# Build Docker script
|
|
54
|
+
docker_script = 'docker run --rm ' + \
|
|
55
|
+
'-v "{}":/image.nii.gz '.format(f) + \
|
|
56
|
+
'-v "{}":/workspace '.format(workspace) + \
|
|
57
|
+
'-v "{}":/app/weights '.format(weights_dir) + \
|
|
58
|
+
'--gpus all ' + \
|
|
59
|
+
'--network host ' + \
|
|
60
|
+
'--shm-size=8g --ulimit memlock=-1 --ulimit stack=67108864 ' + \
|
|
61
|
+
'--entrypoint /bin/sh ' + \
|
|
62
|
+
'shipai/boa-cli -c ' + \
|
|
63
|
+
'"python body_organ_analysis --input-image /image.nii.gz --output-dir /workspace/ --models all --verbose"'
|
|
64
|
+
LOG.info(f'Running BOA Docker script: {docker_script}')
|
|
65
|
+
# Run Docker script
|
|
66
|
+
os.system(docker_script)
|
|
67
|
+
elapsed_f = elapsed_time_in_seconds(start_time_f)
|
|
68
|
+
LOG.info(f'Elapsed {dir_name}: {duration(elapsed_f)}')
|
|
69
|
+
elapsed_total = elapsed_time_in_seconds(start_time)
|
|
70
|
+
LOG.info(f'Elapsed total: {duration(elapsed_total)}')
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import os
|
|
2
|
-
|
|
3
2
|
from mosamatic2.core.pipelines.pipeline import Pipeline
|
|
4
3
|
from mosamatic2.core.managers.logmanager import LogManager
|
|
4
|
+
from mosamatic2.core.utils import is_docker_running, to_unix_path
|
|
5
5
|
|
|
6
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
7
|
|
|
16
8
|
|
|
17
9
|
class DefaultDockerPipeline(Pipeline):
|
|
@@ -32,10 +24,11 @@ class DefaultDockerPipeline(Pipeline):
|
|
|
32
24
|
super(DefaultDockerPipeline, self).__init__(inputs, params, output, overwrite)
|
|
33
25
|
|
|
34
26
|
def run(self):
|
|
27
|
+
assert is_docker_running()
|
|
35
28
|
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()) + \
|
|
29
|
+
'-v "{}":/data/images '.format(to_unix_path(self.input('images'))) + \
|
|
30
|
+
'-v "{}":/data/model_files '.format(to_unix_path(self.input('model_files'))) + \
|
|
31
|
+
'-v "{}":/data/output '.format(to_unix_path(self.output())) + \
|
|
39
32
|
'brecheisen/mosamatic2-cli:{} defaultpipeline '.format(self.param('version')) + \
|
|
40
33
|
'--images /data/images --model_files /data/model_files --output /data/output --overwrite true'
|
|
41
34
|
LOG.info(f'Running Docker script: {docker_script}')
|
|
@@ -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
|
|
@@ -9,7 +9,7 @@ LOG = LogManager()
|
|
|
9
9
|
|
|
10
10
|
class Dicom2NiftiTask(Task):
|
|
11
11
|
INPUTS = ['scans']
|
|
12
|
-
PARAMS = []
|
|
12
|
+
PARAMS = ['compressed']
|
|
13
13
|
|
|
14
14
|
def __init__(self, inputs, params, output, overwrite):
|
|
15
15
|
super(Dicom2NiftiTask, self).__init__(inputs, params, output, overwrite)
|
|
@@ -28,7 +28,10 @@ class Dicom2NiftiTask(Task):
|
|
|
28
28
|
for step in range(nr_steps):
|
|
29
29
|
scan_dir = scan_dirs[step]
|
|
30
30
|
scan_name = os.path.split(scan_dir)[1]
|
|
31
|
-
|
|
31
|
+
if self.param('compressed'):
|
|
32
|
+
nifti_file_name = scan_name + '.nii.gz'
|
|
33
|
+
else:
|
|
34
|
+
nifti_file_name = scan_name + '.nii'
|
|
32
35
|
LOG.info(f'Converting DICOM series in {scan_dir} to {nifti_file_name}')
|
|
33
36
|
dicom2nifti.dicom_series_to_nifti(
|
|
34
37
|
scan_dir,
|
|
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
|
@@ -350,4 +350,18 @@ def convert_numpy_array_to_png_image(
|
|
|
350
350
|
png_file_name += '.png'
|
|
351
351
|
png_file_path = os.path.join(output_dir_path, png_file_name)
|
|
352
352
|
image.save(png_file_path)
|
|
353
|
-
return png_file_path
|
|
353
|
+
return png_file_path
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def is_docker_running():
|
|
357
|
+
import docker
|
|
358
|
+
try:
|
|
359
|
+
client = docker.from_env()
|
|
360
|
+
client.ping()
|
|
361
|
+
return True
|
|
362
|
+
except Exception:
|
|
363
|
+
return False
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def to_unix_path(path):
|
|
367
|
+
return path.replace("\\", "/").replace(" ", "\\ ")
|
mosamatic2/ui/mainwindow.py
CHANGED
|
@@ -21,8 +21,10 @@ 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
|
|
27
|
+
from mosamatic2.ui.widgets.panels.pipelines.boadockerpipelinepanel import BoaDockerPipelinePanel
|
|
26
28
|
from mosamatic2.ui.widgets.panels.visualizations.slicevisualization.slicevisualization import SliceVisualization
|
|
27
29
|
|
|
28
30
|
LOG = LogManager()
|
|
@@ -42,8 +44,10 @@ class MainWindow(QMainWindow):
|
|
|
42
44
|
self._dicom2nifti_task_panel = None
|
|
43
45
|
self._create_dicom_summary_task_panel = None
|
|
44
46
|
self._select_slice_from_scans_task_panel = None
|
|
47
|
+
self._total_segmentator_task_panel = None
|
|
45
48
|
self._default_pipeline_panel = None
|
|
46
49
|
self._default_docker_pipeline_panel = None
|
|
50
|
+
self._boa_docker_pipeline_panel = None
|
|
47
51
|
self._slice_visualization = None
|
|
48
52
|
self.init_window()
|
|
49
53
|
|
|
@@ -88,6 +92,8 @@ class MainWindow(QMainWindow):
|
|
|
88
92
|
create_dicom_summary_task_action.triggered.connect(self.handle_create_dicom_summary_task_action)
|
|
89
93
|
select_slice_from_scans_task_action = QAction('SelectSliceFromScansTask', self)
|
|
90
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)
|
|
91
97
|
tasks_menu = self.menuBar().addMenu('Tasks')
|
|
92
98
|
tasks_menu.addAction(rescale_dicom_images_task_action)
|
|
93
99
|
tasks_menu.addAction(segment_muscle_fat_l3_tensorflow_task_action)
|
|
@@ -96,15 +102,19 @@ class MainWindow(QMainWindow):
|
|
|
96
102
|
tasks_menu.addAction(dicom2nifti_task_action)
|
|
97
103
|
tasks_menu.addAction(create_dicom_summary_task_action)
|
|
98
104
|
tasks_menu.addAction(select_slice_from_scans_task_action)
|
|
105
|
+
tasks_menu.addAction(total_segmentator_task_action)
|
|
99
106
|
|
|
100
107
|
def init_pipelines_menu(self):
|
|
101
108
|
default_pipeline_action = QAction('DefaultPipeline', self)
|
|
102
109
|
default_pipeline_action.triggered.connect(self.handle_default_pipeline_action)
|
|
103
110
|
default_docker_pipeline_action = QAction('DefaultDockerPipeline', self)
|
|
104
111
|
default_docker_pipeline_action.triggered.connect(self.handle_default_docker_pipeline_action)
|
|
112
|
+
boa_docker_pipeline_action = QAction('BoaDockerPipeline', self)
|
|
113
|
+
boa_docker_pipeline_action.triggered.connect(self.handle_boa_docker_pipeline_action)
|
|
105
114
|
pipelines_menu = self.menuBar().addMenu('Pipelines')
|
|
106
115
|
pipelines_menu.addAction(default_pipeline_action)
|
|
107
116
|
pipelines_menu.addAction(default_docker_pipeline_action)
|
|
117
|
+
pipelines_menu.addAction(boa_docker_pipeline_action)
|
|
108
118
|
|
|
109
119
|
def init_visualizations_menu(self):
|
|
110
120
|
slice_visualization_action = QAction('SliceVisualization', self)
|
|
@@ -132,8 +142,10 @@ class MainWindow(QMainWindow):
|
|
|
132
142
|
self._main_panel.add_panel(self.dicom2nifti_task_panel(), 'dicom2niftitaskpanel')
|
|
133
143
|
self._main_panel.add_panel(self.create_dicom_summary_task_panel(), 'createdicomsummarytaskpanel')
|
|
134
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')
|
|
135
146
|
self._main_panel.add_panel(self.default_pipeline_panel(), 'defaultpipelinepanel')
|
|
136
147
|
self._main_panel.add_panel(self.default_docker_pipeline_panel(), 'defaultdockerpipelinepanel')
|
|
148
|
+
self._main_panel.add_panel(self.boa_docker_pipeline_panel(), 'boadockerpipelinepanel')
|
|
137
149
|
self._main_panel.add_panel(self.slice_visualization(), 'slicevisualization')
|
|
138
150
|
self._main_panel.select_panel('defaultpipelinepanel')
|
|
139
151
|
return self._main_panel
|
|
@@ -180,6 +192,11 @@ class MainWindow(QMainWindow):
|
|
|
180
192
|
if not self._select_slice_from_scans_task_panel:
|
|
181
193
|
self._select_slice_from_scans_task_panel = SelectSliceFromScansTaskPanel()
|
|
182
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
|
|
183
200
|
|
|
184
201
|
def default_pipeline_panel(self):
|
|
185
202
|
if not self._default_pipeline_panel:
|
|
@@ -190,6 +207,11 @@ class MainWindow(QMainWindow):
|
|
|
190
207
|
if not self._default_docker_pipeline_panel:
|
|
191
208
|
self._default_docker_pipeline_panel = DefaultDockerPipelinePanel()
|
|
192
209
|
return self._default_docker_pipeline_panel
|
|
210
|
+
|
|
211
|
+
def boa_docker_pipeline_panel(self):
|
|
212
|
+
if not self._boa_docker_pipeline_panel:
|
|
213
|
+
self._boa_docker_pipeline_panel = BoaDockerPipelinePanel()
|
|
214
|
+
return self._boa_docker_pipeline_panel
|
|
193
215
|
|
|
194
216
|
def slice_visualization(self):
|
|
195
217
|
if not self._slice_visualization:
|
|
@@ -224,12 +246,18 @@ class MainWindow(QMainWindow):
|
|
|
224
246
|
def handle_select_slice_from_scans_task_action(self):
|
|
225
247
|
self.main_panel().select_panel('selectslicefromscanstaskpanel')
|
|
226
248
|
|
|
249
|
+
def handle_total_segmentator_task_action(self):
|
|
250
|
+
self.main_panel().select_panel('totalsegmentatortaskpanel')
|
|
251
|
+
|
|
227
252
|
def handle_default_pipeline_action(self):
|
|
228
253
|
self.main_panel().select_panel('defaultpipelinepanel')
|
|
229
254
|
|
|
230
255
|
def handle_default_docker_pipeline_action(self):
|
|
231
256
|
self.main_panel().select_panel('defaultdockerpipelinepanel')
|
|
232
257
|
|
|
258
|
+
def handle_boa_docker_pipeline_action(self):
|
|
259
|
+
self.main_panel().select_panel('boadockerpipelinepanel')
|
|
260
|
+
|
|
233
261
|
def handle_slice_visualization_action(self):
|
|
234
262
|
self.main_panel().select_panel('slicevisualization')
|
|
235
263
|
|
|
@@ -246,8 +274,10 @@ class MainWindow(QMainWindow):
|
|
|
246
274
|
self.dicom2nifti_task_panel().save_inputs_and_parameters()
|
|
247
275
|
self.create_dicom_summary_task_panel().save_inputs_and_parameters()
|
|
248
276
|
self.select_slice_from_scans_task_panel().save_inputs_and_parameters()
|
|
277
|
+
self.total_segmentator_task_panel().save_inputs_and_parameters()
|
|
249
278
|
self.default_pipeline_panel().save_inputs_and_parameters()
|
|
250
279
|
self.default_docker_pipeline_panel().save_inputs_and_parameters()
|
|
280
|
+
self.boa_docker_pipeline_panel().save_inputs_and_parameters()
|
|
251
281
|
self.slice_visualization().save_inputs_and_parameters()
|
|
252
282
|
return super().closeEvent(event)
|
|
253
283
|
|
mosamatic2/ui/resources/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.0.
|
|
1
|
+
2.0.14
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from PySide6.QtWidgets import (
|
|
4
|
+
QLineEdit,
|
|
5
|
+
QCheckBox,
|
|
6
|
+
QHBoxLayout,
|
|
7
|
+
QVBoxLayout,
|
|
8
|
+
QFormLayout,
|
|
9
|
+
QPushButton,
|
|
10
|
+
QFileDialog,
|
|
11
|
+
QMessageBox,
|
|
12
|
+
)
|
|
13
|
+
from PySide6.QtCore import (
|
|
14
|
+
QThread,
|
|
15
|
+
Slot,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
19
|
+
from mosamatic2.ui.widgets.panels.pipelines.pipelinepanel import PipelinePanel
|
|
20
|
+
from mosamatic2.ui.settings import Settings
|
|
21
|
+
from mosamatic2.ui.utils import is_macos
|
|
22
|
+
from mosamatic2.ui.worker import Worker
|
|
23
|
+
from mosamatic2.core.pipelines import BoaDockerPipeline
|
|
24
|
+
|
|
25
|
+
LOG = LogManager()
|
|
26
|
+
|
|
27
|
+
PANEL_TITLE = 'BoaDockerPipeline'
|
|
28
|
+
PANEL_NAME = 'boadockerpipeline'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BoaDockerPipelinePanel(PipelinePanel):
|
|
32
|
+
def __init__(self):
|
|
33
|
+
super(BoaDockerPipelinePanel, self).__init__()
|
|
34
|
+
self.set_title(PANEL_TITLE)
|
|
35
|
+
self._scans_dir_line_edit = None
|
|
36
|
+
self._images_dir_select_button = None
|
|
37
|
+
self._output_dir_line_edit = None
|
|
38
|
+
self._output_dir_select_button = None
|
|
39
|
+
self._overwrite_checkbox = None
|
|
40
|
+
self._form_layout = None
|
|
41
|
+
self._run_pipeline_button = None
|
|
42
|
+
self._settings = None
|
|
43
|
+
self._task = None
|
|
44
|
+
self._worker = None
|
|
45
|
+
self._thread = None
|
|
46
|
+
self.init_layout()
|
|
47
|
+
|
|
48
|
+
def scans_dir_line_edit(self):
|
|
49
|
+
if not self._scans_dir_line_edit:
|
|
50
|
+
self._scans_dir_line_edit = QLineEdit()
|
|
51
|
+
self._scans_dir_line_edit.setText(self.settings().get(f'{PANEL_NAME}/scans_dir'))
|
|
52
|
+
return self._scans_dir_line_edit
|
|
53
|
+
|
|
54
|
+
def scans_dir_select_button(self):
|
|
55
|
+
if not self._images_dir_select_button:
|
|
56
|
+
self._images_dir_select_button = QPushButton('Select')
|
|
57
|
+
self._images_dir_select_button.clicked.connect(self.handle_scans_dir_select_button)
|
|
58
|
+
return self._images_dir_select_button
|
|
59
|
+
|
|
60
|
+
def output_dir_line_edit(self):
|
|
61
|
+
if not self._output_dir_line_edit:
|
|
62
|
+
self._output_dir_line_edit = QLineEdit()
|
|
63
|
+
self._output_dir_line_edit.setText(self.settings().get(f'{PANEL_NAME}/output_dir'))
|
|
64
|
+
return self._output_dir_line_edit
|
|
65
|
+
|
|
66
|
+
def output_dir_select_button(self):
|
|
67
|
+
if not self._output_dir_select_button:
|
|
68
|
+
self._output_dir_select_button = QPushButton('Select')
|
|
69
|
+
self._output_dir_select_button.clicked.connect(self.handle_output_dir_select_button)
|
|
70
|
+
return self._output_dir_select_button
|
|
71
|
+
|
|
72
|
+
def overwrite_checkbox(self):
|
|
73
|
+
if not self._overwrite_checkbox:
|
|
74
|
+
self._overwrite_checkbox = QCheckBox('')
|
|
75
|
+
self._overwrite_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/overwrite', True))
|
|
76
|
+
return self._overwrite_checkbox
|
|
77
|
+
|
|
78
|
+
def form_layout(self):
|
|
79
|
+
if not self._form_layout:
|
|
80
|
+
self._form_layout = QFormLayout()
|
|
81
|
+
if is_macos():
|
|
82
|
+
self._form_layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
|
|
83
|
+
return self._form_layout
|
|
84
|
+
|
|
85
|
+
def run_pipeline_button(self):
|
|
86
|
+
if not self._run_pipeline_button:
|
|
87
|
+
self._run_pipeline_button = QPushButton('Run pipeline')
|
|
88
|
+
self._run_pipeline_button.clicked.connect(self.handle_run_pipeline_button)
|
|
89
|
+
return self._run_pipeline_button
|
|
90
|
+
|
|
91
|
+
def settings(self):
|
|
92
|
+
if not self._settings:
|
|
93
|
+
self._settings = Settings()
|
|
94
|
+
return self._settings
|
|
95
|
+
|
|
96
|
+
def init_help_dialog(self):
|
|
97
|
+
self.help_dialog().set_text('Show some help information')
|
|
98
|
+
|
|
99
|
+
def init_layout(self):
|
|
100
|
+
scans_dir_layout = QHBoxLayout()
|
|
101
|
+
scans_dir_layout.addWidget(self.scans_dir_line_edit())
|
|
102
|
+
scans_dir_layout.addWidget(self.scans_dir_select_button())
|
|
103
|
+
output_dir_layout = QHBoxLayout()
|
|
104
|
+
output_dir_layout.addWidget(self.output_dir_line_edit())
|
|
105
|
+
output_dir_layout.addWidget(self.output_dir_select_button())
|
|
106
|
+
self.form_layout().addRow('Scans directory', scans_dir_layout)
|
|
107
|
+
self.form_layout().addRow('Output directory', output_dir_layout)
|
|
108
|
+
self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
|
|
109
|
+
layout = QVBoxLayout()
|
|
110
|
+
layout.addLayout(self.form_layout())
|
|
111
|
+
layout.addWidget(self.run_pipeline_button())
|
|
112
|
+
self.setLayout(layout)
|
|
113
|
+
self.setObjectName(PANEL_NAME)
|
|
114
|
+
|
|
115
|
+
def handle_scans_dir_select_button(self):
|
|
116
|
+
last_directory = self.settings().get('last_directory')
|
|
117
|
+
directory = QFileDialog.getExistingDirectory(dir=last_directory)
|
|
118
|
+
if directory:
|
|
119
|
+
self.scans_dir_line_edit().setText(directory)
|
|
120
|
+
self.settings().set('last_directory', directory)
|
|
121
|
+
|
|
122
|
+
def handle_output_dir_select_button(self):
|
|
123
|
+
last_directory = self.settings().get('last_directory')
|
|
124
|
+
directory = QFileDialog.getExistingDirectory(dir=last_directory)
|
|
125
|
+
if directory:
|
|
126
|
+
self.output_dir_line_edit().setText(directory)
|
|
127
|
+
self.settings().set('last_directory', directory)
|
|
128
|
+
|
|
129
|
+
def handle_run_pipeline_button(self):
|
|
130
|
+
errors = self.check_inputs_and_parameters()
|
|
131
|
+
if len(errors) > 0:
|
|
132
|
+
error_message = 'Following errors were encountered:\n'
|
|
133
|
+
for error in errors:
|
|
134
|
+
error_message += f' - {error}\n'
|
|
135
|
+
QMessageBox.information(self, 'Error', error_message)
|
|
136
|
+
else:
|
|
137
|
+
LOG.info('Running pipeline...')
|
|
138
|
+
self.run_pipeline_button().setEnabled(False)
|
|
139
|
+
self.save_inputs_and_parameters()
|
|
140
|
+
self._task = BoaDockerPipeline(
|
|
141
|
+
inputs={'scans': self.scans_dir_line_edit().text()},
|
|
142
|
+
params=None,
|
|
143
|
+
output=self.output_dir_line_edit().text(),
|
|
144
|
+
overwrite=self.overwrite_checkbox().isChecked(),
|
|
145
|
+
)
|
|
146
|
+
self._worker = Worker(self._task)
|
|
147
|
+
self._thread = QThread()
|
|
148
|
+
self._worker.moveToThread(self._thread)
|
|
149
|
+
self._thread.started.connect(self._worker.run)
|
|
150
|
+
self._worker.progress.connect(self.handle_progress)
|
|
151
|
+
self._worker.status.connect(self.handle_status)
|
|
152
|
+
self._worker.finished.connect(self.handle_finished)
|
|
153
|
+
self._worker.finished.connect(self._thread.quit)
|
|
154
|
+
self._worker.finished.connect(self._worker.deleteLater)
|
|
155
|
+
self._thread.finished.connect(self._thread.deleteLater)
|
|
156
|
+
self._thread.start()
|
|
157
|
+
|
|
158
|
+
@Slot(int)
|
|
159
|
+
def handle_progress(self, progress):
|
|
160
|
+
LOG.info(f'Progress: {progress} / 100%')
|
|
161
|
+
|
|
162
|
+
@Slot(str)
|
|
163
|
+
def handle_status(self, status):
|
|
164
|
+
LOG.info(f'Status: {status}')
|
|
165
|
+
|
|
166
|
+
@Slot()
|
|
167
|
+
def handle_finished(self):
|
|
168
|
+
self.run_pipeline_button().setEnabled(True)
|
|
169
|
+
|
|
170
|
+
def check_inputs_and_parameters(self):
|
|
171
|
+
errors = []
|
|
172
|
+
if self.scans_dir_line_edit().text() == '':
|
|
173
|
+
errors.append('Empty scans directory path')
|
|
174
|
+
elif not os.path.isdir(self.scans_dir_line_edit().text()):
|
|
175
|
+
errors.append('Scans directory does not exist')
|
|
176
|
+
if self.output_dir_line_edit().text() == '':
|
|
177
|
+
errors.append('Empty output directory path')
|
|
178
|
+
elif os.path.isdir(self.output_dir_line_edit().text()) and not self.overwrite_checkbox().isChecked():
|
|
179
|
+
errors.append('Output directory exists but overwrite=False. Please remove output directory first')
|
|
180
|
+
return errors
|
|
181
|
+
|
|
182
|
+
def save_inputs_and_parameters(self):
|
|
183
|
+
self.settings().set(f'{PANEL_NAME}/scans_dir', self.scans_dir_line_edit().text())
|
|
184
|
+
self.settings().set(f'{PANEL_NAME}/output_dir', self.output_dir_line_edit().text())
|
|
185
|
+
self.settings().set(f'{PANEL_NAME}/overwrite', self.overwrite_checkbox().isChecked())
|
|
@@ -182,7 +182,6 @@ class DefaultDockerPipelinePanel(PipelinePanel):
|
|
|
182
182
|
self.form_layout().addRow('Images directory', images_dir_layout)
|
|
183
183
|
self.form_layout().addRow('Model files directory', model_files_dir_layout)
|
|
184
184
|
self.form_layout().addRow('Output directory', output_dir_layout)
|
|
185
|
-
# self.form_layout().addRow('Version', self.version_line_edit())
|
|
186
185
|
self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
|
|
187
186
|
layout = QVBoxLayout()
|
|
188
187
|
layout.addLayout(self.form_layout())
|
|
@@ -289,23 +288,10 @@ class DefaultDockerPipelinePanel(PipelinePanel):
|
|
|
289
288
|
errors.append('Empty output directory path')
|
|
290
289
|
elif os.path.isdir(self.output_dir_line_edit().text()) and not self.overwrite_checkbox().isChecked():
|
|
291
290
|
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
291
|
return errors
|
|
299
292
|
|
|
300
293
|
def save_inputs_and_parameters(self):
|
|
301
294
|
self.settings().set(f'{PANEL_NAME}/images_dir', self.images_dir_line_edit().text())
|
|
302
295
|
self.settings().set(f'{PANEL_NAME}/model_files_dir', self.model_files_dir_line_edit().text())
|
|
303
296
|
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
297
|
self.settings().set(f'{PANEL_NAME}/overwrite', self.overwrite_checkbox().isChecked())
|
|
@@ -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.14
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Ralph Brecheisen
|
|
6
6
|
Author-email: r.brecheisen@maastrichtuniversity.nl
|
|
@@ -9,6 +9,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
10
10
|
Requires-Dist: antspyx (>=0.5.4)
|
|
11
11
|
Requires-Dist: dicom2nifti (>=2.6.2)
|
|
12
|
+
Requires-Dist: docker
|
|
12
13
|
Requires-Dist: flask (>=3.1.2)
|
|
13
14
|
Requires-Dist: moosez
|
|
14
15
|
Requires-Dist: nibabel (>=5.3.2)
|
|
@@ -22,6 +23,7 @@ Requires-Dist: pyqtgraph (>=0.13.7)
|
|
|
22
23
|
Requires-Dist: pyside6-essentials (>=6.9)
|
|
23
24
|
Requires-Dist: python-gdcm (>=3.0.26)
|
|
24
25
|
Requires-Dist: scipy (>=1.15.3)
|
|
26
|
+
Requires-Dist: slicer
|
|
25
27
|
Requires-Dist: tensorboard (==2.15.2)
|
|
26
28
|
Requires-Dist: tensorboard-data-server (==0.7.2)
|
|
27
29
|
Requires-Dist: tensorflow (==2.15.*) ; platform_system == "Linux"
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
models.py,sha256=Kx6oWKt7IpTTxrhBDrX61X-ZX12J7yPkJFuhVDsDHoQ,8807
|
|
2
2
|
mosamatic2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
mosamatic2/app.py,sha256=RIUa5tvMYFcmEII4xZPLZZdx9dXWqBvwkxkl_R97Jkw,860
|
|
4
|
-
mosamatic2/cli.py,sha256=
|
|
4
|
+
mosamatic2/cli.py,sha256=Fzsz1-uziNRabnBRvqyRcXFpQ7CGLF1dt4w7Js-Y1u8,1614
|
|
5
5
|
mosamatic2/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
mosamatic2/commands/boadockerpipeline.py,sha256=-k9rQlhixfcqGUy29SsKYVXxZFri6DcnWaV6utJ6mhI,1344
|
|
6
7
|
mosamatic2/commands/calculatescores.py,sha256=Lb8Q8L2yq7Tt6VBJ6_lltRuldrev_pac6fcgF-xzZyE,1984
|
|
7
8
|
mosamatic2/commands/createdicomsummary.py,sha256=qbVgWGIJPBL8vTBcAxfThloQ_q15OfQy5ohMprzZL4E,1955
|
|
8
9
|
mosamatic2/commands/createpngsfromsegmentations.py,sha256=uUAQJVTqOkBCfENzi21RBNYvf6_nuesx1MeR3j_-7dM,1682
|
|
10
|
+
mosamatic2/commands/defaultdockerpipeline.py,sha256=IxnpnIIsFH2u3aBLKgrHUb_Au72ajlIzzVe6Y0MFHJU,2025
|
|
9
11
|
mosamatic2/commands/defaultpipeline.py,sha256=-kQRSXbEDimAdy_kT1qMxRlZWUoXdu3PkWf70PCIS5Y,1734
|
|
10
|
-
mosamatic2/commands/dicom2nifti.py,sha256=
|
|
12
|
+
mosamatic2/commands/dicom2nifti.py,sha256=uzI3-QT8_HtwXKkX5kRJzMKssM5dvJGUjZ1Z-XkwLYc,1366
|
|
11
13
|
mosamatic2/commands/rescaledicomimages.py,sha256=25QdCzB5s0sRwkTb3o5zco2bIwy6LttNf7i97kGBDYQ,1280
|
|
12
14
|
mosamatic2/commands/segmentmusclefatl3tensorflow.py,sha256=CdScmA_EQicaN4GY5bBUOYwfhDPqy9om2sxY3WrtmM0,1424
|
|
13
15
|
mosamatic2/commands/selectslicefromscans.py,sha256=3398PM2uBcxF6wpb0-c-Itp_qxoAxBf0SE2nDDI3Ct4,1715
|
|
16
|
+
mosamatic2/commands/totalsegmentator.py,sha256=GY8wAFDHJW-dMWa3ZskHJnaYd71bKWUjpwV2-Wf38W4,1956
|
|
14
17
|
mosamatic2/constants.py,sha256=MVYMwO-x2jQSN37o3zNkRseVQ1nYRA3mLv3v_Q0Mlds,1284
|
|
15
18
|
mosamatic2/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
19
|
mosamatic2/core/data/__init__.py,sha256=j9iGqUTJlGF0N0gPrzzpe_Dhv0Bj9c6FdQ1g7U-_j2g,298
|
|
@@ -26,14 +29,16 @@ mosamatic2/core/data/numpyimage.py,sha256=bnG6WVGSRxNdzIlb2DNj5u6Gv4BAYscIj2Buyj
|
|
|
26
29
|
mosamatic2/core/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
30
|
mosamatic2/core/managers/logmanager.py,sha256=NEaXvhl0aILjBbK710GaWanVuuNvB51HpHhE5rgYvng,1391
|
|
28
31
|
mosamatic2/core/managers/logmanagerlistener.py,sha256=Gaig07yjBnyQq9I8sN85olTEeDCDyCFQnEJdwzvmgvc,99
|
|
29
|
-
mosamatic2/core/pipelines/__init__.py,sha256=
|
|
32
|
+
mosamatic2/core/pipelines/__init__.py,sha256=YPlyO5L_DMrTyO2-2TBq_Bdg-bbMHQiicXJafn66Dg8,283
|
|
33
|
+
mosamatic2/core/pipelines/boadockerpipeline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
+
mosamatic2/core/pipelines/boadockerpipeline/boadockerpipeline.py,sha256=KFZk_L_0KaTdUqHjqGW8MysbCIOKlUKWP8S-T9xyvis,2939
|
|
30
35
|
mosamatic2/core/pipelines/defaultdockerpipeline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
-
mosamatic2/core/pipelines/defaultdockerpipeline/defaultdockerpipeline.py,sha256=
|
|
36
|
+
mosamatic2/core/pipelines/defaultdockerpipeline/defaultdockerpipeline.py,sha256=m_f9c7MmvSRqERF6n-nLGxa6xP13xtoSihW53d387zU,1315
|
|
32
37
|
mosamatic2/core/pipelines/defaultpipeline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
38
|
mosamatic2/core/pipelines/defaultpipeline/defaultpipeline.py,sha256=Bme0r_shnrllWYCYDNc6cLM2fQC2yD8RJKpRdoh_6Uc,3077
|
|
34
39
|
mosamatic2/core/pipelines/pipeline.py,sha256=mRxKXLKwgKDpc8R9mCI6gDKGJ2lKVxRQ__Sf0Mfn_Qc,384
|
|
35
40
|
mosamatic2/core/singleton.py,sha256=FV0k_LlOCmFhlWN6gf1c2x7YXWyd8-7DsIMvOKrI6NY,224
|
|
36
|
-
mosamatic2/core/tasks/__init__.py,sha256=
|
|
41
|
+
mosamatic2/core/tasks/__init__.py,sha256=iJ4uMgLtN-PmZ43FAw8Y_QLh03ctiTaCGZxAUsVFXPM,857
|
|
37
42
|
mosamatic2/core/tasks/calculatescorestask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
43
|
mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py,sha256=cwGVedJR_BGSYzXq6ouTgCbSC6s2VtyD8FzRC-QBXUI,6617
|
|
39
44
|
mosamatic2/core/tasks/createdicomsummarytask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -41,7 +46,7 @@ mosamatic2/core/tasks/createdicomsummarytask/createdicomsummarytask.py,sha256=kc
|
|
|
41
46
|
mosamatic2/core/tasks/createpngsfromsegmentationstask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
47
|
mosamatic2/core/tasks/createpngsfromsegmentationstask/createpngsfromsegmentationstask.py,sha256=1UpOsp1CH668BQ0g4tALou_tFgisC306VcvqOKSDuTo,1884
|
|
43
48
|
mosamatic2/core/tasks/dicom2niftitask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
|
-
mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py,sha256=
|
|
49
|
+
mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py,sha256=jvkgC5rkSC0kAy_OXM10RsjAUYi6A76dYjCXQmpycFY,1436
|
|
45
50
|
mosamatic2/core/tasks/rescaledicomimagestask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
51
|
mosamatic2/core/tasks/rescaledicomimagestask/rescaledicomimagestask.py,sha256=vGSpMJoXFtE-IHGxTEO9DMkerMJcfG5r9tXgtvkxm6Y,3053
|
|
47
52
|
mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -50,15 +55,17 @@ mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorf
|
|
|
50
55
|
mosamatic2/core/tasks/selectslicefromscanstask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
56
|
mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py,sha256=EIEHhWoGL30rcz8qckFl465rU40P-pIkvhMOfSud7Yw,7253
|
|
52
57
|
mosamatic2/core/tasks/task.py,sha256=APPnid6dpSGkPuDqU1vm2RIMR5vkpvbP1CPHUMjympg,1691
|
|
53
|
-
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=y8ujz-sX5nMH2eqvCyzXK2AqYCVVppvJFmGi0UgFXTk,12170
|
|
54
61
|
mosamatic2/server.py,sha256=-cZ9BPsZUXoINKqwhCHN8c59mlvzzDXzTVxsYt9au70,4644
|
|
55
62
|
mosamatic2/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
56
|
-
mosamatic2/ui/mainwindow.py,sha256=
|
|
63
|
+
mosamatic2/ui/mainwindow.py,sha256=DrX-6Hu4-3wJJy6rZkexYuTQ-pO_uliyEQVkYc4GuCM,15848
|
|
57
64
|
mosamatic2/ui/resources/icons/mosamatic2.icns,sha256=OfhC-diJTIgaNMOezxKKilGsY7mRkaGdU5dGr0MOjIA,2994125
|
|
58
65
|
mosamatic2/ui/resources/icons/mosamatic2.ico,sha256=ySD3RYluHK3pgS0Eas7eKrVk_AskdLQ4qs_IT-wNhq4,12229
|
|
59
66
|
mosamatic2/ui/resources/icons/spinner.gif,sha256=rvaac6GUZauHSPFSOLWr0RmLfjmtZih2Q8knQ2WP3Po,16240
|
|
60
67
|
mosamatic2/ui/resources/images/body-composition.jpg,sha256=KD-BudbXwThB4lJOZZN-ad5-TZRaaZ5cKTH0Ar1TOZs,21227
|
|
61
|
-
mosamatic2/ui/resources/VERSION,sha256=
|
|
68
|
+
mosamatic2/ui/resources/VERSION,sha256=cadBjTxujszIMUSfzEbzxhs3Pql7CycMb88twqbpTEk,9
|
|
62
69
|
mosamatic2/ui/settings.py,sha256=YEVHYJIfNsqMO3v1pjzgh7Pih9GGoUX7S9s8S-sBNUk,2121
|
|
63
70
|
mosamatic2/ui/utils.py,sha256=6bbPIrh4RJ_yhQKNZrgPbL4XeUEogjIjbk_e5c3QS5g,853
|
|
64
71
|
mosamatic2/ui/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -70,7 +77,8 @@ mosamatic2/ui/widgets/panels/defaultpanel.py,sha256=Ry32Xv6ibxm6NdZ7eBOCcisWNcmn
|
|
|
70
77
|
mosamatic2/ui/widgets/panels/logpanel.py,sha256=ogswJ6_ryb6u7JeVnOsh2Ez8KWg6jtCFZwij8s87xO4,1861
|
|
71
78
|
mosamatic2/ui/widgets/panels/mainpanel.py,sha256=KqI8dA7GpLFd2unqVRTBkNxdnh6AWGpVPwQuaEg8PmI,2431
|
|
72
79
|
mosamatic2/ui/widgets/panels/pipelines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
73
|
-
mosamatic2/ui/widgets/panels/pipelines/
|
|
80
|
+
mosamatic2/ui/widgets/panels/pipelines/boadockerpipelinepanel.py,sha256=bsyyt-42Qho6D4rpSjo9sBJBM9ISOChX-N8QWc3j2s0,7711
|
|
81
|
+
mosamatic2/ui/widgets/panels/pipelines/defaultdockerpipelinepanel.py,sha256=smEymbmYQ1wy0lgkhjGAGhXQedIhE8wjiX2TqYF6VB8,13284
|
|
74
82
|
mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py,sha256=qkfI4BnLIXqE_YvSQj4sO_FjnK0eVdIMqAZ8sktgI-8,13727
|
|
75
83
|
mosamatic2/ui/widgets/panels/pipelines/pipelinepanel.py,sha256=SlkKme8Wv2Bvp2Alen98mFjv3F5eZCwJylj294gd5uU,178
|
|
76
84
|
mosamatic2/ui/widgets/panels/stackedpanel.py,sha256=dK1YWuHUzxRhVb5gP0Lu9rAiW4XagjcHmGF__5Lpufk,657
|
|
@@ -83,15 +91,16 @@ mosamatic2/ui/widgets/panels/tasks/rescaledicomimagestaskpanel.py,sha256=ds7Jxyn
|
|
|
83
91
|
mosamatic2/ui/widgets/panels/tasks/segmentmusclefatl3tensorflowtaskpanel.py,sha256=QCEZs9lqaE-XAJuyyrfZVnFkNRyjMw6Cfa-6qP9WaV8,9630
|
|
84
92
|
mosamatic2/ui/widgets/panels/tasks/selectslicefromscanstaskpanel.py,sha256=meKltgxPReZ9HioSop6jW_2CFm18URBy3LX11U8tbtc,8059
|
|
85
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
|
|
86
95
|
mosamatic2/ui/widgets/panels/visualizations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
87
96
|
mosamatic2/ui/widgets/panels/visualizations/slicevisualization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
88
97
|
mosamatic2/ui/widgets/panels/visualizations/slicevisualization/custominteractorstyle.py,sha256=GaXmO5SvXwLAnrVtAGglTXgPx6mD0Q6f8gYrGN5WRO4,2644
|
|
89
|
-
mosamatic2/ui/widgets/panels/visualizations/slicevisualization/sliceviewer.py,sha256=
|
|
98
|
+
mosamatic2/ui/widgets/panels/visualizations/slicevisualization/sliceviewer.py,sha256=yMjKzV_r6oPDMtz4-bLSWqTUBv42Jl4uHV-T3H51Xuo,4613
|
|
90
99
|
mosamatic2/ui/widgets/panels/visualizations/slicevisualization/slicevisualization.py,sha256=2cLTHsGrdMMt_dbnq0EUAFSSW7naR6B1neB7RnUOmjQ,4214
|
|
91
100
|
mosamatic2/ui/widgets/panels/visualizations/visualization.py,sha256=JvqTJi7cCGYK1-wrN2oURdCOBoPS2clVUyYglhkoVJg,178
|
|
92
101
|
mosamatic2/ui/widgets/splashscreen.py,sha256=MS-OczOWfwwEQNQd-JWe9_Mh57css0cSQgbu973rwQo,4056
|
|
93
102
|
mosamatic2/ui/worker.py,sha256=v7e3gq7MUudgpB1BJW-P7j5wurzu6-HG5m7I6WHgJp0,699
|
|
94
|
-
mosamatic2-2.0.
|
|
95
|
-
mosamatic2-2.0.
|
|
96
|
-
mosamatic2-2.0.
|
|
97
|
-
mosamatic2-2.0.
|
|
103
|
+
mosamatic2-2.0.14.dist-info/entry_points.txt,sha256=MCUpKkgbej1clgp8EqlLQGs0BIKwGPcBPiVWLfGz9Gw,126
|
|
104
|
+
mosamatic2-2.0.14.dist-info/METADATA,sha256=Sxzdp8j94khefNNBq1PE8DIg8QEcIHSrl6tNhvzu7hg,1534
|
|
105
|
+
mosamatic2-2.0.14.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
106
|
+
mosamatic2-2.0.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|