Commit 5c51e20fac5da48a9d463f6ea92d10d6e5c4a739

Authored by Thiago Franco de Moraes
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
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
... ... @@ -486,6 +486,7 @@ ID_ABOUT = wx.ID_ABOUT
486 486 [wx.NewId() for number in range(3)]
487 487  
488 488 ID_START = wx.NewId()
  489 +ID_PLUGINS_SHOW_PATH = wx.NewId()
489 490  
490 491 ID_FLIP_X = wx.NewId()
491 492 ID_FLIP_Y = wx.NewId()
... ...
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(&quot;logs&quot;)
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():
... ...
invesalius/plugins.py 0 → 100644
... ... @@ -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'):
... ...