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