mosamatic2 2.0.3__tar.gz → 2.0.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mosamatic2 might be problematic. Click here for more details.
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/PKG-INFO +2 -1
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/pyproject.toml +2 -1
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/cli.py +4 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/commands/calculatescores.py +1 -1
- mosamatic2-2.0.5/src/mosamatic2/commands/dicom2nifti.py +46 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/commands/rescaledicomimages.py +1 -1
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/commands/segmentmusclefatl3tensorflow.py +1 -1
- mosamatic2-2.0.5/src/mosamatic2/commands/selectslicefromscans.py +65 -0
- mosamatic2-2.0.5/src/mosamatic2/core/data/multinumpyimage.py +26 -0
- mosamatic2-2.0.5/src/mosamatic2/core/data/numpyimage.py +13 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/tasks/__init__.py +3 -1
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/tasks/calculatescorestask/calculatescorestask.py +6 -6
- mosamatic2-2.0.5/src/mosamatic2/core/tasks/dicom2niftitask/dicom2niftitask.py +24 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/segmentmusclefatl3tensorflowtask.py +1 -3
- mosamatic2-2.0.5/src/mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py +109 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/tasks/task.py +5 -4
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/utils.py +10 -2
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/server.py +32 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/mainwindow.py +30 -0
- mosamatic2-2.0.5/src/mosamatic2/ui/resources/VERSION +1 -0
- mosamatic2-2.0.5/src/mosamatic2/ui/widgets/panels/pipelines/__init__.py +0 -0
- mosamatic2-2.0.5/src/mosamatic2/ui/widgets/panels/tasks/__init__.py +0 -0
- mosamatic2-2.0.5/src/mosamatic2/ui/widgets/panels/tasks/dicom2niftitaskpanel.py +183 -0
- mosamatic2-2.0.3/src/mosamatic2/ui/widgets/panels/tasks/selectslicefromscantaskpanel.py → mosamatic2-2.0.5/src/mosamatic2/ui/widgets/panels/tasks/selectslicefromscanstaskpanel.py +26 -17
- mosamatic2-2.0.3/src/mosamatic2/ui/resources/VERSION +0 -1
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/README.md +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/models.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/__init__.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/app.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/commands/__init__.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/commands/createpngsfromsegmentations.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/constants.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/__init__.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/data/__init__.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/data/dicomimage.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/data/dicomimageseries.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/data/dixonseries.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/data/filedata.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/data/multidicomimage.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/managers/__init__.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/managers/logmanager.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/managers/logmanagerlistener.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/pipelines/__init__.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/pipelines/defaultpipeline.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/pipelines/pipeline.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/singleton.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/tasks/calculatescorestask/__init__.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/tasks/createpngsfromsegmentationstask/__init__.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/tasks/createpngsfromsegmentationstask/createpngsfromsegmentationstask.py +0 -0
- {mosamatic2-2.0.3/src/mosamatic2/core/tasks/rescaledicomimagestask → mosamatic2-2.0.5/src/mosamatic2/core/tasks/dicom2niftitask}/__init__.py +0 -0
- {mosamatic2-2.0.3/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask → mosamatic2-2.0.5/src/mosamatic2/core/tasks/rescaledicomimagestask}/__init__.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/tasks/rescaledicomimagestask/rescaledicomimagestask.py +0 -0
- {mosamatic2-2.0.3/src/mosamatic2/ui → mosamatic2-2.0.5/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask}/__init__.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/tasks/segmentmusclefatl3tensorflowtask/paramloader.py +0 -0
- {mosamatic2-2.0.3/src/mosamatic2/ui/widgets → mosamatic2-2.0.5/src/mosamatic2/core/tasks/selectslicefromscanstask}/__init__.py +0 -0
- {mosamatic2-2.0.3/src/mosamatic2/ui/widgets/dialogs → mosamatic2-2.0.5/src/mosamatic2/ui}/__init__.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/resources/icons/mosamatic2.icns +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/resources/icons/mosamatic2.ico +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/resources/icons/spinner.gif +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/resources/images/body-composition.jpg +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/settings.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/utils.py +0 -0
- {mosamatic2-2.0.3/src/mosamatic2/ui/widgets/panels → mosamatic2-2.0.5/src/mosamatic2/ui/widgets}/__init__.py +0 -0
- {mosamatic2-2.0.3/src/mosamatic2/ui/widgets/panels/pipelines → mosamatic2-2.0.5/src/mosamatic2/ui/widgets/dialogs}/__init__.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/widgets/dialogs/dialog.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/widgets/dialogs/helpdialog.py +0 -0
- {mosamatic2-2.0.3/src/mosamatic2/ui/widgets/panels/tasks → mosamatic2-2.0.5/src/mosamatic2/ui/widgets/panels}/__init__.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/widgets/panels/defaultpanel.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/widgets/panels/logpanel.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/widgets/panels/mainpanel.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/widgets/panels/pipelines/defaultpipelinepanel.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/widgets/panels/stackedpanel.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/widgets/panels/taskpanel.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/widgets/panels/tasks/calculatescorestaskpanel.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/widgets/panels/tasks/createpngsfromsegmentationstaskpanel.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/widgets/panels/tasks/rescaledicomimagestaskpanel.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/widgets/panels/tasks/segmentmusclefatl3tensorflowtaskpanel.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/widgets/splashscreen.py +0 -0
- {mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/worker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: mosamatic2
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.5
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Ralph Brecheisen
|
|
6
6
|
Author-email: r.brecheisen@maastrichtuniversity.nl
|
|
@@ -8,6 +8,7 @@ Requires-Python: >=3.11,<3.12
|
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
10
10
|
Requires-Dist: antspyx (>=0.5.4)
|
|
11
|
+
Requires-Dist: dicom2nifti (>=2.6.2)
|
|
11
12
|
Requires-Dist: flask (>=3.1.2)
|
|
12
13
|
Requires-Dist: nibabel (>=5.3.2)
|
|
13
14
|
Requires-Dist: numpy (>=1.26.4)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mosamatic2"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.5"
|
|
4
4
|
description = ""
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Ralph Brecheisen", email = "r.brecheisen@maastrichtuniversity.nl"}
|
|
@@ -10,6 +10,7 @@ requires-python = ">=3.11,<3.12"
|
|
|
10
10
|
dependencies = [
|
|
11
11
|
"pyside6-essentials>=6.9",
|
|
12
12
|
"pydicom>=3.0.1",
|
|
13
|
+
"dicom2nifti>=2.6.2",
|
|
13
14
|
"numpy>=1.26.4",
|
|
14
15
|
"pandas>=2.3.2",
|
|
15
16
|
"nibabel>=5.3.2",
|
|
@@ -4,6 +4,8 @@ from mosamatic2.commands import (
|
|
|
4
4
|
rescaledicomimages,
|
|
5
5
|
segmentmusclefatl3tensorflow,
|
|
6
6
|
createpngsfromsegmentations,
|
|
7
|
+
dicom2nifti,
|
|
8
|
+
selectslicefromscans,
|
|
7
9
|
)
|
|
8
10
|
from mosamatic2.core.utils import show_doc_command
|
|
9
11
|
|
|
@@ -29,4 +31,6 @@ main.add_command(calculatescores.calculatescores)
|
|
|
29
31
|
main.add_command(rescaledicomimages.rescaledicomimages)
|
|
30
32
|
main.add_command(segmentmusclefatl3tensorflow.segmentmusclefatl3tensorflow)
|
|
31
33
|
main.add_command(createpngsfromsegmentations.createpngsfromsegmentations)
|
|
34
|
+
main.add_command(dicom2nifti.dicom2nifti)
|
|
35
|
+
main.add_command(selectslicefromscans.selectslicefromscans)
|
|
32
36
|
main.add_command(show_doc_command(main)) # Special command to show long description for command
|
|
@@ -48,7 +48,7 @@ def calculatescores(images, segmentations, output, file_type, overwrite):
|
|
|
48
48
|
Parameters
|
|
49
49
|
----------
|
|
50
50
|
--images : str
|
|
51
|
-
Directory with
|
|
51
|
+
Directory with input L3 images
|
|
52
52
|
|
|
53
53
|
--segmentations : str
|
|
54
54
|
Directory with L3 muscle and fat segmenation files. Must be output of
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from mosamatic2.core.tasks import Dicom2NiftiTask
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command(help='Converts DICOM series to NIFTI')
|
|
7
|
+
@click.option(
|
|
8
|
+
'--images',
|
|
9
|
+
required=True,
|
|
10
|
+
type=click.Path(exists=True),
|
|
11
|
+
help='Directory with images',
|
|
12
|
+
)
|
|
13
|
+
@click.option(
|
|
14
|
+
'--output',
|
|
15
|
+
required=True,
|
|
16
|
+
type=click.Path(),
|
|
17
|
+
help='Output directory'
|
|
18
|
+
)
|
|
19
|
+
@click.option(
|
|
20
|
+
'--overwrite',
|
|
21
|
+
type=click.BOOL,
|
|
22
|
+
default=False,
|
|
23
|
+
help='Overwrite [true|false]'
|
|
24
|
+
)
|
|
25
|
+
def dicom2nifti(images, output, overwrite):
|
|
26
|
+
"""
|
|
27
|
+
Converts single DICOM series (scan) to NIFTI
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
--images : str
|
|
32
|
+
Directory with DICOM images of a single series
|
|
33
|
+
|
|
34
|
+
--output : str
|
|
35
|
+
Path to output directory
|
|
36
|
+
|
|
37
|
+
--overwrite : bool
|
|
38
|
+
Overwrite contents output directory [true|false]
|
|
39
|
+
"""
|
|
40
|
+
task = Dicom2NiftiTask(
|
|
41
|
+
inputs={'images': images},
|
|
42
|
+
params=None,
|
|
43
|
+
output=output,
|
|
44
|
+
overwrite=overwrite,
|
|
45
|
+
)
|
|
46
|
+
task.run()
|
{mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/commands/segmentmusclefatl3tensorflow.py
RENAMED
|
@@ -35,7 +35,7 @@ def segmentmusclefatl3tensorflow(images, model_files, output, overwrite):
|
|
|
35
35
|
Parameters
|
|
36
36
|
----------
|
|
37
37
|
--images : str
|
|
38
|
-
Directory with
|
|
38
|
+
Directory with input L3 images
|
|
39
39
|
|
|
40
40
|
--model_files : str
|
|
41
41
|
Directory with AI model files (model-1.0.zip, contour_model-1.0.zip, params-1.0.json)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from mosamatic2.core.tasks import SelectSliceFromScansTask
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command(help='Selects specific slice from 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
|
+
'--vertebra',
|
|
21
|
+
required=True,
|
|
22
|
+
help='Vertebral level for selecting slice (default: "L3")'
|
|
23
|
+
)
|
|
24
|
+
@click.option(
|
|
25
|
+
'--overwrite',
|
|
26
|
+
type=click.BOOL,
|
|
27
|
+
default=False,
|
|
28
|
+
help='Overwrite [true|false]'
|
|
29
|
+
)
|
|
30
|
+
def selectslicefromscans(scans, vertebra, output, overwrite):
|
|
31
|
+
"""
|
|
32
|
+
Selects specific slice from list of CT scans
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
--scans : str
|
|
37
|
+
Directory to scans. Each patient's scan should be in a separate
|
|
38
|
+
subdirectory. For example:
|
|
39
|
+
|
|
40
|
+
/scans
|
|
41
|
+
/scans/patient1
|
|
42
|
+
/scans/patient1/file1.dcm
|
|
43
|
+
/scans/patient1/file2.dcm
|
|
44
|
+
...
|
|
45
|
+
/scans/patient2
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
--output : str
|
|
49
|
+
Path to output directory where selected slices will be placed. Each
|
|
50
|
+
slice's file name will be the same as the scan directory name, so in
|
|
51
|
+
the example above that would be "patient1", "patient2", etc.
|
|
52
|
+
|
|
53
|
+
--vertebra : str
|
|
54
|
+
Vertebral level where to take slice [L3|T4]
|
|
55
|
+
|
|
56
|
+
--overwrite : bool
|
|
57
|
+
Overwrite contents output directory [true|false]
|
|
58
|
+
"""
|
|
59
|
+
task = SelectSliceFromScansTask(
|
|
60
|
+
inputs={'scans': scans},
|
|
61
|
+
params={'vertebra': vertebra},
|
|
62
|
+
output=output,
|
|
63
|
+
overwrite=overwrite,
|
|
64
|
+
)
|
|
65
|
+
task.run()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
3
|
+
from mosamatic2.core.data.filedata import FileData
|
|
4
|
+
from mosamatic2.core.data.numpyimage import NumPyImage
|
|
5
|
+
|
|
6
|
+
LOG = LogManager()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MultiNumPyImage(FileData):
|
|
10
|
+
def __init__(self):
|
|
11
|
+
super(MultiNumPyImage, self).__init__()
|
|
12
|
+
self._images = []
|
|
13
|
+
|
|
14
|
+
def images(self):
|
|
15
|
+
return self._images
|
|
16
|
+
|
|
17
|
+
def load(self):
|
|
18
|
+
if self.path():
|
|
19
|
+
for f in os.listdir(self.path()):
|
|
20
|
+
f_path = os.path.join(self.path(), f)
|
|
21
|
+
image = NumPyImage()
|
|
22
|
+
image.set_path(f_path)
|
|
23
|
+
if image.load():
|
|
24
|
+
self._images.append(image)
|
|
25
|
+
return True
|
|
26
|
+
return False
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from mosamatic2.core.data.filedata import FileData
|
|
2
|
+
from mosamatic2.core.utils import (
|
|
3
|
+
is_numpy,
|
|
4
|
+
load_numpy_array,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
class NumPyImage(FileData):
|
|
8
|
+
def load(self):
|
|
9
|
+
if self.path():
|
|
10
|
+
if is_numpy(self.path()):
|
|
11
|
+
self.set_object(load_numpy_array(self.path()))
|
|
12
|
+
return True
|
|
13
|
+
return False
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from mosamatic2.core.tasks.rescaledicomimagestask.rescaledicomimagestask import RescaleDicomImagesTask
|
|
2
2
|
from mosamatic2.core.tasks.segmentmusclefatl3tensorflowtask.segmentmusclefatl3tensorflowtask import SegmentMuscleFatL3TensorFlowTask
|
|
3
3
|
from mosamatic2.core.tasks.calculatescorestask.calculatescorestask import CalculateScoresTask
|
|
4
|
-
from mosamatic2.core.tasks.createpngsfromsegmentationstask.createpngsfromsegmentationstask import CreatePngsFromSegmentationsTask
|
|
4
|
+
from mosamatic2.core.tasks.createpngsfromsegmentationstask.createpngsfromsegmentationstask import CreatePngsFromSegmentationsTask
|
|
5
|
+
from mosamatic2.core.tasks.dicom2niftitask.dicom2niftitask import Dicom2NiftiTask
|
|
6
|
+
from mosamatic2.core.tasks.selectslicefromscanstask.selectslicefromscanstask import SelectSliceFromScansTask
|
|
@@ -4,10 +4,8 @@ import pandas as pd
|
|
|
4
4
|
from mosamatic2.core.tasks.task import Task
|
|
5
5
|
from mosamatic2.core.managers.logmanager import LogManager
|
|
6
6
|
from mosamatic2.core.data.multidicomimage import MultiDicomImage
|
|
7
|
+
from mosamatic2.core.data.numpyimage import NumPyImage
|
|
7
8
|
from mosamatic2.core.utils import (
|
|
8
|
-
is_dicom,
|
|
9
|
-
load_dicom,
|
|
10
|
-
is_jpeg2000_compressed,
|
|
11
9
|
get_pixels_from_dicom_object,
|
|
12
10
|
calculate_area,
|
|
13
11
|
calculate_mean_radiation_attenuation,
|
|
@@ -41,13 +39,11 @@ class CalculateScoresTask(Task):
|
|
|
41
39
|
if file_type == '.seg.npy':
|
|
42
40
|
f_seg_name = f_seg_name.removesuffix(file_type)
|
|
43
41
|
if f_seg_name == f_img_name:
|
|
44
|
-
# img_seg_pairs.append((f_img_path, f_seg_path))
|
|
45
42
|
img_seg_pairs.append((image, f_seg_path))
|
|
46
43
|
elif file_type == '.tag':
|
|
47
44
|
f_seg_name = f_seg_name.removesuffix(file_type).removesuffix('.dcm')
|
|
48
45
|
f_img_name = f_img_name.removesuffix('.dcm')
|
|
49
46
|
if f_seg_name == f_img_name:
|
|
50
|
-
# img_seg_pairs.append((f_img_path, f_seg_path))
|
|
51
47
|
img_seg_pairs.append((image, f_seg_path))
|
|
52
48
|
else:
|
|
53
49
|
raise RuntimeError('Unknown file type')
|
|
@@ -78,7 +74,11 @@ class CalculateScoresTask(Task):
|
|
|
78
74
|
|
|
79
75
|
def load_segmentation(self, f, file_type='npy'):
|
|
80
76
|
if file_type == 'npy':
|
|
81
|
-
|
|
77
|
+
segmentation = NumPyImage()
|
|
78
|
+
segmentation.set_path(f)
|
|
79
|
+
if segmentation.load():
|
|
80
|
+
return segmentation.object()
|
|
81
|
+
LOG.error(f'Could not load segmentation file {f}')
|
|
82
82
|
if file_type == 'tag':
|
|
83
83
|
pixels = get_pixels_from_tag_file(f)
|
|
84
84
|
try:
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import dicom2nifti
|
|
3
|
+
from mosamatic2.core.tasks.task import Task
|
|
4
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
5
|
+
|
|
6
|
+
LOG = LogManager()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Dicom2NiftiTask(Task):
|
|
10
|
+
INPUTS = ['images']
|
|
11
|
+
PARAMS = []
|
|
12
|
+
|
|
13
|
+
def __init__(self, inputs, params, output, overwrite):
|
|
14
|
+
super(Dicom2NiftiTask, self).__init__(inputs, params, output, overwrite)
|
|
15
|
+
|
|
16
|
+
def run(self):
|
|
17
|
+
nifti_file_name = os.path.split(self.input('images'))[1] + '.nii.gz'
|
|
18
|
+
LOG.info(f'Converting DICOM directory to {nifti_file_name}')
|
|
19
|
+
dicom2nifti.dicom_series_to_nifti(
|
|
20
|
+
self.input('images'),
|
|
21
|
+
os.path.join(self.output(), nifti_file_name),
|
|
22
|
+
reorient_nifti=True,
|
|
23
|
+
)
|
|
24
|
+
self.set_progress(0, 1)
|
|
@@ -28,7 +28,7 @@ class SegmentMuscleFatL3TensorFlowTask(Task):
|
|
|
28
28
|
def __init__(self, inputs, params, output, overwrite=True):
|
|
29
29
|
super(SegmentMuscleFatL3TensorFlowTask, self).__init__(inputs, params, output, overwrite)
|
|
30
30
|
|
|
31
|
-
def load_images(self):
|
|
31
|
+
def load_images(self):
|
|
32
32
|
image_data = MultiDicomImage()
|
|
33
33
|
image_data.set_path(self.input('images'))
|
|
34
34
|
if image_data.load():
|
|
@@ -55,7 +55,6 @@ class SegmentMuscleFatL3TensorFlowTask(Task):
|
|
|
55
55
|
import tensorflow as tf
|
|
56
56
|
tfLoaded = True
|
|
57
57
|
with tempfile.TemporaryDirectory() as model_dir_unzipped:
|
|
58
|
-
# model_dir_unzipped = os.path.join(os.path.split(f_path)[0], 'model_unzipped')
|
|
59
58
|
os.makedirs(model_dir_unzipped, exist_ok=True)
|
|
60
59
|
with zipfile.ZipFile(f_path) as zipObj:
|
|
61
60
|
zipObj.extractall(path=model_dir_unzipped)
|
|
@@ -65,7 +64,6 @@ class SegmentMuscleFatL3TensorFlowTask(Task):
|
|
|
65
64
|
import tensorflow as tf
|
|
66
65
|
tfLoaded = True
|
|
67
66
|
with tempfile.TemporaryDirectory() as contour_model_dir_unzipped:
|
|
68
|
-
# contour_model_dir_unzipped = os.path.join(os.path.split(f_path)[0], 'contour_model_unzipped')
|
|
69
67
|
os.makedirs(contour_model_dir_unzipped, exist_ok=True)
|
|
70
68
|
with zipfile.ZipFile(f_path) as zipObj:
|
|
71
69
|
zipObj.extractall(path=contour_model_dir_unzipped)
|
mosamatic2-2.0.5/src/mosamatic2/core/tasks/selectslicefromscanstask/selectslicefromscanstask.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import math
|
|
3
|
+
import tempfile
|
|
4
|
+
import shutil
|
|
5
|
+
import nibabel as nib
|
|
6
|
+
import numpy as np
|
|
7
|
+
from totalsegmentator.python_api import totalsegmentator
|
|
8
|
+
from mosamatic2.core.tasks.task import Task
|
|
9
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
10
|
+
from mosamatic2.core.utils import load_dicom
|
|
11
|
+
|
|
12
|
+
LOG = LogManager()
|
|
13
|
+
|
|
14
|
+
TOTAL_SEGMENTATOR_OUTPUT_DIR = os.path.join(tempfile.gettempdir(), 'total_segmentator_output')
|
|
15
|
+
TOTAL_SEGMENTATOR_TASK = 'total'
|
|
16
|
+
Z_DELTA_OFFSETS = {
|
|
17
|
+
'vertebrae_L3': 0.333,
|
|
18
|
+
'vertebrae_T4': 0.5,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SelectSliceFromScansTask(Task):
|
|
23
|
+
INPUTS = ['scans']
|
|
24
|
+
PARAMS = ['vertebra']
|
|
25
|
+
|
|
26
|
+
def __init__(self, inputs, params, output, overwrite):
|
|
27
|
+
super(SelectSliceFromScansTask, self).__init__(inputs, params, output, overwrite)
|
|
28
|
+
|
|
29
|
+
def load_scan_dirs(self):
|
|
30
|
+
scan_dirs = []
|
|
31
|
+
for d in os.listdir(self.input('scans')):
|
|
32
|
+
scan_dir = os.path.join(self.input('scans'), d)
|
|
33
|
+
if os.path.isdir(scan_dir):
|
|
34
|
+
scan_dirs.append(scan_dir)
|
|
35
|
+
return scan_dirs
|
|
36
|
+
|
|
37
|
+
def extract_masks(self, scan_dir):
|
|
38
|
+
os.makedirs(TOTAL_SEGMENTATOR_OUTPUT_DIR, exist_ok=True)
|
|
39
|
+
totalsegmentator(input=scan_dir, output=TOTAL_SEGMENTATOR_OUTPUT_DIR, fast=True)
|
|
40
|
+
# os.system(f'TotalSegmentator -i {scan_dir} -o {TOTAL_SEGMENTATOR_OUTPUT_DIR} --fast')
|
|
41
|
+
|
|
42
|
+
def delete_total_segmentator_output(self):
|
|
43
|
+
if os.path.exists(TOTAL_SEGMENTATOR_OUTPUT_DIR):
|
|
44
|
+
shutil.rmtree(TOTAL_SEGMENTATOR_OUTPUT_DIR)
|
|
45
|
+
|
|
46
|
+
def get_z_delta_offset_for_mask(self, mask_name):
|
|
47
|
+
if mask_name not in Z_DELTA_OFFSETS.keys():
|
|
48
|
+
return None
|
|
49
|
+
return Z_DELTA_OFFSETS[mask_name]
|
|
50
|
+
|
|
51
|
+
def find_slice(self, scan_dir, vertebra):
|
|
52
|
+
if vertebra == 'L3':
|
|
53
|
+
vertebral_level = 'vertebrae_L3'
|
|
54
|
+
elif vertebra == 'T4':
|
|
55
|
+
vertebral_level = 'vertebrae_T4'
|
|
56
|
+
else:
|
|
57
|
+
raise RuntimeError(f'Unknown vertbra {vertebra}. Options are "L3" and "T4"')
|
|
58
|
+
# Find Z-positions DICOM images
|
|
59
|
+
z_positions = {}
|
|
60
|
+
for f in os.listdir(scan_dir):
|
|
61
|
+
f_path = os.path.join(scan_dir, f)
|
|
62
|
+
p = load_dicom(f_path, stop_before_pixels=True)
|
|
63
|
+
if p is not None:
|
|
64
|
+
z_positions[p.ImagePositionPatient[2]] = f_path
|
|
65
|
+
# Find Z-position L3 image
|
|
66
|
+
mask_file = os.path.join(TOTAL_SEGMENTATOR_OUTPUT_DIR, f'{vertebral_level}.nii.gz')
|
|
67
|
+
mask_obj = nib.load(mask_file)
|
|
68
|
+
mask = mask_obj.get_fdata()
|
|
69
|
+
affine_transform = mask_obj.affine
|
|
70
|
+
indexes = np.array(np.where(mask == 1))
|
|
71
|
+
index_min = indexes.min(axis=1)
|
|
72
|
+
index_max = indexes.max(axis=1)
|
|
73
|
+
world_min = nib.affines.apply_affine(affine_transform, index_min)
|
|
74
|
+
world_max = nib.affines.apply_affine(affine_transform, index_max)
|
|
75
|
+
z_direction = affine_transform[:3, 2][2]
|
|
76
|
+
z_sign = math.copysign(1, z_direction)
|
|
77
|
+
z_delta_offset = self.get_z_delta_offset_for_mask(vertebral_level)
|
|
78
|
+
if z_delta_offset is None:
|
|
79
|
+
return None
|
|
80
|
+
z_delta = 0.333 * abs(world_max[2] - world_min[2]) # This needs to be vertebra-specific perhaps
|
|
81
|
+
z_l3 = world_max[2] - z_sign * z_delta
|
|
82
|
+
# Find closest L3 image in DICOM set
|
|
83
|
+
positions = sorted(z_positions.keys())
|
|
84
|
+
closest_file = None
|
|
85
|
+
for z1, z2 in zip(positions[:-1], positions[1:]):
|
|
86
|
+
if min(z1, z2) <= z_l3 <= max(z1, z2):
|
|
87
|
+
closest_z = min(z_positions.keys(), key=lambda z: abs(z - z_l3))
|
|
88
|
+
closest_file = z_positions[closest_z]
|
|
89
|
+
LOG.info(f'Closest image: {closest_file}')
|
|
90
|
+
break
|
|
91
|
+
return closest_file
|
|
92
|
+
|
|
93
|
+
def run(self):
|
|
94
|
+
scan_dirs = self.load_scan_dirs()
|
|
95
|
+
vertebra = self.param('vertebra')
|
|
96
|
+
nr_steps = len(scan_dirs)
|
|
97
|
+
for step in range(nr_steps):
|
|
98
|
+
scan_dir = scan_dirs[step]
|
|
99
|
+
scan_name = os.path.split(scan_dir)[1]
|
|
100
|
+
self.extract_masks(scan_dir)
|
|
101
|
+
file_path = self.find_slice(scan_dir, vertebra)
|
|
102
|
+
if file_path is not None:
|
|
103
|
+
extension = '' if file_path.endswith('.dcm') else '.dcm'
|
|
104
|
+
target_file_path = os.path.join(self.output(), vertebra + '_' + scan_name + extension)
|
|
105
|
+
shutil.copyfile(file_path, target_file_path)
|
|
106
|
+
else:
|
|
107
|
+
LOG.error(f'Could not find slice for vertebral level: {vertebra}')
|
|
108
|
+
self.delete_total_segmentator_output()
|
|
109
|
+
self.set_progress(step, nr_steps)
|
|
@@ -25,10 +25,11 @@ class Task:
|
|
|
25
25
|
for k, v in self._inputs.items():
|
|
26
26
|
assert k in self.__class__.INPUTS
|
|
27
27
|
assert isinstance(v, str)
|
|
28
|
-
# Check that param names match specification
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
# Check that param names match specification (if not None)
|
|
29
|
+
if self._params:
|
|
30
|
+
assert len(self._params.keys()) == len(self.__class__.PARAMS)
|
|
31
|
+
for k in self._params.keys():
|
|
32
|
+
assert k in self.__class__.PARAMS
|
|
32
33
|
|
|
33
34
|
def input(self, name):
|
|
34
35
|
return self._inputs[name]
|
|
@@ -119,10 +119,18 @@ def is_numpy_array(value):
|
|
|
119
119
|
return isinstance(value, np.array)
|
|
120
120
|
|
|
121
121
|
|
|
122
|
-
def
|
|
122
|
+
def is_numpy(f):
|
|
123
123
|
try:
|
|
124
|
+
np.load(f)
|
|
125
|
+
return True
|
|
126
|
+
except:
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def load_numpy_array(f):
|
|
131
|
+
if is_numpy(f):
|
|
124
132
|
return np.load(f)
|
|
125
|
-
|
|
133
|
+
else:
|
|
126
134
|
return None
|
|
127
135
|
|
|
128
136
|
|
|
@@ -5,6 +5,8 @@ from mosamatic2.core.tasks import RescaleDicomImagesTask
|
|
|
5
5
|
from mosamatic2.core.tasks import SegmentMuscleFatL3TensorFlowTask
|
|
6
6
|
from mosamatic2.core.tasks import CalculateScoresTask
|
|
7
7
|
from mosamatic2.core.tasks import CreatePngsFromSegmentationsTask
|
|
8
|
+
from mosamatic2.core.tasks import Dicom2NiftiTask
|
|
9
|
+
from mosamatic2.core.tasks import SelectSliceFromScansTask
|
|
8
10
|
|
|
9
11
|
app = Flask(__name__)
|
|
10
12
|
|
|
@@ -89,6 +91,36 @@ def run_createpngsfromsegmentations():
|
|
|
89
91
|
return 'PASSED'
|
|
90
92
|
|
|
91
93
|
|
|
94
|
+
@app.route('/dicom2nifti')
|
|
95
|
+
def run_dicom2nifti():
|
|
96
|
+
images = request.args.get('images')
|
|
97
|
+
output = request.args.get('output')
|
|
98
|
+
overwrite = request.args.get('overwrite', default=True, type=bool)
|
|
99
|
+
task = Dicom2NiftiTask(
|
|
100
|
+
inputs={'images': images},
|
|
101
|
+
params=None,
|
|
102
|
+
output=output,
|
|
103
|
+
overwrite=overwrite,
|
|
104
|
+
)
|
|
105
|
+
task.run()
|
|
106
|
+
return 'PASSED'
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@app.route('/selectslicefromscans')
|
|
110
|
+
def run_selectslicefromscans():
|
|
111
|
+
scans = request.args.get('scans')
|
|
112
|
+
vertebra = request.args.get('vertebra')
|
|
113
|
+
output = request.args.get('output')
|
|
114
|
+
overwrite = request.args.get('overwrite', default=True, type=bool)
|
|
115
|
+
task = SelectSliceFromScansTask(
|
|
116
|
+
inputs={'scans': scans},
|
|
117
|
+
params={'vertebra': vertebra},
|
|
118
|
+
output=output,
|
|
119
|
+
overwrite=overwrite,
|
|
120
|
+
)
|
|
121
|
+
task.run()
|
|
122
|
+
return 'PASSED'
|
|
123
|
+
|
|
92
124
|
|
|
93
125
|
def main():
|
|
94
126
|
parser = argparse.ArgumentParser()
|
|
@@ -18,6 +18,8 @@ from mosamatic2.ui.widgets.panels.tasks.rescaledicomimagestaskpanel import Resca
|
|
|
18
18
|
from mosamatic2.ui.widgets.panels.tasks.segmentmusclefatl3tensorflowtaskpanel import SegmentMuscleFatL3TensorFlowTaskPanel
|
|
19
19
|
from mosamatic2.ui.widgets.panels.tasks.createpngsfromsegmentationstaskpanel import CreatePngsFromSegmentationsTaskPanel
|
|
20
20
|
from mosamatic2.ui.widgets.panels.tasks.calculatescorestaskpanel import CalculateScoresTaskPanel
|
|
21
|
+
from mosamatic2.ui.widgets.panels.tasks.dicom2niftitaskpanel import Dicom2NiftiTaskPanel
|
|
22
|
+
from mosamatic2.ui.widgets.panels.tasks.selectslicefromscanstaskpanel import SelectSliceFromScansTaskPanel
|
|
21
23
|
from mosamatic2.ui.widgets.panels.pipelines.defaultpipelinepanel import DefaultPipelinePanel
|
|
22
24
|
|
|
23
25
|
LOG = LogManager()
|
|
@@ -34,6 +36,8 @@ class MainWindow(QMainWindow):
|
|
|
34
36
|
self._segment_muscle_fat_l3_tensorflow_task_panel = None
|
|
35
37
|
self._create_pngs_from_segmentations_task_panel = None
|
|
36
38
|
self._calculate_scores_task_panel = None
|
|
39
|
+
self._dicom2nifti_task_panel = None
|
|
40
|
+
self._select_slice_from_scans_task_panel = None
|
|
37
41
|
self._default_pipeline_panel = None
|
|
38
42
|
self.init_window()
|
|
39
43
|
|
|
@@ -71,11 +75,17 @@ class MainWindow(QMainWindow):
|
|
|
71
75
|
calculate_scores_task_action.triggered.connect(self.handle_calculate_scores_task_action)
|
|
72
76
|
create_pngs_from_segmentations_task_action = QAction('CreatePngsFromSegmentationsTask', self)
|
|
73
77
|
create_pngs_from_segmentations_task_action.triggered.connect(self.handle_create_pngs_from_segmentations_task_action)
|
|
78
|
+
dicom2nifti_task_action = QAction('Dicom2NiftiTask', self)
|
|
79
|
+
dicom2nifti_task_action.triggered.connect(self.handle_dicom2nifti_task_action)
|
|
80
|
+
select_slice_from_scans_task_action = QAction('SelectSliceFromScansTask', self)
|
|
81
|
+
select_slice_from_scans_task_action.triggered.connect(self.handle_select_slice_from_scans_task_action)
|
|
74
82
|
tasks_menu = self.menuBar().addMenu('Tasks')
|
|
75
83
|
tasks_menu.addAction(rescale_dicom_images_task_action)
|
|
76
84
|
tasks_menu.addAction(segment_muscle_fat_l3_tensorflow_task_action)
|
|
77
85
|
tasks_menu.addAction(calculate_scores_task_action)
|
|
78
86
|
tasks_menu.addAction(create_pngs_from_segmentations_task_action)
|
|
87
|
+
tasks_menu.addAction(dicom2nifti_task_action)
|
|
88
|
+
tasks_menu.addAction(select_slice_from_scans_task_action)
|
|
79
89
|
|
|
80
90
|
def init_pipelines_menu(self):
|
|
81
91
|
default_pipeline_action = QAction('DefaultPipeline', self)
|
|
@@ -100,6 +110,8 @@ class MainWindow(QMainWindow):
|
|
|
100
110
|
self._main_panel.add_panel(self.segment_muscle_fat_l3_tensorflow_task_panel(), 'segmentmusclefatl3tensorflowtaskpanel')
|
|
101
111
|
self._main_panel.add_panel(self.create_pngs_from_segmentations_task_panel(), 'createpngsfromsegmentationstaskpanel')
|
|
102
112
|
self._main_panel.add_panel(self.calculate_scores_task_panel(), 'calculatescorestaskpanel')
|
|
113
|
+
self._main_panel.add_panel(self.dicom2nifti_task_panel(), 'dicom2niftitaskpanel')
|
|
114
|
+
self._main_panel.add_panel(self.select_slice_from_scans_task_panel(), 'selectslicefromscanstaskpanel')
|
|
103
115
|
self._main_panel.add_panel(self.default_pipeline_panel(), 'defaultpipelinepanel')
|
|
104
116
|
self._main_panel.select_panel('defaultpipelinepanel')
|
|
105
117
|
return self._main_panel
|
|
@@ -132,6 +144,16 @@ class MainWindow(QMainWindow):
|
|
|
132
144
|
self._calculate_scores_task_panel = CalculateScoresTaskPanel()
|
|
133
145
|
return self._calculate_scores_task_panel
|
|
134
146
|
|
|
147
|
+
def dicom2nifti_task_panel(self):
|
|
148
|
+
if not self._dicom2nifti_task_panel:
|
|
149
|
+
self._dicom2nifti_task_panel = Dicom2NiftiTaskPanel()
|
|
150
|
+
return self._dicom2nifti_task_panel
|
|
151
|
+
|
|
152
|
+
def select_slice_from_scans_task_panel(self):
|
|
153
|
+
if not self._select_slice_from_scans_task_panel:
|
|
154
|
+
self._select_slice_from_scans_task_panel = SelectSliceFromScansTaskPanel()
|
|
155
|
+
return self._select_slice_from_scans_task_panel
|
|
156
|
+
|
|
135
157
|
def default_pipeline_panel(self):
|
|
136
158
|
if not self._default_pipeline_panel:
|
|
137
159
|
self._default_pipeline_panel = DefaultPipelinePanel()
|
|
@@ -156,6 +178,12 @@ class MainWindow(QMainWindow):
|
|
|
156
178
|
def handle_calculate_scores_task_action(self):
|
|
157
179
|
self.main_panel().select_panel('calculatescorestaskpanel')
|
|
158
180
|
|
|
181
|
+
def handle_dicom2nifti_task_action(self):
|
|
182
|
+
self.main_panel().select_panel('dicom2niftitaskpanel')
|
|
183
|
+
|
|
184
|
+
def handle_select_slice_from_scans_task_action(self):
|
|
185
|
+
self.main_panel().select_panel('selectslicefromscanstaskpanel')
|
|
186
|
+
|
|
159
187
|
def handle_default_pipeline_action(self):
|
|
160
188
|
self.main_panel().select_panel('defaultpipelinepanel')
|
|
161
189
|
|
|
@@ -169,6 +197,8 @@ class MainWindow(QMainWindow):
|
|
|
169
197
|
self.segment_muscle_fat_l3_tensorflow_task_panel().save_inputs_and_parameters()
|
|
170
198
|
self.create_pngs_from_segmentations_task_panel().save_inputs_and_parameters()
|
|
171
199
|
self.calculate_scores_task_panel().save_inputs_and_parameters()
|
|
200
|
+
self.dicom2nifti_task_panel().save_inputs_and_parameters()
|
|
201
|
+
self.select_slice_from_scans_task_panel().save_inputs_and_parameters()
|
|
172
202
|
self.default_pipeline_panel().save_inputs_and_parameters()
|
|
173
203
|
return super().closeEvent(event)
|
|
174
204
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.0.5
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from PySide6.QtWidgets import (
|
|
4
|
+
QLineEdit,
|
|
5
|
+
QCheckBox,
|
|
6
|
+
QSpinBox,
|
|
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.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 Dicom2NiftiTask
|
|
25
|
+
|
|
26
|
+
LOG = LogManager()
|
|
27
|
+
|
|
28
|
+
PANEL_TITLE = 'Dicom2NiftiTask'
|
|
29
|
+
PANEL_NAME = 'dicom2niftitaskpanel'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Dicom2NiftiTaskPanel(TaskPanel):
|
|
33
|
+
def __init__(self):
|
|
34
|
+
super(Dicom2NiftiTaskPanel, self).__init__()
|
|
35
|
+
self.set_title(PANEL_TITLE)
|
|
36
|
+
self._images_dir_line_edit = None
|
|
37
|
+
self._images_dir_select_button = None
|
|
38
|
+
self._output_dir_line_edit = None
|
|
39
|
+
self._output_dir_select_button = None
|
|
40
|
+
self._overwrite_checkbox = None
|
|
41
|
+
self._form_layout = None
|
|
42
|
+
self._run_task_button = None
|
|
43
|
+
self._settings = None
|
|
44
|
+
self._task = None
|
|
45
|
+
self._worker = None
|
|
46
|
+
self._thread = None
|
|
47
|
+
self.init_layout()
|
|
48
|
+
|
|
49
|
+
def images_dir_line_edit(self):
|
|
50
|
+
if not self._images_dir_line_edit:
|
|
51
|
+
self._images_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/images_dir'))
|
|
52
|
+
return self._images_dir_line_edit
|
|
53
|
+
|
|
54
|
+
def images_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_images_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(self.settings().get(f'{PANEL_NAME}/output_dir'))
|
|
63
|
+
return self._output_dir_line_edit
|
|
64
|
+
|
|
65
|
+
def output_dir_select_button(self):
|
|
66
|
+
if not self._output_dir_select_button:
|
|
67
|
+
self._output_dir_select_button = QPushButton('Select')
|
|
68
|
+
self._output_dir_select_button.clicked.connect(self.handle_output_dir_select_button)
|
|
69
|
+
return self._output_dir_select_button
|
|
70
|
+
|
|
71
|
+
def overwrite_checkbox(self):
|
|
72
|
+
if not self._overwrite_checkbox:
|
|
73
|
+
self._overwrite_checkbox = QCheckBox('')
|
|
74
|
+
self._overwrite_checkbox.setChecked(self.settings().get_bool(f'{PANEL_NAME}/overwrite', True))
|
|
75
|
+
return self._overwrite_checkbox
|
|
76
|
+
|
|
77
|
+
def form_layout(self):
|
|
78
|
+
if not self._form_layout:
|
|
79
|
+
self._form_layout = QFormLayout()
|
|
80
|
+
if is_macos():
|
|
81
|
+
self._form_layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
|
|
82
|
+
return self._form_layout
|
|
83
|
+
|
|
84
|
+
def run_task_button(self):
|
|
85
|
+
if not self._run_task_button:
|
|
86
|
+
self._run_task_button = QPushButton('Run task')
|
|
87
|
+
self._run_task_button.clicked.connect(self.handle_run_task_button)
|
|
88
|
+
return self._run_task_button
|
|
89
|
+
|
|
90
|
+
def settings(self):
|
|
91
|
+
if not self._settings:
|
|
92
|
+
self._settings = Settings()
|
|
93
|
+
return self._settings
|
|
94
|
+
|
|
95
|
+
def init_layout(self):
|
|
96
|
+
images_dir_layout = QHBoxLayout()
|
|
97
|
+
images_dir_layout.addWidget(self.images_dir_line_edit())
|
|
98
|
+
images_dir_layout.addWidget(self.images_dir_select_button())
|
|
99
|
+
output_dir_layout = QHBoxLayout()
|
|
100
|
+
output_dir_layout.addWidget(self.output_dir_line_edit())
|
|
101
|
+
output_dir_layout.addWidget(self.output_dir_select_button())
|
|
102
|
+
self.form_layout().addRow('Images directory', images_dir_layout)
|
|
103
|
+
self.form_layout().addRow('Output directory', output_dir_layout)
|
|
104
|
+
self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
|
|
105
|
+
layout = QVBoxLayout()
|
|
106
|
+
layout.addLayout(self.form_layout())
|
|
107
|
+
layout.addWidget(self.run_task_button())
|
|
108
|
+
self.setLayout(layout)
|
|
109
|
+
self.setObjectName(PANEL_NAME)
|
|
110
|
+
|
|
111
|
+
def handle_images_dir_select_button(self):
|
|
112
|
+
last_directory = self.settings().get('last_directory')
|
|
113
|
+
directory = QFileDialog.getExistingDirectory(dir=last_directory)
|
|
114
|
+
if directory:
|
|
115
|
+
self.images_dir_line_edit().setText(directory)
|
|
116
|
+
self.settings().set('last_directory', directory)
|
|
117
|
+
|
|
118
|
+
def handle_output_dir_select_button(self):
|
|
119
|
+
last_directory = self.settings().get('last_directory')
|
|
120
|
+
directory = QFileDialog.getExistingDirectory(dir=last_directory)
|
|
121
|
+
if directory:
|
|
122
|
+
self.output_dir_line_edit().setText(directory)
|
|
123
|
+
self.settings().set('last_directory', directory)
|
|
124
|
+
|
|
125
|
+
def handle_run_task_button(self):
|
|
126
|
+
errors = self.check_inputs_and_parameters()
|
|
127
|
+
if len(errors) > 0:
|
|
128
|
+
error_message = 'Following errors were encountered:\n'
|
|
129
|
+
for error in errors:
|
|
130
|
+
error_message += f' - {error}\n'
|
|
131
|
+
QMessageBox.information(self, 'Error', error_message)
|
|
132
|
+
else:
|
|
133
|
+
LOG.info('Running task...')
|
|
134
|
+
self.run_task_button().setEnabled(False)
|
|
135
|
+
self.save_inputs_and_parameters()
|
|
136
|
+
self._task = Dicom2NiftiTask(
|
|
137
|
+
inputs={'images': self.images_dir_line_edit().text()},
|
|
138
|
+
params=None,
|
|
139
|
+
output=self.output_dir_line_edit().text(),
|
|
140
|
+
overwrite=self.overwrite_checkbox().isChecked(),
|
|
141
|
+
)
|
|
142
|
+
self._worker = Worker(self._task)
|
|
143
|
+
self._thread = QThread()
|
|
144
|
+
self._worker.moveToThread(self._thread)
|
|
145
|
+
self._thread.started.connect(self._worker.run)
|
|
146
|
+
self._worker.progress.connect(self.handle_progress)
|
|
147
|
+
self._worker.status.connect(self.handle_status)
|
|
148
|
+
self._worker.finished.connect(self.handle_finished)
|
|
149
|
+
self._worker.finished.connect(self._thread.quit)
|
|
150
|
+
self._worker.finished.connect(self._worker.deleteLater)
|
|
151
|
+
self._thread.finished.connect(self._thread.deleteLater)
|
|
152
|
+
self._thread.start()
|
|
153
|
+
|
|
154
|
+
@Slot(int)
|
|
155
|
+
def handle_progress(self, progress):
|
|
156
|
+
LOG.info(f'Progress: {progress} / 100%')
|
|
157
|
+
|
|
158
|
+
@Slot(str)
|
|
159
|
+
def handle_status(self, status):
|
|
160
|
+
LOG.info(f'Status: {status}')
|
|
161
|
+
|
|
162
|
+
@Slot()
|
|
163
|
+
def handle_finished(self):
|
|
164
|
+
self.run_task_button().setEnabled(True)
|
|
165
|
+
|
|
166
|
+
# HELPERS
|
|
167
|
+
|
|
168
|
+
def check_inputs_and_parameters(self):
|
|
169
|
+
errors = []
|
|
170
|
+
if self.images_dir_line_edit().text() == '':
|
|
171
|
+
errors.append('Empty images directory path')
|
|
172
|
+
if not os.path.isdir(self.images_dir_line_edit().text()):
|
|
173
|
+
errors.append('Images directory does not exist')
|
|
174
|
+
if self.output_dir_line_edit().text() == '':
|
|
175
|
+
errors.append('Empty output directory path')
|
|
176
|
+
if os.path.isdir(self.output_dir_line_edit().text()) and not self.overwrite_checkbox().isChecked():
|
|
177
|
+
errors.append('Output directory exists but overwrite=False. Please remove output directory first')
|
|
178
|
+
return errors
|
|
179
|
+
|
|
180
|
+
def save_inputs_and_parameters(self):
|
|
181
|
+
self.settings().set(f'{PANEL_NAME}/images_dir', self.images_dir_line_edit().text())
|
|
182
|
+
self.settings().set(f'{PANEL_NAME}/output_dir', self.output_dir_line_edit().text())
|
|
183
|
+
self.settings().set(f'{PANEL_NAME}/overwrite', self.overwrite_checkbox().isChecked())
|
|
@@ -3,7 +3,7 @@ import os
|
|
|
3
3
|
from PySide6.QtWidgets import (
|
|
4
4
|
QLineEdit,
|
|
5
5
|
QCheckBox,
|
|
6
|
-
|
|
6
|
+
QComboBox,
|
|
7
7
|
QHBoxLayout,
|
|
8
8
|
QVBoxLayout,
|
|
9
9
|
QFormLayout,
|
|
@@ -16,26 +16,26 @@ from PySide6.QtCore import (
|
|
|
16
16
|
Slot,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
-
from
|
|
20
|
-
from
|
|
21
|
-
from
|
|
22
|
-
from
|
|
23
|
-
from
|
|
24
|
-
|
|
25
|
-
from mosamatic.tasks import SelectSliceFromScanTask
|
|
19
|
+
from mosamatic2.core.managers.logmanager import LogManager
|
|
20
|
+
from mosamatic2.ui.widgets.panels.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 SelectSliceFromScansTask
|
|
26
25
|
|
|
27
26
|
LOG = LogManager()
|
|
28
27
|
|
|
29
|
-
PANEL_TITLE = 'Automatically select L3
|
|
30
|
-
PANEL_NAME = '
|
|
28
|
+
PANEL_TITLE = 'Automatically select L3 slice from full CT scan'
|
|
29
|
+
PANEL_NAME = 'selectslicefromscanstaskpanel'
|
|
31
30
|
|
|
32
31
|
|
|
33
|
-
class
|
|
32
|
+
class SelectSliceFromScansTaskPanel(TaskPanel):
|
|
34
33
|
def __init__(self):
|
|
35
|
-
super(
|
|
34
|
+
super(SelectSliceFromScansTaskPanel, self).__init__()
|
|
36
35
|
self.set_title(PANEL_TITLE)
|
|
37
36
|
self._scans_dir_line_edit = None
|
|
38
37
|
self._scans_dir_select_button = None
|
|
38
|
+
self._vertebra_combobox = None
|
|
39
39
|
self._output_dir_line_edit = None
|
|
40
40
|
self._output_dir_select_button = None
|
|
41
41
|
self._overwrite_checkbox = None
|
|
@@ -58,6 +58,13 @@ class SelectSliceFromScanTaskPanel(TaskPanel):
|
|
|
58
58
|
self._scans_dir_select_button.clicked.connect(self.handle_scans_dir_select_button)
|
|
59
59
|
return self._scans_dir_select_button
|
|
60
60
|
|
|
61
|
+
def vertebra_combobox(self):
|
|
62
|
+
if not self._vertebra_combobox:
|
|
63
|
+
self._vertebra_combobox = QComboBox()
|
|
64
|
+
self._vertebra_combobox.addItems(['L3'])
|
|
65
|
+
self._vertebra_combobox.setCurrentText(self.settings().get(f'{PANEL_NAME}/vertebra'))
|
|
66
|
+
return self._vertebra_combobox
|
|
67
|
+
|
|
61
68
|
def output_dir_line_edit(self):
|
|
62
69
|
if not self._output_dir_line_edit:
|
|
63
70
|
self._output_dir_line_edit = QLineEdit(self.settings().get(f'{PANEL_NAME}/output_dir'))
|
|
@@ -101,6 +108,7 @@ class SelectSliceFromScanTaskPanel(TaskPanel):
|
|
|
101
108
|
output_dir_layout.addWidget(self.output_dir_line_edit())
|
|
102
109
|
output_dir_layout.addWidget(self.output_dir_select_button())
|
|
103
110
|
self.form_layout().addRow('Scans directory', scans_dir_layout)
|
|
111
|
+
# self.form_layout().addRow('Vertebra', self.vertebra_combobox())
|
|
104
112
|
self.form_layout().addRow('Output directory', output_dir_layout)
|
|
105
113
|
self.form_layout().addRow('Overwrite', self.overwrite_checkbox())
|
|
106
114
|
layout = QVBoxLayout()
|
|
@@ -134,11 +142,11 @@ class SelectSliceFromScanTaskPanel(TaskPanel):
|
|
|
134
142
|
LOG.info('Running task...')
|
|
135
143
|
self.run_task_button().setEnabled(False)
|
|
136
144
|
self.save_inputs_and_parameters()
|
|
137
|
-
self._task =
|
|
138
|
-
self.scans_dir_line_edit().text(),
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
self.overwrite_checkbox().isChecked()
|
|
145
|
+
self._task = SelectSliceFromScansTask(
|
|
146
|
+
inputs={'scans': self.scans_dir_line_edit().text()},
|
|
147
|
+
params={'vertebra': 'L3'},
|
|
148
|
+
output=self.output_dir_line_edit().text(),
|
|
149
|
+
overwrite=self.overwrite_checkbox().isChecked(),
|
|
142
150
|
)
|
|
143
151
|
self._worker = Worker(self._task)
|
|
144
152
|
self._thread = QThread()
|
|
@@ -180,5 +188,6 @@ class SelectSliceFromScanTaskPanel(TaskPanel):
|
|
|
180
188
|
|
|
181
189
|
def save_inputs_and_parameters(self):
|
|
182
190
|
self.settings().set(f'{PANEL_NAME}/scans_dir', self.scans_dir_line_edit().text())
|
|
191
|
+
self.settings().set(f'{PANEL_NAME}/vertebra', self.vertebra_combobox().currentText())
|
|
183
192
|
self.settings().set(f'{PANEL_NAME}/output_dir', self.output_dir_line_edit().text())
|
|
184
193
|
self.settings().set(f'{PANEL_NAME}/overwrite', self.overwrite_checkbox().isChecked())
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2.0.3
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/commands/createpngsfromsegmentations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/core/tasks/calculatescorestask/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mosamatic2-2.0.3 → mosamatic2-2.0.5}/src/mosamatic2/ui/resources/images/body-composition.jpg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|