Commit 5c51e20fac5da48a9d463f6ea92d10d6e5c4a739
Committed by
GitHub
1 parent
a431ff6e
Exists in
master
Starting to support plugins (#222)
* creating plugins folder and add menu * added header to inv_paths * Adding plugin into menu * Added pubsub to call a plugin * starting to calling plugin * importing plugin module * improvements * starting to calling plugin main * Adding plugin style into style_level and slice_styles * Created methods to discard cache nd get slice and actual mask * added option to discard vtk cache from slice and mask * added method to create project from a matrix * show close project dialong when creating a new project from array * replace scipy.misc with imageio * enabling and disabling plugin menu items when open and close a project * using default ww&wl from previous project if available * Starting to create a base editor style * Created a base editor style interactor to ease the creation o segmentation that need to select voxels by a brush * calling methods before and after click, move and release in the BaseImageEdtionInteractorStyle * improvements * improvements * merging with master * Created a method to load project from folder * Created a method to save a project inside a folder * Importing invesalius project folder * Starting new invesalius instance * Showing error when not able to launch new invesalius instance * calling correctly new invesalius instance * improvements
Showing
12 changed files
with
1365 additions
and
463 deletions
Show diff stats
app.py
@@ -312,6 +312,8 @@ def parse_comand_line(): | @@ -312,6 +312,8 @@ def parse_comand_line(): | ||
312 | parser.add_option("--import-all", | 312 | parser.add_option("--import-all", |
313 | action="store") | 313 | action="store") |
314 | 314 | ||
315 | + parser.add_option("--import-folder", action="store", dest="import_folder") | ||
316 | + | ||
315 | parser.add_option("-s", "--save", | 317 | parser.add_option("-s", "--save", |
316 | help="Save the project after an import.") | 318 | help="Save the project after an import.") |
317 | 319 | ||
@@ -354,6 +356,13 @@ def use_cmd_optargs(options, args): | @@ -354,6 +356,13 @@ def use_cmd_optargs(options, args): | ||
354 | check_for_export(options) | 356 | check_for_export(options) |
355 | 357 | ||
356 | return True | 358 | return True |
359 | + elif options.import_folder: | ||
360 | + Publisher.sendMessage('Import folder', folder=options.import_folder) | ||
361 | + if options.save: | ||
362 | + Publisher.sendMessage('Save project', filepath=os.path.abspath(options.save)) | ||
363 | + exit(0) | ||
364 | + check_for_export(options) | ||
365 | + | ||
357 | elif options.import_all: | 366 | elif options.import_all: |
358 | import invesalius.reader.dicom_reader as dcm | 367 | import invesalius.reader.dicom_reader as dcm |
359 | for patient in dcm.GetDicomGroups(options.import_all): | 368 | for patient in dcm.GetDicomGroups(options.import_all): |
invesalius/constants.py
@@ -486,6 +486,7 @@ ID_ABOUT = wx.ID_ABOUT | @@ -486,6 +486,7 @@ ID_ABOUT = wx.ID_ABOUT | ||
486 | [wx.NewId() for number in range(3)] | 486 | [wx.NewId() for number in range(3)] |
487 | 487 | ||
488 | ID_START = wx.NewId() | 488 | ID_START = wx.NewId() |
489 | +ID_PLUGINS_SHOW_PATH = wx.NewId() | ||
489 | 490 | ||
490 | ID_FLIP_X = wx.NewId() | 491 | ID_FLIP_X = wx.NewId() |
491 | ID_FLIP_Y = wx.NewId() | 492 | ID_FLIP_Y = wx.NewId() |
invesalius/control.py
@@ -18,7 +18,12 @@ | @@ -18,7 +18,12 @@ | ||
18 | #-------------------------------------------------------------------------- | 18 | #-------------------------------------------------------------------------- |
19 | import os | 19 | import os |
20 | import plistlib | 20 | import plistlib |
21 | +import tempfile | ||
22 | +import textwrap | ||
23 | + | ||
21 | import wx | 24 | import wx |
25 | +import numpy as np | ||
26 | + | ||
22 | from wx.lib.pubsub import pub as Publisher | 27 | from wx.lib.pubsub import pub as Publisher |
23 | 28 | ||
24 | import invesalius.constants as const | 29 | import invesalius.constants as const |
@@ -43,6 +48,7 @@ import subprocess | @@ -43,6 +48,7 @@ import subprocess | ||
43 | import sys | 48 | import sys |
44 | 49 | ||
45 | from invesalius import inv_paths | 50 | from invesalius import inv_paths |
51 | +from invesalius import plugins | ||
46 | 52 | ||
47 | DEFAULT_THRESH_MODE = 0 | 53 | DEFAULT_THRESH_MODE = 0 |
48 | 54 | ||
@@ -51,6 +57,7 @@ class Controller(): | @@ -51,6 +57,7 @@ class Controller(): | ||
51 | def __init__(self, frame): | 57 | def __init__(self, frame): |
52 | self.surface_manager = srf.SurfaceManager() | 58 | self.surface_manager = srf.SurfaceManager() |
53 | self.volume = volume.Volume() | 59 | self.volume = volume.Volume() |
60 | + self.plugin_manager = plugins.PluginManager() | ||
54 | self.__bind_events() | 61 | self.__bind_events() |
55 | self.frame = frame | 62 | self.frame = frame |
56 | self.progress_dialog = None | 63 | self.progress_dialog = None |
@@ -69,9 +76,12 @@ class Controller(): | @@ -69,9 +76,12 @@ class Controller(): | ||
69 | 76 | ||
70 | Publisher.sendMessage('Load Preferences') | 77 | Publisher.sendMessage('Load Preferences') |
71 | 78 | ||
79 | + self.plugin_manager.find_plugins() | ||
80 | + | ||
72 | def __bind_events(self): | 81 | def __bind_events(self): |
73 | Publisher.subscribe(self.OnImportMedicalImages, 'Import directory') | 82 | Publisher.subscribe(self.OnImportMedicalImages, 'Import directory') |
74 | Publisher.subscribe(self.OnImportGroup, 'Import group') | 83 | Publisher.subscribe(self.OnImportGroup, 'Import group') |
84 | + Publisher.subscribe(self.OnImportFolder, 'Import folder') | ||
75 | Publisher.subscribe(self.OnShowDialogImportDirectory, | 85 | Publisher.subscribe(self.OnShowDialogImportDirectory, |
76 | 'Show import directory dialog') | 86 | 'Show import directory dialog') |
77 | Publisher.subscribe(self.OnShowDialogImportOtherFiles, | 87 | Publisher.subscribe(self.OnShowDialogImportOtherFiles, |
@@ -113,6 +123,8 @@ class Controller(): | @@ -113,6 +123,8 @@ class Controller(): | ||
113 | 123 | ||
114 | Publisher.subscribe(self.Send_affine, 'Get affine matrix') | 124 | Publisher.subscribe(self.Send_affine, 'Get affine matrix') |
115 | 125 | ||
126 | + Publisher.subscribe(self.create_project_from_matrix, 'Create project from matrix') | ||
127 | + | ||
116 | def SetBitmapSpacing(self, spacing): | 128 | def SetBitmapSpacing(self, spacing): |
117 | proj = prj.Project() | 129 | proj = prj.Project() |
118 | proj.spacing = spacing | 130 | proj.spacing = spacing |
@@ -492,7 +504,32 @@ class Controller(): | @@ -492,7 +504,32 @@ class Controller(): | ||
492 | self.LoadProject() | 504 | self.LoadProject() |
493 | Publisher.sendMessage("Enable state project", state=True) | 505 | Publisher.sendMessage("Enable state project", state=True) |
494 | 506 | ||
495 | - #------------------------------------------------------------------------------------- | 507 | + def OnImportFolder(self, folder): |
508 | + Publisher.sendMessage('Begin busy cursor') | ||
509 | + folder = os.path.abspath(folder) | ||
510 | + | ||
511 | + proj = prj.Project() | ||
512 | + proj.load_from_folder(folder) | ||
513 | + | ||
514 | + self.Slice = sl.Slice() | ||
515 | + self.Slice._open_image_matrix(proj.matrix_filename, | ||
516 | + tuple(proj.matrix_shape), | ||
517 | + proj.matrix_dtype) | ||
518 | + | ||
519 | + self.Slice.window_level = proj.level | ||
520 | + self.Slice.window_width = proj.window | ||
521 | + | ||
522 | + Publisher.sendMessage('Update threshold limits list', | ||
523 | + threshold_range=proj.threshold_range) | ||
524 | + | ||
525 | + session = ses.Session() | ||
526 | + filename = proj.name+".inv3" | ||
527 | + filename = filename.replace("/", "") #Fix problem case other/Skull_DICOM | ||
528 | + dirpath = session.CreateProject(filename) | ||
529 | + self.LoadProject() | ||
530 | + Publisher.sendMessage("Enable state project", state=True) | ||
531 | + | ||
532 | + Publisher.sendMessage('End busy cursor') | ||
496 | 533 | ||
497 | def LoadProject(self): | 534 | def LoadProject(self): |
498 | proj = prj.Project() | 535 | proj = prj.Project() |
@@ -673,6 +710,83 @@ class Controller(): | @@ -673,6 +710,83 @@ class Controller(): | ||
673 | 710 | ||
674 | dirpath = session.CreateProject(filename) | 711 | dirpath = session.CreateProject(filename) |
675 | 712 | ||
713 | + | ||
714 | + def create_project_from_matrix(self, name, matrix, orientation="AXIAL", spacing=(1.0, 1.0, 1.0), modality="CT", window_width=None, window_level=None, new_instance=False): | ||
715 | + """ | ||
716 | + Creates a new project from a Numpy 3D array. | ||
717 | + | ||
718 | + name: Name of the project. | ||
719 | + matrix: A Numpy 3D array. It only works with int16 arrays. | ||
720 | + spacing: The spacing between the center of the voxels in X, Y and Z direction. | ||
721 | + modality: Imaging modality. | ||
722 | + """ | ||
723 | + if window_width is None: | ||
724 | + window_width = (matrix.max() - matrix.min()) | ||
725 | + if window_level is None: | ||
726 | + window_level = (matrix.max() + matrix.min()) // 2 | ||
727 | + | ||
728 | + window_width = int(window_width) | ||
729 | + window_level = int(window_level) | ||
730 | + | ||
731 | + name_to_const = {"AXIAL": const.AXIAL, | ||
732 | + "CORONAL": const.CORONAL, | ||
733 | + "SAGITTAL": const.SAGITAL} | ||
734 | + | ||
735 | + if new_instance: | ||
736 | + self.start_new_inv_instance(matrix, name, spacing, modality, name_to_const[orientation], window_width, window_level) | ||
737 | + else: | ||
738 | + # Verifying if there is a project open | ||
739 | + s = ses.Session() | ||
740 | + if s.IsOpen(): | ||
741 | + Publisher.sendMessage('Close Project') | ||
742 | + Publisher.sendMessage('Disconnect tracker') | ||
743 | + | ||
744 | + # Check if user really closed the project, if not, stop project creation | ||
745 | + if s.IsOpen(): | ||
746 | + return | ||
747 | + | ||
748 | + mmap_matrix = image_utils.array2memmap(matrix) | ||
749 | + | ||
750 | + self.Slice = sl.Slice() | ||
751 | + self.Slice.matrix = mmap_matrix | ||
752 | + self.Slice.matrix_filename = mmap_matrix.filename | ||
753 | + self.Slice.spacing = spacing | ||
754 | + | ||
755 | + self.Slice.window_width = window_width | ||
756 | + self.Slice.window_level = window_level | ||
757 | + | ||
758 | + proj = prj.Project() | ||
759 | + proj.name = name | ||
760 | + proj.modality = modality | ||
761 | + proj.SetAcquisitionModality(modality) | ||
762 | + proj.matrix_shape = matrix.shape | ||
763 | + proj.matrix_dtype = matrix.dtype.name | ||
764 | + proj.matrix_filename = self.Slice.matrix_filename | ||
765 | + proj.window = window_width | ||
766 | + proj.level = window_level | ||
767 | + | ||
768 | + | ||
769 | + proj.original_orientation =\ | ||
770 | + name_to_const[orientation] | ||
771 | + | ||
772 | + proj.threshold_range = int(matrix.min()), int(matrix.max()) | ||
773 | + proj.spacing = self.Slice.spacing | ||
774 | + | ||
775 | + Publisher.sendMessage('Update threshold limits list', | ||
776 | + threshold_range=proj.threshold_range) | ||
777 | + | ||
778 | + ###### | ||
779 | + session = ses.Session() | ||
780 | + filename = proj.name + ".inv3" | ||
781 | + | ||
782 | + filename = filename.replace("/", "") | ||
783 | + | ||
784 | + dirpath = session.CreateProject(filename) | ||
785 | + | ||
786 | + self.LoadProject() | ||
787 | + Publisher.sendMessage("Enable state project", state=True) | ||
788 | + | ||
789 | + | ||
676 | def OnOpenBitmapFiles(self, rec_data): | 790 | def OnOpenBitmapFiles(self, rec_data): |
677 | bmp_data = bmp.BitmapData() | 791 | bmp_data = bmp.BitmapData() |
678 | 792 | ||
@@ -954,3 +1068,24 @@ class Controller(): | @@ -954,3 +1068,24 @@ class Controller(): | ||
954 | 1068 | ||
955 | def ApplyReorientation(self): | 1069 | def ApplyReorientation(self): |
956 | self.Slice.apply_reorientation() | 1070 | self.Slice.apply_reorientation() |
1071 | + | ||
1072 | + def start_new_inv_instance(self, image, name, spacing, modality, orientation, window_width, window_level): | ||
1073 | + p = prj.Project() | ||
1074 | + project_folder = tempfile.mkdtemp() | ||
1075 | + p.create_project_file(name, spacing, modality, orientation, window_width, window_level, image, folder=project_folder) | ||
1076 | + err_msg = '' | ||
1077 | + try: | ||
1078 | + sp = subprocess.Popen([sys.executable, sys.argv[0], '--import-folder', project_folder], | ||
1079 | + stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=os.getcwd()) | ||
1080 | + except Exception as err: | ||
1081 | + err_msg = str(err) | ||
1082 | + else: | ||
1083 | + try: | ||
1084 | + if sp.wait(2): | ||
1085 | + err_msg = sp.stderr.read().decode('utf8') | ||
1086 | + sp.terminate() | ||
1087 | + except subprocess.TimeoutExpired: | ||
1088 | + pass | ||
1089 | + | ||
1090 | + if err_msg: | ||
1091 | + dialog.MessageBox(None, "It was not possible to launch new instance of InVesalius3 dsfa dfdsfa sdfas fdsaf asdfasf dsaa", err_msg) |
invesalius/data/imagedata_utils.py
@@ -288,6 +288,16 @@ def create_dicom_thumbnails(image, window=None, level=None): | @@ -288,6 +288,16 @@ def create_dicom_thumbnails(image, window=None, level=None): | ||
288 | return thumbnail_path | 288 | return thumbnail_path |
289 | 289 | ||
290 | 290 | ||
291 | + | ||
292 | +def array2memmap(arr, filename=None): | ||
293 | + if filename is None: | ||
294 | + filename = tempfile.mktemp(prefix='inv3_', suffix='.dat') | ||
295 | + matrix = numpy.memmap(filename, mode='w+', dtype=arr.dtype, shape=arr.shape) | ||
296 | + matrix[:] = arr[:] | ||
297 | + matrix.flush() | ||
298 | + return matrix | ||
299 | + | ||
300 | + | ||
291 | def bitmap2memmap(files, slice_size, orientation, spacing, resolution_percentage): | 301 | def bitmap2memmap(files, slice_size, orientation, spacing, resolution_percentage): |
292 | """ | 302 | """ |
293 | From a list of dicom files it creates memmap file in the temp folder and | 303 | From a list of dicom files it creates memmap file in the temp folder and |
invesalius/data/slice_.py
1 | -#-------------------------------------------------------------------------- | 1 | +# -------------------------------------------------------------------------- |
2 | # Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas | 2 | # Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas |
3 | # Copyright: (C) 2001 Centro de Pesquisas Renato Archer | 3 | # Copyright: (C) 2001 Centro de Pesquisas Renato Archer |
4 | # Homepage: http://www.softwarepublico.gov.br | 4 | # Homepage: http://www.softwarepublico.gov.br |
5 | # Contact: invesalius@cti.gov.br | 5 | # Contact: invesalius@cti.gov.br |
6 | # License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) | 6 | # License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) |
7 | -#-------------------------------------------------------------------------- | 7 | +# -------------------------------------------------------------------------- |
8 | # Este programa e software livre; voce pode redistribui-lo e/ou | 8 | # Este programa e software livre; voce pode redistribui-lo e/ou |
9 | # modifica-lo sob os termos da Licenca Publica Geral GNU, conforme | 9 | # modifica-lo sob os termos da Licenca Publica Geral GNU, conforme |
10 | # publicada pela Free Software Foundation; de acordo com a versao 2 | 10 | # publicada pela Free Software Foundation; de acordo com a versao 2 |
@@ -15,40 +15,38 @@ | @@ -15,40 +15,38 @@ | ||
15 | # COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM | 15 | # COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM |
16 | # PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais | 16 | # PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais |
17 | # detalhes. | 17 | # detalhes. |
18 | -#-------------------------------------------------------------------------- | ||
19 | -from six import with_metaclass | ||
20 | - | 18 | +# -------------------------------------------------------------------------- |
21 | import os | 19 | import os |
22 | import tempfile | 20 | import tempfile |
23 | 21 | ||
24 | import numpy as np | 22 | import numpy as np |
25 | import vtk | 23 | import vtk |
26 | - | ||
27 | from scipy import ndimage | 24 | from scipy import ndimage |
25 | +from six import with_metaclass | ||
28 | from wx.lib.pubsub import pub as Publisher | 26 | from wx.lib.pubsub import pub as Publisher |
29 | 27 | ||
30 | import invesalius.constants as const | 28 | import invesalius.constants as const |
31 | import invesalius.data.converters as converters | 29 | import invesalius.data.converters as converters |
32 | import invesalius.data.imagedata_utils as iu | 30 | import invesalius.data.imagedata_utils as iu |
33 | -import invesalius.style as st | 31 | +import invesalius.data.transformations as transformations |
34 | import invesalius.session as ses | 32 | import invesalius.session as ses |
33 | +import invesalius.style as st | ||
35 | import invesalius.utils as utils | 34 | import invesalius.utils as utils |
35 | +from invesalius.data import mips, transforms | ||
36 | from invesalius.data.mask import Mask | 36 | from invesalius.data.mask import Mask |
37 | from invesalius.project import Project | 37 | from invesalius.project import Project |
38 | -from invesalius.data import mips | ||
39 | 38 | ||
40 | -from invesalius.data import transforms | ||
41 | -import invesalius.data.transformations as transformations | ||
42 | -OTHER=0 | ||
43 | -PLIST=1 | ||
44 | -WIDGET=2 | 39 | +OTHER = 0 |
40 | +PLIST = 1 | ||
41 | +WIDGET = 2 | ||
45 | 42 | ||
46 | 43 | ||
47 | class SliceBuffer(object): | 44 | class SliceBuffer(object): |
48 | - """ | 45 | + """ |
49 | This class is used as buffer that mantains the vtkImageData and numpy array | 46 | This class is used as buffer that mantains the vtkImageData and numpy array |
50 | from actual slices from each orientation. | 47 | from actual slices from each orientation. |
51 | """ | 48 | """ |
49 | + | ||
52 | def __init__(self): | 50 | def __init__(self): |
53 | self.index = -1 | 51 | self.index = -1 |
54 | self.image = None | 52 | self.image = None |
@@ -86,9 +84,10 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -86,9 +84,10 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
86 | self.histogram = None | 84 | self.histogram = None |
87 | self._matrix = None | 85 | self._matrix = None |
88 | self.aux_matrices = {} | 86 | self.aux_matrices = {} |
87 | + self.aux_matrices_colours = {} | ||
89 | self.state = const.STATE_DEFAULT | 88 | self.state = const.STATE_DEFAULT |
90 | 89 | ||
91 | - self.to_show_aux = '' | 90 | + self.to_show_aux = "" |
92 | 91 | ||
93 | self._type_projection = const.PROJECTION_NORMAL | 92 | self._type_projection = const.PROJECTION_NORMAL |
94 | self.n_border = const.PROJECTION_BORDER_SIZE | 93 | self.n_border = const.PROJECTION_BORDER_SIZE |
@@ -105,9 +104,11 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -105,9 +104,11 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
105 | self.hue_range = (0, 0) | 104 | self.hue_range = (0, 0) |
106 | self.value_range = (0, 1) | 105 | self.value_range = (0, 1) |
107 | 106 | ||
108 | - self.buffer_slices = {"AXIAL": SliceBuffer(), | ||
109 | - "CORONAL": SliceBuffer(), | ||
110 | - "SAGITAL": SliceBuffer()} | 107 | + self.buffer_slices = { |
108 | + "AXIAL": SliceBuffer(), | ||
109 | + "CORONAL": SliceBuffer(), | ||
110 | + "SAGITAL": SliceBuffer(), | ||
111 | + } | ||
111 | 112 | ||
112 | self.num_gradient = 0 | 113 | self.num_gradient = 0 |
113 | self.interaction_style = st.StyleStateManager() | 114 | self.interaction_style = st.StyleStateManager() |
@@ -129,7 +130,9 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -129,7 +130,9 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
129 | i, e = value.min(), value.max() | 130 | i, e = value.min(), value.max() |
130 | r = int(e) - int(i) | 131 | r = int(e) - int(i) |
131 | self.histogram = np.histogram(self._matrix, r, (i, e))[0] | 132 | self.histogram = np.histogram(self._matrix, r, (i, e))[0] |
132 | - self.center = [(s * d/2.0) for (d, s) in zip(self.matrix.shape[::-1], self.spacing)] | 133 | + self.center = [ |
134 | + (s * d / 2.0) for (d, s) in zip(self.matrix.shape[::-1], self.spacing) | ||
135 | + ] | ||
133 | 136 | ||
134 | @property | 137 | @property |
135 | def spacing(self): | 138 | def spacing(self): |
@@ -138,85 +141,93 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -138,85 +141,93 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
138 | @spacing.setter | 141 | @spacing.setter |
139 | def spacing(self, value): | 142 | def spacing(self, value): |
140 | self._spacing = value | 143 | self._spacing = value |
141 | - self.center = [(s * d/2.0) for (d, s) in zip(self.matrix.shape[::-1], self.spacing)] | 144 | + self.center = [ |
145 | + (s * d / 2.0) for (d, s) in zip(self.matrix.shape[::-1], self.spacing) | ||
146 | + ] | ||
142 | 147 | ||
143 | def __bind_events(self): | 148 | def __bind_events(self): |
144 | # General slice control | 149 | # General slice control |
145 | - Publisher.subscribe(self.CreateSurfaceFromIndex, | ||
146 | - 'Create surface from index') | 150 | + Publisher.subscribe(self.CreateSurfaceFromIndex, "Create surface from index") |
147 | # Mask control | 151 | # Mask control |
148 | - Publisher.subscribe(self.__add_mask_thresh, 'Create new mask') | ||
149 | - Publisher.subscribe(self.__select_current_mask, | ||
150 | - 'Change mask selected') | 152 | + Publisher.subscribe(self.__add_mask_thresh, "Create new mask") |
153 | + Publisher.subscribe(self.__select_current_mask, "Change mask selected") | ||
151 | # Mask properties | 154 | # Mask properties |
152 | - Publisher.subscribe(self.__set_current_mask_edition_threshold, | ||
153 | - 'Set edition threshold values') | ||
154 | - Publisher.subscribe(self.__set_current_mask_threshold, | ||
155 | - 'Set threshold values') | ||
156 | - Publisher.subscribe(self.__set_current_mask_threshold_actual_slice, | ||
157 | - 'Changing threshold values') | ||
158 | - Publisher.subscribe(self.__set_current_mask_colour, | ||
159 | - 'Change mask colour') | ||
160 | - Publisher.subscribe(self.__set_mask_name, 'Change mask name') | ||
161 | - Publisher.subscribe(self.__show_mask, 'Show mask') | ||
162 | - Publisher.subscribe(self.__hide_current_mask, 'Hide current mask') | ||
163 | - Publisher.subscribe(self.__show_current_mask, 'Show current mask') | ||
164 | - Publisher.subscribe(self.__clean_current_mask, 'Clean current mask') | 155 | + Publisher.subscribe( |
156 | + self.__set_current_mask_edition_threshold, "Set edition threshold values" | ||
157 | + ) | ||
158 | + Publisher.subscribe(self.__set_current_mask_threshold, "Set threshold values") | ||
159 | + Publisher.subscribe( | ||
160 | + self.__set_current_mask_threshold_actual_slice, "Changing threshold values" | ||
161 | + ) | ||
162 | + Publisher.subscribe(self.__set_current_mask_colour, "Change mask colour") | ||
163 | + Publisher.subscribe(self.__set_mask_name, "Change mask name") | ||
164 | + Publisher.subscribe(self.__show_mask, "Show mask") | ||
165 | + Publisher.subscribe(self.__hide_current_mask, "Hide current mask") | ||
166 | + Publisher.subscribe(self.__show_current_mask, "Show current mask") | ||
167 | + Publisher.subscribe(self.__clean_current_mask, "Clean current mask") | ||
165 | 168 | ||
166 | - Publisher.subscribe(self.__export_slice, 'Export slice') | ||
167 | - Publisher.subscribe(self.__export_actual_mask, 'Export actual mask') | 169 | + Publisher.subscribe(self.__export_slice, "Export slice") |
170 | + Publisher.subscribe(self.__export_actual_mask, "Export actual mask") | ||
168 | 171 | ||
169 | - Publisher.subscribe(self.__set_current_mask_threshold_limits, | ||
170 | - 'Update threshold limits') | 172 | + Publisher.subscribe( |
173 | + self.__set_current_mask_threshold_limits, "Update threshold limits" | ||
174 | + ) | ||
171 | 175 | ||
172 | - Publisher.subscribe(self.UpdateWindowLevelBackground,\ | ||
173 | - 'Bright and contrast adjustment image') | 176 | + Publisher.subscribe( |
177 | + self.UpdateWindowLevelBackground, "Bright and contrast adjustment image" | ||
178 | + ) | ||
174 | 179 | ||
175 | - Publisher.subscribe(self.UpdateColourTableBackground,\ | ||
176 | - 'Change colour table from background image') | 180 | + Publisher.subscribe( |
181 | + self.UpdateColourTableBackground, | ||
182 | + "Change colour table from background image", | ||
183 | + ) | ||
177 | 184 | ||
178 | - Publisher.subscribe(self.UpdateColourTableBackgroundPlist,\ | ||
179 | - 'Change colour table from background image from plist') | 185 | + Publisher.subscribe( |
186 | + self.UpdateColourTableBackgroundPlist, | ||
187 | + "Change colour table from background image from plist", | ||
188 | + ) | ||
180 | 189 | ||
181 | - Publisher.subscribe(self.UpdateColourTableBackgroundWidget,\ | ||
182 | - 'Change colour table from background image from widget') | 190 | + Publisher.subscribe( |
191 | + self.UpdateColourTableBackgroundWidget, | ||
192 | + "Change colour table from background image from widget", | ||
193 | + ) | ||
183 | 194 | ||
184 | - Publisher.subscribe(self._set_projection_type, 'Set projection type') | 195 | + Publisher.subscribe(self._set_projection_type, "Set projection type") |
185 | 196 | ||
186 | - Publisher.subscribe(self._do_boolean_op, 'Do boolean operation') | 197 | + Publisher.subscribe(self._do_boolean_op, "Do boolean operation") |
187 | 198 | ||
188 | - Publisher.subscribe(self.OnExportMask,'Export mask to file') | 199 | + Publisher.subscribe(self.OnExportMask, "Export mask to file") |
189 | 200 | ||
190 | - Publisher.subscribe(self.OnCloseProject, 'Close project data') | 201 | + Publisher.subscribe(self.OnCloseProject, "Close project data") |
191 | 202 | ||
192 | - Publisher.subscribe(self.OnEnableStyle, 'Enable style') | ||
193 | - Publisher.subscribe(self.OnDisableStyle, 'Disable style') | ||
194 | - Publisher.subscribe(self.OnDisableActualStyle, 'Disable actual style') | 203 | + Publisher.subscribe(self.OnEnableStyle, "Enable style") |
204 | + Publisher.subscribe(self.OnDisableStyle, "Disable style") | ||
205 | + Publisher.subscribe(self.OnDisableActualStyle, "Disable actual style") | ||
195 | 206 | ||
196 | - Publisher.subscribe(self.OnRemoveMasks, 'Remove masks') | ||
197 | - Publisher.subscribe(self.OnDuplicateMasks, 'Duplicate masks') | ||
198 | - Publisher.subscribe(self.UpdateSlice3D,'Update slice 3D') | 207 | + Publisher.subscribe(self.OnRemoveMasks, "Remove masks") |
208 | + Publisher.subscribe(self.OnDuplicateMasks, "Duplicate masks") | ||
209 | + Publisher.subscribe(self.UpdateSlice3D, "Update slice 3D") | ||
199 | 210 | ||
200 | - Publisher.subscribe(self.OnFlipVolume, 'Flip volume') | ||
201 | - Publisher.subscribe(self.OnSwapVolumeAxes, 'Swap volume axes') | 211 | + Publisher.subscribe(self.OnFlipVolume, "Flip volume") |
212 | + Publisher.subscribe(self.OnSwapVolumeAxes, "Swap volume axes") | ||
202 | 213 | ||
203 | - Publisher.subscribe(self.__undo_edition, 'Undo edition') | ||
204 | - Publisher.subscribe(self.__redo_edition, 'Redo edition') | 214 | + Publisher.subscribe(self.__undo_edition, "Undo edition") |
215 | + Publisher.subscribe(self.__redo_edition, "Redo edition") | ||
205 | 216 | ||
206 | - Publisher.subscribe(self._fill_holes_auto, 'Fill holes automatically') | 217 | + Publisher.subscribe(self._fill_holes_auto, "Fill holes automatically") |
207 | 218 | ||
208 | - Publisher.subscribe(self._set_interpolation_method, 'Set interpolation method') | 219 | + Publisher.subscribe(self._set_interpolation_method, "Set interpolation method") |
209 | 220 | ||
210 | def GetMaxSliceNumber(self, orientation): | 221 | def GetMaxSliceNumber(self, orientation): |
211 | shape = self.matrix.shape | 222 | shape = self.matrix.shape |
212 | 223 | ||
213 | # Because matrix indexing starts with 0 so the last slice is the shape | 224 | # Because matrix indexing starts with 0 so the last slice is the shape |
214 | # minu 1. | 225 | # minu 1. |
215 | - if orientation == 'AXIAL': | 226 | + if orientation == "AXIAL": |
216 | return shape[0] - 1 | 227 | return shape[0] - 1 |
217 | - elif orientation == 'CORONAL': | 228 | + elif orientation == "CORONAL": |
218 | return shape[1] - 1 | 229 | return shape[1] - 1 |
219 | - elif orientation == 'SAGITAL': | 230 | + elif orientation == "SAGITAL": |
220 | return shape[2] - 1 | 231 | return shape[2] - 1 |
221 | 232 | ||
222 | def discard_all_buffers(self): | 233 | def discard_all_buffers(self): |
@@ -233,13 +244,13 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -233,13 +244,13 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
233 | # and discard from buffer all datas related to mask. | 244 | # and discard from buffer all datas related to mask. |
234 | if self.current_mask is not None and item == self.current_mask.index: | 245 | if self.current_mask is not None and item == self.current_mask.index: |
235 | self.current_mask = None | 246 | self.current_mask = None |
236 | - | 247 | + |
237 | for buffer_ in self.buffer_slices.values(): | 248 | for buffer_ in self.buffer_slices.values(): |
238 | buffer_.discard_vtk_mask() | 249 | buffer_.discard_vtk_mask() |
239 | buffer_.discard_mask() | 250 | buffer_.discard_mask() |
240 | 251 | ||
241 | - Publisher.sendMessage('Show mask', index=item, value=False) | ||
242 | - Publisher.sendMessage('Reload actual slice') | 252 | + Publisher.sendMessage("Show mask", index=item, value=False) |
253 | + Publisher.sendMessage("Reload actual slice") | ||
243 | 254 | ||
244 | def OnDuplicateMasks(self, mask_indexes): | 255 | def OnDuplicateMasks(self, mask_indexes): |
245 | proj = Project() | 256 | proj = Project() |
@@ -255,28 +266,28 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -255,28 +266,28 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
255 | self._add_mask_into_proj(copy_mask) | 266 | self._add_mask_into_proj(copy_mask) |
256 | 267 | ||
257 | def OnEnableStyle(self, style): | 268 | def OnEnableStyle(self, style): |
258 | - if (style in const.SLICE_STYLES): | 269 | + if style in const.SLICE_STYLES: |
259 | new_state = self.interaction_style.AddState(style) | 270 | new_state = self.interaction_style.AddState(style) |
260 | - Publisher.sendMessage('Set slice interaction style', style=new_state) | 271 | + Publisher.sendMessage("Set slice interaction style", style=new_state) |
261 | self.state = style | 272 | self.state = style |
262 | 273 | ||
263 | def OnDisableStyle(self, style): | 274 | def OnDisableStyle(self, style): |
264 | - if (style in const.SLICE_STYLES): | 275 | + if style in const.SLICE_STYLES: |
265 | new_state = self.interaction_style.RemoveState(style) | 276 | new_state = self.interaction_style.RemoveState(style) |
266 | - Publisher.sendMessage('Set slice interaction style', style=new_state) | 277 | + Publisher.sendMessage("Set slice interaction style", style=new_state) |
267 | 278 | ||
268 | - if (style == const.SLICE_STATE_EDITOR): | ||
269 | - Publisher.sendMessage('Set interactor default cursor') | 279 | + if style == const.SLICE_STATE_EDITOR: |
280 | + Publisher.sendMessage("Set interactor default cursor") | ||
270 | self.state = new_state | 281 | self.state = new_state |
271 | 282 | ||
272 | def OnDisableActualStyle(self): | 283 | def OnDisableActualStyle(self): |
273 | actual_state = self.interaction_style.GetActualState() | 284 | actual_state = self.interaction_style.GetActualState() |
274 | if actual_state != const.STATE_DEFAULT: | 285 | if actual_state != const.STATE_DEFAULT: |
275 | new_state = self.interaction_style.RemoveState(actual_state) | 286 | new_state = self.interaction_style.RemoveState(actual_state) |
276 | - Publisher.sendMessage('Set slice interaction style', style=new_state) | 287 | + Publisher.sendMessage("Set slice interaction style", style=new_state) |
277 | 288 | ||
278 | # if (actual_state == const.SLICE_STATE_EDITOR): | 289 | # if (actual_state == const.SLICE_STATE_EDITOR): |
279 | - # Publisher.sendMessage('Set interactor default cursor') | 290 | + # Publisher.sendMessage('Set interactor default cursor') |
280 | self.state = new_state | 291 | self.state = new_state |
281 | 292 | ||
282 | def OnCloseProject(self): | 293 | def OnCloseProject(self): |
@@ -285,7 +296,7 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -285,7 +296,7 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
285 | def CloseProject(self): | 296 | def CloseProject(self): |
286 | f = self._matrix.filename | 297 | f = self._matrix.filename |
287 | self._matrix._mmap.close() | 298 | self._matrix._mmap.close() |
288 | - self._matrix = None | 299 | + self._matrix = None |
289 | os.remove(f) | 300 | os.remove(f) |
290 | self.current_mask = None | 301 | self.current_mask = None |
291 | 302 | ||
@@ -299,7 +310,7 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -299,7 +310,7 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
299 | 310 | ||
300 | self.values = None | 311 | self.values = None |
301 | self.nodes = None | 312 | self.nodes = None |
302 | - self.from_= OTHER | 313 | + self.from_ = OTHER |
303 | self.state = const.STATE_DEFAULT | 314 | self.state = const.STATE_DEFAULT |
304 | 315 | ||
305 | self.number_of_colours = 256 | 316 | self.number_of_colours = 256 |
@@ -309,7 +320,7 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -309,7 +320,7 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
309 | 320 | ||
310 | self.interaction_style.Reset() | 321 | self.interaction_style.Reset() |
311 | 322 | ||
312 | - Publisher.sendMessage('Select first item from slice menu') | 323 | + Publisher.sendMessage("Select first item from slice menu") |
313 | 324 | ||
314 | def __set_current_mask_threshold_limits(self, threshold_range): | 325 | def __set_current_mask_threshold_limits(self, threshold_range): |
315 | thresh_min = threshold_range[0] | 326 | thresh_min = threshold_range[0] |
@@ -326,11 +337,11 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -326,11 +337,11 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
326 | self.create_new_mask(name=mask_name, threshold_range=thresh, colour=colour) | 337 | self.create_new_mask(name=mask_name, threshold_range=thresh, colour=colour) |
327 | self.SetMaskColour(self.current_mask.index, self.current_mask.colour) | 338 | self.SetMaskColour(self.current_mask.index, self.current_mask.colour) |
328 | self.SelectCurrentMask(self.current_mask.index) | 339 | self.SelectCurrentMask(self.current_mask.index) |
329 | - Publisher.sendMessage('Reload actual slice') | 340 | + Publisher.sendMessage("Reload actual slice") |
330 | 341 | ||
331 | def __select_current_mask(self, index): | 342 | def __select_current_mask(self, index): |
332 | self.SelectCurrentMask(index) | 343 | self.SelectCurrentMask(index) |
333 | - | 344 | + |
334 | def __set_current_mask_edition_threshold(self, threshold_range): | 345 | def __set_current_mask_edition_threshold(self, threshold_range): |
335 | if self.current_mask: | 346 | if self.current_mask: |
336 | index = self.current_mask.index | 347 | index = self.current_mask.index |
@@ -347,9 +358,12 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -347,9 +358,12 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
347 | to_reload = True | 358 | to_reload = True |
348 | for orientation in self.buffer_slices: | 359 | for orientation in self.buffer_slices: |
349 | self.buffer_slices[orientation].discard_vtk_mask() | 360 | self.buffer_slices[orientation].discard_vtk_mask() |
350 | - self.SetMaskThreshold(index, threshold_range, | ||
351 | - self.buffer_slices[orientation].index, | ||
352 | - orientation) | 361 | + self.SetMaskThreshold( |
362 | + index, | ||
363 | + threshold_range, | ||
364 | + self.buffer_slices[orientation].index, | ||
365 | + orientation, | ||
366 | + ) | ||
353 | 367 | ||
354 | # TODO: merge this code with apply_slice_buffer_to_mask | 368 | # TODO: merge this code with apply_slice_buffer_to_mask |
355 | b_mask = self.buffer_slices["AXIAL"].mask | 369 | b_mask = self.buffer_slices["AXIAL"].mask |
@@ -371,24 +385,27 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -371,24 +385,27 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
371 | self.current_mask.matrix[0, 0, n] = 1 | 385 | self.current_mask.matrix[0, 0, n] = 1 |
372 | 386 | ||
373 | if to_reload: | 387 | if to_reload: |
374 | - Publisher.sendMessage('Reload actual slice') | 388 | + Publisher.sendMessage("Reload actual slice") |
375 | 389 | ||
376 | def __set_current_mask_threshold_actual_slice(self, threshold_range): | 390 | def __set_current_mask_threshold_actual_slice(self, threshold_range): |
377 | index = self.current_mask.index | 391 | index = self.current_mask.index |
378 | for orientation in self.buffer_slices: | 392 | for orientation in self.buffer_slices: |
379 | self.buffer_slices[orientation].discard_vtk_mask() | 393 | self.buffer_slices[orientation].discard_vtk_mask() |
380 | - self.SetMaskThreshold(index, threshold_range, | ||
381 | - self.buffer_slices[orientation].index, | ||
382 | - orientation) | 394 | + self.SetMaskThreshold( |
395 | + index, | ||
396 | + threshold_range, | ||
397 | + self.buffer_slices[orientation].index, | ||
398 | + orientation, | ||
399 | + ) | ||
383 | self.num_gradient += 1 | 400 | self.num_gradient += 1 |
384 | 401 | ||
385 | - Publisher.sendMessage('Reload actual slice') | 402 | + Publisher.sendMessage("Reload actual slice") |
386 | 403 | ||
387 | def __set_current_mask_colour(self, colour): | 404 | def __set_current_mask_colour(self, colour): |
388 | # "if" is necessary because wx events are calling this before any mask | 405 | # "if" is necessary because wx events are calling this before any mask |
389 | # has been created | 406 | # has been created |
390 | if self.current_mask: | 407 | if self.current_mask: |
391 | - colour_vtk = [c/255.0 for c in colour] | 408 | + colour_vtk = [c / 255.0 for c in colour] |
392 | self.SetMaskColour(self.current_mask.index, colour_vtk) | 409 | self.SetMaskColour(self.current_mask.index, colour_vtk) |
393 | 410 | ||
394 | def __set_mask_name(self, index, name): | 411 | def __set_mask_name(self, index, name): |
@@ -400,23 +417,23 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -400,23 +417,23 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
400 | if self.current_mask: | 417 | if self.current_mask: |
401 | self.ShowMask(index, value) | 418 | self.ShowMask(index, value) |
402 | if not value: | 419 | if not value: |
403 | - Publisher.sendMessage('Select mask name in combo', index=-1) | 420 | + Publisher.sendMessage("Select mask name in combo", index=-1) |
404 | 421 | ||
405 | if self._type_projection != const.PROJECTION_NORMAL: | 422 | if self._type_projection != const.PROJECTION_NORMAL: |
406 | self.SetTypeProjection(const.PROJECTION_NORMAL) | 423 | self.SetTypeProjection(const.PROJECTION_NORMAL) |
407 | - Publisher.sendMessage('Reload actual slice') | 424 | + Publisher.sendMessage("Reload actual slice") |
408 | 425 | ||
409 | def __hide_current_mask(self): | 426 | def __hide_current_mask(self): |
410 | if self.current_mask: | 427 | if self.current_mask: |
411 | index = self.current_mask.index | 428 | index = self.current_mask.index |
412 | value = False | 429 | value = False |
413 | - Publisher.sendMessage('Show mask', index=index, value=value) | 430 | + Publisher.sendMessage("Show mask", index=index, value=value) |
414 | 431 | ||
415 | def __show_current_mask(self): | 432 | def __show_current_mask(self): |
416 | if self.current_mask: | 433 | if self.current_mask: |
417 | index = self.current_mask.index | 434 | index = self.current_mask.index |
418 | value = True | 435 | value = True |
419 | - Publisher.sendMessage('Show mask', index=index, value=value) | 436 | + Publisher.sendMessage("Show mask", index=index, value=value) |
420 | 437 | ||
421 | def __clean_current_mask(self): | 438 | def __clean_current_mask(self): |
422 | if self.current_mask: | 439 | if self.current_mask: |
@@ -433,25 +450,27 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -433,25 +450,27 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
433 | 450 | ||
434 | def __export_slice(self, filename): | 451 | def __export_slice(self, filename): |
435 | import h5py | 452 | import h5py |
436 | - f = h5py.File(filename, 'w') | ||
437 | - f['data'] = self.matrix | ||
438 | - f['spacing'] = self.spacing | 453 | + |
454 | + f = h5py.File(filename, "w") | ||
455 | + f["data"] = self.matrix | ||
456 | + f["spacing"] = self.spacing | ||
439 | f.flush() | 457 | f.flush() |
440 | f.close() | 458 | f.close() |
441 | 459 | ||
442 | def __export_actual_mask(self, filename): | 460 | def __export_actual_mask(self, filename): |
443 | import h5py | 461 | import h5py |
444 | - f = h5py.File(filename, 'w') | 462 | + |
463 | + f = h5py.File(filename, "w") | ||
445 | self.do_threshold_to_all_slices() | 464 | self.do_threshold_to_all_slices() |
446 | - f['data'] = self.current_mask.matrix[1:, 1:, 1:] | ||
447 | - f['spacing'] = self.spacing | 465 | + f["data"] = self.current_mask.matrix[1:, 1:, 1:] |
466 | + f["spacing"] = self.spacing | ||
448 | f.flush() | 467 | f.flush() |
449 | f.close() | 468 | f.close() |
450 | 469 | ||
451 | def create_temp_mask(self): | 470 | def create_temp_mask(self): |
452 | temp_file = tempfile.mktemp() | 471 | temp_file = tempfile.mktemp() |
453 | shape = self.matrix.shape | 472 | shape = self.matrix.shape |
454 | - matrix = np.memmap(temp_file, mode='w+', dtype='uint8', shape=shape) | 473 | + matrix = np.memmap(temp_file, mode="w+", dtype="uint8", shape=shape) |
455 | return temp_file, matrix | 474 | return temp_file, matrix |
456 | 475 | ||
457 | def edit_mask_pixel(self, operation, index, position, radius, orientation): | 476 | def edit_mask_pixel(self, operation, index, position, radius, orientation): |
@@ -459,32 +478,30 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -459,32 +478,30 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
459 | image = self.buffer_slices[orientation].image | 478 | image = self.buffer_slices[orientation].image |
460 | thresh_min, thresh_max = self.current_mask.edition_threshold_range | 479 | thresh_min, thresh_max = self.current_mask.edition_threshold_range |
461 | 480 | ||
462 | - print("Threshold", thresh_min, thresh_max) | ||
463 | - | ||
464 | - if hasattr(position, '__iter__'): | 481 | + if hasattr(position, "__iter__"): |
465 | px, py = position | 482 | px, py = position |
466 | - if orientation == 'AXIAL': | 483 | + if orientation == "AXIAL": |
467 | sx = self.spacing[0] | 484 | sx = self.spacing[0] |
468 | sy = self.spacing[1] | 485 | sy = self.spacing[1] |
469 | - elif orientation == 'CORONAL': | 486 | + elif orientation == "CORONAL": |
470 | sx = self.spacing[0] | 487 | sx = self.spacing[0] |
471 | sy = self.spacing[2] | 488 | sy = self.spacing[2] |
472 | - elif orientation == 'SAGITAL': | 489 | + elif orientation == "SAGITAL": |
473 | sx = self.spacing[2] | 490 | sx = self.spacing[2] |
474 | sy = self.spacing[1] | 491 | sy = self.spacing[1] |
475 | 492 | ||
476 | else: | 493 | else: |
477 | - if orientation == 'AXIAL': | 494 | + if orientation == "AXIAL": |
478 | sx = self.spacing[0] | 495 | sx = self.spacing[0] |
479 | sy = self.spacing[1] | 496 | sy = self.spacing[1] |
480 | py = position / mask.shape[1] | 497 | py = position / mask.shape[1] |
481 | px = position % mask.shape[1] | 498 | px = position % mask.shape[1] |
482 | - elif orientation == 'CORONAL': | 499 | + elif orientation == "CORONAL": |
483 | sx = self.spacing[0] | 500 | sx = self.spacing[0] |
484 | sy = self.spacing[2] | 501 | sy = self.spacing[2] |
485 | py = position / mask.shape[1] | 502 | py = position / mask.shape[1] |
486 | px = position % mask.shape[1] | 503 | px = position % mask.shape[1] |
487 | - elif orientation == 'SAGITAL': | 504 | + elif orientation == "SAGITAL": |
488 | sx = self.spacing[2] | 505 | sx = self.spacing[2] |
489 | sy = self.spacing[1] | 506 | sy = self.spacing[1] |
490 | py = position / mask.shape[1] | 507 | py = position / mask.shape[1] |
@@ -498,26 +515,26 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -498,26 +515,26 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
498 | yf = int(yi + index.shape[0]) | 515 | yf = int(yi + index.shape[0]) |
499 | 516 | ||
500 | if yi < 0: | 517 | if yi < 0: |
501 | - index = index[abs(yi):,:] | 518 | + index = index[abs(yi) :, :] |
502 | yi = 0 | 519 | yi = 0 |
503 | if yf > image.shape[0]: | 520 | if yf > image.shape[0]: |
504 | - index = index[:index.shape[0]-(yf-image.shape[0]), :] | 521 | + index = index[: index.shape[0] - (yf - image.shape[0]), :] |
505 | yf = image.shape[0] | 522 | yf = image.shape[0] |
506 | 523 | ||
507 | if xi < 0: | 524 | if xi < 0: |
508 | - index = index[:,abs(xi):] | 525 | + index = index[:, abs(xi) :] |
509 | xi = 0 | 526 | xi = 0 |
510 | if xf > image.shape[1]: | 527 | if xf > image.shape[1]: |
511 | - index = index[:,:index.shape[1]-(xf-image.shape[1])] | 528 | + index = index[:, : index.shape[1] - (xf - image.shape[1])] |
512 | xf = image.shape[1] | 529 | xf = image.shape[1] |
513 | 530 | ||
514 | # Verifying if the points is over the image array. | 531 | # Verifying if the points is over the image array. |
515 | - if (not 0 <= xi <= image.shape[1] and not 0 <= xf <= image.shape[1]) or \ | ||
516 | - (not 0 <= yi <= image.shape[0] and not 0 <= yf <= image.shape[0]): | 532 | + if (not 0 <= xi <= image.shape[1] and not 0 <= xf <= image.shape[1]) or ( |
533 | + not 0 <= yi <= image.shape[0] and not 0 <= yf <= image.shape[0] | ||
534 | + ): | ||
517 | return | 535 | return |
518 | 536 | ||
519 | - | ||
520 | - roi_m = mask[yi:yf,xi:xf] | 537 | + roi_m = mask[yi:yf, xi:xf] |
521 | roi_i = image[yi:yf, xi:xf] | 538 | roi_i = image[yi:yf, xi:xf] |
522 | 539 | ||
523 | # Checking if roi_i has at least one element. | 540 | # Checking if roi_i has at least one element. |
@@ -525,11 +542,13 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -525,11 +542,13 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
525 | if operation == const.BRUSH_THRESH: | 542 | if operation == const.BRUSH_THRESH: |
526 | # It's a trick to make points between threshold gets value 254 | 543 | # It's a trick to make points between threshold gets value 254 |
527 | # (1 * 253 + 1) and out ones gets value 1 (0 * 253 + 1). | 544 | # (1 * 253 + 1) and out ones gets value 1 (0 * 253 + 1). |
528 | - roi_m[index] = (((roi_i[index] >= thresh_min) | ||
529 | - & (roi_i[index] <= thresh_max)) * 253) + 1 | 545 | + roi_m[index] = ( |
546 | + ((roi_i[index] >= thresh_min) & (roi_i[index] <= thresh_max)) * 253 | ||
547 | + ) + 1 | ||
530 | elif operation == const.BRUSH_THRESH_ERASE: | 548 | elif operation == const.BRUSH_THRESH_ERASE: |
531 | - roi_m[index] = (((roi_i[index] < thresh_min) | ||
532 | - | (roi_i[index] > thresh_max)) * 253) + 1 | 549 | + roi_m[index] = ( |
550 | + ((roi_i[index] < thresh_min) | (roi_i[index] > thresh_max)) * 253 | ||
551 | + ) + 1 | ||
533 | elif operation == const.BRUSH_THRESH_ADD_ONLY: | 552 | elif operation == const.BRUSH_THRESH_ADD_ONLY: |
534 | roi_m[((index) & (roi_i >= thresh_min) & (roi_i <= thresh_max))] = 254 | 553 | roi_m[((index) & (roi_i >= thresh_min) & (roi_i <= thresh_max))] = 254 |
535 | elif operation == const.BRUSH_THRESH_ERASE_ONLY: | 554 | elif operation == const.BRUSH_THRESH_ERASE_ONLY: |
@@ -544,18 +563,22 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -544,18 +563,22 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
544 | session = ses.Session() | 563 | session = ses.Session() |
545 | session.ChangeProject() | 564 | session.ChangeProject() |
546 | 565 | ||
547 | - | ||
548 | - def GetSlices(self, orientation, slice_number, number_slices, | ||
549 | - inverted=False, border_size=1.0): | ||
550 | - if self.buffer_slices[orientation].index == slice_number and \ | ||
551 | - self._type_projection == const.PROJECTION_NORMAL: | 566 | + def GetSlices( |
567 | + self, orientation, slice_number, number_slices, inverted=False, border_size=1.0 | ||
568 | + ): | ||
569 | + if ( | ||
570 | + self.buffer_slices[orientation].index == slice_number | ||
571 | + and self._type_projection == const.PROJECTION_NORMAL | ||
572 | + ): | ||
552 | if self.buffer_slices[orientation].vtk_image: | 573 | if self.buffer_slices[orientation].vtk_image: |
553 | image = self.buffer_slices[orientation].vtk_image | 574 | image = self.buffer_slices[orientation].vtk_image |
554 | else: | 575 | else: |
555 | - n_image = self.get_image_slice(orientation, slice_number, | ||
556 | - number_slices, inverted, | ||
557 | - border_size) | ||
558 | - image = converters.to_vtk(n_image, self.spacing, slice_number, orientation) | 576 | + n_image = self.get_image_slice( |
577 | + orientation, slice_number, number_slices, inverted, border_size | ||
578 | + ) | ||
579 | + image = converters.to_vtk( | ||
580 | + n_image, self.spacing, slice_number, orientation | ||
581 | + ) | ||
559 | ww_wl_image = self.do_ww_wl(image) | 582 | ww_wl_image = self.do_ww_wl(image) |
560 | image = self.do_colour_image(ww_wl_image) | 583 | image = self.do_colour_image(ww_wl_image) |
561 | if self.current_mask and self.current_mask.is_shown: | 584 | if self.current_mask and self.current_mask.is_shown: |
@@ -567,7 +590,9 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -567,7 +590,9 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
567 | # Prints that during navigation causes delay in update | 590 | # Prints that during navigation causes delay in update |
568 | # print "Do not getting from buffer" | 591 | # print "Do not getting from buffer" |
569 | n_mask = self.get_mask_slice(orientation, slice_number) | 592 | n_mask = self.get_mask_slice(orientation, slice_number) |
570 | - mask = converters.to_vtk(n_mask, self.spacing, slice_number, orientation) | 593 | + mask = converters.to_vtk( |
594 | + n_mask, self.spacing, slice_number, orientation | ||
595 | + ) | ||
571 | mask = self.do_colour_mask(mask, self.opacity) | 596 | mask = self.do_colour_mask(mask, self.opacity) |
572 | self.buffer_slices[orientation].mask = n_mask | 597 | self.buffer_slices[orientation].mask = n_mask |
573 | final_image = self.do_blend(image, mask) | 598 | final_image = self.do_blend(image, mask) |
@@ -576,15 +601,18 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -576,15 +601,18 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
576 | final_image = image | 601 | final_image = image |
577 | self.buffer_slices[orientation].vtk_image = image | 602 | self.buffer_slices[orientation].vtk_image = image |
578 | else: | 603 | else: |
579 | - n_image = self.get_image_slice(orientation, slice_number, | ||
580 | - number_slices, inverted, border_size) | 604 | + n_image = self.get_image_slice( |
605 | + orientation, slice_number, number_slices, inverted, border_size | ||
606 | + ) | ||
581 | image = converters.to_vtk(n_image, self.spacing, slice_number, orientation) | 607 | image = converters.to_vtk(n_image, self.spacing, slice_number, orientation) |
582 | ww_wl_image = self.do_ww_wl(image) | 608 | ww_wl_image = self.do_ww_wl(image) |
583 | image = self.do_colour_image(ww_wl_image) | 609 | image = self.do_colour_image(ww_wl_image) |
584 | 610 | ||
585 | if self.current_mask and self.current_mask.is_shown: | 611 | if self.current_mask and self.current_mask.is_shown: |
586 | n_mask = self.get_mask_slice(orientation, slice_number) | 612 | n_mask = self.get_mask_slice(orientation, slice_number) |
587 | - mask = converters.to_vtk(n_mask, self.spacing, slice_number, orientation) | 613 | + mask = converters.to_vtk( |
614 | + n_mask, self.spacing, slice_number, orientation | ||
615 | + ) | ||
588 | mask = self.do_colour_mask(mask, self.opacity) | 616 | mask = self.do_colour_mask(mask, self.opacity) |
589 | final_image = self.do_blend(image, mask) | 617 | final_image = self.do_blend(image, mask) |
590 | else: | 618 | else: |
@@ -597,20 +625,34 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -597,20 +625,34 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
597 | self.buffer_slices[orientation].vtk_image = image | 625 | self.buffer_slices[orientation].vtk_image = image |
598 | self.buffer_slices[orientation].vtk_mask = mask | 626 | self.buffer_slices[orientation].vtk_mask = mask |
599 | 627 | ||
600 | - if self.to_show_aux == 'watershed' and self.current_mask.is_shown: | ||
601 | - m = self.get_aux_slice('watershed', orientation, slice_number) | 628 | + if self.to_show_aux == "watershed" and self.current_mask.is_shown: |
629 | + m = self.get_aux_slice("watershed", orientation, slice_number) | ||
602 | tmp_vimage = converters.to_vtk(m, self.spacing, slice_number, orientation) | 630 | tmp_vimage = converters.to_vtk(m, self.spacing, slice_number, orientation) |
603 | - cimage = self.do_custom_colour(tmp_vimage, {0: (0.0, 0.0, 0.0, 0.0), | ||
604 | - 1: (0.0, 1.0, 0.0, 1.0), | ||
605 | - 2: (1.0, 0.0, 0.0, 1.0)}) | 631 | + cimage = self.do_custom_colour( |
632 | + tmp_vimage, | ||
633 | + { | ||
634 | + 0: (0.0, 0.0, 0.0, 0.0), | ||
635 | + 1: (0.0, 1.0, 0.0, 1.0), | ||
636 | + 2: (1.0, 0.0, 0.0, 1.0), | ||
637 | + }, | ||
638 | + ) | ||
606 | final_image = self.do_blend(final_image, cimage) | 639 | final_image = self.do_blend(final_image, cimage) |
607 | elif self.to_show_aux and self.current_mask: | 640 | elif self.to_show_aux and self.current_mask: |
608 | m = self.get_aux_slice(self.to_show_aux, orientation, slice_number) | 641 | m = self.get_aux_slice(self.to_show_aux, orientation, slice_number) |
609 | tmp_vimage = converters.to_vtk(m, self.spacing, slice_number, orientation) | 642 | tmp_vimage = converters.to_vtk(m, self.spacing, slice_number, orientation) |
610 | - aux_image = self.do_custom_colour(tmp_vimage, {0: (0.0, 0.0, 0.0, 0.0), | ||
611 | - 1: (0.0, 0.0, 0.0, 0.0), | ||
612 | - 254: (1.0, 0.0, 0.0, 1.0), | ||
613 | - 255: (1.0, 0.0, 0.0, 1.0)}) | 643 | + try: |
644 | + colour_table = self.aux_matrices_colours[self.to_show_aux] | ||
645 | + except KeyError: | ||
646 | + colour_table = { | ||
647 | + 0: (0.0, 0.0, 0.0, 0.0), | ||
648 | + 1: (0.0, 0.0, 0.0, 0.0), | ||
649 | + 254: (1.0, 0.0, 0.0, 1.0), | ||
650 | + 255: (1.0, 0.0, 0.0, 1.0), | ||
651 | + } | ||
652 | + aux_image = self.do_custom_colour( | ||
653 | + tmp_vimage, | ||
654 | + colour_table | ||
655 | + ) | ||
614 | final_image = self.do_blend(final_image, aux_image) | 656 | final_image = self.do_blend(final_image, aux_image) |
615 | return final_image | 657 | return final_image |
616 | 658 | ||
@@ -644,11 +686,21 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -644,11 +686,21 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
644 | T1 = transformations.translation_matrix((cz, cy, cx)) | 686 | T1 = transformations.translation_matrix((cz, cy, cx)) |
645 | M = transformations.concatenate_matrices(T1, R.T, T0) | 687 | M = transformations.concatenate_matrices(T1, R.T, T0) |
646 | 688 | ||
647 | - | ||
648 | - if orientation == 'AXIAL': | ||
649 | - tmp_array = np.array(self.matrix[slice_number:slice_number + number_slices]) | 689 | + if orientation == "AXIAL": |
690 | + tmp_array = np.array( | ||
691 | + self.matrix[slice_number : slice_number + number_slices] | ||
692 | + ) | ||
650 | if np.any(self.q_orientation[1::]): | 693 | if np.any(self.q_orientation[1::]): |
651 | - transforms.apply_view_matrix_transform(self.matrix, self.spacing, M, slice_number, orientation, self.interp_method, self.matrix.min(), tmp_array) | 694 | + transforms.apply_view_matrix_transform( |
695 | + self.matrix, | ||
696 | + self.spacing, | ||
697 | + M, | ||
698 | + slice_number, | ||
699 | + orientation, | ||
700 | + self.interp_method, | ||
701 | + self.matrix.min(), | ||
702 | + tmp_array, | ||
703 | + ) | ||
652 | if self._type_projection == const.PROJECTION_NORMAL: | 704 | if self._type_projection == const.PROJECTION_NORMAL: |
653 | n_image = tmp_array.reshape(dy, dx) | 705 | n_image = tmp_array.reshape(dy, dx) |
654 | else: | 706 | else: |
@@ -662,48 +714,89 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -662,48 +714,89 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
662 | elif self._type_projection == const.PROJECTION_MeanIP: | 714 | elif self._type_projection == const.PROJECTION_MeanIP: |
663 | n_image = np.array(tmp_array).mean(0) | 715 | n_image = np.array(tmp_array).mean(0) |
664 | elif self._type_projection == const.PROJECTION_LMIP: | 716 | elif self._type_projection == const.PROJECTION_LMIP: |
665 | - n_image = np.empty(shape=(tmp_array.shape[1], | ||
666 | - tmp_array.shape[2]), | ||
667 | - dtype=tmp_array.dtype) | ||
668 | - mips.lmip(tmp_array, 0, self.window_level, self.window_level, n_image) | 717 | + n_image = np.empty( |
718 | + shape=(tmp_array.shape[1], tmp_array.shape[2]), | ||
719 | + dtype=tmp_array.dtype, | ||
720 | + ) | ||
721 | + mips.lmip( | ||
722 | + tmp_array, 0, self.window_level, self.window_level, n_image | ||
723 | + ) | ||
669 | elif self._type_projection == const.PROJECTION_MIDA: | 724 | elif self._type_projection == const.PROJECTION_MIDA: |
670 | - n_image = np.empty(shape=(tmp_array.shape[1], | ||
671 | - tmp_array.shape[2]), | ||
672 | - dtype=tmp_array.dtype) | ||
673 | - mips.mida(tmp_array, 0, self.window_level, self.window_level, n_image) | 725 | + n_image = np.empty( |
726 | + shape=(tmp_array.shape[1], tmp_array.shape[2]), | ||
727 | + dtype=tmp_array.dtype, | ||
728 | + ) | ||
729 | + mips.mida( | ||
730 | + tmp_array, 0, self.window_level, self.window_level, n_image | ||
731 | + ) | ||
674 | elif self._type_projection == const.PROJECTION_CONTOUR_MIP: | 732 | elif self._type_projection == const.PROJECTION_CONTOUR_MIP: |
675 | - n_image = np.empty(shape=(tmp_array.shape[1], | ||
676 | - tmp_array.shape[2]), | ||
677 | - dtype=tmp_array.dtype) | ||
678 | - mips.fast_countour_mip(tmp_array, border_size, 0, self.window_level, | ||
679 | - self.window_level, 0, n_image) | 733 | + n_image = np.empty( |
734 | + shape=(tmp_array.shape[1], tmp_array.shape[2]), | ||
735 | + dtype=tmp_array.dtype, | ||
736 | + ) | ||
737 | + mips.fast_countour_mip( | ||
738 | + tmp_array, | ||
739 | + border_size, | ||
740 | + 0, | ||
741 | + self.window_level, | ||
742 | + self.window_level, | ||
743 | + 0, | ||
744 | + n_image, | ||
745 | + ) | ||
680 | elif self._type_projection == const.PROJECTION_CONTOUR_LMIP: | 746 | elif self._type_projection == const.PROJECTION_CONTOUR_LMIP: |
681 | - n_image = np.empty(shape=(tmp_array.shape[1], | ||
682 | - tmp_array.shape[2]), | ||
683 | - dtype=tmp_array.dtype) | ||
684 | - mips.fast_countour_mip(tmp_array, border_size, 0, self.window_level, | ||
685 | - self.window_level, 1, n_image) | 747 | + n_image = np.empty( |
748 | + shape=(tmp_array.shape[1], tmp_array.shape[2]), | ||
749 | + dtype=tmp_array.dtype, | ||
750 | + ) | ||
751 | + mips.fast_countour_mip( | ||
752 | + tmp_array, | ||
753 | + border_size, | ||
754 | + 0, | ||
755 | + self.window_level, | ||
756 | + self.window_level, | ||
757 | + 1, | ||
758 | + n_image, | ||
759 | + ) | ||
686 | elif self._type_projection == const.PROJECTION_CONTOUR_MIDA: | 760 | elif self._type_projection == const.PROJECTION_CONTOUR_MIDA: |
687 | - n_image = np.empty(shape=(tmp_array.shape[1], | ||
688 | - tmp_array.shape[2]), | ||
689 | - dtype=tmp_array.dtype) | ||
690 | - mips.fast_countour_mip(tmp_array, border_size, 0, self.window_level, | ||
691 | - self.window_level, 2, n_image) | 761 | + n_image = np.empty( |
762 | + shape=(tmp_array.shape[1], tmp_array.shape[2]), | ||
763 | + dtype=tmp_array.dtype, | ||
764 | + ) | ||
765 | + mips.fast_countour_mip( | ||
766 | + tmp_array, | ||
767 | + border_size, | ||
768 | + 0, | ||
769 | + self.window_level, | ||
770 | + self.window_level, | ||
771 | + 2, | ||
772 | + n_image, | ||
773 | + ) | ||
692 | else: | 774 | else: |
693 | n_image = np.array(self.matrix[slice_number]) | 775 | n_image = np.array(self.matrix[slice_number]) |
694 | 776 | ||
695 | - elif orientation == 'CORONAL': | ||
696 | - tmp_array = np.array(self.matrix[:, slice_number: slice_number + number_slices, :]) | 777 | + elif orientation == "CORONAL": |
778 | + tmp_array = np.array( | ||
779 | + self.matrix[:, slice_number : slice_number + number_slices, :] | ||
780 | + ) | ||
697 | if np.any(self.q_orientation[1::]): | 781 | if np.any(self.q_orientation[1::]): |
698 | - transforms.apply_view_matrix_transform(self.matrix, self.spacing, M, slice_number, orientation, self.interp_method, self.matrix.min(), tmp_array) | 782 | + transforms.apply_view_matrix_transform( |
783 | + self.matrix, | ||
784 | + self.spacing, | ||
785 | + M, | ||
786 | + slice_number, | ||
787 | + orientation, | ||
788 | + self.interp_method, | ||
789 | + self.matrix.min(), | ||
790 | + tmp_array, | ||
791 | + ) | ||
699 | 792 | ||
700 | if self._type_projection == const.PROJECTION_NORMAL: | 793 | if self._type_projection == const.PROJECTION_NORMAL: |
701 | n_image = tmp_array.reshape(dz, dx) | 794 | n_image = tmp_array.reshape(dz, dx) |
702 | else: | 795 | else: |
703 | - #if slice_number == 0: | ||
704 | - #slice_number = 1 | ||
705 | - #if slice_number - number_slices < 0: | ||
706 | - #number_slices = slice_number | 796 | + # if slice_number == 0: |
797 | + # slice_number = 1 | ||
798 | + # if slice_number - number_slices < 0: | ||
799 | + # number_slices = slice_number | ||
707 | if inverted: | 800 | if inverted: |
708 | tmp_array = tmp_array[:, ::-1, :] | 801 | tmp_array = tmp_array[:, ::-1, :] |
709 | if self._type_projection == const.PROJECTION_MaxIP: | 802 | if self._type_projection == const.PROJECTION_MaxIP: |
@@ -713,39 +806,80 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -713,39 +806,80 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
713 | elif self._type_projection == const.PROJECTION_MeanIP: | 806 | elif self._type_projection == const.PROJECTION_MeanIP: |
714 | n_image = np.array(tmp_array).mean(1) | 807 | n_image = np.array(tmp_array).mean(1) |
715 | elif self._type_projection == const.PROJECTION_LMIP: | 808 | elif self._type_projection == const.PROJECTION_LMIP: |
716 | - n_image = np.empty(shape=(tmp_array.shape[0], | ||
717 | - tmp_array.shape[2]), | ||
718 | - dtype=tmp_array.dtype) | ||
719 | - mips.lmip(tmp_array, 1, self.window_level, self.window_level, n_image) | 809 | + n_image = np.empty( |
810 | + shape=(tmp_array.shape[0], tmp_array.shape[2]), | ||
811 | + dtype=tmp_array.dtype, | ||
812 | + ) | ||
813 | + mips.lmip( | ||
814 | + tmp_array, 1, self.window_level, self.window_level, n_image | ||
815 | + ) | ||
720 | elif self._type_projection == const.PROJECTION_MIDA: | 816 | elif self._type_projection == const.PROJECTION_MIDA: |
721 | - n_image = np.empty(shape=(tmp_array.shape[0], | ||
722 | - tmp_array.shape[2]), | ||
723 | - dtype=tmp_array.dtype) | ||
724 | - mips.mida(tmp_array, 1, self.window_level, self.window_level, n_image) | 817 | + n_image = np.empty( |
818 | + shape=(tmp_array.shape[0], tmp_array.shape[2]), | ||
819 | + dtype=tmp_array.dtype, | ||
820 | + ) | ||
821 | + mips.mida( | ||
822 | + tmp_array, 1, self.window_level, self.window_level, n_image | ||
823 | + ) | ||
725 | elif self._type_projection == const.PROJECTION_CONTOUR_MIP: | 824 | elif self._type_projection == const.PROJECTION_CONTOUR_MIP: |
726 | - n_image = np.empty(shape=(tmp_array.shape[0], | ||
727 | - tmp_array.shape[2]), | ||
728 | - dtype=tmp_array.dtype) | ||
729 | - mips.fast_countour_mip(tmp_array, border_size, 1, self.window_level, | ||
730 | - self.window_level, 0, n_image) | 825 | + n_image = np.empty( |
826 | + shape=(tmp_array.shape[0], tmp_array.shape[2]), | ||
827 | + dtype=tmp_array.dtype, | ||
828 | + ) | ||
829 | + mips.fast_countour_mip( | ||
830 | + tmp_array, | ||
831 | + border_size, | ||
832 | + 1, | ||
833 | + self.window_level, | ||
834 | + self.window_level, | ||
835 | + 0, | ||
836 | + n_image, | ||
837 | + ) | ||
731 | elif self._type_projection == const.PROJECTION_CONTOUR_LMIP: | 838 | elif self._type_projection == const.PROJECTION_CONTOUR_LMIP: |
732 | - n_image = np.empty(shape=(tmp_array.shape[0], | ||
733 | - tmp_array.shape[2]), | ||
734 | - dtype=tmp_array.dtype) | ||
735 | - mips.fast_countour_mip(tmp_array, border_size, 1, self.window_level, | ||
736 | - self.window_level, 1, n_image) | 839 | + n_image = np.empty( |
840 | + shape=(tmp_array.shape[0], tmp_array.shape[2]), | ||
841 | + dtype=tmp_array.dtype, | ||
842 | + ) | ||
843 | + mips.fast_countour_mip( | ||
844 | + tmp_array, | ||
845 | + border_size, | ||
846 | + 1, | ||
847 | + self.window_level, | ||
848 | + self.window_level, | ||
849 | + 1, | ||
850 | + n_image, | ||
851 | + ) | ||
737 | elif self._type_projection == const.PROJECTION_CONTOUR_MIDA: | 852 | elif self._type_projection == const.PROJECTION_CONTOUR_MIDA: |
738 | - n_image = np.empty(shape=(tmp_array.shape[0], | ||
739 | - tmp_array.shape[2]), | ||
740 | - dtype=tmp_array.dtype) | ||
741 | - mips.fast_countour_mip(tmp_array, border_size, 1, self.window_level, | ||
742 | - self.window_level, 2, n_image) | 853 | + n_image = np.empty( |
854 | + shape=(tmp_array.shape[0], tmp_array.shape[2]), | ||
855 | + dtype=tmp_array.dtype, | ||
856 | + ) | ||
857 | + mips.fast_countour_mip( | ||
858 | + tmp_array, | ||
859 | + border_size, | ||
860 | + 1, | ||
861 | + self.window_level, | ||
862 | + self.window_level, | ||
863 | + 2, | ||
864 | + n_image, | ||
865 | + ) | ||
743 | else: | 866 | else: |
744 | n_image = np.array(self.matrix[:, slice_number, :]) | 867 | n_image = np.array(self.matrix[:, slice_number, :]) |
745 | - elif orientation == 'SAGITAL': | ||
746 | - tmp_array = np.array(self.matrix[:, :, slice_number: slice_number + number_slices]) | 868 | + elif orientation == "SAGITAL": |
869 | + tmp_array = np.array( | ||
870 | + self.matrix[:, :, slice_number : slice_number + number_slices] | ||
871 | + ) | ||
747 | if np.any(self.q_orientation[1::]): | 872 | if np.any(self.q_orientation[1::]): |
748 | - transforms.apply_view_matrix_transform(self.matrix, self.spacing, M, slice_number, orientation, self.interp_method, self.matrix.min(), tmp_array) | 873 | + transforms.apply_view_matrix_transform( |
874 | + self.matrix, | ||
875 | + self.spacing, | ||
876 | + M, | ||
877 | + slice_number, | ||
878 | + orientation, | ||
879 | + self.interp_method, | ||
880 | + self.matrix.min(), | ||
881 | + tmp_array, | ||
882 | + ) | ||
749 | 883 | ||
750 | if self._type_projection == const.PROJECTION_NORMAL: | 884 | if self._type_projection == const.PROJECTION_NORMAL: |
751 | n_image = tmp_array.reshape(dz, dy) | 885 | n_image = tmp_array.reshape(dz, dy) |
@@ -759,34 +893,64 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -759,34 +893,64 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
759 | elif self._type_projection == const.PROJECTION_MeanIP: | 893 | elif self._type_projection == const.PROJECTION_MeanIP: |
760 | n_image = np.array(tmp_array).mean(2) | 894 | n_image = np.array(tmp_array).mean(2) |
761 | elif self._type_projection == const.PROJECTION_LMIP: | 895 | elif self._type_projection == const.PROJECTION_LMIP: |
762 | - n_image = np.empty(shape=(tmp_array.shape[0], | ||
763 | - tmp_array.shape[1]), | ||
764 | - dtype=tmp_array.dtype) | ||
765 | - mips.lmip(tmp_array, 2, self.window_level, self.window_level, n_image) | 896 | + n_image = np.empty( |
897 | + shape=(tmp_array.shape[0], tmp_array.shape[1]), | ||
898 | + dtype=tmp_array.dtype, | ||
899 | + ) | ||
900 | + mips.lmip( | ||
901 | + tmp_array, 2, self.window_level, self.window_level, n_image | ||
902 | + ) | ||
766 | elif self._type_projection == const.PROJECTION_MIDA: | 903 | elif self._type_projection == const.PROJECTION_MIDA: |
767 | - n_image = np.empty(shape=(tmp_array.shape[0], | ||
768 | - tmp_array.shape[1]), | ||
769 | - dtype=tmp_array.dtype) | ||
770 | - mips.mida(tmp_array, 2, self.window_level, self.window_level, n_image) | 904 | + n_image = np.empty( |
905 | + shape=(tmp_array.shape[0], tmp_array.shape[1]), | ||
906 | + dtype=tmp_array.dtype, | ||
907 | + ) | ||
908 | + mips.mida( | ||
909 | + tmp_array, 2, self.window_level, self.window_level, n_image | ||
910 | + ) | ||
771 | 911 | ||
772 | elif self._type_projection == const.PROJECTION_CONTOUR_MIP: | 912 | elif self._type_projection == const.PROJECTION_CONTOUR_MIP: |
773 | - n_image = np.empty(shape=(tmp_array.shape[0], | ||
774 | - tmp_array.shape[1]), | ||
775 | - dtype=tmp_array.dtype) | ||
776 | - mips.fast_countour_mip(tmp_array, border_size, 2, self.window_level, | ||
777 | - self.window_level, 0, n_image) | 913 | + n_image = np.empty( |
914 | + shape=(tmp_array.shape[0], tmp_array.shape[1]), | ||
915 | + dtype=tmp_array.dtype, | ||
916 | + ) | ||
917 | + mips.fast_countour_mip( | ||
918 | + tmp_array, | ||
919 | + border_size, | ||
920 | + 2, | ||
921 | + self.window_level, | ||
922 | + self.window_level, | ||
923 | + 0, | ||
924 | + n_image, | ||
925 | + ) | ||
778 | elif self._type_projection == const.PROJECTION_CONTOUR_LMIP: | 926 | elif self._type_projection == const.PROJECTION_CONTOUR_LMIP: |
779 | - n_image = np.empty(shape=(tmp_array.shape[0], | ||
780 | - tmp_array.shape[1]), | ||
781 | - dtype=tmp_array.dtype) | ||
782 | - mips.fast_countour_mip(tmp_array, border_size, 2, self.window_level, | ||
783 | - self.window_level, 1, n_image) | 927 | + n_image = np.empty( |
928 | + shape=(tmp_array.shape[0], tmp_array.shape[1]), | ||
929 | + dtype=tmp_array.dtype, | ||
930 | + ) | ||
931 | + mips.fast_countour_mip( | ||
932 | + tmp_array, | ||
933 | + border_size, | ||
934 | + 2, | ||
935 | + self.window_level, | ||
936 | + self.window_level, | ||
937 | + 1, | ||
938 | + n_image, | ||
939 | + ) | ||
784 | elif self._type_projection == const.PROJECTION_CONTOUR_MIDA: | 940 | elif self._type_projection == const.PROJECTION_CONTOUR_MIDA: |
785 | - n_image = np.empty(shape=(tmp_array.shape[0], | ||
786 | - tmp_array.shape[1]), | ||
787 | - dtype=tmp_array.dtype) | ||
788 | - mips.fast_countour_mip(tmp_array, border_size, 2, self.window_level, | ||
789 | - self.window_level, 2, n_image) | 941 | + n_image = np.empty( |
942 | + shape=(tmp_array.shape[0], tmp_array.shape[1]), | ||
943 | + dtype=tmp_array.dtype, | ||
944 | + ) | ||
945 | + mips.fast_countour_mip( | ||
946 | + tmp_array, | ||
947 | + border_size, | ||
948 | + 2, | ||
949 | + self.window_level, | ||
950 | + self.window_level, | ||
951 | + 2, | ||
952 | + n_image, | ||
953 | + ) | ||
790 | else: | 954 | else: |
791 | n_image = np.array(self.matrix[:, :, slice_number]) | 955 | n_image = np.array(self.matrix[:, :, slice_number]) |
792 | 956 | ||
@@ -794,63 +958,71 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -794,63 +958,71 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
794 | return n_image | 958 | return n_image |
795 | 959 | ||
796 | def get_mask_slice(self, orientation, slice_number): | 960 | def get_mask_slice(self, orientation, slice_number): |
797 | - """ | 961 | + """ |
798 | It gets the from actual mask the given slice from given orientation | 962 | It gets the from actual mask the given slice from given orientation |
799 | """ | 963 | """ |
800 | # It's necessary because the first position for each dimension from | 964 | # It's necessary because the first position for each dimension from |
801 | # mask matrix is used as flags to control if the mask in the | 965 | # mask matrix is used as flags to control if the mask in the |
802 | # slice_number position has been generated. | 966 | # slice_number position has been generated. |
803 | - if self.buffer_slices[orientation].index == slice_number \ | ||
804 | - and self.buffer_slices[orientation].mask is not None: | 967 | + if ( |
968 | + self.buffer_slices[orientation].index == slice_number | ||
969 | + and self.buffer_slices[orientation].mask is not None | ||
970 | + ): | ||
805 | return self.buffer_slices[orientation].mask | 971 | return self.buffer_slices[orientation].mask |
806 | n = slice_number + 1 | 972 | n = slice_number + 1 |
807 | - if orientation == 'AXIAL': | 973 | + if orientation == "AXIAL": |
808 | if self.current_mask.matrix[n, 0, 0] == 0: | 974 | if self.current_mask.matrix[n, 0, 0] == 0: |
809 | mask = self.current_mask.matrix[n, 1:, 1:] | 975 | mask = self.current_mask.matrix[n, 1:, 1:] |
810 | - mask[:] = self.do_threshold_to_a_slice(self.get_image_slice(orientation, | ||
811 | - slice_number), | ||
812 | - mask) | 976 | + mask[:] = self.do_threshold_to_a_slice( |
977 | + self.get_image_slice(orientation, slice_number), mask | ||
978 | + ) | ||
813 | self.current_mask.matrix[n, 0, 0] = 1 | 979 | self.current_mask.matrix[n, 0, 0] = 1 |
814 | - n_mask = np.array(self.current_mask.matrix[n, 1:, 1:], | ||
815 | - dtype=self.current_mask.matrix.dtype) | 980 | + n_mask = np.array( |
981 | + self.current_mask.matrix[n, 1:, 1:], | ||
982 | + dtype=self.current_mask.matrix.dtype, | ||
983 | + ) | ||
816 | 984 | ||
817 | - elif orientation == 'CORONAL': | 985 | + elif orientation == "CORONAL": |
818 | if self.current_mask.matrix[0, n, 0] == 0: | 986 | if self.current_mask.matrix[0, n, 0] == 0: |
819 | mask = self.current_mask.matrix[1:, n, 1:] | 987 | mask = self.current_mask.matrix[1:, n, 1:] |
820 | - mask[:] = self.do_threshold_to_a_slice(self.get_image_slice(orientation, | ||
821 | - slice_number), | ||
822 | - mask) | 988 | + mask[:] = self.do_threshold_to_a_slice( |
989 | + self.get_image_slice(orientation, slice_number), mask | ||
990 | + ) | ||
823 | self.current_mask.matrix[0, n, 0] = 1 | 991 | self.current_mask.matrix[0, n, 0] = 1 |
824 | - n_mask = np.array(self.current_mask.matrix[1:, n, 1:], | ||
825 | - dtype=self.current_mask.matrix.dtype) | 992 | + n_mask = np.array( |
993 | + self.current_mask.matrix[1:, n, 1:], | ||
994 | + dtype=self.current_mask.matrix.dtype, | ||
995 | + ) | ||
826 | 996 | ||
827 | - elif orientation == 'SAGITAL': | 997 | + elif orientation == "SAGITAL": |
828 | if self.current_mask.matrix[0, 0, n] == 0: | 998 | if self.current_mask.matrix[0, 0, n] == 0: |
829 | mask = self.current_mask.matrix[1:, 1:, n] | 999 | mask = self.current_mask.matrix[1:, 1:, n] |
830 | - mask[:] = self.do_threshold_to_a_slice(self.get_image_slice(orientation, | ||
831 | - slice_number), | ||
832 | - mask) | 1000 | + mask[:] = self.do_threshold_to_a_slice( |
1001 | + self.get_image_slice(orientation, slice_number), mask | ||
1002 | + ) | ||
833 | self.current_mask.matrix[0, 0, n] = 1 | 1003 | self.current_mask.matrix[0, 0, n] = 1 |
834 | - n_mask = np.array(self.current_mask.matrix[1:, 1:, n], | ||
835 | - dtype=self.current_mask.matrix.dtype) | 1004 | + n_mask = np.array( |
1005 | + self.current_mask.matrix[1:, 1:, n], | ||
1006 | + dtype=self.current_mask.matrix.dtype, | ||
1007 | + ) | ||
836 | 1008 | ||
837 | return n_mask | 1009 | return n_mask |
838 | 1010 | ||
839 | def get_aux_slice(self, name, orientation, n): | 1011 | def get_aux_slice(self, name, orientation, n): |
840 | m = self.aux_matrices[name] | 1012 | m = self.aux_matrices[name] |
841 | - if orientation == 'AXIAL': | 1013 | + if orientation == "AXIAL": |
842 | return np.array(m[n]) | 1014 | return np.array(m[n]) |
843 | - elif orientation == 'CORONAL': | 1015 | + elif orientation == "CORONAL": |
844 | return np.array(m[:, n, :]) | 1016 | return np.array(m[:, n, :]) |
845 | - elif orientation == 'SAGITAL': | 1017 | + elif orientation == "SAGITAL": |
846 | return np.array(m[:, :, n]) | 1018 | return np.array(m[:, :, n]) |
847 | 1019 | ||
848 | def GetNumberOfSlices(self, orientation): | 1020 | def GetNumberOfSlices(self, orientation): |
849 | - if orientation == 'AXIAL': | 1021 | + if orientation == "AXIAL": |
850 | return self.matrix.shape[0] | 1022 | return self.matrix.shape[0] |
851 | - elif orientation == 'CORONAL': | 1023 | + elif orientation == "CORONAL": |
852 | return self.matrix.shape[1] | 1024 | return self.matrix.shape[1] |
853 | - elif orientation == 'SAGITAL': | 1025 | + elif orientation == "SAGITAL": |
854 | return self.matrix.shape[2] | 1026 | return self.matrix.shape[2] |
855 | 1027 | ||
856 | def SetMaskColour(self, index, colour, update=True): | 1028 | def SetMaskColour(self, index, colour, update=True): |
@@ -858,16 +1030,17 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -858,16 +1030,17 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
858 | proj = Project() | 1030 | proj = Project() |
859 | proj.mask_dict[index].colour = colour | 1031 | proj.mask_dict[index].colour = colour |
860 | 1032 | ||
861 | - (r,g,b) = colour[:3] | ||
862 | - colour_wx = [r*255, g*255, b*255] | ||
863 | - Publisher.sendMessage('Change mask colour in notebook', | ||
864 | - index=index, colour=(r,g,b)) | ||
865 | - Publisher.sendMessage('Set GUI items colour', colour=colour_wx) | 1033 | + (r, g, b) = colour[:3] |
1034 | + colour_wx = [r * 255, g * 255, b * 255] | ||
1035 | + Publisher.sendMessage( | ||
1036 | + "Change mask colour in notebook", index=index, colour=(r, g, b) | ||
1037 | + ) | ||
1038 | + Publisher.sendMessage("Set GUI items colour", colour=colour_wx) | ||
866 | if update: | 1039 | if update: |
867 | # Updating mask colour on vtkimagedata. | 1040 | # Updating mask colour on vtkimagedata. |
868 | for buffer_ in self.buffer_slices.values(): | 1041 | for buffer_ in self.buffer_slices.values(): |
869 | buffer_.discard_vtk_mask() | 1042 | buffer_.discard_vtk_mask() |
870 | - Publisher.sendMessage('Reload actual slice') | 1043 | + Publisher.sendMessage("Reload actual slice") |
871 | 1044 | ||
872 | session = ses.Session() | 1045 | session = ses.Session() |
873 | session.ChangeProject() | 1046 | session.ChangeProject() |
@@ -885,8 +1058,9 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -885,8 +1058,9 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
885 | proj = Project() | 1058 | proj = Project() |
886 | proj.mask_dict[index].edition_threshold_range = threshold_range | 1059 | proj.mask_dict[index].edition_threshold_range = threshold_range |
887 | 1060 | ||
888 | - def SetMaskThreshold(self, index, threshold_range, slice_number=None, | ||
889 | - orientation=None): | 1061 | + def SetMaskThreshold( |
1062 | + self, index, threshold_range, slice_number=None, orientation=None | ||
1063 | + ): | ||
890 | """ | 1064 | """ |
891 | Set a mask threshold range given its index and tuple of min and max | 1065 | Set a mask threshold range given its index and tuple of min and max |
892 | threshold values. | 1066 | threshold values. |
@@ -905,19 +1079,23 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -905,19 +1079,23 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
905 | m[slice_ < thresh_min] = 0 | 1079 | m[slice_ < thresh_min] = 0 |
906 | m[slice_ > thresh_max] = 0 | 1080 | m[slice_ > thresh_max] = 0 |
907 | m[m == 1] = 255 | 1081 | m[m == 1] = 255 |
908 | - self.current_mask.matrix[n+1, 1:, 1:] = m | 1082 | + self.current_mask.matrix[n + 1, 1:, 1:] = m |
909 | else: | 1083 | else: |
910 | slice_ = self.buffer_slices[orientation].image | 1084 | slice_ = self.buffer_slices[orientation].image |
911 | if slice_ is not None: | 1085 | if slice_ is not None: |
912 | - self.buffer_slices[orientation].mask = (255 * ((slice_ >= thresh_min) & (slice_ <= thresh_max))).astype('uint8') | 1086 | + self.buffer_slices[orientation].mask = ( |
1087 | + 255 * ((slice_ >= thresh_min) & (slice_ <= thresh_max)) | ||
1088 | + ).astype("uint8") | ||
913 | 1089 | ||
914 | # Update viewer | 1090 | # Update viewer |
915 | - #Publisher.sendMessage('Update slice viewer') | 1091 | + # Publisher.sendMessage('Update slice viewer') |
916 | 1092 | ||
917 | # Update data notebook (GUI) | 1093 | # Update data notebook (GUI) |
918 | - Publisher.sendMessage('Set mask threshold in notebook', | ||
919 | - index=self.current_mask.index, | ||
920 | - threshold_range=self.current_mask.threshold_range) | 1094 | + Publisher.sendMessage( |
1095 | + "Set mask threshold in notebook", | ||
1096 | + index=self.current_mask.index, | ||
1097 | + threshold_range=self.current_mask.threshold_range, | ||
1098 | + ) | ||
921 | else: | 1099 | else: |
922 | proj = Project() | 1100 | proj = Project() |
923 | proj.mask_dict[index].threshold_range = threshold_range | 1101 | proj.mask_dict[index].threshold_range = threshold_range |
@@ -932,15 +1110,18 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -932,15 +1110,18 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
932 | proj.mask_dict[index].on_show() | 1110 | proj.mask_dict[index].on_show() |
933 | 1111 | ||
934 | if value: | 1112 | if value: |
935 | - threshold_range = proj.mask_dict[index].edition_threshold_range | ||
936 | - Publisher.sendMessage('Set edition threshold gui', threshold_range=threshold_range) | 1113 | + threshold_range = proj.mask_dict[index].threshold_range |
1114 | + Publisher.sendMessage( | ||
1115 | + "Set edition threshold gui", threshold_range=threshold_range | ||
1116 | + ) | ||
937 | 1117 | ||
938 | - if (index == self.current_mask.index): | 1118 | + if index == self.current_mask.index: |
939 | for buffer_ in self.buffer_slices.values(): | 1119 | for buffer_ in self.buffer_slices.values(): |
940 | buffer_.discard_vtk_mask() | 1120 | buffer_.discard_vtk_mask() |
941 | buffer_.discard_mask() | 1121 | buffer_.discard_mask() |
942 | - Publisher.sendMessage('Reload actual slice') | ||
943 | - #--------------------------------------------------------------------------- | 1122 | + Publisher.sendMessage("Reload actual slice") |
1123 | + | ||
1124 | + # --------------------------------------------------------------------------- | ||
944 | 1125 | ||
945 | def SelectCurrentMask(self, index): | 1126 | def SelectCurrentMask(self, index): |
946 | "Insert mask data, based on given index, into pipeline." | 1127 | "Insert mask data, based on given index, into pipeline." |
@@ -952,27 +1133,37 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -952,27 +1133,37 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
952 | colour = future_mask.colour | 1133 | colour = future_mask.colour |
953 | self.SetMaskColour(index, colour, update=False) | 1134 | self.SetMaskColour(index, colour, update=False) |
954 | 1135 | ||
955 | - self.buffer_slices = {"AXIAL": SliceBuffer(), | ||
956 | - "CORONAL": SliceBuffer(), | ||
957 | - "SAGITAL": SliceBuffer()} | ||
958 | - | ||
959 | - Publisher.sendMessage('Set mask threshold in notebook', | ||
960 | - index=index, | ||
961 | - threshold_range=self.current_mask.threshold_range) | ||
962 | - Publisher.sendMessage('Set threshold values in gradient', | ||
963 | - threshold_range=self.current_mask.threshold_range) | ||
964 | - Publisher.sendMessage('Select mask name in combo', index=index) | ||
965 | - Publisher.sendMessage('Update slice viewer') | ||
966 | - #--------------------------------------------------------------------------- | 1136 | + self.buffer_slices = { |
1137 | + "AXIAL": SliceBuffer(), | ||
1138 | + "CORONAL": SliceBuffer(), | ||
1139 | + "SAGITAL": SliceBuffer(), | ||
1140 | + } | ||
1141 | + | ||
1142 | + Publisher.sendMessage( | ||
1143 | + "Set mask threshold in notebook", | ||
1144 | + index=index, | ||
1145 | + threshold_range=self.current_mask.threshold_range, | ||
1146 | + ) | ||
1147 | + Publisher.sendMessage( | ||
1148 | + "Set threshold values in gradient", | ||
1149 | + threshold_range=self.current_mask.threshold_range, | ||
1150 | + ) | ||
1151 | + Publisher.sendMessage("Select mask name in combo", index=index) | ||
1152 | + Publisher.sendMessage("Update slice viewer") | ||
1153 | + | ||
1154 | + # --------------------------------------------------------------------------- | ||
967 | 1155 | ||
968 | def CreateSurfaceFromIndex(self, surface_parameters): | 1156 | def CreateSurfaceFromIndex(self, surface_parameters): |
969 | proj = Project() | 1157 | proj = Project() |
970 | - mask = proj.mask_dict[surface_parameters['options']['index']] | 1158 | + mask = proj.mask_dict[surface_parameters["options"]["index"]] |
971 | 1159 | ||
972 | self.do_threshold_to_all_slices(mask) | 1160 | self.do_threshold_to_all_slices(mask) |
973 | - Publisher.sendMessage('Create surface', | ||
974 | - slice_=self, mask=mask, | ||
975 | - surface_parameters=surface_parameters) | 1161 | + Publisher.sendMessage( |
1162 | + "Create surface", | ||
1163 | + slice_=self, | ||
1164 | + mask=mask, | ||
1165 | + surface_parameters=surface_parameters, | ||
1166 | + ) | ||
976 | 1167 | ||
977 | def GetOutput(self): | 1168 | def GetOutput(self): |
978 | return self.blend_filter.GetOutput() | 1169 | return self.blend_filter.GetOutput() |
@@ -986,69 +1177,73 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -986,69 +1177,73 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
986 | def SetTypeProjection(self, tprojection): | 1177 | def SetTypeProjection(self, tprojection): |
987 | if self._type_projection != tprojection: | 1178 | if self._type_projection != tprojection: |
988 | if self._type_projection == const.PROJECTION_NORMAL: | 1179 | if self._type_projection == const.PROJECTION_NORMAL: |
989 | - Publisher.sendMessage('Hide current mask') | 1180 | + Publisher.sendMessage("Hide current mask") |
990 | 1181 | ||
991 | if tprojection == const.PROJECTION_NORMAL: | 1182 | if tprojection == const.PROJECTION_NORMAL: |
992 | - Publisher.sendMessage('Show MIP interface', flag=False) | 1183 | + Publisher.sendMessage("Show MIP interface", flag=False) |
993 | else: | 1184 | else: |
994 | - Publisher.sendMessage('Show MIP interface', flag=True) | 1185 | + Publisher.sendMessage("Show MIP interface", flag=True) |
995 | 1186 | ||
996 | self._type_projection = tprojection | 1187 | self._type_projection = tprojection |
997 | for buffer_ in self.buffer_slices.values(): | 1188 | for buffer_ in self.buffer_slices.values(): |
998 | buffer_.discard_buffer() | 1189 | buffer_.discard_buffer() |
999 | 1190 | ||
1000 | - Publisher.sendMessage('Check projection menu', projection_id=tprojection) | 1191 | + Publisher.sendMessage("Check projection menu", projection_id=tprojection) |
1001 | 1192 | ||
1002 | def SetInterpolationMethod(self, interp_method): | 1193 | def SetInterpolationMethod(self, interp_method): |
1003 | if self.interp_method != interp_method: | 1194 | if self.interp_method != interp_method: |
1004 | self.interp_method = interp_method | 1195 | self.interp_method = interp_method |
1005 | for buffer_ in self.buffer_slices.values(): | 1196 | for buffer_ in self.buffer_slices.values(): |
1006 | buffer_.discard_buffer() | 1197 | buffer_.discard_buffer() |
1007 | - Publisher.sendMessage('Reload actual slice') | 1198 | + Publisher.sendMessage("Reload actual slice") |
1008 | 1199 | ||
1009 | def UpdateWindowLevelBackground(self, window, level): | 1200 | def UpdateWindowLevelBackground(self, window, level): |
1010 | self.window_width = window | 1201 | self.window_width = window |
1011 | self.window_level = level | 1202 | self.window_level = level |
1012 | 1203 | ||
1013 | for buffer_ in self.buffer_slices.values(): | 1204 | for buffer_ in self.buffer_slices.values(): |
1014 | - if self._type_projection in (const.PROJECTION_NORMAL, | ||
1015 | - const.PROJECTION_MaxIP, | ||
1016 | - const.PROJECTION_MinIP, | ||
1017 | - const.PROJECTION_MeanIP, | ||
1018 | - const.PROJECTION_LMIP): | 1205 | + if self._type_projection in ( |
1206 | + const.PROJECTION_NORMAL, | ||
1207 | + const.PROJECTION_MaxIP, | ||
1208 | + const.PROJECTION_MinIP, | ||
1209 | + const.PROJECTION_MeanIP, | ||
1210 | + const.PROJECTION_LMIP, | ||
1211 | + ): | ||
1019 | buffer_.discard_vtk_image() | 1212 | buffer_.discard_vtk_image() |
1020 | else: | 1213 | else: |
1021 | buffer_.discard_buffer() | 1214 | buffer_.discard_buffer() |
1022 | 1215 | ||
1023 | - Publisher.sendMessage('Reload actual slice') | 1216 | + Publisher.sendMessage("Reload actual slice") |
1024 | 1217 | ||
1025 | def UpdateColourTableBackground(self, values): | 1218 | def UpdateColourTableBackground(self, values): |
1026 | - self.from_= OTHER | ||
1027 | - self.number_of_colours= values[0] | 1219 | + self.from_ = OTHER |
1220 | + self.number_of_colours = values[0] | ||
1028 | self.saturation_range = values[1] | 1221 | self.saturation_range = values[1] |
1029 | self.hue_range = values[2] | 1222 | self.hue_range = values[2] |
1030 | self.value_range = values[3] | 1223 | self.value_range = values[3] |
1031 | for buffer_ in self.buffer_slices.values(): | 1224 | for buffer_ in self.buffer_slices.values(): |
1032 | buffer_.discard_vtk_image() | 1225 | buffer_.discard_vtk_image() |
1033 | - Publisher.sendMessage('Reload actual slice') | 1226 | + Publisher.sendMessage("Reload actual slice") |
1034 | 1227 | ||
1035 | def UpdateColourTableBackgroundPlist(self, values): | 1228 | def UpdateColourTableBackgroundPlist(self, values): |
1036 | self.values = values | 1229 | self.values = values |
1037 | - self.from_= PLIST | 1230 | + self.from_ = PLIST |
1038 | for buffer_ in self.buffer_slices.values(): | 1231 | for buffer_ in self.buffer_slices.values(): |
1039 | buffer_.discard_vtk_image() | 1232 | buffer_.discard_vtk_image() |
1040 | 1233 | ||
1041 | - Publisher.sendMessage('Reload actual slice') | 1234 | + Publisher.sendMessage("Reload actual slice") |
1042 | 1235 | ||
1043 | def UpdateColourTableBackgroundWidget(self, nodes): | 1236 | def UpdateColourTableBackgroundWidget(self, nodes): |
1044 | self.nodes = nodes | 1237 | self.nodes = nodes |
1045 | - self.from_= WIDGET | 1238 | + self.from_ = WIDGET |
1046 | for buffer_ in self.buffer_slices.values(): | 1239 | for buffer_ in self.buffer_slices.values(): |
1047 | - if self._type_projection in (const.PROJECTION_NORMAL, | ||
1048 | - const.PROJECTION_MaxIP, | ||
1049 | - const.PROJECTION_MinIP, | ||
1050 | - const.PROJECTION_MeanIP, | ||
1051 | - const.PROJECTION_LMIP): | 1240 | + if self._type_projection in ( |
1241 | + const.PROJECTION_NORMAL, | ||
1242 | + const.PROJECTION_MaxIP, | ||
1243 | + const.PROJECTION_MinIP, | ||
1244 | + const.PROJECTION_MeanIP, | ||
1245 | + const.PROJECTION_LMIP, | ||
1246 | + ): | ||
1052 | buffer_.discard_vtk_image() | 1247 | buffer_.discard_vtk_image() |
1053 | else: | 1248 | else: |
1054 | buffer_.discard_buffer() | 1249 | buffer_.discard_buffer() |
@@ -1060,34 +1255,37 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1060,34 +1255,37 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1060 | self.window_width = pn - p0 | 1255 | self.window_width = pn - p0 |
1061 | self.window_level = (pn + p0) / 2 | 1256 | self.window_level = (pn + p0) / 2 |
1062 | 1257 | ||
1063 | - Publisher.sendMessage('Reload actual slice') | 1258 | + Publisher.sendMessage("Reload actual slice") |
1064 | 1259 | ||
1065 | def UpdateSlice3D(self, widget, orientation): | 1260 | def UpdateSlice3D(self, widget, orientation): |
1066 | img = self.buffer_slices[orientation].vtk_image | 1261 | img = self.buffer_slices[orientation].vtk_image |
1067 | original_orientation = Project().original_orientation | 1262 | original_orientation = Project().original_orientation |
1068 | cast = vtk.vtkImageCast() | 1263 | cast = vtk.vtkImageCast() |
1069 | cast.SetInputData(img) | 1264 | cast.SetInputData(img) |
1070 | - cast.SetOutputScalarTypeToDouble() | 1265 | + cast.SetOutputScalarTypeToDouble() |
1071 | cast.ClampOverflowOn() | 1266 | cast.ClampOverflowOn() |
1072 | cast.Update() | 1267 | cast.Update() |
1073 | 1268 | ||
1074 | - #if (original_orientation == const.AXIAL): | 1269 | + # if (original_orientation == const.AXIAL): |
1075 | flip = vtk.vtkImageFlip() | 1270 | flip = vtk.vtkImageFlip() |
1076 | flip.SetInputConnection(cast.GetOutputPort()) | 1271 | flip.SetInputConnection(cast.GetOutputPort()) |
1077 | flip.SetFilteredAxis(1) | 1272 | flip.SetFilteredAxis(1) |
1078 | flip.FlipAboutOriginOn() | 1273 | flip.FlipAboutOriginOn() |
1079 | flip.Update() | 1274 | flip.Update() |
1080 | widget.SetInputConnection(flip.GetOutputPort()) | 1275 | widget.SetInputConnection(flip.GetOutputPort()) |
1081 | - #else: | ||
1082 | - #widget.SetInput(cast.GetOutput()) | ||
1083 | - | ||
1084 | - def create_new_mask(self, name=None, | ||
1085 | - colour=None, | ||
1086 | - opacity=None, | ||
1087 | - threshold_range=None, | ||
1088 | - edition_threshold_range=None, | ||
1089 | - add_to_project=True, | ||
1090 | - show=True): | 1276 | + # else: |
1277 | + # widget.SetInput(cast.GetOutput()) | ||
1278 | + | ||
1279 | + def create_new_mask( | ||
1280 | + self, | ||
1281 | + name=None, | ||
1282 | + colour=None, | ||
1283 | + opacity=None, | ||
1284 | + threshold_range=None, | ||
1285 | + edition_threshold_range=None, | ||
1286 | + add_to_project=True, | ||
1287 | + show=True, | ||
1288 | + ): | ||
1091 | """ | 1289 | """ |
1092 | Creates a new mask and add it to project. | 1290 | Creates a new mask and add it to project. |
1093 | 1291 | ||
@@ -1126,7 +1324,6 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1126,7 +1324,6 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1126 | 1324 | ||
1127 | return future_mask | 1325 | return future_mask |
1128 | 1326 | ||
1129 | - | ||
1130 | def _add_mask_into_proj(self, mask, show=True): | 1327 | def _add_mask_into_proj(self, mask, show=True): |
1131 | """ | 1328 | """ |
1132 | Insert a new mask into project and retrieve its index. | 1329 | Insert a new mask into project and retrieve its index. |
@@ -1140,14 +1337,13 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1140,14 +1337,13 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1140 | mask.index = index | 1337 | mask.index = index |
1141 | 1338 | ||
1142 | ## update gui related to mask | 1339 | ## update gui related to mask |
1143 | - Publisher.sendMessage('Add mask', | ||
1144 | - mask=mask) | 1340 | + Publisher.sendMessage("Add mask", mask=mask) |
1145 | 1341 | ||
1146 | if show: | 1342 | if show: |
1147 | self.current_mask = mask | 1343 | self.current_mask = mask |
1148 | - Publisher.sendMessage('Show mask', index=mask.index, value=True) | ||
1149 | - Publisher.sendMessage('Change mask selected', index=mask.index) | ||
1150 | - Publisher.sendMessage('Update slice viewer') | 1344 | + Publisher.sendMessage("Show mask", index=mask.index, value=True) |
1345 | + Publisher.sendMessage("Change mask selected", index=mask.index) | ||
1346 | + Publisher.sendMessage("Update slice viewer") | ||
1151 | 1347 | ||
1152 | def do_ww_wl(self, image): | 1348 | def do_ww_wl(self, image): |
1153 | if self.from_ == PLIST: | 1349 | if self.from_ == PLIST: |
@@ -1158,7 +1354,7 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1158,7 +1354,7 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1158 | 1354 | ||
1159 | i = 0 | 1355 | i = 0 |
1160 | for r, g, b in self.values: | 1356 | for r, g, b in self.values: |
1161 | - lut.SetTableValue(i, r/255.0, g/255.0, b/255.0, 1.0) | 1357 | + lut.SetTableValue(i, r / 255.0, g / 255.0, b / 255.0, 1.0) |
1162 | i += 1 | 1358 | i += 1 |
1163 | 1359 | ||
1164 | colorer = vtk.vtkImageMapToColors() | 1360 | colorer = vtk.vtkImageMapToColors() |
@@ -1167,11 +1363,11 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1167,11 +1363,11 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1167 | colorer.SetOutputFormatToRGB() | 1363 | colorer.SetOutputFormatToRGB() |
1168 | colorer.Update() | 1364 | colorer.Update() |
1169 | elif self.from_ == WIDGET: | 1365 | elif self.from_ == WIDGET: |
1170 | - lut = vtk.vtkColorTransferFunction() | 1366 | + lut = vtk.vtkColorTransferFunction() |
1171 | 1367 | ||
1172 | for n in self.nodes: | 1368 | for n in self.nodes: |
1173 | r, g, b = n.colour | 1369 | r, g, b = n.colour |
1174 | - lut.AddRGBPoint(n.value, r/255.0, g/255.0, b/255.0) | 1370 | + lut.AddRGBPoint(n.value, r / 255.0, g / 255.0, b / 255.0) |
1175 | 1371 | ||
1176 | lut.Build() | 1372 | lut.Build() |
1177 | 1373 | ||
@@ -1191,7 +1387,7 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1191,7 +1387,7 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1191 | return colorer.GetOutput() | 1387 | return colorer.GetOutput() |
1192 | 1388 | ||
1193 | def _update_wwwl_widget_nodes(self, ww, wl): | 1389 | def _update_wwwl_widget_nodes(self, ww, wl): |
1194 | - if self.from_ == WIDGET: | 1390 | + if self.from_ == WIDGET: |
1195 | knodes = sorted(self.nodes) | 1391 | knodes = sorted(self.nodes) |
1196 | 1392 | ||
1197 | p1 = knodes[0] | 1393 | p1 = knodes[0] |
@@ -1217,7 +1413,7 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1217,7 +1413,7 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1217 | node.value += shiftWW * factor | 1413 | node.value += shiftWW * factor |
1218 | 1414 | ||
1219 | def do_threshold_to_a_slice(self, slice_matrix, mask, threshold=None): | 1415 | def do_threshold_to_a_slice(self, slice_matrix, mask, threshold=None): |
1220 | - """ | 1416 | + """ |
1221 | Based on the current threshold bounds generates a threshold mask to | 1417 | Based on the current threshold bounds generates a threshold mask to |
1222 | given slice_matrix. | 1418 | given slice_matrix. |
1223 | """ | 1419 | """ |
@@ -1226,12 +1422,12 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1226,12 +1422,12 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1226 | else: | 1422 | else: |
1227 | thresh_min, thresh_max = self.current_mask.threshold_range | 1423 | thresh_min, thresh_max = self.current_mask.threshold_range |
1228 | 1424 | ||
1229 | - m = (((slice_matrix >= thresh_min) & (slice_matrix <= thresh_max)) * 255) | 1425 | + m = ((slice_matrix >= thresh_min) & (slice_matrix <= thresh_max)) * 255 |
1230 | m[mask == 1] = 1 | 1426 | m[mask == 1] = 1 |
1231 | m[mask == 2] = 2 | 1427 | m[mask == 2] = 2 |
1232 | m[mask == 253] = 253 | 1428 | m[mask == 253] = 253 |
1233 | m[mask == 254] = 254 | 1429 | m[mask == 254] = 254 |
1234 | - return m.astype('uint8') | 1430 | + return m.astype("uint8") |
1235 | 1431 | ||
1236 | def do_threshold_to_all_slices(self, mask=None): | 1432 | def do_threshold_to_all_slices(self, mask=None): |
1237 | """ | 1433 | """ |
@@ -1246,7 +1442,9 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1246,7 +1442,9 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1246 | for n in range(1, mask.matrix.shape[0]): | 1442 | for n in range(1, mask.matrix.shape[0]): |
1247 | if mask.matrix[n, 0, 0] == 0: | 1443 | if mask.matrix[n, 0, 0] == 0: |
1248 | m = mask.matrix[n, 1:, 1:] | 1444 | m = mask.matrix[n, 1:, 1:] |
1249 | - mask.matrix[n, 1:, 1:] = self.do_threshold_to_a_slice(self.matrix[n-1], m, mask.threshold_range) | 1445 | + mask.matrix[n, 1:, 1:] = self.do_threshold_to_a_slice( |
1446 | + self.matrix[n - 1], m, mask.threshold_range | ||
1447 | + ) | ||
1250 | 1448 | ||
1251 | mask.matrix.flush() | 1449 | mask.matrix.flush() |
1252 | 1450 | ||
@@ -1318,7 +1516,7 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1318,7 +1516,7 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1318 | lut_mask.SetNumberOfTableValues(ncolours) | 1516 | lut_mask.SetNumberOfTableValues(ncolours) |
1319 | 1517 | ||
1320 | for v in map_colours: | 1518 | for v in map_colours: |
1321 | - r,g, b,a = map_colours[v] | 1519 | + r, g, b, a = map_colours[v] |
1322 | lut_mask.SetTableValue(v, r, g, b, a) | 1520 | lut_mask.SetTableValue(v, r, g, b, a) |
1323 | 1521 | ||
1324 | lut_mask.SetRampToLinear() | 1522 | lut_mask.SetRampToLinear() |
@@ -1352,13 +1550,13 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1352,13 +1550,13 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1352 | def _do_boolean_op(self, operation, mask1, mask2): | 1550 | def _do_boolean_op(self, operation, mask1, mask2): |
1353 | self.do_boolean_op(operation, mask1, mask2) | 1551 | self.do_boolean_op(operation, mask1, mask2) |
1354 | 1552 | ||
1355 | - | ||
1356 | def do_boolean_op(self, op, m1, m2): | 1553 | def do_boolean_op(self, op, m1, m2): |
1357 | - name_ops = {const.BOOLEAN_UNION: _(u"Union"), | ||
1358 | - const.BOOLEAN_DIFF: _(u"Diff"), | ||
1359 | - const.BOOLEAN_AND: _(u"Intersection"), | ||
1360 | - const.BOOLEAN_XOR: _(u"XOR")} | ||
1361 | - | 1554 | + name_ops = { |
1555 | + const.BOOLEAN_UNION: _(u"Union"), | ||
1556 | + const.BOOLEAN_DIFF: _(u"Diff"), | ||
1557 | + const.BOOLEAN_AND: _(u"Intersection"), | ||
1558 | + const.BOOLEAN_XOR: _(u"XOR"), | ||
1559 | + } | ||
1362 | 1560 | ||
1363 | name = u"%s_%s_%s" % (name_ops[op], m1.name, m2.name) | 1561 | name = u"%s_%s_%s" % (name_ops[op], m1.name, m2.name) |
1364 | proj = Project() | 1562 | proj = Project() |
@@ -1406,32 +1604,32 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1406,32 +1604,32 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1406 | index = self.buffer_slices[orientation].index | 1604 | index = self.buffer_slices[orientation].index |
1407 | 1605 | ||
1408 | # TODO: Voltar a usar marcacao na mascara | 1606 | # TODO: Voltar a usar marcacao na mascara |
1409 | - if orientation == 'AXIAL': | ||
1410 | - #if self.current_mask.matrix[index+1, 0, 0] != 2: | ||
1411 | - #self.current_mask.save_history(index, orientation, | ||
1412 | - #self.current_mask.matrix[index+1,1:,1:], | ||
1413 | - #clean=True) | ||
1414 | - p_mask = self.current_mask.matrix[index+1,1:,1:].copy() | ||
1415 | - self.current_mask.matrix[index+1,1:,1:] = b_mask | ||
1416 | - self.current_mask.matrix[index+1, 0, 0] = 2 | ||
1417 | - | ||
1418 | - elif orientation == 'CORONAL': | ||
1419 | - #if self.current_mask.matrix[0, index+1, 0] != 2: | ||
1420 | - #self.current_mask.save_history(index, orientation, | ||
1421 | - #self.current_mask.matrix[1:, index+1, 1:], | ||
1422 | - #clean=True) | ||
1423 | - p_mask = self.current_mask.matrix[1:, index+1, 1:].copy() | ||
1424 | - self.current_mask.matrix[1:, index+1, 1:] = b_mask | ||
1425 | - self.current_mask.matrix[0, index+1, 0] = 2 | ||
1426 | - | ||
1427 | - elif orientation == 'SAGITAL': | ||
1428 | - #if self.current_mask.matrix[0, 0, index+1] != 2: | ||
1429 | - #self.current_mask.save_history(index, orientation, | ||
1430 | - #self.current_mask.matrix[1:, 1:, index+1], | ||
1431 | - #clean=True) | ||
1432 | - p_mask = self.current_mask.matrix[1:, 1:, index+1].copy() | ||
1433 | - self.current_mask.matrix[1:, 1:, index+1] = b_mask | ||
1434 | - self.current_mask.matrix[0, 0, index+1] = 2 | 1607 | + if orientation == "AXIAL": |
1608 | + # if self.current_mask.matrix[index+1, 0, 0] != 2: | ||
1609 | + # self.current_mask.save_history(index, orientation, | ||
1610 | + # self.current_mask.matrix[index+1,1:,1:], | ||
1611 | + # clean=True) | ||
1612 | + p_mask = self.current_mask.matrix[index + 1, 1:, 1:].copy() | ||
1613 | + self.current_mask.matrix[index + 1, 1:, 1:] = b_mask | ||
1614 | + self.current_mask.matrix[index + 1, 0, 0] = 2 | ||
1615 | + | ||
1616 | + elif orientation == "CORONAL": | ||
1617 | + # if self.current_mask.matrix[0, index+1, 0] != 2: | ||
1618 | + # self.current_mask.save_history(index, orientation, | ||
1619 | + # self.current_mask.matrix[1:, index+1, 1:], | ||
1620 | + # clean=True) | ||
1621 | + p_mask = self.current_mask.matrix[1:, index + 1, 1:].copy() | ||
1622 | + self.current_mask.matrix[1:, index + 1, 1:] = b_mask | ||
1623 | + self.current_mask.matrix[0, index + 1, 0] = 2 | ||
1624 | + | ||
1625 | + elif orientation == "SAGITAL": | ||
1626 | + # if self.current_mask.matrix[0, 0, index+1] != 2: | ||
1627 | + # self.current_mask.save_history(index, orientation, | ||
1628 | + # self.current_mask.matrix[1:, 1:, index+1], | ||
1629 | + # clean=True) | ||
1630 | + p_mask = self.current_mask.matrix[1:, 1:, index + 1].copy() | ||
1631 | + self.current_mask.matrix[1:, 1:, index + 1] = b_mask | ||
1632 | + self.current_mask.matrix[0, 0, index + 1] = 2 | ||
1435 | 1633 | ||
1436 | self.current_mask.save_history(index, orientation, b_mask, p_mask) | 1634 | self.current_mask.save_history(index, orientation, b_mask, p_mask) |
1437 | self.current_mask.was_edited = True | 1635 | self.current_mask.was_edited = True |
@@ -1440,11 +1638,13 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1440,11 +1638,13 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1440 | if o != orientation: | 1638 | if o != orientation: |
1441 | self.buffer_slices[o].discard_mask() | 1639 | self.buffer_slices[o].discard_mask() |
1442 | self.buffer_slices[o].discard_vtk_mask() | 1640 | self.buffer_slices[o].discard_vtk_mask() |
1443 | - Publisher.sendMessage('Reload actual slice') | 1641 | + Publisher.sendMessage("Reload actual slice") |
1444 | 1642 | ||
1445 | def apply_reorientation(self): | 1643 | def apply_reorientation(self): |
1446 | temp_file = tempfile.mktemp() | 1644 | temp_file = tempfile.mktemp() |
1447 | - mcopy = np.memmap(temp_file, shape=self.matrix.shape, dtype=self.matrix.dtype, mode='w+') | 1645 | + mcopy = np.memmap( |
1646 | + temp_file, shape=self.matrix.shape, dtype=self.matrix.dtype, mode="w+" | ||
1647 | + ) | ||
1448 | mcopy[:] = self.matrix | 1648 | mcopy[:] = self.matrix |
1449 | 1649 | ||
1450 | cx, cy, cz = self.center | 1650 | cx, cy, cz = self.center |
@@ -1453,13 +1653,24 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1453,13 +1653,24 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1453 | T1 = transformations.translation_matrix((cz, cy, cx)) | 1653 | T1 = transformations.translation_matrix((cz, cy, cx)) |
1454 | M = transformations.concatenate_matrices(T1, R.T, T0) | 1654 | M = transformations.concatenate_matrices(T1, R.T, T0) |
1455 | 1655 | ||
1456 | - transforms.apply_view_matrix_transform(mcopy, self.spacing, M, 0, 'AXIAL', self.interp_method, mcopy.min(), self.matrix) | 1656 | + transforms.apply_view_matrix_transform( |
1657 | + mcopy, | ||
1658 | + self.spacing, | ||
1659 | + M, | ||
1660 | + 0, | ||
1661 | + "AXIAL", | ||
1662 | + self.interp_method, | ||
1663 | + mcopy.min(), | ||
1664 | + self.matrix, | ||
1665 | + ) | ||
1457 | 1666 | ||
1458 | del mcopy | 1667 | del mcopy |
1459 | os.remove(temp_file) | 1668 | os.remove(temp_file) |
1460 | 1669 | ||
1461 | self.q_orientation = np.array((1, 0, 0, 0)) | 1670 | self.q_orientation = np.array((1, 0, 0, 0)) |
1462 | - self.center = [(s * d/2.0) for (d, s) in zip(self.matrix.shape[::-1], self.spacing)] | 1671 | + self.center = [ |
1672 | + (s * d / 2.0) for (d, s) in zip(self.matrix.shape[::-1], self.spacing) | ||
1673 | + ] | ||
1463 | 1674 | ||
1464 | self.__clean_current_mask() | 1675 | self.__clean_current_mask() |
1465 | if self.current_mask: | 1676 | if self.current_mask: |
@@ -1469,35 +1680,39 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1469,35 +1680,39 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1469 | for o in self.buffer_slices: | 1680 | for o in self.buffer_slices: |
1470 | self.buffer_slices[o].discard_buffer() | 1681 | self.buffer_slices[o].discard_buffer() |
1471 | 1682 | ||
1472 | - Publisher.sendMessage('Reload actual slice') | 1683 | + Publisher.sendMessage("Reload actual slice") |
1473 | 1684 | ||
1474 | def __undo_edition(self): | 1685 | def __undo_edition(self): |
1475 | buffer_slices = self.buffer_slices | 1686 | buffer_slices = self.buffer_slices |
1476 | - actual_slices = {"AXIAL": buffer_slices["AXIAL"].index, | ||
1477 | - "CORONAL": buffer_slices["CORONAL"].index, | ||
1478 | - "SAGITAL": buffer_slices["SAGITAL"].index, | ||
1479 | - "VOLUME": 0} | 1687 | + actual_slices = { |
1688 | + "AXIAL": buffer_slices["AXIAL"].index, | ||
1689 | + "CORONAL": buffer_slices["CORONAL"].index, | ||
1690 | + "SAGITAL": buffer_slices["SAGITAL"].index, | ||
1691 | + "VOLUME": 0, | ||
1692 | + } | ||
1480 | self.current_mask.undo_history(actual_slices) | 1693 | self.current_mask.undo_history(actual_slices) |
1481 | for o in self.buffer_slices: | 1694 | for o in self.buffer_slices: |
1482 | self.buffer_slices[o].discard_mask() | 1695 | self.buffer_slices[o].discard_mask() |
1483 | self.buffer_slices[o].discard_vtk_mask() | 1696 | self.buffer_slices[o].discard_vtk_mask() |
1484 | - Publisher.sendMessage('Reload actual slice') | 1697 | + Publisher.sendMessage("Reload actual slice") |
1485 | 1698 | ||
1486 | def __redo_edition(self): | 1699 | def __redo_edition(self): |
1487 | buffer_slices = self.buffer_slices | 1700 | buffer_slices = self.buffer_slices |
1488 | - actual_slices = {"AXIAL": buffer_slices["AXIAL"].index, | ||
1489 | - "CORONAL": buffer_slices["CORONAL"].index, | ||
1490 | - "SAGITAL": buffer_slices["SAGITAL"].index, | ||
1491 | - "VOLUME": 0} | 1701 | + actual_slices = { |
1702 | + "AXIAL": buffer_slices["AXIAL"].index, | ||
1703 | + "CORONAL": buffer_slices["CORONAL"].index, | ||
1704 | + "SAGITAL": buffer_slices["SAGITAL"].index, | ||
1705 | + "VOLUME": 0, | ||
1706 | + } | ||
1492 | self.current_mask.redo_history(actual_slices) | 1707 | self.current_mask.redo_history(actual_slices) |
1493 | for o in self.buffer_slices: | 1708 | for o in self.buffer_slices: |
1494 | self.buffer_slices[o].discard_mask() | 1709 | self.buffer_slices[o].discard_mask() |
1495 | self.buffer_slices[o].discard_vtk_mask() | 1710 | self.buffer_slices[o].discard_vtk_mask() |
1496 | - Publisher.sendMessage('Reload actual slice') | 1711 | + Publisher.sendMessage("Reload actual slice") |
1497 | 1712 | ||
1498 | def _open_image_matrix(self, filename, shape, dtype): | 1713 | def _open_image_matrix(self, filename, shape, dtype): |
1499 | self.matrix_filename = filename | 1714 | self.matrix_filename = filename |
1500 | - self.matrix = np.memmap(filename, shape=shape, dtype=dtype, mode='r+') | 1715 | + self.matrix = np.memmap(filename, shape=shape, dtype=dtype, mode="r+") |
1501 | 1716 | ||
1502 | def OnFlipVolume(self, axis): | 1717 | def OnFlipVolume(self, axis): |
1503 | if axis == 0: | 1718 | if axis == 0: |
@@ -1526,16 +1741,16 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1526,16 +1741,16 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1526 | def OnExportMask(self, filename, filetype): | 1741 | def OnExportMask(self, filename, filetype): |
1527 | imagedata = self.current_mask.imagedata | 1742 | imagedata = self.current_mask.imagedata |
1528 | # imagedata = self.imagedata | 1743 | # imagedata = self.imagedata |
1529 | - if (filetype == const.FILETYPE_IMAGEDATA): | 1744 | + if filetype == const.FILETYPE_IMAGEDATA: |
1530 | iu.Export(imagedata, filename) | 1745 | iu.Export(imagedata, filename) |
1531 | 1746 | ||
1532 | def _fill_holes_auto(self, parameters): | 1747 | def _fill_holes_auto(self, parameters): |
1533 | - target = parameters['target'] | ||
1534 | - conn = parameters['conn'] | ||
1535 | - orientation = parameters['orientation'] | ||
1536 | - size = parameters['size'] | 1748 | + target = parameters["target"] |
1749 | + conn = parameters["conn"] | ||
1750 | + orientation = parameters["orientation"] | ||
1751 | + size = parameters["size"] | ||
1537 | 1752 | ||
1538 | - if target == '2D': | 1753 | + if target == "2D": |
1539 | index = self.buffer_slices[orientation].index | 1754 | index = self.buffer_slices[orientation].index |
1540 | else: | 1755 | else: |
1541 | index = 0 | 1756 | index = 0 |
@@ -1543,15 +1758,15 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1543,15 +1758,15 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1543 | 1758 | ||
1544 | self.current_mask.fill_holes_auto(target, conn, orientation, index, size) | 1759 | self.current_mask.fill_holes_auto(target, conn, orientation, index, size) |
1545 | 1760 | ||
1546 | - self.buffer_slices['AXIAL'].discard_mask() | ||
1547 | - self.buffer_slices['CORONAL'].discard_mask() | ||
1548 | - self.buffer_slices['SAGITAL'].discard_mask() | 1761 | + self.buffer_slices["AXIAL"].discard_mask() |
1762 | + self.buffer_slices["CORONAL"].discard_mask() | ||
1763 | + self.buffer_slices["SAGITAL"].discard_mask() | ||
1549 | 1764 | ||
1550 | - self.buffer_slices['AXIAL'].discard_vtk_mask() | ||
1551 | - self.buffer_slices['CORONAL'].discard_vtk_mask() | ||
1552 | - self.buffer_slices['SAGITAL'].discard_vtk_mask() | 1765 | + self.buffer_slices["AXIAL"].discard_vtk_mask() |
1766 | + self.buffer_slices["CORONAL"].discard_vtk_mask() | ||
1767 | + self.buffer_slices["SAGITAL"].discard_vtk_mask() | ||
1553 | 1768 | ||
1554 | - Publisher.sendMessage('Reload actual slice') | 1769 | + Publisher.sendMessage("Reload actual slice") |
1555 | 1770 | ||
1556 | def calc_image_density(self, mask=None): | 1771 | def calc_image_density(self, mask=None): |
1557 | if mask is None: | 1772 | if mask is None: |
@@ -1573,26 +1788,27 @@ class Slice(with_metaclass(utils.Singleton, object)): | @@ -1573,26 +1788,27 @@ class Slice(with_metaclass(utils.Singleton, object)): | ||
1573 | mask = self.current_mask | 1788 | mask = self.current_mask |
1574 | 1789 | ||
1575 | self.do_threshold_to_all_slices(mask) | 1790 | self.do_threshold_to_all_slices(mask) |
1576 | - bin_img = (mask.matrix[1:, 1:, 1:] > 127) | 1791 | + bin_img = mask.matrix[1:, 1:, 1:] > 127 |
1577 | 1792 | ||
1578 | sx, sy, sz = self.spacing | 1793 | sx, sy, sz = self.spacing |
1579 | 1794 | ||
1580 | kernel = np.zeros((3, 3, 3)) | 1795 | kernel = np.zeros((3, 3, 3)) |
1581 | kernel[1, 1, 1] = 2 * sx * sy + 2 * sx * sz + 2 * sy * sz | 1796 | kernel[1, 1, 1] = 2 * sx * sy + 2 * sx * sz + 2 * sy * sz |
1582 | - kernel[0, 1, 1] = - (sx * sy) | ||
1583 | - kernel[2, 1, 1] = - (sx * sy) | 1797 | + kernel[0, 1, 1] = -(sx * sy) |
1798 | + kernel[2, 1, 1] = -(sx * sy) | ||
1584 | 1799 | ||
1585 | - kernel[1, 0, 1] = - (sx * sz) | ||
1586 | - kernel[1, 2, 1] = - (sx * sz) | 1800 | + kernel[1, 0, 1] = -(sx * sz) |
1801 | + kernel[1, 2, 1] = -(sx * sz) | ||
1587 | 1802 | ||
1588 | - kernel[1, 1, 0] = - (sy * sz) | ||
1589 | - kernel[1, 1, 2] = - (sy * sz) | 1803 | + kernel[1, 1, 0] = -(sy * sz) |
1804 | + kernel[1, 1, 2] = -(sy * sz) | ||
1590 | 1805 | ||
1591 | # area = ndimage.generic_filter(bin_img * 1.0, _conv_area, size=(3, 3, 3), mode='constant', cval=1, extra_arguments=(sx, sy, sz)).sum() | 1806 | # area = ndimage.generic_filter(bin_img * 1.0, _conv_area, size=(3, 3, 3), mode='constant', cval=1, extra_arguments=(sx, sy, sz)).sum() |
1592 | area = transforms.convolve_non_zero(bin_img * 1.0, kernel, 1).sum() | 1807 | area = transforms.convolve_non_zero(bin_img * 1.0, kernel, 1).sum() |
1593 | 1808 | ||
1594 | return area | 1809 | return area |
1595 | 1810 | ||
1811 | + | ||
1596 | def _conv_area(x, sx, sy, sz): | 1812 | def _conv_area(x, sx, sy, sz): |
1597 | x = x.reshape((3, 3, 3)) | 1813 | x = x.reshape((3, 3, 3)) |
1598 | if x[1, 1, 1]: | 1814 | if x[1, 1, 1]: |
invesalius/data/styles.py
@@ -17,43 +17,37 @@ | @@ -17,43 +17,37 @@ | ||
17 | # detalhes. | 17 | # detalhes. |
18 | #-------------------------------------------------------------------------- | 18 | #-------------------------------------------------------------------------- |
19 | 19 | ||
20 | -from six import with_metaclass | ||
21 | - | ||
22 | -import os | 20 | +import math |
23 | import multiprocessing | 21 | import multiprocessing |
22 | +import os | ||
24 | import tempfile | 23 | import tempfile |
25 | import time | 24 | import time |
26 | -import math | ||
27 | - | ||
28 | from concurrent import futures | 25 | from concurrent import futures |
29 | 26 | ||
27 | +import numpy as np | ||
30 | import vtk | 28 | import vtk |
31 | import wx | 29 | import wx |
32 | - | 30 | +from scipy import ndimage |
31 | +from imageio import imsave | ||
32 | +from scipy.ndimage import generate_binary_structure, watershed_ift | ||
33 | +from six import with_metaclass | ||
34 | +from skimage.morphology import watershed | ||
33 | from wx.lib.pubsub import pub as Publisher | 35 | from wx.lib.pubsub import pub as Publisher |
34 | 36 | ||
35 | import invesalius.constants as const | 37 | import invesalius.constants as const |
36 | import invesalius.data.converters as converters | 38 | import invesalius.data.converters as converters |
37 | import invesalius.data.cursor_actors as ca | 39 | import invesalius.data.cursor_actors as ca |
38 | -import invesalius.session as ses | ||
39 | - | ||
40 | -import numpy as np | ||
41 | - | ||
42 | -from scipy import ndimage | ||
43 | -from imageio import imsave | ||
44 | -from scipy.ndimage import watershed_ift, generate_binary_structure | ||
45 | -from skimage.morphology import watershed | ||
46 | - | 40 | +import invesalius.data.geometry as geom |
41 | +import invesalius.data.transformations as transformations | ||
42 | +import invesalius.data.watershed_process as watershed_process | ||
47 | import invesalius.gui.dialogs as dialogs | 43 | import invesalius.gui.dialogs as dialogs |
48 | -from invesalius.data.measures import MeasureData, CircleDensityMeasure, PolygonDensityMeasure | 44 | +import invesalius.session as ses |
45 | +import invesalius.utils as utils | ||
46 | +from invesalius.data.measures import (CircleDensityMeasure, MeasureData, | ||
47 | + PolygonDensityMeasure) | ||
49 | 48 | ||
50 | from . import floodfill | 49 | from . import floodfill |
51 | 50 | ||
52 | -import invesalius.data.watershed_process as watershed_process | ||
53 | -import invesalius.utils as utils | ||
54 | -import invesalius.data.transformations as transformations | ||
55 | -import invesalius.data.geometry as geom | ||
56 | - | ||
57 | ORIENTATIONS = { | 51 | ORIENTATIONS = { |
58 | "AXIAL": const.AXIAL, | 52 | "AXIAL": const.AXIAL, |
59 | "CORONAL": const.CORONAL, | 53 | "CORONAL": const.CORONAL, |
@@ -198,6 +192,266 @@ class DefaultInteractorStyle(BaseImageInteractorStyle): | @@ -198,6 +192,266 @@ class DefaultInteractorStyle(BaseImageInteractorStyle): | ||
198 | self.viewer.OnScrollBackward() | 192 | self.viewer.OnScrollBackward() |
199 | 193 | ||
200 | 194 | ||
195 | +class BaseImageEditionInteractorStyle(DefaultInteractorStyle): | ||
196 | + def __init__(self, viewer): | ||
197 | + super().__init__(viewer) | ||
198 | + | ||
199 | + self.viewer = viewer | ||
200 | + self.orientation = self.viewer.orientation | ||
201 | + | ||
202 | + self.picker = vtk.vtkWorldPointPicker() | ||
203 | + self.matrix = None | ||
204 | + | ||
205 | + self.cursor = None | ||
206 | + self.brush_size = const.BRUSH_SIZE | ||
207 | + self.brush_format = const.DEFAULT_BRUSH_FORMAT | ||
208 | + self.brush_colour = const.BRUSH_COLOUR | ||
209 | + self._set_cursor() | ||
210 | + | ||
211 | + self.fill_value = 254 | ||
212 | + | ||
213 | + self.AddObserver("EnterEvent", self.OnBIEnterInteractor) | ||
214 | + self.AddObserver("LeaveEvent", self.OnBILeaveInteractor) | ||
215 | + | ||
216 | + self.AddObserver("LeftButtonPressEvent", self.OnBIBrushClick) | ||
217 | + self.AddObserver("LeftButtonReleaseEvent", self.OnBIBrushRelease) | ||
218 | + self.AddObserver("MouseMoveEvent", self.OnBIBrushMove) | ||
219 | + | ||
220 | + self.RemoveObservers("MouseWheelForwardEvent") | ||
221 | + self.RemoveObservers("MouseWheelBackwardEvent") | ||
222 | + self.AddObserver("MouseWheelForwardEvent", self.OnBIScrollForward) | ||
223 | + self.AddObserver("MouseWheelBackwardEvent", self.OnBIScrollBackward) | ||
224 | + | ||
225 | + def _set_cursor(self): | ||
226 | + if const.DEFAULT_BRUSH_FORMAT == const.BRUSH_SQUARE: | ||
227 | + self.cursor = ca.CursorRectangle() | ||
228 | + elif const.DEFAULT_BRUSH_FORMAT == const.BRUSH_CIRCLE: | ||
229 | + self.cursor = ca.CursorCircle() | ||
230 | + | ||
231 | + self.cursor.SetOrientation(self.orientation) | ||
232 | + n = self.viewer.slice_data.number | ||
233 | + coordinates = {"SAGITAL": [n, 0, 0], | ||
234 | + "CORONAL": [0, n, 0], | ||
235 | + "AXIAL": [0, 0, n]} | ||
236 | + self.cursor.SetPosition(coordinates[self.orientation]) | ||
237 | + spacing = self.viewer.slice_.spacing | ||
238 | + self.cursor.SetSpacing(spacing) | ||
239 | + self.cursor.SetColour(self.viewer._brush_cursor_colour) | ||
240 | + self.cursor.SetSize(self.brush_size) | ||
241 | + self.viewer.slice_data.SetCursor(self.cursor) | ||
242 | + | ||
243 | + def set_brush_size(self, size): | ||
244 | + self.brush_size = size | ||
245 | + self._set_cursor() | ||
246 | + | ||
247 | + def set_brush_format(self, format): | ||
248 | + self.brush_format = format | ||
249 | + self._set_cursor() | ||
250 | + | ||
251 | + def set_brush_operation(self, operation): | ||
252 | + self.brush_operation = operation | ||
253 | + self._set_cursor() | ||
254 | + | ||
255 | + def set_fill_value(self, fill_value): | ||
256 | + self.fill_value = fill_value | ||
257 | + | ||
258 | + def set_matrix(self, matrix): | ||
259 | + self.matrix = matrix | ||
260 | + | ||
261 | + def OnBIEnterInteractor(self, obj, evt): | ||
262 | + if (self.viewer.slice_.buffer_slices[self.orientation].mask is None): | ||
263 | + return | ||
264 | + self.viewer.slice_data.cursor.Show() | ||
265 | + self.viewer.interactor.SetCursor(wx.Cursor(wx.CURSOR_BLANK)) | ||
266 | + self.viewer.interactor.Render() | ||
267 | + | ||
268 | + def OnBILeaveInteractor(self, obj, evt): | ||
269 | + self.viewer.slice_data.cursor.Show(0) | ||
270 | + self.viewer.interactor.SetCursor(wx.Cursor(wx.CURSOR_DEFAULT)) | ||
271 | + self.viewer.interactor.Render() | ||
272 | + | ||
273 | + def OnBIBrushClick(self, obj, evt): | ||
274 | + try: | ||
275 | + self.before_brush_click() | ||
276 | + except AttributeError: | ||
277 | + pass | ||
278 | + | ||
279 | + if (self.viewer.slice_.buffer_slices[self.orientation].mask is None): | ||
280 | + return | ||
281 | + | ||
282 | + viewer = self.viewer | ||
283 | + iren = viewer.interactor | ||
284 | + operation = self.config.operation | ||
285 | + | ||
286 | + viewer._set_editor_cursor_visibility(1) | ||
287 | + | ||
288 | + mouse_x, mouse_y = iren.GetEventPosition() | ||
289 | + render = iren.FindPokedRenderer(mouse_x, mouse_y) | ||
290 | + slice_data = viewer.get_slice_data(render) | ||
291 | + | ||
292 | + slice_data.cursor.Show() | ||
293 | + | ||
294 | + wx, wy, wz = viewer.get_coordinate_cursor(mouse_x, mouse_y, self.picker) | ||
295 | + position = viewer.get_slice_pixel_coord_by_world_pos(wx, wy, wz) | ||
296 | + index = slice_data.number | ||
297 | + | ||
298 | + cursor = slice_data.cursor | ||
299 | + radius = cursor.radius | ||
300 | + | ||
301 | + slice_data.cursor.SetPosition((wx, wy, wz)) | ||
302 | + | ||
303 | + self.edit_mask_pixel(self.fill_value, index, cursor.GetPixels(), | ||
304 | + position, radius, viewer.orientation) | ||
305 | + | ||
306 | + try: | ||
307 | + self.after_brush_click() | ||
308 | + except AttributeError: | ||
309 | + pass | ||
310 | + | ||
311 | + viewer.OnScrollBar() | ||
312 | + | ||
313 | + def OnBIBrushMove(self, obj, evt): | ||
314 | + try: | ||
315 | + self.before_brush_move() | ||
316 | + except AttributeError: | ||
317 | + pass | ||
318 | + | ||
319 | + if (self.viewer.slice_.buffer_slices[self.orientation].mask is None): | ||
320 | + return | ||
321 | + | ||
322 | + viewer = self.viewer | ||
323 | + iren = viewer.interactor | ||
324 | + | ||
325 | + viewer._set_editor_cursor_visibility(1) | ||
326 | + | ||
327 | + mouse_x, mouse_y = iren.GetEventPosition() | ||
328 | + render = iren.FindPokedRenderer(mouse_x, mouse_y) | ||
329 | + slice_data = viewer.get_slice_data(render) | ||
330 | + operation = self.config.operation | ||
331 | + | ||
332 | + wx, wy, wz = viewer.get_coordinate_cursor(mouse_x, mouse_y, self.picker) | ||
333 | + slice_data.cursor.SetPosition((wx, wy, wz)) | ||
334 | + | ||
335 | + if (self.left_pressed): | ||
336 | + cursor = slice_data.cursor | ||
337 | + radius = cursor.radius | ||
338 | + | ||
339 | + position = viewer.get_slice_pixel_coord_by_world_pos(wx, wy, wz) | ||
340 | + index = slice_data.number | ||
341 | + | ||
342 | + slice_data.cursor.SetPosition((wx, wy, wz)) | ||
343 | + self.edit_mask_pixel(self.fill_value, index, cursor.GetPixels(), | ||
344 | + position, radius, viewer.orientation) | ||
345 | + try: | ||
346 | + self.after_brush_move() | ||
347 | + except AttributeError: | ||
348 | + pass | ||
349 | + viewer.OnScrollBar(update3D=False) | ||
350 | + else: | ||
351 | + viewer.interactor.Render() | ||
352 | + | ||
353 | + def OnBIBrushRelease(self, evt, obj): | ||
354 | + try: | ||
355 | + self.before_brush_release() | ||
356 | + except AttributeError: | ||
357 | + pass | ||
358 | + | ||
359 | + if (self.viewer.slice_.buffer_slices[self.orientation].mask is None): | ||
360 | + return | ||
361 | + | ||
362 | + self.after_brush_release() | ||
363 | + self.viewer.discard_mask_cache(all_orientations=True, vtk_cache=True) | ||
364 | + Publisher.sendMessage('Reload actual slice') | ||
365 | + | ||
366 | + def edit_mask_pixel(self, fill_value, n, index, position, radius, orientation): | ||
367 | + if orientation == 'AXIAL': | ||
368 | + matrix = self.matrix[n, :, :] | ||
369 | + elif orientation == 'CORONAL': | ||
370 | + matrix = self.matrix[:, n, :] | ||
371 | + elif orientation == 'SAGITAL': | ||
372 | + matrix = self.matrix[:, :, n] | ||
373 | + | ||
374 | + spacing = self.viewer.slice_.spacing | ||
375 | + if hasattr(position, '__iter__'): | ||
376 | + px, py = position | ||
377 | + if orientation == 'AXIAL': | ||
378 | + sx = spacing[0] | ||
379 | + sy = spacing[1] | ||
380 | + elif orientation == 'CORONAL': | ||
381 | + sx = spacing[0] | ||
382 | + sy = spacing[2] | ||
383 | + elif orientation == 'SAGITAL': | ||
384 | + sx = spacing[2] | ||
385 | + sy = spacing[1] | ||
386 | + | ||
387 | + else: | ||
388 | + if orientation == 'AXIAL': | ||
389 | + sx = spacing[0] | ||
390 | + sy = spacing[1] | ||
391 | + py = position / matrix.shape[1] | ||
392 | + px = position % matrix.shape[1] | ||
393 | + elif orientation == 'CORONAL': | ||
394 | + sx = spacing[0] | ||
395 | + sy = spacing[2] | ||
396 | + py = position / matrix.shape[1] | ||
397 | + px = position % matrix.shape[1] | ||
398 | + elif orientation == 'SAGITAL': | ||
399 | + sx = spacing[2] | ||
400 | + sy = spacing[1] | ||
401 | + py = position / matrix.shape[1] | ||
402 | + px = position % matrix.shape[1] | ||
403 | + | ||
404 | + cx = index.shape[1] / 2 + 1 | ||
405 | + cy = index.shape[0] / 2 + 1 | ||
406 | + xi = int(px - index.shape[1] + cx) | ||
407 | + xf = int(xi + index.shape[1]) | ||
408 | + yi = int(py - index.shape[0] + cy) | ||
409 | + yf = int(yi + index.shape[0]) | ||
410 | + | ||
411 | + if yi < 0: | ||
412 | + index = index[abs(yi):,:] | ||
413 | + yi = 0 | ||
414 | + if yf > matrix.shape[0]: | ||
415 | + index = index[:index.shape[0]-(yf-matrix.shape[0]), :] | ||
416 | + yf = matrix.shape[0] | ||
417 | + | ||
418 | + if xi < 0: | ||
419 | + index = index[:,abs(xi):] | ||
420 | + xi = 0 | ||
421 | + if xf > matrix.shape[1]: | ||
422 | + index = index[:,:index.shape[1]-(xf-matrix.shape[1])] | ||
423 | + xf = matrix.shape[1] | ||
424 | + | ||
425 | + # Verifying if the points is over the image array. | ||
426 | + if (not 0 <= xi <= matrix.shape[1] and not 0 <= xf <= matrix.shape[1]) or \ | ||
427 | + (not 0 <= yi <= matrix.shape[0] and not 0 <= yf <= matrix.shape[0]): | ||
428 | + return | ||
429 | + | ||
430 | + roi_m = matrix[yi:yf,xi:xf] | ||
431 | + | ||
432 | + # Checking if roi_i has at least one element. | ||
433 | + if roi_m.size: | ||
434 | + roi_m[index] = self.fill_value | ||
435 | + | ||
436 | + def OnBIScrollForward(self, evt, obj): | ||
437 | + iren = self.viewer.interactor | ||
438 | + if iren.GetControlKey(): | ||
439 | + size = self.brush_size + 1 | ||
440 | + if size <= 100: | ||
441 | + self.set_brush_size(size) | ||
442 | + else: | ||
443 | + self.OnScrollForward(obj, evt) | ||
444 | + | ||
445 | + def OnBIScrollBackward(self, evt, obj): | ||
446 | + iren = self.viewer.interactor | ||
447 | + if iren.GetControlKey(): | ||
448 | + size = self.brush_size - 1 | ||
449 | + if size > 0: | ||
450 | + self.set_brush_size(size) | ||
451 | + else: | ||
452 | + self.OnScrollBackward(obj, evt) | ||
453 | + | ||
454 | + | ||
201 | class CrossInteractorStyle(DefaultInteractorStyle): | 455 | class CrossInteractorStyle(DefaultInteractorStyle): |
202 | """ | 456 | """ |
203 | Interactor style responsible for the Cross. | 457 | Interactor style responsible for the Cross. |
@@ -2509,10 +2763,8 @@ class FloodFillSegmentInteractorStyle(DefaultInteractorStyle): | @@ -2509,10 +2763,8 @@ class FloodFillSegmentInteractorStyle(DefaultInteractorStyle): | ||
2509 | 2763 | ||
2510 | 2764 | ||
2511 | 2765 | ||
2512 | - | ||
2513 | - | ||
2514 | -def get_style(style): | ||
2515 | - STYLES = { | 2766 | +class Styles: |
2767 | + styles = { | ||
2516 | const.STATE_DEFAULT: DefaultInteractorStyle, | 2768 | const.STATE_DEFAULT: DefaultInteractorStyle, |
2517 | const.SLICE_STATE_CROSS: CrossInteractorStyle, | 2769 | const.SLICE_STATE_CROSS: CrossInteractorStyle, |
2518 | const.STATE_WL: WWWLInteractorStyle, | 2770 | const.STATE_WL: WWWLInteractorStyle, |
@@ -2534,4 +2786,26 @@ def get_style(style): | @@ -2534,4 +2786,26 @@ def get_style(style): | ||
2534 | const.SLICE_STATE_FFILL_SEGMENTATION: FloodFillSegmentInteractorStyle, | 2786 | const.SLICE_STATE_FFILL_SEGMENTATION: FloodFillSegmentInteractorStyle, |
2535 | const.SLICE_STATE_CROP_MASK: CropMaskInteractorStyle, | 2787 | const.SLICE_STATE_CROP_MASK: CropMaskInteractorStyle, |
2536 | } | 2788 | } |
2537 | - return STYLES[style] | 2789 | + |
2790 | + @classmethod | ||
2791 | + def add_style(cls, style_cls, level=1): | ||
2792 | + if style_cls in cls.styles.values(): | ||
2793 | + for style_id in cls.styles: | ||
2794 | + if cls.styles[style_id] == style_cls: | ||
2795 | + const.SLICE_STYLES.append(style_id) | ||
2796 | + const.STYLE_LEVEL[style_id] = level | ||
2797 | + return style_id | ||
2798 | + | ||
2799 | + new_style_id = max(cls.styles) + 1 | ||
2800 | + cls.styles[new_style_id] = style_cls | ||
2801 | + const.SLICE_STYLES.append(new_style_id) | ||
2802 | + const.STYLE_LEVEL[new_style_id] = level | ||
2803 | + return new_style_id | ||
2804 | + | ||
2805 | + @classmethod | ||
2806 | + def remove_style(cls, style_id): | ||
2807 | + del cls.styles[style_id] | ||
2808 | + | ||
2809 | + @classmethod | ||
2810 | + def get_style(cls, style): | ||
2811 | + return cls.styles[style] |
invesalius/data/viewer_slice.py
@@ -303,8 +303,8 @@ class Viewer(wx.Panel): | @@ -303,8 +303,8 @@ class Viewer(wx.Panel): | ||
303 | self.style.CleanUp() | 303 | self.style.CleanUp() |
304 | 304 | ||
305 | del self.style | 305 | del self.style |
306 | - | ||
307 | - style = styles.get_style(state)(self) | 306 | + |
307 | + style = styles.Styles.get_style(state)(self) | ||
308 | 308 | ||
309 | setup = getattr(style, 'SetUp', None) | 309 | setup = getattr(style, 'SetUp', None) |
310 | if setup: | 310 | if setup: |
@@ -1539,3 +1539,39 @@ class Viewer(wx.Panel): | @@ -1539,3 +1539,39 @@ class Viewer(wx.Panel): | ||
1539 | renderer.RemoveActor(actor) | 1539 | renderer.RemoveActor(actor) |
1540 | # and remove the actor from the actor's list | 1540 | # and remove the actor from the actor's list |
1541 | self.actors_by_slice_number[slice_number].remove(actor) | 1541 | self.actors_by_slice_number[slice_number].remove(actor) |
1542 | + | ||
1543 | + def get_actual_mask(self): | ||
1544 | + # Returns actual mask. Returns None if there is not a mask or no mask | ||
1545 | + # visible. | ||
1546 | + mask = self.slice_.current_mask | ||
1547 | + return mask | ||
1548 | + | ||
1549 | + def get_slice(self): | ||
1550 | + return self.slice_ | ||
1551 | + | ||
1552 | + def discard_slice_cache(self, all_orientations=False, vtk_cache=True): | ||
1553 | + if all_orientations: | ||
1554 | + for orientation in self.slice_.buffer_slices: | ||
1555 | + buffer_ = self.slice_.buffer_slices[orientation] | ||
1556 | + buffer_.discard_image() | ||
1557 | + if vtk_cache: | ||
1558 | + buffer_.discard_vtk_image() | ||
1559 | + else: | ||
1560 | + buffer_ = self.slice_.buffer_slices[self.orientation] | ||
1561 | + buffer_.discard_image() | ||
1562 | + if vtk_cache: | ||
1563 | + buffer_.discard_vtk_image() | ||
1564 | + | ||
1565 | + def discard_mask_cache(self, all_orientations=False, vtk_cache=True): | ||
1566 | + if all_orientations: | ||
1567 | + for orientation in self.slice_.buffer_slices: | ||
1568 | + buffer_ = self.slice_.buffer_slices[orientation] | ||
1569 | + buffer_.discard_mask() | ||
1570 | + if vtk_cache: | ||
1571 | + buffer_.discard_vtk_mask() | ||
1572 | + | ||
1573 | + else: | ||
1574 | + buffer_ = self.slice_.buffer_slices[self.orientation] | ||
1575 | + buffer_.discard_mask() | ||
1576 | + if vtk_cache: | ||
1577 | + buffer_.discard_vtk_mask() |
invesalius/gui/dialogs.py
@@ -755,6 +755,34 @@ class UpdateMessageDialog(wx.Dialog): | @@ -755,6 +755,34 @@ class UpdateMessageDialog(wx.Dialog): | ||
755 | self.Destroy() | 755 | self.Destroy() |
756 | 756 | ||
757 | 757 | ||
758 | +class MessageBox(wx.Dialog): | ||
759 | + def __init__(self, parent, title, message, caption="InVesalius3 Error"): | ||
760 | + wx.Dialog.__init__(self, parent, title=caption, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER) | ||
761 | + | ||
762 | + title_label = wx.StaticText(self, -1, title) | ||
763 | + | ||
764 | + text = wx.TextCtrl(self, style=wx.TE_MULTILINE|wx.TE_READONLY|wx.BORDER_NONE) | ||
765 | + text.SetValue(message) | ||
766 | + text.SetBackgroundColour(wx.SystemSettings.GetColour(4)) | ||
767 | + | ||
768 | + width, height = text.GetTextExtent("O"*30) | ||
769 | + text.SetMinSize((width, -1)) | ||
770 | + | ||
771 | + btn_ok = wx.Button(self, wx.ID_OK) | ||
772 | + btnsizer = wx.StdDialogButtonSizer() | ||
773 | + btnsizer.AddButton(btn_ok) | ||
774 | + btnsizer.Realize() | ||
775 | + | ||
776 | + sizer = wx.BoxSizer(wx.VERTICAL) | ||
777 | + sizer.Add(title_label, 0, wx.ALIGN_CENTRE|wx.ALL|wx.EXPAND, 5) | ||
778 | + sizer.Add(text, 1, wx.ALIGN_CENTRE|wx.ALL|wx.EXPAND, 5) | ||
779 | + sizer.Add(btnsizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.EXPAND|wx.ALL, 5) | ||
780 | + self.SetSizer(sizer) | ||
781 | + sizer.Fit(self) | ||
782 | + self.Center() | ||
783 | + self.ShowModal() | ||
784 | + | ||
785 | + | ||
758 | def SaveChangesDialog__Old(filename): | 786 | def SaveChangesDialog__Old(filename): |
759 | message = _("The project %s has been modified.\nSave changes?")%filename | 787 | message = _("The project %s has been modified.\nSave changes?")%filename |
760 | dlg = MessageDialog(message) | 788 | dlg = MessageDialog(message) |
invesalius/gui/frame.py
@@ -20,6 +20,7 @@ | @@ -20,6 +20,7 @@ | ||
20 | import math | 20 | import math |
21 | import os.path | 21 | import os.path |
22 | import platform | 22 | import platform |
23 | +import subprocess | ||
23 | import sys | 24 | import sys |
24 | import webbrowser | 25 | import webbrowser |
25 | 26 | ||
@@ -534,6 +535,9 @@ class Frame(wx.Frame): | @@ -534,6 +535,9 @@ class Frame(wx.Frame): | ||
534 | elif id == const.ID_CREATE_MASK: | 535 | elif id == const.ID_CREATE_MASK: |
535 | Publisher.sendMessage('New mask from shortcut') | 536 | Publisher.sendMessage('New mask from shortcut') |
536 | 537 | ||
538 | + elif id == const.ID_PLUGINS_SHOW_PATH: | ||
539 | + self.ShowPluginsFolder() | ||
540 | + | ||
537 | def OnDbsMode(self): | 541 | def OnDbsMode(self): |
538 | st = self.actived_dbs_mode.IsChecked() | 542 | st = self.actived_dbs_mode.IsChecked() |
539 | Publisher.sendMessage('Deactive target button') | 543 | Publisher.sendMessage('Deactive target button') |
@@ -742,6 +746,19 @@ class Frame(wx.Frame): | @@ -742,6 +746,19 @@ class Frame(wx.Frame): | ||
742 | def OnCropMask(self): | 746 | def OnCropMask(self): |
743 | Publisher.sendMessage('Enable style', style=const.SLICE_STATE_CROP_MASK) | 747 | Publisher.sendMessage('Enable style', style=const.SLICE_STATE_CROP_MASK) |
744 | 748 | ||
749 | + def ShowPluginsFolder(self): | ||
750 | + """ | ||
751 | + Show getting started window. | ||
752 | + """ | ||
753 | + inv_paths.create_conf_folders() | ||
754 | + path = str(inv_paths.USER_PLUGINS_DIRECTORY) | ||
755 | + if platform.system() == "Windows": | ||
756 | + os.startfile(path) | ||
757 | + elif platform.system() == "Darwin": | ||
758 | + subprocess.Popen(["open", path]) | ||
759 | + else: | ||
760 | + subprocess.Popen(["xdg-open", path]) | ||
761 | + | ||
745 | # ------------------------------------------------------------------ | 762 | # ------------------------------------------------------------------ |
746 | # ------------------------------------------------------------------ | 763 | # ------------------------------------------------------------------ |
747 | # ------------------------------------------------------------------ | 764 | # ------------------------------------------------------------------ |
@@ -755,6 +772,7 @@ class MenuBar(wx.MenuBar): | @@ -755,6 +772,7 @@ class MenuBar(wx.MenuBar): | ||
755 | wx.MenuBar.__init__(self) | 772 | wx.MenuBar.__init__(self) |
756 | 773 | ||
757 | self.parent = parent | 774 | self.parent = parent |
775 | + self._plugins_menu_ids = {} | ||
758 | 776 | ||
759 | # Used to enable/disable menu items if project is opened or | 777 | # Used to enable/disable menu items if project is opened or |
760 | # not. Eg. save should only be available if a project is open | 778 | # not. Eg. save should only be available if a project is open |
@@ -809,6 +827,8 @@ class MenuBar(wx.MenuBar): | @@ -809,6 +827,8 @@ class MenuBar(wx.MenuBar): | ||
809 | sub(self.OnUpdateSliceInterpolation, "Update Slice Interpolation MenuBar") | 827 | sub(self.OnUpdateSliceInterpolation, "Update Slice Interpolation MenuBar") |
810 | sub(self.OnUpdateNavigationMode, "Update Navigation Mode MenuBar") | 828 | sub(self.OnUpdateNavigationMode, "Update Navigation Mode MenuBar") |
811 | 829 | ||
830 | + sub(self.AddPluginsItems, "Add plugins menu items") | ||
831 | + | ||
812 | self.num_masks = 0 | 832 | self.num_masks = 0 |
813 | 833 | ||
814 | def __init_items(self): | 834 | def __init_items(self): |
@@ -1003,6 +1023,10 @@ class MenuBar(wx.MenuBar): | @@ -1003,6 +1023,10 @@ class MenuBar(wx.MenuBar): | ||
1003 | 1023 | ||
1004 | self.actived_navigation_mode = self.mode_menu | 1024 | self.actived_navigation_mode = self.mode_menu |
1005 | 1025 | ||
1026 | + plugins_menu = wx.Menu() | ||
1027 | + plugins_menu.Append(const.ID_PLUGINS_SHOW_PATH, _("Open Plugins folder")) | ||
1028 | + self.plugins_menu = plugins_menu | ||
1029 | + | ||
1006 | # HELP | 1030 | # HELP |
1007 | help_menu = wx.Menu() | 1031 | help_menu = wx.Menu() |
1008 | help_menu.Append(const.ID_START, _("Getting started...")) | 1032 | help_menu.Append(const.ID_START, _("Getting started...")) |
@@ -1020,11 +1044,24 @@ class MenuBar(wx.MenuBar): | @@ -1020,11 +1044,24 @@ class MenuBar(wx.MenuBar): | ||
1020 | self.Append(file_edit, _("Edit")) | 1044 | self.Append(file_edit, _("Edit")) |
1021 | self.Append(view_menu, _(u"View")) | 1045 | self.Append(view_menu, _(u"View")) |
1022 | self.Append(tools_menu, _(u"Tools")) | 1046 | self.Append(tools_menu, _(u"Tools")) |
1047 | + self.Append(plugins_menu, _(u"Plugins")) | ||
1023 | #self.Append(tools_menu, "Tools") | 1048 | #self.Append(tools_menu, "Tools") |
1024 | self.Append(options_menu, _("Options")) | 1049 | self.Append(options_menu, _("Options")) |
1025 | self.Append(mode_menu, _("Mode")) | 1050 | self.Append(mode_menu, _("Mode")) |
1026 | self.Append(help_menu, _("Help")) | 1051 | self.Append(help_menu, _("Help")) |
1027 | 1052 | ||
1053 | + plugins_menu.Bind(wx.EVT_MENU, self.OnPluginMenu) | ||
1054 | + | ||
1055 | + def OnPluginMenu(self, evt): | ||
1056 | + id = evt.GetId() | ||
1057 | + if id != const.ID_PLUGINS_SHOW_PATH: | ||
1058 | + try: | ||
1059 | + plugin_name = self._plugins_menu_ids[id]["name"] | ||
1060 | + print("Loading plugin:", plugin_name) | ||
1061 | + Publisher.sendMessage("Load plugin", plugin_name=plugin_name) | ||
1062 | + except KeyError: | ||
1063 | + print("Invalid plugin") | ||
1064 | + evt.Skip() | ||
1028 | 1065 | ||
1029 | def SliceInterpolationStatus(self): | 1066 | def SliceInterpolationStatus(self): |
1030 | 1067 | ||
@@ -1052,6 +1089,18 @@ class MenuBar(wx.MenuBar): | @@ -1052,6 +1089,18 @@ class MenuBar(wx.MenuBar): | ||
1052 | v = self.NavigationModeStatus() | 1089 | v = self.NavigationModeStatus() |
1053 | self.mode_menu.Check(const.ID_MODE_NAVIGATION, v) | 1090 | self.mode_menu.Check(const.ID_MODE_NAVIGATION, v) |
1054 | 1091 | ||
1092 | + def AddPluginsItems(self, items): | ||
1093 | + for menu_item in self.plugins_menu.GetMenuItems(): | ||
1094 | + if menu_item.GetId() != const.ID_PLUGINS_SHOW_PATH: | ||
1095 | + self.plugins_menu.DestroyItem(menu_item) | ||
1096 | + | ||
1097 | + for item in items: | ||
1098 | + _new_id = wx.NewId() | ||
1099 | + self._plugins_menu_ids[_new_id] = items[item] | ||
1100 | + menu_item = self.plugins_menu.Append(_new_id, item, items[item]["description"]) | ||
1101 | + menu_item.Enable(items[item]["enable_startup"]) | ||
1102 | + print(">>> menu", item) | ||
1103 | + | ||
1055 | def OnEnableState(self, state): | 1104 | def OnEnableState(self, state): |
1056 | """ | 1105 | """ |
1057 | Based on given state, enables or disables menu items which | 1106 | Based on given state, enables or disables menu items which |
@@ -1069,6 +1118,11 @@ class MenuBar(wx.MenuBar): | @@ -1069,6 +1118,11 @@ class MenuBar(wx.MenuBar): | ||
1069 | for item in self.enable_items: | 1118 | for item in self.enable_items: |
1070 | self.Enable(item, False) | 1119 | self.Enable(item, False) |
1071 | 1120 | ||
1121 | + # Disabling plugins menus that needs a project open | ||
1122 | + for item in self._plugins_menu_ids: | ||
1123 | + if not self._plugins_menu_ids[item]["enable_startup"]: | ||
1124 | + self.Enable(item, False) | ||
1125 | + | ||
1072 | def SetStateProjectOpen(self): | 1126 | def SetStateProjectOpen(self): |
1073 | """ | 1127 | """ |
1074 | Enable menu items (e.g. save) when project is opened. | 1128 | Enable menu items (e.g. save) when project is opened. |
@@ -1076,6 +1130,11 @@ class MenuBar(wx.MenuBar): | @@ -1076,6 +1130,11 @@ class MenuBar(wx.MenuBar): | ||
1076 | for item in self.enable_items: | 1130 | for item in self.enable_items: |
1077 | self.Enable(item, True) | 1131 | self.Enable(item, True) |
1078 | 1132 | ||
1133 | + # Enabling plugins menus that needs a project open | ||
1134 | + for item in self._plugins_menu_ids: | ||
1135 | + if not self._plugins_menu_ids[item]["enable_startup"]: | ||
1136 | + self.Enable(item, True) | ||
1137 | + | ||
1079 | def OnEnableUndo(self, value): | 1138 | def OnEnableUndo(self, value): |
1080 | if value: | 1139 | if value: |
1081 | self.FindItemById(wx.ID_UNDO).Enable(True) | 1140 | self.FindItemById(wx.ID_UNDO).Enable(True) |
invesalius/inv_paths.py
1 | +# -------------------------------------------------------------------- | ||
2 | +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas | ||
3 | +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer | ||
4 | +# Homepage: http://www.softwarepublico.gov.br | ||
5 | +# Contact: invesalius@cti.gov.br | ||
6 | +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) | ||
7 | +# -------------------------------------------------------------------- | ||
8 | +# Este programa e software livre; voce pode redistribui-lo e/ou | ||
9 | +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme | ||
10 | +# publicada pela Free Software Foundation; de acordo com a versao 2 | ||
11 | +# da Licenca. | ||
12 | +# | ||
13 | +# Este programa eh distribuido na expectativa de ser util, mas SEM | ||
14 | +# QUALQUER GARANTIA; sem mesmo a garantia implicita de | ||
15 | +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM | ||
16 | +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais | ||
17 | +# detalhes. | ||
18 | +# -------------------------------------------------------------------- | ||
1 | import os | 19 | import os |
2 | import pathlib | 20 | import pathlib |
3 | import shutil | 21 | import shutil |
@@ -12,6 +30,8 @@ USER_LOG_DIR = USER_INV_DIR.joinpath("logs") | @@ -12,6 +30,8 @@ USER_LOG_DIR = USER_INV_DIR.joinpath("logs") | ||
12 | USER_RAYCASTING_PRESETS_DIRECTORY = USER_PRESET_DIR.joinpath("raycasting") | 30 | USER_RAYCASTING_PRESETS_DIRECTORY = USER_PRESET_DIR.joinpath("raycasting") |
13 | TEMP_DIR = tempfile.gettempdir() | 31 | TEMP_DIR = tempfile.gettempdir() |
14 | 32 | ||
33 | +USER_PLUGINS_DIRECTORY = USER_INV_DIR.joinpath("plugins") | ||
34 | + | ||
15 | OLD_USER_INV_DIR = USER_DIR.joinpath(".invesalius") | 35 | OLD_USER_INV_DIR = USER_DIR.joinpath(".invesalius") |
16 | OLD_USER_PRESET_DIR = OLD_USER_INV_DIR.joinpath("presets") | 36 | OLD_USER_PRESET_DIR = OLD_USER_INV_DIR.joinpath("presets") |
17 | OLD_USER_LOG_DIR = OLD_USER_INV_DIR.joinpath("logs") | 37 | OLD_USER_LOG_DIR = OLD_USER_INV_DIR.joinpath("logs") |
@@ -26,6 +46,7 @@ RAYCASTING_PRESETS_COLOR_DIRECTORY = INV_TOP_DIR.joinpath( | @@ -26,6 +46,7 @@ RAYCASTING_PRESETS_COLOR_DIRECTORY = INV_TOP_DIR.joinpath( | ||
26 | "presets", "raycasting", "color_list" | 46 | "presets", "raycasting", "color_list" |
27 | ) | 47 | ) |
28 | 48 | ||
49 | + | ||
29 | # Inside the windows executable | 50 | # Inside the windows executable |
30 | if hasattr(sys, "frozen") and ( | 51 | if hasattr(sys, "frozen") and ( |
31 | sys.frozen == "windows_exe" or sys.frozen == "console_exe" | 52 | sys.frozen == "windows_exe" or sys.frozen == "console_exe" |
@@ -71,6 +92,7 @@ def create_conf_folders(): | @@ -71,6 +92,7 @@ def create_conf_folders(): | ||
71 | USER_INV_DIR.mkdir(parents=True, exist_ok=True) | 92 | USER_INV_DIR.mkdir(parents=True, exist_ok=True) |
72 | USER_PRESET_DIR.mkdir(parents=True, exist_ok=True) | 93 | USER_PRESET_DIR.mkdir(parents=True, exist_ok=True) |
73 | USER_LOG_DIR.mkdir(parents=True, exist_ok=True) | 94 | USER_LOG_DIR.mkdir(parents=True, exist_ok=True) |
95 | + USER_PLUGINS_DIRECTORY.mkdir(parents=True, exist_ok=True) | ||
74 | 96 | ||
75 | 97 | ||
76 | def copy_old_files(): | 98 | def copy_old_files(): |
@@ -0,0 +1,74 @@ | @@ -0,0 +1,74 @@ | ||
1 | +# -------------------------------------------------------------------- | ||
2 | +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas | ||
3 | +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer | ||
4 | +# Homepage: http://www.softwarepublico.gov.br | ||
5 | +# Contact: invesalius@cti.gov.br | ||
6 | +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) | ||
7 | +# -------------------------------------------------------------------- | ||
8 | +# Este programa e software livre; voce pode redistribui-lo e/ou | ||
9 | +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme | ||
10 | +# publicada pela Free Software Foundation; de acordo com a versao 2 | ||
11 | +# da Licenca. | ||
12 | +# | ||
13 | +# Este programa eh distribuido na expectativa de ser util, mas SEM | ||
14 | +# QUALQUER GARANTIA; sem mesmo a garantia implicita de | ||
15 | +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM | ||
16 | +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais | ||
17 | +# detalhes. | ||
18 | +# -------------------------------------------------------------------- | ||
19 | + | ||
20 | +import importlib.util | ||
21 | +import json | ||
22 | +import sys | ||
23 | + | ||
24 | +from wx.lib.pubsub import pub as Publisher | ||
25 | + | ||
26 | +import invesalius.constants as consts | ||
27 | +from invesalius import inv_paths | ||
28 | + | ||
29 | + | ||
30 | +def import_source(module_name, module_file_path): | ||
31 | + module_spec = importlib.util.spec_from_file_location(module_name, module_file_path) | ||
32 | + module = importlib.util.module_from_spec(module_spec) | ||
33 | + module_spec.loader.exec_module(module) | ||
34 | + return module | ||
35 | + | ||
36 | + | ||
37 | +class PluginManager: | ||
38 | + def __init__(self): | ||
39 | + self.plugins = {} | ||
40 | + self.__bind_pubsub_evt() | ||
41 | + | ||
42 | + def __bind_pubsub_evt(self): | ||
43 | + Publisher.subscribe(self.load_plugin, "Load plugin") | ||
44 | + | ||
45 | + def find_plugins(self): | ||
46 | + self.plugins = {} | ||
47 | + for p in sorted(inv_paths.USER_PLUGINS_DIRECTORY.glob("*")): | ||
48 | + if p.is_dir(): | ||
49 | + try: | ||
50 | + with p.joinpath("plugin.json").open() as f: | ||
51 | + jdict = json.load(f) | ||
52 | + plugin_name = jdict["name"] | ||
53 | + plugin_description = jdict["description"] | ||
54 | + enable_startup = jdict.get("enable-startup", False) | ||
55 | + | ||
56 | + self.plugins[plugin_name] = { | ||
57 | + "name": plugin_name, | ||
58 | + "description": plugin_description, | ||
59 | + "folder": p, | ||
60 | + "enable_startup": enable_startup, | ||
61 | + } | ||
62 | + except Exception as err: | ||
63 | + print("It was not possible to load plugin. Error: {}".format(err)) | ||
64 | + | ||
65 | + Publisher.sendMessage("Add plugins menu items", items=self.plugins) | ||
66 | + | ||
67 | + def load_plugin(self, plugin_name): | ||
68 | + if plugin_name in self.plugins: | ||
69 | + plugin_module = import_source( | ||
70 | + plugin_name, self.plugins[plugin_name]["folder"].joinpath("__init__.py") | ||
71 | + ) | ||
72 | + sys.modules[plugin_name] = plugin_module | ||
73 | + main = importlib.import_module(plugin_name + '.main') | ||
74 | + main.load() |
invesalius/project.py
@@ -17,8 +17,6 @@ | @@ -17,8 +17,6 @@ | ||
17 | # detalhes. | 17 | # detalhes. |
18 | #-------------------------------------------------------------------------- | 18 | #-------------------------------------------------------------------------- |
19 | 19 | ||
20 | -from six import with_metaclass | ||
21 | - | ||
22 | import datetime | 20 | import datetime |
23 | import glob | 21 | import glob |
24 | import os | 22 | import os |
@@ -29,17 +27,18 @@ import tarfile | @@ -29,17 +27,18 @@ import tarfile | ||
29 | import tempfile | 27 | import tempfile |
30 | 28 | ||
31 | import numpy as np | 29 | import numpy as np |
30 | +import vtk | ||
32 | import wx | 31 | import wx |
32 | +from six import with_metaclass | ||
33 | from wx.lib.pubsub import pub as Publisher | 33 | from wx.lib.pubsub import pub as Publisher |
34 | -import vtk | ||
35 | 34 | ||
36 | import invesalius.constants as const | 35 | import invesalius.constants as const |
37 | import invesalius.data.polydata_utils as pu | 36 | import invesalius.data.polydata_utils as pu |
38 | -from invesalius.presets import Presets | ||
39 | -from invesalius.utils import Singleton, debug, touch, decode | ||
40 | import invesalius.version as version | 37 | import invesalius.version as version |
41 | - | ||
42 | from invesalius import inv_paths | 38 | from invesalius import inv_paths |
39 | +from invesalius.data import imagedata_utils | ||
40 | +from invesalius.presets import Presets | ||
41 | +from invesalius.utils import Singleton, debug, decode, touch | ||
43 | 42 | ||
44 | if sys.platform == 'win32': | 43 | if sys.platform == 'win32': |
45 | try: | 44 | try: |
@@ -277,20 +276,24 @@ class Project(with_metaclass(Singleton, object)): | @@ -277,20 +276,24 @@ class Project(with_metaclass(Singleton, object)): | ||
277 | os.remove(f) | 276 | os.remove(f) |
278 | 277 | ||
279 | def OpenPlistProject(self, filename): | 278 | def OpenPlistProject(self, filename): |
280 | - import invesalius.data.measures as ms | ||
281 | - import invesalius.data.mask as msk | ||
282 | - import invesalius.data.surface as srf | ||
283 | - | ||
284 | if not const.VTK_WARNING: | 279 | if not const.VTK_WARNING: |
285 | log_path = os.path.join(inv_paths.USER_LOG_DIR, 'vtkoutput.txt') | 280 | log_path = os.path.join(inv_paths.USER_LOG_DIR, 'vtkoutput.txt') |
286 | fow = vtk.vtkFileOutputWindow() | 281 | fow = vtk.vtkFileOutputWindow() |
287 | fow.SetFileName(log_path.encode(const.FS_ENCODE)) | 282 | fow.SetFileName(log_path.encode(const.FS_ENCODE)) |
288 | ow = vtk.vtkOutputWindow() | 283 | ow = vtk.vtkOutputWindow() |
289 | ow.SetInstance(fow) | 284 | ow.SetInstance(fow) |
290 | - | 285 | + |
291 | filelist = Extract(filename, tempfile.mkdtemp()) | 286 | filelist = Extract(filename, tempfile.mkdtemp()) |
292 | dirpath = os.path.abspath(os.path.split(filelist[0])[0]) | 287 | dirpath = os.path.abspath(os.path.split(filelist[0])[0]) |
288 | + self.load_from_folder(dirpath) | ||
293 | 289 | ||
290 | + def load_from_folder(self, dirpath): | ||
291 | + """ | ||
292 | + Loads invesalius3 project files from dipath. | ||
293 | + """ | ||
294 | + import invesalius.data.measures as ms | ||
295 | + import invesalius.data.mask as msk | ||
296 | + import invesalius.data.surface as srf | ||
294 | # Opening the main file from invesalius 3 project | 297 | # Opening the main file from invesalius 3 project |
295 | main_plist = os.path.join(dirpath ,'main.plist') | 298 | main_plist = os.path.join(dirpath ,'main.plist') |
296 | project = plistlib.readPlist(main_plist) | 299 | project = plistlib.readPlist(main_plist) |
@@ -308,7 +311,7 @@ class Project(with_metaclass(Singleton, object)): | @@ -308,7 +311,7 @@ class Project(with_metaclass(Singleton, object)): | ||
308 | self.level = project["window_level"] | 311 | self.level = project["window_level"] |
309 | self.threshold_range = project["scalar_range"] | 312 | self.threshold_range = project["scalar_range"] |
310 | self.spacing = project["spacing"] | 313 | self.spacing = project["spacing"] |
311 | - if project.get("affine"): | 314 | + if project.get("affine", ""): |
312 | self.affine = project["affine"] | 315 | self.affine = project["affine"] |
313 | Publisher.sendMessage('Update affine matrix', | 316 | Publisher.sendMessage('Update affine matrix', |
314 | affine=self.affine, status=True) | 317 | affine=self.affine, status=True) |
@@ -323,7 +326,7 @@ class Project(with_metaclass(Singleton, object)): | @@ -323,7 +326,7 @@ class Project(with_metaclass(Singleton, object)): | ||
323 | 326 | ||
324 | # Opening the masks | 327 | # Opening the masks |
325 | self.mask_dict = {} | 328 | self.mask_dict = {} |
326 | - for index in project["masks"]: | 329 | + for index in project.get("masks", []): |
327 | filename = project["masks"][index] | 330 | filename = project["masks"][index] |
328 | filepath = os.path.join(dirpath, filename) | 331 | filepath = os.path.join(dirpath, filename) |
329 | m = msk.Mask() | 332 | m = msk.Mask() |
@@ -332,7 +335,7 @@ class Project(with_metaclass(Singleton, object)): | @@ -332,7 +335,7 @@ class Project(with_metaclass(Singleton, object)): | ||
332 | 335 | ||
333 | # Opening the surfaces | 336 | # Opening the surfaces |
334 | self.surface_dict = {} | 337 | self.surface_dict = {} |
335 | - for index in project["surfaces"]: | 338 | + for index in project.get("surfaces", []): |
336 | filename = project["surfaces"][index] | 339 | filename = project["surfaces"][index] |
337 | filepath = os.path.join(dirpath, filename) | 340 | filepath = os.path.join(dirpath, filename) |
338 | s = srf.Surface(int(index)) | 341 | s = srf.Surface(int(index)) |
@@ -341,15 +344,50 @@ class Project(with_metaclass(Singleton, object)): | @@ -341,15 +344,50 @@ class Project(with_metaclass(Singleton, object)): | ||
341 | 344 | ||
342 | # Opening the measurements | 345 | # Opening the measurements |
343 | self.measurement_dict = {} | 346 | self.measurement_dict = {} |
344 | - measurements = plistlib.readPlist(os.path.join(dirpath, | ||
345 | - project["measurements"])) | ||
346 | - for index in measurements: | ||
347 | - if measurements[index]["type"] in (const.DENSITY_ELLIPSE, const.DENSITY_POLYGON): | ||
348 | - measure = ms.DensityMeasurement() | ||
349 | - else: | ||
350 | - measure = ms.Measurement() | ||
351 | - measure.Load(measurements[index]) | ||
352 | - self.measurement_dict[int(index)] = measure | 347 | + measures_file = os.path.join(dirpath, project.get("measurements", "measurements.plist")) |
348 | + if os.path.exists(measures_file): | ||
349 | + measurements = plistlib.readPlist(measures_file) | ||
350 | + for index in measurements: | ||
351 | + if measurements[index]["type"] in (const.DENSITY_ELLIPSE, const.DENSITY_POLYGON): | ||
352 | + measure = ms.DensityMeasurement() | ||
353 | + else: | ||
354 | + measure = ms.Measurement() | ||
355 | + measure.Load(measurements[index]) | ||
356 | + self.measurement_dict[int(index)] = measure | ||
357 | + | ||
358 | + def create_project_file(self, name, spacing, modality, orientation, window_width, window_level, image, affine='', folder=None): | ||
359 | + if folder is None: | ||
360 | + folder = tempfile.mkdtemp() | ||
361 | + if not os.path.exists(folder): | ||
362 | + os.mkdir(folder) | ||
363 | + image_file = os.path.join(folder, 'matrix.dat') | ||
364 | + image_mmap = imagedata_utils.array2memmap(image, image_file) | ||
365 | + matrix = { | ||
366 | + 'filename': 'matrix.dat', | ||
367 | + 'shape': image.shape, | ||
368 | + 'dtype': str(image.dtype) | ||
369 | + } | ||
370 | + project = { | ||
371 | + # Format info | ||
372 | + "format_version": const.INVESALIUS_ACTUAL_FORMAT_VERSION, | ||
373 | + "invesalius_version": const.INVESALIUS_VERSION, | ||
374 | + "date": datetime.datetime.now().isoformat(), | ||
375 | + "compress": True, | ||
376 | + | ||
377 | + # case info | ||
378 | + "name": name, # patient's name | ||
379 | + "modality": modality, # CT, RMI, ... | ||
380 | + "orientation": orientation, | ||
381 | + "window_width": window_width, | ||
382 | + "window_level": window_level, | ||
383 | + "scalar_range": (int(image.min()), int(image.max())), | ||
384 | + "spacing": spacing, | ||
385 | + "affine": affine, | ||
386 | + | ||
387 | + "matrix": matrix, | ||
388 | + } | ||
389 | + plistlib.writePlist(project, os.path.join(folder, 'main.plist')) | ||
390 | + | ||
353 | 391 | ||
354 | def export_project(self, filename, save_masks=True): | 392 | def export_project(self, filename, save_masks=True): |
355 | if filename.lower().endswith('.hdf5') or filename.lower().endswith('.h5'): | 393 | if filename.lower().endswith('.hdf5') or filename.lower().endswith('.h5'): |