Commit 595672743516cd0b26e1df1e9ccc4496323e8587

Authored by tatiana
1 parent 159f813a

ADD: Edit mask pixel tool (erase) in default mode - not working

.gitattributes
... ... @@ -73,6 +73,7 @@ invesalius/.svnignore -text
73 73 invesalius/constants.py -text
74 74 invesalius/control.py -text
75 75 invesalius/data/__init__.py -text
  76 +invesalius/data/cursor_actors.py -text
76 77 invesalius/data/editor.py -text
77 78 invesalius/data/imagedata_utils.py -text
78 79 invesalius/data/mask.py -text
... ...
invesalius/data/cursor_actors.py 0 → 100644
... ... @@ -0,0 +1,111 @@
  1 +from math import *
  2 +
  3 +import vtk
  4 +
  5 +class CursorCircle:
  6 + # TODO: Think and try to change this class to an actor
  7 + # CursorCircleActor(vtk.vtkActor)
  8 +
  9 + def __init__(self):
  10 +
  11 + self.colour = (0.0, 0.0, 1.0)
  12 + self.opacity = 1
  13 + self.radius = 20
  14 + self.position = (0 ,0, 1)
  15 + self.points = []
  16 + self.orientation = "AXIAL"
  17 +
  18 + self.mapper = vtk.vtkPolyDataMapper()
  19 + self.disk = vtk.vtkDiskSource()
  20 + self.actor = vtk.vtkActor()
  21 +
  22 + self.__build_actor()
  23 + self.__calculate_area_pixels()
  24 +
  25 + def __build_actor(self):
  26 + """
  27 + Function to plot the circle
  28 + """
  29 +
  30 + disk = self.disk
  31 + disk.SetInnerRadius(self.radius)
  32 + disk.SetOuterRadius(0) # filled
  33 + disk.SetRadialResolution(50)
  34 + disk.SetCircumferentialResolution(50)
  35 +
  36 + mapper = self.mapper
  37 + mapper.SetInput(disk.GetOutput())
  38 +
  39 + actor = self.actor
  40 + actor.SetMapper(mapper)
  41 + actor.GetProperty().SetOpacity(self.opacity)
  42 + actor.GetProperty().SetColor(self.colour)
  43 + actor.SetPosition(self.position)
  44 + actor.SetVisibility(1)
  45 + actor.PickableOff()
  46 +
  47 + def __calculate_area_pixels(self):
  48 + """
  49 + Return the cursor's pixels.
  50 + This method scans the circle line by line.
  51 + Extracted equation.
  52 + http://www.mathopenref.com/chord.html
  53 + """
  54 + xc = 0
  55 + yc = 0
  56 + z = 0
  57 + self.pixel_list = []
  58 + radius = int(self.radius)
  59 + for i in xrange(int(yc - radius), int(yc + radius)):
  60 + # distance from the line to the circle's center
  61 + d = yc - i
  62 + # line size
  63 + line = sqrt(round(radius ** 2) - round(d ** 2)) * 2
  64 + # line initial x
  65 + xi = int(xc - line/2)
  66 + # line final
  67 + xf = int(line/2 + xc)
  68 + yi = i
  69 + for k in xrange(xi,xf):
  70 + self.pixel_list.append((k, yi))
  71 +
  72 + def SetSize(self, radius):
  73 + self.radius = radius
  74 + disk.SetInnerRadius(radius)
  75 + self.__calculate_area_pixels()
  76 +
  77 + def SetColour(self, colour):
  78 + self.actor.GetProperty().SetColor(self.colour)
  79 +
  80 + def SetOrientation(self, orientation):
  81 + self.orientation = orientation
  82 +
  83 + if orientation == "CORONAL":
  84 + self.actor.RotateX(90)
  85 +
  86 + if orientation == "SAGITAL":
  87 + self.actor.RotateY(90)
  88 +
  89 + def SetPosition(self, position):
  90 +
  91 + #if self.orientation == "AXIAL":
  92 + # z = 1
  93 + #elif self.orientation == "CORONAL":
  94 + # y = 1
  95 + #elif self.orientation == "SAGITAL":
  96 + # x = 1
  97 + self.position = position
  98 + self.actor.SetPosition(position)
  99 +
  100 + def GetPixels(self):
  101 + px, py, pz = self.position
  102 + orient = self.orientation
  103 + for pixel_0,pixel_1 in self.pixel_list:
  104 + # The position of the pixels in this list is relative (based only on
  105 + # the area, and not the cursor position).
  106 + # Let's calculate the absolute position
  107 + # TODO: Optimize this!!!!
  108 + absolute_pixel = {"AXIAL": (px + pixel_0, py + pixel_1, pz),
  109 + "CORONAL": (px + pixel_0, py, pz + pixel_1),
  110 + "SAGITAL": (px, py + pixel_0, pz + pixel_1)}
  111 + yield absolute_pixel[orient]
... ...
invesalius/data/slice_.py
... ... @@ -33,6 +33,12 @@ class Slice(object):
33 33 'Update cursor position in slice')
34 34 ps.Publisher().subscribe(self.ShowMask, 'Show mask')
35 35 ps.Publisher().subscribe(self.ChangeMaskName, 'Change mask name')
  36 + ps.Publisher().subscribe(self.EraseMaskPixel, 'Erase mask pixel')
  37 +
  38 +
  39 + def EraseMaskPixel(self, pubsub_evt):
  40 + position = pubsub_evt.data
  41 + self.ErasePixel(position)
36 42  
37 43 def ChangeMaskName(self, pubsub_evt):
38 44 index, name = pubsub_evt.data
... ... @@ -118,6 +124,8 @@ class Slice(object):
118 124 ps.Publisher().sendMessage('Select mask name in combo', mask_index)
119 125 ps.Publisher().sendMessage('Update slice viewer')
120 126  
  127 +
  128 +
121 129 def ChangeCurrentMaskColour(self, colour, update=True):
122 130 # This is necessary because wx events are calling this before it was created
123 131 if self.current_mask:
... ... @@ -358,27 +366,31 @@ class Slice(object):
358 366 # thresh_min, thresh_max = evt.data
359 367 # self.current_mask.edition_threshold_range = thresh_min, thresh_max
360 368  
361   - def ErasePixel(self, x, y, z):
  369 + def ErasePixel(self, position):
362 370 """
363 371 Delete pixel, based on x, y and z position coordinates.
364 372 """
365   - colour = imagedata.GetScalarRange()[0]
366   - self.imagedata.SetScalarComponentFromDouble(x, y, z, 0, colour)
367   - self.imagedata.Update()
  373 + x, y, z = position
  374 + imagedata = self.current_mask.imagedata
  375 + colour = imagedata.GetScalarRange()[0]# - 1 # Important to effect erase
  376 + imagedata.SetScalarComponentFromDouble(x, y, z, 0, colour)
  377 + imagedata.Update()
368 378  
369 379 def DrawPixel(self, x, y, z, colour=None):
370 380 """
371 381 Draw pixel, based on x, y and z position coordinates.
372 382 """
  383 + imagedata = self.current_mask.imagedata
373 384 if colour is None:
374 385 colour = imagedata.GetScalarRange()[1]
375   - self.imagedata.SetScalarComponentFromDouble(x, y, z, 0, colour)
  386 + imagedata.SetScalarComponentFromDouble(x, y, z, 0, colour)
376 387  
377 388 def EditPixelBasedOnThreshold(self, x, y, z):
378 389 """
379 390 Erase or draw pixel based on edition threshold range.
380 391 """
381   - pixel_colour = self.imagedata.GetScalarComponentAsDouble(x, y, z, 0)
  392 +
  393 + pixel_colour = imagedata.GetScalarComponentAsDouble(x, y, z, 0)
382 394 thresh_min, thresh_max = self.current_mask.edition_threshold_range
383 395  
384 396 if (pixel_colour >= thresh_min) and (pixel_colour <= thresh_max):
... ...
invesalius/data/viewer_slice.py
... ... @@ -26,6 +26,7 @@ import wx.lib.pubsub as ps
26 26 import data.slice_ as sl
27 27 import constants as const
28 28 import project
  29 +import cursor_actors as ca
29 30  
30 31 class Viewer(wx.Panel):
31 32  
... ... @@ -36,7 +37,7 @@ class Viewer(wx.Panel):
36 37 self.SetBackgroundColour(colour)
37 38  
38 39 # Interactor aditional style
39   - self.modes = []
  40 + self.modes = ['DEFAULT']
40 41 self.mouse_pressed = 0
41 42  
42 43 self.__init_gui()
... ... @@ -83,7 +84,7 @@ class Viewer(wx.Panel):
83 84 self.cam = ren.GetActiveCamera()
84 85 self.ren = ren
85 86  
86   - self.AppendMode('DEFAULT')
  87 + self.AppendMode('EDITOR')
87 88  
88 89 def AppendMode(self, mode):
89 90  
... ... @@ -115,6 +116,19 @@ class Viewer(wx.Panel):
115 116 # Bind event
116 117 style.AddObserver(event,
117 118 action[mode][event])
  119 +
  120 + # Insert cursor
  121 + cursor = ca.CursorCircle()
  122 + cursor.SetOrientation(self.orientation)
  123 + coordinates = {"SAGITAL": [self.slice_number, 0, 0],
  124 + "CORONAL": [0, self.slice_number, 0],
  125 + "AXIAL": [0, 0, self.slice_number]}
  126 + cursor.SetPosition(coordinates[self.orientation])
  127 + self.ren.AddActor(cursor.actor)
  128 + self.ren.Render()
  129 +
  130 + self.cursor = cursor
  131 +
118 132  
119 133  
120 134 def OnMouseClick(self, obj, evt_vtk):
... ... @@ -129,9 +143,15 @@ class Viewer(wx.Panel):
129 143 print "Edit pixel region based on origin:", coord
130 144  
131 145 def OnBrushMove(self, obj, evt_vtk):
132   - coord = self.GetCoordinate()
  146 + coord = self.GetCoordinateCursor()
  147 + self.cursor.SetPosition(coord)
  148 + self.ren.Render()
133 149 if self.mouse_pressed:
134 150 print "Edit pixel region based on origin:", coord
  151 + pixels = self.cursor.GetPixels()
  152 + for coord in pixels:
  153 + ps.Publisher().sendMessage('Erase mask pixel', coord)
  154 + self.interactor.Render()
135 155  
136 156 def OnCrossMove(self, obj, evt_vtk):
137 157 coord = self.GetCoordinate()
... ... @@ -187,6 +207,48 @@ class Viewer(wx.Panel):
187 207 #print "New coordinate: ", coord
188 208  
189 209 return coord
  210 +
  211 +
  212 + def GetCoordinateCursor(self):
  213 +
  214 + # Find position
  215 + mouse_x, mouse_y = self.interactor.GetEventPosition()
  216 + self.pick.Pick(mouse_x, mouse_y, 0, self.ren)
  217 + x, y, z = self.pick.GetPickPosition()
  218 +
  219 + # First we fix the position origin, based on vtkActor bounds
  220 + bounds = self.actor.GetBounds()
  221 + bound_xi, bound_xf, bound_yi, bound_yf, bound_zi, bound_zf = bounds
  222 + x = float(x - bound_xi)
  223 + y = float(y - bound_yi)
  224 + z = float(z - bound_zi)
  225 +
  226 + # Then we fix the porpotion, based on vtkImageData spacing
  227 + #spacing_x, spacing_y, spacing_z = self.imagedata.GetSpacing()
  228 + #x = x/spacing_x
  229 + #y = y/spacing_y
  230 + #z = z/spacing_z
  231 +
  232 + # Based on the current orientation, we define 3D position
  233 + coordinates = {"SAGITAL": [self.slice_number, y, z],
  234 + "CORONAL": [x, self.slice_number, z],
  235 + "AXIAL": [x, y, self.slice_number]}
  236 + coord = [int(coord) for coord in coordinates[self.orientation]]
  237 +
  238 + # According to vtkImageData extent, we limit min and max value
  239 + # If this is not done, a VTK Error occurs when mouse is pressed outside
  240 + # vtkImageData extent
  241 + #extent = self.imagedata.GetWholeExtent()
  242 + #extent_min = extent[0], extent[2], extent[4]
  243 + #extent_max = extent[1], extent[3], extent[5]
  244 + #for index in xrange(3):
  245 + # if coord[index] > extent_max[index]:
  246 + # coord[index] = extent_max[index]
  247 + # elif coord[index] < extent_min[index]:
  248 + # coord[index] = extent_min[index]
  249 + #print "New coordinate: ", coord
  250 +
  251 + return coord
190 252  
191 253 def __bind_events(self):
192 254 ps.Publisher().subscribe(self.LoadImagedata, 'Load slice to viewer')
... ...