Commit 16a1bfd7153c0613dfee307397d5a902edf0da65

Authored by Thiago Franco de Moraes
Committed by GitHub
1 parent a25e8aeb

Floodfill segmentation (2D and 3D)

Adds floodfill segmentation to InVesalius. Its possible to segment using an given threshold or a dynamic threshold. The dynamic threshold is based on the value pointed by the user and a deviation (given by the user). Also, it's possible to apply an WW&Wl before the segmentation.
invesalius/constants.py
... ... @@ -484,6 +484,7 @@ ID_REORIENT_IMG = wx.NewId()
484 484 ID_FLOODFILL_MASK = wx.NewId()
485 485 ID_REMOVE_MASK_PART = wx.NewId()
486 486 ID_SELECT_MASK_PART = wx.NewId()
  487 +ID_FLOODFILL_SEGMENTATION = wx.NewId()
487 488  
488 489 #---------------------------------------------------------
489 490 STATE_DEFAULT = 1000
... ... @@ -504,6 +505,7 @@ SLICE_STATE_REORIENT = 3010
504 505 SLICE_STATE_MASK_FFILL = 3011
505 506 SLICE_STATE_REMOVE_MASK_PARTS = 3012
506 507 SLICE_STATE_SELECT_MASK_PARTS = 3013
  508 +SLICE_STATE_FFILL_SEGMENTATION = 3014
507 509  
508 510 VOLUME_STATE_SEED = 2001
509 511 # STATE_LINEAR_MEASURE = 3001
... ... @@ -524,6 +526,7 @@ SLICE_STYLES.append(SLICE_STATE_WATERSHED)
524 526 SLICE_STYLES.append(SLICE_STATE_MASK_FFILL)
525 527 SLICE_STYLES.append(SLICE_STATE_REMOVE_MASK_PARTS)
526 528 SLICE_STYLES.append(SLICE_STATE_SELECT_MASK_PARTS)
  529 +SLICE_STYLES.append(SLICE_STATE_FFILL_SEGMENTATION)
527 530  
528 531 VOLUME_STYLES = TOOL_STATES + [VOLUME_STATE_SEED, STATE_MEASURE_DISTANCE,
529 532 STATE_MEASURE_ANGLE]
... ... @@ -535,6 +538,7 @@ STYLE_LEVEL = {SLICE_STATE_EDITOR: 1,
535 538 SLICE_STATE_MASK_FFILL: 2,
536 539 SLICE_STATE_REMOVE_MASK_PARTS: 2,
537 540 SLICE_STATE_SELECT_MASK_PARTS: 2,
  541 + SLICE_STATE_FFILL_SEGMENTATION: 2,
538 542 SLICE_STATE_CROSS: 2,
539 543 SLICE_STATE_SCROLL: 2,
540 544 SLICE_STATE_REORIENT: 2,
... ...
invesalius/data/styles.py
... ... @@ -76,6 +76,16 @@ def get_LUT_value(data, window, level):
76 76 data.shape = shape
77 77 return data
78 78  
  79 +def get_LUT_value_255(data, window, level):
  80 + shape = data.shape
  81 + data_ = data.ravel()
  82 + data = np.piecewise(data_,
  83 + [data_ <= (level - 0.5 - (window-1)/2),
  84 + data_ > (level - 0.5 + (window-1)/2)],
  85 + [0, 255, lambda data_: ((data_ - (level - 0.5))/(window-1) + 0.5)*(255)])
  86 + data.shape = shape
  87 + return data
  88 +
79 89  
80 90 class BaseImageInteractorStyle(vtk.vtkInteractorStyleImage):
81 91 def __init__(self, viewer):
... ... @@ -1841,6 +1851,200 @@ class SelectMaskPartsInteractorStyle(DefaultInteractorStyle):
1841 1851 self.config.mask = mask
1842 1852  
1843 1853  
  1854 +class FFillSegmentationConfig(object):
  1855 + __metaclass__= utils.Singleton
  1856 + def __init__(self):
  1857 + self.dlg_visible = False
  1858 + self.target = "2D"
  1859 + self.con_2d = 4
  1860 + self.con_3d = 6
  1861 +
  1862 + self.t0 = None
  1863 + self.t1 = None
  1864 +
  1865 + self.fill_value = 254
  1866 +
  1867 + self.method = 'threshold'
  1868 +
  1869 + self.dev_min = 25
  1870 + self.dev_max = 25
  1871 +
  1872 + self.use_ww_wl = True
  1873 +
  1874 +
  1875 +class FloodFillSegmentInteractorStyle(DefaultInteractorStyle):
  1876 + def __init__(self, viewer):
  1877 + DefaultInteractorStyle.__init__(self, viewer)
  1878 +
  1879 + self.viewer = viewer
  1880 + self.orientation = self.viewer.orientation
  1881 +
  1882 + self.picker = vtk.vtkWorldPointPicker()
  1883 + self.slice_actor = viewer.slice_data.actor
  1884 + self.slice_data = viewer.slice_data
  1885 +
  1886 + self.config = FFillSegmentationConfig()
  1887 + self.dlg_ffill = None
  1888 +
  1889 + self._progr_title = _(u"Floodfill segmentation")
  1890 + self._progr_msg = _(u"Segmenting ...")
  1891 +
  1892 + self.AddObserver("LeftButtonPressEvent", self.OnFFClick)
  1893 +
  1894 + def SetUp(self):
  1895 + if not self.config.dlg_visible:
  1896 +
  1897 + if self.config.t0 is None:
  1898 + image = self.viewer.slice_.matrix
  1899 + _min, _max = image.min(), image.max()
  1900 +
  1901 + self.config.t0 = int(_min + (3.0/4.0) * (_max - _min))
  1902 + self.config.t1 = int(_max)
  1903 +
  1904 + self.config.dlg_visible = True
  1905 + self.dlg_ffill = dialogs.FFillSegmentationOptionsDialog(self.config)
  1906 + self.dlg_ffill.Show()
  1907 +
  1908 + def CleanUp(self):
  1909 + if (self.dlg_ffill is not None) and (self.config.dlg_visible):
  1910 + self.config.dlg_visible = False
  1911 + self.dlg_ffill.Destroy()
  1912 + self.dlg_ffill = None
  1913 +
  1914 + def OnFFClick(self, obj, evt):
  1915 + if (self.viewer.slice_.buffer_slices[self.orientation].mask is None):
  1916 + return
  1917 +
  1918 + if self.config.target == "3D":
  1919 + self.do_3d_seg()
  1920 + with futures.ThreadPoolExecutor(max_workers=1) as executor:
  1921 + future = executor.submit(self.do_3d_seg)
  1922 +
  1923 + dlg = wx.ProgressDialog(self._progr_title, self._progr_msg, parent=None, style=wx.PD_APP_MODAL)
  1924 + while not future.done():
  1925 + dlg.Pulse()
  1926 + time.sleep(0.1)
  1927 +
  1928 + dlg.Destroy()
  1929 +
  1930 + else:
  1931 + self.do_2d_seg()
  1932 +
  1933 + self.viewer.slice_.buffer_slices['AXIAL'].discard_mask()
  1934 + self.viewer.slice_.buffer_slices['CORONAL'].discard_mask()
  1935 + self.viewer.slice_.buffer_slices['SAGITAL'].discard_mask()
  1936 +
  1937 + self.viewer.slice_.buffer_slices['AXIAL'].discard_vtk_mask()
  1938 + self.viewer.slice_.buffer_slices['CORONAL'].discard_vtk_mask()
  1939 + self.viewer.slice_.buffer_slices['SAGITAL'].discard_vtk_mask()
  1940 +
  1941 + self.viewer.slice_.current_mask.was_edited = True
  1942 + Publisher.sendMessage('Reload actual slice')
  1943 +
  1944 + def do_2d_seg(self):
  1945 + viewer = self.viewer
  1946 + iren = viewer.interactor
  1947 + mouse_x, mouse_y = iren.GetEventPosition()
  1948 + x, y = self.viewer.get_slice_pixel_coord_by_screen_pos(mouse_x, mouse_y, self.picker)
  1949 +
  1950 + mask = self.viewer.slice_.buffer_slices[self.orientation].mask.copy()
  1951 + image = self.viewer.slice_.buffer_slices[self.orientation].image
  1952 +
  1953 + if self.config.method == 'threshold':
  1954 + v = image[y, x]
  1955 + t0 = self.config.t0
  1956 + t1 = self.config.t1
  1957 +
  1958 + elif self.config.method == 'dynamic':
  1959 + if self.config.use_ww_wl:
  1960 + print "Using WW&WL"
  1961 + ww = self.viewer.slice_.window_width
  1962 + wl = self.viewer.slice_.window_level
  1963 + image = get_LUT_value_255(image, ww, wl)
  1964 +
  1965 + v = image[y, x]
  1966 +
  1967 + t0 = v - self.config.dev_min
  1968 + t1 = v + self.config.dev_max
  1969 +
  1970 + if image[y, x] < t0 or image[y, x] > t1:
  1971 + return
  1972 +
  1973 + dy, dx = image.shape
  1974 + image = image.reshape((1, dy, dx))
  1975 + mask = mask.reshape((1, dy, dx))
  1976 +
  1977 + out_mask = np.zeros_like(mask)
  1978 +
  1979 + bstruct = np.array(generate_binary_structure(2, CON2D[self.config.con_2d]), dtype='uint8')
  1980 + bstruct = bstruct.reshape((1, 3, 3))
  1981 +
  1982 + floodfill.floodfill_threshold(image, [[x, y, 0]], t0, t1, 1, bstruct, out_mask)
  1983 +
  1984 + mask[out_mask.astype('bool')] = self.config.fill_value
  1985 +
  1986 + index = self.viewer.slice_.buffer_slices[self.orientation].index
  1987 + b_mask = self.viewer.slice_.buffer_slices[self.orientation].mask
  1988 + vol_mask = self.viewer.slice_.current_mask.matrix[1:, 1:, 1:]
  1989 +
  1990 + if self.orientation == 'AXIAL':
  1991 + vol_mask[index, :, :] = mask
  1992 + elif self.orientation == 'CORONAL':
  1993 + vol_mask[:, index, :] = mask
  1994 + elif self.orientation == 'SAGITAL':
  1995 + vol_mask[:, :, index] = mask
  1996 +
  1997 + self.viewer.slice_.current_mask.save_history(index, self.orientation, mask, b_mask)
  1998 +
  1999 + def do_3d_seg(self):
  2000 + viewer = self.viewer
  2001 + iren = viewer.interactor
  2002 + mouse_x, mouse_y = iren.GetEventPosition()
  2003 + x, y, z = self.viewer.get_voxel_coord_by_screen_pos(mouse_x, mouse_y, self.picker)
  2004 +
  2005 + mask = self.viewer.slice_.current_mask.matrix[1:, 1:, 1:]
  2006 + image = self.viewer.slice_.matrix
  2007 +
  2008 + if self.config.method == 'threshold':
  2009 + v = image[z, y, x]
  2010 + t0 = self.config.t0
  2011 + t1 = self.config.t1
  2012 +
  2013 + elif self.config.method == 'dynamic':
  2014 + if self.config.use_ww_wl:
  2015 + print "Using WW&WL"
  2016 + ww = self.viewer.slice_.window_width
  2017 + wl = self.viewer.slice_.window_level
  2018 + image = get_LUT_value_255(image, ww, wl)
  2019 +
  2020 + v = image[z, y, x]
  2021 +
  2022 + t0 = v - self.config.dev_min
  2023 + t1 = v + self.config.dev_max
  2024 +
  2025 + if image[z, y, x] < t0 or image[z, y, x] > t1:
  2026 + return
  2027 +
  2028 + out_mask = np.zeros_like(mask)
  2029 +
  2030 + bstruct = np.array(generate_binary_structure(3, CON3D[self.config.con_3d]), dtype='uint8')
  2031 + self.viewer.slice_.do_threshold_to_all_slices()
  2032 + cp_mask = self.viewer.slice_.current_mask.matrix.copy()
  2033 +
  2034 + with futures.ThreadPoolExecutor(max_workers=1) as executor:
  2035 + future = executor.submit(floodfill.floodfill_threshold, image, [[x, y, z]], t0, t1, 1, bstruct, out_mask)
  2036 +
  2037 + dlg = wx.ProgressDialog(self._progr_title, self._progr_msg, parent=None, style=wx.PD_APP_MODAL)
  2038 + while not future.done():
  2039 + dlg.Pulse()
  2040 + time.sleep(0.1)
  2041 +
  2042 + dlg.Destroy()
  2043 +
  2044 + mask[out_mask.astype('bool')] = self.config.fill_value
  2045 +
  2046 + self.viewer.slice_.current_mask.save_history(0, 'VOLUME', self.viewer.slice_.current_mask.matrix.copy(), cp_mask)
  2047 +
1844 2048 def get_style(style):
1845 2049 STYLES = {
1846 2050 const.STATE_DEFAULT: DefaultInteractorStyle,
... ... @@ -1859,6 +2063,7 @@ def get_style(style):
1859 2063 const.SLICE_STATE_MASK_FFILL: FloodFillMaskInteractorStyle,
1860 2064 const.SLICE_STATE_REMOVE_MASK_PARTS: RemoveMaskPartsInteractorStyle,
1861 2065 const.SLICE_STATE_SELECT_MASK_PARTS: SelectMaskPartsInteractorStyle,
  2066 + const.SLICE_STATE_FFILL_SEGMENTATION: FloodFillSegmentInteractorStyle,
1862 2067 }
1863 2068 return STYLES[style]
1864 2069  
... ...
invesalius/gui/dialogs.py
... ... @@ -2031,3 +2031,177 @@ class SelectPartsOptionsDialog(wx.Dialog):
2031 2031 Publisher.sendMessage('Disable style', const.SLICE_STATE_SELECT_MASK_PARTS)
2032 2032 evt.Skip()
2033 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 + self.threshold.SetMinSize((250, -1))
  2090 +
  2091 + self.method_threshold = wx.RadioButton(self, -1, _(u"Threshold"), style=wx.RB_GROUP)
  2092 + self.method_dynamic = wx.RadioButton(self, -1, _(u"Dynamic"))
  2093 +
  2094 + if self.config.method == 'dynamic':
  2095 + self.method_dynamic.SetValue(1)
  2096 + else:
  2097 + self.method_threshold.SetValue(1)
  2098 + self.config.method = 'threshold'
  2099 +
  2100 + self.use_ww_wl = wx.CheckBox(self, -1, _(u"Use WW\&WL"))
  2101 + self.use_ww_wl.SetValue(self.config.use_ww_wl)
  2102 +
  2103 + self.deviation_min = wx.SpinCtrl(self, -1, value='%d' % self.config.dev_min, min=0, max=10000)
  2104 + self.deviation_max = wx.SpinCtrl(self, -1, value='%d' % self.config.dev_max, min=0, max=10000)
  2105 +
  2106 + # Sizer
  2107 + sizer = wx.BoxSizer(wx.VERTICAL)
  2108 +
  2109 + sizer.AddSpacer(7)
  2110 +
  2111 + sizer.Add(wx.StaticText(self, -1, _(u"Parameters")), flag=wx.LEFT, border=7)
  2112 + sizer.AddSpacer(5)
  2113 + sizer.Add(self.target_2d, flag=wx.LEFT, border=9)
  2114 + sizer.Add(self.target_3d, flag=wx.LEFT, border=9)
  2115 +
  2116 + sizer.AddSpacer(7)
  2117 +
  2118 + sizer.Add(wx.StaticText(self, -1, _(u"2D Connectivity")), flag=wx.LEFT, border=9)
  2119 + sizer.AddSpacer(5)
  2120 + sizer_2d = wx.BoxSizer(wx.HORIZONTAL)
  2121 + sizer_2d.Add(self.conect2D_4, flag=wx.LEFT, border=11)
  2122 + sizer_2d.Add(self.conect2D_8, flag=wx.LEFT, border=11)
  2123 + sizer.Add(sizer_2d)
  2124 +
  2125 + sizer.AddSpacer(7)
  2126 +
  2127 + sizer.Add(wx.StaticText(self, -1, _(u"3D Connectivity")), flag=wx.LEFT, border=9)
  2128 + sizer.AddSpacer(5)
  2129 + sizer_3d = wx.BoxSizer(wx.HORIZONTAL)
  2130 + sizer_3d.Add(self.conect3D_6, flag=wx.LEFT, border=11)
  2131 + sizer_3d.Add(self.conect3D_18, flag=wx.LEFT, border=11)
  2132 + sizer_3d.Add(self.conect3D_26, flag=wx.LEFT, border=11)
  2133 + sizer.Add(sizer_3d)
  2134 +
  2135 + sizer.AddSpacer(7)
  2136 +
  2137 + sizer.Add(wx.StaticText(self, -1, _(u"Method")), flag=wx.LEFT, border=9)
  2138 + sizer.AddSpacer(5)
  2139 + sizer.Add(self.method_threshold, flag=wx.LEFT, border=11)
  2140 + sizer.AddSpacer(5)
  2141 + sizer.Add(self.threshold, flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=13)
  2142 + sizer.AddSpacer(5)
  2143 + sizer.Add(self.method_dynamic, flag=wx.LEFT, border=11)
  2144 + sizer.AddSpacer(5)
  2145 + sizer.Add(self.use_ww_wl, flag=wx.LEFT, border=13)
  2146 + sizer.AddSpacer(5)
  2147 + sizer.Add(self.deviation_min, flag=wx.LEFT, border=13)
  2148 + sizer.Add(self.deviation_max, flag=wx.LEFT, border=13)
  2149 +
  2150 + sizer.AddSpacer(7)
  2151 +
  2152 + self.SetSizer(sizer)
  2153 + sizer.Fit(self)
  2154 + self.Layout()
  2155 +
  2156 + self.Bind(wx.EVT_RADIOBUTTON, self.OnSetRadio)
  2157 + self.Bind(grad.EVT_THRESHOLD_CHANGING, self.OnSlideChanged, self.threshold)
  2158 + self.Bind(grad.EVT_THRESHOLD_CHANGED, self.OnSlideChanged, self.threshold)
  2159 + self.use_ww_wl.Bind(wx.EVT_CHECKBOX, self.OnSetUseWWWL)
  2160 + self.deviation_min.Bind(wx.EVT_SPINCTRL, self.OnSetDeviation)
  2161 + self.deviation_max.Bind(wx.EVT_SPINCTRL, self.OnSetDeviation)
  2162 + self.Bind(wx.EVT_CLOSE, self.OnClose)
  2163 +
  2164 + def OnSetRadio(self, evt):
  2165 + # Target
  2166 + if self.target_2d.GetValue():
  2167 + self.config.target = "2D"
  2168 + else:
  2169 + self.config.target = "3D"
  2170 +
  2171 + # 2D
  2172 + if self.conect2D_4.GetValue():
  2173 + self.config.con_2d = 4
  2174 + elif self.conect2D_8.GetValue():
  2175 + self.config.con_2d = 8
  2176 +
  2177 + # 3D
  2178 + if self.conect3D_6.GetValue():
  2179 + self.config.con_3d = 6
  2180 + elif self.conect3D_18.GetValue():
  2181 + self.config.con_3d = 18
  2182 + elif self.conect3D_26.GetValue():
  2183 + self.config.con_3d = 26
  2184 +
  2185 + # Method
  2186 + if self.method_threshold.GetValue():
  2187 + self.config.method = 'threshold'
  2188 + else:
  2189 + self.config.method = 'dynamic'
  2190 +
  2191 + def OnSlideChanged(self, evt):
  2192 + self.config.t0 = int(self.threshold.GetMinValue())
  2193 + self.config.t1 = int(self.threshold.GetMaxValue())
  2194 + print self.config.t0, self.config.t1
  2195 +
  2196 + def OnSetUseWWWL(self, evt):
  2197 + self.config.use_ww_wl = self.use_ww_wl.GetValue()
  2198 +
  2199 + def OnSetDeviation(self, evt):
  2200 + self.config.dev_max = self.deviation_max.GetValue()
  2201 + self.config.dev_min = self.deviation_min.GetValue()
  2202 +
  2203 + def OnClose(self, evt):
  2204 + if self.config.dlg_visible:
  2205 + Publisher.sendMessage('Disable style', const.SLICE_STATE_MASK_FFILL)
  2206 + evt.Skip()
  2207 + self.Destroy()
... ...
invesalius/gui/frame.py
... ... @@ -454,6 +454,9 @@ class Frame(wx.Frame):
454 454 elif id == const.ID_SELECT_MASK_PART:
455 455 self.OnSelectMaskParts()
456 456  
  457 + elif id == const.ID_FLOODFILL_SEGMENTATION:
  458 + self.OnFFillSegmentation()
  459 +
457 460 elif id == const.ID_VIEW_INTERPOLATED:
458 461 st = self.actived_interpolated_slices.IsChecked(const.ID_VIEW_INTERPOLATED)
459 462 if st:
... ... @@ -593,6 +596,9 @@ class Frame(wx.Frame):
593 596 def OnSelectMaskParts(self):
594 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 602 def OnInterpolatedSlices(self, status):
597 603 Publisher.sendMessage('Set interpolated slices', status)
598 604  
... ... @@ -618,7 +624,8 @@ class MenuBar(wx.MenuBar):
618 624 const.ID_REORIENT_IMG,
619 625 const.ID_FLOODFILL_MASK,
620 626 const.ID_REMOVE_MASK_PART,
621   - const.ID_SELECT_MASK_PART,]
  627 + const.ID_SELECT_MASK_PART,
  628 + const.ID_FLOODFILL_SEGMENTATION,]
622 629 self.__init_items()
623 630 self.__bind_events()
624 631  
... ... @@ -741,6 +748,13 @@ class MenuBar(wx.MenuBar):
741 748  
742 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 758 # Image menu
745 759 image_menu = wx.Menu()
746 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 772 self.view_menu.Check(const.ID_VIEW_INTERPOLATED, v)
759 773  
760 774 self.actived_interpolated_slices = self.view_menu
761   -
762   -
763 775  
764 776 #view_tool_menu = wx.Menu()
765 777 #app = view_tool_menu.Append
... ...