Commit e0d8a6393e482788eecd7be651de1043f139379c

Authored by Thiago Franco de Moraes
Committed by GitHub
1 parent 6e437a17

Add Region growing confidence segmentation tool (#57)

2D and 3D segmentation. Added a GUI to config this segmentation.
invesalius/data/styles.py
... ... @@ -1983,6 +1983,9 @@ class FFillSegmentationConfig(object):
1983 1983  
1984 1984 self.use_ww_wl = True
1985 1985  
  1986 + self.confid_mult = 2.5
  1987 + self.confid_iters = 3
  1988 +
1986 1989  
1987 1990 class FloodFillSegmentInteractorStyle(DefaultInteractorStyle):
1988 1991 def __init__(self, viewer):
... ... @@ -2052,36 +2055,47 @@ class FloodFillSegmentInteractorStyle(DefaultInteractorStyle):
2052 2055 mask = self.viewer.slice_.buffer_slices[self.orientation].mask.copy()
2053 2056 image = self.viewer.slice_.buffer_slices[self.orientation].image
2054 2057  
2055   - if self.config.method == 'threshold':
2056   - v = image[y, x]
2057   - t0 = self.config.t0
2058   - t1 = self.config.t1
  2058 + if self.config.method == 'confidence':
  2059 + dy, dx = image.shape
  2060 + image = image.reshape((1, dy, dx))
  2061 + mask = mask.reshape((1, dy, dx))
2059 2062  
2060   - elif self.config.method == 'dynamic':
2061   - if self.config.use_ww_wl:
2062   - print "Using WW&WL"
2063   - ww = self.viewer.slice_.window_width
2064   - wl = self.viewer.slice_.window_level
2065   - image = get_LUT_value_255(image, ww, wl)
  2063 + bstruct = np.array(generate_binary_structure(2, CON2D[self.config.con_2d]), dtype='uint8')
  2064 + bstruct = bstruct.reshape((1, 3, 3))
2066 2065  
2067   - v = image[y, x]
  2066 + out_mask = self.do_rg_confidence(image, mask, (x, y, 0), bstruct)
  2067 + else:
  2068 + if self.config.method == 'threshold':
  2069 + v = image[y, x]
  2070 + t0 = self.config.t0
  2071 + t1 = self.config.t1
2068 2072  
2069   - t0 = v - self.config.dev_min
2070   - t1 = v + self.config.dev_max
  2073 + elif self.config.method == 'dynamic':
  2074 + if self.config.use_ww_wl:
  2075 + print "Using WW&WL"
  2076 + ww = self.viewer.slice_.window_width
  2077 + wl = self.viewer.slice_.window_level
  2078 + image = get_LUT_value_255(image, ww, wl)
2071 2079  
2072   - if image[y, x] < t0 or image[y, x] > t1:
2073   - return
  2080 + v = image[y, x]
2074 2081  
2075   - dy, dx = image.shape
2076   - image = image.reshape((1, dy, dx))
2077   - mask = mask.reshape((1, dy, dx))
  2082 + t0 = v - self.config.dev_min
  2083 + t1 = v + self.config.dev_max
2078 2084  
2079   - out_mask = np.zeros_like(mask)
2080 2085  
2081   - bstruct = np.array(generate_binary_structure(2, CON2D[self.config.con_2d]), dtype='uint8')
2082   - bstruct = bstruct.reshape((1, 3, 3))
  2086 + if image[y, x] < t0 or image[y, x] > t1:
  2087 + return
  2088 +
  2089 + dy, dx = image.shape
  2090 + image = image.reshape((1, dy, dx))
  2091 + mask = mask.reshape((1, dy, dx))
  2092 +
  2093 + out_mask = np.zeros_like(mask)
  2094 +
  2095 + bstruct = np.array(generate_binary_structure(2, CON2D[self.config.con_2d]), dtype='uint8')
  2096 + bstruct = bstruct.reshape((1, 3, 3))
2083 2097  
2084   - floodfill.floodfill_threshold(image, [[x, y, 0]], t0, t1, 1, bstruct, out_mask)
  2098 + floodfill.floodfill_threshold(image, [[x, y, 0]], t0, t1, 1, bstruct, out_mask)
2085 2099  
2086 2100 mask[out_mask.astype('bool')] = self.config.fill_value
2087 2101  
... ... @@ -2107,46 +2121,91 @@ class FloodFillSegmentInteractorStyle(DefaultInteractorStyle):
2107 2121 mask = self.viewer.slice_.current_mask.matrix[1:, 1:, 1:]
2108 2122 image = self.viewer.slice_.matrix
2109 2123  
2110   - if self.config.method == 'threshold':
2111   - v = image[z, y, x]
2112   - t0 = self.config.t0
2113   - t1 = self.config.t1
  2124 + if self.config.method != 'confidence':
  2125 + if self.config.method == 'threshold':
  2126 + v = image[z, y, x]
  2127 + t0 = self.config.t0
  2128 + t1 = self.config.t1
2114 2129  
2115   - elif self.config.method == 'dynamic':
2116   - if self.config.use_ww_wl:
2117   - print "Using WW&WL"
2118   - ww = self.viewer.slice_.window_width
2119   - wl = self.viewer.slice_.window_level
2120   - image = get_LUT_value_255(image, ww, wl)
  2130 + elif self.config.method == 'dynamic':
  2131 + if self.config.use_ww_wl:
  2132 + print "Using WW&WL"
  2133 + ww = self.viewer.slice_.window_width
  2134 + wl = self.viewer.slice_.window_level
  2135 + image = get_LUT_value_255(image, ww, wl)
2121 2136  
2122   - v = image[z, y, x]
  2137 + v = image[z, y, x]
2123 2138  
2124   - t0 = v - self.config.dev_min
2125   - t1 = v + self.config.dev_max
  2139 + t0 = v - self.config.dev_min
  2140 + t1 = v + self.config.dev_max
2126 2141  
2127   - if image[z, y, x] < t0 or image[z, y, x] > t1:
2128   - return
  2142 + if image[z, y, x] < t0 or image[z, y, x] > t1:
  2143 + return
2129 2144  
2130   - out_mask = np.zeros_like(mask)
2131 2145  
2132 2146 bstruct = np.array(generate_binary_structure(3, CON3D[self.config.con_3d]), dtype='uint8')
2133 2147 self.viewer.slice_.do_threshold_to_all_slices()
2134 2148 cp_mask = self.viewer.slice_.current_mask.matrix.copy()
2135 2149  
2136   - with futures.ThreadPoolExecutor(max_workers=1) as executor:
2137   - future = executor.submit(floodfill.floodfill_threshold, image, [[x, y, z]], t0, t1, 1, bstruct, out_mask)
  2150 + if self.config.method == 'confidence':
  2151 + with futures.ThreadPoolExecutor(max_workers=1) as executor:
  2152 + future = executor.submit(self.do_rg_confidence, image, mask, (x, y, z), bstruct)
2138 2153  
2139   - dlg = wx.ProgressDialog(self._progr_title, self._progr_msg, parent=None, style=wx.PD_APP_MODAL)
2140   - while not future.done():
2141   - dlg.Pulse()
2142   - time.sleep(0.1)
  2154 + dlg = wx.ProgressDialog(self._progr_title, self._progr_msg, parent=None, style=wx.PD_APP_MODAL)
  2155 + while not future.done():
  2156 + dlg.Pulse()
  2157 + time.sleep(0.1)
  2158 + dlg.Destroy()
  2159 + out_mask = future.result()
  2160 + else:
  2161 + out_mask = np.zeros_like(mask)
  2162 + with futures.ThreadPoolExecutor(max_workers=1) as executor:
  2163 + future = executor.submit(floodfill.floodfill_threshold, image, [[x, y, z]], t0, t1, 1, bstruct, out_mask)
2143 2164  
2144   - dlg.Destroy()
  2165 + dlg = wx.ProgressDialog(self._progr_title, self._progr_msg, parent=None, style=wx.PD_APP_MODAL)
  2166 + while not future.done():
  2167 + dlg.Pulse()
  2168 + time.sleep(0.1)
  2169 +
  2170 + dlg.Destroy()
2145 2171  
2146 2172 mask[out_mask.astype('bool')] = self.config.fill_value
2147 2173  
2148 2174 self.viewer.slice_.current_mask.save_history(0, 'VOLUME', self.viewer.slice_.current_mask.matrix.copy(), cp_mask)
2149 2175  
  2176 + def do_rg_confidence(self, image, mask, p, bstruct):
  2177 + x, y, z = p
  2178 + if self.config.use_ww_wl:
  2179 + ww = self.viewer.slice_.window_width
  2180 + wl = self.viewer.slice_.window_level
  2181 + image = get_LUT_value_255(image, ww, wl)
  2182 + bool_mask = np.zeros_like(mask, dtype='bool')
  2183 + out_mask = np.zeros_like(mask)
  2184 +
  2185 + for k in xrange(int(z-1), int(z+2)):
  2186 + if k < 0 or k >= bool_mask.shape[0]:
  2187 + continue
  2188 + for j in xrange(int(y-1), int(y+2)):
  2189 + if j < 0 or j >= bool_mask.shape[1]:
  2190 + continue
  2191 + for i in xrange(int(x-1), int(x+2)):
  2192 + if i < 0 or i >= bool_mask.shape[2]:
  2193 + continue
  2194 + bool_mask[k, j, i] = True
  2195 +
  2196 + for i in xrange(self.config.confid_iters):
  2197 + var = np.std(image[bool_mask])
  2198 + mean = np.mean(image[bool_mask])
  2199 +
  2200 + t0 = mean - var * self.config.confid_mult
  2201 + t1 = mean + var * self.config.confid_mult
  2202 +
  2203 + floodfill.floodfill_threshold(image, [[x, y, z]], t0, t1, 1, bstruct, out_mask)
  2204 +
  2205 + bool_mask[out_mask == 1] = True
  2206 +
  2207 + return out_mask
  2208 +
2150 2209 def get_style(style):
2151 2210 STYLES = {
2152 2211 const.STATE_DEFAULT: DefaultInteractorStyle,
... ...
invesalius/gui/dialogs.py
... ... @@ -2031,6 +2031,64 @@ class PanelFFillDynamic(wx.Panel):
2031 2031 self.config.dev_min = self.deviation_min.GetValue()
2032 2032  
2033 2033  
  2034 +class PanelFFillConfidence(wx.Panel):
  2035 + def __init__(self, parent, config, ID=-1, style=wx.TAB_TRAVERSAL|wx.NO_BORDER):
  2036 + wx.Panel.__init__(self, parent, ID, style=style)
  2037 +
  2038 + self.config = config
  2039 +
  2040 + self._init_gui()
  2041 +
  2042 + def _init_gui(self):
  2043 + self.use_ww_wl = wx.CheckBox(self, -1, _(u"Use WW&WL"))
  2044 + self.use_ww_wl.SetValue(self.config.use_ww_wl)
  2045 +
  2046 + self.spin_mult = floatspin.FloatSpin(self, -1,
  2047 + value=self.config.confid_mult,
  2048 + min_val=1.0, max_val=10.0,
  2049 + increment=0.1, digits=1,
  2050 + style=wx.TE_PROCESS_TAB|wx.TE_PROCESS_ENTER,
  2051 + agwStyle=floatspin.FS_RIGHT)
  2052 + w, h = self.spin_mult.GetTextExtent('M')
  2053 + self.spin_mult.SetMinSize((w*7, -1))
  2054 +
  2055 + self.spin_iters = wx.SpinCtrl(self, -1, value='%d' % self.config.confid_iters, min=0, max=100)
  2056 + self.spin_iters.SetMinSize((w*7, -1))
  2057 +
  2058 + sizer = wx.GridBagSizer(5, 5)
  2059 +
  2060 + sizer.AddStretchSpacer((0, 0))
  2061 +
  2062 + sizer.Add(self.use_ww_wl, (1, 0), (1, 6), flag=wx.LEFT, border=5)
  2063 +
  2064 + sizer.AddStretchSpacer((2, 0))
  2065 +
  2066 + sizer.Add(wx.StaticText(self, -1, _(u"Multiplier")), (3, 0), (1, 3), flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=5)
  2067 + sizer.Add(self.spin_mult, (3, 3), (1, 2))
  2068 +
  2069 + sizer.Add(wx.StaticText(self, -1, _(u"Iterations")), (4, 0), (1, 3), flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=5)
  2070 + sizer.Add(self.spin_iters, (4, 3), (1, 2))
  2071 +
  2072 + sizer.AddStretchSpacer((5, 0))
  2073 +
  2074 + self.SetSizer(sizer)
  2075 + sizer.Fit(self)
  2076 + self.Layout()
  2077 +
  2078 + self.use_ww_wl.Bind(wx.EVT_CHECKBOX, self.OnSetUseWWWL)
  2079 + self.spin_mult.Bind(wx.EVT_SPINCTRL, self.OnSetMult)
  2080 + self.spin_iters.Bind(wx.EVT_SPINCTRL, self.OnSetIters)
  2081 +
  2082 + def OnSetUseWWWL(self, evt):
  2083 + self.config.use_ww_wl = self.use_ww_wl.GetValue()
  2084 +
  2085 + def OnSetMult(self, evt):
  2086 + self.config.confid_mult = self.spin_mult.GetValue()
  2087 +
  2088 + def OnSetIters(self, evt):
  2089 + self.config.confid_iters = self.spin_iters.GetValue()
  2090 +
  2091 +
2034 2092 class FFillOptionsDialog(wx.Dialog):
2035 2093 def __init__(self, title, config):
2036 2094 pre = wx.PreDialog()
... ... @@ -2273,13 +2331,14 @@ class FFillSegmentationOptionsDialog(wx.Dialog):
2273 2331 else:
2274 2332 self.panel3dcon.conect3D_6.SetValue(1)
2275 2333  
2276   - self.cmb_method = wx.ComboBox(self, -1, choices=(_(u"Dynamic"), _(u"Threshold")), style=wx.CB_READONLY)
  2334 + self.cmb_method = wx.ComboBox(self, -1, choices=(_(u"Dynamic"), _(u"Threshold"), _(u"Confidence")), style=wx.CB_READONLY)
2277 2335  
2278 2336 if self.config.method == 'dynamic':
2279 2337 self.cmb_method.SetSelection(0)
2280   - else:
  2338 + elif self.config.method == 'threshold':
2281 2339 self.cmb_method.SetSelection(1)
2282   - self.config.method = 'threshold'
  2340 + elif self.config.method == 'confidence':
  2341 + self.cmb_method.SetSelection(2)
2283 2342  
2284 2343 self.panel_ffill_threshold = PanelFFillThreshold(self, self.config, -1, style=border_style|wx.TAB_TRAVERSAL)
2285 2344 self.panel_ffill_threshold.SetMinSize((250, -1))
... ... @@ -2289,6 +2348,10 @@ class FFillSegmentationOptionsDialog(wx.Dialog):
2289 2348 self.panel_ffill_dynamic.SetMinSize((250, -1))
2290 2349 self.panel_ffill_dynamic.Hide()
2291 2350  
  2351 + self.panel_ffill_confidence = PanelFFillConfidence(self, self.config, -1, style=border_style|wx.TAB_TRAVERSAL)
  2352 + self.panel_ffill_confidence.SetMinSize((250, -1))
  2353 + self.panel_ffill_confidence.Hide()
  2354 +
2292 2355 self.close_btn = wx.Button(self, wx.ID_CLOSE)
2293 2356  
2294 2357 # Sizer
... ... @@ -2313,6 +2376,10 @@ class FFillSegmentationOptionsDialog(wx.Dialog):
2313 2376 self.cmb_method.SetSelection(0)
2314 2377 self.panel_ffill_dynamic.Show()
2315 2378 sizer.Add(self.panel_ffill_dynamic, (11, 0), (1, 6), flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=7)
  2379 + elif self.config.method == 'confidence':
  2380 + self.cmb_method.SetSelection(2)
  2381 + self.panel_ffill_confidence.Show()
  2382 + sizer.Add(self.panel_ffill_confidence, (11, 0), (1, 6), flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=7)
2316 2383 else:
2317 2384 self.cmb_method.SetSelection(1)
2318 2385 self.panel_ffill_threshold.Show()
... ... @@ -2358,16 +2425,25 @@ class FFillSegmentationOptionsDialog(wx.Dialog):
2358 2425 self.config.con_3d = 26
2359 2426  
2360 2427 def OnSetMethod(self, evt):
  2428 + item_panel = self.GetSizer().FindItemAtPosition((11, 0)).GetWindow()
  2429 +
2361 2430 if self.cmb_method.GetSelection() == 0:
2362 2431 self.config.method = 'dynamic'
2363   - self.panel_ffill_threshold.Hide()
  2432 + item_panel.Hide()
2364 2433 self.panel_ffill_dynamic.Show()
2365   - self.GetSizer().Replace(self.panel_ffill_threshold, self.panel_ffill_dynamic)
  2434 + self.GetSizer().Replace(item_panel, self.panel_ffill_dynamic)
  2435 +
  2436 + elif self.cmb_method.GetSelection() == 2:
  2437 + self.config.method = 'confidence'
  2438 + item_panel.Hide()
  2439 + self.panel_ffill_confidence.Show()
  2440 + self.GetSizer().Replace(item_panel, self.panel_ffill_confidence)
  2441 +
2366 2442 else:
2367 2443 self.config.method = 'threshold'
2368   - self.panel_ffill_dynamic.Hide()
  2444 + item_panel.Hide()
2369 2445 self.panel_ffill_threshold.Show()
2370   - self.GetSizer().Replace(self.panel_ffill_dynamic, self.panel_ffill_threshold)
  2446 + self.GetSizer().Replace(item_panel, self.panel_ffill_threshold)
2371 2447  
2372 2448 self.GetSizer().Fit(self)
2373 2449 self.Layout()
... ...