Commit 66fa5cd60953006d0c96842289ec66d8044f07db

Authored by tatiana
1 parent c6e7c193

ADD: Inserted cross that follows left mouse clicks on slices 2D

invesalius/constants.py
... ... @@ -78,3 +78,10 @@ WINDOW_LEVEL = ({"Abdomen":(350,50),
78 78 "Vasculature - Hard":(240,80),
79 79 "Vasculature - Soft":(650,160)
80 80 })
  81 +
  82 +ORIENTATION_COLOUR = {'AXIAL': (1,0,0), # Red
  83 + 'CORONAL': (0,1,0), # Green
  84 + 'SAGITAL': (0,0,1)} # Blue
  85 +CAM_POSITION = {"AXIAL":(0, 0, 1), "CORONAL":(0, -1, 0), "SAGITAL":(1, 0, 0)}
  86 +CAM_VIEW_UP = {"AXIAL":(0, 1, 0), "CORONAL":(0, 0, 1), "SAGITAL":(0, 0, 1)}
  87 +
... ...
invesalius/data/slice_.py
... ... @@ -28,6 +28,8 @@ class Slice(object):
28 28 ps.Publisher().subscribe(self.OnChangeCurrentMask, 'Change mask selected')
29 29 ps.Publisher().subscribe(self.CreateSurfaceFromIndex,
30 30 'Create surface from index')
  31 + ps.Publisher().subscribe(self.UpdateCursorPosition,
  32 + 'Update cursor position in slice')
31 33  
32 34 def CreateSurfaceFromIndex(self, pubsub_evt):
33 35 mask_index = pubsub_evt.data
... ... @@ -116,7 +118,46 @@ class Slice(object):
116 118 blend_imagedata.SetInput(1, imagedata_mask)
117 119 blend_imagedata.SetBlendModeToNormal()
118 120 blend_imagedata.GetOutput().ReleaseDataFlagOn()
119   - self.blend_imagedata = blend_imagedata
  121 + #self.blend_imagedata = blend_imagedata
  122 +
  123 +
  124 + #blend_imagedata.GetExtent()
  125 +
  126 + # global values
  127 + CURSOR_X = 0 # SAGITAL
  128 + CURSOR_Y = 0 # CORONAL
  129 + CURSOR_Z = 0 # AXIAL
  130 +
  131 + CURSOR_VALUE = 4095
  132 + CURSOR_RADIUS = 1000
  133 +
  134 + cross = vtk.vtkImageCursor3D()
  135 + cross.GetOutput().ReleaseDataFlagOn()
  136 + cross.SetInput(blend_imagedata.GetOutput())
  137 + cross.SetCursorPosition(CURSOR_X, CURSOR_Y, CURSOR_Z)
  138 + cross.SetCursorValue(CURSOR_VALUE)
  139 + cross.SetCursorRadius(CURSOR_RADIUS)
  140 + cross.Modified()
  141 + self.cross = cross
  142 +
  143 + cast = vtk.vtkImageCast()
  144 + cast.SetInput(cross.GetOutput())
  145 + cast.GetOutput().SetUpdateExtentToWholeExtent()
  146 + cast.SetOutputScalarTypeToUnsignedChar()
  147 + cast.Update()
  148 +
  149 + self.blend_imagedata = cast
  150 +
  151 +
  152 + def UpdateCursorPosition(self, pubsub_evt):
  153 +
  154 + new_pos = pubsub_evt.data
  155 + self.cross.SetCursorPosition(new_pos)
  156 + self.cross.Modified()
  157 + self.blend_imagedata.Update()
  158 + ps.Publisher().sendMessage('Update slice viewer', None)
  159 +
  160 +
120 161  
121 162 def __create_background(self, imagedata):
122 163  
... ...
invesalius/data/viewer_slice.py
... ... @@ -24,35 +24,49 @@ import wx
24 24 import wx.lib.pubsub as ps
25 25  
26 26 import data.slice_ as sl
27   -
  27 +import constants as const
28 28 import project
29 29  
30 30 class Viewer(wx.Panel):
31 31  
32 32 def __init__(self, prnt, orientation='AXIAL'):
33 33 wx.Panel.__init__(self, prnt, size=wx.Size(320, 300))
34   -
  34 +
  35 + colour = [255*c for c in const.ORIENTATION_COLOUR[orientation]]
  36 + self.SetBackgroundColour(colour)
  37 +
35 38 self.__init_gui()
36   - self.__config_interactor()
37 39  
38 40 self.orientation = orientation
39 41 self.slice_number = 0
40 42  
  43 + # Interactor aditional style
  44 + self.mode = 'DEFAULT'
  45 + self.mouse_pressed = 0
  46 +
  47 + # VTK pipeline and actors
  48 + self.__config_interactor()
  49 + self.pick = vtk.vtkCellPicker()
  50 + #self.cursor =
  51 +
41 52 self.__bind_events()
42 53 self.__bind_events_wx()
43 54  
44 55 def __init_gui(self):
  56 +
45 57 interactor = wxVTKRenderWindowInteractor(self, -1, size=self.GetSize())
46 58  
47 59 scroll = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL)
48 60 self.scroll = scroll
49 61  
50 62 sizer = wx.BoxSizer(wx.HORIZONTAL)
51   - self.SetSizer(sizer)
52   -
53   - sizer.Add(scroll, 0, wx.EXPAND|wx.GROW)
54 63 sizer.Add(interactor, 1, wx.EXPAND|wx.GROW)
55   - sizer.Fit(self)
  64 +
  65 + background_sizer = wx.BoxSizer(wx.HORIZONTAL)
  66 + background_sizer.AddSizer(sizer, 1, wx.EXPAND|wx.GROW|wx.ALL, 2)
  67 + background_sizer.Add(scroll, 0, wx.EXPAND|wx.GROW)
  68 + self.SetSizer(background_sizer)
  69 + background_sizer.Fit(self)
56 70  
57 71 self.Layout()
58 72 self.Update()
... ... @@ -61,7 +75,10 @@ class Viewer(wx.Panel):
61 75 self.interactor = interactor
62 76  
63 77 def __config_interactor(self):
  78 +
64 79 style = vtk.vtkInteractorStyleImage()
  80 + self.style = style
  81 +
65 82 ren = vtk.vtkRenderer()
66 83  
67 84 interactor = self.interactor
... ... @@ -69,26 +86,116 @@ class Viewer(wx.Panel):
69 86 interactor.GetRenderWindow().AddRenderer(ren)
70 87  
71 88 self.cam = ren.GetActiveCamera()
72   -
73 89 self.ren = ren
74   -
  90 +
  91 + self.SetMode(self.mode)
  92 +
  93 + def SetMode(self, mode):
  94 + self.mode = mode
  95 + # All modes and bindings
  96 + action = {'DEFAULT': {
  97 + "MouseMoveEvent": self.OnCrossMove,
  98 + "LeftButtonPressEvent": self.OnMouseClick,
  99 + "LeftButtonReleaseEvent": self.OnMouseRelease
  100 + },
  101 + 'EDITOR': {
  102 + "MouseMoveEvent": self.OnBrushMove,
  103 + "LeftButtonPressEvent": self.OnBrushClick,
  104 + "LeftButtonReleaseEvent": self.OnMouseRelease
  105 + }
  106 + }
  107 +
  108 + # Bind method according to current mode
  109 + style = self.style
  110 + style.AddObserver("MouseMoveEvent", action[mode]["MouseMoveEvent"])
  111 + style.AddObserver("LeftButtonPressEvent", action[mode]["LeftButtonPressEvent"])
  112 + style.AddObserver("LeftButtonReleaseEvent", action[mode]["LeftButtonReleaseEvent"])
  113 +
  114 + def OnMouseClick(self, obj, evt_vtk):
  115 + self.mouse_pressed = 1
  116 +
  117 + def OnMouseRelease(self, obj, evt_vtk):
  118 + self.mouse_pressed = 0
  119 +
  120 + def OnBrushClick(self, obj, evt_vtk):
  121 + self.mouse_pressed = 1
  122 + coord = self.GetCoordinate()
  123 + print "Edit pixel region based on origin:", coord
  124 +
  125 + def OnBrushMove(self, obj, evt_vtk):
  126 + coord = self.GetCoordinate()
  127 + if self.mouse_pressed:
  128 + print "Edit pixel region based on origin:", coord
  129 +
  130 + def OnCrossMove(self, obj, evt_vtk):
  131 + coord = self.GetCoordinate()
  132 + # Update position in other slices
  133 + if self.mouse_pressed:
  134 + ps.Publisher().sendMessage('Update cursor position in slice', coord)
  135 + ps.Publisher().sendMessage(('Set scroll position', 'SAGITAL'), coord[0])
  136 + ps.Publisher().sendMessage(('Set scroll position', 'CORONAL'), coord[1])
  137 + ps.Publisher().sendMessage(('Set scroll position', 'AXIAL'), coord[2])
  138 +
  139 + def GetCoordinate(self):
  140 +
  141 + # Find position
  142 + mouse_x, mouse_y = self.interactor.GetEventPosition()
  143 + self.pick.Pick(mouse_x, mouse_y, 0, self.ren)
  144 + x, y, z = self.pick.GetPickPosition()
  145 +
  146 + # First we fix the position origin, based on vtkActor bounds
  147 + bounds = self.actor.GetBounds()
  148 + bound_xi, bound_xf, bound_yi, bound_yf, bound_zi, bound_zf = bounds
  149 + x = float(x - bound_xi)
  150 + y = float(y - bound_yi)
  151 + z = float(z - bound_zi)
  152 +
  153 + # Then we fix the porpotion, based on vtkImageData spacing
  154 + spacing_x, spacing_y, spacing_z = self.imagedata.GetSpacing()
  155 + x = x/spacing_x
  156 + y = y/spacing_y
  157 + z = z/spacing_z
  158 +
  159 + # Based on the current orientation, we define 3D position
  160 + coordinates = {"SAGITAL": [self.slice_number, y, z],
  161 + "CORONAL": [x, self.slice_number, z],
  162 + "AXIAL": [x, y, self.slice_number]}
  163 + coord = [int(coord) for coord in coordinates[self.orientation]]
  164 +
  165 + # According to vtkImageData extent, we limit min and max value
  166 + # If this is not done, a VTK Error occurs when mouse is pressed outside
  167 + # vtkImageData extent
  168 + extent = self.imagedata.GetWholeExtent()
  169 + extent_min = extent[0], extent[2], extent[4]
  170 + extent_max = extent[1], extent[3], extent[5]
  171 + for index in xrange(3):
  172 + if coord[index] > extent_max[index]:
  173 + coord[index] = extent_max[index]
  174 + elif coord[index] < extent_min[index]:
  175 + coord[index] = extent_min[index]
  176 +
  177 + print "New coordinate: ", coord
  178 +
  179 + return coord
  180 +
75 181 def __bind_events(self):
76 182 ps.Publisher().subscribe(self.LoadImagedata, 'Load slice to viewer')
77 183 ps.Publisher().subscribe(self.SetColour, 'Change mask colour')
78 184 ps.Publisher().subscribe(self.UpdateRender, 'Update slice viewer')
79   - ps.Publisher().subscribe(self.SetScrollPosition, ('Set scroll position',
  185 + ps.Publisher().subscribe(self.ChangeSliceNumber, ('Set scroll position',
80 186 self.orientation))
81 187  
82 188 def __bind_events_wx(self):
83 189 self.scroll.Bind(wx.EVT_SCROLL, self.OnScrollBar)
84   - #self.interactor.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
85 190  
86 191 def LoadImagedata(self, pubsub_evt):
87 192 imagedata = pubsub_evt.data
88 193 self.SetInput(imagedata)
89 194  
90 195 def SetInput(self, imagedata):
  196 +
91 197 self.imagedata = imagedata
  198 +
92 199 ren = self.ren
93 200 interactor = self.interactor
94 201  
... ... @@ -101,77 +208,72 @@ class Viewer(wx.Panel):
101 208 actor.SetInput(slice_.GetOutput())
102 209 self.actor = actor
103 210  
  211 + colour = const.ORIENTATION_COLOUR[self.orientation]
  212 +
  213 + text_property = vtk.vtkTextProperty()
  214 + text_property.SetFontSize(16)
  215 + text_property.SetFontFamilyToArial()
  216 + text_property.BoldOn()
  217 + text_property.SetColor(colour)
  218 +
  219 + # Text related to slice number shown
  220 + text_mapper = vtk.vtkTextMapper()
  221 + text_mapper.SetInput("%d"%(self.slice_number))
  222 + text_mapper.GetTextProperty().ShallowCopy(text_property)
  223 + self.text_mapper = text_mapper
  224 +
  225 + text_actor = vtk.vtkActor2D()
  226 + text_actor.SetMapper(text_mapper)
  227 + text_actor.SetLayerNumber(1)
  228 + text_actor.GetPositionCoordinate().SetValue(self.GetSize()[0] - 80, 20)
  229 + text_actor.SetVisibility(1)
  230 +
104 231 ren.AddActor(actor)
  232 + ren.AddActor(text_actor)
105 233 self.__update_camera()
106 234  
107 235 max_slice_number = actor.GetSliceNumberMax()
108 236 self.scroll.SetScrollbar(wx.SB_VERTICAL, 1, max_slice_number,
109 237 max_slice_number)
  238 + self.SetScrollPosition(0)
110 239  
111 240 def SetOrientation(self, orientation):
112 241 self.orientation = orientation
113 242 self.__update_camera()
114 243  
115 244 def __update_camera(self):
116   - cam = self.cam
117   -
118 245 orientation = self.orientation
119 246  
120   - if orientation == "AXIAL":
121   - cam.SetFocalPoint(0, 0, 0)
122   - cam.SetPosition(0, 0, 1)
123   - cam.ComputeViewPlaneNormal()
124   - cam.SetViewUp(0, 1, 0)
125   - elif orientation == "CORONAL":
126   - cam.SetFocalPoint(0, 0, 0)
127   - cam.SetPosition(0, -1, 0)
128   - cam.ComputeViewPlaneNormal()
129   - cam.SetViewUp(0, 0, 1)
130   - elif orientation == "SAGITAL":
131   - cam.SetFocalPoint(0, 0, 0)
132   - cam.SetPosition(1, 0, 0)
133   - cam.ComputeViewPlaneNormal()
134   - cam.SetViewUp(0, 0, 1)
135   -
  247 + cam = self.cam
  248 + cam.SetFocalPoint(0, 0, 0)
  249 + cam.SetPosition(const.CAM_POSITION[self.orientation])
  250 + cam.SetViewUp(const.CAM_VIEW_UP[self.orientation])
  251 + cam.ComputeViewPlaneNormal()
136 252 cam.OrthogonalizeViewUp()
  253 + cam.ParallelProjectionOn()
  254 +
137 255 self.__update_display_extent()
138   - cam.ParallelProjectionOn()
  256 +
139 257 self.ren.ResetCamera()
140 258 self.ren.Render()
141 259  
142 260 def __update_display_extent(self):
143   - actor = self.actor
144   - slice_number = self.slice_number
145   - extent = self.imagedata.GetWholeExtent()
146   - if self.orientation == "AXIAL":
147   - xs = extent[1] - extent[0] + 1
148   - ys = extent[3] - extent[2] + 1
149   - actor.SetDisplayExtent(extent[0], extent[1],
150   - extent[2], extent[3],
151   - slice_number, slice_number)
152   - elif self.orientation == "CORONAL":
153   - xs = extent[1] - extent[0] + 1
154   - ys = extent[5] - extent[4] + 1
155   - actor.SetDisplayExtent(extent[0], extent[1],
156   - slice_number,slice_number,
157   - extent[4], extent[5])
158   - elif self.orientation == "SAGITAL":
159   - xs = extent[3] - extent[2] + 1
160   - ys = extent[5] - extent[4] + 1
161   - actor.SetDisplayExtent(slice_number, slice_number,
162   - extent[2], extent[3],
163   - extent[4], extent[5])
164 261  
  262 + pos = self.slice_number
  263 + e = self.imagedata.GetWholeExtent()
  264 +
  265 + new_extent = {"SAGITAL": (pos, pos, e[2], e[3], e[4], e[5]),
  266 + "CORONAL": (e[0], e[1], pos, pos, e[4], e[5]),
  267 + "AXIAL": (e[0], e[1], e[2], e[3], pos, pos)}
  268 +
  269 + self.actor.SetDisplayExtent(new_extent[self.orientation])
165 270 self.ren.ResetCameraClippingRange()
166 271 self.ren.Render()
167 272  
168 273 def UpdateRender(self, evt):
169 274 self.interactor.Render()
170 275  
171   - def SetScrollPosition(self, pubsub_evt):
172   - value = pubsub_evt.data
173   - position = self.scroll.GetThumbPosition()
174   - position += value
  276 + def SetScrollPosition(self, position):
175 277 self.scroll.SetThumbPosition(position)
176 278 self.OnScrollBar()
177 279  
... ... @@ -183,9 +285,16 @@ class Viewer(wx.Panel):
183 285 evt.Skip()
184 286  
185 287 def SetSliceNumber(self, index):
  288 + self.text_mapper.SetInput(str(index))
186 289 self.slice_number = index
187 290 self.__update_display_extent()
188 291  
  292 + def ChangeSliceNumber(self, pubsub_evt):
  293 + index = pubsub_evt.data
  294 + self.SetSliceNumber(index)
  295 + self.scroll.SetThumbPosition(index)
  296 + self.interactor.Render()
  297 +
189 298 def SetColour(self, pubsub_evt):
190 299 colour_wx = pubsub_evt.data
191 300 colour_vtk = [colour/float(255) for colour in colour_wx]
... ...
invesalius/data/viewer_volume.py
... ... @@ -61,6 +61,26 @@ class Viewer(wx.Panel):
61 61 def __bind_events(self):
62 62 ps.Publisher().subscribe(self.LoadActor, 'Load surface actor into viewer')
63 63 ps.Publisher().subscribe(self.UpdateRender, 'Render volume viewer')
  64 + ps.Publisher().subscribe(self.ChangeBackgroundColour,
  65 + 'Change volume viewer background colour')
  66 + ps.Publisher().subscribe(self.ShowRaycastingVolume,
  67 + 'Show raycasting volume')
  68 + ps.Publisher().subscribe(self.HideRaycastingVolume,
  69 + 'Hide raycasting volume')
  70 +
  71 +
  72 + def ShowRaycastingVolume(self, pubsub_evt):
  73 + pass
  74 +
  75 + def HideRaycastingVolume(self, pubsub_evt):
  76 + pass
  77 +
  78 +
  79 +
  80 + def ChangeBackgroundColour(self, pubsub_evt):
  81 + colour = pubsub_evt.data
  82 + self.ren.SetBackground(colour)
  83 + self.UpdateRender()
64 84  
65 85 def LoadActor(self, pubsub_evt):
66 86 actor = pubsub_evt.data
... ... @@ -76,7 +96,7 @@ class Viewer(wx.Panel):
76 96  
77 97 self.iren.Render()
78 98  
79   - def UpdateRender(self, evt_pubsub):
  99 + def UpdateRender(self, evt_pubsub=None):
80 100 self.iren.Render()
81 101  
82 102 def CreatePlanes(self):
... ...
invesalius/gui/default_viewers.py
... ... @@ -21,18 +21,6 @@ import wx.lib.agw.fourwaysplitter as fws
21 21 import data.viewer_slice as slice_viewer
22 22 import data.viewer_volume as volume_viewer
23 23  
24   -class SamplePane(wx.Panel):
25   - """
26   - Just a simple test window to put into the splitter.
27   - """
28   - def __init__(self, parent, colour, label):
29   - wx.Panel.__init__(self, parent, style=wx.BORDER_SUNKEN)
30   - self.SetBackgroundColour(colour)
31   - wx.StaticText(self, -1, label, (5,5))
32   -
33   - def SetOtherLabel(self, label):
34   - wx.StaticText(self, -1, label, (5, 30))
35   -
36 24  
37 25 class Panel(wx.Panel):
38 26 def __init__(self, parent):
... ... @@ -89,7 +77,8 @@ class Panel(wx.Panel):
89 77 Name("Sagital Slice").Caption("Sagital slice").
90 78 MaximizeButton(True).CloseButton(False))
91 79  
92   - self.aui_manager.AddPane(volume_viewer.Viewer(self),
  80 + self.aui_manager.AddPane(VolumeViewerCover(self),
  81 + #self.aui_manager.AddPane(volume_viewer.Viewer(self)
93 82 wx.aui.AuiPaneInfo().Row(1).Name("Volume").
94 83 Bottom().Centre().Caption("Volume").
95 84 MaximizeButton(True).CloseButton(False))
... ... @@ -150,8 +139,8 @@ class Panel(wx.Panel):
150 139 Name("Sagittal Slice").Caption("Sagittal slice").
151 140 MaximizeButton(True).CloseButton(False))
152 141  
153   - p4 = volume_viewer.Viewer(self)
154   - aui_manager.AddPane(p4,
  142 + #p4 = volume_viewer.Viewer(self)
  143 + aui_manager.AddPane(VolumeViewerCover,
155 144 wx.aui.AuiPaneInfo().
156 145 Name("Volume").Caption("Volume").
157 146 MaximizeButton(True).CloseButton(False))
... ... @@ -163,3 +152,50 @@ class Panel(wx.Panel):
163 152  
164 153  
165 154 aui_manager.Update()
  155 +
  156 +class VolumeViewerCover(wx.Panel):
  157 + def __init__(self, parent):
  158 + wx.Panel.__init__(self, parent)
  159 +
  160 + sizer = wx.BoxSizer(wx.HORIZONTAL)
  161 + sizer.Add(volume_viewer.Viewer(self), 1, wx.EXPAND|wx.GROW)
  162 + sizer.Add(VolumeToolPanel(self), 0, wx.EXPAND)
  163 + self.SetSizer(sizer)
  164 + sizer.Fit(self)
  165 +
  166 +#import wx.lib.platebtn as pbtn
  167 +import wx.lib.buttons as btn
  168 +import wx.lib.pubsub as ps
  169 +import wx.lib.colourselect as csel
  170 +
  171 +class VolumeToolPanel(wx.Panel):
  172 + def __init__(self, parent):
  173 + wx.Panel.__init__(self, parent, size = (8,100))
  174 +
  175 + BMP_RAYCASTING = wx.Bitmap("../icons/volume_raycasting.png", wx.BITMAP_TYPE_PNG)
  176 + BMP_RAYCASTING.SetWidth(22)
  177 + BMP_RAYCASTING.SetHeight(22)
  178 +
  179 + button_raycasting=btn.GenBitmapToggleButton(self, 1, BMP_RAYCASTING, size=(24,24))
  180 + button_raycasting.Bind(wx.EVT_BUTTON, self.OnToggleRaycasting)
  181 + self.button_raycasting = button_raycasting
  182 +
  183 + button_colour= csel.ColourSelect(self, 111,colour=(0,0,0),size=(24,24))
  184 + button_colour.Bind(csel.EVT_COLOURSELECT, self.OnSelectColour)
  185 + self.button_colour = button_colour
  186 +
  187 + sizer = wx.BoxSizer(wx.VERTICAL)
  188 + sizer.Add(button_colour, 0, wx.ALL, 1)
  189 + sizer.Add(button_raycasting, 0, wx.ALL, 1)
  190 + self.SetSizer(sizer)
  191 + sizer.Fit(self)
  192 +
  193 + def OnSelectColour(self, evt):
  194 + colour = c = [i/255.0 for i in evt.GetValue()]
  195 + ps.Publisher().sendMessage('Change volume viewer background colour', colour)
  196 +
  197 + def OnToggleRaycasting(self, evt):
  198 + if self.button_raycasting.GetToggle():
  199 + ps.Publisher().sendMessage('Show raycasting volume')
  200 + else:
  201 + ps.Publisher().sendMessage('Hide raycasting volume')
... ...
invesalius/gui/frame.py
... ... @@ -30,7 +30,8 @@ import default_viewers as viewers
30 30 class Frame(wx.Frame):
31 31 def __init__(self, prnt):
32 32 wx.Frame.__init__(self, id=-1, name='', parent=prnt,
33   - pos=wx.Point(0, 0), size=wx.Size(1024, 768),
  33 + pos=wx.Point(0, 0),
  34 + size=wx.Size(1024, 768), #size = wx.DisplaySize(),
34 35 style=wx.DEFAULT_FRAME_STYLE, title='InVesalius 3.0')
35 36 self.Center(wx.BOTH)
36 37 self.SetIcon(wx.Icon("../icons/invesalius.ico", wx.BITMAP_TYPE_ICO))
... ...