Commit bf2722383d09caf040fd9becd1bab29662a6ab97

Authored by Thiago Franco de Moraes
1 parent 9a0892f4
Exists in watershed

Applying threshold before starting watershed mode

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