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