Commit 2fade61abc95553bd4a1b9f00981ee64e1c9c734

Authored by Santiago Castro
Committed by Thiago Franco de Moraes
1 parent 78173f1e
Exists in master

Add command line options for saving and exporting (#85)

* Create option in app.py to import and then save the project

* Add command to import and export in STL to all available presets

* Create option to open InVesalius without gui

* Add option --export

* No-gui doing less work when creating surface

* Print on debug

* Fix missing session assignment in non-gui mode

* Add option to import every group of DICOM images

* Checking surface index exists in actors_dict before removing it
.dockerignore 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +.git*
  2 +.python-version
  3 +Dockerfile
Dockerfile 0 → 100644
@@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
  1 +FROM ubuntu:16.04
  2 +
  3 +RUN apt-get update
  4 +RUN apt-get install -y \
  5 + cython \
  6 + locales \
  7 + python-concurrent.futures \
  8 + python-gdcm \
  9 + python-matplotlib \
  10 + python-nibabel \
  11 + python-numpy \
  12 + python-pil \
  13 + python-psutil \
  14 + python-scipy \
  15 + python-serial \
  16 + python-skimage \
  17 + python-vtk6 \
  18 + python-vtkgdcm \
  19 + python-wxgtk3.0
  20 +
  21 +RUN locale-gen en_US.UTF-8
  22 +ENV LANG en_US.UTF-8
  23 +ENV LANGUAGE en_US:en
  24 +ENV LC_ALL en_US.UTF-8
  25 +
  26 +WORKDIR /usr/local/app
  27 +
  28 +COPY . .
  29 +
  30 +RUN python setup.py build_ext --inplace
@@ -24,6 +24,9 @@ import optparse as op @@ -24,6 +24,9 @@ import optparse as op
24 import os 24 import os
25 import sys 25 import sys
26 import shutil 26 import shutil
  27 +import traceback
  28 +
  29 +import re
27 30
28 if sys.platform == 'win32': 31 if sys.platform == 'win32':
29 import _winreg 32 import _winreg
@@ -218,7 +221,8 @@ class SplashScreen(wx.SplashScreen): @@ -218,7 +221,8 @@ class SplashScreen(wx.SplashScreen):
218 self.control = Controller(self.main) 221 self.control = Controller(self.main)
219 222
220 self.fc = wx.FutureCall(1, self.ShowMain) 223 self.fc = wx.FutureCall(1, self.ShowMain)
221 - wx.FutureCall(1, parse_comand_line) 224 + options, args = parse_comand_line()
  225 + wx.FutureCall(1, use_cmd_optargs, options, args)
222 226
223 # Check for updates 227 # Check for updates
224 from threading import Thread 228 from threading import Thread
@@ -244,6 +248,24 @@ class SplashScreen(wx.SplashScreen): @@ -244,6 +248,24 @@ class SplashScreen(wx.SplashScreen):
244 if self.fc.IsRunning(): 248 if self.fc.IsRunning():
245 self.Raise() 249 self.Raise()
246 250
  251 +
  252 +def non_gui_startup(options, args):
  253 + lang = 'en'
  254 + _ = i18n.InstallLanguage(lang)
  255 +
  256 + from invesalius.control import Controller
  257 + from invesalius.project import Project
  258 +
  259 + session = ses.Session()
  260 + if not session.ReadSession():
  261 + session.CreateItens()
  262 + session.SetLanguage(lang)
  263 + session.WriteSessionFile()
  264 +
  265 + control = Controller(None)
  266 +
  267 + use_cmd_optargs(options, args)
  268 +
247 # ------------------------------------------------------------------ 269 # ------------------------------------------------------------------
248 270
249 271
@@ -262,39 +284,138 @@ def parse_comand_line(): @@ -262,39 +284,138 @@ def parse_comand_line():
262 action="store_true", 284 action="store_true",
263 dest="debug") 285 dest="debug")
264 286
  287 + parser.add_option('--no-gui',
  288 + action='store_true',
  289 + dest='no_gui')
  290 +
265 # -i or --import: import DICOM directory 291 # -i or --import: import DICOM directory
266 # chooses largest series 292 # chooses largest series
267 parser.add_option("-i", "--import", 293 parser.add_option("-i", "--import",
268 action="store", 294 action="store",
269 dest="dicom_dir") 295 dest="dicom_dir")
  296 +
  297 + parser.add_option("--import-all",
  298 + action="store")
  299 +
  300 + parser.add_option("-s", "--save",
  301 + help="Save the project after an import.")
  302 +
  303 + parser.add_option("-t", "--threshold",
  304 + help="Define the threshold for the export (e.g. 100-780).")
  305 +
  306 + parser.add_option("-e", "--export",
  307 + help="Export to STL.")
  308 +
  309 + parser.add_option("-a", "--export-to-all",
  310 + help="Export to STL for all mask presets.")
  311 +
270 options, args = parser.parse_args() 312 options, args = parser.parse_args()
  313 + return options, args
  314 +
271 315
  316 +def use_cmd_optargs(options, args):
272 # If debug argument... 317 # If debug argument...
273 if options.debug: 318 if options.debug:
274 Publisher.subscribe(print_events, Publisher.ALL_TOPICS) 319 Publisher.subscribe(print_events, Publisher.ALL_TOPICS)
  320 + session = ses.Session()
275 session.debug = 1 321 session.debug = 1
276 322
277 # If import DICOM argument... 323 # If import DICOM argument...
278 if options.dicom_dir: 324 if options.dicom_dir:
279 import_dir = options.dicom_dir 325 import_dir = options.dicom_dir
280 - Publisher.sendMessage('Import directory', import_dir) 326 + Publisher.sendMessage('Import directory', {'directory': import_dir, 'gui': not options.no_gui})
  327 +
  328 + if options.save:
  329 + Publisher.sendMessage('Save project', os.path.abspath(options.save))
  330 + exit(0)
  331 +
  332 + check_for_export(options)
  333 +
  334 + return True
  335 + elif options.import_all:
  336 + import invesalius.reader.dicom_reader as dcm
  337 + for patient in dcm.GetDicomGroups(options.import_all):
  338 + for group in patient.GetGroups():
  339 + Publisher.sendMessage('Import group', {'group': group, 'gui': not options.no_gui})
  340 + check_for_export(options, suffix=group.title, remove_surfaces=False)
  341 + Publisher.sendMessage('Remove masks', [0])
281 return True 342 return True
282 343
283 # Check if there is a file path somewhere in what the user wrote 344 # Check if there is a file path somewhere in what the user wrote
284 # In case there is, try opening as it was a inv3 345 # In case there is, try opening as it was a inv3
285 else: 346 else:
286 - i = len(args)  
287 - while i:  
288 - i -= 1  
289 - file = args[i].decode(sys.stdin.encoding)  
290 - if os.path.isfile(file):  
291 - path = os.path.abspath(file)  
292 - Publisher.sendMessage('Open project', path)  
293 - i = 0 347 + for arg in reversed(args):
  348 + if os.path.isfile(arg):
  349 + path_ = os.path.abspath(arg.decode(sys.stdin.encoding))
  350 + Publisher.sendMessage('Open project', path_)
  351 +
  352 + check_for_export(options)
294 return True 353 return True
295 return False 354 return False
296 355
297 356
  357 +def sanitize(text):
  358 + text = str(text).strip().replace(' ', '_')
  359 + return re.sub(r'(?u)[^-\w.]', '', text)
  360 +
  361 +
  362 +def check_for_export(options, suffix='', remove_surfaces=False):
  363 + suffix = sanitize(suffix)
  364 +
  365 + if options.export:
  366 + if not options.threshold:
  367 + print("Need option --threshold when using --export.")
  368 + exit(1)
  369 + threshold_range = tuple([int(n) for n in options.threshold.split(',')])
  370 +
  371 + if suffix:
  372 + if options.export.endswith('.stl'):
  373 + path_ = '{}-{}.stl'.format(options.export[:-4], suffix)
  374 + else:
  375 + path_ = '{}-{}.stl'.format(options.export, suffix)
  376 + else:
  377 + path_ = options.export
  378 +
  379 + export(path_, threshold_range, remove_surface=remove_surfaces)
  380 + elif options.export_to_all:
  381 + # noinspection PyBroadException
  382 + try:
  383 + from invesalius.project import Project
  384 +
  385 + for threshold_name, threshold_range in Project().presets.thresh_ct.iteritems():
  386 + if isinstance(threshold_range[0], int):
  387 + path_ = u'{}-{}-{}.stl'.format(options.export_to_all, suffix, threshold_name)
  388 + export(path_, threshold_range, remove_surface=True)
  389 + except:
  390 + traceback.print_exc()
  391 + finally:
  392 + exit(0)
  393 +
  394 +
  395 +def export(path_, threshold_range, remove_surface=False):
  396 + import invesalius.constants as const
  397 +
  398 + Publisher.sendMessage('Set threshold values', threshold_range)
  399 +
  400 + surface_options = {
  401 + 'method': {
  402 + 'algorithm': 'Default',
  403 + 'options': {},
  404 + }, 'options': {
  405 + 'index': 0,
  406 + 'name': '',
  407 + 'quality': _('Optimal *'),
  408 + 'fill': False,
  409 + 'keep_largest': False,
  410 + 'overwrite': False,
  411 + }
  412 + }
  413 + Publisher.sendMessage('Create surface from index', surface_options)
  414 + Publisher.sendMessage('Export surface to file', (path_, const.FILETYPE_STL))
  415 + if remove_surface:
  416 + Publisher.sendMessage('Remove surfaces', [0])
  417 +
  418 +
298 def print_events(data): 419 def print_events(data):
299 """ 420 """
300 Print pubsub messages 421 Print pubsub messages
@@ -305,8 +426,13 @@ def main(): @@ -305,8 +426,13 @@ def main():
305 """ 426 """
306 Initialize InVesalius GUI 427 Initialize InVesalius GUI
307 """ 428 """
308 - application = InVesalius(0)  
309 - application.MainLoop() 429 + options, args = parse_comand_line()
  430 +
  431 + if options.no_gui:
  432 + non_gui_startup(options, args)
  433 + else:
  434 + application = InVesalius(0)
  435 + application.MainLoop()
310 436
311 if __name__ == '__main__': 437 if __name__ == '__main__':
312 #Is needed because of pyinstaller 438 #Is needed because of pyinstaller
invesalius/constants.py
@@ -694,19 +694,19 @@ BTNS_IMG_MKS = {IR1: {0: 'LEI'}, @@ -694,19 +694,19 @@ BTNS_IMG_MKS = {IR1: {0: 'LEI'},
694 IR2: {1: 'REI'}, 694 IR2: {1: 'REI'},
695 IR3: {2: 'NAI'}} 695 IR3: {2: 'NAI'}}
696 696
697 -TIPS_IMG = [wx.ToolTip(_("Select left ear in image")),  
698 - wx.ToolTip(_("Select right ear in image")),  
699 - wx.ToolTip(_("Select nasion in image"))] 697 +TIPS_IMG = [_("Select left ear in image"),
  698 + _("Select right ear in image"),
  699 + _("Select nasion in image")]
700 700
701 BTNS_TRK = {TR1: {3: _('LET')}, 701 BTNS_TRK = {TR1: {3: _('LET')},
702 TR2: {4: _('RET')}, 702 TR2: {4: _('RET')},
703 TR3: {5: _('NAT')}, 703 TR3: {5: _('NAT')},
704 SET: {6: _('SET')}} 704 SET: {6: _('SET')}}
705 705
706 -TIPS_TRK = [wx.ToolTip(_("Select left ear with spatial tracker")),  
707 - wx.ToolTip(_("Select right ear with spatial tracker")),  
708 - wx.ToolTip(_("Select nasion with spatial tracker")),  
709 - wx.ToolTip(_("Show set coordinates in image"))] 706 +TIPS_TRK = [_("Select left ear with spatial tracker"),
  707 + _("Select right ear with spatial tracker"),
  708 + _("Select nasion with spatial tracker"),
  709 + _("Show set coordinates in image")]
710 710
711 CAL_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'mtc_files', 'CalibrationFiles')) 711 CAL_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'mtc_files', 'CalibrationFiles'))
712 MAR_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'mtc_files', 'Markers')) 712 MAR_DIR = os.path.abspath(os.path.join(FILE_PATH, '..', 'navigation', 'mtc_files', 'Markers'))
invesalius/control.py
@@ -68,6 +68,7 @@ class Controller(): @@ -68,6 +68,7 @@ class Controller():
68 68
69 def __bind_events(self): 69 def __bind_events(self):
70 Publisher.subscribe(self.OnImportMedicalImages, 'Import directory') 70 Publisher.subscribe(self.OnImportMedicalImages, 'Import directory')
  71 + Publisher.subscribe(self.OnImportGroup, 'Import group')
71 Publisher.subscribe(self.OnShowDialogImportDirectory, 72 Publisher.subscribe(self.OnShowDialogImportDirectory,
72 'Show import directory dialog') 73 'Show import directory dialog')
73 Publisher.subscribe(self.OnShowDialogImportOtherFiles, 74 Publisher.subscribe(self.OnShowDialogImportOtherFiles,
@@ -105,6 +106,8 @@ class Controller(): @@ -105,6 +106,8 @@ class Controller():
105 106
106 Publisher.subscribe(self.SetBitmapSpacing, 'Set bitmap spacing') 107 Publisher.subscribe(self.SetBitmapSpacing, 'Set bitmap spacing')
107 108
  109 + Publisher.subscribe(self.OnSaveProject, 'Save project')
  110 +
108 def SetBitmapSpacing(self, pubsub_evt): 111 def SetBitmapSpacing(self, pubsub_evt):
109 proj = prj.Project() 112 proj = prj.Project()
110 proj.spacing = pubsub_evt.data 113 proj.spacing = pubsub_evt.data
@@ -329,6 +332,10 @@ class Controller(): @@ -329,6 +332,10 @@ class Controller():
329 session.OpenProject(filepath) 332 session.OpenProject(filepath)
330 Publisher.sendMessage("Enable state project", True) 333 Publisher.sendMessage("Enable state project", True)
331 334
  335 + def OnSaveProject(self, pubsub_evt):
  336 + path = pubsub_evt.data
  337 + self.SaveProject(path)
  338 +
332 def SaveProject(self, path=None): 339 def SaveProject(self, path=None):
333 Publisher.sendMessage('Begin busy cursor') 340 Publisher.sendMessage('Begin busy cursor')
334 session = ses.Session() 341 session = ses.Session()
@@ -442,10 +449,11 @@ class Controller(): @@ -442,10 +449,11 @@ class Controller():
442 #----------- to import by command line --------------------------------------------------- 449 #----------- to import by command line ---------------------------------------------------
443 450
444 def OnImportMedicalImages(self, pubsub_evt): 451 def OnImportMedicalImages(self, pubsub_evt):
445 - directory = pubsub_evt.data  
446 - self.ImportMedicalImages(directory) 452 + directory = pubsub_evt.data['directory']
  453 + gui = pubsub_evt.data['gui']
  454 + self.ImportMedicalImages(directory, gui)
447 455
448 - def ImportMedicalImages(self, directory): 456 + def ImportMedicalImages(self, directory, gui=True):
449 patients_groups = dcm.GetDicomGroups(directory) 457 patients_groups = dcm.GetDicomGroups(directory)
450 name = directory.rpartition('\\')[-1].split('.') 458 name = directory.rpartition('\\')[-1].split('.')
451 print "patients: ", patients_groups 459 print "patients: ", patients_groups
@@ -453,7 +461,7 @@ class Controller(): @@ -453,7 +461,7 @@ class Controller():
453 if len(patients_groups): 461 if len(patients_groups):
454 # OPTION 1: DICOM 462 # OPTION 1: DICOM
455 group = dcm.SelectLargerDicomGroup(patients_groups) 463 group = dcm.SelectLargerDicomGroup(patients_groups)
456 - matrix, matrix_filename, dicom = self.OpenDicomGroup(group, 0, [0, 0], gui=True) 464 + matrix, matrix_filename, dicom = self.OpenDicomGroup(group, 0, [0, 0], gui=gui)
457 self.CreateDicomProject(dicom, matrix, matrix_filename) 465 self.CreateDicomProject(dicom, matrix, matrix_filename)
458 else: 466 else:
459 # OPTION 2: NIfTI, Analyze or PAR/REC 467 # OPTION 2: NIfTI, Analyze or PAR/REC
@@ -476,6 +484,18 @@ class Controller(): @@ -476,6 +484,18 @@ class Controller():
476 self.LoadProject() 484 self.LoadProject()
477 Publisher.sendMessage("Enable state project", True) 485 Publisher.sendMessage("Enable state project", True)
478 486
  487 + def OnImportGroup(self, pubsub_evt):
  488 + group = pubsub_evt.data['group']
  489 + gui = pubsub_evt.data['gui']
  490 + self.ImportGroup(group, gui)
  491 +
  492 + def ImportGroup(self, group, gui=True):
  493 + matrix, matrix_filename, dicom = self.OpenDicomGroup(group, 0, [0, 0], gui=gui)
  494 + self.CreateDicomProject(dicom, matrix, matrix_filename)
  495 +
  496 + self.LoadProject()
  497 + Publisher.sendMessage("Enable state project", True)
  498 +
479 #------------------------------------------------------------------------------------- 499 #-------------------------------------------------------------------------------------
480 500
481 def LoadProject(self): 501 def LoadProject(self):
@@ -785,7 +805,7 @@ class Controller(): @@ -785,7 +805,7 @@ class Controller():
785 n_slices = len(filelist) 805 n_slices = len(filelist)
786 resolution_percentage = utils.calculate_resizing_tofitmemory(int(sx), int(sy), n_slices, bits/8) 806 resolution_percentage = utils.calculate_resizing_tofitmemory(int(sx), int(sy), n_slices, bits/8)
787 807
788 - if resolution_percentage < 1.0: 808 + if resolution_percentage < 1.0 and gui:
789 re_dialog = dialog.ResizeImageDialog() 809 re_dialog = dialog.ResizeImageDialog()
790 re_dialog.SetValue(int(resolution_percentage*100)) 810 re_dialog.SetValue(int(resolution_percentage*100))
791 re_dialog_value = re_dialog.ShowModal() 811 re_dialog_value = re_dialog.ShowModal()
invesalius/data/slice_.py
@@ -359,19 +359,22 @@ class Slice(object): @@ -359,19 +359,22 @@ class Slice(object):
359 359
360 # TODO: merge this code with apply_slice_buffer_to_mask 360 # TODO: merge this code with apply_slice_buffer_to_mask
361 b_mask = self.buffer_slices["AXIAL"].mask 361 b_mask = self.buffer_slices["AXIAL"].mask
362 - n = self.buffer_slices["AXIAL"].index + 1  
363 - self.current_mask.matrix[n, 1:, 1:] = b_mask  
364 - self.current_mask.matrix[n, 0, 0] = 1 362 + if b_mask is not None:
  363 + n = self.buffer_slices["AXIAL"].index + 1
  364 + self.current_mask.matrix[n, 1:, 1:] = b_mask
  365 + self.current_mask.matrix[n, 0, 0] = 1
365 366
366 b_mask = self.buffer_slices["CORONAL"].mask 367 b_mask = self.buffer_slices["CORONAL"].mask
367 - n = self.buffer_slices["CORONAL"].index + 1  
368 - self.current_mask.matrix[1:, n, 1:] = b_mask  
369 - self.current_mask.matrix[0, n, 0] = 1 368 + if b_mask is not None:
  369 + n = self.buffer_slices["CORONAL"].index + 1
  370 + self.current_mask.matrix[1:, n, 1:] = b_mask
  371 + self.current_mask.matrix[0, n, 0] = 1
370 372
371 b_mask = self.buffer_slices["SAGITAL"].mask 373 b_mask = self.buffer_slices["SAGITAL"].mask
372 - n = self.buffer_slices["SAGITAL"].index + 1  
373 - self.current_mask.matrix[1:, 1:, n] = b_mask  
374 - self.current_mask.matrix[0, 0, n] = 1 374 + if b_mask is not None:
  375 + n = self.buffer_slices["SAGITAL"].index + 1
  376 + self.current_mask.matrix[1:, 1:, n] = b_mask
  377 + self.current_mask.matrix[0, 0, n] = 1
375 378
376 if to_reload: 379 if to_reload:
377 Publisher.sendMessage('Reload actual slice') 380 Publisher.sendMessage('Reload actual slice')
@@ -888,7 +891,8 @@ class Slice(object): @@ -888,7 +891,8 @@ class Slice(object):
888 self.current_mask.matrix[n+1, 1:, 1:] = m 891 self.current_mask.matrix[n+1, 1:, 1:] = m
889 else: 892 else:
890 slice_ = self.buffer_slices[orientation].image 893 slice_ = self.buffer_slices[orientation].image
891 - self.buffer_slices[orientation].mask = (255 * ((slice_ >= thresh_min) & (slice_ <= thresh_max))).astype('uint8') 894 + if slice_ is not None:
  895 + self.buffer_slices[orientation].mask = (255 * ((slice_ >= thresh_min) & (slice_ <= thresh_max))).astype('uint8')
892 896
893 # Update viewer 897 # Update viewer
894 #Publisher.sendMessage('Update slice viewer') 898 #Publisher.sendMessage('Update slice viewer')
invesalius/data/surface.py
@@ -191,14 +191,15 @@ class SurfaceManager(): @@ -191,14 +191,15 @@ class SurfaceManager():
191 if selected_items: 191 if selected_items:
192 for index in selected_items: 192 for index in selected_items:
193 proj.RemoveSurface(index) 193 proj.RemoveSurface(index)
194 - actor = old_dict[index]  
195 - for i in old_dict:  
196 - if i < index:  
197 - new_dict[i] = old_dict[i]  
198 - if i > index:  
199 - new_dict[i-1] = old_dict[i]  
200 - old_dict = new_dict  
201 - Publisher.sendMessage('Remove surface actor from viewer', actor) 194 + if index in old_dict:
  195 + actor = old_dict[index]
  196 + for i in old_dict:
  197 + if i < index:
  198 + new_dict[i] = old_dict[i]
  199 + if i > index:
  200 + new_dict[i-1] = old_dict[i]
  201 + old_dict = new_dict
  202 + Publisher.sendMessage('Remove surface actor from viewer', actor)
202 self.actors_dict = new_dict 203 self.actors_dict = new_dict
203 204
204 if self.last_surface_index in selected_items: 205 if self.last_surface_index in selected_items:
@@ -674,7 +675,6 @@ class SurfaceManager(): @@ -674,7 +675,6 @@ class SurfaceManager():
674 # polydata.SetSource(None) 675 # polydata.SetSource(None)
675 del decimation 676 del decimation
676 677
677 - to_measure = polydata  
678 #to_measure.Register(None) 678 #to_measure.Register(None)
679 # to_measure.SetSource(None) 679 # to_measure.SetSource(None)
680 680
@@ -713,115 +713,133 @@ class SurfaceManager(): @@ -713,115 +713,133 @@ class SurfaceManager():
713 # polydata.DebugOn() 713 # polydata.DebugOn()
714 del filled_polydata 714 del filled_polydata
715 715
716 - normals = vtk.vtkPolyDataNormals()  
717 - # normals.ReleaseDataFlagOn()  
718 - normals_ref = weakref.ref(normals)  
719 - normals_ref().AddObserver("ProgressEvent", lambda obj,evt:  
720 - UpdateProgress(normals_ref(), _("Creating 3D surface...")))  
721 - normals.SetInputData(polydata)  
722 - normals.SetFeatureAngle(80)  
723 - normals.AutoOrientNormalsOn()  
724 - # normals.GetOutput().ReleaseDataFlagOn()  
725 - normals.Update()  
726 - del polydata  
727 - polydata = normals.GetOutput()  
728 - #polydata.Register(None)  
729 - # polydata.SetSource(None)  
730 - del normals  
731 -  
732 - # Improve performance  
733 - stripper = vtk.vtkStripper()  
734 - # stripper.ReleaseDataFlagOn()  
735 - stripper_ref = weakref.ref(stripper)  
736 - stripper_ref().AddObserver("ProgressEvent", lambda obj,evt:  
737 - UpdateProgress(stripper_ref(), _("Creating 3D surface...")))  
738 - stripper.SetInputData(polydata)  
739 - stripper.PassThroughCellIdsOn()  
740 - stripper.PassThroughPointIdsOn()  
741 - # stripper.GetOutput().ReleaseDataFlagOn()  
742 - stripper.Update()  
743 - del polydata  
744 - polydata = stripper.GetOutput()  
745 - #polydata.Register(None)  
746 - # polydata.SetSource(None)  
747 - del stripper 716 + to_measure = polydata
748 717
749 - # Map polygonal data (vtkPolyData) to graphics primitives.  
750 - mapper = vtk.vtkPolyDataMapper()  
751 - mapper.SetInputData(polydata)  
752 - mapper.ScalarVisibilityOff()  
753 - # mapper.ReleaseDataFlagOn()  
754 - mapper.ImmediateModeRenderingOn() # improve performance 718 + # If InVesalius is running without GUI
  719 + if wx.GetApp() is None:
  720 + proj = prj.Project()
  721 + #Create Surface instance
  722 + if overwrite:
  723 + surface = Surface(index = self.last_surface_index)
  724 + proj.ChangeSurface(surface)
  725 + else:
  726 + surface = Surface(name=surface_name)
  727 + index = proj.AddSurface(surface)
  728 + surface.index = index
  729 + self.last_surface_index = index
  730 + surface.colour = colour
  731 + surface.polydata = polydata
755 732
756 - # Represent an object (geometry & properties) in the rendered scene  
757 - actor = vtk.vtkActor()  
758 - actor.SetMapper(mapper)  
759 - del mapper  
760 - #Create Surface instance  
761 - if overwrite:  
762 - surface = Surface(index = self.last_surface_index) 733 + # With GUI
763 else: 734 else:
764 - surface = Surface(name=surface_name)  
765 - surface.colour = colour  
766 - surface.polydata = polydata  
767 - del polydata 735 + normals = vtk.vtkPolyDataNormals()
  736 + # normals.ReleaseDataFlagOn()
  737 + normals_ref = weakref.ref(normals)
  738 + normals_ref().AddObserver("ProgressEvent", lambda obj,evt:
  739 + UpdateProgress(normals_ref(), _("Creating 3D surface...")))
  740 + normals.SetInputData(polydata)
  741 + normals.SetFeatureAngle(80)
  742 + normals.AutoOrientNormalsOn()
  743 + # normals.GetOutput().ReleaseDataFlagOn()
  744 + normals.Update()
  745 + del polydata
  746 + polydata = normals.GetOutput()
  747 + #polydata.Register(None)
  748 + # polydata.SetSource(None)
  749 + del normals
768 750
769 - # Set actor colour and transparency  
770 - actor.GetProperty().SetColor(colour)  
771 - actor.GetProperty().SetOpacity(1-surface.transparency) 751 + # Improve performance
  752 + stripper = vtk.vtkStripper()
  753 + # stripper.ReleaseDataFlagOn()
  754 + stripper_ref = weakref.ref(stripper)
  755 + stripper_ref().AddObserver("ProgressEvent", lambda obj,evt:
  756 + UpdateProgress(stripper_ref(), _("Creating 3D surface...")))
  757 + stripper.SetInputData(polydata)
  758 + stripper.PassThroughCellIdsOn()
  759 + stripper.PassThroughPointIdsOn()
  760 + # stripper.GetOutput().ReleaseDataFlagOn()
  761 + stripper.Update()
  762 + del polydata
  763 + polydata = stripper.GetOutput()
  764 + #polydata.Register(None)
  765 + # polydata.SetSource(None)
  766 + del stripper
772 767
773 - prop = actor.GetProperty() 768 + # Map polygonal data (vtkPolyData) to graphics primitives.
  769 + mapper = vtk.vtkPolyDataMapper()
  770 + mapper.SetInputData(polydata)
  771 + mapper.ScalarVisibilityOff()
  772 + # mapper.ReleaseDataFlagOn()
  773 + mapper.ImmediateModeRenderingOn() # improve performance
774 774
775 - interpolation = int(ses.Session().surface_interpolation) 775 + # Represent an object (geometry & properties) in the rendered scene
  776 + actor = vtk.vtkActor()
  777 + actor.SetMapper(mapper)
  778 + del mapper
  779 + #Create Surface instance
  780 + if overwrite:
  781 + surface = Surface(index = self.last_surface_index)
  782 + else:
  783 + surface = Surface(name=surface_name)
  784 + surface.colour = colour
  785 + surface.polydata = polydata
  786 + del polydata
776 787
777 - prop.SetInterpolation(interpolation) 788 + # Set actor colour and transparency
  789 + actor.GetProperty().SetColor(colour)
  790 + actor.GetProperty().SetOpacity(1-surface.transparency)
778 791
779 - proj = prj.Project()  
780 - if overwrite:  
781 - proj.ChangeSurface(surface)  
782 - else:  
783 - index = proj.AddSurface(surface)  
784 - surface.index = index  
785 - self.last_surface_index = index 792 + prop = actor.GetProperty()
786 793
787 - session = ses.Session()  
788 - session.ChangeProject() 794 + interpolation = int(ses.Session().surface_interpolation)
789 795
790 - # The following lines have to be here, otherwise all volumes disappear  
791 - measured_polydata = vtk.vtkMassProperties()  
792 - # measured_polydata.ReleaseDataFlagOn()  
793 - measured_polydata.SetInputData(to_measure)  
794 - volume = float(measured_polydata.GetVolume())  
795 - area = float(measured_polydata.GetSurfaceArea())  
796 - surface.volume = volume  
797 - surface.area = area  
798 - self.last_surface_index = surface.index  
799 - del measured_polydata  
800 - del to_measure 796 + prop.SetInterpolation(interpolation)
801 797
802 - Publisher.sendMessage('Load surface actor into viewer', actor) 798 + proj = prj.Project()
  799 + if overwrite:
  800 + proj.ChangeSurface(surface)
  801 + else:
  802 + index = proj.AddSurface(surface)
  803 + surface.index = index
  804 + self.last_surface_index = index
803 805
804 - # Send actor by pubsub to viewer's render  
805 - if overwrite and self.actors_dict.keys():  
806 - old_actor = self.actors_dict[self.last_surface_index]  
807 - Publisher.sendMessage('Remove surface actor from viewer', old_actor) 806 + session = ses.Session()
  807 + session.ChangeProject()
808 808
809 - # Save actor for future management tasks  
810 - self.actors_dict[surface.index] = actor 809 + measured_polydata = vtk.vtkMassProperties()
  810 + # measured_polydata.ReleaseDataFlagOn()
  811 + measured_polydata.SetInputData(to_measure)
  812 + volume = float(measured_polydata.GetVolume())
  813 + area = float(measured_polydata.GetSurfaceArea())
  814 + surface.volume = volume
  815 + surface.area = area
  816 + self.last_surface_index = surface.index
  817 + del measured_polydata
  818 + del to_measure
811 819
812 - Publisher.sendMessage('Update surface info in GUI',  
813 - (surface.index, surface.name,  
814 - surface.colour, surface.volume,  
815 - surface.area,  
816 - surface.transparency))  
817 -  
818 - #When you finalize the progress. The bar is cleaned.  
819 - UpdateProgress = vu.ShowProgress(1)  
820 - UpdateProgress(0, _("Ready"))  
821 - Publisher.sendMessage('Update status text in GUI', _("Ready"))  
822 -  
823 - Publisher.sendMessage('End busy cursor')  
824 - del actor 820 + Publisher.sendMessage('Load surface actor into viewer', actor)
  821 +
  822 + # Send actor by pubsub to viewer's render
  823 + if overwrite and self.actors_dict.keys():
  824 + old_actor = self.actors_dict[self.last_surface_index]
  825 + Publisher.sendMessage('Remove surface actor from viewer', old_actor)
  826 +
  827 + # Save actor for future management tasks
  828 + self.actors_dict[surface.index] = actor
  829 +
  830 + Publisher.sendMessage('Update surface info in GUI',
  831 + (surface.index, surface.name,
  832 + surface.colour, surface.volume,
  833 + surface.area,
  834 + surface.transparency))
  835 +
  836 + #When you finalize the progress. The bar is cleaned.
  837 + UpdateProgress = vu.ShowProgress(1)
  838 + UpdateProgress(0, _("Ready"))
  839 + Publisher.sendMessage('Update status text in GUI', _("Ready"))
  840 +
  841 + Publisher.sendMessage('End busy cursor')
  842 + del actor
825 843
826 def UpdateSurfaceInterpolation(self, pub_evt): 844 def UpdateSurfaceInterpolation(self, pub_evt):
827 interpolation = int(ses.Session().surface_interpolation) 845 interpolation = int(ses.Session().surface_interpolation)
invesalius/data/vtk_utils.py
@@ -44,7 +44,10 @@ def ShowProgress(number_of_filters = 1, @@ -44,7 +44,10 @@ def ShowProgress(number_of_filters = 1,
44 progress = [0] 44 progress = [0]
45 last_obj_progress = [0] 45 last_obj_progress = [0]
46 if (dialog_type == "ProgressDialog"): 46 if (dialog_type == "ProgressDialog"):
47 - dlg = ProgressDialog(100) 47 + try:
  48 + dlg = ProgressDialog(100)
  49 + except wx._core.PyNoAppError:
  50 + return lambda obj, label: 0
48 51
49 52
50 # when the pipeline is larger than 1, we have to consider this object 53 # when the pipeline is larger than 1, we have to consider this object
invesalius/gui/task_navigator.py
@@ -247,7 +247,7 @@ class NeuronavigationPanel(wx.Panel): @@ -247,7 +247,7 @@ class NeuronavigationPanel(wx.Panel):
247 n = btns_img[k].keys()[0] 247 n = btns_img[k].keys()[0]
248 lab = btns_img[k].values()[0] 248 lab = btns_img[k].values()[0]
249 self.btns_coord[n] = wx.ToggleButton(self, k, label=lab, size=wx.Size(45, 23)) 249 self.btns_coord[n] = wx.ToggleButton(self, k, label=lab, size=wx.Size(45, 23))
250 - self.btns_coord[n].SetToolTip(tips_img[n]) 250 + self.btns_coord[n].SetToolTip(wx.ToolTip(tips_img[n]))
251 self.btns_coord[n].Bind(wx.EVT_TOGGLEBUTTON, self.OnImageFiducials) 251 self.btns_coord[n].Bind(wx.EVT_TOGGLEBUTTON, self.OnImageFiducials)
252 252
253 # Push buttons for tracker fiducials 253 # Push buttons for tracker fiducials
@@ -258,7 +258,7 @@ class NeuronavigationPanel(wx.Panel): @@ -258,7 +258,7 @@ class NeuronavigationPanel(wx.Panel):
258 n = btns_trk[k].keys()[0] 258 n = btns_trk[k].keys()[0]
259 lab = btns_trk[k].values()[0] 259 lab = btns_trk[k].values()[0]
260 self.btns_coord[n] = wx.Button(self, k, label=lab, size=wx.Size(45, 23)) 260 self.btns_coord[n] = wx.Button(self, k, label=lab, size=wx.Size(45, 23))
261 - self.btns_coord[n].SetToolTip(tips_trk[n-3]) 261 + self.btns_coord[n].SetToolTip(wx.ToolTip(tips_trk[n-3]))
262 # Excepetion for event of button that set image coordinates 262 # Excepetion for event of button that set image coordinates
263 if n == 6: 263 if n == 6:
264 self.btns_coord[n].Bind(wx.EVT_BUTTON, self.OnSetImageCoordinates) 264 self.btns_coord[n].Bind(wx.EVT_BUTTON, self.OnSetImageCoordinates)
invesalius/utils.py
@@ -76,6 +76,7 @@ def debug(error_str): @@ -76,6 +76,7 @@ def debug(error_str):
76 from invesalius.session import Session 76 from invesalius.session import Session
77 session = Session() 77 session = Session()
78 #if session.debug: 78 #if session.debug:
  79 + print(error_str)
79 80
80 def next_copy_name(original_name, names_list): 81 def next_copy_name(original_name, names_list):
81 """ 82 """