Commit d54e05ee97b91d31a931b4ceb6ec2b7ea6face14

Authored by Thiago Franco de Moraes
1 parent a25e8aeb
Exists in ffill_segmentation

Segmenting using floodfill

invesalius/constants.py
@@ -484,6 +484,7 @@ ID_REORIENT_IMG = wx.NewId() @@ -484,6 +484,7 @@ ID_REORIENT_IMG = wx.NewId()
484 ID_FLOODFILL_MASK = wx.NewId() 484 ID_FLOODFILL_MASK = wx.NewId()
485 ID_REMOVE_MASK_PART = wx.NewId() 485 ID_REMOVE_MASK_PART = wx.NewId()
486 ID_SELECT_MASK_PART = wx.NewId() 486 ID_SELECT_MASK_PART = wx.NewId()
  487 +ID_FLOODFILL_SEGMENTATION = wx.NewId()
487 488
488 #--------------------------------------------------------- 489 #---------------------------------------------------------
489 STATE_DEFAULT = 1000 490 STATE_DEFAULT = 1000
@@ -504,6 +505,7 @@ SLICE_STATE_REORIENT = 3010 @@ -504,6 +505,7 @@ SLICE_STATE_REORIENT = 3010
504 SLICE_STATE_MASK_FFILL = 3011 505 SLICE_STATE_MASK_FFILL = 3011
505 SLICE_STATE_REMOVE_MASK_PARTS = 3012 506 SLICE_STATE_REMOVE_MASK_PARTS = 3012
506 SLICE_STATE_SELECT_MASK_PARTS = 3013 507 SLICE_STATE_SELECT_MASK_PARTS = 3013
  508 +SLICE_STATE_FFILL_SEGMENTATION = 3014
507 509
508 VOLUME_STATE_SEED = 2001 510 VOLUME_STATE_SEED = 2001
509 # STATE_LINEAR_MEASURE = 3001 511 # STATE_LINEAR_MEASURE = 3001
@@ -524,6 +526,7 @@ SLICE_STYLES.append(SLICE_STATE_WATERSHED) @@ -524,6 +526,7 @@ SLICE_STYLES.append(SLICE_STATE_WATERSHED)
524 SLICE_STYLES.append(SLICE_STATE_MASK_FFILL) 526 SLICE_STYLES.append(SLICE_STATE_MASK_FFILL)
525 SLICE_STYLES.append(SLICE_STATE_REMOVE_MASK_PARTS) 527 SLICE_STYLES.append(SLICE_STATE_REMOVE_MASK_PARTS)
526 SLICE_STYLES.append(SLICE_STATE_SELECT_MASK_PARTS) 528 SLICE_STYLES.append(SLICE_STATE_SELECT_MASK_PARTS)
  529 +SLICE_STYLES.append(SLICE_STATE_FFILL_SEGMENTATION)
527 530
528 VOLUME_STYLES = TOOL_STATES + [VOLUME_STATE_SEED, STATE_MEASURE_DISTANCE, 531 VOLUME_STYLES = TOOL_STATES + [VOLUME_STATE_SEED, STATE_MEASURE_DISTANCE,
529 STATE_MEASURE_ANGLE] 532 STATE_MEASURE_ANGLE]
@@ -535,6 +538,7 @@ STYLE_LEVEL = {SLICE_STATE_EDITOR: 1, @@ -535,6 +538,7 @@ STYLE_LEVEL = {SLICE_STATE_EDITOR: 1,
535 SLICE_STATE_MASK_FFILL: 2, 538 SLICE_STATE_MASK_FFILL: 2,
536 SLICE_STATE_REMOVE_MASK_PARTS: 2, 539 SLICE_STATE_REMOVE_MASK_PARTS: 2,
537 SLICE_STATE_SELECT_MASK_PARTS: 2, 540 SLICE_STATE_SELECT_MASK_PARTS: 2,
  541 + SLICE_STATE_FFILL_SEGMENTATION: 2,
538 SLICE_STATE_CROSS: 2, 542 SLICE_STATE_CROSS: 2,
539 SLICE_STATE_SCROLL: 2, 543 SLICE_STATE_SCROLL: 2,
540 SLICE_STATE_REORIENT: 2, 544 SLICE_STATE_REORIENT: 2,
invesalius/data/styles.py
@@ -1841,6 +1841,120 @@ class SelectMaskPartsInteractorStyle(DefaultInteractorStyle): @@ -1841,6 +1841,120 @@ class SelectMaskPartsInteractorStyle(DefaultInteractorStyle):
1841 self.config.mask = mask 1841 self.config.mask = mask
1842 1842
1843 1843
  1844 +class FFillSegmentationConfig(object):
  1845 + __metaclass__= utils.Singleton
  1846 + def __init__(self):
  1847 + self.dlg_visible = False
  1848 + self.target = "2D"
  1849 + self.con_2d = 4
  1850 + self.con_3d = 6
  1851 +
  1852 + self.t0 = 0
  1853 + self.t1 = 100
  1854 +
  1855 + self.fill_value = 254
  1856 +
  1857 +
  1858 +class FloodFillSegmentInteractorStyle(DefaultInteractorStyle):
  1859 + def __init__(self, viewer):
  1860 + DefaultInteractorStyle.__init__(self, viewer)
  1861 +
  1862 + self.viewer = viewer
  1863 + self.orientation = self.viewer.orientation
  1864 +
  1865 + self.picker = vtk.vtkWorldPointPicker()
  1866 + self.slice_actor = viewer.slice_data.actor
  1867 + self.slice_data = viewer.slice_data
  1868 +
  1869 + self.config = FFillSegmentationConfig()
  1870 + self.dlg_ffill = None
  1871 +
  1872 + self._progr_title = _(u"Fill hole")
  1873 + self._progr_msg = _(u"Filling hole ...")
  1874 +
  1875 + self.AddObserver("LeftButtonPressEvent", self.OnFFClick)
  1876 +
  1877 + def SetUp(self):
  1878 + if not self.config.dlg_visible:
  1879 + self.config.dlg_visible = True
  1880 + self.dlg_ffill = dialogs.FFillSegmentationOptionsDialog(self.config)
  1881 + self.dlg_ffill.Show()
  1882 +
  1883 + def CleanUp(self):
  1884 + if (self.dlg_ffill is not None) and (self.config.dlg_visible):
  1885 + self.config.dlg_visible = False
  1886 + self.dlg_ffill.Destroy()
  1887 + self.dlg_ffill = None
  1888 +
  1889 + def OnFFClick(self, obj, evt):
  1890 + if (self.viewer.slice_.buffer_slices[self.orientation].mask is None):
  1891 + return
  1892 +
  1893 + viewer = self.viewer
  1894 + iren = viewer.interactor
  1895 + mouse_x, mouse_y = iren.GetEventPosition()
  1896 + x, y, z = self.viewer.get_voxel_coord_by_screen_pos(mouse_x, mouse_y, self.picker)
  1897 +
  1898 + mask = self.viewer.slice_.current_mask.matrix[1:, 1:, 1:]
  1899 + image = self.viewer.slice_.matrix
  1900 +
  1901 + if mask[z, y, x] < self.config.t0 or mask[z, y, x] > self.config.t1:
  1902 + return
  1903 +
  1904 + if self.config.target == "3D":
  1905 + bstruct = np.array(generate_binary_structure(3, CON3D[self.config.con_3d]), dtype='uint8')
  1906 + self.viewer.slice_.do_threshold_to_all_slices()
  1907 + cp_mask = self.viewer.slice_.current_mask.matrix.copy()
  1908 + else:
  1909 + _bstruct = generate_binary_structure(2, CON2D[self.config.con_2d])
  1910 + if self.orientation == 'AXIAL':
  1911 + bstruct = np.zeros((1, 3, 3), dtype='uint8')
  1912 + bstruct[0] = _bstruct
  1913 + elif self.orientation == 'CORONAL':
  1914 + bstruct = np.zeros((3, 1, 3), dtype='uint8')
  1915 + bstruct[:, 0, :] = _bstruct
  1916 + elif self.orientation == 'SAGITAL':
  1917 + bstruct = np.zeros((3, 3, 1), dtype='uint8')
  1918 + bstruct[:, :, 0] = _bstruct
  1919 +
  1920 + if self.config.target == '2D':
  1921 + floodfill.floodfill_threshold(image, [[x, y, z]], self.config.t0, self.config.t1, self.config.fill_value, bstruct, mask)
  1922 + b_mask = self.viewer.slice_.buffer_slices[self.orientation].mask
  1923 + index = self.viewer.slice_.buffer_slices[self.orientation].index
  1924 +
  1925 + if self.orientation == 'AXIAL':
  1926 + p_mask = mask[index,:,:].copy()
  1927 + elif self.orientation == 'CORONAL':
  1928 + p_mask = mask[:, index, :].copy()
  1929 + elif self.orientation == 'SAGITAL':
  1930 + p_mask = mask[:, :, index].copy()
  1931 +
  1932 + self.viewer.slice_.current_mask.save_history(index, self.orientation, p_mask, b_mask)
  1933 + else:
  1934 + with futures.ThreadPoolExecutor(max_workers=1) as executor:
  1935 + future = executor.submit(floodfill.floodfill_threshold, image, [[x, y, z]], self.config.t0, self.config.t1, self.config.fill_value, bstruct, mask)
  1936 +
  1937 + dlg = wx.ProgressDialog(self._progr_title, self._progr_msg, parent=None, style=wx.PD_APP_MODAL)
  1938 + while not future.done():
  1939 + dlg.Pulse()
  1940 + time.sleep(0.1)
  1941 +
  1942 + dlg.Destroy()
  1943 +
  1944 + self.viewer.slice_.current_mask.save_history(0, 'VOLUME', self.viewer.slice_.current_mask.matrix.copy(), cp_mask)
  1945 +
  1946 + self.viewer.slice_.buffer_slices['AXIAL'].discard_mask()
  1947 + self.viewer.slice_.buffer_slices['CORONAL'].discard_mask()
  1948 + self.viewer.slice_.buffer_slices['SAGITAL'].discard_mask()
  1949 +
  1950 + self.viewer.slice_.buffer_slices['AXIAL'].discard_vtk_mask()
  1951 + self.viewer.slice_.buffer_slices['CORONAL'].discard_vtk_mask()
  1952 + self.viewer.slice_.buffer_slices['SAGITAL'].discard_vtk_mask()
  1953 +
  1954 + self.viewer.slice_.current_mask.was_edited = True
  1955 + Publisher.sendMessage('Reload actual slice')
  1956 +
  1957 +
1844 def get_style(style): 1958 def get_style(style):
1845 STYLES = { 1959 STYLES = {
1846 const.STATE_DEFAULT: DefaultInteractorStyle, 1960 const.STATE_DEFAULT: DefaultInteractorStyle,
@@ -1859,6 +1973,7 @@ def get_style(style): @@ -1859,6 +1973,7 @@ def get_style(style):
1859 const.SLICE_STATE_MASK_FFILL: FloodFillMaskInteractorStyle, 1973 const.SLICE_STATE_MASK_FFILL: FloodFillMaskInteractorStyle,
1860 const.SLICE_STATE_REMOVE_MASK_PARTS: RemoveMaskPartsInteractorStyle, 1974 const.SLICE_STATE_REMOVE_MASK_PARTS: RemoveMaskPartsInteractorStyle,
1861 const.SLICE_STATE_SELECT_MASK_PARTS: SelectMaskPartsInteractorStyle, 1975 const.SLICE_STATE_SELECT_MASK_PARTS: SelectMaskPartsInteractorStyle,
  1976 + const.SLICE_STATE_FFILL_SEGMENTATION: FloodFillSegmentInteractorStyle,
1862 } 1977 }
1863 return STYLES[style] 1978 return STYLES[style]
1864 1979
invesalius/gui/dialogs.py
@@ -2031,3 +2031,124 @@ class SelectPartsOptionsDialog(wx.Dialog): @@ -2031,3 +2031,124 @@ class SelectPartsOptionsDialog(wx.Dialog):
2031 Publisher.sendMessage('Disable style', const.SLICE_STATE_SELECT_MASK_PARTS) 2031 Publisher.sendMessage('Disable style', const.SLICE_STATE_SELECT_MASK_PARTS)
2032 evt.Skip() 2032 evt.Skip()
2033 self.Destroy() 2033 self.Destroy()
  2034 +
  2035 +
  2036 +class FFillSegmentationOptionsDialog(wx.Dialog):
  2037 + def __init__(self, config):
  2038 + pre = wx.PreDialog()
  2039 + pre.Create(wx.GetApp().GetTopWindow(), -1, _(u"Floodfill Segmentation"), style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT)
  2040 + self.PostCreate(pre)
  2041 +
  2042 + self.config = config
  2043 +
  2044 + self._init_gui()
  2045 +
  2046 + def _init_gui(self):
  2047 + """
  2048 + Create the widgets.
  2049 + """
  2050 + import project as prj
  2051 + # Target
  2052 + self.target_2d = wx.RadioButton(self, -1, _(u"2D - Actual slice"), style=wx.RB_GROUP)
  2053 + self.target_3d = wx.RadioButton(self, -1, _(u"3D - All slices"))
  2054 +
  2055 + if self.config.target == "2D":
  2056 + self.target_2d.SetValue(1)
  2057 + else:
  2058 + self.target_3d.SetValue(1)
  2059 +
  2060 + # Connectivity 2D
  2061 + self.conect2D_4 = wx.RadioButton(self, -1, "4", style=wx.RB_GROUP)
  2062 + self.conect2D_8 = wx.RadioButton(self, -1, "8")
  2063 +
  2064 + if self.config.con_2d == 8:
  2065 + self.conect2D_8.SetValue(1)
  2066 + else:
  2067 + self.conect2D_4.SetValue(1)
  2068 + self.config.con_2d = 4
  2069 +
  2070 + # Connectivity 3D
  2071 + self.conect3D_6 = wx.RadioButton(self, -1, "6", style=wx.RB_GROUP)
  2072 + self.conect3D_18 = wx.RadioButton(self, -1, "18")
  2073 + self.conect3D_26 = wx.RadioButton(self, -1, "26")
  2074 +
  2075 + if self.config.con_3d == 18:
  2076 + self.conect3D_18.SetValue(1)
  2077 + elif self.config.con_3d == 26:
  2078 + self.conect3D_26.SetValue(1)
  2079 + else:
  2080 + self.conect3D_6.SetValue(1)
  2081 +
  2082 + project = prj.Project()
  2083 + bound_min, bound_max = project.threshold_range
  2084 + colour = [i*255 for i in const.MASK_COLOUR[0]]
  2085 + colour.append(100)
  2086 + self.threshold = grad.GradientCtrl(self, -1, int(bound_min),
  2087 + int(bound_max), self.config.t0,
  2088 + self.config.t1, colour)
  2089 +
  2090 + # Sizer
  2091 + sizer = wx.GridBagSizer(15, 6)
  2092 + sizer.AddStretchSpacer((0, 0))
  2093 +
  2094 + sizer.Add(wx.StaticText(self, -1, _(u"Parameters")), (1, 0), (1, 6), flag=wx.LEFT, border=7)
  2095 + sizer.Add(self.target_2d, (2, 0), (1, 6), flag=wx.LEFT, border=9)
  2096 + sizer.Add(self.target_3d, (3, 0), (1, 6), flag=wx.LEFT, border=9)
  2097 +
  2098 + sizer.AddStretchSpacer((4, 0))
  2099 +
  2100 + sizer.Add(wx.StaticText(self, -1, _(u"2D Connectivity")), (5, 0), (1, 6), flag=wx.LEFT, border=9)
  2101 + sizer.Add(self.conect2D_4, (6, 0), flag=wx.LEFT, border=9)
  2102 + sizer.Add(self.conect2D_8, (6, 1), flag=wx.LEFT, border=9)
  2103 +
  2104 + sizer.AddStretchSpacer((7, 0))
  2105 +
  2106 + sizer.Add(wx.StaticText(self, -1, _(u"3D Connectivity")), (8, 0), (1, 6), flag=wx.LEFT, border=9)
  2107 + sizer.Add(self.conect3D_6, (9, 0), flag=wx.LEFT, border=9)
  2108 + sizer.Add(self.conect3D_18, (9, 1), flag=wx.LEFT, border=9)
  2109 + sizer.Add(self.conect3D_26, (9, 2), flag=wx.LEFT, border=9)
  2110 + sizer.AddStretchSpacer((10, 0))
  2111 +
  2112 + sizer.Add(wx.StaticText(self, -1, _(u"Threshold")), (11, 0), (1, 6), flag=wx.LEFT, border=9)
  2113 + sizer.Add(self.threshold, (12, 0), (1, 6), flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=9)
  2114 + sizer.AddStretchSpacer((13, 0))
  2115 +
  2116 + self.SetSizer(sizer)
  2117 + sizer.Fit(self)
  2118 + self.Layout()
  2119 +
  2120 + self.Bind(wx.EVT_RADIOBUTTON, self.OnSetRadio)
  2121 + self.Bind(grad.EVT_THRESHOLD_CHANGING, self.OnSlideChanged, self.threshold)
  2122 + self.Bind(wx.EVT_CLOSE, self.OnClose)
  2123 +
  2124 + def OnSetRadio(self, evt):
  2125 + # Target
  2126 + if self.target_2d.GetValue():
  2127 + self.config.target = "2D"
  2128 + else:
  2129 + self.config.target = "3D"
  2130 +
  2131 + # 2D
  2132 + if self.conect2D_4.GetValue():
  2133 + self.config.con_2d = 4
  2134 + elif self.conect2D_8.GetValue():
  2135 + self.config.con_2d = 8
  2136 +
  2137 + # 3D
  2138 + if self.conect3D_6.GetValue():
  2139 + self.config.con_3d = 6
  2140 + elif self.conect3D_18.GetValue():
  2141 + self.config.con_3d = 18
  2142 + elif self.conect3D_26.GetValue():
  2143 + self.config.con_3d = 26
  2144 +
  2145 + def OnSlideChanged(self, evt):
  2146 + self.config.t0 = self.threshold.GetMinValue()
  2147 + self.config.t1 = self.threshold.GetMaxValue()
  2148 + print self.config.t0, self.config.t1
  2149 +
  2150 + def OnClose(self, evt):
  2151 + if self.config.dlg_visible:
  2152 + Publisher.sendMessage('Disable style', const.SLICE_STATE_MASK_FFILL)
  2153 + evt.Skip()
  2154 + self.Destroy()
invesalius/gui/frame.py
@@ -454,6 +454,9 @@ class Frame(wx.Frame): @@ -454,6 +454,9 @@ class Frame(wx.Frame):
454 elif id == const.ID_SELECT_MASK_PART: 454 elif id == const.ID_SELECT_MASK_PART:
455 self.OnSelectMaskParts() 455 self.OnSelectMaskParts()
456 456
  457 + elif id == const.ID_FLOODFILL_SEGMENTATION:
  458 + self.OnFFillSegmentation()
  459 +
457 elif id == const.ID_VIEW_INTERPOLATED: 460 elif id == const.ID_VIEW_INTERPOLATED:
458 st = self.actived_interpolated_slices.IsChecked(const.ID_VIEW_INTERPOLATED) 461 st = self.actived_interpolated_slices.IsChecked(const.ID_VIEW_INTERPOLATED)
459 if st: 462 if st:
@@ -593,6 +596,9 @@ class Frame(wx.Frame): @@ -593,6 +596,9 @@ class Frame(wx.Frame):
593 def OnSelectMaskParts(self): 596 def OnSelectMaskParts(self):
594 Publisher.sendMessage('Enable style', const.SLICE_STATE_SELECT_MASK_PARTS) 597 Publisher.sendMessage('Enable style', const.SLICE_STATE_SELECT_MASK_PARTS)
595 598
  599 + def OnFFillSegmentation(self):
  600 + Publisher.sendMessage('Enable style', const.SLICE_STATE_FFILL_SEGMENTATION)
  601 +
596 def OnInterpolatedSlices(self, status): 602 def OnInterpolatedSlices(self, status):
597 Publisher.sendMessage('Set interpolated slices', status) 603 Publisher.sendMessage('Set interpolated slices', status)
598 604
@@ -618,7 +624,8 @@ class MenuBar(wx.MenuBar): @@ -618,7 +624,8 @@ class MenuBar(wx.MenuBar):
618 const.ID_REORIENT_IMG, 624 const.ID_REORIENT_IMG,
619 const.ID_FLOODFILL_MASK, 625 const.ID_FLOODFILL_MASK,
620 const.ID_REMOVE_MASK_PART, 626 const.ID_REMOVE_MASK_PART,
621 - const.ID_SELECT_MASK_PART,] 627 + const.ID_SELECT_MASK_PART,
  628 + const.ID_FLOODFILL_SEGMENTATION,]
622 self.__init_items() 629 self.__init_items()
623 self.__bind_events() 630 self.__bind_events()
624 631
@@ -741,6 +748,13 @@ class MenuBar(wx.MenuBar): @@ -741,6 +748,13 @@ class MenuBar(wx.MenuBar):
741 748
742 tools_menu.AppendMenu(-1, _(u"Mask"), mask_menu) 749 tools_menu.AppendMenu(-1, _(u"Mask"), mask_menu)
743 750
  751 + # Segmentation Menu
  752 + segmentation_menu = wx.Menu()
  753 + self.ffill_segmentation = segmentation_menu.Append(const.ID_FLOODFILL_SEGMENTATION, _(u"Floodfill"))
  754 + self.ffill_segmentation.Enable(False)
  755 +
  756 + tools_menu.AppendMenu(-1, _("Segmentation"), segmentation_menu)
  757 +
744 # Image menu 758 # Image menu
745 image_menu = wx.Menu() 759 image_menu = wx.Menu()
746 reorient_menu = image_menu.Append(const.ID_REORIENT_IMG, _(u'Reorient image\tCtrl+Shift+R')) 760 reorient_menu = image_menu.Append(const.ID_REORIENT_IMG, _(u'Reorient image\tCtrl+Shift+R'))
@@ -758,8 +772,6 @@ class MenuBar(wx.MenuBar): @@ -758,8 +772,6 @@ class MenuBar(wx.MenuBar):
758 self.view_menu.Check(const.ID_VIEW_INTERPOLATED, v) 772 self.view_menu.Check(const.ID_VIEW_INTERPOLATED, v)
759 773
760 self.actived_interpolated_slices = self.view_menu 774 self.actived_interpolated_slices = self.view_menu
761 -  
762 -  
763 775
764 #view_tool_menu = wx.Menu() 776 #view_tool_menu = wx.Menu()
765 #app = view_tool_menu.Append 777 #app = view_tool_menu.Append
invesalius/gui/widgets/gradient.py
@@ -493,6 +493,7 @@ class GradientCtrl(wx.Panel): @@ -493,6 +493,7 @@ class GradientCtrl(wx.Panel):
493 return self.minimun 493 return self.minimun
494 494
495 def _GenerateEvent(self, event): 495 def _GenerateEvent(self, event):
  496 + print "GEN"
496 if event == myEVT_THRESHOLD_CHANGING: 497 if event == myEVT_THRESHOLD_CHANGING:
497 self.changed = True 498 self.changed = True
498 elif event == myEVT_THRESHOLD_CHANGED : 499 elif event == myEVT_THRESHOLD_CHANGED :