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,6 +73,7 @@ invesalius/.svnignore -text
73 invesalius/constants.py -text 73 invesalius/constants.py -text
74 invesalius/control.py -text 74 invesalius/control.py -text
75 invesalius/data/__init__.py -text 75 invesalius/data/__init__.py -text
  76 +invesalius/data/cursor_actors.py -text
76 invesalius/data/editor.py -text 77 invesalius/data/editor.py -text
77 invesalius/data/imagedata_utils.py -text 78 invesalius/data/imagedata_utils.py -text
78 invesalius/data/mask.py -text 79 invesalius/data/mask.py -text
invesalius/data/cursor_actors.py 0 → 100644
@@ -0,0 +1,111 @@ @@ -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,6 +33,12 @@ class Slice(object):
33 'Update cursor position in slice') 33 'Update cursor position in slice')
34 ps.Publisher().subscribe(self.ShowMask, 'Show mask') 34 ps.Publisher().subscribe(self.ShowMask, 'Show mask')
35 ps.Publisher().subscribe(self.ChangeMaskName, 'Change mask name') 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 def ChangeMaskName(self, pubsub_evt): 43 def ChangeMaskName(self, pubsub_evt):
38 index, name = pubsub_evt.data 44 index, name = pubsub_evt.data
@@ -118,6 +124,8 @@ class Slice(object): @@ -118,6 +124,8 @@ class Slice(object):
118 ps.Publisher().sendMessage('Select mask name in combo', mask_index) 124 ps.Publisher().sendMessage('Select mask name in combo', mask_index)
119 ps.Publisher().sendMessage('Update slice viewer') 125 ps.Publisher().sendMessage('Update slice viewer')
120 126
  127 +
  128 +
121 def ChangeCurrentMaskColour(self, colour, update=True): 129 def ChangeCurrentMaskColour(self, colour, update=True):
122 # This is necessary because wx events are calling this before it was created 130 # This is necessary because wx events are calling this before it was created
123 if self.current_mask: 131 if self.current_mask:
@@ -358,27 +366,31 @@ class Slice(object): @@ -358,27 +366,31 @@ class Slice(object):
358 # thresh_min, thresh_max = evt.data 366 # thresh_min, thresh_max = evt.data
359 # self.current_mask.edition_threshold_range = thresh_min, thresh_max 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 Delete pixel, based on x, y and z position coordinates. 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 def DrawPixel(self, x, y, z, colour=None): 379 def DrawPixel(self, x, y, z, colour=None):
370 """ 380 """
371 Draw pixel, based on x, y and z position coordinates. 381 Draw pixel, based on x, y and z position coordinates.
372 """ 382 """
  383 + imagedata = self.current_mask.imagedata
373 if colour is None: 384 if colour is None:
374 colour = imagedata.GetScalarRange()[1] 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 def EditPixelBasedOnThreshold(self, x, y, z): 388 def EditPixelBasedOnThreshold(self, x, y, z):
378 """ 389 """
379 Erase or draw pixel based on edition threshold range. 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 thresh_min, thresh_max = self.current_mask.edition_threshold_range 394 thresh_min, thresh_max = self.current_mask.edition_threshold_range
383 395
384 if (pixel_colour >= thresh_min) and (pixel_colour <= thresh_max): 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,6 +26,7 @@ import wx.lib.pubsub as ps
26 import data.slice_ as sl 26 import data.slice_ as sl
27 import constants as const 27 import constants as const
28 import project 28 import project
  29 +import cursor_actors as ca
29 30
30 class Viewer(wx.Panel): 31 class Viewer(wx.Panel):
31 32
@@ -36,7 +37,7 @@ class Viewer(wx.Panel): @@ -36,7 +37,7 @@ class Viewer(wx.Panel):
36 self.SetBackgroundColour(colour) 37 self.SetBackgroundColour(colour)
37 38
38 # Interactor aditional style 39 # Interactor aditional style
39 - self.modes = [] 40 + self.modes = ['DEFAULT']
40 self.mouse_pressed = 0 41 self.mouse_pressed = 0
41 42
42 self.__init_gui() 43 self.__init_gui()
@@ -83,7 +84,7 @@ class Viewer(wx.Panel): @@ -83,7 +84,7 @@ class Viewer(wx.Panel):
83 self.cam = ren.GetActiveCamera() 84 self.cam = ren.GetActiveCamera()
84 self.ren = ren 85 self.ren = ren
85 86
86 - self.AppendMode('DEFAULT') 87 + self.AppendMode('EDITOR')
87 88
88 def AppendMode(self, mode): 89 def AppendMode(self, mode):
89 90
@@ -115,6 +116,19 @@ class Viewer(wx.Panel): @@ -115,6 +116,19 @@ class Viewer(wx.Panel):
115 # Bind event 116 # Bind event
116 style.AddObserver(event, 117 style.AddObserver(event,
117 action[mode][event]) 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 def OnMouseClick(self, obj, evt_vtk): 134 def OnMouseClick(self, obj, evt_vtk):
@@ -129,9 +143,15 @@ class Viewer(wx.Panel): @@ -129,9 +143,15 @@ class Viewer(wx.Panel):
129 print "Edit pixel region based on origin:", coord 143 print "Edit pixel region based on origin:", coord
130 144
131 def OnBrushMove(self, obj, evt_vtk): 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 if self.mouse_pressed: 149 if self.mouse_pressed:
134 print "Edit pixel region based on origin:", coord 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 def OnCrossMove(self, obj, evt_vtk): 156 def OnCrossMove(self, obj, evt_vtk):
137 coord = self.GetCoordinate() 157 coord = self.GetCoordinate()
@@ -187,6 +207,48 @@ class Viewer(wx.Panel): @@ -187,6 +207,48 @@ class Viewer(wx.Panel):
187 #print "New coordinate: ", coord 207 #print "New coordinate: ", coord
188 208
189 return coord 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 def __bind_events(self): 253 def __bind_events(self):
192 ps.Publisher().subscribe(self.LoadImagedata, 'Load slice to viewer') 254 ps.Publisher().subscribe(self.LoadImagedata, 'Load slice to viewer')