Commit 3e4ed11ddd539f45e2800e17665c4f4bff817166

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

Create border closed (#149)

* Created a function to pad image

* taking padding in consideration when converting to vtk

* Better normals computation

* Added option to close border holes
invesalius/data/converters.py
... ... @@ -21,7 +21,7 @@ import numpy
21 21 import vtk
22 22 from vtk.util import numpy_support
23 23  
24   -def to_vtk(n_array, spacing, slice_number, orientation):
  24 +def to_vtk(n_array, spacing, slice_number, orientation, origin=(0, 0, 0), padding=(0, 0, 0)):
25 25  
26 26 if orientation == "SAGITTAL":
27 27 orientation = "SAGITAL"
... ... @@ -32,20 +32,22 @@ def to_vtk(n_array, spacing, slice_number, orientation):
32 32 dy, dx = n_array.shape
33 33 dz = 1
34 34  
  35 + px, py, pz = padding
  36 +
35 37 v_image = numpy_support.numpy_to_vtk(n_array.flat)
36 38  
37 39 if orientation == 'AXIAL':
38   - extent = (0, dx -1, 0, dy -1, slice_number, slice_number + dz - 1)
  40 + extent = (0 - px , dx -1 - px, 0 - py, dy - 1 - py, slice_number - pz, slice_number + dz - 1 - pz)
39 41 elif orientation == 'SAGITAL':
40 42 dx, dy, dz = dz, dx, dy
41   - extent = (slice_number, slice_number + dx - 1, 0, dy - 1, 0, dz - 1)
  43 + extent = (slice_number - px, slice_number + dx - 1 - px, 0 - py, dy - 1 - py, 0 - pz, dz - 1 - pz)
42 44 elif orientation == 'CORONAL':
43 45 dx, dy, dz = dx, dz, dy
44   - extent = (0, dx - 1, slice_number, slice_number + dy - 1, 0, dz - 1)
  46 + extent = (0 - px, dx - 1 - px, slice_number - py, slice_number + dy - 1 - py, 0 - pz, dz - 1 - pz)
45 47  
46 48 # Generating the vtkImageData
47 49 image = vtk.vtkImageData()
48   - image.SetOrigin(0, 0, 0)
  50 + image.SetOrigin(origin)
49 51 image.SetSpacing(spacing)
50 52 image.SetDimensions(dx, dy, dz)
51 53 # SetNumberOfScalarComponents and SetScalrType were replaced by
... ...
invesalius/data/imagedata_utils.py
... ... @@ -751,3 +751,22 @@ def imgnormalize(data, srange=(0, 255)):
751 751 datan = datan.astype(numpy.int16)
752 752  
753 753 return datan
  754 +
  755 +
  756 +def pad_image(image, pad_value, pad_bottom, pad_top):
  757 + dz, dy, dx = image.shape
  758 + z_iadd = 0
  759 + z_eadd = 0
  760 + if pad_bottom:
  761 + z_iadd = 1
  762 + dz += 1
  763 + if pad_top:
  764 + z_eadd = 1
  765 + dz += 1
  766 + new_shape = dz, dy + 2, dx + 2
  767 +
  768 + paded_image = numpy.empty(shape=new_shape, dtype=image.dtype)
  769 + paded_image[:] = pad_value
  770 + paded_image[z_iadd: z_iadd + image.shape[0], 1:-1, 1:-1] = image
  771 +
  772 + return paded_image
... ...
invesalius/data/surface.py
... ... @@ -538,6 +538,8 @@ class SurfaceManager():
538 538 fill_holes = surface_parameters['options']['fill']
539 539 keep_largest = surface_parameters['options']['keep_largest']
540 540  
  541 + fill_border_holes = surface_parameters['options'].get('fill_border_holes', True)
  542 +
541 543 mode = 'CONTOUR' # 'GRAYSCALE'
542 544 min_value, max_value = mask.threshold_range
543 545 colour = mask.colour[:3]
... ... @@ -598,7 +600,7 @@ class SurfaceManager():
598 600 smooth_relaxation_factor,
599 601 smooth_iterations, language, flip_image,
600 602 algorithm != 'Default', algorithm,
601   - imagedata_resolution),
  603 + imagedata_resolution, fill_border_holes),
602 604 callback=lambda x: filenames.append(x))
603 605  
604 606 while len(filenames) != n_pieces:
... ... @@ -659,7 +661,7 @@ class SurfaceManager():
659 661 smooth_relaxation_factor,
660 662 smooth_iterations, language, flip_image,
661 663 algorithm != 'Default', algorithm,
662   - imagedata_resolution),
  664 + imagedata_resolution, fill_border_holes),
663 665 callback=lambda x: filenames.append(x),
664 666 error_callback=functools.partial(self._on_callback_error,
665 667 dialog=sp))
... ... @@ -673,7 +675,7 @@ class SurfaceManager():
673 675 smooth_relaxation_factor,
674 676 smooth_iterations, language, flip_image,
675 677 algorithm != 'Default', algorithm,
676   - imagedata_resolution),
  678 + imagedata_resolution, fill_border_holes),
677 679 callback=lambda x: filenames.append(x))
678 680  
679 681 while len(filenames) != n_pieces:
... ...
invesalius/data/surface_process.py
... ... @@ -14,7 +14,7 @@ import vtk
14 14 import invesalius.i18n as i18n
15 15 import invesalius.data.converters as converters
16 16 from invesalius.data import cy_mesh
17   -# import invesalius.data.imagedata_utils as iu
  17 +import invesalius.data.imagedata_utils as iu
18 18  
19 19 import weakref
20 20 from scipy import ndimage
... ... @@ -45,15 +45,25 @@ def create_surface_piece(filename, shape, dtype, mask_filename, mask_shape,
45 45 mask_dtype, roi, spacing, mode, min_value, max_value,
46 46 decimate_reduction, smooth_relaxation_factor,
47 47 smooth_iterations, language, flip_image,
48   - from_binary, algorithm, imagedata_resolution):
  48 + from_binary, algorithm, imagedata_resolution, fill_border_holes):
  49 +
  50 + pad_bottom = (roi.start == 0)
  51 + pad_top = (roi.stop >= shape[0])
  52 +
  53 + if fill_border_holes:
  54 + padding = (1, 1, pad_bottom)
  55 + else:
  56 + padding = (0, 0, 0)
  57 +
49 58 if from_binary:
50 59 mask = numpy.memmap(mask_filename, mode='r',
51 60 dtype=mask_dtype,
52 61 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")
  62 + if fill_border_holes:
  63 + a_mask = iu.pad_image(mask[roi.start + 1: roi.stop + 1, 1:, 1:], 0, pad_bottom, pad_top)
  64 + else:
  65 + a_mask = numpy.array(mask[roi.start + 1: roi.stop + 1, 1:, 1:])
  66 + image = converters.to_vtk(a_mask, spacing, roi.start, "AXIAL", padding=padding)
57 67 del a_mask
58 68 else:
59 69 image = numpy.memmap(filename, mode='r', dtype=dtype,
... ... @@ -61,16 +71,21 @@ def create_surface_piece(filename, shape, dtype, mask_filename, mask_shape,
61 71 mask = numpy.memmap(mask_filename, mode='r',
62 72 dtype=mask_dtype,
63 73 shape=mask_shape)
64   - a_image = numpy.array(image[roi])
  74 + if fill_border_holes:
  75 + a_image = iu.pad_image(image[roi], numpy.iinfo(image.dtype).min, pad_bottom, pad_top)
  76 + else:
  77 + a_image = numpy.array(image[roi])
  78 + # if z_iadd:
  79 + # a_image[0, 1:-1, 1:-1] = image[0]
  80 + # if z_eadd:
  81 + # a_image[-1, 1:-1, 1:-1] = image[-1]
65 82  
66 83 if algorithm == u'InVesalius 3.b2':
67   - a_mask = numpy.array(mask[roi.start + 1: roi.stop + 1,
68   - 1:, 1:])
  84 + a_mask = numpy.array(mask[roi.start + 1: roi.stop + 1, 1:, 1:])
69 85 a_image[a_mask == 1] = a_image.min() - 1
70 86 a_image[a_mask == 254] = (min_value + max_value) / 2.0
71 87  
72   - image = converters.to_vtk(a_image, spacing, roi.start,
73   - "AXIAL")
  88 + image = converters.to_vtk(a_image, spacing, roi.start, "AXIAL", padding=padding)
74 89  
75 90 gauss = vtk.vtkImageGaussianSmooth()
76 91 gauss.SetInputData(image)
... ... @@ -83,8 +98,11 @@ def create_surface_piece(filename, shape, dtype, mask_filename, mask_shape,
83 98 del gauss
84 99 del a_mask
85 100 else:
86   - image = converters.to_vtk(a_image, spacing, roi.start,
87   - "AXIAL")
  101 + # if z_iadd:
  102 + # origin = -spacing[0], -spacing[1], -spacing[2]
  103 + # else:
  104 + # origin = 0, -spacing[1], -spacing[2]
  105 + image = converters.to_vtk(a_image, spacing, roi.start, "AXIAL", padding=padding)
88 106 del a_image
89 107  
90 108 if imagedata_resolution:
... ... @@ -97,6 +115,11 @@ def create_surface_piece(filename, shape, dtype, mask_filename, mask_shape,
97 115 flip.ReleaseDataFlagOn()
98 116 flip.Update()
99 117  
  118 + # writer = vtk.vtkXMLImageDataWriter()
  119 + # writer.SetFileName('/tmp/camboja.vti')
  120 + # writer.SetInputData(flip.GetOutput())
  121 + # writer.Write()
  122 +
100 123 del image
101 124 image = flip.GetOutput()
102 125 del flip
... ... @@ -224,34 +247,34 @@ def join_process_surface(filenames, algorithm, smooth_iterations, smooth_relaxat
224 247  
225 248 # polydata.SetSource(None)
226 249 # 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:
  250 + # else:
  251 + # #smoother = vtk.vtkWindowedSincPolyDataFilter()
  252 + # send_message('Smoothing ...')
  253 + # smoother = vtk.vtkSmoothPolyDataFilter()
  254 + # smoother_ref = weakref.ref(smoother)
  255 + # # smoother_ref().AddObserver("ProgressEvent", lambda obj,evt:
  256 + # # UpdateProgress(smoother_ref(), _("Creating 3D surface...")))
  257 + # smoother.SetInputData(polydata)
  258 + # smoother.SetNumberOfIterations(smooth_iterations)
  259 + # smoother.SetRelaxationFactor(smooth_relaxation_factor)
  260 + # smoother.SetFeatureAngle(80)
  261 + # #smoother.SetEdgeAngle(90.0)
  262 + # #smoother.SetPassBand(0.1)
  263 + # smoother.BoundarySmoothingOn()
  264 + # smoother.FeatureEdgeSmoothingOn()
  265 + # #smoother.NormalizeCoordinatesOn()
  266 + # #smoother.NonManifoldSmoothingOn()
  267 + # # smoother.ReleaseDataFlagOn()
  268 + # # smoother.GetOutput().ReleaseDataFlagOn()
  269 + # smoother.Update()
  270 + # del polydata
  271 + # polydata = smoother.GetOutput()
  272 + # #polydata.Register(None)
  273 + # # polydata.SetSource(None)
  274 + # del smoother
  275 +
  276 +
  277 + if not decimate_reduction:
255 278 print("Decimating", decimate_reduction)
256 279 send_message('Decimating ...')
257 280 decimation = vtk.vtkQuadricDecimation()
... ... @@ -320,9 +343,11 @@ def join_process_surface(filenames, algorithm, smooth_iterations, smooth_relaxat
320 343 # normals_ref().AddObserver("ProgressEvent", lambda obj,evt:
321 344 # UpdateProgress(normals_ref(), _("Creating 3D surface...")))
322 345 normals.SetInputData(polydata)
323   - # normals.SetFeatureAngle(80)
324   - # normals.SplittingOff()
325   - # normals.AutoOrientNormalsOn()
  346 + normals.SetFeatureAngle(80)
  347 + normals.SplittingOn()
  348 + normals.AutoOrientNormalsOn()
  349 + normals.NonManifoldTraversalOn()
  350 + normals.ComputeCellNormalsOn()
326 351 # normals.GetOutput().ReleaseDataFlagOn()
327 352 normals.Update()
328 353 del polydata
... ... @@ -332,22 +357,22 @@ def join_process_surface(filenames, algorithm, smooth_iterations, smooth_relaxat
332 357 del normals
333 358  
334 359  
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
  360 + # # Improve performance
  361 + # stripper = vtk.vtkStripper()
  362 + # # stripper.ReleaseDataFlagOn()
  363 + # # stripper_ref = weakref.ref(stripper)
  364 + # # stripper_ref().AddObserver("ProgressEvent", lambda obj,evt:
  365 + # # UpdateProgress(stripper_ref(), _("Creating 3D surface...")))
  366 + # stripper.SetInputData(polydata)
  367 + # stripper.PassThroughCellIdsOn()
  368 + # stripper.PassThroughPointIdsOn()
  369 + # # stripper.GetOutput().ReleaseDataFlagOn()
  370 + # stripper.Update()
  371 + # del polydata
  372 + # polydata = stripper.GetOutput()
  373 + # #polydata.Register(None)
  374 + # # polydata.SetSource(None)
  375 + # del stripper
351 376  
352 377 send_message('Calculating area and volume ...')
353 378 measured_polydata = vtk.vtkMassProperties()
... ...
invesalius/gui/dialogs.py
... ... @@ -1662,7 +1662,10 @@ class SurfaceCreationOptionsPanel(wx.Panel):
1662 1662 (combo_quality, 0, flag_button, 0)])
1663 1663  
1664 1664  
1665   - # LINES 4 and 5: Checkboxes
  1665 + # LINES 4, 5 and 6: Checkboxes
  1666 + check_box_border_holes = wx.CheckBox(self, -1, _("Fill border holes"))
  1667 + check_box_border_holes.SetValue(False)
  1668 + self.check_box_border_holes = check_box_border_holes
1666 1669 check_box_holes = wx.CheckBox(self, -1, _("Fill holes"))
1667 1670 check_box_holes.SetValue(False)
1668 1671 self.check_box_holes = check_box_holes
... ... @@ -1673,6 +1676,7 @@ class SurfaceCreationOptionsPanel(wx.Panel):
1673 1676 # Merge all sizers and checkboxes
1674 1677 sizer = wx.BoxSizer(wx.VERTICAL)
1675 1678 sizer.Add(fixed_sizer, 0, wx.TOP|wx.RIGHT|wx.LEFT|wx.GROW|wx.EXPAND, 5)
  1679 + sizer.Add(check_box_border_holes, 0, wx.RIGHT|wx.LEFT, 5)
1676 1680 sizer.Add(check_box_holes, 0, wx.RIGHT|wx.LEFT, 5)
1677 1681 sizer.Add(check_box_largest, 0, wx.RIGHT|wx.LEFT, 5)
1678 1682  
... ... @@ -1687,11 +1691,13 @@ class SurfaceCreationOptionsPanel(wx.Panel):
1687 1691 mask_index = self.combo_mask.GetSelection()
1688 1692 surface_name = self.text.GetValue()
1689 1693 quality = const.SURFACE_QUALITY_LIST[self.combo_quality.GetSelection()]
  1694 + fill_border_holes = self.check_box_border_holes.GetValue()
1690 1695 fill_holes = self.check_box_holes.GetValue()
1691 1696 keep_largest = self.check_box_largest.GetValue()
1692 1697 return {"index": mask_index,
1693 1698 "name": surface_name,
1694 1699 "quality": quality,
  1700 + "fill_border_holes": fill_border_holes,
1695 1701 "fill": fill_holes,
1696 1702 "keep_largest": keep_largest,
1697 1703 "overwrite": False}
... ...
invesalius/gui/preferences.py
... ... @@ -15,7 +15,7 @@ except ImportError: # if it's not there locally, try the wxPython lib.
15 15 class Preferences(wx.Dialog):
16 16  
17 17 def __init__( self, parent, id = ID, title = _("Preferences"), size=wx.DefaultSize,\
18   - pos=wx.DefaultPosition, style=wx.DEFAULT_DIALOG_STYLE):
  18 + pos=wx.DefaultPosition, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
19 19  
20 20 try:
21 21 pre = wx.PreDialog()
... ... @@ -36,14 +36,16 @@ class Preferences(wx.Dialog):
36 36 else:
37 37 self.book = fnb.FlatNotebook(self, wx.ID_ANY, agwStyle=bookStyle)
38 38  
39   - sizer.Add(self.book, 80, wx.EXPAND|wx.ALL)
  39 + sizer.Add(self.book, 1, wx.EXPAND|wx.ALL)
40 40  
41 41 self.pnl_viewer2d = Viewer2D(self)
42 42 self.pnl_viewer3d = Viewer3D(self)
  43 + # self.pnl_surface = SurfaceCreation(self)
43 44 self.pnl_language = Language(self)
44 45  
45 46 self.book.AddPage(self.pnl_viewer2d, _("2D Visualization"))
46 47 self.book.AddPage(self.pnl_viewer3d, _("3D Visualization"))
  48 + # self.book.AddPage(self.pnl_surface, _("Surface creation"))
47 49 self.book.AddPage(self.pnl_language, _("Language"))
48 50  
49 51 line = wx.StaticLine(self, -1, size=(20,-1), style=wx.LI_HORIZONTAL)
... ... @@ -59,7 +61,7 @@ class Preferences(wx.Dialog):
59 61  
60 62 btnsizer.Realize()
61 63  
62   - sizer.Add(btnsizer, 10, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP|wx.BOTTOM, 5)
  64 + sizer.Add(btnsizer, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP|wx.BOTTOM, 5)
63 65  
64 66 self.SetSizer(sizer)
65 67 sizer.Fit(self)
... ... @@ -214,3 +216,21 @@ class Language(wx.Panel):
214 216 locales = self.lg.GetLocalesKey()
215 217 selection = locales.index(language)
216 218 self.cmb_lang.SetSelection(int(selection))
  219 +
  220 +
  221 +
  222 +class SurfaceCreation(wx.Panel):
  223 + def __init__(self, parent):
  224 + wx.Panel.__init__(self, parent)
  225 + self.rb_fill_border = wx.RadioBox(self, -1, "Fill border holes", choices=[_('Yes'), _('No')], style=wx.RA_SPECIFY_COLS | wx.NO_BORDER)
  226 +
  227 + sizer = wx.BoxSizer(wx.VERTICAL)
  228 + sizer.Add(self.rb_fill_border)
  229 +
  230 + self.SetSizerAndFit(sizer)
  231 +
  232 + def GetSelection(self):
  233 + return {}
  234 +
  235 + def LoadSelection(self, values):
  236 + pass
... ...