Commit c60264663d8996611a3f055a5de54c86e2a3ea05

Authored by Thiago Franco de Moraes
Committed by GitHub
1 parent 6dc0fd02
Exists in master

Cancelable surface creation (#147) closes #142

* Creating the surface using function instead of a class

* Generating and joining surface using multiprocessing

* Stripper and normal calc in surface process

* Calculating volume and area in the surface process

* Better messages in the progress dialog

* Cancel working in windows

* Using callback

* Using callback errors

* python2 doesnt have error_callback
invesalius/data/interpolation.pyx
@@ -4,7 +4,7 @@ import numpy as np @@ -4,7 +4,7 @@ import numpy as np
4 cimport numpy as np 4 cimport numpy as np
5 cimport cython 5 cimport cython
6 6
7 -from libc.math cimport floor, ceil, sqrt, fabs, round, sin, M_PI 7 +from libc.math cimport floor, ceil, sqrt, fabs, sin, M_PI
8 from cython.parallel import prange 8 from cython.parallel import prange
9 9
10 DEF LANCZOS_A = 4 10 DEF LANCZOS_A = 4
@@ -81,7 +81,7 @@ cdef double[64][64] temp = [ @@ -81,7 +81,7 @@ cdef double[64][64] temp = [
81 @cython.cdivision(True) 81 @cython.cdivision(True)
82 @cython.wraparound(False) 82 @cython.wraparound(False)
83 cdef double nearest_neighbour_interp(image_t[:, :, :] V, double x, double y, double z) nogil: 83 cdef double nearest_neighbour_interp(image_t[:, :, :] V, double x, double y, double z) nogil:
84 - return V[<int>round(z), <int>round(y), <int>round(x)] 84 + return V[<int>(z), <int>(y), <int>(x)]
85 85
86 @cython.boundscheck(False) # turn of bounds-checking for entire function 86 @cython.boundscheck(False) # turn of bounds-checking for entire function
87 @cython.cdivision(True) 87 @cython.cdivision(True)
invesalius/data/surface.py
@@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
17 # detalhes. 17 # detalhes.
18 #-------------------------------------------------------------------------- 18 #--------------------------------------------------------------------------
19 19
  20 +import functools
20 import multiprocessing 21 import multiprocessing
21 import os 22 import os
22 import plistlib 23 import plistlib
@@ -24,10 +25,19 @@ import random @@ -24,10 +25,19 @@ import random
24 import shutil 25 import shutil
25 import sys 26 import sys
26 import tempfile 27 import tempfile
  28 +import time
  29 +import traceback
27 import weakref 30 import weakref
28 31
  32 +try:
  33 + import queue
  34 +except ImportError:
  35 + import Queue as queue
  36 +
29 import vtk 37 import vtk
30 import wx 38 import wx
  39 +import wx.lib.agw.genericmessagedialog as GMD
  40 +
31 from wx.lib.pubsub import pub as Publisher 41 from wx.lib.pubsub import pub as Publisher
32 42
33 if sys.platform == 'win32': 43 if sys.platform == 'win32':
@@ -48,9 +58,12 @@ import invesalius.data.surface_process as surface_process @@ -48,9 +58,12 @@ import invesalius.data.surface_process as surface_process
48 import invesalius.utils as utl 58 import invesalius.utils as utl
49 import invesalius.data.vtk_utils as vu 59 import invesalius.data.vtk_utils as vu
50 60
  61 +from invesalius.gui import dialogs
  62 +
51 from invesalius.data import cy_mesh 63 from invesalius.data import cy_mesh
52 # TODO: Verificar ReleaseDataFlagOn and SetSource 64 # TODO: Verificar ReleaseDataFlagOn and SetSource
53 65
  66 +
54 class Surface(): 67 class Surface():
55 """ 68 """
56 Represent both vtkPolyData and associated properties. 69 Represent both vtkPolyData and associated properties.
@@ -438,10 +451,81 @@ class SurfaceManager(): @@ -438,10 +451,81 @@ class SurfaceManager():
438 #### 451 ####
439 #(mask_index, surface_name, quality, fill_holes, keep_largest) 452 #(mask_index, surface_name, quality, fill_holes, keep_largest)
440 453
  454 + def _on_complete_surface_creation(self, args, overwrite, surface_name, colour, dialog):
  455 + surface_filename, surface_measures = args
  456 + print(surface_filename, surface_measures)
  457 + reader = vtk.vtkXMLPolyDataReader()
  458 + reader.SetFileName(surface_filename)
  459 + reader.Update()
  460 + polydata = reader.GetOutput()
  461 +
  462 + # Map polygonal data (vtkPolyData) to graphics primitives.
  463 + mapper = vtk.vtkPolyDataMapper()
  464 + mapper.SetInputData(polydata)
  465 + mapper.ScalarVisibilityOff()
  466 + # mapper.ReleaseDataFlagOn()
  467 + mapper.ImmediateModeRenderingOn() # improve performance
  468 +
  469 + # Represent an object (geometry & properties) in the rendered scene
  470 + actor = vtk.vtkActor()
  471 + actor.SetMapper(mapper)
  472 + del mapper
  473 + #Create Surface instance
  474 + if overwrite:
  475 + surface = Surface(index = self.last_surface_index)
  476 + else:
  477 + surface = Surface(name=surface_name)
  478 + surface.colour = colour
  479 + surface.polydata = polydata
  480 + surface.volume = surface_measures['volume']
  481 + surface.area = surface_measures['area']
  482 + del polydata
  483 +
  484 + # Set actor colour and transparency
  485 + actor.GetProperty().SetColor(colour)
  486 + actor.GetProperty().SetOpacity(1-surface.transparency)
  487 +
  488 + prop = actor.GetProperty()
  489 +
  490 + interpolation = int(ses.Session().surface_interpolation)
  491 +
  492 + prop.SetInterpolation(interpolation)
  493 +
  494 + proj = prj.Project()
  495 + if overwrite:
  496 + proj.ChangeSurface(surface)
  497 + else:
  498 + index = proj.AddSurface(surface)
  499 + surface.index = index
  500 + self.last_surface_index = index
  501 +
  502 + session = ses.Session()
  503 + session.ChangeProject()
  504 +
  505 + Publisher.sendMessage('Load surface actor into viewer', actor=actor)
  506 +
  507 + # Send actor by pubsub to viewer's render
  508 + if overwrite and self.actors_dict.keys():
  509 + old_actor = self.actors_dict[self.last_surface_index]
  510 + Publisher.sendMessage('Remove surface actor from viewer', actor=old_actor)
  511 +
  512 + # Save actor for future management tasks
  513 + self.actors_dict[surface.index] = actor
  514 + Publisher.sendMessage('Update surface info in GUI', surface=surface)
  515 + Publisher.sendMessage('End busy cursor')
  516 +
  517 + dialog.running = False
  518 +
  519 + def _on_callback_error(self, e, dialog=None):
  520 + dialog.running = False
  521 + msg = utl.log_traceback(e)
  522 + dialog.error = msg
  523 +
441 def AddNewActor(self, slice_, mask, surface_parameters): 524 def AddNewActor(self, slice_, mask, surface_parameters):
442 """ 525 """
443 Create surface actor, save into project and send it to viewer. 526 Create surface actor, save into project and send it to viewer.
444 """ 527 """
  528 + t_init = time.time()
445 matrix = slice_.matrix 529 matrix = slice_.matrix
446 filename_img = slice_.matrix_filename 530 filename_img = slice_.matrix_filename
447 spacing = slice_.spacing 531 spacing = slice_.spacing
@@ -470,9 +554,6 @@ class SurfaceManager(): @@ -470,9 +554,6 @@ class SurfaceManager():
470 smooth_relaxation_factor = const.SURFACE_QUALITY[quality][2] 554 smooth_relaxation_factor = const.SURFACE_QUALITY[quality][2]
471 decimate_reduction = const.SURFACE_QUALITY[quality][3] 555 decimate_reduction = const.SURFACE_QUALITY[quality][3]
472 556
473 - #if imagedata_resolution:  
474 - #imagedata = iu.ResampleImage3D(imagedata, imagedata_resolution)  
475 -  
476 pipeline_size = 4 557 pipeline_size = 4
477 if decimate_reduction: 558 if decimate_reduction:
478 pipeline_size += 1 559 pipeline_size += 1
@@ -483,10 +564,6 @@ class SurfaceManager(): @@ -483,10 +564,6 @@ class SurfaceManager():
483 if keep_largest: 564 if keep_largest:
484 pipeline_size += 1 565 pipeline_size += 1
485 566
486 - ## Update progress value in GUI  
487 - UpdateProgress = vu.ShowProgress(pipeline_size)  
488 - UpdateProgress(0, _("Creating 3D surface..."))  
489 -  
490 language = ses.Session().language 567 language = ses.Session().language
491 568
492 if (prj.Project().original_orientation == const.CORONAL): 569 if (prj.Project().original_orientation == const.CORONAL):
@@ -496,220 +573,59 @@ class SurfaceManager(): @@ -496,220 +573,59 @@ class SurfaceManager():
496 573
497 n_processors = multiprocessing.cpu_count() 574 n_processors = multiprocessing.cpu_count()
498 575
499 - pipe_in, pipe_out = multiprocessing.Pipe()  
500 o_piece = 1 576 o_piece = 1
501 - piece_size = 2000 577 + piece_size = 20
502 578
503 n_pieces = int(round(matrix.shape[0] / piece_size + 0.5, 0)) 579 n_pieces = int(round(matrix.shape[0] / piece_size + 0.5, 0))
504 580
505 - q_in = multiprocessing.Queue()  
506 - q_out = multiprocessing.Queue()  
507 -  
508 - p = []  
509 - for i in range(n_processors):  
510 - sp = surface_process.SurfaceProcess(pipe_in, filename_img,  
511 - matrix.shape, matrix.dtype,  
512 - mask.temp_file,  
513 - mask.matrix.shape,  
514 - mask.matrix.dtype,  
515 - spacing,  
516 - mode, min_value, max_value,  
517 - decimate_reduction,  
518 - smooth_relaxation_factor,  
519 - smooth_iterations, language,  
520 - flip_image, q_in, q_out,  
521 - algorithm != 'Default',  
522 - algorithm,  
523 - imagedata_resolution)  
524 - p.append(sp)  
525 - sp.start()  
526 -  
527 - for i in range(n_pieces):  
528 - init = i * piece_size  
529 - end = init + piece_size + o_piece  
530 - roi = slice(init, end)  
531 - q_in.put(roi)  
532 - print("new_piece", roi)  
533 -  
534 - for i in p:  
535 - q_in.put(None)  
536 -  
537 - none_count = 1  
538 - while 1:  
539 - msg = pipe_out.recv()  
540 - if(msg is None):  
541 - none_count += 1  
542 - else:  
543 - UpdateProgress(msg[0]/(n_pieces * pipeline_size), msg[1])  
544 -  
545 - if none_count > n_pieces:  
546 - break 581 + filenames = []
  582 + pool = multiprocessing.Pool(processes=min(n_pieces, n_processors))
  583 + manager = multiprocessing.Manager()
  584 + msg_queue = manager.Queue(1)
547 585
548 - polydata_append = vtk.vtkAppendPolyData()  
549 - # polydata_append.ReleaseDataFlagOn()  
550 - t = n_pieces  
551 - while t:  
552 - filename_polydata = q_out.get() 586 + # If InVesalius is running without GUI
  587 + if wx.GetApp() is None:
  588 + for i in range(n_pieces):
  589 + init = i * piece_size
  590 + end = init + piece_size + o_piece
  591 + roi = slice(init, end)
  592 + print("new_piece", roi)
  593 + f = pool.apply_async(surface_process.create_surface_piece,
  594 + args = (filename_img, matrix.shape, matrix.dtype,
  595 + mask.temp_file, mask.matrix.shape,
  596 + mask.matrix.dtype, roi, spacing, mode,
  597 + min_value, max_value, decimate_reduction,
  598 + smooth_relaxation_factor,
  599 + smooth_iterations, language, flip_image,
  600 + algorithm != 'Default', algorithm,
  601 + imagedata_resolution),
  602 + callback=lambda x: filenames.append(x))
  603 +
  604 + while len(filenames) != n_pieces:
  605 + time.sleep(0.25)
  606 +
  607 + f = pool.apply_async(surface_process.join_process_surface,
  608 + args=(filenames, algorithm, smooth_iterations,
  609 + smooth_relaxation_factor,
  610 + decimate_reduction, keep_largest,
  611 + fill_holes, options, msg_queue))
  612 +
  613 + while not f.ready():
  614 + time.sleep(0.25)
  615 +
  616 + try:
  617 + surface_filename, surface_measures = f.get()
  618 + except Exception as e:
  619 + print(_("InVesalius was not able to create the surface"))
  620 + print(traceback.print_exc())
  621 + return
553 622
554 reader = vtk.vtkXMLPolyDataReader() 623 reader = vtk.vtkXMLPolyDataReader()
555 - reader.SetFileName(filename_polydata)  
556 - # reader.ReleaseDataFlagOn() 624 + reader.SetFileName(surface_filename)
557 reader.Update() 625 reader.Update()
558 - # reader.GetOutput().ReleaseDataFlagOn()  
559 626
560 polydata = reader.GetOutput() 627 polydata = reader.GetOutput()
561 - # polydata.SetSource(None)  
562 -  
563 - polydata_append.AddInputData(polydata)  
564 - del reader  
565 - del polydata  
566 - t -= 1  
567 -  
568 - polydata_append.Update()  
569 - # polydata_append.GetOutput().ReleaseDataFlagOn()  
570 - polydata = polydata_append.GetOutput()  
571 - #polydata.Register(None)  
572 - # polydata.SetSource(None)  
573 - del polydata_append  
574 -  
575 - if algorithm == 'ca_smoothing':  
576 - normals = vtk.vtkPolyDataNormals()  
577 - normals_ref = weakref.ref(normals)  
578 - normals_ref().AddObserver("ProgressEvent", lambda obj,evt:  
579 - UpdateProgress(normals_ref(), _("Creating 3D surface...")))  
580 - normals.SetInputData(polydata)  
581 - # normals.ReleaseDataFlagOn()  
582 - #normals.SetFeatureAngle(80)  
583 - #normals.AutoOrientNormalsOn()  
584 - normals.ComputeCellNormalsOn()  
585 - # normals.GetOutput().ReleaseDataFlagOn()  
586 - normals.Update()  
587 - del polydata  
588 - polydata = normals.GetOutput()  
589 - # polydata.SetSource(None)  
590 - del normals  
591 -  
592 - clean = vtk.vtkCleanPolyData()  
593 - # clean.ReleaseDataFlagOn()  
594 - # clean.GetOutput().ReleaseDataFlagOn()  
595 - clean_ref = weakref.ref(clean)  
596 - clean_ref().AddObserver("ProgressEvent", lambda obj,evt:  
597 - UpdateProgress(clean_ref(), _("Creating 3D surface...")))  
598 - clean.SetInputData(polydata)  
599 - clean.PointMergingOn()  
600 - clean.Update()  
601 -  
602 - del polydata  
603 - polydata = clean.GetOutput()  
604 - # polydata.SetSource(None)  
605 - del clean  
606 -  
607 - # try:  
608 - # polydata.BuildLinks()  
609 - # except TypeError:  
610 - # polydata.BuildLinks(0)  
611 - # polydata = ca_smoothing.ca_smoothing(polydata, options['angle'],  
612 - # options['max distance'],  
613 - # options['min weight'],  
614 - # options['steps'])  
615 -  
616 - mesh = cy_mesh.Mesh(polydata)  
617 - cy_mesh.ca_smoothing(mesh, options['angle'],  
618 - options['max distance'],  
619 - options['min weight'],  
620 - options['steps'])  
621 - # polydata = mesh.to_vtk()  
622 -  
623 - # polydata.SetSource(None)  
624 - # polydata.DebugOn()  
625 - else:  
626 - #smoother = vtk.vtkWindowedSincPolyDataFilter()  
627 - smoother = vtk.vtkSmoothPolyDataFilter()  
628 - smoother_ref = weakref.ref(smoother)  
629 - smoother_ref().AddObserver("ProgressEvent", lambda obj,evt:  
630 - UpdateProgress(smoother_ref(), _("Creating 3D surface...")))  
631 - smoother.SetInputData(polydata)  
632 - smoother.SetNumberOfIterations(smooth_iterations)  
633 - smoother.SetRelaxationFactor(smooth_relaxation_factor)  
634 - smoother.SetFeatureAngle(80)  
635 - #smoother.SetEdgeAngle(90.0)  
636 - #smoother.SetPassBand(0.1)  
637 - smoother.BoundarySmoothingOn()  
638 - smoother.FeatureEdgeSmoothingOn()  
639 - #smoother.NormalizeCoordinatesOn()  
640 - #smoother.NonManifoldSmoothingOn()  
641 - # smoother.ReleaseDataFlagOn()  
642 - # smoother.GetOutput().ReleaseDataFlagOn()  
643 - smoother.Update()  
644 - del polydata  
645 - polydata = smoother.GetOutput()  
646 - #polydata.Register(None)  
647 - # polydata.SetSource(None)  
648 - del smoother  
649 -  
650 -  
651 - if decimate_reduction:  
652 - print("Decimating", decimate_reduction)  
653 - decimation = vtk.vtkQuadricDecimation()  
654 - # decimation.ReleaseDataFlagOn()  
655 - decimation.SetInputData(polydata)  
656 - decimation.SetTargetReduction(decimate_reduction)  
657 - decimation_ref = weakref.ref(decimation)  
658 - decimation_ref().AddObserver("ProgressEvent", lambda obj,evt:  
659 - UpdateProgress(decimation_ref(), _("Creating 3D surface...")))  
660 - #decimation.PreserveTopologyOn()  
661 - #decimation.SplittingOff()  
662 - #decimation.BoundaryVertexDeletionOff()  
663 - # decimation.GetOutput().ReleaseDataFlagOn()  
664 - decimation.Update()  
665 - del polydata  
666 - polydata = decimation.GetOutput()  
667 - #polydata.Register(None)  
668 - # polydata.SetSource(None)  
669 - del decimation  
670 -  
671 - #to_measure.Register(None)  
672 - # to_measure.SetSource(None)  
673 628
674 - if keep_largest:  
675 - conn = vtk.vtkPolyDataConnectivityFilter()  
676 - conn.SetInputData(polydata)  
677 - conn.SetExtractionModeToLargestRegion()  
678 - conn_ref = weakref.ref(conn)  
679 - conn_ref().AddObserver("ProgressEvent", lambda obj,evt:  
680 - UpdateProgress(conn_ref(), _("Creating 3D surface...")))  
681 - conn.Update()  
682 - # conn.GetOutput().ReleaseDataFlagOn()  
683 - del polydata  
684 - polydata = conn.GetOutput()  
685 - #polydata.Register(None)  
686 - # polydata.SetSource(None)  
687 - del conn  
688 -  
689 - #Filter used to detect and fill holes. Only fill boundary edges holes.  
690 - #TODO: Hey! This piece of code is the same from  
691 - #polydata_utils.FillSurfaceHole, we need to review this.  
692 - if fill_holes:  
693 - filled_polydata = vtk.vtkFillHolesFilter()  
694 - # filled_polydata.ReleaseDataFlagOn()  
695 - filled_polydata.SetInputData(polydata)  
696 - filled_polydata.SetHoleSize(300)  
697 - filled_polydata_ref = weakref.ref(filled_polydata)  
698 - filled_polydata_ref().AddObserver("ProgressEvent", lambda obj,evt:  
699 - UpdateProgress(filled_polydata_ref(), _("Creating 3D surface...")))  
700 - filled_polydata.Update()  
701 - # filled_polydata.GetOutput().ReleaseDataFlagOn()  
702 - del polydata  
703 - polydata = filled_polydata.GetOutput()  
704 - #polydata.Register(None)  
705 - # polydata.SetSource(None)  
706 - # polydata.DebugOn()  
707 - del filled_polydata  
708 -  
709 - to_measure = polydata  
710 -  
711 - # If InVesalius is running without GUI  
712 - if wx.GetApp() is None:  
713 proj = prj.Project() 629 proj = prj.Project()
714 #Create Surface instance 630 #Create Surface instance
715 if overwrite: 631 if overwrite:
@@ -720,115 +636,108 @@ class SurfaceManager(): @@ -720,115 +636,108 @@ class SurfaceManager():
720 index = proj.AddSurface(surface) 636 index = proj.AddSurface(surface)
721 surface.index = index 637 surface.index = index
722 self.last_surface_index = index 638 self.last_surface_index = index
  639 +
723 surface.colour = colour 640 surface.colour = colour
724 surface.polydata = polydata 641 surface.polydata = polydata
  642 + surface.volume = surface_measures['volume']
  643 + surface.area = surface_measures['area']
725 644
726 # With GUI 645 # With GUI
727 else: 646 else:
728 - normals = vtk.vtkPolyDataNormals()  
729 - # normals.ReleaseDataFlagOn()  
730 - normals_ref = weakref.ref(normals)  
731 - normals_ref().AddObserver("ProgressEvent", lambda obj,evt:  
732 - UpdateProgress(normals_ref(), _("Creating 3D surface...")))  
733 - normals.SetInputData(polydata)  
734 - normals.SetFeatureAngle(80)  
735 - normals.AutoOrientNormalsOn()  
736 - # normals.GetOutput().ReleaseDataFlagOn()  
737 - normals.Update()  
738 - del polydata  
739 - polydata = normals.GetOutput()  
740 - #polydata.Register(None)  
741 - # polydata.SetSource(None)  
742 - del normals  
743 -  
744 - # Improve performance  
745 - stripper = vtk.vtkStripper()  
746 - # stripper.ReleaseDataFlagOn()  
747 - stripper_ref = weakref.ref(stripper)  
748 - stripper_ref().AddObserver("ProgressEvent", lambda obj,evt:  
749 - UpdateProgress(stripper_ref(), _("Creating 3D surface...")))  
750 - stripper.SetInputData(polydata)  
751 - stripper.PassThroughCellIdsOn()  
752 - stripper.PassThroughPointIdsOn()  
753 - # stripper.GetOutput().ReleaseDataFlagOn()  
754 - stripper.Update()  
755 - del polydata  
756 - polydata = stripper.GetOutput()  
757 - #polydata.Register(None)  
758 - # polydata.SetSource(None)  
759 - del stripper  
760 -  
761 - # Map polygonal data (vtkPolyData) to graphics primitives.  
762 - mapper = vtk.vtkPolyDataMapper()  
763 - mapper.SetInputData(polydata)  
764 - mapper.ScalarVisibilityOff()  
765 - # mapper.ReleaseDataFlagOn()  
766 - mapper.ImmediateModeRenderingOn() # improve performance  
767 -  
768 - # Represent an object (geometry & properties) in the rendered scene  
769 - actor = vtk.vtkActor()  
770 - actor.SetMapper(mapper)  
771 - del mapper  
772 - #Create Surface instance  
773 - if overwrite:  
774 - surface = Surface(index = self.last_surface_index)  
775 - else:  
776 - surface = Surface(name=surface_name)  
777 - surface.colour = colour  
778 - surface.polydata = polydata  
779 - del polydata  
780 -  
781 - # Set actor colour and transparency  
782 - actor.GetProperty().SetColor(colour)  
783 - actor.GetProperty().SetOpacity(1-surface.transparency)  
784 -  
785 - prop = actor.GetProperty()  
786 -  
787 - interpolation = int(ses.Session().surface_interpolation)  
788 -  
789 - prop.SetInterpolation(interpolation)  
790 -  
791 - proj = prj.Project()  
792 - if overwrite:  
793 - proj.ChangeSurface(surface)  
794 - else:  
795 - index = proj.AddSurface(surface)  
796 - surface.index = index  
797 - self.last_surface_index = index  
798 -  
799 - session = ses.Session()  
800 - session.ChangeProject()  
801 -  
802 - measured_polydata = vtk.vtkMassProperties()  
803 - # measured_polydata.ReleaseDataFlagOn()  
804 - measured_polydata.SetInputData(to_measure)  
805 - volume = float(measured_polydata.GetVolume())  
806 - area = float(measured_polydata.GetSurfaceArea())  
807 - surface.volume = volume  
808 - surface.area = area  
809 - self.last_surface_index = surface.index  
810 - del measured_polydata  
811 - del to_measure  
812 -  
813 - Publisher.sendMessage('Load surface actor into viewer', actor=actor)  
814 -  
815 - # Send actor by pubsub to viewer's render  
816 - if overwrite and self.actors_dict.keys():  
817 - old_actor = self.actors_dict[self.last_surface_index]  
818 - Publisher.sendMessage('Remove surface actor from viewer', actor=old_actor)  
819 -  
820 - # Save actor for future management tasks  
821 - self.actors_dict[surface.index] = actor  
822 -  
823 - Publisher.sendMessage('Update surface info in GUI', surface=surface)  
824 -  
825 - #When you finalize the progress. The bar is cleaned.  
826 - UpdateProgress = vu.ShowProgress(1)  
827 - UpdateProgress(0, _("Ready"))  
828 - Publisher.sendMessage('Update status text in GUI', label=_("Ready"))  
829 -  
830 - Publisher.sendMessage('End busy cursor')  
831 - del actor 647 + sp = dialogs.SurfaceProgressWindow()
  648 + for i in range(n_pieces):
  649 + init = i * piece_size
  650 + end = init + piece_size + o_piece
  651 + roi = slice(init, end)
  652 + print("new_piece", roi)
  653 + try:
  654 + f = pool.apply_async(surface_process.create_surface_piece,
  655 + args = (filename_img, matrix.shape, matrix.dtype,
  656 + mask.temp_file, mask.matrix.shape,
  657 + mask.matrix.dtype, roi, spacing, mode,
  658 + min_value, max_value, decimate_reduction,
  659 + smooth_relaxation_factor,
  660 + smooth_iterations, language, flip_image,
  661 + algorithm != 'Default', algorithm,
  662 + imagedata_resolution),
  663 + callback=lambda x: filenames.append(x),
  664 + error_callback=functools.partial(self._on_callback_error,
  665 + dialog=sp))
  666 + # python2
  667 + except TypeError:
  668 + f = pool.apply_async(surface_process.create_surface_piece,
  669 + args = (filename_img, matrix.shape, matrix.dtype,
  670 + mask.temp_file, mask.matrix.shape,
  671 + mask.matrix.dtype, roi, spacing, mode,
  672 + min_value, max_value, decimate_reduction,
  673 + smooth_relaxation_factor,
  674 + smooth_iterations, language, flip_image,
  675 + algorithm != 'Default', algorithm,
  676 + imagedata_resolution),
  677 + callback=lambda x: filenames.append(x))
  678 +
  679 + while len(filenames) != n_pieces:
  680 + if sp.WasCancelled() or not sp.running:
  681 + break
  682 + time.sleep(0.25)
  683 + sp.Update(_("Creating 3D surface..."))
  684 + wx.Yield()
  685 +
  686 + if not sp.WasCancelled() or sp.running:
  687 + try:
  688 + f = pool.apply_async(surface_process.join_process_surface,
  689 + args=(filenames, algorithm, smooth_iterations,
  690 + smooth_relaxation_factor,
  691 + decimate_reduction, keep_largest,
  692 + fill_holes, options, msg_queue),
  693 + callback=functools.partial(self._on_complete_surface_creation,
  694 + overwrite=overwrite,
  695 + surface_name=surface_name,
  696 + colour=colour,
  697 + dialog=sp),
  698 + error_callback=functools.partial(self._on_callback_error,
  699 + dialog=sp))
  700 + # python2
  701 + except TypeError:
  702 + f = pool.apply_async(surface_process.join_process_surface,
  703 + args=(filenames, algorithm, smooth_iterations,
  704 + smooth_relaxation_factor,
  705 + decimate_reduction, keep_largest,
  706 + fill_holes, options, msg_queue),
  707 + callback=functools.partial(self._on_complete_surface_creation,
  708 + overwrite=overwrite,
  709 + surface_name=surface_name,
  710 + colour=colour,
  711 + dialog=sp))
  712 +
  713 + while sp.running:
  714 + if sp.WasCancelled():
  715 + break
  716 + time.sleep(0.25)
  717 + try:
  718 + msg = msg_queue.get_nowait()
  719 + sp.Update(msg)
  720 + except:
  721 + sp.Update(None)
  722 + wx.Yield()
  723 +
  724 + t_end = time.time()
  725 + print("Elapsed time - {}".format(t_end-t_init))
  726 + sp.Close()
  727 + if sp.error:
  728 + dlg = GMD.GenericMessageDialog(None, sp.error,
  729 + "Exception!",
  730 + wx.OK|wx.ICON_ERROR)
  731 + dlg.ShowModal()
  732 + del sp
  733 +
  734 + pool.close()
  735 + pool.terminate()
  736 + del pool
  737 + del manager
  738 + del msg_queue
  739 + import gc
  740 + gc.collect()
832 741
833 def UpdateSurfaceInterpolation(self): 742 def UpdateSurfaceInterpolation(self):
834 interpolation = int(ses.Session().surface_interpolation) 743 interpolation = int(ses.Session().surface_interpolation)
invesalius/data/surface_process.py
1 import multiprocessing 1 import multiprocessing
  2 +import os
2 import tempfile 3 import tempfile
3 import time 4 import time
4 5
  6 +try:
  7 + import queue
  8 +except ImportError:
  9 + import Queue as queue
  10 +
5 import numpy 11 import numpy
6 import vtk 12 import vtk
7 13
8 import invesalius.i18n as i18n 14 import invesalius.i18n as i18n
9 import invesalius.data.converters as converters 15 import invesalius.data.converters as converters
  16 +from invesalius.data import cy_mesh
10 # import invesalius.data.imagedata_utils as iu 17 # import invesalius.data.imagedata_utils as iu
11 18
  19 +import weakref
12 from scipy import ndimage 20 from scipy import ndimage
13 21
14 # TODO: Code duplicated from file {imagedata_utils.py}. 22 # TODO: Code duplicated from file {imagedata_utils.py}.
@@ -32,182 +40,329 @@ def ResampleImage3D(imagedata, value): @@ -32,182 +40,329 @@ def ResampleImage3D(imagedata, value):
32 40
33 return resample.GetOutput() 41 return resample.GetOutput()
34 42
35 -class SurfaceProcess(multiprocessing.Process):  
36 -  
37 - def __init__(self, pipe, filename, shape, dtype, mask_filename,  
38 - mask_shape, mask_dtype, spacing, mode, min_value, max_value,  
39 - decimate_reduction, smooth_relaxation_factor,  
40 - smooth_iterations, language, flip_image, q_in, q_out,  
41 - from_binary, algorithm, imagedata_resolution):  
42 -  
43 - multiprocessing.Process.__init__(self)  
44 - self.pipe = pipe  
45 - self.spacing = spacing  
46 - self.filename = filename  
47 - self.mode = mode  
48 - self.min_value = min_value  
49 - self.max_value = max_value  
50 - self.decimate_reduction = decimate_reduction  
51 - self.smooth_relaxation_factor = smooth_relaxation_factor  
52 - self.smooth_iterations = smooth_iterations  
53 - self.language = language  
54 - self.flip_image = flip_image  
55 - self.q_in = q_in  
56 - self.q_out = q_out  
57 - self.dtype = dtype  
58 - self.shape = shape  
59 - self.from_binary = from_binary  
60 - self.algorithm = algorithm  
61 - self.imagedata_resolution = imagedata_resolution  
62 -  
63 - self.mask_filename = mask_filename  
64 - self.mask_shape = mask_shape  
65 - self.mask_dtype = mask_dtype  
66 -  
67 - def run(self):  
68 - if self.from_binary:  
69 - self.mask = numpy.memmap(self.mask_filename, mode='r',  
70 - dtype=self.mask_dtype,  
71 - shape=self.mask_shape)  
72 - else:  
73 - self.image = numpy.memmap(self.filename, mode='r', dtype=self.dtype,  
74 - shape=self.shape)  
75 - self.mask = numpy.memmap(self.mask_filename, mode='r',  
76 - dtype=self.mask_dtype,  
77 - shape=self.mask_shape)  
78 -  
79 - while 1:  
80 - roi = self.q_in.get()  
81 - if roi is None:  
82 - break  
83 - self.CreateSurface(roi)  
84 -  
85 - def SendProgress(self, obj, msg):  
86 - prog = obj.GetProgress()  
87 - self.pipe.send([prog, msg])  
88 -  
89 - def CreateSurface(self, roi):  
90 - if self.from_binary:  
91 - a_mask = numpy.array(self.mask[roi.start + 1: roi.stop + 1, 43 +
  44 +def create_surface_piece(filename, shape, dtype, mask_filename, mask_shape,
  45 + mask_dtype, roi, spacing, mode, min_value, max_value,
  46 + decimate_reduction, smooth_relaxation_factor,
  47 + smooth_iterations, language, flip_image,
  48 + from_binary, algorithm, imagedata_resolution):
  49 + if from_binary:
  50 + mask = numpy.memmap(mask_filename, mode='r',
  51 + dtype=mask_dtype,
  52 + shape=mask_shape)
  53 + a_mask = numpy.array(mask[roi.start + 1: roi.stop + 1,
  54 + 1:, 1:])
  55 + image = converters.to_vtk(a_mask, spacing, roi.start,
  56 + "AXIAL")
  57 + del a_mask
  58 + else:
  59 + image = numpy.memmap(filename, mode='r', dtype=dtype,
  60 + shape=shape)
  61 + mask = numpy.memmap(mask_filename, mode='r',
  62 + dtype=mask_dtype,
  63 + shape=mask_shape)
  64 + a_image = numpy.array(image[roi])
  65 +
  66 + if algorithm == u'InVesalius 3.b2':
  67 + a_mask = numpy.array(mask[roi.start + 1: roi.stop + 1,
92 1:, 1:]) 68 1:, 1:])
93 - image = converters.to_vtk(a_mask, self.spacing, roi.start, 69 + a_image[a_mask == 1] = a_image.min() - 1
  70 + a_image[a_mask == 254] = (min_value + max_value) / 2.0
  71 +
  72 + image = converters.to_vtk(a_image, spacing, roi.start,
94 "AXIAL") 73 "AXIAL")
  74 +
  75 + gauss = vtk.vtkImageGaussianSmooth()
  76 + gauss.SetInputData(image)
  77 + gauss.SetRadiusFactor(0.3)
  78 + gauss.ReleaseDataFlagOn()
  79 + gauss.Update()
  80 +
  81 + del image
  82 + image = gauss.GetOutput()
  83 + del gauss
95 del a_mask 84 del a_mask
96 else: 85 else:
97 - a_image = numpy.array(self.image[roi])  
98 -  
99 - if self.algorithm == u'InVesalius 3.b2':  
100 - a_mask = numpy.array(self.mask[roi.start + 1: roi.stop + 1,  
101 - 1:, 1:])  
102 - a_image[a_mask == 1] = a_image.min() - 1  
103 - a_image[a_mask == 254] = (self.min_value + self.max_value) / 2.0  
104 -  
105 - image = converters.to_vtk(a_image, self.spacing, roi.start,  
106 - "AXIAL")  
107 -  
108 - gauss = vtk.vtkImageGaussianSmooth()  
109 - gauss.SetInputData(image)  
110 - gauss.SetRadiusFactor(0.3)  
111 - gauss.ReleaseDataFlagOn()  
112 - gauss.Update()  
113 -  
114 - del image  
115 - image = gauss.GetOutput()  
116 - del gauss  
117 - del a_mask  
118 - else:  
119 - image = converters.to_vtk(a_image, self.spacing, roi.start,  
120 - "AXIAL")  
121 - del a_image  
122 -  
123 - if self.imagedata_resolution:  
124 - # image = iu.ResampleImage3D(image, self.imagedata_resolution)  
125 - image = ResampleImage3D(image, self.imagedata_resolution)  
126 -  
127 - flip = vtk.vtkImageFlip()  
128 - flip.SetInputData(image)  
129 - flip.SetFilteredAxis(1)  
130 - flip.FlipAboutOriginOn()  
131 - flip.ReleaseDataFlagOn()  
132 - flip.Update()  
133 -  
134 - del image  
135 - image = flip.GetOutput()  
136 - del flip  
137 -  
138 - #filename = tempfile.mktemp(suffix='_%s.vti' % (self.pid))  
139 - #writer = vtk.vtkXMLImageDataWriter()  
140 - #writer.SetInput(mask_vtk)  
141 - #writer.SetFileName(filename)  
142 - #writer.Write()  
143 -  
144 - #print "Writing piece", roi, "to", filename  
145 -  
146 - # Create vtkPolyData from vtkImageData  
147 - #print "Generating Polydata"  
148 - #if self.mode == "CONTOUR":  
149 - #print "Contour"  
150 - contour = vtk.vtkContourFilter()  
151 - contour.SetInputData(image)  
152 - #contour.SetInput(flip.GetOutput())  
153 - if self.from_binary:  
154 - contour.SetValue(0, 127) # initial threshold  
155 - else:  
156 - contour.SetValue(0, self.min_value) # initial threshold  
157 - contour.SetValue(1, self.max_value) # final threshold  
158 - contour.ComputeScalarsOn()  
159 - contour.ComputeGradientsOn()  
160 - contour.ComputeNormalsOn()  
161 - contour.ReleaseDataFlagOn()  
162 - contour.Update()  
163 - #contour.AddObserver("ProgressEvent", lambda obj,evt:  
164 - # self.SendProgress(obj, _("Generating 3D surface...")))  
165 - polydata = contour.GetOutput()  
166 - del image  
167 - del contour  
168 -  
169 - #else: #mode == "GRAYSCALE":  
170 - #mcubes = vtk.vtkMarchingCubes()  
171 - #mcubes.SetInput(flip.GetOutput())  
172 - #mcubes.SetValue(0, self.min_value)  
173 - #mcubes.SetValue(1, self.max_value)  
174 - #mcubes.ComputeScalarsOff()  
175 - #mcubes.ComputeGradientsOff()  
176 - #mcubes.ComputeNormalsOff()  
177 - #mcubes.AddObserver("ProgressEvent", lambda obj,evt:  
178 - #self.SendProgress(obj, _("Generating 3D surface...")))  
179 - #polydata = mcubes.GetOutput()  
180 -  
181 - #triangle = vtk.vtkTriangleFilter()  
182 - #triangle.SetInput(polydata)  
183 - #triangle.AddObserver("ProgressEvent", lambda obj,evt:  
184 - #self.SendProgress(obj, _("Generating 3D surface...")))  
185 - #triangle.Update()  
186 - #polydata = triangle.GetOutput()  
187 -  
188 - #if self.decimate_reduction:  
189 -  
190 - ##print "Decimating"  
191 - #decimation = vtk.vtkDecimatePro()  
192 - #decimation.SetInput(polydata)  
193 - #decimation.SetTargetReduction(0.3)  
194 - #decimation.AddObserver("ProgressEvent", lambda obj,evt:  
195 - #self.SendProgress(obj, _("Generating 3D surface...")))  
196 - ##decimation.PreserveTopologyOn()  
197 - #decimation.SplittingOff()  
198 - #decimation.BoundaryVertexDeletionOff()  
199 - #polydata = decimation.GetOutput()  
200 -  
201 - self.pipe.send(None)  
202 -  
203 - filename = tempfile.mktemp(suffix='_%s.vtp' % (self.pid))  
204 - writer = vtk.vtkXMLPolyDataWriter()  
205 - writer.SetInputData(polydata)  
206 - writer.SetFileName(filename)  
207 - writer.Write()  
208 -  
209 - print("Writing piece", roi, "to", filename) 86 + image = converters.to_vtk(a_image, spacing, roi.start,
  87 + "AXIAL")
  88 + del a_image
  89 +
  90 + if imagedata_resolution:
  91 + image = ResampleImage3D(image, imagedata_resolution)
  92 +
  93 + flip = vtk.vtkImageFlip()
  94 + flip.SetInputData(image)
  95 + flip.SetFilteredAxis(1)
  96 + flip.FlipAboutOriginOn()
  97 + flip.ReleaseDataFlagOn()
  98 + flip.Update()
  99 +
  100 + del image
  101 + image = flip.GetOutput()
  102 + del flip
  103 +
  104 + contour = vtk.vtkContourFilter()
  105 + contour.SetInputData(image)
  106 + if from_binary:
  107 + contour.SetValue(0, 127) # initial threshold
  108 + else:
  109 + contour.SetValue(0, min_value) # initial threshold
  110 + contour.SetValue(1, max_value) # final threshold
  111 + # contour.ComputeScalarsOn()
  112 + # contour.ComputeGradientsOn()
  113 + # contour.ComputeNormalsOn()
  114 + contour.ReleaseDataFlagOn()
  115 + contour.Update()
  116 +
  117 + polydata = contour.GetOutput()
  118 + del image
  119 + del contour
  120 +
  121 + filename = tempfile.mktemp(suffix='_%d_%d.vtp' % (roi.start, roi.stop))
  122 + writer = vtk.vtkXMLPolyDataWriter()
  123 + writer.SetInputData(polydata)
  124 + writer.SetFileName(filename)
  125 + writer.Write()
  126 +
  127 + print("Writing piece", roi, "to", filename)
  128 + print("MY PID MC", os.getpid())
  129 + return filename
  130 +
  131 +
  132 +def join_process_surface(filenames, algorithm, smooth_iterations, smooth_relaxation_factor, decimate_reduction, keep_largest, fill_holes, options, msg_queue):
  133 + def send_message(msg):
  134 + try:
  135 + msg_queue.put_nowait(msg)
  136 + except queue.Full as e:
  137 + print(e)
  138 +
  139 + send_message('Joining surfaces ...')
  140 + polydata_append = vtk.vtkAppendPolyData()
  141 + for f in filenames:
  142 + reader = vtk.vtkXMLPolyDataReader()
  143 + reader.SetFileName(f)
  144 + reader.Update()
  145 +
  146 + polydata = reader.GetOutput()
  147 +
  148 + polydata_append.AddInputData(polydata)
  149 + del reader
210 del polydata 150 del polydata
211 - del writer  
212 151
213 - self.q_out.put(filename) 152 + polydata_append.Update()
  153 + # polydata_append.GetOutput().ReleaseDataFlagOn()
  154 + polydata = polydata_append.GetOutput()
  155 + #polydata.Register(None)
  156 + # polydata.SetSource(None)
  157 + del polydata_append
  158 +
  159 + send_message('Cleaning surface ...')
  160 + clean = vtk.vtkCleanPolyData()
  161 + # clean.ReleaseDataFlagOn()
  162 + # clean.GetOutput().ReleaseDataFlagOn()
  163 + clean_ref = weakref.ref(clean)
  164 + # clean_ref().AddObserver("ProgressEvent", lambda obj,evt:
  165 + # UpdateProgress(clean_ref(), _("Creating 3D surface...")))
  166 + clean.SetInputData(polydata)
  167 + clean.PointMergingOn()
  168 + clean.Update()
  169 +
  170 + del polydata
  171 + polydata = clean.GetOutput()
  172 + # polydata.SetSource(None)
  173 + del clean
  174 +
  175 + if algorithm == 'ca_smoothing':
  176 + send_message('Calculating normals ...')
  177 + normals = vtk.vtkPolyDataNormals()
  178 + normals_ref = weakref.ref(normals)
  179 + # normals_ref().AddObserver("ProgressEvent", lambda obj,evt:
  180 + # UpdateProgress(normals_ref(), _("Creating 3D surface...")))
  181 + normals.SetInputData(polydata)
  182 + # normals.ReleaseDataFlagOn()
  183 + #normals.SetFeatureAngle(80)
  184 + #normals.AutoOrientNormalsOn()
  185 + normals.ComputeCellNormalsOn()
  186 + # normals.GetOutput().ReleaseDataFlagOn()
  187 + normals.Update()
  188 + del polydata
  189 + polydata = normals.GetOutput()
  190 + # polydata.SetSource(None)
  191 + del normals
  192 +
  193 + clean = vtk.vtkCleanPolyData()
  194 + # clean.ReleaseDataFlagOn()
  195 + # clean.GetOutput().ReleaseDataFlagOn()
  196 + clean_ref = weakref.ref(clean)
  197 + # clean_ref().AddObserver("ProgressEvent", lambda obj,evt:
  198 + # UpdateProgress(clean_ref(), _("Creating 3D surface...")))
  199 + clean.SetInputData(polydata)
  200 + clean.PointMergingOn()
  201 + clean.Update()
  202 +
  203 + del polydata
  204 + polydata = clean.GetOutput()
  205 + # polydata.SetSource(None)
  206 + del clean
  207 +
  208 + # try:
  209 + # polydata.BuildLinks()
  210 + # except TypeError:
  211 + # polydata.BuildLinks(0)
  212 + # polydata = ca_smoothing.ca_smoothing(polydata, options['angle'],
  213 + # options['max distance'],
  214 + # options['min weight'],
  215 + # options['steps'])
  216 +
  217 + send_message('Context Aware smoothing ...')
  218 + mesh = cy_mesh.Mesh(polydata)
  219 + cy_mesh.ca_smoothing(mesh, options['angle'],
  220 + options['max distance'],
  221 + options['min weight'],
  222 + options['steps'])
  223 + # polydata = mesh.to_vtk()
  224 +
  225 + # polydata.SetSource(None)
  226 + # polydata.DebugOn()
  227 + else:
  228 + #smoother = vtk.vtkWindowedSincPolyDataFilter()
  229 + send_message('Smoothing ...')
  230 + smoother = vtk.vtkSmoothPolyDataFilter()
  231 + smoother_ref = weakref.ref(smoother)
  232 + # smoother_ref().AddObserver("ProgressEvent", lambda obj,evt:
  233 + # UpdateProgress(smoother_ref(), _("Creating 3D surface...")))
  234 + smoother.SetInputData(polydata)
  235 + smoother.SetNumberOfIterations(smooth_iterations)
  236 + smoother.SetRelaxationFactor(smooth_relaxation_factor)
  237 + smoother.SetFeatureAngle(80)
  238 + #smoother.SetEdgeAngle(90.0)
  239 + #smoother.SetPassBand(0.1)
  240 + smoother.BoundarySmoothingOn()
  241 + smoother.FeatureEdgeSmoothingOn()
  242 + #smoother.NormalizeCoordinatesOn()
  243 + #smoother.NonManifoldSmoothingOn()
  244 + # smoother.ReleaseDataFlagOn()
  245 + # smoother.GetOutput().ReleaseDataFlagOn()
  246 + smoother.Update()
  247 + del polydata
  248 + polydata = smoother.GetOutput()
  249 + #polydata.Register(None)
  250 + # polydata.SetSource(None)
  251 + del smoother
  252 +
  253 +
  254 + if decimate_reduction:
  255 + print("Decimating", decimate_reduction)
  256 + send_message('Decimating ...')
  257 + decimation = vtk.vtkQuadricDecimation()
  258 + # decimation.ReleaseDataFlagOn()
  259 + decimation.SetInputData(polydata)
  260 + decimation.SetTargetReduction(decimate_reduction)
  261 + decimation_ref = weakref.ref(decimation)
  262 + # decimation_ref().AddObserver("ProgressEvent", lambda obj,evt:
  263 + # UpdateProgress(decimation_ref(), _("Creating 3D surface...")))
  264 + #decimation.PreserveTopologyOn()
  265 + #decimation.SplittingOff()
  266 + #decimation.BoundaryVertexDeletionOff()
  267 + # decimation.GetOutput().ReleaseDataFlagOn()
  268 + decimation.Update()
  269 + del polydata
  270 + polydata = decimation.GetOutput()
  271 + #polydata.Register(None)
  272 + # polydata.SetSource(None)
  273 + del decimation
  274 +
  275 + #to_measure.Register(None)
  276 + # to_measure.SetSource(None)
  277 +
  278 + if keep_largest:
  279 + send_message('Finding the largest ...')
  280 + conn = vtk.vtkPolyDataConnectivityFilter()
  281 + conn.SetInputData(polydata)
  282 + conn.SetExtractionModeToLargestRegion()
  283 + conn_ref = weakref.ref(conn)
  284 + # conn_ref().AddObserver("ProgressEvent", lambda obj,evt:
  285 + # UpdateProgress(conn_ref(), _("Creating 3D surface...")))
  286 + conn.Update()
  287 + # conn.GetOutput().ReleaseDataFlagOn()
  288 + del polydata
  289 + polydata = conn.GetOutput()
  290 + #polydata.Register(None)
  291 + # polydata.SetSource(None)
  292 + del conn
  293 +
  294 + #Filter used to detect and fill holes. Only fill boundary edges holes.
  295 + #TODO: Hey! This piece of code is the same from
  296 + #polydata_utils.FillSurfaceHole, we need to review this.
  297 + if fill_holes:
  298 + send_message('Filling holes ...')
  299 + filled_polydata = vtk.vtkFillHolesFilter()
  300 + # filled_polydata.ReleaseDataFlagOn()
  301 + filled_polydata.SetInputData(polydata)
  302 + filled_polydata.SetHoleSize(300)
  303 + filled_polydata_ref = weakref.ref(filled_polydata)
  304 + # filled_polydata_ref().AddObserver("ProgressEvent", lambda obj,evt:
  305 + # UpdateProgress(filled_polydata_ref(), _("Creating 3D surface...")))
  306 + filled_polydata.Update()
  307 + # filled_polydata.GetOutput().ReleaseDataFlagOn()
  308 + del polydata
  309 + polydata = filled_polydata.GetOutput()
  310 + #polydata.Register(None)
  311 + # polydata.SetSource(None)
  312 + # polydata.DebugOn()
  313 + del filled_polydata
  314 +
  315 + to_measure = polydata
  316 +
  317 + normals = vtk.vtkPolyDataNormals()
  318 + # normals.ReleaseDataFlagOn()
  319 + # normals_ref = weakref.ref(normals)
  320 + # normals_ref().AddObserver("ProgressEvent", lambda obj,evt:
  321 + # UpdateProgress(normals_ref(), _("Creating 3D surface...")))
  322 + normals.SetInputData(polydata)
  323 + # normals.SetFeatureAngle(80)
  324 + # normals.SplittingOff()
  325 + # normals.AutoOrientNormalsOn()
  326 + # normals.GetOutput().ReleaseDataFlagOn()
  327 + normals.Update()
  328 + del polydata
  329 + polydata = normals.GetOutput()
  330 + #polydata.Register(None)
  331 + # polydata.SetSource(None)
  332 + del normals
  333 +
  334 +
  335 + # Improve performance
  336 + stripper = vtk.vtkStripper()
  337 + # stripper.ReleaseDataFlagOn()
  338 + # stripper_ref = weakref.ref(stripper)
  339 + # stripper_ref().AddObserver("ProgressEvent", lambda obj,evt:
  340 + # UpdateProgress(stripper_ref(), _("Creating 3D surface...")))
  341 + stripper.SetInputData(polydata)
  342 + stripper.PassThroughCellIdsOn()
  343 + stripper.PassThroughPointIdsOn()
  344 + # stripper.GetOutput().ReleaseDataFlagOn()
  345 + stripper.Update()
  346 + del polydata
  347 + polydata = stripper.GetOutput()
  348 + #polydata.Register(None)
  349 + # polydata.SetSource(None)
  350 + del stripper
  351 +
  352 + send_message('Calculating area and volume ...')
  353 + measured_polydata = vtk.vtkMassProperties()
  354 + measured_polydata.SetInputData(to_measure)
  355 + measured_polydata.Update()
  356 + volume = float(measured_polydata.GetVolume())
  357 + area = float(measured_polydata.GetSurfaceArea())
  358 + del measured_polydata
  359 +
  360 + filename = tempfile.mktemp(suffix='_full.vtp')
  361 + writer = vtk.vtkXMLPolyDataWriter()
  362 + writer.SetInputData(polydata)
  363 + writer.SetFileName(filename)
  364 + writer.Write()
  365 + del writer
  366 +
  367 + print("MY PID", os.getpid())
  368 + return filename, {'volume': volume, 'area': area}
invesalius/gui/dialogs.py
@@ -3523,3 +3523,30 @@ class ObjectCalibrationDialog(wx.Dialog): @@ -3523,3 +3523,30 @@ class ObjectCalibrationDialog(wx.Dialog):
3523 3523
3524 def GetValue(self): 3524 def GetValue(self):
3525 return self.obj_fiducials, self.obj_orients, self.obj_ref_id, self.obj_name 3525 return self.obj_fiducials, self.obj_orients, self.obj_ref_id, self.obj_name
  3526 +
  3527 +
  3528 +class SurfaceProgressWindow(object):
  3529 + def __init__(self):
  3530 + self.title = "InVesalius 3"
  3531 + self.msg = _("Creating 3D surface ...")
  3532 + self.style = wx.PD_APP_MODAL | wx.PD_APP_MODAL | wx.PD_CAN_ABORT | wx.PD_ELAPSED_TIME
  3533 + self.dlg = wx.ProgressDialog(self.title,
  3534 + self.msg,
  3535 + parent=None,
  3536 + style=self.style)
  3537 + self.running = True
  3538 + self.error = None
  3539 + self.dlg.Show()
  3540 +
  3541 + def WasCancelled(self):
  3542 + # print("Cancelled?", self.dlg.WasCancelled())
  3543 + return self.dlg.WasCancelled()
  3544 +
  3545 + def Update(self, msg=None, value=None):
  3546 + if msg is None:
  3547 + self.dlg.Pulse()
  3548 + else:
  3549 + self.dlg.Pulse(msg)
  3550 +
  3551 + def Close(self):
  3552 + self.dlg.Destroy()
invesalius/utils.py
@@ -22,8 +22,10 @@ import sys @@ -22,8 +22,10 @@ import sys
22 import re 22 import re
23 import locale 23 import locale
24 import math 24 import math
  25 +import traceback
25 26
26 from distutils.version import LooseVersion 27 from distutils.version import LooseVersion
  28 +from functools import wraps
27 29
28 import numpy as np 30 import numpy as np
29 31
@@ -462,3 +464,24 @@ def encode(text, encoding, *args): @@ -462,3 +464,24 @@ def encode(text, encoding, *args):
462 return text.encode(encoding, *args) 464 return text.encode(encoding, *args)
463 except AttributeError: 465 except AttributeError:
464 return text 466 return text
  467 +
  468 +
  469 +def timing(f):
  470 + @wraps(f)
  471 + def wrapper(*args, **kwargs):
  472 + start = time.time()
  473 + result = f(*args, **kwargs)
  474 + end = time.time()
  475 + print('{} elapsed time: {}'.format(f.__name__, end-start))
  476 + return result
  477 + return wrapper
  478 +
  479 +
  480 +def log_traceback(ex):
  481 + if hasattr(ex, '__traceback__'):
  482 + ex_traceback = ex.__traceback__
  483 + else:
  484 + _, _, ex_traceback = sys.exc_info()
  485 + tb_lines = [line.rstrip('\n') for line in
  486 + traceback.format_exception(ex.__class__, ex, ex_traceback)]
  487 + return ''.join(tb_lines)