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() | ... | ... |