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