Commit c79f0659a38f58644ae9feda327f828b704f2f2c
Committed by
GitHub
1 parent
ad7294a7
Exists in
master
Select mask part tool (#50)
* Starting to select part to new mask * Showing the selected part * Improvement * Better create_new_mask method and doc * Removed the old method to add new mask * Better method to get the position of the clicked voxel * Option to deselect part * Not setting new mask as current in task_slice * Strip white spaces and better doc in Mask create_mask class * Showing dialog * Appending the mask only after the proccess is over * Better gui
Showing
9 changed files
with
310 additions
and
42 deletions
Show diff stats
invesalius/constants.py
@@ -483,6 +483,7 @@ ID_CLEAN_MASK = wx.NewId() | @@ -483,6 +483,7 @@ ID_CLEAN_MASK = wx.NewId() | ||
483 | ID_REORIENT_IMG = wx.NewId() | 483 | 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 | 487 | ||
487 | #--------------------------------------------------------- | 488 | #--------------------------------------------------------- |
488 | STATE_DEFAULT = 1000 | 489 | STATE_DEFAULT = 1000 |
@@ -502,6 +503,7 @@ SLICE_STATE_WATERSHED = 3009 | @@ -502,6 +503,7 @@ SLICE_STATE_WATERSHED = 3009 | ||
502 | SLICE_STATE_REORIENT = 3010 | 503 | SLICE_STATE_REORIENT = 3010 |
503 | SLICE_STATE_MASK_FFILL = 3011 | 504 | SLICE_STATE_MASK_FFILL = 3011 |
504 | SLICE_STATE_REMOVE_MASK_PARTS = 3012 | 505 | SLICE_STATE_REMOVE_MASK_PARTS = 3012 |
506 | +SLICE_STATE_SELECT_MASK_PARTS = 3013 | ||
505 | 507 | ||
506 | VOLUME_STATE_SEED = 2001 | 508 | VOLUME_STATE_SEED = 2001 |
507 | # STATE_LINEAR_MEASURE = 3001 | 509 | # STATE_LINEAR_MEASURE = 3001 |
@@ -521,6 +523,7 @@ SLICE_STYLES.append(SLICE_STATE_EDITOR) | @@ -521,6 +523,7 @@ SLICE_STYLES.append(SLICE_STATE_EDITOR) | ||
521 | SLICE_STYLES.append(SLICE_STATE_WATERSHED) | 523 | SLICE_STYLES.append(SLICE_STATE_WATERSHED) |
522 | SLICE_STYLES.append(SLICE_STATE_MASK_FFILL) | 524 | SLICE_STYLES.append(SLICE_STATE_MASK_FFILL) |
523 | SLICE_STYLES.append(SLICE_STATE_REMOVE_MASK_PARTS) | 525 | SLICE_STYLES.append(SLICE_STATE_REMOVE_MASK_PARTS) |
526 | +SLICE_STYLES.append(SLICE_STATE_SELECT_MASK_PARTS) | ||
524 | 527 | ||
525 | VOLUME_STYLES = TOOL_STATES + [VOLUME_STATE_SEED, STATE_MEASURE_DISTANCE, | 528 | VOLUME_STYLES = TOOL_STATES + [VOLUME_STATE_SEED, STATE_MEASURE_DISTANCE, |
526 | STATE_MEASURE_ANGLE] | 529 | STATE_MEASURE_ANGLE] |
@@ -531,6 +534,7 @@ STYLE_LEVEL = {SLICE_STATE_EDITOR: 1, | @@ -531,6 +534,7 @@ STYLE_LEVEL = {SLICE_STATE_EDITOR: 1, | ||
531 | SLICE_STATE_WATERSHED: 1, | 534 | SLICE_STATE_WATERSHED: 1, |
532 | SLICE_STATE_MASK_FFILL: 2, | 535 | SLICE_STATE_MASK_FFILL: 2, |
533 | SLICE_STATE_REMOVE_MASK_PARTS: 2, | 536 | SLICE_STATE_REMOVE_MASK_PARTS: 2, |
537 | + SLICE_STATE_SELECT_MASK_PARTS: 2, | ||
534 | SLICE_STATE_CROSS: 2, | 538 | SLICE_STATE_CROSS: 2, |
535 | SLICE_STATE_SCROLL: 2, | 539 | SLICE_STATE_SCROLL: 2, |
536 | SLICE_STATE_REORIENT: 2, | 540 | SLICE_STATE_REORIENT: 2, |
invesalius/data/mask.py
@@ -126,7 +126,7 @@ class EditionHistory(object): | @@ -126,7 +126,7 @@ class EditionHistory(object): | ||
126 | h[self.index].commit_history(mvolume) | 126 | h[self.index].commit_history(mvolume) |
127 | self._reload_slice(self.index) | 127 | self._reload_slice(self.index) |
128 | Publisher.sendMessage("Enable redo", True) | 128 | Publisher.sendMessage("Enable redo", True) |
129 | - | 129 | + |
130 | if self.index == 0: | 130 | if self.index == 0: |
131 | Publisher.sendMessage("Enable undo", False) | 131 | Publisher.sendMessage("Enable undo", False) |
132 | print "AT", self.index, len(self.history), self.history[self.index].filename | 132 | print "AT", self.index, len(self.history), self.history[self.index].filename |
@@ -154,7 +154,7 @@ class EditionHistory(object): | @@ -154,7 +154,7 @@ class EditionHistory(object): | ||
154 | h[self.index].commit_history(mvolume) | 154 | h[self.index].commit_history(mvolume) |
155 | self._reload_slice(self.index) | 155 | self._reload_slice(self.index) |
156 | Publisher.sendMessage("Enable undo", True) | 156 | Publisher.sendMessage("Enable undo", True) |
157 | - | 157 | + |
158 | if self.index == len(h) - 1: | 158 | if self.index == len(h) - 1: |
159 | Publisher.sendMessage("Enable redo", False) | 159 | Publisher.sendMessage("Enable redo", False) |
160 | print "AT", self.index, len(h), h[self.index].filename | 160 | print "AT", self.index, len(h), h[self.index].filename |
@@ -174,7 +174,7 @@ class EditionHistory(object): | @@ -174,7 +174,7 @@ class EditionHistory(object): | ||
174 | v_undo = False | 174 | v_undo = False |
175 | elif self.index == len(self.history) - 1: | 175 | elif self.index == len(self.history) - 1: |
176 | v_redo = False | 176 | v_redo = False |
177 | - | 177 | + |
178 | Publisher.sendMessage("Enable undo", v_undo) | 178 | Publisher.sendMessage("Enable undo", v_undo) |
179 | Publisher.sendMessage("Enable redo", v_redo) | 179 | Publisher.sendMessage("Enable redo", v_redo) |
180 | 180 | ||
@@ -229,7 +229,7 @@ class Mask(): | @@ -229,7 +229,7 @@ class Mask(): | ||
229 | 229 | ||
230 | def SavePlist(self, dir_temp, filelist): | 230 | def SavePlist(self, dir_temp, filelist): |
231 | mask = {} | 231 | mask = {} |
232 | - filename = u'mask_%d' % self.index | 232 | + filename = u'mask_%d' % self.index |
233 | mask_filename = u'%s.dat' % filename | 233 | mask_filename = u'%s.dat' % filename |
234 | mask_filepath = os.path.join(dir_temp, mask_filename) | 234 | mask_filepath = os.path.join(dir_temp, mask_filename) |
235 | filelist[self.temp_file] = mask_filename | 235 | filelist[self.temp_file] = mask_filename |
@@ -304,7 +304,12 @@ class Mask(): | @@ -304,7 +304,12 @@ class Mask(): | ||
304 | Mask.general_index = index | 304 | Mask.general_index = index |
305 | 305 | ||
306 | def create_mask(self, shape): | 306 | def create_mask(self, shape): |
307 | - print "Creating a mask" | 307 | + """ |
308 | + Creates a new mask object. This method do not append this new mask into the project. | ||
309 | + | ||
310 | + Parameters: | ||
311 | + shape(int, int, int): The shape of the new mask. | ||
312 | + """ | ||
308 | self.temp_file = tempfile.mktemp() | 313 | self.temp_file = tempfile.mktemp() |
309 | shape = shape[0] + 1, shape[1] + 1, shape[2] + 1 | 314 | shape = shape[0] + 1, shape[1] + 1, shape[2] + 1 |
310 | self.matrix = numpy.memmap(self.temp_file, mode='w+', dtype='uint8', shape=shape) | 315 | self.matrix = numpy.memmap(self.temp_file, mode='w+', dtype='uint8', shape=shape) |
@@ -329,7 +334,7 @@ class Mask(): | @@ -329,7 +334,7 @@ class Mask(): | ||
329 | new_mask.threshold_range = self.threshold_range | 334 | new_mask.threshold_range = self.threshold_range |
330 | new_mask.edition_threshold_range = self.edition_threshold_range | 335 | new_mask.edition_threshold_range = self.edition_threshold_range |
331 | new_mask.is_shown = self.is_shown | 336 | new_mask.is_shown = self.is_shown |
332 | - | 337 | + |
333 | new_mask.create_mask(shape=[i-1 for i in self.matrix.shape]) | 338 | new_mask.create_mask(shape=[i-1 for i in self.matrix.shape]) |
334 | new_mask.matrix[:] = self.matrix[:] | 339 | new_mask.matrix[:] = self.matrix[:] |
335 | 340 |
invesalius/data/slice_.py
@@ -302,14 +302,14 @@ class Slice(object): | @@ -302,14 +302,14 @@ class Slice(object): | ||
302 | 302 | ||
303 | def __add_mask(self, pubsub_evt): | 303 | def __add_mask(self, pubsub_evt): |
304 | mask_name = pubsub_evt.data | 304 | mask_name = pubsub_evt.data |
305 | - self.CreateMask(name=mask_name) | 305 | + self.create_new_mask(name=mask_name) |
306 | self.SetMaskColour(self.current_mask.index, self.current_mask.colour) | 306 | self.SetMaskColour(self.current_mask.index, self.current_mask.colour) |
307 | 307 | ||
308 | def __add_mask_thresh(self, pubsub_evt): | 308 | def __add_mask_thresh(self, pubsub_evt): |
309 | mask_name = pubsub_evt.data[0] | 309 | mask_name = pubsub_evt.data[0] |
310 | thresh = pubsub_evt.data[1] | 310 | thresh = pubsub_evt.data[1] |
311 | colour = pubsub_evt.data[2] | 311 | colour = pubsub_evt.data[2] |
312 | - self.CreateMask(name=mask_name, threshold_range=thresh, colour =colour) | 312 | + self.create_new_mask(name=mask_name, threshold_range=thresh, colour=colour) |
313 | self.SetMaskColour(self.current_mask.index, self.current_mask.colour) | 313 | self.SetMaskColour(self.current_mask.index, self.current_mask.colour) |
314 | self.SelectCurrentMask(self.current_mask.index) | 314 | self.SelectCurrentMask(self.current_mask.index) |
315 | Publisher.sendMessage('Reload actual slice') | 315 | Publisher.sendMessage('Reload actual slice') |
@@ -574,6 +574,14 @@ class Slice(object): | @@ -574,6 +574,14 @@ class Slice(object): | ||
574 | 1: (0.0, 1.0, 0.0, 1.0), | 574 | 1: (0.0, 1.0, 0.0, 1.0), |
575 | 2: (1.0, 0.0, 0.0, 1.0)}) | 575 | 2: (1.0, 0.0, 0.0, 1.0)}) |
576 | final_image = self.do_blend(final_image, cimage) | 576 | final_image = self.do_blend(final_image, cimage) |
577 | + elif self.to_show_aux and self.current_mask: | ||
578 | + m = self.get_aux_slice(self.to_show_aux, orientation, slice_number) | ||
579 | + tmp_vimage = converters.to_vtk(m, self.spacing, slice_number, orientation) | ||
580 | + aux_image = self.do_custom_colour(tmp_vimage, {0: (0.0, 0.0, 0.0, 0.0), | ||
581 | + 1: (0.0, 0.0, 0.0, 0.0), | ||
582 | + 254: (1.0, 0.0, 0.0, 1.0), | ||
583 | + 255: (1.0, 0.0, 0.0, 1.0)}) | ||
584 | + final_image = self.do_blend(final_image, aux_image) | ||
577 | return final_image | 585 | return final_image |
578 | 586 | ||
579 | def get_image_slice(self, orientation, slice_number, number_slices=1, | 587 | def get_image_slice(self, orientation, slice_number, number_slices=1, |
@@ -1026,14 +1034,32 @@ class Slice(object): | @@ -1026,14 +1034,32 @@ class Slice(object): | ||
1026 | #else: | 1034 | #else: |
1027 | #widget.SetInput(cast.GetOutput()) | 1035 | #widget.SetInput(cast.GetOutput()) |
1028 | 1036 | ||
1029 | - | ||
1030 | - | ||
1031 | - def CreateMask(self, imagedata=None, name=None, colour=None, | ||
1032 | - opacity=None, threshold_range=None, | ||
1033 | - edition_threshold_range = None, | ||
1034 | - edited_points=None): | ||
1035 | - | ||
1036 | - # TODO: mask system to new system. | 1037 | + def create_new_mask(self, name=None, |
1038 | + colour=None, | ||
1039 | + opacity=None, | ||
1040 | + threshold_range=None, | ||
1041 | + edition_threshold_range=None, | ||
1042 | + add_to_project=True, | ||
1043 | + show=True): | ||
1044 | + """ | ||
1045 | + Creates a new mask and add it to project. | ||
1046 | + | ||
1047 | + Parameters: | ||
1048 | + name (string): name of the new mask. If name is None a automatic | ||
1049 | + name will be used. | ||
1050 | + colour (R, G, B): a RGB tuple of float number. | ||
1051 | + opacity (float): a float number, from 0 to 1. If opacity is None | ||
1052 | + the default one will be used. | ||
1053 | + threshold_range (int, int): a 2-tuple indicating threshold range. | ||
1054 | + If None the default one will be used. | ||
1055 | + edition_threshold_range (int, int): a 2-tuple indicating threshold | ||
1056 | + range. If None the default one will be used. | ||
1057 | + show (bool): if this new mask will be showed and set as current | ||
1058 | + mask. | ||
1059 | + | ||
1060 | + Returns: | ||
1061 | + new_mask: The new mask object. | ||
1062 | + """ | ||
1037 | future_mask = Mask() | 1063 | future_mask = Mask() |
1038 | future_mask.create_mask(self.matrix.shape) | 1064 | future_mask.create_mask(self.matrix.shape) |
1039 | 1065 | ||
@@ -1045,12 +1071,13 @@ class Slice(object): | @@ -1045,12 +1071,13 @@ class Slice(object): | ||
1045 | future_mask.opacity = opacity | 1071 | future_mask.opacity = opacity |
1046 | if edition_threshold_range: | 1072 | if edition_threshold_range: |
1047 | future_mask.edition_threshold_range = edition_threshold_range | 1073 | future_mask.edition_threshold_range = edition_threshold_range |
1048 | - if edited_points: | ||
1049 | - future_mask.edited_points = edited_points | ||
1050 | if threshold_range: | 1074 | if threshold_range: |
1051 | future_mask.threshold_range = threshold_range | 1075 | future_mask.threshold_range = threshold_range |
1052 | 1076 | ||
1053 | - self._add_mask_into_proj(future_mask) | 1077 | + if add_to_project: |
1078 | + self._add_mask_into_proj(future_mask, show=show) | ||
1079 | + | ||
1080 | + return future_mask | ||
1054 | 1081 | ||
1055 | 1082 | ||
1056 | def _add_mask_into_proj(self, mask, show=True): | 1083 | def _add_mask_into_proj(self, mask, show=True): |
@@ -1282,6 +1309,7 @@ class Slice(object): | @@ -1282,6 +1309,7 @@ class Slice(object): | ||
1282 | op, m1, m2 = pubsub_evt.data | 1309 | op, m1, m2 = pubsub_evt.data |
1283 | self.do_boolean_op(op, m1, m2) | 1310 | self.do_boolean_op(op, m1, m2) |
1284 | 1311 | ||
1312 | + | ||
1285 | def do_boolean_op(self, op, m1, m2): | 1313 | def do_boolean_op(self, op, m1, m2): |
1286 | name_ops = {const.BOOLEAN_UNION: _(u"Union"), | 1314 | name_ops = {const.BOOLEAN_UNION: _(u"Union"), |
1287 | const.BOOLEAN_DIFF: _(u"Diff"), | 1315 | const.BOOLEAN_DIFF: _(u"Diff"), |
invesalius/data/styles.py
@@ -1928,6 +1928,98 @@ class RemoveMaskPartsInteractorStyle(FloodFillMaskInteractorStyle): | @@ -1928,6 +1928,98 @@ class RemoveMaskPartsInteractorStyle(FloodFillMaskInteractorStyle): | ||
1928 | self._progr_msg = _(u"Removing part ...") | 1928 | self._progr_msg = _(u"Removing part ...") |
1929 | 1929 | ||
1930 | 1930 | ||
1931 | +class SelectPartConfig(object): | ||
1932 | + __metaclass__= utils.Singleton | ||
1933 | + def __init__(self): | ||
1934 | + self.mask = None | ||
1935 | + self.con_3d = 6 | ||
1936 | + self.dlg_visible = False | ||
1937 | + self.mask_name = '' | ||
1938 | + | ||
1939 | + | ||
1940 | +class SelectMaskPartsInteractorStyle(DefaultInteractorStyle): | ||
1941 | + def __init__(self, viewer): | ||
1942 | + DefaultInteractorStyle.__init__(self, viewer) | ||
1943 | + | ||
1944 | + self.viewer = viewer | ||
1945 | + self.orientation = self.viewer.orientation | ||
1946 | + | ||
1947 | + self.picker = vtk.vtkWorldPointPicker() | ||
1948 | + self.slice_actor = viewer.slice_data.actor | ||
1949 | + self.slice_data = viewer.slice_data | ||
1950 | + | ||
1951 | + self.config = SelectPartConfig() | ||
1952 | + self.dlg = None | ||
1953 | + | ||
1954 | + self.t0 = 254 | ||
1955 | + self.t1 = 255 | ||
1956 | + self.fill_value = 254 | ||
1957 | + | ||
1958 | + self.AddObserver("LeftButtonPressEvent", self.OnSelect) | ||
1959 | + | ||
1960 | + def SetUp(self): | ||
1961 | + if not self.config.dlg_visible: | ||
1962 | + import data.mask as mask | ||
1963 | + default_name = const.MASK_NAME_PATTERN %(mask.Mask.general_index+2) | ||
1964 | + | ||
1965 | + self.config.mask_name = default_name | ||
1966 | + self.config.dlg_visible = True | ||
1967 | + self.dlg= dialogs.SelectPartsOptionsDialog(self.config) | ||
1968 | + self.dlg.Show() | ||
1969 | + | ||
1970 | + def CleanUp(self): | ||
1971 | + if (self.dlg is not None) and (self.config.dlg_visible): | ||
1972 | + self.config.dlg_visible = False | ||
1973 | + self.dlg.Destroy() | ||
1974 | + self.dlg = None | ||
1975 | + | ||
1976 | + if self.config.mask: | ||
1977 | + self.config.mask.name = self.config.mask_name | ||
1978 | + self.viewer.slice_._add_mask_into_proj(self.config.mask) | ||
1979 | + self.viewer.slice_.SelectCurrentMask(self.config.mask.index) | ||
1980 | + Publisher.sendMessage('Change mask selected', self.config.mask.index) | ||
1981 | + self.config.mask = None | ||
1982 | + del self.viewer.slice_.aux_matrices['SELECT'] | ||
1983 | + self.viewer.slice_.to_show_aux = '' | ||
1984 | + Publisher.sendMessage('Reload actual slice') | ||
1985 | + | ||
1986 | + def OnSelect(self, obj, evt): | ||
1987 | + if (self.viewer.slice_.buffer_slices[self.orientation].mask is None): | ||
1988 | + return | ||
1989 | + | ||
1990 | + iren = self.viewer.interactor | ||
1991 | + mouse_x, mouse_y = iren.GetEventPosition() | ||
1992 | + x, y, z = self.viewer.get_voxel_clicked(mouse_x, mouse_y, self.picker) | ||
1993 | + | ||
1994 | + mask = self.viewer.slice_.current_mask.matrix[1:, 1:, 1:] | ||
1995 | + | ||
1996 | + bstruct = np.array(generate_binary_structure(3, CON3D[self.config.con_3d]), dtype='uint8') | ||
1997 | + self.viewer.slice_.do_threshold_to_all_slices() | ||
1998 | + | ||
1999 | + if self.config.mask is None: | ||
2000 | + self._create_new_mask() | ||
2001 | + | ||
2002 | + if iren.GetControlKey(): | ||
2003 | + floodfill.floodfill_threshold(self.config.mask.matrix[1:, 1:, 1:], [[x, y, z]], 254, 255, 0, bstruct, self.config.mask.matrix[1:, 1:, 1:]) | ||
2004 | + else: | ||
2005 | + floodfill.floodfill_threshold(mask, [[x, y, z]], self.t0, self.t1, self.fill_value, bstruct, self.config.mask.matrix[1:, 1:, 1:]) | ||
2006 | + | ||
2007 | + self.viewer.slice_.aux_matrices['SELECT'] = self.config.mask.matrix[1:, 1:, 1:] | ||
2008 | + self.viewer.slice_.to_show_aux = 'SELECT' | ||
2009 | + | ||
2010 | + self.config.mask.was_edited = True | ||
2011 | + Publisher.sendMessage('Reload actual slice') | ||
2012 | + | ||
2013 | + def _create_new_mask(self): | ||
2014 | + mask = self.viewer.slice_.create_new_mask(show=False, add_to_project=False) | ||
2015 | + mask.was_edited = True | ||
2016 | + mask.matrix[0, :, :] = 1 | ||
2017 | + mask.matrix[:, 0, :] = 1 | ||
2018 | + mask.matrix[:, :, 0] = 1 | ||
2019 | + | ||
2020 | + self.config.mask = mask | ||
2021 | + | ||
2022 | + | ||
1931 | def get_style(style): | 2023 | def get_style(style): |
1932 | STYLES = { | 2024 | STYLES = { |
1933 | const.STATE_DEFAULT: DefaultInteractorStyle, | 2025 | const.STATE_DEFAULT: DefaultInteractorStyle, |
@@ -1945,6 +2037,7 @@ def get_style(style): | @@ -1945,6 +2037,7 @@ def get_style(style): | ||
1945 | const.SLICE_STATE_REORIENT: ReorientImageInteractorStyle, | 2037 | const.SLICE_STATE_REORIENT: ReorientImageInteractorStyle, |
1946 | const.SLICE_STATE_MASK_FFILL: FloodFillMaskInteractorStyle, | 2038 | const.SLICE_STATE_MASK_FFILL: FloodFillMaskInteractorStyle, |
1947 | const.SLICE_STATE_REMOVE_MASK_PARTS: RemoveMaskPartsInteractorStyle, | 2039 | const.SLICE_STATE_REMOVE_MASK_PARTS: RemoveMaskPartsInteractorStyle, |
2040 | + const.SLICE_STATE_SELECT_MASK_PARTS: SelectMaskPartsInteractorStyle, | ||
1948 | } | 2041 | } |
1949 | return STYLES[style] | 2042 | return STYLES[style] |
1950 | 2043 |
invesalius/data/viewer_slice.py
@@ -977,9 +977,12 @@ class Viewer(wx.Panel): | @@ -977,9 +977,12 @@ class Viewer(wx.Panel): | ||
977 | my = round((z - zi)/self.slice_.spacing[2], 0) | 977 | my = round((z - zi)/self.slice_.spacing[2], 0) |
978 | return my, mx | 978 | return my, mx |
979 | 979 | ||
980 | - def get_coordinate_cursor(self): | 980 | + def get_coordinate_cursor(self, picker=None): |
981 | # Find position | 981 | # Find position |
982 | - x, y, z = self.pick.GetPickPosition() | 982 | + if picker is None: |
983 | + picker = self.pick | ||
984 | + | ||
985 | + x, y, z = picker.GetPickPosition() | ||
983 | bounds = self.slice_data.actor.GetBounds() | 986 | bounds = self.slice_data.actor.GetBounds() |
984 | if bounds[0] == bounds[1]: | 987 | if bounds[0] == bounds[1]: |
985 | x = bounds[0] | 988 | x = bounds[0] |
@@ -989,11 +992,14 @@ class Viewer(wx.Panel): | @@ -989,11 +992,14 @@ class Viewer(wx.Panel): | ||
989 | z = bounds[4] | 992 | z = bounds[4] |
990 | return x, y, z | 993 | return x, y, z |
991 | 994 | ||
992 | - def get_coordinate_cursor_edition(self, slice_data): | 995 | + def get_coordinate_cursor_edition(self, slice_data, picker=None): |
993 | # Find position | 996 | # Find position |
994 | actor = slice_data.actor | 997 | actor = slice_data.actor |
995 | slice_number = slice_data.number | 998 | slice_number = slice_data.number |
996 | - x, y, z = self.pick.GetPickPosition() | 999 | + if picker is None: |
1000 | + picker = self.pick | ||
1001 | + | ||
1002 | + x, y, z = picker.GetPickPosition() | ||
997 | 1003 | ||
998 | # First we fix the position origin, based on vtkActor bounds | 1004 | # First we fix the position origin, based on vtkActor bounds |
999 | bounds = actor.GetBounds() | 1005 | bounds = actor.GetBounds() |
@@ -1023,6 +1029,41 @@ class Viewer(wx.Panel): | @@ -1023,6 +1029,41 @@ class Viewer(wx.Panel): | ||
1023 | 1029 | ||
1024 | return x, y, z | 1030 | return x, y, z |
1025 | 1031 | ||
1032 | + def get_voxel_clicked(self, mx, my, picker=None): | ||
1033 | + """ | ||
1034 | + Given the (mx, my) mouse clicked position returns the voxel coordinate | ||
1035 | + of the voxel at (that mx, my) position. | ||
1036 | + | ||
1037 | + Parameters: | ||
1038 | + mx (int): x position. | ||
1039 | + my (int): y position | ||
1040 | + picker: the picker used to get calculate the voxel coordinate. | ||
1041 | + | ||
1042 | + Returns: | ||
1043 | + voxel_coordinate (x, y, z): voxel coordinate inside the matrix. Can | ||
1044 | + be used to access the voxel value inside the matrix. | ||
1045 | + """ | ||
1046 | + if picker is None: | ||
1047 | + picker = self.pick | ||
1048 | + | ||
1049 | + slice_data = self.slice_data | ||
1050 | + renderer = slice_data.renderer | ||
1051 | + | ||
1052 | + picker.Pick(mx, my, 0, renderer) | ||
1053 | + | ||
1054 | + coord = self.get_coordinate_cursor(picker) | ||
1055 | + position = slice_data.actor.GetInput().FindPoint(coord) | ||
1056 | + | ||
1057 | + if position != -1: | ||
1058 | + coord = slice_data.actor.GetInput().GetPoint(position) | ||
1059 | + | ||
1060 | + if position < 0: | ||
1061 | + position = viewer.calculate_matrix_position(coord) | ||
1062 | + | ||
1063 | + x, y, z = self.calcultate_scroll_position(position) | ||
1064 | + | ||
1065 | + return (x, y, z) | ||
1066 | + | ||
1026 | def __bind_events(self): | 1067 | def __bind_events(self): |
1027 | Publisher.subscribe(self.LoadImagedata, | 1068 | Publisher.subscribe(self.LoadImagedata, |
1028 | 'Load slice to viewer') | 1069 | 'Load slice to viewer') |
@@ -1044,8 +1085,8 @@ class Viewer(wx.Panel): | @@ -1044,8 +1085,8 @@ class Viewer(wx.Panel): | ||
1044 | Publisher.subscribe(self.Navigation, | 1085 | Publisher.subscribe(self.Navigation, |
1045 | 'Co-registered Points') | 1086 | 'Co-registered Points') |
1046 | ### | 1087 | ### |
1047 | - Publisher.subscribe(self.ChangeBrushColour, | ||
1048 | - 'Add mask') | 1088 | + # Publisher.subscribe(self.ChangeBrushColour, |
1089 | + # 'Add mask') | ||
1049 | 1090 | ||
1050 | Publisher.subscribe(self.UpdateWindowLevelValue, | 1091 | Publisher.subscribe(self.UpdateWindowLevelValue, |
1051 | 'Update window level value') | 1092 | 'Update window level value') |
invesalius/gui/data_notebook.py
@@ -512,11 +512,11 @@ class MasksListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): | @@ -512,11 +512,11 @@ class MasksListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): | ||
512 | self.SetStringItem(index, 1, label, | 512 | self.SetStringItem(index, 1, label, |
513 | imageId=self.mask_list_index[index]) | 513 | imageId=self.mask_list_index[index]) |
514 | self.SetStringItem(index, 2, threshold) | 514 | self.SetStringItem(index, 2, threshold) |
515 | - self.SetItemImage(index, 1) | ||
516 | - for key in self.mask_list_index.keys(): | ||
517 | - if key != index: | ||
518 | - self.SetItemImage(key, 0) | ||
519 | - self.current_index = index | 515 | + # self.SetItemImage(index, 1) |
516 | + # for key in self.mask_list_index.keys(): | ||
517 | + # if key != index: | ||
518 | + # self.SetItemImage(key, 0) | ||
519 | + # self.current_index = index | ||
520 | 520 | ||
521 | def AddMask(self, pubsub_evt): | 521 | def AddMask(self, pubsub_evt): |
522 | index, mask_name, threshold_range, colour = pubsub_evt.data | 522 | index, mask_name, threshold_range, colour = pubsub_evt.data |
invesalius/gui/dialogs.py
@@ -1940,3 +1940,94 @@ class FFillOptionsDialog(wx.Dialog): | @@ -1940,3 +1940,94 @@ class FFillOptionsDialog(wx.Dialog): | ||
1940 | Publisher.sendMessage('Disable style', const.SLICE_STATE_MASK_FFILL) | 1940 | Publisher.sendMessage('Disable style', const.SLICE_STATE_MASK_FFILL) |
1941 | evt.Skip() | 1941 | evt.Skip() |
1942 | self.Destroy() | 1942 | self.Destroy() |
1943 | + | ||
1944 | + | ||
1945 | +class SelectPartsOptionsDialog(wx.Dialog): | ||
1946 | + def __init__(self, config): | ||
1947 | + pre = wx.PreDialog() | ||
1948 | + pre.Create(wx.GetApp().GetTopWindow(), -1, _(u"Select mask parts"), style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT) | ||
1949 | + self.PostCreate(pre) | ||
1950 | + | ||
1951 | + self.config = config | ||
1952 | + | ||
1953 | + self._init_gui() | ||
1954 | + | ||
1955 | + def _init_gui(self): | ||
1956 | + self.target_name = wx.TextCtrl(self, -1) | ||
1957 | + self.target_name.SetValue(self.config.mask_name) | ||
1958 | + | ||
1959 | + # Connectivity 3D | ||
1960 | + self.conect3D_6 = wx.RadioButton(self, -1, "6", style=wx.RB_GROUP) | ||
1961 | + self.conect3D_18 = wx.RadioButton(self, -1, "18") | ||
1962 | + self.conect3D_26 = wx.RadioButton(self, -1, "26") | ||
1963 | + | ||
1964 | + if self.config.con_3d == 18: | ||
1965 | + self.conect3D_18.SetValue(1) | ||
1966 | + elif self.config.con_3d == 26: | ||
1967 | + self.conect3D_26.SetValue(1) | ||
1968 | + else: | ||
1969 | + self.conect3D_6.SetValue(1) | ||
1970 | + | ||
1971 | + sizer_t = wx.BoxSizer(wx.HORIZONTAL) | ||
1972 | + sizer_t.AddSpacer(7) | ||
1973 | + sizer_t.Add(wx.StaticText(self, -1, _(u"Target mask name")), 1, wx.ALIGN_CENTRE_VERTICAL) | ||
1974 | + sizer_t.AddSpacer(7) | ||
1975 | + sizer_t.Add(self.target_name, 1, wx.EXPAND) | ||
1976 | + sizer_t.AddSpacer(7) | ||
1977 | + | ||
1978 | + sizer_c = wx.BoxSizer(wx.HORIZONTAL) | ||
1979 | + sizer_c.AddSpacer(7) | ||
1980 | + sizer_c.Add(self.conect3D_6) | ||
1981 | + sizer_c.AddSpacer(7) | ||
1982 | + sizer_c.Add(self.conect3D_18) | ||
1983 | + sizer_c.AddSpacer(7) | ||
1984 | + sizer_c.Add(self.conect3D_26) | ||
1985 | + | ||
1986 | + sizer = wx.BoxSizer(wx.VERTICAL) | ||
1987 | + sizer.AddSpacer(7) | ||
1988 | + sizer.Add(sizer_t, 1, wx.EXPAND) | ||
1989 | + sizer.AddSpacer(7) | ||
1990 | + sizer.Add(wx.StaticText(self, -1, _(u"3D Connectivity")), 0, wx.LEFT, 7) | ||
1991 | + sizer.AddSpacer(5) | ||
1992 | + sizer.Add(sizer_c) | ||
1993 | + sizer.AddSpacer(7) | ||
1994 | + | ||
1995 | + # sizer = wx.GridBagSizer(11, 6) | ||
1996 | + # sizer.AddStretchSpacer((0, 0)) | ||
1997 | + | ||
1998 | + # sizer.Add(wx.StaticText(self, -1, _(u"Target mask name")), (1, 0), (1, 6), flag=wx.LEFT|wx.ALIGN_BOTTOM|wx.EXPAND, border=7) | ||
1999 | + # sizer.Add(self.target_name, (2, 0), (1, 6), flag=wx.LEFT|wx.EXPAND|wx.RIGHT|wx.ALIGN_TOP, border=9) | ||
2000 | + | ||
2001 | + # # sizer.AddStretchSpacer((3, 0)) | ||
2002 | + | ||
2003 | + # sizer.Add(wx.StaticText(self, -1, _(u"3D Connectivity")), (3, 0), (1, 6), flag=wx.LEFT, border=7) | ||
2004 | + # sizer.Add(self.conect3D_6, (4, 0), flag=wx.LEFT, border=9) | ||
2005 | + # sizer.Add(self.conect3D_18, (4, 1), flag=wx.LEFT, border=9) | ||
2006 | + # sizer.Add(self.conect3D_26, (4, 2), flag=wx.LEFT, border=9) | ||
2007 | + # sizer.AddStretchSpacer((5, 0)) | ||
2008 | + | ||
2009 | + self.SetSizer(sizer) | ||
2010 | + sizer.Fit(self) | ||
2011 | + self.Layout() | ||
2012 | + | ||
2013 | + self.target_name.Bind(wx.EVT_CHAR, self.OnChar) | ||
2014 | + self.Bind(wx.EVT_RADIOBUTTON, self.OnSetRadio) | ||
2015 | + self.Bind(wx.EVT_CLOSE, self.OnClose) | ||
2016 | + | ||
2017 | + def OnChar(self, evt): | ||
2018 | + evt.Skip() | ||
2019 | + self.config.mask_name = self.target_name.GetValue() | ||
2020 | + | ||
2021 | + def OnSetRadio(self, evt): | ||
2022 | + if self.conect3D_6.GetValue(): | ||
2023 | + self.config.con_3d = 6 | ||
2024 | + elif self.conect3D_18.GetValue(): | ||
2025 | + self.config.con_3d = 18 | ||
2026 | + elif self.conect3D_26.GetValue(): | ||
2027 | + self.config.con_3d = 26 | ||
2028 | + | ||
2029 | + def OnClose(self, evt): | ||
2030 | + if self.config.dlg_visible: | ||
2031 | + Publisher.sendMessage('Disable style', const.SLICE_STATE_SELECT_MASK_PARTS) | ||
2032 | + evt.Skip() | ||
2033 | + self.Destroy() |
invesalius/gui/frame.py
@@ -451,20 +451,16 @@ class Frame(wx.Frame): | @@ -451,20 +451,16 @@ class Frame(wx.Frame): | ||
451 | elif id == const.ID_REMOVE_MASK_PART: | 451 | elif id == const.ID_REMOVE_MASK_PART: |
452 | self.OnRemoveMaskParts() | 452 | self.OnRemoveMaskParts() |
453 | 453 | ||
454 | + elif id == const.ID_SELECT_MASK_PART: | ||
455 | + self.OnSelectMaskParts() | ||
456 | + | ||
454 | elif id == const.ID_VIEW_INTERPOLATED: | 457 | elif id == const.ID_VIEW_INTERPOLATED: |
455 | - | ||
456 | st = self.actived_interpolated_slices.IsChecked(const.ID_VIEW_INTERPOLATED) | 458 | st = self.actived_interpolated_slices.IsChecked(const.ID_VIEW_INTERPOLATED) |
457 | - | ||
458 | if st: | 459 | if st: |
459 | self.OnInterpolatedSlices(True) | 460 | self.OnInterpolatedSlices(True) |
460 | else: | 461 | else: |
461 | self.OnInterpolatedSlices(False) | 462 | self.OnInterpolatedSlices(False) |
462 | 463 | ||
463 | - def OnInterpolatedSlices(self, status): | ||
464 | - | ||
465 | - Publisher.sendMessage('Set interpolated slices', status) | ||
466 | - | ||
467 | - | ||
468 | def OnSize(self, evt): | 464 | def OnSize(self, evt): |
469 | """ | 465 | """ |
470 | Refresh GUI when frame is resized. | 466 | Refresh GUI when frame is resized. |
@@ -594,6 +590,12 @@ class Frame(wx.Frame): | @@ -594,6 +590,12 @@ class Frame(wx.Frame): | ||
594 | def OnRemoveMaskParts(self): | 590 | def OnRemoveMaskParts(self): |
595 | Publisher.sendMessage('Enable style', const.SLICE_STATE_REMOVE_MASK_PARTS) | 591 | Publisher.sendMessage('Enable style', const.SLICE_STATE_REMOVE_MASK_PARTS) |
596 | 592 | ||
593 | + def OnSelectMaskParts(self): | ||
594 | + Publisher.sendMessage('Enable style', const.SLICE_STATE_SELECT_MASK_PARTS) | ||
595 | + | ||
596 | + def OnInterpolatedSlices(self, status): | ||
597 | + Publisher.sendMessage('Set interpolated slices', status) | ||
598 | + | ||
597 | # ------------------------------------------------------------------ | 599 | # ------------------------------------------------------------------ |
598 | # ------------------------------------------------------------------ | 600 | # ------------------------------------------------------------------ |
599 | # ------------------------------------------------------------------ | 601 | # ------------------------------------------------------------------ |
@@ -615,7 +617,8 @@ class MenuBar(wx.MenuBar): | @@ -615,7 +617,8 @@ class MenuBar(wx.MenuBar): | ||
615 | const.ID_PROJECT_CLOSE, | 617 | const.ID_PROJECT_CLOSE, |
616 | const.ID_REORIENT_IMG, | 618 | const.ID_REORIENT_IMG, |
617 | const.ID_FLOODFILL_MASK, | 619 | const.ID_FLOODFILL_MASK, |
618 | - const.ID_REMOVE_MASK_PART,] | 620 | + const.ID_REMOVE_MASK_PART, |
621 | + const.ID_SELECT_MASK_PART,] | ||
619 | self.__init_items() | 622 | self.__init_items() |
620 | self.__bind_events() | 623 | self.__bind_events() |
621 | 624 | ||
@@ -733,6 +736,9 @@ class MenuBar(wx.MenuBar): | @@ -733,6 +736,9 @@ class MenuBar(wx.MenuBar): | ||
733 | self.remove_mask_part_menu = mask_menu.Append(const.ID_REMOVE_MASK_PART, _(u"Remove parts")) | 736 | self.remove_mask_part_menu = mask_menu.Append(const.ID_REMOVE_MASK_PART, _(u"Remove parts")) |
734 | self.remove_mask_part_menu.Enable(False) | 737 | self.remove_mask_part_menu.Enable(False) |
735 | 738 | ||
739 | + self.select_mask_part_menu = mask_menu.Append(const.ID_SELECT_MASK_PART, _(u"Select parts")) | ||
740 | + self.select_mask_part_menu.Enable(False) | ||
741 | + | ||
736 | tools_menu.AppendMenu(-1, _(u"Mask"), mask_menu) | 742 | tools_menu.AppendMenu(-1, _(u"Mask"), mask_menu) |
737 | 743 | ||
738 | # Image menu | 744 | # Image menu |
invesalius/gui/task_slice.py
@@ -562,10 +562,10 @@ class MaskProperties(wx.Panel): | @@ -562,10 +562,10 @@ class MaskProperties(wx.Panel): | ||
562 | mask_thresh = evt_pubsub.data[2] | 562 | mask_thresh = evt_pubsub.data[2] |
563 | mask_colour = [int(c*255) for c in evt_pubsub.data[3]] | 563 | mask_colour = [int(c*255) for c in evt_pubsub.data[3]] |
564 | index = self.combo_mask_name.Append(mask_name) | 564 | index = self.combo_mask_name.Append(mask_name) |
565 | - self.combo_mask_name.SetSelection(index) | ||
566 | - self.button_colour.SetColour(mask_colour) | ||
567 | - self.gradient.SetColour(mask_colour) | ||
568 | - self.combo_mask_name.SetSelection(index) | 565 | + # self.combo_mask_name.SetSelection(index) |
566 | + # self.button_colour.SetColour(mask_colour) | ||
567 | + # self.gradient.SetColour(mask_colour) | ||
568 | + # self.combo_mask_name.SetSelection(index) | ||
569 | 569 | ||
570 | def GetMaskSelected(self): | 570 | def GetMaskSelected(self): |
571 | x = self.combo_mask_name.GetSelection() | 571 | x = self.combo_mask_name.GetSelection() |