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 517  
518 518 ID_REORIENT_IMG = wx.NewId()
519 519 ID_FLOODFILL_MASK = wx.NewId()
  520 +ID_FILL_HOLE_AUTO = wx.NewId()
520 521 ID_REMOVE_MASK_PART = wx.NewId()
521 522 ID_SELECT_MASK_PART = wx.NewId()
522 523 ID_FLOODFILL_SEGMENTATION = wx.NewId()
... ...
invesalius/data/floodfill.pyx
... ... @@ -4,6 +4,7 @@ cimport cython
4 4  
5 5 from collections import deque
6 6  
  7 +from cython.parallel import prange
7 8 from libc.math cimport floor, ceil
8 9 from libcpp.deque cimport deque as cdeque
9 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 231  
231 232 if to_return:
232 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 23 import shutil
24 24 import tempfile
25 25  
26   -import numpy
  26 +import numpy as np
27 27 import vtk
28 28  
29 29 import invesalius.constants as const
30 30 import invesalius.data.imagedata_utils as iu
31 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 38 class EditionHistoryNode(object):
37 39 def __init__(self, index, orientation, array, clean=False):
... ... @@ -43,11 +45,11 @@ class EditionHistoryNode(object):
43 45 self._save_array(array)
44 46  
45 47 def _save_array(self, array):
46   - numpy.save(self.filename, array)
  48 + np.save(self.filename, array)
47 49 print "Saving history", self.index, self.orientation, self.filename, self.clean
48 50  
49 51 def commit_history(self, mvolume):
50   - array = numpy.load(self.filename)
  52 + array = np.load(self.filename)
51 53 if self.orientation == 'AXIAL':
52 54 mvolume[self.index+1,1:,1:] = array
53 55 if self.clean:
... ... @@ -295,7 +297,7 @@ class Mask():
295 297 def _open_mask(self, filename, shape, dtype='uint8'):
296 298 print ">>", filename, shape
297 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 302 def _set_class_index(self, index):
301 303 Mask.general_index = index
... ... @@ -309,7 +311,7 @@ class Mask():
309 311 """
310 312 self.temp_file = tempfile.mktemp()
311 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 316 def clean(self):
315 317 self.matrix[1:, 1:, 1:] = 0
... ... @@ -340,6 +342,24 @@ class Mask():
340 342 def clear_history(self):
341 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 363 def __del__(self):
344 364 if self.is_shown:
345 365 self.history._config_undo_redo(False)
... ...
invesalius/data/slice_.py
... ... @@ -194,6 +194,8 @@ class Slice(object):
194 194  
195 195 Publisher.subscribe(self.__undo_edition, 'Undo edition')
196 196 Publisher.subscribe(self.__redo_edition, 'Redo edition')
  197 +
  198 + Publisher.subscribe(self._fill_holes_auto, 'Fill holes automatically')
197 199  
198 200 def GetMaxSliceNumber(self, orientation):
199 201 shape = self.matrix.shape
... ... @@ -1482,3 +1484,17 @@ class Slice(object):
1482 1484 #filename, filetype = pubsub_evt.data
1483 1485 #if (filetype == const.FILETYPE_IMAGEDATA):
1484 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 1863 self.Layout()
1864 1864  
1865 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 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 1871 self.conect2D_4 = wx.RadioButton(self, -1, "4", style=wx.RB_GROUP)
1872 1872 self.conect2D_8 = wx.RadioButton(self, -1, "8")
1873 1873  
... ... @@ -1879,6 +1879,14 @@ class Panel2DConnectivity(wx.Panel):
1879 1879 sizer.Add(self.conect2D_8, (2, 1), flag=wx.LEFT, border=7)
1880 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 1890 self.SetSizer(sizer)
1883 1891 sizer.Fit(self)
1884 1892 self.Layout()
... ... @@ -2473,3 +2481,90 @@ class CropOptionsDialog(wx.Dialog):
2473 2481 Publisher.sendMessage('Disable style', const.SLICE_STATE_CROP_MASK)
2474 2482 evt.Skip()
2475 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 445 elif id == const.ID_FLOODFILL_MASK:
446 446 self.OnFillHolesManually()
447 447  
  448 + elif id == const.ID_FILL_HOLE_AUTO:
  449 + self.OnFillHolesAutomatically()
  450 +
448 451 elif id == const.ID_REMOVE_MASK_PART:
449 452 self.OnRemoveMaskParts()
450 453  
... ... @@ -592,6 +595,11 @@ class Frame(wx.Frame):
592 595 def OnFillHolesManually(self):
593 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 603 def OnRemoveMaskParts(self):
596 604 Publisher.sendMessage('Enable style', const.SLICE_STATE_REMOVE_MASK_PARTS)
597 605  
... ... @@ -628,6 +636,7 @@ class MenuBar(wx.MenuBar):
628 636 const.ID_PROJECT_CLOSE,
629 637 const.ID_REORIENT_IMG,
630 638 const.ID_FLOODFILL_MASK,
  639 + const.ID_FILL_HOLE_AUTO,
631 640 const.ID_REMOVE_MASK_PART,
632 641 const.ID_SELECT_MASK_PART,
633 642 const.ID_FLOODFILL_SEGMENTATION,]
... ... @@ -746,6 +755,9 @@ class MenuBar(wx.MenuBar):
746 755 self.fill_hole_mask_menu = mask_menu.Append(const.ID_FLOODFILL_MASK, _(u"Fill holes manually"))
747 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 761 self.remove_mask_part_menu = mask_menu.Append(const.ID_REMOVE_MASK_PART, _(u"Remove parts"))
750 762 self.remove_mask_part_menu.Enable(False)
751 763  
... ...