mosamatic2 2.0.15__py3-none-any.whl → 2.0.17__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 +6 -0
- mosamatic2/commands/calculatemaskstatistics.py +59 -0
- mosamatic2/commands/defaultdockerpipeline.py +12 -1
- mosamatic2/commands/liveranalysispipeline.py +61 -0
- mosamatic2/commands/totalsegmentator.py +11 -2
- mosamatic2/core/pipelines/__init__.py +2 -1
- mosamatic2/core/pipelines/defaultdockerpipeline/defaultdockerpipeline.py +2 -9
- mosamatic2/core/pipelines/liveranalysispipeline/__init__.py +0 -0
- mosamatic2/core/pipelines/liveranalysispipeline/liveranalysispipeline.py +48 -0
- mosamatic2/core/tasks/__init__.py +2 -1
- mosamatic2/core/tasks/calculatemaskstatisticstask/__init__.py +0 -0
- mosamatic2/core/tasks/calculatemaskstatisticstask/calculatemaskstatisticstask.py +104 -0
- mosamatic2/core/tasks/totalsegmentatortask/totalsegmentatortask.py +35 -10
- mosamatic2/ui/mainwindow.py +30 -0
- mosamatic2/ui/resources/VERSION +1 -1
- mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py +1 -1
- mosamatic2/ui/widgets/panels/pipelines/liveranalysispipelinepanel.py +187 -0
- mosamatic2/ui/widgets/panels/tasks/calculatemaskstatisticstaskpanel.py +215 -0
- mosamatic2/ui/widgets/panels/visualizations/slicevisualization/custominteractorstyle.py +18 -0
- mosamatic2/ui/widgets/panels/visualizations/slicevisualization/sliceviewer.py +21 -3
- mosamatic2/ui/widgets/panels/visualizations/slicevisualization/slicevisualization.py +36 -1
- {mosamatic2-2.0.15.dist-info → mosamatic2-2.0.17.dist-info}/METADATA +4 -4
- {mosamatic2-2.0.15.dist-info → mosamatic2-2.0.17.dist-info}/RECORD +25 -17
- {mosamatic2-2.0.15.dist-info → mosamatic2-2.0.17.dist-info}/WHEEL +0 -0
- {mosamatic2-2.0.15.dist-info → mosamatic2-2.0.17.dist-info}/entry_points.txt +0 -0
mosamatic2/cli.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from mosamatic2.commands import (
|
|
3
3
|
calculatescores,
|
|
4
|
+
calculatemaskstatistics,
|
|
4
5
|
rescaledicomimages,
|
|
5
6
|
segmentmusclefatl3tensorflow,
|
|
6
7
|
createpngsfromsegmentations,
|
|
@@ -10,6 +11,8 @@ from mosamatic2.commands import (
|
|
|
10
11
|
defaultpipeline,
|
|
11
12
|
defaultdockerpipeline,
|
|
12
13
|
boadockerpipeline,
|
|
14
|
+
liveranalysispipeline,
|
|
15
|
+
totalsegmentator,
|
|
13
16
|
)
|
|
14
17
|
from mosamatic2.core.utils import show_doc_command
|
|
15
18
|
|
|
@@ -32,6 +35,7 @@ def main():
|
|
|
32
35
|
|
|
33
36
|
|
|
34
37
|
main.add_command(calculatescores.calculatescores)
|
|
38
|
+
main.add_command(calculatemaskstatistics.calculatemaskstatistics)
|
|
35
39
|
main.add_command(rescaledicomimages.rescaledicomimages)
|
|
36
40
|
main.add_command(segmentmusclefatl3tensorflow.segmentmusclefatl3tensorflow)
|
|
37
41
|
main.add_command(createpngsfromsegmentations.createpngsfromsegmentations)
|
|
@@ -41,4 +45,6 @@ main.add_command(createdicomsummary.createdicomsummary)
|
|
|
41
45
|
main.add_command(defaultpipeline.defaultpipeline)
|
|
42
46
|
main.add_command(defaultdockerpipeline.defaultdockerpipeline)
|
|
43
47
|
main.add_command(boadockerpipeline.boadockerpipeline)
|
|
48
|
+
main.add_command(liveranalysispipeline.liveranalysispipeline)
|
|
49
|
+
main.add_command(totalsegmentator.totalsegmentator)
|
|
44
50
|
main.add_command(show_doc_command(main)) # Special command to show long description for command
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from mosamatic2.core.tasks import CalculateMaskStatisticsTask
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command(help='Calculates segmentation mask statistics')
|
|
7
|
+
@click.option(
|
|
8
|
+
'--scans',
|
|
9
|
+
required=True,
|
|
10
|
+
type=click.Path(exists=True),
|
|
11
|
+
help='Directory with scans in NIFTI format',
|
|
12
|
+
)
|
|
13
|
+
@click.option(
|
|
14
|
+
'--masks',
|
|
15
|
+
required=True,
|
|
16
|
+
type=click.Path(exists=True),
|
|
17
|
+
help='Directory with segmentation mask files in NIFTI format',
|
|
18
|
+
)
|
|
19
|
+
@click.option(
|
|
20
|
+
'--output',
|
|
21
|
+
required=True,
|
|
22
|
+
type=click.Path(),
|
|
23
|
+
help='Output directory'
|
|
24
|
+
)
|
|
25
|
+
@click.option(
|
|
26
|
+
'--overwrite',
|
|
27
|
+
type=click.BOOL,
|
|
28
|
+
default=False,
|
|
29
|
+
help='Overwrite [true|false]'
|
|
30
|
+
)
|
|
31
|
+
def calculatemaskstatistics(scans, masks, output, overwrite):
|
|
32
|
+
"""
|
|
33
|
+
Calculates segmentation mask statistics. The following metrics are calculated:
|
|
34
|
+
|
|
35
|
+
(1) Mean radiation attenuation (HU)
|
|
36
|
+
(2) Standard deviation radiation attenuation (HU)
|
|
37
|
+
(3) Segmentation mask volume (mL)
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
--scans : str
|
|
42
|
+
Directory with scans in NIFTI format
|
|
43
|
+
|
|
44
|
+
--masks : str
|
|
45
|
+
Directory with segmentation mask files in NIFTI format
|
|
46
|
+
|
|
47
|
+
--output : str
|
|
48
|
+
Path to output directory
|
|
49
|
+
|
|
50
|
+
--overwrite : bool
|
|
51
|
+
Overwrite contents output directory [true|false]
|
|
52
|
+
"""
|
|
53
|
+
task = CalculateMaskStatisticsTask(
|
|
54
|
+
inputs={'scans': scans, 'masks': masks},
|
|
55
|
+
params=None,
|
|
56
|
+
output=output,
|
|
57
|
+
overwrite=overwrite,
|
|
58
|
+
)
|
|
59
|
+
task.run()
|
|
@@ -16,6 +16,11 @@ from mosamatic2.core.pipelines import DefaultDockerPipeline
|
|
|
16
16
|
type=click.Path(),
|
|
17
17
|
help='Input directory with AI model files (no spaces allowed)'
|
|
18
18
|
)
|
|
19
|
+
@click.option(
|
|
20
|
+
'--version',
|
|
21
|
+
required=True,
|
|
22
|
+
help='Docker image version'
|
|
23
|
+
)
|
|
19
24
|
@click.option(
|
|
20
25
|
'--output',
|
|
21
26
|
required=True,
|
|
@@ -28,7 +33,7 @@ from mosamatic2.core.pipelines import DefaultDockerPipeline
|
|
|
28
33
|
default=False,
|
|
29
34
|
help='Overwrite [true|false]'
|
|
30
35
|
)
|
|
31
|
-
def defaultdockerpipeline(images, model_files, output, overwrite):
|
|
36
|
+
def defaultdockerpipeline(images, model_files, version, output, overwrite):
|
|
32
37
|
"""
|
|
33
38
|
Runs default L3 analysis pipeline through Docker
|
|
34
39
|
|
|
@@ -51,6 +56,11 @@ def defaultdockerpipeline(images, model_files, output, overwrite):
|
|
|
51
56
|
|
|
52
57
|
Warning: This directory path cannot contain any spaces!
|
|
53
58
|
|
|
59
|
+
--version : str
|
|
60
|
+
Docker image version, e.g., 2.0.16
|
|
61
|
+
Check https://hub.docker.com/repository/docker/brecheisen/mosamatic2-cli/general
|
|
62
|
+
for the latest version and older versions.
|
|
63
|
+
|
|
54
64
|
--output : str
|
|
55
65
|
Path to output directory (no spaces!)
|
|
56
66
|
|
|
@@ -66,6 +76,7 @@ def defaultdockerpipeline(images, model_files, output, overwrite):
|
|
|
66
76
|
'file_type': 'npy',
|
|
67
77
|
'fig_width': 10,
|
|
68
78
|
'fig_height': 10,
|
|
79
|
+
'version': version,
|
|
69
80
|
},
|
|
70
81
|
output=output,
|
|
71
82
|
overwrite=overwrite,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from mosamatic2.core.pipelines import LiverAnalysisPipeline
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command(help='Runs liver analysis pipeline')
|
|
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
|
+
'--compressed',
|
|
15
|
+
default=True,
|
|
16
|
+
help='Whether to produce compressed NIFTI file or not (default: True)'
|
|
17
|
+
)
|
|
18
|
+
@click.option(
|
|
19
|
+
'--output',
|
|
20
|
+
required=True,
|
|
21
|
+
type=click.Path(),
|
|
22
|
+
help='Output directory (no spaces allowed)'
|
|
23
|
+
)
|
|
24
|
+
@click.option(
|
|
25
|
+
'--overwrite',
|
|
26
|
+
type=click.BOOL,
|
|
27
|
+
default=False,
|
|
28
|
+
help='Overwrite [true|false]'
|
|
29
|
+
)
|
|
30
|
+
def liveranalysispipeline(scans, compressed, output, overwrite):
|
|
31
|
+
"""
|
|
32
|
+
Runs liver analysis pipeline. This pipeline run the following steps on each scan:
|
|
33
|
+
|
|
34
|
+
(1) DICOM to NIFTI conversion
|
|
35
|
+
(2) Extract liver segments using Total Segmentator
|
|
36
|
+
(3) Calculate segment statistics, e.g., volume (mL), mean HU, std HU and PNG image
|
|
37
|
+
of each segments HU histogram.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
--scans : str
|
|
42
|
+
Root directory with scan directories for each patient. Each scan directory should
|
|
43
|
+
contain DICOM images for a single scan only and nothing else. Also, the directory
|
|
44
|
+
paths cannot contain any spaces.
|
|
45
|
+
|
|
46
|
+
--compressed : str
|
|
47
|
+
Whether to produce compressed NIFTI file or not (default: True)
|
|
48
|
+
|
|
49
|
+
--output : str
|
|
50
|
+
Path to output directory. No spaces allowed.
|
|
51
|
+
|
|
52
|
+
--overwrite : bool
|
|
53
|
+
Overwrite contents output directory [true|false]
|
|
54
|
+
"""
|
|
55
|
+
pipeline = LiverAnalysisPipeline(
|
|
56
|
+
inputs={'scans': scans},
|
|
57
|
+
params={'compressed': compressed},
|
|
58
|
+
output=output,
|
|
59
|
+
overwrite=overwrite,
|
|
60
|
+
)
|
|
61
|
+
pipeline.run()
|
|
@@ -21,13 +21,18 @@ from mosamatic2.core.tasks import TotalSegmentatorTask
|
|
|
21
21
|
default='total',
|
|
22
22
|
help='Comma-separated list of Total Segmentator tasks to run (no spaces!)'
|
|
23
23
|
)
|
|
24
|
+
@click.option(
|
|
25
|
+
'--format',
|
|
26
|
+
default='dicom',
|
|
27
|
+
help='Process scans in DICOM or NIFTI format [dicom|nifti] (default: dicom)'
|
|
28
|
+
)
|
|
24
29
|
@click.option(
|
|
25
30
|
'--overwrite',
|
|
26
31
|
type=click.BOOL,
|
|
27
32
|
default=False,
|
|
28
33
|
help='Overwrite [true|false]'
|
|
29
34
|
)
|
|
30
|
-
def totalsegmentator(scans, tasks, output, overwrite):
|
|
35
|
+
def totalsegmentator(scans, tasks, format, output, overwrite):
|
|
31
36
|
"""
|
|
32
37
|
Run Total Segmentator on CT scans. If you want to run specialized tasks
|
|
33
38
|
like "liver_segments" or "liver_vessels" you need an educational license.
|
|
@@ -56,12 +61,16 @@ def totalsegmentator(scans, tasks, output, overwrite):
|
|
|
56
61
|
--tasks : str
|
|
57
62
|
Comma-separated list of Total Segmentator tasks to run (no spaces!)
|
|
58
63
|
|
|
64
|
+
--format : str
|
|
65
|
+
Process scans in DICOM or NIFTI format. Options: [dicom|nifti]
|
|
66
|
+
Default is 'dicom'.
|
|
67
|
+
|
|
59
68
|
--overwrite : bool
|
|
60
69
|
Overwrite contents output directory [true|false]
|
|
61
70
|
"""
|
|
62
71
|
task = TotalSegmentatorTask(
|
|
63
72
|
inputs={'scans': scans},
|
|
64
|
-
params={'tasks': tasks},
|
|
73
|
+
params={'tasks': tasks, 'format': format},
|
|
65
74
|
output=output,
|
|
66
75
|
overwrite=overwrite,
|
|
67
76
|
)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
from mosamatic2.core.pipelines.defaultpipeline.defaultpipeline import DefaultPipeline
|
|
2
2
|
from mosamatic2.core.pipelines.defaultdockerpipeline.defaultdockerpipeline import DefaultDockerPipeline
|
|
3
|
-
from mosamatic2.core.pipelines.boadockerpipeline.boadockerpipeline import BoaDockerPipeline
|
|
3
|
+
from mosamatic2.core.pipelines.boadockerpipeline.boadockerpipeline import BoaDockerPipeline
|
|
4
|
+
from mosamatic2.core.pipelines.liveranalysispipeline.liveranalysispipeline import LiverAnalysisPipeline
|
|
@@ -11,15 +11,8 @@ class DefaultDockerPipeline(Pipeline):
|
|
|
11
11
|
'images',
|
|
12
12
|
'model_files',
|
|
13
13
|
]
|
|
14
|
-
PARAMS = [
|
|
15
|
-
|
|
16
|
-
'file_type',
|
|
17
|
-
'fig_width',
|
|
18
|
-
'fig_height',
|
|
19
|
-
'model_type',
|
|
20
|
-
'model_version',
|
|
21
|
-
'version',
|
|
22
|
-
]
|
|
14
|
+
PARAMS = ['version']
|
|
15
|
+
|
|
23
16
|
def __init__(self, inputs, params, output, overwrite):
|
|
24
17
|
super(DefaultDockerPipeline, self).__init__(inputs, params, output, overwrite)
|
|
25
18
|
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from mosamatic2.core.pipelines.pipeline import Pipeline
|
|
3
|
+
from mosamatic2.core.tasks import (
|
|
4
|
+
Dicom2NiftiTask,
|
|
5
|
+
TotalSegmentatorTask,
|
|
6
|
+
CalculateMaskStatisticsTask,
|
|
7
|
+
)
|
|
8
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
9
|
+
|
|
10
|
+
LOG = LogManager()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LiverAnalysisPipeline(Pipeline):
|
|
14
|
+
INPUTS = ['scans']
|
|
15
|
+
PARAMS = ['compressed']
|
|
16
|
+
|
|
17
|
+
def __init__(self, inputs, params, output, overwrite):
|
|
18
|
+
super(LiverAnalysisPipeline, self).__init__(inputs, params, output, overwrite)
|
|
19
|
+
self.add_task(
|
|
20
|
+
Dicom2NiftiTask(
|
|
21
|
+
inputs={'scans': self.input('scans')},
|
|
22
|
+
params={'compressed': self.param('compressed')},
|
|
23
|
+
output=self.output(),
|
|
24
|
+
overwrite=self.overwrite(),
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
self.add_task(
|
|
28
|
+
TotalSegmentatorTask(
|
|
29
|
+
inputs={'scans': os.path.join(self.output(), 'dicom2niftitask')},
|
|
30
|
+
params={
|
|
31
|
+
'tasks': 'liver_segments',
|
|
32
|
+
'format': 'nifti',
|
|
33
|
+
},
|
|
34
|
+
output=self.output(),
|
|
35
|
+
overwrite=self.overwrite(),
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
self.add_task(
|
|
39
|
+
CalculateMaskStatisticsTask(
|
|
40
|
+
inputs={
|
|
41
|
+
'scans': os.path.join(self.output(), 'dicom2niftitask'),
|
|
42
|
+
'masks': os.path.join(self.output(), 'totalsegmentatortask'),
|
|
43
|
+
},
|
|
44
|
+
params=None,
|
|
45
|
+
output=self.output(),
|
|
46
|
+
overwrite=self.overwrite(),
|
|
47
|
+
)
|
|
48
|
+
)
|
|
@@ -5,4 +5,5 @@ from mosamatic2.core.tasks.createpngsfromsegmentationstask.createpngsfromsegment
|
|
|
5
5
|
from mosamatic2.core.tasks.dicom2niftitask.dicom2niftitask import Dicom2NiftiTask
|
|
6
6
|
from mosamatic2.core.tasks.selectslicefromscanstask.selectslicefromscanstask import SelectSliceFromScansTask
|
|
7
7
|
from mosamatic2.core.tasks.createdicomsummarytask.createdicomsummarytask import CreateDicomSummaryTask
|
|
8
|
-
from mosamatic2.core.tasks.totalsegmentatortask.totalsegmentatortask import TotalSegmentatorTask
|
|
8
|
+
from mosamatic2.core.tasks.totalsegmentatortask.totalsegmentatortask import TotalSegmentatorTask
|
|
9
|
+
from mosamatic2.core.tasks.calculatemaskstatisticstask.calculatemaskstatisticstask import CalculateMaskStatisticsTask
|
|
File without changes
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
import nibabel as nb
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
from mosamatic2.core.tasks.task import Task
|
|
7
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
8
|
+
|
|
9
|
+
LOG = LogManager()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CalculateMaskStatisticsTask(Task):
|
|
13
|
+
INPUTS = ['scans', 'masks']
|
|
14
|
+
PARAMS = []
|
|
15
|
+
|
|
16
|
+
def __init__(self, inputs, params, output, overwrite):
|
|
17
|
+
super(CalculateMaskStatisticsTask, self).__init__(inputs, params, output, overwrite)
|
|
18
|
+
|
|
19
|
+
def load_scans(self):
|
|
20
|
+
scans = []
|
|
21
|
+
for f in os.listdir(self.input('scans')):
|
|
22
|
+
if f.endswith('.nii.gz'):
|
|
23
|
+
scan = os.path.join(self.input('scans'), f)
|
|
24
|
+
if os.path.isfile(scan):
|
|
25
|
+
scans.append(scan)
|
|
26
|
+
return scans
|
|
27
|
+
|
|
28
|
+
def load_mask_files(self):
|
|
29
|
+
mask_files = []
|
|
30
|
+
for f in os.listdir(self.input('masks')):
|
|
31
|
+
if f.endswith('.nii.gz'):
|
|
32
|
+
mask_file = os.path.join(self.input('masks'), f)
|
|
33
|
+
if os.path.isfile(mask_file):
|
|
34
|
+
mask_files.append(mask_file)
|
|
35
|
+
return mask_files
|
|
36
|
+
|
|
37
|
+
def collect_scan_mask_file_pairs(self, scans, mask_files):
|
|
38
|
+
scan_mask_file_pairs = []
|
|
39
|
+
for scan in scans:
|
|
40
|
+
scan_name = os.path.split(scan)[1][:-7]
|
|
41
|
+
for mask_file in mask_files:
|
|
42
|
+
mask_file_name = os.path.split(mask_file)[1][:-7]
|
|
43
|
+
if scan_name in mask_file_name:
|
|
44
|
+
scan_mask_file_pairs.append((scan, mask_file))
|
|
45
|
+
return scan_mask_file_pairs
|
|
46
|
+
|
|
47
|
+
def get_masked_voxels(self, scan_mask_file_pair):
|
|
48
|
+
scan_image_file = scan_mask_file_pair[0]
|
|
49
|
+
scan_image = nb.load(scan_image_file)
|
|
50
|
+
scan_image_data = scan_image.get_fdata()
|
|
51
|
+
mask_file_image_file = scan_mask_file_pair[1]
|
|
52
|
+
mask_file_image_file_name = os.path.split(mask_file_image_file)[1]
|
|
53
|
+
mask_file_image = nb.load(mask_file_image_file)
|
|
54
|
+
mask_file_image_data = mask_file_image.get_fdata()
|
|
55
|
+
mask = mask_file_image_data > 0
|
|
56
|
+
masked_voxels = scan_image_data[mask]
|
|
57
|
+
return masked_voxels, mask_file_image_file_name
|
|
58
|
+
|
|
59
|
+
def calculate_volume_in_mL(self, mask_file):
|
|
60
|
+
nifti_image = nb.load(mask_file)
|
|
61
|
+
nifti_image_data = nifti_image.get_fdata()
|
|
62
|
+
voxel_dims = nifti_image.header.get_zooms()[:3]
|
|
63
|
+
voxel_volume = np.prod(voxel_dims)
|
|
64
|
+
num_voxels = np.sum(nifti_image_data > 0.5)
|
|
65
|
+
mask_volume = num_voxels * voxel_volume / 1000.0
|
|
66
|
+
return mask_volume
|
|
67
|
+
|
|
68
|
+
def create_histogram_png(self, masked_voxels, file_name):
|
|
69
|
+
histogram_png_file = os.path.join(self.output(), f'{file_name}.png')
|
|
70
|
+
plt.figure(figsize=(8,6))
|
|
71
|
+
plt.hist(masked_voxels, bins=100, color='steelblue', edgecolor='black')
|
|
72
|
+
plt.title(f'Histogram of HU values inside {file_name}')
|
|
73
|
+
plt.xlabel('HU')
|
|
74
|
+
plt.ylabel('Frequency')
|
|
75
|
+
plt.savefig(histogram_png_file, dpi=300, bbox_inches='tight')
|
|
76
|
+
plt.close()
|
|
77
|
+
|
|
78
|
+
def save_data_to_file(self, data):
|
|
79
|
+
csv_file_path = os.path.join(self.output(), 'statistics.csv')
|
|
80
|
+
xls_file_path = os.path.join(self.output(), 'statistics.xlsx')
|
|
81
|
+
df = pd.DataFrame(data=data)
|
|
82
|
+
df.to_csv(csv_file_path, index=False, sep=';')
|
|
83
|
+
df.to_excel(xls_file_path, index=False, engine='openpyxl')
|
|
84
|
+
|
|
85
|
+
def run(self):
|
|
86
|
+
scans = self.load_scans()
|
|
87
|
+
mask_files = self.load_mask_files()
|
|
88
|
+
scan_mask_file_pairs = self.collect_scan_mask_file_pairs(scans, mask_files)
|
|
89
|
+
nr_steps = len(scan_mask_file_pairs)
|
|
90
|
+
data = {
|
|
91
|
+
'file': [],
|
|
92
|
+
'mean_HU': [],
|
|
93
|
+
'std_HU': [],
|
|
94
|
+
'volume_mL': [],
|
|
95
|
+
}
|
|
96
|
+
for step in range(nr_steps):
|
|
97
|
+
masked_voxels, file_name = self.get_masked_voxels(scan_mask_file_pairs[step])
|
|
98
|
+
data['file'].append(file_name)
|
|
99
|
+
data['mean_HU'].append(round(np.mean(masked_voxels)))
|
|
100
|
+
data['std_HU'].append(round(np.std(masked_voxels)))
|
|
101
|
+
data['volume_mL'].append(round(self.calculate_volume_in_mL(scan_mask_file_pairs[step][1])))
|
|
102
|
+
self.create_histogram_png(masked_voxels, file_name)
|
|
103
|
+
self.set_progress(step, nr_steps)
|
|
104
|
+
self.save_data_to_file(data)
|
|
@@ -11,7 +11,7 @@ TOTAL_SEGMENTATOR_OUTPUT_DIR = os.path.join(tempfile.gettempdir(), 'total_segmen
|
|
|
11
11
|
|
|
12
12
|
class TotalSegmentatorTask(Task):
|
|
13
13
|
INPUTS = ['scans']
|
|
14
|
-
PARAMS = ['tasks']
|
|
14
|
+
PARAMS = ['tasks', 'format']
|
|
15
15
|
|
|
16
16
|
def __init__(self, inputs, params, output, overwrite):
|
|
17
17
|
super(TotalSegmentatorTask, self).__init__(inputs, params, output, overwrite)
|
|
@@ -25,26 +25,51 @@ class TotalSegmentatorTask(Task):
|
|
|
25
25
|
scan_dirs.append(scan_dir)
|
|
26
26
|
return scan_dirs
|
|
27
27
|
|
|
28
|
-
def
|
|
28
|
+
def load_scans(self):
|
|
29
|
+
scans = []
|
|
30
|
+
for f in os.listdir(self.input('scans')):
|
|
31
|
+
if f.endswith('.nii.gz'):
|
|
32
|
+
scan = os.path.join(self.input('scans'), f)
|
|
33
|
+
if os.path.isfile(scan):
|
|
34
|
+
scans.append(scan)
|
|
35
|
+
return scans
|
|
36
|
+
|
|
37
|
+
def extract_masks(self, scan_dir_or_file):
|
|
29
38
|
os.makedirs(TOTAL_SEGMENTATOR_OUTPUT_DIR, exist_ok=True)
|
|
30
39
|
tasks = self.param('tasks').split(",") if self.param('tasks') else []
|
|
31
40
|
for task in tasks:
|
|
32
41
|
LOG.info(f'Running task {task}...')
|
|
33
|
-
totalsegmentator(input=
|
|
42
|
+
totalsegmentator(input=scan_dir_or_file, output=TOTAL_SEGMENTATOR_OUTPUT_DIR, task=task)
|
|
43
|
+
|
|
44
|
+
def delete_total_segmentator_output(self):
|
|
45
|
+
if os.path.exists(TOTAL_SEGMENTATOR_OUTPUT_DIR):
|
|
46
|
+
shutil.rmtree(TOTAL_SEGMENTATOR_OUTPUT_DIR)
|
|
34
47
|
|
|
35
48
|
def run(self):
|
|
36
|
-
|
|
37
|
-
|
|
49
|
+
if self.param('format') == 'dicom':
|
|
50
|
+
scan_dirs_or_files = self.load_scan_dirs()
|
|
51
|
+
elif self.param('format') == 'nifti':
|
|
52
|
+
scan_dirs_or_files = self.load_scans()
|
|
53
|
+
else:
|
|
54
|
+
LOG.error('Unknown format: {}'.format(self.param('format')))
|
|
55
|
+
return
|
|
56
|
+
nr_steps = len(scan_dirs_or_files)
|
|
38
57
|
for step in range(nr_steps):
|
|
39
|
-
|
|
58
|
+
scan_dir_or_file = scan_dirs_or_files[step]
|
|
59
|
+
scan_dir_or_file_name = os.path.split(scan_dir_or_file)[1]
|
|
60
|
+
if self.param('format') == 'nifti':
|
|
61
|
+
scan_dir_or_file_name = scan_dir_or_file_name[:-7]
|
|
40
62
|
try:
|
|
41
|
-
self.extract_masks(
|
|
63
|
+
self.extract_masks(scan_dir_or_file)
|
|
42
64
|
except Exception as e:
|
|
43
|
-
LOG.error(f'{
|
|
65
|
+
LOG.error(f'{scan_dir_or_file}: Could not extract masks [{str(e)}]. Skipping scan...')
|
|
44
66
|
self.set_progress(step, nr_steps)
|
|
45
67
|
LOG.info(f'Copying temporary output to final output directory...')
|
|
46
68
|
for f in os.listdir(TOTAL_SEGMENTATOR_OUTPUT_DIR):
|
|
47
69
|
if f.endswith('.nii') or f.endswith('.nii.gz'):
|
|
48
70
|
f_path = os.path.join(TOTAL_SEGMENTATOR_OUTPUT_DIR, f)
|
|
49
|
-
|
|
50
|
-
|
|
71
|
+
target_file = os.path.join(self.output(), f'{scan_dir_or_file_name}_{f}')
|
|
72
|
+
shutil.copyfile(f_path, target_file)
|
|
73
|
+
LOG.info(f'Copied {f} to {target_file}')
|
|
74
|
+
LOG.info('Cleaning up Total Segmentator temporary output...')
|
|
75
|
+
self.delete_total_segmentator_output()
|
mosamatic2/ui/mainwindow.py
CHANGED
|
@@ -22,9 +22,11 @@ from mosamatic2.ui.widgets.panels.tasks.dicom2niftitaskpanel import Dicom2NiftiT
|
|
|
22
22
|
from mosamatic2.ui.widgets.panels.tasks.createdicomsummarytaskpanel import CreateDicomSummaryTaskPanel
|
|
23
23
|
from mosamatic2.ui.widgets.panels.tasks.selectslicefromscanstaskpanel import SelectSliceFromScansTaskPanel
|
|
24
24
|
from mosamatic2.ui.widgets.panels.tasks.totalsegmentatortaskpanel import TotalSegmentatorTaskPanel
|
|
25
|
+
from mosamatic2.ui.widgets.panels.tasks.calculatemaskstatisticstaskpanel import CalculateMaskStatisticsTaskPanel
|
|
25
26
|
from mosamatic2.ui.widgets.panels.pipelines.defaultpipelinepanel import DefaultPipelinePanel
|
|
26
27
|
from mosamatic2.ui.widgets.panels.pipelines.defaultdockerpipelinepanel import DefaultDockerPipelinePanel
|
|
27
28
|
from mosamatic2.ui.widgets.panels.pipelines.boadockerpipelinepanel import BoaDockerPipelinePanel
|
|
29
|
+
from mosamatic2.ui.widgets.panels.pipelines.liveranalysispipelinepanel import LiverAnalysisPipelinePanel
|
|
28
30
|
from mosamatic2.ui.widgets.panels.visualizations.slicevisualization.slicevisualization import SliceVisualization
|
|
29
31
|
|
|
30
32
|
LOG = LogManager()
|
|
@@ -45,9 +47,11 @@ class MainWindow(QMainWindow):
|
|
|
45
47
|
self._create_dicom_summary_task_panel = None
|
|
46
48
|
self._select_slice_from_scans_task_panel = None
|
|
47
49
|
self._total_segmentator_task_panel = None
|
|
50
|
+
self._calculate_mask_statistics_task_panel = None
|
|
48
51
|
self._default_pipeline_panel = None
|
|
49
52
|
self._default_docker_pipeline_panel = None
|
|
50
53
|
self._boa_docker_pipeline_panel = None
|
|
54
|
+
self._liver_analysis_pipeline_panel = None
|
|
51
55
|
self._slice_visualization = None
|
|
52
56
|
self.init_window()
|
|
53
57
|
|
|
@@ -94,6 +98,8 @@ class MainWindow(QMainWindow):
|
|
|
94
98
|
select_slice_from_scans_task_action.triggered.connect(self.handle_select_slice_from_scans_task_action)
|
|
95
99
|
total_segmentator_task_action = QAction('TotalSegmentatorTask', self)
|
|
96
100
|
total_segmentator_task_action.triggered.connect(self.handle_total_segmentator_task_action)
|
|
101
|
+
calculate_mask_statistics_task_action = QAction('CalculateMaskStatisticsTask', self)
|
|
102
|
+
calculate_mask_statistics_task_action.triggered.connect(self.handle_calculate_mask_statistics_task_action)
|
|
97
103
|
tasks_menu = self.menuBar().addMenu('Tasks')
|
|
98
104
|
tasks_menu.addAction(rescale_dicom_images_task_action)
|
|
99
105
|
tasks_menu.addAction(segment_muscle_fat_l3_tensorflow_task_action)
|
|
@@ -103,6 +109,7 @@ class MainWindow(QMainWindow):
|
|
|
103
109
|
tasks_menu.addAction(create_dicom_summary_task_action)
|
|
104
110
|
tasks_menu.addAction(select_slice_from_scans_task_action)
|
|
105
111
|
tasks_menu.addAction(total_segmentator_task_action)
|
|
112
|
+
tasks_menu.addAction(calculate_mask_statistics_task_action)
|
|
106
113
|
|
|
107
114
|
def init_pipelines_menu(self):
|
|
108
115
|
default_pipeline_action = QAction('DefaultPipeline', self)
|
|
@@ -111,10 +118,13 @@ class MainWindow(QMainWindow):
|
|
|
111
118
|
default_docker_pipeline_action.triggered.connect(self.handle_default_docker_pipeline_action)
|
|
112
119
|
boa_docker_pipeline_action = QAction('BoaDockerPipeline', self)
|
|
113
120
|
boa_docker_pipeline_action.triggered.connect(self.handle_boa_docker_pipeline_action)
|
|
121
|
+
liver_analysis_pipeline_action = QAction('LiverAnalysisPipeline', self)
|
|
122
|
+
liver_analysis_pipeline_action.triggered.connect(self.handle_liver_analysis_pipeline_action)
|
|
114
123
|
pipelines_menu = self.menuBar().addMenu('Pipelines')
|
|
115
124
|
pipelines_menu.addAction(default_pipeline_action)
|
|
116
125
|
pipelines_menu.addAction(default_docker_pipeline_action)
|
|
117
126
|
pipelines_menu.addAction(boa_docker_pipeline_action)
|
|
127
|
+
pipelines_menu.addAction(liver_analysis_pipeline_action)
|
|
118
128
|
|
|
119
129
|
def init_visualizations_menu(self):
|
|
120
130
|
slice_visualization_action = QAction('SliceVisualization', self)
|
|
@@ -143,9 +153,11 @@ class MainWindow(QMainWindow):
|
|
|
143
153
|
self._main_panel.add_panel(self.create_dicom_summary_task_panel(), 'createdicomsummarytaskpanel')
|
|
144
154
|
self._main_panel.add_panel(self.select_slice_from_scans_task_panel(), 'selectslicefromscanstaskpanel')
|
|
145
155
|
self._main_panel.add_panel(self.total_segmentator_task_panel(), 'totalsegmentatortaskpanel')
|
|
156
|
+
self._main_panel.add_panel(self.calculate_mask_statistics_task_panel(), 'calculatemaskstatisticstaskpanel')
|
|
146
157
|
self._main_panel.add_panel(self.default_pipeline_panel(), 'defaultpipelinepanel')
|
|
147
158
|
self._main_panel.add_panel(self.default_docker_pipeline_panel(), 'defaultdockerpipelinepanel')
|
|
148
159
|
self._main_panel.add_panel(self.boa_docker_pipeline_panel(), 'boadockerpipelinepanel')
|
|
160
|
+
self._main_panel.add_panel(self.liver_analysis_pipeline_panel(), 'liveranalysispipelinepanel')
|
|
149
161
|
self._main_panel.add_panel(self.slice_visualization(), 'slicevisualization')
|
|
150
162
|
self._main_panel.select_panel('defaultpipelinepanel')
|
|
151
163
|
return self._main_panel
|
|
@@ -198,6 +210,11 @@ class MainWindow(QMainWindow):
|
|
|
198
210
|
self._total_segmentator_task_panel = TotalSegmentatorTaskPanel()
|
|
199
211
|
return self._total_segmentator_task_panel
|
|
200
212
|
|
|
213
|
+
def calculate_mask_statistics_task_panel(self):
|
|
214
|
+
if not self._calculate_mask_statistics_task_panel:
|
|
215
|
+
self._calculate_mask_statistics_task_panel = CalculateMaskStatisticsTaskPanel()
|
|
216
|
+
return self._calculate_mask_statistics_task_panel
|
|
217
|
+
|
|
201
218
|
def default_pipeline_panel(self):
|
|
202
219
|
if not self._default_pipeline_panel:
|
|
203
220
|
self._default_pipeline_panel = DefaultPipelinePanel()
|
|
@@ -213,6 +230,11 @@ class MainWindow(QMainWindow):
|
|
|
213
230
|
self._boa_docker_pipeline_panel = BoaDockerPipelinePanel()
|
|
214
231
|
return self._boa_docker_pipeline_panel
|
|
215
232
|
|
|
233
|
+
def liver_analysis_pipeline_panel(self):
|
|
234
|
+
if not self._liver_analysis_pipeline_panel:
|
|
235
|
+
self._liver_analysis_pipeline_panel = LiverAnalysisPipelinePanel()
|
|
236
|
+
return self._liver_analysis_pipeline_panel
|
|
237
|
+
|
|
216
238
|
def slice_visualization(self):
|
|
217
239
|
if not self._slice_visualization:
|
|
218
240
|
self._slice_visualization = SliceVisualization()
|
|
@@ -249,6 +271,9 @@ class MainWindow(QMainWindow):
|
|
|
249
271
|
def handle_total_segmentator_task_action(self):
|
|
250
272
|
self.main_panel().select_panel('totalsegmentatortaskpanel')
|
|
251
273
|
|
|
274
|
+
def handle_calculate_mask_statistics_task_action(self):
|
|
275
|
+
self.main_panel().select_panel('calculatemaskstatisticstaskpanel')
|
|
276
|
+
|
|
252
277
|
def handle_default_pipeline_action(self):
|
|
253
278
|
self.main_panel().select_panel('defaultpipelinepanel')
|
|
254
279
|
|
|
@@ -258,6 +283,9 @@ class MainWindow(QMainWindow):
|
|
|
258
283
|
def handle_boa_docker_pipeline_action(self):
|
|
259
284
|
self.main_panel().select_panel('boadockerpipelinepanel')
|
|
260
285
|
|
|
286
|
+
def handle_liver_analysis_pipeline_action(self):
|
|
287
|
+
self.main_panel().select_panel('liveranalysispipelinepanel')
|
|
288
|
+
|
|
261
289
|
def handle_slice_visualization_action(self):
|
|
262
290
|
self.main_panel().select_panel('slicevisualization')
|
|
263
291
|
|
|
@@ -275,9 +303,11 @@ class MainWindow(QMainWindow):
|
|
|
275
303
|
self.create_dicom_summary_task_panel().save_inputs_and_parameters()
|
|
276
304
|
self.select_slice_from_scans_task_panel().save_inputs_and_parameters()
|
|
277
305
|
self.total_segmentator_task_panel().save_inputs_and_parameters()
|
|
306
|
+
self.calculate_mask_statistics_task_panel().save_inputs_and_parameters()
|
|
278
307
|
self.default_pipeline_panel().save_inputs_and_parameters()
|
|
279
308
|
self.default_docker_pipeline_panel().save_inputs_and_parameters()
|
|
280
309
|
self.boa_docker_pipeline_panel().save_inputs_and_parameters()
|
|
310
|
+
self.liver_analysis_pipeline_panel().save_inputs_and_parameters()
|
|
281
311
|
self.slice_visualization().save_inputs_and_parameters()
|
|
282
312
|
return super().closeEvent(event)
|
|
283
313
|
|
mosamatic2/ui/resources/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.0.
|
|
1
|
+
2.0.17
|
|
@@ -27,7 +27,7 @@ from mosamatic2.core.pipelines import DefaultPipeline
|
|
|
27
27
|
LOG = LogManager()
|
|
28
28
|
|
|
29
29
|
PANEL_TITLE = 'DefaultPipeline'
|
|
30
|
-
PANEL_NAME = '
|
|
30
|
+
PANEL_NAME = 'defaultpipelinepanel'
|
|
31
31
|
MODEL_TYPE_ITEM_NAMES = ['tensorflow', 'pytorch']
|
|
32
32
|
MODEL_VERSION_ITEM_NAMES = ['1.0', '2.2']
|
|
33
33
|
|