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 | 312 | parser.add_option("--import-all", |
313 | 313 | action="store") |
314 | 314 | |
315 | + parser.add_option("--import-folder", action="store", dest="import_folder") | |
316 | + | |
315 | 317 | parser.add_option("-s", "--save", |
316 | 318 | help="Save the project after an import.") |
317 | 319 | |
... | ... | @@ -354,6 +356,13 @@ def use_cmd_optargs(options, args): |
354 | 356 | check_for_export(options) |
355 | 357 | |
356 | 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 | 366 | elif options.import_all: |
358 | 367 | import invesalius.reader.dicom_reader as dcm |
359 | 368 | for patient in dcm.GetDicomGroups(options.import_all): | ... | ... |
invesalius/constants.py
invesalius/control.py
... | ... | @@ -18,7 +18,12 @@ |
18 | 18 | #-------------------------------------------------------------------------- |
19 | 19 | import os |
20 | 20 | import plistlib |
21 | +import tempfile | |
22 | +import textwrap | |
23 | + | |
21 | 24 | import wx |
25 | +import numpy as np | |
26 | + | |
22 | 27 | from wx.lib.pubsub import pub as Publisher |
23 | 28 | |
24 | 29 | import invesalius.constants as const |
... | ... | @@ -43,6 +48,7 @@ import subprocess |
43 | 48 | import sys |
44 | 49 | |
45 | 50 | from invesalius import inv_paths |
51 | +from invesalius import plugins | |
46 | 52 | |
47 | 53 | DEFAULT_THRESH_MODE = 0 |
48 | 54 | |
... | ... | @@ -51,6 +57,7 @@ class Controller(): |
51 | 57 | def __init__(self, frame): |
52 | 58 | self.surface_manager = srf.SurfaceManager() |
53 | 59 | self.volume = volume.Volume() |
60 | + self.plugin_manager = plugins.PluginManager() | |
54 | 61 | self.__bind_events() |
55 | 62 | self.frame = frame |
56 | 63 | self.progress_dialog = None |
... | ... | @@ -69,9 +76,12 @@ class Controller(): |
69 | 76 | |
70 | 77 | Publisher.sendMessage('Load Preferences') |
71 | 78 | |
79 | + self.plugin_manager.find_plugins() | |
80 | + | |
72 | 81 | def __bind_events(self): |
73 | 82 | Publisher.subscribe(self.OnImportMedicalImages, 'Import directory') |
74 | 83 | Publisher.subscribe(self.OnImportGroup, 'Import group') |
84 | + Publisher.subscribe(self.OnImportFolder, 'Import folder') | |
75 | 85 | Publisher.subscribe(self.OnShowDialogImportDirectory, |
76 | 86 | 'Show import directory dialog') |
77 | 87 | Publisher.subscribe(self.OnShowDialogImportOtherFiles, |
... | ... | @@ -113,6 +123,8 @@ class Controller(): |
113 | 123 | |
114 | 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 | 128 | def SetBitmapSpacing(self, spacing): |
117 | 129 | proj = prj.Project() |
118 | 130 | proj.spacing = spacing |
... | ... | @@ -492,7 +504,32 @@ class Controller(): |
492 | 504 | self.LoadProject() |
493 | 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 | 534 | def LoadProject(self): |
498 | 535 | proj = prj.Project() |
... | ... | @@ -673,6 +710,83 @@ class Controller(): |
673 | 710 | |
674 | 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 | 790 | def OnOpenBitmapFiles(self, rec_data): |
677 | 791 | bmp_data = bmp.BitmapData() |
678 | 792 | |
... | ... | @@ -954,3 +1068,24 @@ class Controller(): |
954 | 1068 | |
955 | 1069 | def ApplyReorientation(self): |
956 | 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 | 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 | 301 | def bitmap2memmap(files, slice_size, orientation, spacing, resolution_percentage): |
292 | 302 | """ |
293 | 303 | From a list of dicom files it creates memmap file in the temp folder and | ... | ... |
invesalius/data/slice_.py
1 | -#-------------------------------------------------------------------------- | |
1 | +# -------------------------------------------------------------------------- | |
2 | 2 | # Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas |
3 | 3 | # Copyright: (C) 2001 Centro de Pesquisas Renato Archer |
4 | 4 | # Homepage: http://www.softwarepublico.gov.br |
5 | 5 | # Contact: invesalius@cti.gov.br |
6 | 6 | # License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) |
7 | -#-------------------------------------------------------------------------- | |
7 | +# -------------------------------------------------------------------------- | |
8 | 8 | # Este programa e software livre; voce pode redistribui-lo e/ou |
9 | 9 | # modifica-lo sob os termos da Licenca Publica Geral GNU, conforme |
10 | 10 | # publicada pela Free Software Foundation; de acordo com a versao 2 |
... | ... | @@ -15,40 +15,38 @@ |
15 | 15 | # COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM |
16 | 16 | # PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais |
17 | 17 | # detalhes. |
18 | -#-------------------------------------------------------------------------- | |
19 | -from six import with_metaclass | |
20 | - | |
18 | +# -------------------------------------------------------------------------- | |
21 | 19 | import os |
22 | 20 | import tempfile |
23 | 21 | |
24 | 22 | import numpy as np |
25 | 23 | import vtk |
26 | - | |
27 | 24 | from scipy import ndimage |
25 | +from six import with_metaclass | |
28 | 26 | from wx.lib.pubsub import pub as Publisher |
29 | 27 | |
30 | 28 | import invesalius.constants as const |
31 | 29 | import invesalius.data.converters as converters |
32 | 30 | import invesalius.data.imagedata_utils as iu |
33 | -import invesalius.style as st | |
31 | +import invesalius.data.transformations as transformations | |
34 | 32 | import invesalius.session as ses |
33 | +import invesalius.style as st | |
35 | 34 | import invesalius.utils as utils |
35 | +from invesalius.data import mips, transforms | |
36 | 36 | from invesalius.data.mask import Mask |
37 | 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 | 44 | class SliceBuffer(object): |
48 | - """ | |
45 | + """ | |
49 | 46 | This class is used as buffer that mantains the vtkImageData and numpy array |
50 | 47 | from actual slices from each orientation. |
51 | 48 | """ |
49 | + | |
52 | 50 | def __init__(self): |
53 | 51 | self.index = -1 |
54 | 52 | self.image = None |
... | ... | @@ -86,9 +84,10 @@ class Slice(with_metaclass(utils.Singleton, object)): |
86 | 84 | self.histogram = None |
87 | 85 | self._matrix = None |
88 | 86 | self.aux_matrices = {} |
87 | + self.aux_matrices_colours = {} | |
89 | 88 | self.state = const.STATE_DEFAULT |
90 | 89 | |
91 | - self.to_show_aux = '' | |
90 | + self.to_show_aux = "" | |
92 | 91 | |
93 | 92 | self._type_projection = const.PROJECTION_NORMAL |
94 | 93 | self.n_border = const.PROJECTION_BORDER_SIZE |
... | ... | @@ -105,9 +104,11 @@ class Slice(with_metaclass(utils.Singleton, object)): |
105 | 104 | self.hue_range = (0, 0) |
106 | 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 | 113 | self.num_gradient = 0 |
113 | 114 | self.interaction_style = st.StyleStateManager() |
... | ... | @@ -129,7 +130,9 @@ class Slice(with_metaclass(utils.Singleton, object)): |
129 | 130 | i, e = value.min(), value.max() |
130 | 131 | r = int(e) - int(i) |
131 | 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 | 137 | @property |
135 | 138 | def spacing(self): |
... | ... | @@ -138,85 +141,93 @@ class Slice(with_metaclass(utils.Singleton, object)): |
138 | 141 | @spacing.setter |
139 | 142 | def spacing(self, value): |
140 | 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 | 148 | def __bind_events(self): |
144 | 149 | # General slice control |
145 | - Publisher.subscribe(self.CreateSurfaceFromIndex, | |
146 | - 'Create surface from index') | |
150 | + Publisher.subscribe(self.CreateSurfaceFromIndex, "Create surface from index") | |
147 | 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 | 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 | 221 | def GetMaxSliceNumber(self, orientation): |
211 | 222 | shape = self.matrix.shape |
212 | 223 | |
213 | 224 | # Because matrix indexing starts with 0 so the last slice is the shape |
214 | 225 | # minu 1. |
215 | - if orientation == 'AXIAL': | |
226 | + if orientation == "AXIAL": | |
216 | 227 | return shape[0] - 1 |
217 | - elif orientation == 'CORONAL': | |
228 | + elif orientation == "CORONAL": | |
218 | 229 | return shape[1] - 1 |
219 | - elif orientation == 'SAGITAL': | |
230 | + elif orientation == "SAGITAL": | |
220 | 231 | return shape[2] - 1 |
221 | 232 | |
222 | 233 | def discard_all_buffers(self): |
... | ... | @@ -233,13 +244,13 @@ class Slice(with_metaclass(utils.Singleton, object)): |
233 | 244 | # and discard from buffer all datas related to mask. |
234 | 245 | if self.current_mask is not None and item == self.current_mask.index: |
235 | 246 | self.current_mask = None |
236 | - | |
247 | + | |
237 | 248 | for buffer_ in self.buffer_slices.values(): |
238 | 249 | buffer_.discard_vtk_mask() |
239 | 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 | 255 | def OnDuplicateMasks(self, mask_indexes): |
245 | 256 | proj = Project() |
... | ... | @@ -255,28 +266,28 @@ class Slice(with_metaclass(utils.Singleton, object)): |
255 | 266 | self._add_mask_into_proj(copy_mask) |
256 | 267 | |
257 | 268 | def OnEnableStyle(self, style): |
258 | - if (style in const.SLICE_STYLES): | |
269 | + if style in const.SLICE_STYLES: | |
259 | 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 | 272 | self.state = style |
262 | 273 | |
263 | 274 | def OnDisableStyle(self, style): |
264 | - if (style in const.SLICE_STYLES): | |
275 | + if style in const.SLICE_STYLES: | |
265 | 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 | 281 | self.state = new_state |
271 | 282 | |
272 | 283 | def OnDisableActualStyle(self): |
273 | 284 | actual_state = self.interaction_style.GetActualState() |
274 | 285 | if actual_state != const.STATE_DEFAULT: |
275 | 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 | 289 | # if (actual_state == const.SLICE_STATE_EDITOR): |
279 | - # Publisher.sendMessage('Set interactor default cursor') | |
290 | + # Publisher.sendMessage('Set interactor default cursor') | |
280 | 291 | self.state = new_state |
281 | 292 | |
282 | 293 | def OnCloseProject(self): |
... | ... | @@ -285,7 +296,7 @@ class Slice(with_metaclass(utils.Singleton, object)): |
285 | 296 | def CloseProject(self): |
286 | 297 | f = self._matrix.filename |
287 | 298 | self._matrix._mmap.close() |
288 | - self._matrix = None | |
299 | + self._matrix = None | |
289 | 300 | os.remove(f) |
290 | 301 | self.current_mask = None |
291 | 302 | |
... | ... | @@ -299,7 +310,7 @@ class Slice(with_metaclass(utils.Singleton, object)): |
299 | 310 | |
300 | 311 | self.values = None |
301 | 312 | self.nodes = None |
302 | - self.from_= OTHER | |
313 | + self.from_ = OTHER | |
303 | 314 | self.state = const.STATE_DEFAULT |
304 | 315 | |
305 | 316 | self.number_of_colours = 256 |
... | ... | @@ -309,7 +320,7 @@ class Slice(with_metaclass(utils.Singleton, object)): |
309 | 320 | |
310 | 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 | 325 | def __set_current_mask_threshold_limits(self, threshold_range): |
315 | 326 | thresh_min = threshold_range[0] |
... | ... | @@ -326,11 +337,11 @@ class Slice(with_metaclass(utils.Singleton, object)): |
326 | 337 | self.create_new_mask(name=mask_name, threshold_range=thresh, colour=colour) |
327 | 338 | self.SetMaskColour(self.current_mask.index, self.current_mask.colour) |
328 | 339 | self.SelectCurrentMask(self.current_mask.index) |
329 | - Publisher.sendMessage('Reload actual slice') | |
340 | + Publisher.sendMessage("Reload actual slice") | |
330 | 341 | |
331 | 342 | def __select_current_mask(self, index): |
332 | 343 | self.SelectCurrentMask(index) |
333 | - | |
344 | + | |
334 | 345 | def __set_current_mask_edition_threshold(self, threshold_range): |
335 | 346 | if self.current_mask: |
336 | 347 | index = self.current_mask.index |
... | ... | @@ -347,9 +358,12 @@ class Slice(with_metaclass(utils.Singleton, object)): |
347 | 358 | to_reload = True |
348 | 359 | for orientation in self.buffer_slices: |
349 | 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 | 368 | # TODO: merge this code with apply_slice_buffer_to_mask |
355 | 369 | b_mask = self.buffer_slices["AXIAL"].mask |
... | ... | @@ -371,24 +385,27 @@ class Slice(with_metaclass(utils.Singleton, object)): |
371 | 385 | self.current_mask.matrix[0, 0, n] = 1 |
372 | 386 | |
373 | 387 | if to_reload: |
374 | - Publisher.sendMessage('Reload actual slice') | |
388 | + Publisher.sendMessage("Reload actual slice") | |
375 | 389 | |
376 | 390 | def __set_current_mask_threshold_actual_slice(self, threshold_range): |
377 | 391 | index = self.current_mask.index |
378 | 392 | for orientation in self.buffer_slices: |
379 | 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 | 400 | self.num_gradient += 1 |
384 | 401 | |
385 | - Publisher.sendMessage('Reload actual slice') | |
402 | + Publisher.sendMessage("Reload actual slice") | |
386 | 403 | |
387 | 404 | def __set_current_mask_colour(self, colour): |
388 | 405 | # "if" is necessary because wx events are calling this before any mask |
389 | 406 | # has been created |
390 | 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 | 409 | self.SetMaskColour(self.current_mask.index, colour_vtk) |
393 | 410 | |
394 | 411 | def __set_mask_name(self, index, name): |
... | ... | @@ -400,23 +417,23 @@ class Slice(with_metaclass(utils.Singleton, object)): |
400 | 417 | if self.current_mask: |
401 | 418 | self.ShowMask(index, value) |
402 | 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 | 422 | if self._type_projection != const.PROJECTION_NORMAL: |
406 | 423 | self.SetTypeProjection(const.PROJECTION_NORMAL) |
407 | - Publisher.sendMessage('Reload actual slice') | |
424 | + Publisher.sendMessage("Reload actual slice") | |
408 | 425 | |
409 | 426 | def __hide_current_mask(self): |
410 | 427 | if self.current_mask: |
411 | 428 | index = self.current_mask.index |
412 | 429 | value = False |
413 | - Publisher.sendMessage('Show mask', index=index, value=value) | |
430 | + Publisher.sendMessage("Show mask", index=index, value=value) | |
414 | 431 | |
415 | 432 | def __show_current_mask(self): |
416 | 433 | if self.current_mask: |
417 | 434 | index = self.current_mask.index |
418 | 435 | value = True |
419 | - Publisher.sendMessage('Show mask', index=index, value=value) | |
436 | + Publisher.sendMessage("Show mask", index=index, value=value) | |
420 | 437 | |
421 | 438 | def __clean_current_mask(self): |
422 | 439 | if self.current_mask: |
... | ... | @@ -433,25 +450,27 @@ class Slice(with_metaclass(utils.Singleton, object)): |
433 | 450 | |
434 | 451 | def __export_slice(self, filename): |
435 | 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 | 457 | f.flush() |
440 | 458 | f.close() |
441 | 459 | |
442 | 460 | def __export_actual_mask(self, filename): |
443 | 461 | import h5py |
444 | - f = h5py.File(filename, 'w') | |
462 | + | |
463 | + f = h5py.File(filename, "w") | |
445 | 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 | 467 | f.flush() |
449 | 468 | f.close() |
450 | 469 | |
451 | 470 | def create_temp_mask(self): |
452 | 471 | temp_file = tempfile.mktemp() |
453 | 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 | 474 | return temp_file, matrix |
456 | 475 | |
457 | 476 | def edit_mask_pixel(self, operation, index, position, radius, orientation): |
... | ... | @@ -459,32 +478,30 @@ class Slice(with_metaclass(utils.Singleton, object)): |
459 | 478 | image = self.buffer_slices[orientation].image |
460 | 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 | 482 | px, py = position |
466 | - if orientation == 'AXIAL': | |
483 | + if orientation == "AXIAL": | |
467 | 484 | sx = self.spacing[0] |
468 | 485 | sy = self.spacing[1] |
469 | - elif orientation == 'CORONAL': | |
486 | + elif orientation == "CORONAL": | |
470 | 487 | sx = self.spacing[0] |
471 | 488 | sy = self.spacing[2] |
472 | - elif orientation == 'SAGITAL': | |
489 | + elif orientation == "SAGITAL": | |
473 | 490 | sx = self.spacing[2] |
474 | 491 | sy = self.spacing[1] |
475 | 492 | |
476 | 493 | else: |
477 | - if orientation == 'AXIAL': | |
494 | + if orientation == "AXIAL": | |
478 | 495 | sx = self.spacing[0] |
479 | 496 | sy = self.spacing[1] |
480 | 497 | py = position / mask.shape[1] |
481 | 498 | px = position % mask.shape[1] |
482 | - elif orientation == 'CORONAL': | |
499 | + elif orientation == "CORONAL": | |
483 | 500 | sx = self.spacing[0] |
484 | 501 | sy = self.spacing[2] |
485 | 502 | py = position / mask.shape[1] |
486 | 503 | px = position % mask.shape[1] |
487 | - elif orientation == 'SAGITAL': | |
504 | + elif orientation == "SAGITAL": | |
488 | 505 | sx = self.spacing[2] |
489 | 506 | sy = self.spacing[1] |
490 | 507 | py = position / mask.shape[1] |
... | ... | @@ -498,26 +515,26 @@ class Slice(with_metaclass(utils.Singleton, object)): |
498 | 515 | yf = int(yi + index.shape[0]) |
499 | 516 | |
500 | 517 | if yi < 0: |
501 | - index = index[abs(yi):,:] | |
518 | + index = index[abs(yi) :, :] | |
502 | 519 | yi = 0 |
503 | 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 | 522 | yf = image.shape[0] |
506 | 523 | |
507 | 524 | if xi < 0: |
508 | - index = index[:,abs(xi):] | |
525 | + index = index[:, abs(xi) :] | |
509 | 526 | xi = 0 |
510 | 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 | 529 | xf = image.shape[1] |
513 | 530 | |
514 | 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 | 535 | return |
518 | 536 | |
519 | - | |
520 | - roi_m = mask[yi:yf,xi:xf] | |
537 | + roi_m = mask[yi:yf, xi:xf] | |
521 | 538 | roi_i = image[yi:yf, xi:xf] |
522 | 539 | |
523 | 540 | # Checking if roi_i has at least one element. |
... | ... | @@ -525,11 +542,13 @@ class Slice(with_metaclass(utils.Singleton, object)): |
525 | 542 | if operation == const.BRUSH_THRESH: |
526 | 543 | # It's a trick to make points between threshold gets value 254 |
527 | 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 | 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 | 552 | elif operation == const.BRUSH_THRESH_ADD_ONLY: |
534 | 553 | roi_m[((index) & (roi_i >= thresh_min) & (roi_i <= thresh_max))] = 254 |
535 | 554 | elif operation == const.BRUSH_THRESH_ERASE_ONLY: |
... | ... | @@ -544,18 +563,22 @@ class Slice(with_metaclass(utils.Singleton, object)): |
544 | 563 | session = ses.Session() |
545 | 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 | 573 | if self.buffer_slices[orientation].vtk_image: |
553 | 574 | image = self.buffer_slices[orientation].vtk_image |
554 | 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 | 582 | ww_wl_image = self.do_ww_wl(image) |
560 | 583 | image = self.do_colour_image(ww_wl_image) |
561 | 584 | if self.current_mask and self.current_mask.is_shown: |
... | ... | @@ -567,7 +590,9 @@ class Slice(with_metaclass(utils.Singleton, object)): |
567 | 590 | # Prints that during navigation causes delay in update |
568 | 591 | # print "Do not getting from buffer" |
569 | 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 | 596 | mask = self.do_colour_mask(mask, self.opacity) |
572 | 597 | self.buffer_slices[orientation].mask = n_mask |
573 | 598 | final_image = self.do_blend(image, mask) |
... | ... | @@ -576,15 +601,18 @@ class Slice(with_metaclass(utils.Singleton, object)): |
576 | 601 | final_image = image |
577 | 602 | self.buffer_slices[orientation].vtk_image = image |
578 | 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 | 607 | image = converters.to_vtk(n_image, self.spacing, slice_number, orientation) |
582 | 608 | ww_wl_image = self.do_ww_wl(image) |
583 | 609 | image = self.do_colour_image(ww_wl_image) |
584 | 610 | |
585 | 611 | if self.current_mask and self.current_mask.is_shown: |
586 | 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 | 616 | mask = self.do_colour_mask(mask, self.opacity) |
589 | 617 | final_image = self.do_blend(image, mask) |
590 | 618 | else: |
... | ... | @@ -597,20 +625,34 @@ class Slice(with_metaclass(utils.Singleton, object)): |
597 | 625 | self.buffer_slices[orientation].vtk_image = image |
598 | 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 | 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 | 639 | final_image = self.do_blend(final_image, cimage) |
607 | 640 | elif self.to_show_aux and self.current_mask: |
608 | 641 | m = self.get_aux_slice(self.to_show_aux, orientation, slice_number) |
609 | 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 | 656 | final_image = self.do_blend(final_image, aux_image) |
615 | 657 | return final_image |
616 | 658 | |
... | ... | @@ -644,11 +686,21 @@ class Slice(with_metaclass(utils.Singleton, object)): |
644 | 686 | T1 = transformations.translation_matrix((cz, cy, cx)) |
645 | 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 | 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 | 704 | if self._type_projection == const.PROJECTION_NORMAL: |
653 | 705 | n_image = tmp_array.reshape(dy, dx) |
654 | 706 | else: |
... | ... | @@ -662,48 +714,89 @@ class Slice(with_metaclass(utils.Singleton, object)): |
662 | 714 | elif self._type_projection == const.PROJECTION_MeanIP: |
663 | 715 | n_image = np.array(tmp_array).mean(0) |
664 | 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 | 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 | 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 | 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 | 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 | 774 | else: |
693 | 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 | 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 | 793 | if self._type_projection == const.PROJECTION_NORMAL: |
701 | 794 | n_image = tmp_array.reshape(dz, dx) |
702 | 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 | 800 | if inverted: |
708 | 801 | tmp_array = tmp_array[:, ::-1, :] |
709 | 802 | if self._type_projection == const.PROJECTION_MaxIP: |
... | ... | @@ -713,39 +806,80 @@ class Slice(with_metaclass(utils.Singleton, object)): |
713 | 806 | elif self._type_projection == const.PROJECTION_MeanIP: |
714 | 807 | n_image = np.array(tmp_array).mean(1) |
715 | 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 | 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 | 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 | 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 | 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 | 866 | else: |
744 | 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 | 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 | 884 | if self._type_projection == const.PROJECTION_NORMAL: |
751 | 885 | n_image = tmp_array.reshape(dz, dy) |
... | ... | @@ -759,34 +893,64 @@ class Slice(with_metaclass(utils.Singleton, object)): |
759 | 893 | elif self._type_projection == const.PROJECTION_MeanIP: |
760 | 894 | n_image = np.array(tmp_array).mean(2) |
761 | 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 | 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 | 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 | 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 | 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 | 954 | else: |
791 | 955 | n_image = np.array(self.matrix[:, :, slice_number]) |
792 | 956 | |
... | ... | @@ -794,63 +958,71 @@ class Slice(with_metaclass(utils.Singleton, object)): |
794 | 958 | return n_image |
795 | 959 | |
796 | 960 | def get_mask_slice(self, orientation, slice_number): |
797 | - """ | |
961 | + """ | |
798 | 962 | It gets the from actual mask the given slice from given orientation |
799 | 963 | """ |
800 | 964 | # It's necessary because the first position for each dimension from |
801 | 965 | # mask matrix is used as flags to control if the mask in the |
802 | 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 | 971 | return self.buffer_slices[orientation].mask |
806 | 972 | n = slice_number + 1 |
807 | - if orientation == 'AXIAL': | |
973 | + if orientation == "AXIAL": | |
808 | 974 | if self.current_mask.matrix[n, 0, 0] == 0: |
809 | 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 | 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 | 986 | if self.current_mask.matrix[0, n, 0] == 0: |
819 | 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 | 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 | 998 | if self.current_mask.matrix[0, 0, n] == 0: |
829 | 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 | 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 | 1009 | return n_mask |
838 | 1010 | |
839 | 1011 | def get_aux_slice(self, name, orientation, n): |
840 | 1012 | m = self.aux_matrices[name] |
841 | - if orientation == 'AXIAL': | |
1013 | + if orientation == "AXIAL": | |
842 | 1014 | return np.array(m[n]) |
843 | - elif orientation == 'CORONAL': | |
1015 | + elif orientation == "CORONAL": | |
844 | 1016 | return np.array(m[:, n, :]) |
845 | - elif orientation == 'SAGITAL': | |
1017 | + elif orientation == "SAGITAL": | |
846 | 1018 | return np.array(m[:, :, n]) |
847 | 1019 | |
848 | 1020 | def GetNumberOfSlices(self, orientation): |
849 | - if orientation == 'AXIAL': | |
1021 | + if orientation == "AXIAL": | |
850 | 1022 | return self.matrix.shape[0] |
851 | - elif orientation == 'CORONAL': | |
1023 | + elif orientation == "CORONAL": | |
852 | 1024 | return self.matrix.shape[1] |
853 | - elif orientation == 'SAGITAL': | |
1025 | + elif orientation == "SAGITAL": | |
854 | 1026 | return self.matrix.shape[2] |
855 | 1027 | |
856 | 1028 | def SetMaskColour(self, index, colour, update=True): |
... | ... | @@ -858,16 +1030,17 @@ class Slice(with_metaclass(utils.Singleton, object)): |
858 | 1030 | proj = Project() |
859 | 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 | 1039 | if update: |
867 | 1040 | # Updating mask colour on vtkimagedata. |
868 | 1041 | for buffer_ in self.buffer_slices.values(): |
869 | 1042 | buffer_.discard_vtk_mask() |
870 | - Publisher.sendMessage('Reload actual slice') | |
1043 | + Publisher.sendMessage("Reload actual slice") | |
871 | 1044 | |
872 | 1045 | session = ses.Session() |
873 | 1046 | session.ChangeProject() |
... | ... | @@ -885,8 +1058,9 @@ class Slice(with_metaclass(utils.Singleton, object)): |
885 | 1058 | proj = Project() |
886 | 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 | 1065 | Set a mask threshold range given its index and tuple of min and max |
892 | 1066 | threshold values. |
... | ... | @@ -905,19 +1079,23 @@ class Slice(with_metaclass(utils.Singleton, object)): |
905 | 1079 | m[slice_ < thresh_min] = 0 |
906 | 1080 | m[slice_ > thresh_max] = 0 |
907 | 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 | 1083 | else: |
910 | 1084 | slice_ = self.buffer_slices[orientation].image |
911 | 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 | 1090 | # Update viewer |
915 | - #Publisher.sendMessage('Update slice viewer') | |
1091 | + # Publisher.sendMessage('Update slice viewer') | |
916 | 1092 | |
917 | 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 | 1099 | else: |
922 | 1100 | proj = Project() |
923 | 1101 | proj.mask_dict[index].threshold_range = threshold_range |
... | ... | @@ -932,15 +1110,18 @@ class Slice(with_metaclass(utils.Singleton, object)): |
932 | 1110 | proj.mask_dict[index].on_show() |
933 | 1111 | |
934 | 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 | 1119 | for buffer_ in self.buffer_slices.values(): |
940 | 1120 | buffer_.discard_vtk_mask() |
941 | 1121 | buffer_.discard_mask() |
942 | - Publisher.sendMessage('Reload actual slice') | |
943 | - #--------------------------------------------------------------------------- | |
1122 | + Publisher.sendMessage("Reload actual slice") | |
1123 | + | |
1124 | + # --------------------------------------------------------------------------- | |
944 | 1125 | |
945 | 1126 | def SelectCurrentMask(self, index): |
946 | 1127 | "Insert mask data, based on given index, into pipeline." |
... | ... | @@ -952,27 +1133,37 @@ class Slice(with_metaclass(utils.Singleton, object)): |
952 | 1133 | colour = future_mask.colour |
953 | 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 | 1156 | def CreateSurfaceFromIndex(self, surface_parameters): |
969 | 1157 | proj = Project() |
970 | - mask = proj.mask_dict[surface_parameters['options']['index']] | |
1158 | + mask = proj.mask_dict[surface_parameters["options"]["index"]] | |
971 | 1159 | |
972 | 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 | 1168 | def GetOutput(self): |
978 | 1169 | return self.blend_filter.GetOutput() |
... | ... | @@ -986,69 +1177,73 @@ class Slice(with_metaclass(utils.Singleton, object)): |
986 | 1177 | def SetTypeProjection(self, tprojection): |
987 | 1178 | if self._type_projection != tprojection: |
988 | 1179 | if self._type_projection == const.PROJECTION_NORMAL: |
989 | - Publisher.sendMessage('Hide current mask') | |
1180 | + Publisher.sendMessage("Hide current mask") | |
990 | 1181 | |
991 | 1182 | if tprojection == const.PROJECTION_NORMAL: |
992 | - Publisher.sendMessage('Show MIP interface', flag=False) | |
1183 | + Publisher.sendMessage("Show MIP interface", flag=False) | |
993 | 1184 | else: |
994 | - Publisher.sendMessage('Show MIP interface', flag=True) | |
1185 | + Publisher.sendMessage("Show MIP interface", flag=True) | |
995 | 1186 | |
996 | 1187 | self._type_projection = tprojection |
997 | 1188 | for buffer_ in self.buffer_slices.values(): |
998 | 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 | 1193 | def SetInterpolationMethod(self, interp_method): |
1003 | 1194 | if self.interp_method != interp_method: |
1004 | 1195 | self.interp_method = interp_method |
1005 | 1196 | for buffer_ in self.buffer_slices.values(): |
1006 | 1197 | buffer_.discard_buffer() |
1007 | - Publisher.sendMessage('Reload actual slice') | |
1198 | + Publisher.sendMessage("Reload actual slice") | |
1008 | 1199 | |
1009 | 1200 | def UpdateWindowLevelBackground(self, window, level): |
1010 | 1201 | self.window_width = window |
1011 | 1202 | self.window_level = level |
1012 | 1203 | |
1013 | 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 | 1212 | buffer_.discard_vtk_image() |
1020 | 1213 | else: |
1021 | 1214 | buffer_.discard_buffer() |
1022 | 1215 | |
1023 | - Publisher.sendMessage('Reload actual slice') | |
1216 | + Publisher.sendMessage("Reload actual slice") | |
1024 | 1217 | |
1025 | 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 | 1221 | self.saturation_range = values[1] |
1029 | 1222 | self.hue_range = values[2] |
1030 | 1223 | self.value_range = values[3] |
1031 | 1224 | for buffer_ in self.buffer_slices.values(): |
1032 | 1225 | buffer_.discard_vtk_image() |
1033 | - Publisher.sendMessage('Reload actual slice') | |
1226 | + Publisher.sendMessage("Reload actual slice") | |
1034 | 1227 | |
1035 | 1228 | def UpdateColourTableBackgroundPlist(self, values): |
1036 | 1229 | self.values = values |
1037 | - self.from_= PLIST | |
1230 | + self.from_ = PLIST | |
1038 | 1231 | for buffer_ in self.buffer_slices.values(): |
1039 | 1232 | buffer_.discard_vtk_image() |
1040 | 1233 | |
1041 | - Publisher.sendMessage('Reload actual slice') | |
1234 | + Publisher.sendMessage("Reload actual slice") | |
1042 | 1235 | |
1043 | 1236 | def UpdateColourTableBackgroundWidget(self, nodes): |
1044 | 1237 | self.nodes = nodes |
1045 | - self.from_= WIDGET | |
1238 | + self.from_ = WIDGET | |
1046 | 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 | 1247 | buffer_.discard_vtk_image() |
1053 | 1248 | else: |
1054 | 1249 | buffer_.discard_buffer() |
... | ... | @@ -1060,34 +1255,37 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1060 | 1255 | self.window_width = pn - p0 |
1061 | 1256 | self.window_level = (pn + p0) / 2 |
1062 | 1257 | |
1063 | - Publisher.sendMessage('Reload actual slice') | |
1258 | + Publisher.sendMessage("Reload actual slice") | |
1064 | 1259 | |
1065 | 1260 | def UpdateSlice3D(self, widget, orientation): |
1066 | 1261 | img = self.buffer_slices[orientation].vtk_image |
1067 | 1262 | original_orientation = Project().original_orientation |
1068 | 1263 | cast = vtk.vtkImageCast() |
1069 | 1264 | cast.SetInputData(img) |
1070 | - cast.SetOutputScalarTypeToDouble() | |
1265 | + cast.SetOutputScalarTypeToDouble() | |
1071 | 1266 | cast.ClampOverflowOn() |
1072 | 1267 | cast.Update() |
1073 | 1268 | |
1074 | - #if (original_orientation == const.AXIAL): | |
1269 | + # if (original_orientation == const.AXIAL): | |
1075 | 1270 | flip = vtk.vtkImageFlip() |
1076 | 1271 | flip.SetInputConnection(cast.GetOutputPort()) |
1077 | 1272 | flip.SetFilteredAxis(1) |
1078 | 1273 | flip.FlipAboutOriginOn() |
1079 | 1274 | flip.Update() |
1080 | 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 | 1290 | Creates a new mask and add it to project. |
1093 | 1291 | |
... | ... | @@ -1126,7 +1324,6 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1126 | 1324 | |
1127 | 1325 | return future_mask |
1128 | 1326 | |
1129 | - | |
1130 | 1327 | def _add_mask_into_proj(self, mask, show=True): |
1131 | 1328 | """ |
1132 | 1329 | Insert a new mask into project and retrieve its index. |
... | ... | @@ -1140,14 +1337,13 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1140 | 1337 | mask.index = index |
1141 | 1338 | |
1142 | 1339 | ## update gui related to mask |
1143 | - Publisher.sendMessage('Add mask', | |
1144 | - mask=mask) | |
1340 | + Publisher.sendMessage("Add mask", mask=mask) | |
1145 | 1341 | |
1146 | 1342 | if show: |
1147 | 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 | 1348 | def do_ww_wl(self, image): |
1153 | 1349 | if self.from_ == PLIST: |
... | ... | @@ -1158,7 +1354,7 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1158 | 1354 | |
1159 | 1355 | i = 0 |
1160 | 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 | 1358 | i += 1 |
1163 | 1359 | |
1164 | 1360 | colorer = vtk.vtkImageMapToColors() |
... | ... | @@ -1167,11 +1363,11 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1167 | 1363 | colorer.SetOutputFormatToRGB() |
1168 | 1364 | colorer.Update() |
1169 | 1365 | elif self.from_ == WIDGET: |
1170 | - lut = vtk.vtkColorTransferFunction() | |
1366 | + lut = vtk.vtkColorTransferFunction() | |
1171 | 1367 | |
1172 | 1368 | for n in self.nodes: |
1173 | 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 | 1372 | lut.Build() |
1177 | 1373 | |
... | ... | @@ -1191,7 +1387,7 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1191 | 1387 | return colorer.GetOutput() |
1192 | 1388 | |
1193 | 1389 | def _update_wwwl_widget_nodes(self, ww, wl): |
1194 | - if self.from_ == WIDGET: | |
1390 | + if self.from_ == WIDGET: | |
1195 | 1391 | knodes = sorted(self.nodes) |
1196 | 1392 | |
1197 | 1393 | p1 = knodes[0] |
... | ... | @@ -1217,7 +1413,7 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1217 | 1413 | node.value += shiftWW * factor |
1218 | 1414 | |
1219 | 1415 | def do_threshold_to_a_slice(self, slice_matrix, mask, threshold=None): |
1220 | - """ | |
1416 | + """ | |
1221 | 1417 | Based on the current threshold bounds generates a threshold mask to |
1222 | 1418 | given slice_matrix. |
1223 | 1419 | """ |
... | ... | @@ -1226,12 +1422,12 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1226 | 1422 | else: |
1227 | 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 | 1426 | m[mask == 1] = 1 |
1231 | 1427 | m[mask == 2] = 2 |
1232 | 1428 | m[mask == 253] = 253 |
1233 | 1429 | m[mask == 254] = 254 |
1234 | - return m.astype('uint8') | |
1430 | + return m.astype("uint8") | |
1235 | 1431 | |
1236 | 1432 | def do_threshold_to_all_slices(self, mask=None): |
1237 | 1433 | """ |
... | ... | @@ -1246,7 +1442,9 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1246 | 1442 | for n in range(1, mask.matrix.shape[0]): |
1247 | 1443 | if mask.matrix[n, 0, 0] == 0: |
1248 | 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 | 1449 | mask.matrix.flush() |
1252 | 1450 | |
... | ... | @@ -1318,7 +1516,7 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1318 | 1516 | lut_mask.SetNumberOfTableValues(ncolours) |
1319 | 1517 | |
1320 | 1518 | for v in map_colours: |
1321 | - r,g, b,a = map_colours[v] | |
1519 | + r, g, b, a = map_colours[v] | |
1322 | 1520 | lut_mask.SetTableValue(v, r, g, b, a) |
1323 | 1521 | |
1324 | 1522 | lut_mask.SetRampToLinear() |
... | ... | @@ -1352,13 +1550,13 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1352 | 1550 | def _do_boolean_op(self, operation, mask1, mask2): |
1353 | 1551 | self.do_boolean_op(operation, mask1, mask2) |
1354 | 1552 | |
1355 | - | |
1356 | 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 | 1561 | name = u"%s_%s_%s" % (name_ops[op], m1.name, m2.name) |
1364 | 1562 | proj = Project() |
... | ... | @@ -1406,32 +1604,32 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1406 | 1604 | index = self.buffer_slices[orientation].index |
1407 | 1605 | |
1408 | 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 | 1634 | self.current_mask.save_history(index, orientation, b_mask, p_mask) |
1437 | 1635 | self.current_mask.was_edited = True |
... | ... | @@ -1440,11 +1638,13 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1440 | 1638 | if o != orientation: |
1441 | 1639 | self.buffer_slices[o].discard_mask() |
1442 | 1640 | self.buffer_slices[o].discard_vtk_mask() |
1443 | - Publisher.sendMessage('Reload actual slice') | |
1641 | + Publisher.sendMessage("Reload actual slice") | |
1444 | 1642 | |
1445 | 1643 | def apply_reorientation(self): |
1446 | 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 | 1648 | mcopy[:] = self.matrix |
1449 | 1649 | |
1450 | 1650 | cx, cy, cz = self.center |
... | ... | @@ -1453,13 +1653,24 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1453 | 1653 | T1 = transformations.translation_matrix((cz, cy, cx)) |
1454 | 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 | 1667 | del mcopy |
1459 | 1668 | os.remove(temp_file) |
1460 | 1669 | |
1461 | 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 | 1675 | self.__clean_current_mask() |
1465 | 1676 | if self.current_mask: |
... | ... | @@ -1469,35 +1680,39 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1469 | 1680 | for o in self.buffer_slices: |
1470 | 1681 | self.buffer_slices[o].discard_buffer() |
1471 | 1682 | |
1472 | - Publisher.sendMessage('Reload actual slice') | |
1683 | + Publisher.sendMessage("Reload actual slice") | |
1473 | 1684 | |
1474 | 1685 | def __undo_edition(self): |
1475 | 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 | 1693 | self.current_mask.undo_history(actual_slices) |
1481 | 1694 | for o in self.buffer_slices: |
1482 | 1695 | self.buffer_slices[o].discard_mask() |
1483 | 1696 | self.buffer_slices[o].discard_vtk_mask() |
1484 | - Publisher.sendMessage('Reload actual slice') | |
1697 | + Publisher.sendMessage("Reload actual slice") | |
1485 | 1698 | |
1486 | 1699 | def __redo_edition(self): |
1487 | 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 | 1707 | self.current_mask.redo_history(actual_slices) |
1493 | 1708 | for o in self.buffer_slices: |
1494 | 1709 | self.buffer_slices[o].discard_mask() |
1495 | 1710 | self.buffer_slices[o].discard_vtk_mask() |
1496 | - Publisher.sendMessage('Reload actual slice') | |
1711 | + Publisher.sendMessage("Reload actual slice") | |
1497 | 1712 | |
1498 | 1713 | def _open_image_matrix(self, filename, shape, dtype): |
1499 | 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 | 1717 | def OnFlipVolume(self, axis): |
1503 | 1718 | if axis == 0: |
... | ... | @@ -1526,16 +1741,16 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1526 | 1741 | def OnExportMask(self, filename, filetype): |
1527 | 1742 | imagedata = self.current_mask.imagedata |
1528 | 1743 | # imagedata = self.imagedata |
1529 | - if (filetype == const.FILETYPE_IMAGEDATA): | |
1744 | + if filetype == const.FILETYPE_IMAGEDATA: | |
1530 | 1745 | iu.Export(imagedata, filename) |
1531 | 1746 | |
1532 | 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 | 1754 | index = self.buffer_slices[orientation].index |
1540 | 1755 | else: |
1541 | 1756 | index = 0 |
... | ... | @@ -1543,15 +1758,15 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1543 | 1758 | |
1544 | 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 | 1771 | def calc_image_density(self, mask=None): |
1557 | 1772 | if mask is None: |
... | ... | @@ -1573,26 +1788,27 @@ class Slice(with_metaclass(utils.Singleton, object)): |
1573 | 1788 | mask = self.current_mask |
1574 | 1789 | |
1575 | 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 | 1793 | sx, sy, sz = self.spacing |
1579 | 1794 | |
1580 | 1795 | kernel = np.zeros((3, 3, 3)) |
1581 | 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 | 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 | 1807 | area = transforms.convolve_non_zero(bin_img * 1.0, kernel, 1).sum() |
1593 | 1808 | |
1594 | 1809 | return area |
1595 | 1810 | |
1811 | + | |
1596 | 1812 | def _conv_area(x, sx, sy, sz): |
1597 | 1813 | x = x.reshape((3, 3, 3)) |
1598 | 1814 | if x[1, 1, 1]: | ... | ... |
invesalius/data/styles.py
... | ... | @@ -17,43 +17,37 @@ |
17 | 17 | # detalhes. |
18 | 18 | #-------------------------------------------------------------------------- |
19 | 19 | |
20 | -from six import with_metaclass | |
21 | - | |
22 | -import os | |
20 | +import math | |
23 | 21 | import multiprocessing |
22 | +import os | |
24 | 23 | import tempfile |
25 | 24 | import time |
26 | -import math | |
27 | - | |
28 | 25 | from concurrent import futures |
29 | 26 | |
27 | +import numpy as np | |
30 | 28 | import vtk |
31 | 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 | 35 | from wx.lib.pubsub import pub as Publisher |
34 | 36 | |
35 | 37 | import invesalius.constants as const |
36 | 38 | import invesalius.data.converters as converters |
37 | 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 | 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 | 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 | 51 | ORIENTATIONS = { |
58 | 52 | "AXIAL": const.AXIAL, |
59 | 53 | "CORONAL": const.CORONAL, |
... | ... | @@ -198,6 +192,266 @@ class DefaultInteractorStyle(BaseImageInteractorStyle): |
198 | 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 | 455 | class CrossInteractorStyle(DefaultInteractorStyle): |
202 | 456 | """ |
203 | 457 | Interactor style responsible for the Cross. |
... | ... | @@ -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 | 2768 | const.STATE_DEFAULT: DefaultInteractorStyle, |
2517 | 2769 | const.SLICE_STATE_CROSS: CrossInteractorStyle, |
2518 | 2770 | const.STATE_WL: WWWLInteractorStyle, |
... | ... | @@ -2534,4 +2786,26 @@ def get_style(style): |
2534 | 2786 | const.SLICE_STATE_FFILL_SEGMENTATION: FloodFillSegmentInteractorStyle, |
2535 | 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 | 303 | self.style.CleanUp() |
304 | 304 | |
305 | 305 | del self.style |
306 | - | |
307 | - style = styles.get_style(state)(self) | |
306 | + | |
307 | + style = styles.Styles.get_style(state)(self) | |
308 | 308 | |
309 | 309 | setup = getattr(style, 'SetUp', None) |
310 | 310 | if setup: |
... | ... | @@ -1539,3 +1539,39 @@ class Viewer(wx.Panel): |
1539 | 1539 | renderer.RemoveActor(actor) |
1540 | 1540 | # and remove the actor from the actor's list |
1541 | 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 | 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 | 786 | def SaveChangesDialog__Old(filename): |
759 | 787 | message = _("The project %s has been modified.\nSave changes?")%filename |
760 | 788 | dlg = MessageDialog(message) | ... | ... |
invesalius/gui/frame.py
... | ... | @@ -20,6 +20,7 @@ |
20 | 20 | import math |
21 | 21 | import os.path |
22 | 22 | import platform |
23 | +import subprocess | |
23 | 24 | import sys |
24 | 25 | import webbrowser |
25 | 26 | |
... | ... | @@ -534,6 +535,9 @@ class Frame(wx.Frame): |
534 | 535 | elif id == const.ID_CREATE_MASK: |
535 | 536 | Publisher.sendMessage('New mask from shortcut') |
536 | 537 | |
538 | + elif id == const.ID_PLUGINS_SHOW_PATH: | |
539 | + self.ShowPluginsFolder() | |
540 | + | |
537 | 541 | def OnDbsMode(self): |
538 | 542 | st = self.actived_dbs_mode.IsChecked() |
539 | 543 | Publisher.sendMessage('Deactive target button') |
... | ... | @@ -742,6 +746,19 @@ class Frame(wx.Frame): |
742 | 746 | def OnCropMask(self): |
743 | 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 | 772 | wx.MenuBar.__init__(self) |
756 | 773 | |
757 | 774 | self.parent = parent |
775 | + self._plugins_menu_ids = {} | |
758 | 776 | |
759 | 777 | # Used to enable/disable menu items if project is opened or |
760 | 778 | # not. Eg. save should only be available if a project is open |
... | ... | @@ -809,6 +827,8 @@ class MenuBar(wx.MenuBar): |
809 | 827 | sub(self.OnUpdateSliceInterpolation, "Update Slice Interpolation MenuBar") |
810 | 828 | sub(self.OnUpdateNavigationMode, "Update Navigation Mode MenuBar") |
811 | 829 | |
830 | + sub(self.AddPluginsItems, "Add plugins menu items") | |
831 | + | |
812 | 832 | self.num_masks = 0 |
813 | 833 | |
814 | 834 | def __init_items(self): |
... | ... | @@ -1003,6 +1023,10 @@ class MenuBar(wx.MenuBar): |
1003 | 1023 | |
1004 | 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 | 1030 | # HELP |
1007 | 1031 | help_menu = wx.Menu() |
1008 | 1032 | help_menu.Append(const.ID_START, _("Getting started...")) |
... | ... | @@ -1020,11 +1044,24 @@ class MenuBar(wx.MenuBar): |
1020 | 1044 | self.Append(file_edit, _("Edit")) |
1021 | 1045 | self.Append(view_menu, _(u"View")) |
1022 | 1046 | self.Append(tools_menu, _(u"Tools")) |
1047 | + self.Append(plugins_menu, _(u"Plugins")) | |
1023 | 1048 | #self.Append(tools_menu, "Tools") |
1024 | 1049 | self.Append(options_menu, _("Options")) |
1025 | 1050 | self.Append(mode_menu, _("Mode")) |
1026 | 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 | 1066 | def SliceInterpolationStatus(self): |
1030 | 1067 | |
... | ... | @@ -1052,6 +1089,18 @@ class MenuBar(wx.MenuBar): |
1052 | 1089 | v = self.NavigationModeStatus() |
1053 | 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 | 1104 | def OnEnableState(self, state): |
1056 | 1105 | """ |
1057 | 1106 | Based on given state, enables or disables menu items which |
... | ... | @@ -1069,6 +1118,11 @@ class MenuBar(wx.MenuBar): |
1069 | 1118 | for item in self.enable_items: |
1070 | 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 | 1126 | def SetStateProjectOpen(self): |
1073 | 1127 | """ |
1074 | 1128 | Enable menu items (e.g. save) when project is opened. |
... | ... | @@ -1076,6 +1130,11 @@ class MenuBar(wx.MenuBar): |
1076 | 1130 | for item in self.enable_items: |
1077 | 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 | 1138 | def OnEnableUndo(self, value): |
1080 | 1139 | if value: |
1081 | 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 | 19 | import os |
2 | 20 | import pathlib |
3 | 21 | import shutil |
... | ... | @@ -12,6 +30,8 @@ USER_LOG_DIR = USER_INV_DIR.joinpath("logs") |
12 | 30 | USER_RAYCASTING_PRESETS_DIRECTORY = USER_PRESET_DIR.joinpath("raycasting") |
13 | 31 | TEMP_DIR = tempfile.gettempdir() |
14 | 32 | |
33 | +USER_PLUGINS_DIRECTORY = USER_INV_DIR.joinpath("plugins") | |
34 | + | |
15 | 35 | OLD_USER_INV_DIR = USER_DIR.joinpath(".invesalius") |
16 | 36 | OLD_USER_PRESET_DIR = OLD_USER_INV_DIR.joinpath("presets") |
17 | 37 | OLD_USER_LOG_DIR = OLD_USER_INV_DIR.joinpath("logs") |
... | ... | @@ -26,6 +46,7 @@ RAYCASTING_PRESETS_COLOR_DIRECTORY = INV_TOP_DIR.joinpath( |
26 | 46 | "presets", "raycasting", "color_list" |
27 | 47 | ) |
28 | 48 | |
49 | + | |
29 | 50 | # Inside the windows executable |
30 | 51 | if hasattr(sys, "frozen") and ( |
31 | 52 | sys.frozen == "windows_exe" or sys.frozen == "console_exe" |
... | ... | @@ -71,6 +92,7 @@ def create_conf_folders(): |
71 | 92 | USER_INV_DIR.mkdir(parents=True, exist_ok=True) |
72 | 93 | USER_PRESET_DIR.mkdir(parents=True, exist_ok=True) |
73 | 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 | 98 | def copy_old_files(): | ... | ... |
... | ... | @@ -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 | 17 | # detalhes. |
18 | 18 | #-------------------------------------------------------------------------- |
19 | 19 | |
20 | -from six import with_metaclass | |
21 | - | |
22 | 20 | import datetime |
23 | 21 | import glob |
24 | 22 | import os |
... | ... | @@ -29,17 +27,18 @@ import tarfile |
29 | 27 | import tempfile |
30 | 28 | |
31 | 29 | import numpy as np |
30 | +import vtk | |
32 | 31 | import wx |
32 | +from six import with_metaclass | |
33 | 33 | from wx.lib.pubsub import pub as Publisher |
34 | -import vtk | |
35 | 34 | |
36 | 35 | import invesalius.constants as const |
37 | 36 | import invesalius.data.polydata_utils as pu |
38 | -from invesalius.presets import Presets | |
39 | -from invesalius.utils import Singleton, debug, touch, decode | |
40 | 37 | import invesalius.version as version |
41 | - | |
42 | 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 | 43 | if sys.platform == 'win32': |
45 | 44 | try: |
... | ... | @@ -277,20 +276,24 @@ class Project(with_metaclass(Singleton, object)): |
277 | 276 | os.remove(f) |
278 | 277 | |
279 | 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 | 279 | if not const.VTK_WARNING: |
285 | 280 | log_path = os.path.join(inv_paths.USER_LOG_DIR, 'vtkoutput.txt') |
286 | 281 | fow = vtk.vtkFileOutputWindow() |
287 | 282 | fow.SetFileName(log_path.encode(const.FS_ENCODE)) |
288 | 283 | ow = vtk.vtkOutputWindow() |
289 | 284 | ow.SetInstance(fow) |
290 | - | |
285 | + | |
291 | 286 | filelist = Extract(filename, tempfile.mkdtemp()) |
292 | 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 | 297 | # Opening the main file from invesalius 3 project |
295 | 298 | main_plist = os.path.join(dirpath ,'main.plist') |
296 | 299 | project = plistlib.readPlist(main_plist) |
... | ... | @@ -308,7 +311,7 @@ class Project(with_metaclass(Singleton, object)): |
308 | 311 | self.level = project["window_level"] |
309 | 312 | self.threshold_range = project["scalar_range"] |
310 | 313 | self.spacing = project["spacing"] |
311 | - if project.get("affine"): | |
314 | + if project.get("affine", ""): | |
312 | 315 | self.affine = project["affine"] |
313 | 316 | Publisher.sendMessage('Update affine matrix', |
314 | 317 | affine=self.affine, status=True) |
... | ... | @@ -323,7 +326,7 @@ class Project(with_metaclass(Singleton, object)): |
323 | 326 | |
324 | 327 | # Opening the masks |
325 | 328 | self.mask_dict = {} |
326 | - for index in project["masks"]: | |
329 | + for index in project.get("masks", []): | |
327 | 330 | filename = project["masks"][index] |
328 | 331 | filepath = os.path.join(dirpath, filename) |
329 | 332 | m = msk.Mask() |
... | ... | @@ -332,7 +335,7 @@ class Project(with_metaclass(Singleton, object)): |
332 | 335 | |
333 | 336 | # Opening the surfaces |
334 | 337 | self.surface_dict = {} |
335 | - for index in project["surfaces"]: | |
338 | + for index in project.get("surfaces", []): | |
336 | 339 | filename = project["surfaces"][index] |
337 | 340 | filepath = os.path.join(dirpath, filename) |
338 | 341 | s = srf.Surface(int(index)) |
... | ... | @@ -341,15 +344,50 @@ class Project(with_metaclass(Singleton, object)): |
341 | 344 | |
342 | 345 | # Opening the measurements |
343 | 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 | 392 | def export_project(self, filename, save_masks=True): |
355 | 393 | if filename.lower().endswith('.hdf5') or filename.lower().endswith('.h5'): | ... | ... |