Commit 16a1bfd7153c0613dfee307397d5a902edf0da65
Committed by
GitHub
1 parent
a25e8aeb
Exists in
master
and in
18 other branches
Floodfill segmentation (2D and 3D)
Adds floodfill segmentation to InVesalius. Its possible to segment using an given threshold or a dynamic threshold. The dynamic threshold is based on the value pointed by the user and a deviation (given by the user). Also, it's possible to apply an WW&Wl before the segmentation.
Showing
4 changed files
with
398 additions
and
3 deletions
Show diff stats
invesalius/constants.py
@@ -484,6 +484,7 @@ ID_REORIENT_IMG = wx.NewId() | @@ -484,6 +484,7 @@ ID_REORIENT_IMG = wx.NewId() | ||
484 | ID_FLOODFILL_MASK = wx.NewId() | 484 | ID_FLOODFILL_MASK = wx.NewId() |
485 | ID_REMOVE_MASK_PART = wx.NewId() | 485 | ID_REMOVE_MASK_PART = wx.NewId() |
486 | ID_SELECT_MASK_PART = wx.NewId() | 486 | ID_SELECT_MASK_PART = wx.NewId() |
487 | +ID_FLOODFILL_SEGMENTATION = wx.NewId() | ||
487 | 488 | ||
488 | #--------------------------------------------------------- | 489 | #--------------------------------------------------------- |
489 | STATE_DEFAULT = 1000 | 490 | STATE_DEFAULT = 1000 |
@@ -504,6 +505,7 @@ SLICE_STATE_REORIENT = 3010 | @@ -504,6 +505,7 @@ SLICE_STATE_REORIENT = 3010 | ||
504 | SLICE_STATE_MASK_FFILL = 3011 | 505 | SLICE_STATE_MASK_FFILL = 3011 |
505 | SLICE_STATE_REMOVE_MASK_PARTS = 3012 | 506 | SLICE_STATE_REMOVE_MASK_PARTS = 3012 |
506 | SLICE_STATE_SELECT_MASK_PARTS = 3013 | 507 | SLICE_STATE_SELECT_MASK_PARTS = 3013 |
508 | +SLICE_STATE_FFILL_SEGMENTATION = 3014 | ||
507 | 509 | ||
508 | VOLUME_STATE_SEED = 2001 | 510 | VOLUME_STATE_SEED = 2001 |
509 | # STATE_LINEAR_MEASURE = 3001 | 511 | # STATE_LINEAR_MEASURE = 3001 |
@@ -524,6 +526,7 @@ SLICE_STYLES.append(SLICE_STATE_WATERSHED) | @@ -524,6 +526,7 @@ SLICE_STYLES.append(SLICE_STATE_WATERSHED) | ||
524 | SLICE_STYLES.append(SLICE_STATE_MASK_FFILL) | 526 | SLICE_STYLES.append(SLICE_STATE_MASK_FFILL) |
525 | SLICE_STYLES.append(SLICE_STATE_REMOVE_MASK_PARTS) | 527 | SLICE_STYLES.append(SLICE_STATE_REMOVE_MASK_PARTS) |
526 | SLICE_STYLES.append(SLICE_STATE_SELECT_MASK_PARTS) | 528 | SLICE_STYLES.append(SLICE_STATE_SELECT_MASK_PARTS) |
529 | +SLICE_STYLES.append(SLICE_STATE_FFILL_SEGMENTATION) | ||
527 | 530 | ||
528 | VOLUME_STYLES = TOOL_STATES + [VOLUME_STATE_SEED, STATE_MEASURE_DISTANCE, | 531 | VOLUME_STYLES = TOOL_STATES + [VOLUME_STATE_SEED, STATE_MEASURE_DISTANCE, |
529 | STATE_MEASURE_ANGLE] | 532 | STATE_MEASURE_ANGLE] |
@@ -535,6 +538,7 @@ STYLE_LEVEL = {SLICE_STATE_EDITOR: 1, | @@ -535,6 +538,7 @@ STYLE_LEVEL = {SLICE_STATE_EDITOR: 1, | ||
535 | SLICE_STATE_MASK_FFILL: 2, | 538 | SLICE_STATE_MASK_FFILL: 2, |
536 | SLICE_STATE_REMOVE_MASK_PARTS: 2, | 539 | SLICE_STATE_REMOVE_MASK_PARTS: 2, |
537 | SLICE_STATE_SELECT_MASK_PARTS: 2, | 540 | SLICE_STATE_SELECT_MASK_PARTS: 2, |
541 | + SLICE_STATE_FFILL_SEGMENTATION: 2, | ||
538 | SLICE_STATE_CROSS: 2, | 542 | SLICE_STATE_CROSS: 2, |
539 | SLICE_STATE_SCROLL: 2, | 543 | SLICE_STATE_SCROLL: 2, |
540 | SLICE_STATE_REORIENT: 2, | 544 | SLICE_STATE_REORIENT: 2, |
invesalius/data/styles.py
@@ -76,6 +76,16 @@ def get_LUT_value(data, window, level): | @@ -76,6 +76,16 @@ def get_LUT_value(data, window, level): | ||
76 | data.shape = shape | 76 | data.shape = shape |
77 | return data | 77 | return data |
78 | 78 | ||
79 | +def get_LUT_value_255(data, window, level): | ||
80 | + shape = data.shape | ||
81 | + data_ = data.ravel() | ||
82 | + data = np.piecewise(data_, | ||
83 | + [data_ <= (level - 0.5 - (window-1)/2), | ||
84 | + data_ > (level - 0.5 + (window-1)/2)], | ||
85 | + [0, 255, lambda data_: ((data_ - (level - 0.5))/(window-1) + 0.5)*(255)]) | ||
86 | + data.shape = shape | ||
87 | + return data | ||
88 | + | ||
79 | 89 | ||
80 | class BaseImageInteractorStyle(vtk.vtkInteractorStyleImage): | 90 | class BaseImageInteractorStyle(vtk.vtkInteractorStyleImage): |
81 | def __init__(self, viewer): | 91 | def __init__(self, viewer): |
@@ -1841,6 +1851,200 @@ class SelectMaskPartsInteractorStyle(DefaultInteractorStyle): | @@ -1841,6 +1851,200 @@ class SelectMaskPartsInteractorStyle(DefaultInteractorStyle): | ||
1841 | self.config.mask = mask | 1851 | self.config.mask = mask |
1842 | 1852 | ||
1843 | 1853 | ||
1854 | +class FFillSegmentationConfig(object): | ||
1855 | + __metaclass__= utils.Singleton | ||
1856 | + def __init__(self): | ||
1857 | + self.dlg_visible = False | ||
1858 | + self.target = "2D" | ||
1859 | + self.con_2d = 4 | ||
1860 | + self.con_3d = 6 | ||
1861 | + | ||
1862 | + self.t0 = None | ||
1863 | + self.t1 = None | ||
1864 | + | ||
1865 | + self.fill_value = 254 | ||
1866 | + | ||
1867 | + self.method = 'threshold' | ||
1868 | + | ||
1869 | + self.dev_min = 25 | ||
1870 | + self.dev_max = 25 | ||
1871 | + | ||
1872 | + self.use_ww_wl = True | ||
1873 | + | ||
1874 | + | ||
1875 | +class FloodFillSegmentInteractorStyle(DefaultInteractorStyle): | ||
1876 | + def __init__(self, viewer): | ||
1877 | + DefaultInteractorStyle.__init__(self, viewer) | ||
1878 | + | ||
1879 | + self.viewer = viewer | ||
1880 | + self.orientation = self.viewer.orientation | ||
1881 | + | ||
1882 | + self.picker = vtk.vtkWorldPointPicker() | ||
1883 | + self.slice_actor = viewer.slice_data.actor | ||
1884 | + self.slice_data = viewer.slice_data | ||
1885 | + | ||
1886 | + self.config = FFillSegmentationConfig() | ||
1887 | + self.dlg_ffill = None | ||
1888 | + | ||
1889 | + self._progr_title = _(u"Floodfill segmentation") | ||
1890 | + self._progr_msg = _(u"Segmenting ...") | ||
1891 | + | ||
1892 | + self.AddObserver("LeftButtonPressEvent", self.OnFFClick) | ||
1893 | + | ||
1894 | + def SetUp(self): | ||
1895 | + if not self.config.dlg_visible: | ||
1896 | + | ||
1897 | + if self.config.t0 is None: | ||
1898 | + image = self.viewer.slice_.matrix | ||
1899 | + _min, _max = image.min(), image.max() | ||
1900 | + | ||
1901 | + self.config.t0 = int(_min + (3.0/4.0) * (_max - _min)) | ||
1902 | + self.config.t1 = int(_max) | ||
1903 | + | ||
1904 | + self.config.dlg_visible = True | ||
1905 | + self.dlg_ffill = dialogs.FFillSegmentationOptionsDialog(self.config) | ||
1906 | + self.dlg_ffill.Show() | ||
1907 | + | ||
1908 | + def CleanUp(self): | ||
1909 | + if (self.dlg_ffill is not None) and (self.config.dlg_visible): | ||
1910 | + self.config.dlg_visible = False | ||
1911 | + self.dlg_ffill.Destroy() | ||
1912 | + self.dlg_ffill = None | ||
1913 | + | ||
1914 | + def OnFFClick(self, obj, evt): | ||
1915 | + if (self.viewer.slice_.buffer_slices[self.orientation].mask is None): | ||
1916 | + return | ||
1917 | + | ||
1918 | + if self.config.target == "3D": | ||
1919 | + self.do_3d_seg() | ||
1920 | + with futures.ThreadPoolExecutor(max_workers=1) as executor: | ||
1921 | + future = executor.submit(self.do_3d_seg) | ||
1922 | + | ||
1923 | + dlg = wx.ProgressDialog(self._progr_title, self._progr_msg, parent=None, style=wx.PD_APP_MODAL) | ||
1924 | + while not future.done(): | ||
1925 | + dlg.Pulse() | ||
1926 | + time.sleep(0.1) | ||
1927 | + | ||
1928 | + dlg.Destroy() | ||
1929 | + | ||
1930 | + else: | ||
1931 | + self.do_2d_seg() | ||
1932 | + | ||
1933 | + self.viewer.slice_.buffer_slices['AXIAL'].discard_mask() | ||
1934 | + self.viewer.slice_.buffer_slices['CORONAL'].discard_mask() | ||
1935 | + self.viewer.slice_.buffer_slices['SAGITAL'].discard_mask() | ||
1936 | + | ||
1937 | + self.viewer.slice_.buffer_slices['AXIAL'].discard_vtk_mask() | ||
1938 | + self.viewer.slice_.buffer_slices['CORONAL'].discard_vtk_mask() | ||
1939 | + self.viewer.slice_.buffer_slices['SAGITAL'].discard_vtk_mask() | ||
1940 | + | ||
1941 | + self.viewer.slice_.current_mask.was_edited = True | ||
1942 | + Publisher.sendMessage('Reload actual slice') | ||
1943 | + | ||
1944 | + def do_2d_seg(self): | ||
1945 | + viewer = self.viewer | ||
1946 | + iren = viewer.interactor | ||
1947 | + mouse_x, mouse_y = iren.GetEventPosition() | ||
1948 | + x, y = self.viewer.get_slice_pixel_coord_by_screen_pos(mouse_x, mouse_y, self.picker) | ||
1949 | + | ||
1950 | + mask = self.viewer.slice_.buffer_slices[self.orientation].mask.copy() | ||
1951 | + image = self.viewer.slice_.buffer_slices[self.orientation].image | ||
1952 | + | ||
1953 | + if self.config.method == 'threshold': | ||
1954 | + v = image[y, x] | ||
1955 | + t0 = self.config.t0 | ||
1956 | + t1 = self.config.t1 | ||
1957 | + | ||
1958 | + elif self.config.method == 'dynamic': | ||
1959 | + if self.config.use_ww_wl: | ||
1960 | + print "Using WW&WL" | ||
1961 | + ww = self.viewer.slice_.window_width | ||
1962 | + wl = self.viewer.slice_.window_level | ||
1963 | + image = get_LUT_value_255(image, ww, wl) | ||
1964 | + | ||
1965 | + v = image[y, x] | ||
1966 | + | ||
1967 | + t0 = v - self.config.dev_min | ||
1968 | + t1 = v + self.config.dev_max | ||
1969 | + | ||
1970 | + if image[y, x] < t0 or image[y, x] > t1: | ||
1971 | + return | ||
1972 | + | ||
1973 | + dy, dx = image.shape | ||
1974 | + image = image.reshape((1, dy, dx)) | ||
1975 | + mask = mask.reshape((1, dy, dx)) | ||
1976 | + | ||
1977 | + out_mask = np.zeros_like(mask) | ||
1978 | + | ||
1979 | + bstruct = np.array(generate_binary_structure(2, CON2D[self.config.con_2d]), dtype='uint8') | ||
1980 | + bstruct = bstruct.reshape((1, 3, 3)) | ||
1981 | + | ||
1982 | + floodfill.floodfill_threshold(image, [[x, y, 0]], t0, t1, 1, bstruct, out_mask) | ||
1983 | + | ||
1984 | + mask[out_mask.astype('bool')] = self.config.fill_value | ||
1985 | + | ||
1986 | + index = self.viewer.slice_.buffer_slices[self.orientation].index | ||
1987 | + b_mask = self.viewer.slice_.buffer_slices[self.orientation].mask | ||
1988 | + vol_mask = self.viewer.slice_.current_mask.matrix[1:, 1:, 1:] | ||
1989 | + | ||
1990 | + if self.orientation == 'AXIAL': | ||
1991 | + vol_mask[index, :, :] = mask | ||
1992 | + elif self.orientation == 'CORONAL': | ||
1993 | + vol_mask[:, index, :] = mask | ||
1994 | + elif self.orientation == 'SAGITAL': | ||
1995 | + vol_mask[:, :, index] = mask | ||
1996 | + | ||
1997 | + self.viewer.slice_.current_mask.save_history(index, self.orientation, mask, b_mask) | ||
1998 | + | ||
1999 | + def do_3d_seg(self): | ||
2000 | + viewer = self.viewer | ||
2001 | + iren = viewer.interactor | ||
2002 | + mouse_x, mouse_y = iren.GetEventPosition() | ||
2003 | + x, y, z = self.viewer.get_voxel_coord_by_screen_pos(mouse_x, mouse_y, self.picker) | ||
2004 | + | ||
2005 | + mask = self.viewer.slice_.current_mask.matrix[1:, 1:, 1:] | ||
2006 | + image = self.viewer.slice_.matrix | ||
2007 | + | ||
2008 | + if self.config.method == 'threshold': | ||
2009 | + v = image[z, y, x] | ||
2010 | + t0 = self.config.t0 | ||
2011 | + t1 = self.config.t1 | ||
2012 | + | ||
2013 | + elif self.config.method == 'dynamic': | ||
2014 | + if self.config.use_ww_wl: | ||
2015 | + print "Using WW&WL" | ||
2016 | + ww = self.viewer.slice_.window_width | ||
2017 | + wl = self.viewer.slice_.window_level | ||
2018 | + image = get_LUT_value_255(image, ww, wl) | ||
2019 | + | ||
2020 | + v = image[z, y, x] | ||
2021 | + | ||
2022 | + t0 = v - self.config.dev_min | ||
2023 | + t1 = v + self.config.dev_max | ||
2024 | + | ||
2025 | + if image[z, y, x] < t0 or image[z, y, x] > t1: | ||
2026 | + return | ||
2027 | + | ||
2028 | + out_mask = np.zeros_like(mask) | ||
2029 | + | ||
2030 | + bstruct = np.array(generate_binary_structure(3, CON3D[self.config.con_3d]), dtype='uint8') | ||
2031 | + self.viewer.slice_.do_threshold_to_all_slices() | ||
2032 | + cp_mask = self.viewer.slice_.current_mask.matrix.copy() | ||
2033 | + | ||
2034 | + with futures.ThreadPoolExecutor(max_workers=1) as executor: | ||
2035 | + future = executor.submit(floodfill.floodfill_threshold, image, [[x, y, z]], t0, t1, 1, bstruct, out_mask) | ||
2036 | + | ||
2037 | + dlg = wx.ProgressDialog(self._progr_title, self._progr_msg, parent=None, style=wx.PD_APP_MODAL) | ||
2038 | + while not future.done(): | ||
2039 | + dlg.Pulse() | ||
2040 | + time.sleep(0.1) | ||
2041 | + | ||
2042 | + dlg.Destroy() | ||
2043 | + | ||
2044 | + mask[out_mask.astype('bool')] = self.config.fill_value | ||
2045 | + | ||
2046 | + self.viewer.slice_.current_mask.save_history(0, 'VOLUME', self.viewer.slice_.current_mask.matrix.copy(), cp_mask) | ||
2047 | + | ||
1844 | def get_style(style): | 2048 | def get_style(style): |
1845 | STYLES = { | 2049 | STYLES = { |
1846 | const.STATE_DEFAULT: DefaultInteractorStyle, | 2050 | const.STATE_DEFAULT: DefaultInteractorStyle, |
@@ -1859,6 +2063,7 @@ def get_style(style): | @@ -1859,6 +2063,7 @@ def get_style(style): | ||
1859 | const.SLICE_STATE_MASK_FFILL: FloodFillMaskInteractorStyle, | 2063 | const.SLICE_STATE_MASK_FFILL: FloodFillMaskInteractorStyle, |
1860 | const.SLICE_STATE_REMOVE_MASK_PARTS: RemoveMaskPartsInteractorStyle, | 2064 | const.SLICE_STATE_REMOVE_MASK_PARTS: RemoveMaskPartsInteractorStyle, |
1861 | const.SLICE_STATE_SELECT_MASK_PARTS: SelectMaskPartsInteractorStyle, | 2065 | const.SLICE_STATE_SELECT_MASK_PARTS: SelectMaskPartsInteractorStyle, |
2066 | + const.SLICE_STATE_FFILL_SEGMENTATION: FloodFillSegmentInteractorStyle, | ||
1862 | } | 2067 | } |
1863 | return STYLES[style] | 2068 | return STYLES[style] |
1864 | 2069 |
invesalius/gui/dialogs.py
@@ -2031,3 +2031,177 @@ class SelectPartsOptionsDialog(wx.Dialog): | @@ -2031,3 +2031,177 @@ class SelectPartsOptionsDialog(wx.Dialog): | ||
2031 | Publisher.sendMessage('Disable style', const.SLICE_STATE_SELECT_MASK_PARTS) | 2031 | Publisher.sendMessage('Disable style', const.SLICE_STATE_SELECT_MASK_PARTS) |
2032 | evt.Skip() | 2032 | evt.Skip() |
2033 | self.Destroy() | 2033 | self.Destroy() |
2034 | + | ||
2035 | + | ||
2036 | +class FFillSegmentationOptionsDialog(wx.Dialog): | ||
2037 | + def __init__(self, config): | ||
2038 | + pre = wx.PreDialog() | ||
2039 | + pre.Create(wx.GetApp().GetTopWindow(), -1, _(u"Floodfill Segmentation"), style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT) | ||
2040 | + self.PostCreate(pre) | ||
2041 | + | ||
2042 | + self.config = config | ||
2043 | + | ||
2044 | + self._init_gui() | ||
2045 | + | ||
2046 | + def _init_gui(self): | ||
2047 | + """ | ||
2048 | + Create the widgets. | ||
2049 | + """ | ||
2050 | + import project as prj | ||
2051 | + # Target | ||
2052 | + self.target_2d = wx.RadioButton(self, -1, _(u"2D - Actual slice"), style=wx.RB_GROUP) | ||
2053 | + self.target_3d = wx.RadioButton(self, -1, _(u"3D - All slices")) | ||
2054 | + | ||
2055 | + if self.config.target == "2D": | ||
2056 | + self.target_2d.SetValue(1) | ||
2057 | + else: | ||
2058 | + self.target_3d.SetValue(1) | ||
2059 | + | ||
2060 | + # Connectivity 2D | ||
2061 | + self.conect2D_4 = wx.RadioButton(self, -1, "4", style=wx.RB_GROUP) | ||
2062 | + self.conect2D_8 = wx.RadioButton(self, -1, "8") | ||
2063 | + | ||
2064 | + if self.config.con_2d == 8: | ||
2065 | + self.conect2D_8.SetValue(1) | ||
2066 | + else: | ||
2067 | + self.conect2D_4.SetValue(1) | ||
2068 | + self.config.con_2d = 4 | ||
2069 | + | ||
2070 | + # Connectivity 3D | ||
2071 | + self.conect3D_6 = wx.RadioButton(self, -1, "6", style=wx.RB_GROUP) | ||
2072 | + self.conect3D_18 = wx.RadioButton(self, -1, "18") | ||
2073 | + self.conect3D_26 = wx.RadioButton(self, -1, "26") | ||
2074 | + | ||
2075 | + if self.config.con_3d == 18: | ||
2076 | + self.conect3D_18.SetValue(1) | ||
2077 | + elif self.config.con_3d == 26: | ||
2078 | + self.conect3D_26.SetValue(1) | ||
2079 | + else: | ||
2080 | + self.conect3D_6.SetValue(1) | ||
2081 | + | ||
2082 | + project = prj.Project() | ||
2083 | + bound_min, bound_max = project.threshold_range | ||
2084 | + colour = [i*255 for i in const.MASK_COLOUR[0]] | ||
2085 | + colour.append(100) | ||
2086 | + self.threshold = grad.GradientCtrl(self, -1, int(bound_min), | ||
2087 | + int(bound_max), self.config.t0, | ||
2088 | + self.config.t1, colour) | ||
2089 | + self.threshold.SetMinSize((250, -1)) | ||
2090 | + | ||
2091 | + self.method_threshold = wx.RadioButton(self, -1, _(u"Threshold"), style=wx.RB_GROUP) | ||
2092 | + self.method_dynamic = wx.RadioButton(self, -1, _(u"Dynamic")) | ||
2093 | + | ||
2094 | + if self.config.method == 'dynamic': | ||
2095 | + self.method_dynamic.SetValue(1) | ||
2096 | + else: | ||
2097 | + self.method_threshold.SetValue(1) | ||
2098 | + self.config.method = 'threshold' | ||
2099 | + | ||
2100 | + self.use_ww_wl = wx.CheckBox(self, -1, _(u"Use WW\&WL")) | ||
2101 | + self.use_ww_wl.SetValue(self.config.use_ww_wl) | ||
2102 | + | ||
2103 | + self.deviation_min = wx.SpinCtrl(self, -1, value='%d' % self.config.dev_min, min=0, max=10000) | ||
2104 | + self.deviation_max = wx.SpinCtrl(self, -1, value='%d' % self.config.dev_max, min=0, max=10000) | ||
2105 | + | ||
2106 | + # Sizer | ||
2107 | + sizer = wx.BoxSizer(wx.VERTICAL) | ||
2108 | + | ||
2109 | + sizer.AddSpacer(7) | ||
2110 | + | ||
2111 | + sizer.Add(wx.StaticText(self, -1, _(u"Parameters")), flag=wx.LEFT, border=7) | ||
2112 | + sizer.AddSpacer(5) | ||
2113 | + sizer.Add(self.target_2d, flag=wx.LEFT, border=9) | ||
2114 | + sizer.Add(self.target_3d, flag=wx.LEFT, border=9) | ||
2115 | + | ||
2116 | + sizer.AddSpacer(7) | ||
2117 | + | ||
2118 | + sizer.Add(wx.StaticText(self, -1, _(u"2D Connectivity")), flag=wx.LEFT, border=9) | ||
2119 | + sizer.AddSpacer(5) | ||
2120 | + sizer_2d = wx.BoxSizer(wx.HORIZONTAL) | ||
2121 | + sizer_2d.Add(self.conect2D_4, flag=wx.LEFT, border=11) | ||
2122 | + sizer_2d.Add(self.conect2D_8, flag=wx.LEFT, border=11) | ||
2123 | + sizer.Add(sizer_2d) | ||
2124 | + | ||
2125 | + sizer.AddSpacer(7) | ||
2126 | + | ||
2127 | + sizer.Add(wx.StaticText(self, -1, _(u"3D Connectivity")), flag=wx.LEFT, border=9) | ||
2128 | + sizer.AddSpacer(5) | ||
2129 | + sizer_3d = wx.BoxSizer(wx.HORIZONTAL) | ||
2130 | + sizer_3d.Add(self.conect3D_6, flag=wx.LEFT, border=11) | ||
2131 | + sizer_3d.Add(self.conect3D_18, flag=wx.LEFT, border=11) | ||
2132 | + sizer_3d.Add(self.conect3D_26, flag=wx.LEFT, border=11) | ||
2133 | + sizer.Add(sizer_3d) | ||
2134 | + | ||
2135 | + sizer.AddSpacer(7) | ||
2136 | + | ||
2137 | + sizer.Add(wx.StaticText(self, -1, _(u"Method")), flag=wx.LEFT, border=9) | ||
2138 | + sizer.AddSpacer(5) | ||
2139 | + sizer.Add(self.method_threshold, flag=wx.LEFT, border=11) | ||
2140 | + sizer.AddSpacer(5) | ||
2141 | + sizer.Add(self.threshold, flag=wx.LEFT|wx.RIGHT|wx.EXPAND, border=13) | ||
2142 | + sizer.AddSpacer(5) | ||
2143 | + sizer.Add(self.method_dynamic, flag=wx.LEFT, border=11) | ||
2144 | + sizer.AddSpacer(5) | ||
2145 | + sizer.Add(self.use_ww_wl, flag=wx.LEFT, border=13) | ||
2146 | + sizer.AddSpacer(5) | ||
2147 | + sizer.Add(self.deviation_min, flag=wx.LEFT, border=13) | ||
2148 | + sizer.Add(self.deviation_max, flag=wx.LEFT, border=13) | ||
2149 | + | ||
2150 | + sizer.AddSpacer(7) | ||
2151 | + | ||
2152 | + self.SetSizer(sizer) | ||
2153 | + sizer.Fit(self) | ||
2154 | + self.Layout() | ||
2155 | + | ||
2156 | + self.Bind(wx.EVT_RADIOBUTTON, self.OnSetRadio) | ||
2157 | + self.Bind(grad.EVT_THRESHOLD_CHANGING, self.OnSlideChanged, self.threshold) | ||
2158 | + self.Bind(grad.EVT_THRESHOLD_CHANGED, self.OnSlideChanged, self.threshold) | ||
2159 | + self.use_ww_wl.Bind(wx.EVT_CHECKBOX, self.OnSetUseWWWL) | ||
2160 | + self.deviation_min.Bind(wx.EVT_SPINCTRL, self.OnSetDeviation) | ||
2161 | + self.deviation_max.Bind(wx.EVT_SPINCTRL, self.OnSetDeviation) | ||
2162 | + self.Bind(wx.EVT_CLOSE, self.OnClose) | ||
2163 | + | ||
2164 | + def OnSetRadio(self, evt): | ||
2165 | + # Target | ||
2166 | + if self.target_2d.GetValue(): | ||
2167 | + self.config.target = "2D" | ||
2168 | + else: | ||
2169 | + self.config.target = "3D" | ||
2170 | + | ||
2171 | + # 2D | ||
2172 | + if self.conect2D_4.GetValue(): | ||
2173 | + self.config.con_2d = 4 | ||
2174 | + elif self.conect2D_8.GetValue(): | ||
2175 | + self.config.con_2d = 8 | ||
2176 | + | ||
2177 | + # 3D | ||
2178 | + if self.conect3D_6.GetValue(): | ||
2179 | + self.config.con_3d = 6 | ||
2180 | + elif self.conect3D_18.GetValue(): | ||
2181 | + self.config.con_3d = 18 | ||
2182 | + elif self.conect3D_26.GetValue(): | ||
2183 | + self.config.con_3d = 26 | ||
2184 | + | ||
2185 | + # Method | ||
2186 | + if self.method_threshold.GetValue(): | ||
2187 | + self.config.method = 'threshold' | ||
2188 | + else: | ||
2189 | + self.config.method = 'dynamic' | ||
2190 | + | ||
2191 | + def OnSlideChanged(self, evt): | ||
2192 | + self.config.t0 = int(self.threshold.GetMinValue()) | ||
2193 | + self.config.t1 = int(self.threshold.GetMaxValue()) | ||
2194 | + print self.config.t0, self.config.t1 | ||
2195 | + | ||
2196 | + def OnSetUseWWWL(self, evt): | ||
2197 | + self.config.use_ww_wl = self.use_ww_wl.GetValue() | ||
2198 | + | ||
2199 | + def OnSetDeviation(self, evt): | ||
2200 | + self.config.dev_max = self.deviation_max.GetValue() | ||
2201 | + self.config.dev_min = self.deviation_min.GetValue() | ||
2202 | + | ||
2203 | + def OnClose(self, evt): | ||
2204 | + if self.config.dlg_visible: | ||
2205 | + Publisher.sendMessage('Disable style', const.SLICE_STATE_MASK_FFILL) | ||
2206 | + evt.Skip() | ||
2207 | + self.Destroy() |
invesalius/gui/frame.py
@@ -454,6 +454,9 @@ class Frame(wx.Frame): | @@ -454,6 +454,9 @@ class Frame(wx.Frame): | ||
454 | elif id == const.ID_SELECT_MASK_PART: | 454 | elif id == const.ID_SELECT_MASK_PART: |
455 | self.OnSelectMaskParts() | 455 | self.OnSelectMaskParts() |
456 | 456 | ||
457 | + elif id == const.ID_FLOODFILL_SEGMENTATION: | ||
458 | + self.OnFFillSegmentation() | ||
459 | + | ||
457 | elif id == const.ID_VIEW_INTERPOLATED: | 460 | elif id == const.ID_VIEW_INTERPOLATED: |
458 | st = self.actived_interpolated_slices.IsChecked(const.ID_VIEW_INTERPOLATED) | 461 | st = self.actived_interpolated_slices.IsChecked(const.ID_VIEW_INTERPOLATED) |
459 | if st: | 462 | if st: |
@@ -593,6 +596,9 @@ class Frame(wx.Frame): | @@ -593,6 +596,9 @@ class Frame(wx.Frame): | ||
593 | def OnSelectMaskParts(self): | 596 | def OnSelectMaskParts(self): |
594 | Publisher.sendMessage('Enable style', const.SLICE_STATE_SELECT_MASK_PARTS) | 597 | Publisher.sendMessage('Enable style', const.SLICE_STATE_SELECT_MASK_PARTS) |
595 | 598 | ||
599 | + def OnFFillSegmentation(self): | ||
600 | + Publisher.sendMessage('Enable style', const.SLICE_STATE_FFILL_SEGMENTATION) | ||
601 | + | ||
596 | def OnInterpolatedSlices(self, status): | 602 | def OnInterpolatedSlices(self, status): |
597 | Publisher.sendMessage('Set interpolated slices', status) | 603 | Publisher.sendMessage('Set interpolated slices', status) |
598 | 604 | ||
@@ -618,7 +624,8 @@ class MenuBar(wx.MenuBar): | @@ -618,7 +624,8 @@ class MenuBar(wx.MenuBar): | ||
618 | const.ID_REORIENT_IMG, | 624 | const.ID_REORIENT_IMG, |
619 | const.ID_FLOODFILL_MASK, | 625 | const.ID_FLOODFILL_MASK, |
620 | const.ID_REMOVE_MASK_PART, | 626 | const.ID_REMOVE_MASK_PART, |
621 | - const.ID_SELECT_MASK_PART,] | 627 | + const.ID_SELECT_MASK_PART, |
628 | + const.ID_FLOODFILL_SEGMENTATION,] | ||
622 | self.__init_items() | 629 | self.__init_items() |
623 | self.__bind_events() | 630 | self.__bind_events() |
624 | 631 | ||
@@ -741,6 +748,13 @@ class MenuBar(wx.MenuBar): | @@ -741,6 +748,13 @@ class MenuBar(wx.MenuBar): | ||
741 | 748 | ||
742 | tools_menu.AppendMenu(-1, _(u"Mask"), mask_menu) | 749 | tools_menu.AppendMenu(-1, _(u"Mask"), mask_menu) |
743 | 750 | ||
751 | + # Segmentation Menu | ||
752 | + segmentation_menu = wx.Menu() | ||
753 | + self.ffill_segmentation = segmentation_menu.Append(const.ID_FLOODFILL_SEGMENTATION, _(u"Floodfill")) | ||
754 | + self.ffill_segmentation.Enable(False) | ||
755 | + | ||
756 | + tools_menu.AppendMenu(-1, _("Segmentation"), segmentation_menu) | ||
757 | + | ||
744 | # Image menu | 758 | # Image menu |
745 | image_menu = wx.Menu() | 759 | image_menu = wx.Menu() |
746 | reorient_menu = image_menu.Append(const.ID_REORIENT_IMG, _(u'Reorient image\tCtrl+Shift+R')) | 760 | reorient_menu = image_menu.Append(const.ID_REORIENT_IMG, _(u'Reorient image\tCtrl+Shift+R')) |
@@ -758,8 +772,6 @@ class MenuBar(wx.MenuBar): | @@ -758,8 +772,6 @@ class MenuBar(wx.MenuBar): | ||
758 | self.view_menu.Check(const.ID_VIEW_INTERPOLATED, v) | 772 | self.view_menu.Check(const.ID_VIEW_INTERPOLATED, v) |
759 | 773 | ||
760 | self.actived_interpolated_slices = self.view_menu | 774 | self.actived_interpolated_slices = self.view_menu |
761 | - | ||
762 | - | ||
763 | 775 | ||
764 | #view_tool_menu = wx.Menu() | 776 | #view_tool_menu = wx.Menu() |
765 | #app = view_tool_menu.Append | 777 | #app = view_tool_menu.Append |