Commit e0d8a6393e482788eecd7be651de1043f139379c
Committed by
GitHub
1 parent
6e437a17
Exists in
master
and in
15 other branches
Add Region growing confidence segmentation tool (#57)
2D and 3D segmentation. Added a GUI to config this segmentation.
Showing
2 changed files
with
187 additions
and
52 deletions
Show diff stats
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() | ... | ... |