Commit bf2722383d09caf040fd9becd1bab29662a6ab97
1 parent
9a0892f4
Exists in
watershed
Applying threshold before starting watershed mode
Showing
4 changed files
with
225 additions
and
10 deletions
Show diff stats
invesalius/data/slice_.py
| ... | ... | @@ -107,6 +107,7 @@ class Slice(object): |
| 107 | 107 | |
| 108 | 108 | self.from_ = OTHER |
| 109 | 109 | self.__bind_events() |
| 110 | + self.opacity = 0.8 | |
| 110 | 111 | self.qblend = {'AXIAL': {}, |
| 111 | 112 | 'CORONAL': {}, |
| 112 | 113 | 'SAGITAL': {}} |
| ... | ... | @@ -1181,6 +1182,19 @@ class Slice(object): |
| 1181 | 1182 | m[mask == 254] = 254 |
| 1182 | 1183 | return m.astype('uint8') |
| 1183 | 1184 | |
| 1185 | + def do_threshold_to_all_slices(self): | |
| 1186 | + mask = self.current_mask | |
| 1187 | + | |
| 1188 | + # This is very important. Do not use masks' imagedata. It would mess up | |
| 1189 | + # surface quality event when using contour | |
| 1190 | + #self.SetMaskThreshold(mask.index, threshold) | |
| 1191 | + for n in xrange(1, mask.matrix.shape[0]): | |
| 1192 | + if mask.matrix[n, 0, 0] == 0: | |
| 1193 | + m = mask.matrix[n, 1:, 1:] | |
| 1194 | + mask.matrix[n, 1:, 1:] = self.do_threshold_to_a_slice(self.matrix[n-1], m) | |
| 1195 | + | |
| 1196 | + mask.matrix.flush() | |
| 1197 | + | |
| 1184 | 1198 | def do_colour_image(self, imagedata): |
| 1185 | 1199 | if self.from_ in (PLIST, WIDGET): |
| 1186 | 1200 | return imagedata |
| ... | ... | @@ -1216,8 +1230,10 @@ class Slice(object): |
| 1216 | 1230 | lut_mask.SetNumberOfTableValues(256) |
| 1217 | 1231 | lut_mask.SetTableValue(0, 0, 0, 0, 0.0) |
| 1218 | 1232 | lut_mask.SetTableValue(1, 0, 0, 0, 0.0) |
| 1219 | - lut_mask.SetTableValue(254, r, g, b, 1.0) | |
| 1220 | - lut_mask.SetTableValue(255, r, g, b, 1.0) | |
| 1233 | + lut_mask.SetTableValue(2, 0, 0, 0, 0.0) | |
| 1234 | + lut_mask.SetTableValue(253, r, g, b, self.opacity) | |
| 1235 | + lut_mask.SetTableValue(254, r, g, b, self.opacity) | |
| 1236 | + lut_mask.SetTableValue(255, r, g, b, self.opacity) | |
| 1221 | 1237 | lut_mask.SetRampToLinear() |
| 1222 | 1238 | lut_mask.Build() |
| 1223 | 1239 | # self.lut_mask = lut_mask | ... | ... |
invesalius/data/styles.py
| ... | ... | @@ -674,11 +674,17 @@ class WaterShedInteractorStyle(DefaultInteractorStyle): |
| 674 | 674 | self.AddObserver("EnterEvent", self.OnEnterInteractor) |
| 675 | 675 | self.AddObserver("LeaveEvent", self.OnLeaveInteractor) |
| 676 | 676 | |
| 677 | + self.RemoveObservers("MouseWheelForwardEvent") | |
| 678 | + self.RemoveObservers("MouseWheelBackwardEvent") | |
| 679 | + self.AddObserver("MouseWheelForwardEvent",self.WOnScrollForward) | |
| 680 | + self.AddObserver("MouseWheelBackwardEvent", self.WOnScrollBackward) | |
| 681 | + | |
| 677 | 682 | self.AddObserver("LeftButtonPressEvent", self.OnBrushClick) |
| 678 | 683 | self.AddObserver("LeftButtonReleaseEvent", self.OnBrushRelease) |
| 679 | 684 | self.AddObserver("MouseMoveEvent", self.OnBrushMove) |
| 680 | 685 | |
| 681 | 686 | def SetUp(self): |
| 687 | + self.viewer.slice_.do_threshold_to_all_slices() | |
| 682 | 688 | mask = self.viewer.slice_.current_mask.matrix |
| 683 | 689 | mask[0] = 1 |
| 684 | 690 | mask[:, 0, :] = 1 |
| ... | ... | @@ -687,6 +693,7 @@ class WaterShedInteractorStyle(DefaultInteractorStyle): |
| 687 | 693 | |
| 688 | 694 | def CleanUp(self): |
| 689 | 695 | self._remove_mask() |
| 696 | + self.viewer.slice_.qblend[self.orientation] = {} | |
| 690 | 697 | |
| 691 | 698 | def _create_mask(self): |
| 692 | 699 | if self.matrix is None: |
| ... | ... | @@ -711,6 +718,35 @@ class WaterShedInteractorStyle(DefaultInteractorStyle): |
| 711 | 718 | self.viewer.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) |
| 712 | 719 | self.viewer.interactor.Render() |
| 713 | 720 | |
| 721 | + def WOnScrollBackward(self, obj, evt): | |
| 722 | + viewer = self.viewer | |
| 723 | + iren = viewer.interactor | |
| 724 | + if iren.GetControlKey(): | |
| 725 | + if viewer.slice_.opacity > 0: | |
| 726 | + viewer.slice_.opacity -= 0.1 | |
| 727 | + self.viewer.slice_.buffer_slices['AXIAL'].discard_vtk_mask() | |
| 728 | + self.viewer.slice_.buffer_slices['CORONAL'].discard_vtk_mask() | |
| 729 | + self.viewer.slice_.buffer_slices['SAGITAL'].discard_vtk_mask() | |
| 730 | + viewer.OnScrollBar() | |
| 731 | + else: | |
| 732 | + self.OnScrollBackward(obj, evt) | |
| 733 | + | |
| 734 | + | |
| 735 | + def WOnScrollForward(self, obj, evt): | |
| 736 | + viewer = self.viewer | |
| 737 | + iren = viewer.interactor | |
| 738 | + print "AUIQ" | |
| 739 | + if iren.GetControlKey(): | |
| 740 | + if viewer.slice_.opacity < 1: | |
| 741 | + viewer.slice_.opacity += 0.1 | |
| 742 | + self.viewer.slice_.buffer_slices['AXIAL'].discard_vtk_mask() | |
| 743 | + self.viewer.slice_.buffer_slices['CORONAL'].discard_vtk_mask() | |
| 744 | + self.viewer.slice_.buffer_slices['SAGITAL'].discard_vtk_mask() | |
| 745 | + viewer.OnScrollBar() | |
| 746 | + else: | |
| 747 | + self.OnScrollForward(obj, evt) | |
| 748 | + | |
| 749 | + | |
| 714 | 750 | def OnBrushClick(self, obj, evt): |
| 715 | 751 | if (self.viewer.slice_.buffer_slices[self.orientation].mask is None): |
| 716 | 752 | return |
| ... | ... | @@ -873,11 +909,17 @@ class WaterShedInteractorStyle(DefaultInteractorStyle): |
| 873 | 909 | wl = self.viewer.slice_.window_level |
| 874 | 910 | |
| 875 | 911 | #tmp_image = get_LUT_value(image, ww, wl).astype('uint16') |
| 876 | - tmp_image = ndimage.morphological_gradient((image - image.min()).astype('uint16'), 5) | |
| 912 | + tmp_image = ndimage.morphological_gradient(get_LUT_value(image, ww, wl).astype('uint16'), 5) | |
| 877 | 913 | print tmp_image.dtype, tmp_image.min(), tmp_image.max() |
| 878 | 914 | tmp_mask = watershed(tmp_image, markers) |
| 879 | - mask[:] = 0 | |
| 880 | - mask[tmp_mask == 1] = 255 | |
| 915 | + | |
| 916 | + if self.viewer.overwrite_mask: | |
| 917 | + mask[:] = 0 | |
| 918 | + mask[tmp_mask == 1] = 253 | |
| 919 | + else: | |
| 920 | + mask[(tmp_mask==2) & ((mask == 0) | (mask == 2) | (mask == 253))] = 2 | |
| 921 | + mask[(tmp_mask==1) & ((mask == 0) | (mask == 2) | (mask == 253))] = 253 | |
| 922 | + | |
| 881 | 923 | self.viewer._flush_buffer = True |
| 882 | 924 | self.viewer.OnScrollBar(update3D=False) |
| 883 | 925 | |
| ... | ... | @@ -942,14 +984,14 @@ class WaterShedInteractorStyle(DefaultInteractorStyle): |
| 942 | 984 | index = index[abs(yi):,:] |
| 943 | 985 | yi = 0 |
| 944 | 986 | if yf > mask.shape[0]: |
| 945 | - index = index[:index.shape[0]-(yf-image.shape[0]), :] | |
| 987 | + index = index[:index.shape[0]-(yf-mask.shape[0]), :] | |
| 946 | 988 | yf = mask.shape[0] |
| 947 | 989 | |
| 948 | 990 | if xi < 0: |
| 949 | 991 | index = index[:,abs(xi):] |
| 950 | 992 | xi = 0 |
| 951 | 993 | if xf > mask.shape[1]: |
| 952 | - index = index[:,:index.shape[1]-(xf-image.shape[1])] | |
| 994 | + index = index[:,:index.shape[1]-(xf-mask.shape[1])] | |
| 953 | 995 | xf = mask.shape[1] |
| 954 | 996 | |
| 955 | 997 | # Verifying if the points is over the image array. | ... | ... |
invesalius/data/viewer_slice.py
| ... | ... | @@ -166,6 +166,8 @@ class Viewer(wx.Panel): |
| 166 | 166 | self.last_position_mouse_move = () |
| 167 | 167 | self.state = const.STATE_DEFAULT |
| 168 | 168 | |
| 169 | + self.overwrite_mask = False | |
| 170 | + | |
| 169 | 171 | # All renderers and image actors in this viewer |
| 170 | 172 | self.slice_data_list = [] |
| 171 | 173 | self.slice_data = None |
| ... | ... | @@ -751,6 +753,8 @@ class Viewer(wx.Panel): |
| 751 | 753 | Publisher.subscribe(self.OnSetMIPInvert, 'Set MIP Invert %s' % self.orientation) |
| 752 | 754 | Publisher.subscribe(self.OnShowMIPInterface, 'Show MIP interface') |
| 753 | 755 | |
| 756 | + Publisher.subscribe(self.OnSetOverwriteMask, "Set overwrite mask") | |
| 757 | + | |
| 754 | 758 | def SetDefaultCursor(self, pusub_evt): |
| 755 | 759 | self.interactor.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) |
| 756 | 760 | |
| ... | ... | @@ -1247,7 +1251,10 @@ class Viewer(wx.Panel): |
| 1247 | 1251 | self.mip_ctrls.Hide() |
| 1248 | 1252 | self.GetSizer().Remove(self.mip_ctrls) |
| 1249 | 1253 | self.Layout() |
| 1250 | - | |
| 1254 | + | |
| 1255 | + def OnSetOverwriteMask(self, pubsub_evt): | |
| 1256 | + value = pubsub_evt.data | |
| 1257 | + self.overwrite_mask = value | |
| 1251 | 1258 | |
| 1252 | 1259 | def set_slice_number(self, index): |
| 1253 | 1260 | inverted = self.mip_ctrls.inverted.GetValue() | ... | ... |
invesalius/gui/task_slice.py
| ... | ... | @@ -228,7 +228,7 @@ class InnerFoldPanel(wx.Panel): |
| 228 | 228 | # parent panel. Perhaps we need to insert the item into the sizer also... |
| 229 | 229 | # Study this. |
| 230 | 230 | fold_panel = fpb.FoldPanelBar(self, -1, wx.DefaultPosition, |
| 231 | - (10, 190), 0,fpb.FPB_SINGLE_FOLD) | |
| 231 | + (10, 220), 0,fpb.FPB_SINGLE_FOLD) | |
| 232 | 232 | |
| 233 | 233 | # Fold panel style |
| 234 | 234 | style = fpb.CaptionBarStyle() |
| ... | ... | @@ -706,6 +706,156 @@ class EditionTools(wx.Panel): |
| 706 | 706 | |
| 707 | 707 | |
| 708 | 708 | class WatershedTool(EditionTools): |
| 709 | - pass | |
| 709 | + def __init__(self, parent): | |
| 710 | + wx.Panel.__init__(self, parent, size=(50,240)) | |
| 711 | + default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR) | |
| 712 | + self.SetBackgroundColour(default_colour) | |
| 713 | + | |
| 714 | + ## LINE 1 | |
| 715 | + text1 = wx.StaticText(self, -1, _("Choose brush type, size or operation:")) | |
| 716 | + | |
| 717 | + ## LINE 2 | |
| 718 | + menu = wx.Menu() | |
| 719 | + | |
| 720 | + CIRCLE_BMP = wx.Bitmap("../icons/brush_circle.jpg", wx.BITMAP_TYPE_JPEG) | |
| 721 | + item = wx.MenuItem(menu, MENU_BRUSH_CIRCLE, _("Circle")) | |
| 722 | + item.SetBitmap(CIRCLE_BMP) | |
| 723 | + | |
| 724 | + SQUARE_BMP = wx.Bitmap("../icons/brush_square.jpg", wx.BITMAP_TYPE_JPEG) | |
| 725 | + item2 = wx.MenuItem(menu, MENU_BRUSH_SQUARE, _("Square")) | |
| 726 | + item2.SetBitmap(SQUARE_BMP) | |
| 727 | + | |
| 728 | + menu.AppendItem(item) | |
| 729 | + menu.AppendItem(item2) | |
| 730 | + | |
| 731 | + bmp_brush_format = {const.BRUSH_CIRCLE: CIRCLE_BMP, | |
| 732 | + const.BRUSH_SQUARE: SQUARE_BMP} | |
| 733 | + selected_bmp = bmp_brush_format[const.DEFAULT_BRUSH_FORMAT] | |
| 734 | + | |
| 735 | + btn_brush_format = pbtn.PlateButton(self, wx.ID_ANY,"", selected_bmp, | |
| 736 | + style=pbtn.PB_STYLE_SQUARE) | |
| 737 | + btn_brush_format.SetMenu(menu) | |
| 738 | + self.btn_brush_format = btn_brush_format | |
| 739 | + | |
| 740 | + spin_brush_size = wx.SpinCtrl(self, -1, "", (20, 50)) | |
| 741 | + spin_brush_size.SetRange(1,100) | |
| 742 | + spin_brush_size.SetValue(const.BRUSH_SIZE) | |
| 743 | + spin_brush_size.Bind(wx.EVT_TEXT, self.OnBrushSize) | |
| 744 | + self.spin = spin_brush_size | |
| 745 | + | |
| 746 | + combo_brush_op = wx.ComboBox(self, -1, "", size=(15,-1), | |
| 747 | + choices = const.BRUSH_OP_NAME, | |
| 748 | + style = wx.CB_DROPDOWN|wx.CB_READONLY) | |
| 749 | + combo_brush_op.SetSelection(const.DEFAULT_BRUSH_OP) | |
| 750 | + if sys.platform != 'win32': | |
| 751 | + combo_brush_op.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) | |
| 752 | + self.combo_brush_op = combo_brush_op | |
| 753 | + | |
| 754 | + # Sizer which represents the second line | |
| 755 | + line2 = wx.BoxSizer(wx.HORIZONTAL) | |
| 756 | + line2.Add(btn_brush_format, 0, wx.EXPAND|wx.GROW|wx.TOP|wx.RIGHT, 0) | |
| 757 | + line2.Add(spin_brush_size, 0, wx.RIGHT, 5) | |
| 758 | + line2.Add(combo_brush_op, 1, wx.EXPAND|wx.TOP|wx.RIGHT|wx.LEFT, 5) | |
| 759 | + | |
| 760 | + ## LINE 3 | |
| 761 | + text_thresh = wx.StaticText(self, -1, _("Brush threshold range:")) | |
| 762 | + | |
| 763 | + ## LINE 4 | |
| 764 | + gradient_thresh = grad.GradientCtrl(self, -1, 0, 5000, 0, 5000, | |
| 765 | + (0, 0, 255, 100)) | |
| 766 | + self.gradient_thresh = gradient_thresh | |
| 767 | + self.bind_evt_gradient = True | |
| 768 | + | |
| 769 | + # LINE 5 | |
| 770 | + check_box = wx.CheckBox(self, -1, _("Overwrite mask")) | |
| 771 | + self.check_box = check_box | |
| 772 | + | |
| 773 | + # Add lines into main sizer | |
| 774 | + sizer = wx.BoxSizer(wx.VERTICAL) | |
| 775 | + sizer.Add(text1, 0, wx.GROW|wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, 5) | |
| 776 | + sizer.Add(line2, 0, wx.GROW|wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, 5) | |
| 777 | + sizer.Add(text_thresh, 0, wx.GROW|wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, 5) | |
| 778 | + sizer.Add(gradient_thresh, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT| | |
| 779 | + wx.BOTTOM, 6) | |
| 780 | + sizer.Add(check_box, 0, wx.GROW|wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, 5) | |
| 781 | + sizer.Fit(self) | |
| 782 | + | |
| 783 | + self.SetSizer(sizer) | |
| 784 | + self.Update() | |
| 785 | + self.SetAutoLayout(1) | |
| 786 | + | |
| 787 | + self.__bind_events() | |
| 788 | + self.__bind_events_wx() | |
| 789 | + | |
| 790 | + | |
| 791 | + def __bind_events_wx(self): | |
| 792 | + self.Bind(wx.EVT_MENU, self.OnMenu) | |
| 793 | + self.Bind(grad.EVT_THRESHOLD_CHANGED, self.OnGradientChanged, | |
| 794 | + self.gradient_thresh) | |
| 795 | + self.combo_brush_op.Bind(wx.EVT_COMBOBOX, self.OnComboBrushOp) | |
| 796 | + self.check_box.Bind(wx.EVT_CHECKBOX, self.OnCheckOverwriteMask) | |
| 797 | + | |
| 798 | + def __bind_events(self): | |
| 799 | + Publisher.subscribe(self.SetThresholdBounds, | |
| 800 | + 'Update threshold limits') | |
| 801 | + Publisher.subscribe(self.ChangeMaskColour, 'Change mask colour') | |
| 802 | + Publisher.subscribe(self.SetGradientColour, 'Add mask') | |
| 803 | + | |
| 804 | + def ChangeMaskColour(self, pubsub_evt): | |
| 805 | + colour = pubsub_evt.data | |
| 806 | + self.gradient_thresh.SetColour(colour) | |
| 807 | + | |
| 808 | + def SetGradientColour(self, pubsub_evt): | |
| 809 | + vtk_colour = pubsub_evt.data[3] | |
| 810 | + wx_colour = [c*255 for c in vtk_colour] | |
| 811 | + self.gradient_thresh.SetColour(wx_colour) | |
| 812 | + | |
| 813 | + def SetThresholdValues(self, pubsub_evt): | |
| 814 | + thresh_min, thresh_max = pubsub_evt.data | |
| 815 | + self.bind_evt_gradient = False | |
| 816 | + self.gradient_thresh.SetMinValue(thresh_min) | |
| 817 | + self.gradient_thresh.SetMaxValue(thresh_max) | |
| 818 | + self.bind_evt_gradient = True | |
| 819 | + | |
| 820 | + def SetThresholdBounds(self, pubsub_evt): | |
| 821 | + thresh_min = pubsub_evt.data[0] | |
| 822 | + thresh_max = pubsub_evt.data[1] | |
| 823 | + self.gradient_thresh.SetMinRange(thresh_min) | |
| 824 | + self.gradient_thresh.SetMaxRange(thresh_max) | |
| 825 | + self.gradient_thresh.SetMinValue(thresh_min) | |
| 826 | + self.gradient_thresh.SetMaxValue(thresh_max) | |
| 827 | + | |
| 828 | + def OnGradientChanged(self, evt): | |
| 829 | + thresh_min = self.gradient_thresh.GetMinValue() | |
| 830 | + thresh_max = self.gradient_thresh.GetMaxValue() | |
| 831 | + if self.bind_evt_gradient: | |
| 832 | + Publisher.sendMessage('Set edition threshold values', | |
| 833 | + (thresh_min, thresh_max)) | |
| 834 | + | |
| 835 | + def OnMenu(self, evt): | |
| 836 | + SQUARE_BMP = wx.Bitmap("../icons/brush_square.jpg", wx.BITMAP_TYPE_JPEG) | |
| 837 | + CIRCLE_BMP = wx.Bitmap("../icons/brush_circle.jpg", wx.BITMAP_TYPE_JPEG) | |
| 838 | + | |
| 839 | + brush = {MENU_BRUSH_CIRCLE: const.BRUSH_CIRCLE, | |
| 840 | + MENU_BRUSH_SQUARE: const.BRUSH_SQUARE} | |
| 841 | + bitmap = {MENU_BRUSH_CIRCLE: CIRCLE_BMP, | |
| 842 | + MENU_BRUSH_SQUARE: SQUARE_BMP} | |
| 843 | + | |
| 844 | + self.btn_brush_format.SetBitmap(bitmap[evt.GetId()]) | |
| 845 | + | |
| 846 | + Publisher.sendMessage('Set brush format', brush[evt.GetId()]) | |
| 847 | + | |
| 848 | + def OnBrushSize(self, evt): | |
| 849 | + """ """ | |
| 850 | + # FIXME: Using wx.EVT_SPINCTRL in MacOS it doesnt capture changes only | |
| 851 | + # in the text ctrl - so we are capturing only changes on text | |
| 852 | + # Strangelly this is being called twice | |
| 853 | + Publisher.sendMessage('Set edition brush size',self.spin.GetValue()) | |
| 710 | 854 | |
| 855 | + def OnComboBrushOp(self, evt): | |
| 856 | + brush_op_id = evt.GetSelection() | |
| 857 | + Publisher.sendMessage('Set edition operation', brush_op_id) | |
| 711 | 858 | |
| 859 | + def OnCheckOverwriteMask(self, evt): | |
| 860 | + value = self.check_box.GetValue() | |
| 861 | + Publisher.sendMessage('Set overwrite mask', value) | ... | ... |