From e0d8a6393e482788eecd7be651de1043f139379c Mon Sep 17 00:00:00 2001 From: Thiago Franco de Moraes Date: Wed, 5 Oct 2016 13:50:59 -0300 Subject: [PATCH] Add Region growing confidence segmentation tool (#57) --- invesalius/data/styles.py | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------- invesalius/gui/dialogs.py | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 187 insertions(+), 52 deletions(-) diff --git a/invesalius/data/styles.py b/invesalius/data/styles.py index a92bd78..c82ba40 100644 --- a/invesalius/data/styles.py +++ b/invesalius/data/styles.py @@ -1983,6 +1983,9 @@ class FFillSegmentationConfig(object): self.use_ww_wl = True + self.confid_mult = 2.5 + self.confid_iters = 3 + class FloodFillSegmentInteractorStyle(DefaultInteractorStyle): def __init__(self, viewer): @@ -2052,36 +2055,47 @@ class FloodFillSegmentInteractorStyle(DefaultInteractorStyle): mask = self.viewer.slice_.buffer_slices[self.orientation].mask.copy() image = self.viewer.slice_.buffer_slices[self.orientation].image - if self.config.method == 'threshold': - v = image[y, x] - t0 = self.config.t0 - t1 = self.config.t1 + if self.config.method == 'confidence': + dy, dx = image.shape + image = image.reshape((1, dy, dx)) + mask = mask.reshape((1, dy, dx)) - elif self.config.method == 'dynamic': - if self.config.use_ww_wl: - print "Using WW&WL" - ww = self.viewer.slice_.window_width - wl = self.viewer.slice_.window_level - image = get_LUT_value_255(image, ww, wl) + bstruct = np.array(generate_binary_structure(2, CON2D[self.config.con_2d]), dtype='uint8') + bstruct = bstruct.reshape((1, 3, 3)) - v = image[y, x] + out_mask = self.do_rg_confidence(image, mask, (x, y, 0), bstruct) + else: + if self.config.method == 'threshold': + v = image[y, x] + t0 = self.config.t0 + t1 = self.config.t1 - t0 = v - self.config.dev_min - t1 = v + self.config.dev_max + elif self.config.method == 'dynamic': + if self.config.use_ww_wl: + print "Using WW&WL" + ww = self.viewer.slice_.window_width + wl = self.viewer.slice_.window_level + image = get_LUT_value_255(image, ww, wl) - if image[y, x] < t0 or image[y, x] > t1: - return + v = image[y, x] - dy, dx = image.shape - image = image.reshape((1, dy, dx)) - mask = mask.reshape((1, dy, dx)) + t0 = v - self.config.dev_min + t1 = v + self.config.dev_max - out_mask = np.zeros_like(mask) - bstruct = np.array(generate_binary_structure(2, CON2D[self.config.con_2d]), dtype='uint8') - bstruct = bstruct.reshape((1, 3, 3)) + if image[y, x] < t0 or image[y, x] > t1: + return + + dy, dx = image.shape + image = image.reshape((1, dy, dx)) + mask = mask.reshape((1, dy, dx)) + + out_mask = np.zeros_like(mask) + + bstruct = np.array(generate_binary_structure(2, CON2D[self.config.con_2d]), dtype='uint8') + bstruct = bstruct.reshape((1, 3, 3)) - floodfill.floodfill_threshold(image, [[x, y, 0]], t0, t1, 1, bstruct, out_mask) + floodfill.floodfill_threshold(image, [[x, y, 0]], t0, t1, 1, bstruct, out_mask) mask[out_mask.astype('bool')] = self.config.fill_value @@ -2107,46 +2121,91 @@ class FloodFillSegmentInteractorStyle(DefaultInteractorStyle): mask = self.viewer.slice_.current_mask.matrix[1:, 1:, 1:] image = self.viewer.slice_.matrix - if self.config.method == 'threshold': - v = image[z, y, x] - t0 = self.config.t0 - t1 = self.config.t1 + if self.config.method != 'confidence': + if self.config.method == 'threshold': + v = image[z, y, x] + t0 = self.config.t0 + t1 = self.config.t1 - elif self.config.method == 'dynamic': - if self.config.use_ww_wl: - print "Using WW&WL" - ww = self.viewer.slice_.window_width - wl = self.viewer.slice_.window_level - image = get_LUT_value_255(image, ww, wl) + elif self.config.method == 'dynamic': + if self.config.use_ww_wl: + print "Using WW&WL" + ww = self.viewer.slice_.window_width + wl = self.viewer.slice_.window_level + image = get_LUT_value_255(image, ww, wl) - v = image[z, y, x] + v = image[z, y, x] - t0 = v - self.config.dev_min - t1 = v + self.config.dev_max + t0 = v - self.config.dev_min + t1 = v + self.config.dev_max - if image[z, y, x] < t0 or image[z, y, x] > t1: - return + if image[z, y, x] < t0 or image[z, y, x] > t1: + return - out_mask = np.zeros_like(mask) bstruct = np.array(generate_binary_structure(3, CON3D[self.config.con_3d]), dtype='uint8') self.viewer.slice_.do_threshold_to_all_slices() cp_mask = self.viewer.slice_.current_mask.matrix.copy() - with futures.ThreadPoolExecutor(max_workers=1) as executor: - future = executor.submit(floodfill.floodfill_threshold, image, [[x, y, z]], t0, t1, 1, bstruct, out_mask) + if self.config.method == 'confidence': + with futures.ThreadPoolExecutor(max_workers=1) as executor: + future = executor.submit(self.do_rg_confidence, image, mask, (x, y, z), bstruct) - dlg = wx.ProgressDialog(self._progr_title, self._progr_msg, parent=None, style=wx.PD_APP_MODAL) - while not future.done(): - dlg.Pulse() - time.sleep(0.1) + dlg = wx.ProgressDialog(self._progr_title, self._progr_msg, parent=None, style=wx.PD_APP_MODAL) + while not future.done(): + dlg.Pulse() + time.sleep(0.1) + dlg.Destroy() + out_mask = future.result() + else: + out_mask = np.zeros_like(mask) + with futures.ThreadPoolExecutor(max_workers=1) as executor: + future = executor.submit(floodfill.floodfill_threshold, image, [[x, y, z]], t0, t1, 1, bstruct, out_mask) - dlg.Destroy() + dlg = wx.ProgressDialog(self._progr_title, self._progr_msg, parent=None, style=wx.PD_APP_MODAL) + while not future.done(): + dlg.Pulse() + time.sleep(0.1) + + dlg.Destroy() mask[out_mask.astype('bool')] = self.config.fill_value self.viewer.slice_.current_mask.save_history(0, 'VOLUME', self.viewer.slice_.current_mask.matrix.copy(), cp_mask) + def do_rg_confidence(self, image, mask, p, bstruct): + x, y, z = p + if self.config.use_ww_wl: + ww = self.viewer.slice_.window_width + wl = self.viewer.slice_.window_level + image = get_LUT_value_255(image, ww, wl) + bool_mask = np.zeros_like(mask, dtype='bool') + out_mask = np.zeros_like(mask) + + for k in xrange(int(z-1), int(z+2)): + if k < 0 or k >= bool_mask.shape[0]: + continue + for j in xrange(int(y-1), int(y+2)): + if j < 0 or j >= bool_mask.shape[1]: + continue + for i in xrange(int(x-1), int(x+2)): + if i < 0 or i >= bool_mask.shape[2]: + continue + bool_mask[k, j, i] = True + + for i in xrange(self.config.confid_iters): + var = np.std(image[bool_mask]) + mean = np.mean(image[bool_mask]) + + t0 = mean - var * self.config.confid_mult + t1 = mean + var * self.config.confid_mult + + floodfill.floodfill_threshold(image, [[x, y, z]], t0, t1, 1, bstruct, out_mask) + + bool_mask[out_mask == 1] = True + + return out_mask + def get_style(style): STYLES = { const.STATE_DEFAULT: DefaultInteractorStyle, diff --git a/invesalius/gui/dialogs.py b/invesalius/gui/dialogs.py index bc4db77..d000e05 100644 --- a/invesalius/gui/dialogs.py +++ b/invesalius/gui/dialogs.py @@ -2031,6 +2031,64 @@ class PanelFFillDynamic(wx.Panel): self.config.dev_min = self.deviation_min.GetValue() +class PanelFFillConfidence(wx.Panel): + def __init__(self, parent, config, ID=-1, style=wx.TAB_TRAVERSAL|wx.NO_BORDER): + wx.Panel.__init__(self, parent, ID, style=style) + + self.config = config + + self._init_gui() + + def _init_gui(self): + self.use_ww_wl = wx.CheckBox(self, -1, _(u"Use WW&WL")) + self.use_ww_wl.SetValue(self.config.use_ww_wl) + + self.spin_mult = floatspin.FloatSpin(self, -1, + value=self.config.confid_mult, + min_val=1.0, max_val=10.0, + increment=0.1, digits=1, + style=wx.TE_PROCESS_TAB|wx.TE_PROCESS_ENTER, + agwStyle=floatspin.FS_RIGHT) + w, h = self.spin_mult.GetTextExtent('M') + self.spin_mult.SetMinSize((w*7, -1)) + + self.spin_iters = wx.SpinCtrl(self, -1, value='%d' % self.config.confid_iters, min=0, max=100) + self.spin_iters.SetMinSize((w*7, -1)) + + sizer = wx.GridBagSizer(5, 5) + + sizer.AddStretchSpacer((0, 0)) + + sizer.Add(self.use_ww_wl, (1, 0), (1, 6), flag=wx.LEFT, border=5) + + sizer.AddStretchSpacer((2, 0)) + + sizer.Add(wx.StaticText(self, -1, _(u"Multiplier")), (3, 0), (1, 3), flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=5) + sizer.Add(self.spin_mult, (3, 3), (1, 2)) + + sizer.Add(wx.StaticText(self, -1, _(u"Iterations")), (4, 0), (1, 3), flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=5) + sizer.Add(self.spin_iters, (4, 3), (1, 2)) + + sizer.AddStretchSpacer((5, 0)) + + self.SetSizer(sizer) + sizer.Fit(self) + self.Layout() + + self.use_ww_wl.Bind(wx.EVT_CHECKBOX, self.OnSetUseWWWL) + self.spin_mult.Bind(wx.EVT_SPINCTRL, self.OnSetMult) + self.spin_iters.Bind(wx.EVT_SPINCTRL, self.OnSetIters) + + def OnSetUseWWWL(self, evt): + self.config.use_ww_wl = self.use_ww_wl.GetValue() + + def OnSetMult(self, evt): + self.config.confid_mult = self.spin_mult.GetValue() + + def OnSetIters(self, evt): + self.config.confid_iters = self.spin_iters.GetValue() + + class FFillOptionsDialog(wx.Dialog): def __init__(self, title, config): pre = wx.PreDialog() @@ -2273,13 +2331,14 @@ class FFillSegmentationOptionsDialog(wx.Dialog): else: self.panel3dcon.conect3D_6.SetValue(1) - self.cmb_method = wx.ComboBox(self, -1, choices=(_(u"Dynamic"), _(u"Threshold")), style=wx.CB_READONLY) + self.cmb_method = wx.ComboBox(self, -1, choices=(_(u"Dynamic"), _(u"Threshold"), _(u"Confidence")), style=wx.CB_READONLY) if self.config.method == 'dynamic': self.cmb_method.SetSelection(0) - else: + elif self.config.method == 'threshold': self.cmb_method.SetSelection(1) - self.config.method = 'threshold' + elif self.config.method == 'confidence': + self.cmb_method.SetSelection(2) self.panel_ffill_threshold = PanelFFillThreshold(self, self.config, -1, style=border_style|wx.TAB_TRAVERSAL) self.panel_ffill_threshold.SetMinSize((250, -1)) @@ -2289,6 +2348,10 @@ class FFillSegmentationOptionsDialog(wx.Dialog): self.panel_ffill_dynamic.SetMinSize((250, -1)) self.panel_ffill_dynamic.Hide() + self.panel_ffill_confidence = PanelFFillConfidence(self, self.config, -1, style=border_style|wx.TAB_TRAVERSAL) + self.panel_ffill_confidence.SetMinSize((250, -1)) + self.panel_ffill_confidence.Hide() + self.close_btn = wx.Button(self, wx.ID_CLOSE) # Sizer @@ -2313,6 +2376,10 @@ class FFillSegmentationOptionsDialog(wx.Dialog): self.cmb_method.SetSelection(0) self.panel_ffill_dynamic.Show() sizer.Add(self.panel_ffill_dynamic, (11, 0), (1, 6), flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=7) + elif self.config.method == 'confidence': + self.cmb_method.SetSelection(2) + self.panel_ffill_confidence.Show() + sizer.Add(self.panel_ffill_confidence, (11, 0), (1, 6), flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=7) else: self.cmb_method.SetSelection(1) self.panel_ffill_threshold.Show() @@ -2358,16 +2425,25 @@ class FFillSegmentationOptionsDialog(wx.Dialog): self.config.con_3d = 26 def OnSetMethod(self, evt): + item_panel = self.GetSizer().FindItemAtPosition((11, 0)).GetWindow() + if self.cmb_method.GetSelection() == 0: self.config.method = 'dynamic' - self.panel_ffill_threshold.Hide() + item_panel.Hide() self.panel_ffill_dynamic.Show() - self.GetSizer().Replace(self.panel_ffill_threshold, self.panel_ffill_dynamic) + self.GetSizer().Replace(item_panel, self.panel_ffill_dynamic) + + elif self.cmb_method.GetSelection() == 2: + self.config.method = 'confidence' + item_panel.Hide() + self.panel_ffill_confidence.Show() + self.GetSizer().Replace(item_panel, self.panel_ffill_confidence) + else: self.config.method = 'threshold' - self.panel_ffill_dynamic.Hide() + item_panel.Hide() self.panel_ffill_threshold.Show() - self.GetSizer().Replace(self.panel_ffill_dynamic, self.panel_ffill_threshold) + self.GetSizer().Replace(item_panel, self.panel_ffill_threshold) self.GetSizer().Fit(self) self.Layout() -- libgit2 0.21.2