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,6 +1983,9 @@ class FFillSegmentationConfig(object): | ||
1983 | 1983 | ||
1984 | self.use_ww_wl = True | 1984 | self.use_ww_wl = True |
1985 | 1985 | ||
1986 | + self.confid_mult = 2.5 | ||
1987 | + self.confid_iters = 3 | ||
1988 | + | ||
1986 | 1989 | ||
1987 | class FloodFillSegmentInteractorStyle(DefaultInteractorStyle): | 1990 | class FloodFillSegmentInteractorStyle(DefaultInteractorStyle): |
1988 | def __init__(self, viewer): | 1991 | def __init__(self, viewer): |
@@ -2052,36 +2055,47 @@ class FloodFillSegmentInteractorStyle(DefaultInteractorStyle): | @@ -2052,36 +2055,47 @@ class FloodFillSegmentInteractorStyle(DefaultInteractorStyle): | ||
2052 | mask = self.viewer.slice_.buffer_slices[self.orientation].mask.copy() | 2055 | mask = self.viewer.slice_.buffer_slices[self.orientation].mask.copy() |
2053 | image = self.viewer.slice_.buffer_slices[self.orientation].image | 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 | mask[out_mask.astype('bool')] = self.config.fill_value | 2100 | mask[out_mask.astype('bool')] = self.config.fill_value |
2087 | 2101 | ||
@@ -2107,46 +2121,91 @@ class FloodFillSegmentInteractorStyle(DefaultInteractorStyle): | @@ -2107,46 +2121,91 @@ class FloodFillSegmentInteractorStyle(DefaultInteractorStyle): | ||
2107 | mask = self.viewer.slice_.current_mask.matrix[1:, 1:, 1:] | 2121 | mask = self.viewer.slice_.current_mask.matrix[1:, 1:, 1:] |
2108 | image = self.viewer.slice_.matrix | 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 | bstruct = np.array(generate_binary_structure(3, CON3D[self.config.con_3d]), dtype='uint8') | 2146 | bstruct = np.array(generate_binary_structure(3, CON3D[self.config.con_3d]), dtype='uint8') |
2133 | self.viewer.slice_.do_threshold_to_all_slices() | 2147 | self.viewer.slice_.do_threshold_to_all_slices() |
2134 | cp_mask = self.viewer.slice_.current_mask.matrix.copy() | 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 | mask[out_mask.astype('bool')] = self.config.fill_value | 2172 | mask[out_mask.astype('bool')] = self.config.fill_value |
2147 | 2173 | ||
2148 | self.viewer.slice_.current_mask.save_history(0, 'VOLUME', self.viewer.slice_.current_mask.matrix.copy(), cp_mask) | 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 | def get_style(style): | 2209 | def get_style(style): |
2151 | STYLES = { | 2210 | STYLES = { |
2152 | const.STATE_DEFAULT: DefaultInteractorStyle, | 2211 | const.STATE_DEFAULT: DefaultInteractorStyle, |
invesalius/gui/dialogs.py
@@ -2031,6 +2031,64 @@ class PanelFFillDynamic(wx.Panel): | @@ -2031,6 +2031,64 @@ class PanelFFillDynamic(wx.Panel): | ||
2031 | self.config.dev_min = self.deviation_min.GetValue() | 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 | class FFillOptionsDialog(wx.Dialog): | 2092 | class FFillOptionsDialog(wx.Dialog): |
2035 | def __init__(self, title, config): | 2093 | def __init__(self, title, config): |
2036 | pre = wx.PreDialog() | 2094 | pre = wx.PreDialog() |
@@ -2273,13 +2331,14 @@ class FFillSegmentationOptionsDialog(wx.Dialog): | @@ -2273,13 +2331,14 @@ class FFillSegmentationOptionsDialog(wx.Dialog): | ||
2273 | else: | 2331 | else: |
2274 | self.panel3dcon.conect3D_6.SetValue(1) | 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 | if self.config.method == 'dynamic': | 2336 | if self.config.method == 'dynamic': |
2279 | self.cmb_method.SetSelection(0) | 2337 | self.cmb_method.SetSelection(0) |
2280 | - else: | 2338 | + elif self.config.method == 'threshold': |
2281 | self.cmb_method.SetSelection(1) | 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 | self.panel_ffill_threshold = PanelFFillThreshold(self, self.config, -1, style=border_style|wx.TAB_TRAVERSAL) | 2343 | self.panel_ffill_threshold = PanelFFillThreshold(self, self.config, -1, style=border_style|wx.TAB_TRAVERSAL) |
2285 | self.panel_ffill_threshold.SetMinSize((250, -1)) | 2344 | self.panel_ffill_threshold.SetMinSize((250, -1)) |
@@ -2289,6 +2348,10 @@ class FFillSegmentationOptionsDialog(wx.Dialog): | @@ -2289,6 +2348,10 @@ class FFillSegmentationOptionsDialog(wx.Dialog): | ||
2289 | self.panel_ffill_dynamic.SetMinSize((250, -1)) | 2348 | self.panel_ffill_dynamic.SetMinSize((250, -1)) |
2290 | self.panel_ffill_dynamic.Hide() | 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 | self.close_btn = wx.Button(self, wx.ID_CLOSE) | 2355 | self.close_btn = wx.Button(self, wx.ID_CLOSE) |
2293 | 2356 | ||
2294 | # Sizer | 2357 | # Sizer |
@@ -2313,6 +2376,10 @@ class FFillSegmentationOptionsDialog(wx.Dialog): | @@ -2313,6 +2376,10 @@ class FFillSegmentationOptionsDialog(wx.Dialog): | ||
2313 | self.cmb_method.SetSelection(0) | 2376 | self.cmb_method.SetSelection(0) |
2314 | self.panel_ffill_dynamic.Show() | 2377 | self.panel_ffill_dynamic.Show() |
2315 | sizer.Add(self.panel_ffill_dynamic, (11, 0), (1, 6), flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=7) | 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 | else: | 2383 | else: |
2317 | self.cmb_method.SetSelection(1) | 2384 | self.cmb_method.SetSelection(1) |
2318 | self.panel_ffill_threshold.Show() | 2385 | self.panel_ffill_threshold.Show() |
@@ -2358,16 +2425,25 @@ class FFillSegmentationOptionsDialog(wx.Dialog): | @@ -2358,16 +2425,25 @@ class FFillSegmentationOptionsDialog(wx.Dialog): | ||
2358 | self.config.con_3d = 26 | 2425 | self.config.con_3d = 26 |
2359 | 2426 | ||
2360 | def OnSetMethod(self, evt): | 2427 | def OnSetMethod(self, evt): |
2428 | + item_panel = self.GetSizer().FindItemAtPosition((11, 0)).GetWindow() | ||
2429 | + | ||
2361 | if self.cmb_method.GetSelection() == 0: | 2430 | if self.cmb_method.GetSelection() == 0: |
2362 | self.config.method = 'dynamic' | 2431 | self.config.method = 'dynamic' |
2363 | - self.panel_ffill_threshold.Hide() | 2432 | + item_panel.Hide() |
2364 | self.panel_ffill_dynamic.Show() | 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 | else: | 2442 | else: |
2367 | self.config.method = 'threshold' | 2443 | self.config.method = 'threshold' |
2368 | - self.panel_ffill_dynamic.Hide() | 2444 | + item_panel.Hide() |
2369 | self.panel_ffill_threshold.Show() | 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 | self.GetSizer().Fit(self) | 2448 | self.GetSizer().Fit(self) |
2373 | self.Layout() | 2449 | self.Layout() |