Commit 03ed982ba7b0be007742bed4244fe4af378ee89c

Authored by Thiago Franco de Moraes
1 parent 87c3a46d
Exists in fill_holes_auto

filling hole without gui

invesalius/constants.py
@@ -517,6 +517,7 @@ ID_CLEAN_MASK = wx.NewId() @@ -517,6 +517,7 @@ ID_CLEAN_MASK = wx.NewId()
517 517
518 ID_REORIENT_IMG = wx.NewId() 518 ID_REORIENT_IMG = wx.NewId()
519 ID_FLOODFILL_MASK = wx.NewId() 519 ID_FLOODFILL_MASK = wx.NewId()
  520 +ID_FILL_HOLE_AUTO = wx.NewId()
520 ID_REMOVE_MASK_PART = wx.NewId() 521 ID_REMOVE_MASK_PART = wx.NewId()
521 ID_SELECT_MASK_PART = wx.NewId() 522 ID_SELECT_MASK_PART = wx.NewId()
522 ID_FLOODFILL_SEGMENTATION = wx.NewId() 523 ID_FLOODFILL_SEGMENTATION = wx.NewId()
invesalius/data/floodfill.pyx
@@ -4,6 +4,7 @@ cimport cython @@ -4,6 +4,7 @@ cimport cython
4 4
5 from collections import deque 5 from collections import deque
6 6
  7 +from cython.parallel import prange
7 from libc.math cimport floor, ceil 8 from libc.math cimport floor, ceil
8 from libcpp.deque cimport deque as cdeque 9 from libcpp.deque cimport deque as cdeque
9 from libcpp.vector cimport vector 10 from libcpp.vector cimport vector
@@ -230,3 +231,28 @@ def floodfill_auto_threshold(np.ndarray[image_t, ndim=3] data, list seeds, float @@ -230,3 +231,28 @@ def floodfill_auto_threshold(np.ndarray[image_t, ndim=3] data, list seeds, float
230 231
231 if to_return: 232 if to_return:
232 return out 233 return out
  234 +
  235 +
  236 +# @cython.boundscheck(False)
  237 +# @cython.wraparound(False)
  238 +# @cython.nonecheck(False)
  239 +def fill_holes_automatically(np.ndarray[mask_t, ndim=3] mask, np.ndarray[np.uint16_t, ndim=3] labels, unsigned int nlabels, unsigned int max_size):
  240 + cdef np.ndarray[np.uint32_t, ndim=1] sizes = np.zeros(shape=(nlabels + 1), dtype=np.uint32)
  241 + cdef int x, y, z
  242 + cdef int dx, dy, dz
  243 +
  244 + dz = mask.shape[0]
  245 + dy = mask.shape[1]
  246 + dx = mask.shape[2]
  247 +
  248 + for z in xrange(dz):
  249 + for y in xrange(dy):
  250 + for x in xrange(dx):
  251 + sizes[labels[z, y, x]] += 1
  252 +
  253 +
  254 + for z in prange(dz, nogil=True):
  255 + for y in xrange(dy):
  256 + for x in xrange(dx):
  257 + if sizes[labels[z, y, x]] <= max_size:
  258 + mask[z, y, x] = 254
invesalius/data/mask.py
@@ -23,15 +23,17 @@ import random @@ -23,15 +23,17 @@ import random
23 import shutil 23 import shutil
24 import tempfile 24 import tempfile
25 25
26 -import numpy 26 +import numpy as np
27 import vtk 27 import vtk
28 28
29 import invesalius.constants as const 29 import invesalius.constants as const
30 import invesalius.data.imagedata_utils as iu 30 import invesalius.data.imagedata_utils as iu
31 import invesalius.session as ses 31 import invesalius.session as ses
32 32
33 -from wx.lib.pubsub import pub as Publisher 33 +from . import floodfill
34 34
  35 +from wx.lib.pubsub import pub as Publisher
  36 +from scipy import ndimage
35 37
36 class EditionHistoryNode(object): 38 class EditionHistoryNode(object):
37 def __init__(self, index, orientation, array, clean=False): 39 def __init__(self, index, orientation, array, clean=False):
@@ -43,11 +45,11 @@ class EditionHistoryNode(object): @@ -43,11 +45,11 @@ class EditionHistoryNode(object):
43 self._save_array(array) 45 self._save_array(array)
44 46
45 def _save_array(self, array): 47 def _save_array(self, array):
46 - numpy.save(self.filename, array) 48 + np.save(self.filename, array)
47 print "Saving history", self.index, self.orientation, self.filename, self.clean 49 print "Saving history", self.index, self.orientation, self.filename, self.clean
48 50
49 def commit_history(self, mvolume): 51 def commit_history(self, mvolume):
50 - array = numpy.load(self.filename) 52 + array = np.load(self.filename)
51 if self.orientation == 'AXIAL': 53 if self.orientation == 'AXIAL':
52 mvolume[self.index+1,1:,1:] = array 54 mvolume[self.index+1,1:,1:] = array
53 if self.clean: 55 if self.clean:
@@ -295,7 +297,7 @@ class Mask(): @@ -295,7 +297,7 @@ class Mask():
295 def _open_mask(self, filename, shape, dtype='uint8'): 297 def _open_mask(self, filename, shape, dtype='uint8'):
296 print ">>", filename, shape 298 print ">>", filename, shape
297 self.temp_file = filename 299 self.temp_file = filename
298 - self.matrix = numpy.memmap(filename, shape=shape, dtype=dtype, mode="r+") 300 + self.matrix = np.memmap(filename, shape=shape, dtype=dtype, mode="r+")
299 301
300 def _set_class_index(self, index): 302 def _set_class_index(self, index):
301 Mask.general_index = index 303 Mask.general_index = index
@@ -309,7 +311,7 @@ class Mask(): @@ -309,7 +311,7 @@ class Mask():
309 """ 311 """
310 self.temp_file = tempfile.mktemp() 312 self.temp_file = tempfile.mktemp()
311 shape = shape[0] + 1, shape[1] + 1, shape[2] + 1 313 shape = shape[0] + 1, shape[1] + 1, shape[2] + 1
312 - self.matrix = numpy.memmap(self.temp_file, mode='w+', dtype='uint8', shape=shape) 314 + self.matrix = np.memmap(self.temp_file, mode='w+', dtype='uint8', shape=shape)
313 315
314 def clean(self): 316 def clean(self):
315 self.matrix[1:, 1:, 1:] = 0 317 self.matrix[1:, 1:, 1:] = 0
@@ -340,6 +342,24 @@ class Mask(): @@ -340,6 +342,24 @@ class Mask():
340 def clear_history(self): 342 def clear_history(self):
341 self.history.clear_history() 343 self.history.clear_history()
342 344
  345 + def fill_holes_auto(self, idx):
  346 + matrix = self.matrix[idx+1, 1:, 1:]
  347 + matrix = matrix.reshape(1, matrix.shape[0], matrix.shape[1])
  348 + imask = (~(matrix > 127))
  349 + labels, nlabels = ndimage.label(imask, output=np.uint16)
  350 +
  351 + floodfill.fill_holes_automatically(matrix, labels, nlabels, 100000)
  352 +
  353 + # for l in xrange(nlabels):
  354 + # trues = (labels == l)
  355 + # size = trues.sum()
  356 + # print l, size
  357 +
  358 + # if size <= 1000:
  359 + # matrix[trues] = 254
  360 + # self.was_edited = True
  361 +
  362 +
343 def __del__(self): 363 def __del__(self):
344 if self.is_shown: 364 if self.is_shown:
345 self.history._config_undo_redo(False) 365 self.history._config_undo_redo(False)
invesalius/data/slice_.py
@@ -194,6 +194,8 @@ class Slice(object): @@ -194,6 +194,8 @@ class Slice(object):
194 194
195 Publisher.subscribe(self.__undo_edition, 'Undo edition') 195 Publisher.subscribe(self.__undo_edition, 'Undo edition')
196 Publisher.subscribe(self.__redo_edition, 'Redo edition') 196 Publisher.subscribe(self.__redo_edition, 'Redo edition')
  197 +
  198 + Publisher.subscribe(self._fill_holes_auto, 'Fill holes automatically')
197 199
198 def GetMaxSliceNumber(self, orientation): 200 def GetMaxSliceNumber(self, orientation):
199 shape = self.matrix.shape 201 shape = self.matrix.shape
@@ -1482,3 +1484,17 @@ class Slice(object): @@ -1482,3 +1484,17 @@ class Slice(object):
1482 #filename, filetype = pubsub_evt.data 1484 #filename, filetype = pubsub_evt.data
1483 #if (filetype == const.FILETYPE_IMAGEDATA): 1485 #if (filetype == const.FILETYPE_IMAGEDATA):
1484 #iu.Export(imagedata, filename) 1486 #iu.Export(imagedata, filename)
  1487 +
  1488 + def _fill_holes_auto(self, pubsub_evt):
  1489 + self.do_threshold_to_all_slices()
  1490 + self.current_mask.fill_holes_auto(self.buffer_slices['AXIAL'].index)
  1491 +
  1492 + self.buffer_slices['AXIAL'].discard_mask()
  1493 + self.buffer_slices['CORONAL'].discard_mask()
  1494 + self.buffer_slices['SAGITAL'].discard_mask()
  1495 +
  1496 + self.buffer_slices['AXIAL'].discard_vtk_mask()
  1497 + self.buffer_slices['CORONAL'].discard_vtk_mask()
  1498 + self.buffer_slices['SAGITAL'].discard_vtk_mask()
  1499 +
  1500 + Publisher.sendMessage('Reload actual slice')
invesalius/gui/dialogs.py
@@ -1863,11 +1863,11 @@ class PanelTargeFFill(wx.Panel): @@ -1863,11 +1863,11 @@ class PanelTargeFFill(wx.Panel):
1863 self.Layout() 1863 self.Layout()
1864 1864
1865 class Panel2DConnectivity(wx.Panel): 1865 class Panel2DConnectivity(wx.Panel):
1866 - def __init__(self, parent, ID=-1, style=wx.TAB_TRAVERSAL|wx.NO_BORDER): 1866 + def __init__(self, parent, ID=-1, show_orientation=False, style=wx.TAB_TRAVERSAL|wx.NO_BORDER):
1867 wx.Panel.__init__(self, parent, ID, style=style) 1867 wx.Panel.__init__(self, parent, ID, style=style)
1868 - self._init_gui() 1868 + self._init_gui(show_orientation)
1869 1869
1870 - def _init_gui(self): 1870 + def _init_gui(self, show_orientation):
1871 self.conect2D_4 = wx.RadioButton(self, -1, "4", style=wx.RB_GROUP) 1871 self.conect2D_4 = wx.RadioButton(self, -1, "4", style=wx.RB_GROUP)
1872 self.conect2D_8 = wx.RadioButton(self, -1, "8") 1872 self.conect2D_8 = wx.RadioButton(self, -1, "8")
1873 1873
@@ -1879,6 +1879,14 @@ class Panel2DConnectivity(wx.Panel): @@ -1879,6 +1879,14 @@ class Panel2DConnectivity(wx.Panel):
1879 sizer.Add(self.conect2D_8, (2, 1), flag=wx.LEFT, border=7) 1879 sizer.Add(self.conect2D_8, (2, 1), flag=wx.LEFT, border=7)
1880 sizer.AddStretchSpacer((3, 0)) 1880 sizer.AddStretchSpacer((3, 0))
1881 1881
  1882 + if show_orientation:
  1883 + self.cmb_orientation = wx.ComboBox(self, -1, choices=(_(u"Axial"), _(u"Coronal"), _(u"Sagital")), style=wx.CB_READONLY)
  1884 + self.cmb_orientation.SetSelection(0)
  1885 +
  1886 + sizer.Add(wx.StaticText(self, -1, _(u"Orientation")), (4, 0), (1, 6), flag=wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=5)
  1887 + sizer.Add(self.cmb_orientation, (5, 0), (1, 10), flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=7)
  1888 + sizer.AddStretchSpacer((6, 0))
  1889 +
1882 self.SetSizer(sizer) 1890 self.SetSizer(sizer)
1883 sizer.Fit(self) 1891 sizer.Fit(self)
1884 self.Layout() 1892 self.Layout()
@@ -2473,3 +2481,90 @@ class CropOptionsDialog(wx.Dialog): @@ -2473,3 +2481,90 @@ class CropOptionsDialog(wx.Dialog):
2473 Publisher.sendMessage('Disable style', const.SLICE_STATE_CROP_MASK) 2481 Publisher.sendMessage('Disable style', const.SLICE_STATE_CROP_MASK)
2474 evt.Skip() 2482 evt.Skip()
2475 self.Destroy() 2483 self.Destroy()
  2484 +
  2485 +
  2486 +class FillHolesAutoDialog(wx.Dialog):
  2487 + def __init__(self, title, config):
  2488 + pre = wx.PreDialog()
  2489 + pre.Create(wx.GetApp().GetTopWindow(), -1, title, style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT)
  2490 + self.PostCreate(pre)
  2491 +
  2492 + self._init_gui()
  2493 +
  2494 + def _init_gui(self):
  2495 + if sys.platform == "win32":
  2496 + border_style = wx.SIMPLE_BORDER
  2497 + else:
  2498 + border_style = wx.SUNKEN_BORDER
  2499 +
  2500 + self.panel_target = PanelTargeFFill(self, style=border_style|wx.TAB_TRAVERSAL)
  2501 + self.panel2dcon = Panel2DConnectivity(self, show_orientation=True, style=border_style|wx.TAB_TRAVERSAL)
  2502 + self.panel3dcon = Panel3DConnectivity(self, style=border_style|wx.TAB_TRAVERSAL)
  2503 +
  2504 + self.panel_target.target_2d.SetValue(1)
  2505 + self.panel2dcon.Enable(1)
  2506 + self.panel3dcon.Enable(0)
  2507 +
  2508 + self.panel2dcon.conect2D_8.SetValue(1)
  2509 +
  2510 + self.apply_btn = wx.Button(self, wx.ID_APPLY)
  2511 + self.close_btn = wx.Button(self, wx.ID_CLOSE)
  2512 +
  2513 + # Sizer
  2514 + sizer = wx.BoxSizer(wx.VERTICAL)
  2515 +
  2516 + sizer.AddSpacer(5)
  2517 + sizer.Add(wx.StaticText(self, -1, _(u"Parameters")), flag=wx.LEFT, border=5)
  2518 + sizer.AddSpacer(5)
  2519 + sizer.Add(self.panel_target, flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=7)
  2520 + sizer.AddSpacer(5)
  2521 + sizer.Add(self.panel2dcon, flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=7)
  2522 + sizer.AddSpacer(5)
  2523 + sizer.Add(self.panel3dcon, flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=7)
  2524 + sizer.AddSpacer(5)
  2525 + sizer.Add(self.apply_btn, 0, flag=wx.ALIGN_RIGHT|wx.RIGHT, border=7)
  2526 + sizer.Add(self.close_btn, 0, flag=wx.ALIGN_RIGHT|wx.RIGHT, border=7)
  2527 + sizer.AddSpacer(5)
  2528 +
  2529 + self.SetSizer(sizer)
  2530 + sizer.Fit(self)
  2531 + self.Layout()
  2532 +
  2533 + self.close_btn.Bind(wx.EVT_BUTTON, self.OnBtnClose)
  2534 + self.Bind(wx.EVT_RADIOBUTTON, self.OnSetRadio)
  2535 + self.Bind(wx.EVT_CLOSE, self.OnClose)
  2536 +
  2537 + def OnBtnClose(self, evt):
  2538 + self.Close()
  2539 +
  2540 + def OnSetRadio(self, evt):
  2541 + # Target
  2542 + if self.panel_target.target_2d.GetValue():
  2543 + self.config.target = "2D"
  2544 + self.panel2dcon.Enable(1)
  2545 + self.panel3dcon.Enable(0)
  2546 + else:
  2547 + self.config.target = "3D"
  2548 + self.panel3dcon.Enable(1)
  2549 + self.panel2dcon.Enable(0)
  2550 +
  2551 + # 2D
  2552 + if self.panel2dcon.conect2D_4.GetValue():
  2553 + self.config.con_2d = 4
  2554 + elif self.panel2dcon.conect2D_8.GetValue():
  2555 + self.config.con_2d = 8
  2556 +
  2557 + # 3D
  2558 + if self.panel3dcon.conect3D_6.GetValue():
  2559 + self.config.con_3d = 6
  2560 + elif self.panel3dcon.conect3D_18.GetValue():
  2561 + self.config.con_3d = 18
  2562 + elif self.panel3dcon.conect3D_26.GetValue():
  2563 + self.config.con_3d = 26
  2564 +
  2565 + def OnClose(self, evt):
  2566 + print "ONCLOSE"
  2567 + if self.config.dlg_visible:
  2568 + Publisher.sendMessage('Disable style', const.SLICE_STATE_MASK_FFILL)
  2569 + evt.Skip()
  2570 + self.Destroy()
invesalius/gui/frame.py
@@ -445,6 +445,9 @@ class Frame(wx.Frame): @@ -445,6 +445,9 @@ class Frame(wx.Frame):
445 elif id == const.ID_FLOODFILL_MASK: 445 elif id == const.ID_FLOODFILL_MASK:
446 self.OnFillHolesManually() 446 self.OnFillHolesManually()
447 447
  448 + elif id == const.ID_FILL_HOLE_AUTO:
  449 + self.OnFillHolesAutomatically()
  450 +
448 elif id == const.ID_REMOVE_MASK_PART: 451 elif id == const.ID_REMOVE_MASK_PART:
449 self.OnRemoveMaskParts() 452 self.OnRemoveMaskParts()
450 453
@@ -592,6 +595,11 @@ class Frame(wx.Frame): @@ -592,6 +595,11 @@ class Frame(wx.Frame):
592 def OnFillHolesManually(self): 595 def OnFillHolesManually(self):
593 Publisher.sendMessage('Enable style', const.SLICE_STATE_MASK_FFILL) 596 Publisher.sendMessage('Enable style', const.SLICE_STATE_MASK_FFILL)
594 597
  598 + def OnFillHolesAutomatically(self):
  599 + # Publisher.sendMessage('Fill holes automatically')
  600 + fdlg = dlg.FillHolesAutoDialog()
  601 + fdlg.Show()
  602 +
595 def OnRemoveMaskParts(self): 603 def OnRemoveMaskParts(self):
596 Publisher.sendMessage('Enable style', const.SLICE_STATE_REMOVE_MASK_PARTS) 604 Publisher.sendMessage('Enable style', const.SLICE_STATE_REMOVE_MASK_PARTS)
597 605
@@ -628,6 +636,7 @@ class MenuBar(wx.MenuBar): @@ -628,6 +636,7 @@ class MenuBar(wx.MenuBar):
628 const.ID_PROJECT_CLOSE, 636 const.ID_PROJECT_CLOSE,
629 const.ID_REORIENT_IMG, 637 const.ID_REORIENT_IMG,
630 const.ID_FLOODFILL_MASK, 638 const.ID_FLOODFILL_MASK,
  639 + const.ID_FILL_HOLE_AUTO,
631 const.ID_REMOVE_MASK_PART, 640 const.ID_REMOVE_MASK_PART,
632 const.ID_SELECT_MASK_PART, 641 const.ID_SELECT_MASK_PART,
633 const.ID_FLOODFILL_SEGMENTATION,] 642 const.ID_FLOODFILL_SEGMENTATION,]
@@ -746,6 +755,9 @@ class MenuBar(wx.MenuBar): @@ -746,6 +755,9 @@ class MenuBar(wx.MenuBar):
746 self.fill_hole_mask_menu = mask_menu.Append(const.ID_FLOODFILL_MASK, _(u"Fill holes manually")) 755 self.fill_hole_mask_menu = mask_menu.Append(const.ID_FLOODFILL_MASK, _(u"Fill holes manually"))
747 self.fill_hole_mask_menu.Enable(False) 756 self.fill_hole_mask_menu.Enable(False)
748 757
  758 + self.fill_hole_auto_menu = mask_menu.Append(const.ID_FILL_HOLE_AUTO, _(u"Fill holes automatically"))
  759 + self.fill_hole_mask_menu.Enable(False)
  760 +
749 self.remove_mask_part_menu = mask_menu.Append(const.ID_REMOVE_MASK_PART, _(u"Remove parts")) 761 self.remove_mask_part_menu = mask_menu.Append(const.ID_REMOVE_MASK_PART, _(u"Remove parts"))
750 self.remove_mask_part_menu.Enable(False) 762 self.remove_mask_part_menu.Enable(False)
751 763