Commit 9de6858764937e33dd2090be46587d1b9659d8b2

Authored by sotodela
Committed by GitHub
1 parent 85de3a6a
Exists in master

Coil projection on peel (#308)

* add add_object_orientation_disk and add_addobjectArrow functions, add orientation disk and projection arrow actors

* add getCenter and getNormal functions, create locator and add self.peel_centers and self.peel_normals variables, add to calculate normals and centers when calling getPeelActor (new peel)

* add GetPeelCenters and Initlocator_viewer and corresponding publisher call

* add in  OnLinkBrain and TractographyPanel class (OnSelectPeelingDepth) call to get peel centers and normals and initiator)

* remove print on GetPeelCenters and Initlocator_viewer

* add objectArrowlocation to calculate coil norms and directions

* Add UpdateObjectArrowOrientation and Getcellintersection

  - getcellintersection function calculates the intersection between the
  coil normal and the peel center
  -calculates the angle between the coil normal and the normal of the
  center of the intersected cell
  -shows the coil projected with arrow and disk
  -Color of the disk and arrow change according to the value of the
  angle between the normal of the coil and the peel center mesh
  intersected.
  -updateobjectarroworientation is used to show and update the position
  of the projected arrow and disk

* add comments, removed not used code on class def, modify currentPeel as local variable

* modify getPeelActor removing input currentPeel

* add affine_vtk matrix as input to getPeelActor, add change position of curent peel

* add case when not intersection found in getcellintersection function

* add remove arrow and disk actor only when intersecting with the peel

* add comments to getcellintersection function, remove TODO

* add comment to getcellintersection

* add transformpeelposition function

* add setvisibility to arrow and disk actor on OnNavigationStatus

* add tangent lines for debuggint at getcellintersection function

* add coil_dir from coordinates from tracker to objectArrowlocation

* modify visibility of disk and coil projection arrow, to be shown when coil is not shown

* comment tangent lines for debuggin on getcellintersection

* modify names of functions to invesalius naming convention

* correct mispelling error when calling GetCellIntersection

* add angle threshold for projectio arrow in constansts

* add angle threshold for arrow projection in constants

* change size of disk and arrow in coil projection

* add normal lines for debugging arrow projection

* Modify function names to invensalius convention

* add condition for track if peel is loaded or not

* fix removing arrow and disk projection when distance is larger than set value, removing normal lines for debugging

* clean comments

* add self.peel_loaded to vis_components

* Modify UpdatePeelVisualization to pass peel_loaded from navigation class

* change color disk and arrow
invesalius/constants.py
@@ -781,6 +781,7 @@ ARROW_UPPER_LIMIT = 15 @@ -781,6 +781,7 @@ ARROW_UPPER_LIMIT = 15
781 COIL_ANGLES_THRESHOLD = 3 781 COIL_ANGLES_THRESHOLD = 3
782 COIL_COORD_THRESHOLD = 3 782 COIL_COORD_THRESHOLD = 3
783 TIMESTAMP = 2.0 783 TIMESTAMP = 2.0
  784 +COIL_ANGLE_ARROW_PROJECTION_THRESHOLD = 10
784 785
785 CAM_MODE = True 786 CAM_MODE = True
786 787
invesalius/data/brainmesh_handler.py
@@ -8,34 +8,33 @@ import pyvista @@ -8,34 +8,33 @@ import pyvista
8 8
9 class Brain: 9 class Brain:
10 def __init__(self, img_path, mask_path, n_peels, affine_vtk): 10 def __init__(self, img_path, mask_path, n_peels, affine_vtk):
  11 + # Create arrays to access the peel data and peel Actors
11 self.peel = [] 12 self.peel = []
12 self.peelActors = [] 13 self.peelActors = []
13 - 14 + # Read the image
14 T1_reader = vtk.vtkNIFTIImageReader() 15 T1_reader = vtk.vtkNIFTIImageReader()
15 T1_reader.SetFileName(img_path) 16 T1_reader.SetFileName(img_path)
16 T1_reader.Update() 17 T1_reader.Update()
17 -  
18 - # self.refImage = vtk.vtkImageData() 18 + # Image
19 self.refImage = T1_reader.GetOutput() 19 self.refImage = T1_reader.GetOutput()
20 - 20 + # Read the mask
21 mask_reader = vtk.vtkNIFTIImageReader() 21 mask_reader = vtk.vtkNIFTIImageReader()
22 mask_reader.SetFileName(mask_path) 22 mask_reader.SetFileName(mask_path)
23 mask_reader.Update() 23 mask_reader.Update()
24 - 24 + # Use the mask to create isosurface
25 mc = vtk.vtkContourFilter() 25 mc = vtk.vtkContourFilter()
26 mc.SetInputConnection(mask_reader.GetOutputPort()) 26 mc.SetInputConnection(mask_reader.GetOutputPort())
27 mc.SetValue(0, 1) 27 mc.SetValue(0, 1)
28 mc.Update() 28 mc.Update()
29 29
30 - refSurface = vtk.vtkPolyData() 30 + # Mask isosurface
31 refSurface = mc.GetOutput() 31 refSurface = mc.GetOutput()
32 -  
33 - tmpPeel = vtk.vtkPolyData() 32 + # Create a uniformly meshed surface
34 tmpPeel = downsample(refSurface) 33 tmpPeel = downsample(refSurface)
35 -  
36 - mask_sFormMatrix = vtk.vtkMatrix4x4() 34 + # Standard space coordinates
37 mask_sFormMatrix = mask_reader.GetSFormMatrix() 35 mask_sFormMatrix = mask_reader.GetSFormMatrix()
38 36
  37 + # Apply coordinate transform to the meshed mask
39 mask_ijk2xyz = vtk.vtkTransform() 38 mask_ijk2xyz = vtk.vtkTransform()
40 mask_ijk2xyz.SetMatrix(mask_sFormMatrix) 39 mask_ijk2xyz.SetMatrix(mask_sFormMatrix)
41 40
@@ -44,17 +43,21 @@ class Brain: @@ -44,17 +43,21 @@ class Brain:
44 mask_ijk2xyz_filter.SetTransform(mask_ijk2xyz) 43 mask_ijk2xyz_filter.SetTransform(mask_ijk2xyz)
45 mask_ijk2xyz_filter.Update() 44 mask_ijk2xyz_filter.Update()
46 45
  46 + # Smooth the mesh
47 tmpPeel = smooth(mask_ijk2xyz_filter.GetOutput()) 47 tmpPeel = smooth(mask_ijk2xyz_filter.GetOutput())
  48 + # Configure calculation of normals
48 tmpPeel = fixMesh(tmpPeel) 49 tmpPeel = fixMesh(tmpPeel)
  50 + # Remove duplicate points etc
49 tmpPeel = cleanMesh(tmpPeel) 51 tmpPeel = cleanMesh(tmpPeel)
  52 + # Generate triangles
50 tmpPeel = upsample(tmpPeel) 53 tmpPeel = upsample(tmpPeel)
  54 +
51 tmpPeel = smooth(tmpPeel) 55 tmpPeel = smooth(tmpPeel)
52 tmpPeel = fixMesh(tmpPeel) 56 tmpPeel = fixMesh(tmpPeel)
53 tmpPeel = cleanMesh(tmpPeel) 57 tmpPeel = cleanMesh(tmpPeel)
54 58
55 - # sFormMatrix = vtk.vtkMatrix4x4() 59 + # Scanner coordinates from image
56 qFormMatrix = T1_reader.GetQFormMatrix() 60 qFormMatrix = T1_reader.GetQFormMatrix()
57 - # sFormMatrix = T1_reader.GetSFormMatrix()  
58 61
59 refImageSpace2_xyz_transform = vtk.vtkTransform() 62 refImageSpace2_xyz_transform = vtk.vtkTransform()
60 refImageSpace2_xyz_transform.SetMatrix(qFormMatrix) 63 refImageSpace2_xyz_transform.SetMatrix(qFormMatrix)
@@ -69,29 +72,32 @@ class Brain: @@ -69,29 +72,32 @@ class Brain:
69 self.xyz2_refImageSpace = vtk.vtkTransformPolyDataFilter() 72 self.xyz2_refImageSpace = vtk.vtkTransformPolyDataFilter()
70 self.xyz2_refImageSpace.SetTransform(xyz2_refImageSpace_transform) 73 self.xyz2_refImageSpace.SetTransform(xyz2_refImageSpace_transform)
71 74
72 - # self.currentPeel = vtk.vtkPolyData()  
73 - self.currentPeel = tmpPeel 75 + currentPeel = tmpPeel
74 self.currentPeelNo = 0 76 self.currentPeelNo = 0
75 - self.mapImageOnCurrentPeel() 77 + currentPeel= self.MapImageOnCurrentPeel(currentPeel)
76 78
77 newPeel = vtk.vtkPolyData() 79 newPeel = vtk.vtkPolyData()
78 - newPeel.DeepCopy(self.currentPeel) 80 + newPeel.DeepCopy(currentPeel)
  81 + newPeel.DeepCopy(currentPeel)
  82 + self.peel_normals = vtk.vtkFloatArray()
  83 + self.peel_centers = vtk.vtkFloatArray()
79 self.peel.append(newPeel) 84 self.peel.append(newPeel)
80 self.currentPeelActor = vtk.vtkActor() 85 self.currentPeelActor = vtk.vtkActor()
81 self.currentPeelActor.SetUserMatrix(affine_vtk) 86 self.currentPeelActor.SetUserMatrix(affine_vtk)
82 - self.getCurrentPeelActor() 87 + self.GetCurrentPeelActor(currentPeel)
83 self.peelActors.append(self.currentPeelActor) 88 self.peelActors.append(self.currentPeelActor)
84 - 89 + # locator will later find the triangle on the peel surface where the coil's normal intersect
  90 + self.locator = vtk.vtkCellLocator()
85 self.numberOfPeels = n_peels 91 self.numberOfPeels = n_peels
86 - self.peelDown() 92 + self.PeelDown(currentPeel)
87 93
88 - def get_actor(self, n):  
89 - return self.getPeelActor(n) 94 + def get_actor(self, n, affine_vtk):
  95 + return self.GetPeelActor(n, affine_vtk)
90 96
91 - def sliceDown(self): 97 + def SliceDown(self, currentPeel):
92 # Warp using the normals 98 # Warp using the normals
93 warp = vtk.vtkWarpVector() 99 warp = vtk.vtkWarpVector()
94 - warp.SetInputData(fixMesh(downsample(self.currentPeel))) # fixMesh here updates normals needed for warping 100 + warp.SetInputData(fixMesh(downsample(currentPeel))) # fixMesh here updates normals needed for warping
95 warp.SetInputArrayToProcess(0, 0, 0, vtk.vtkDataObject().FIELD_ASSOCIATION_POINTS, 101 warp.SetInputArrayToProcess(0, 0, 0, vtk.vtkDataObject().FIELD_ASSOCIATION_POINTS,
96 vtk.vtkDataSetAttributes().NORMALS) 102 vtk.vtkDataSetAttributes().NORMALS)
97 warp.SetScaleFactor(-1) 103 warp.SetScaleFactor(-1)
@@ -103,8 +109,8 @@ class Brain: @@ -103,8 +109,8 @@ class Brain:
103 out = fixMesh(out) 109 out = fixMesh(out)
104 out = cleanMesh(out) 110 out = cleanMesh(out)
105 111
106 - self.currentPeel = out  
107 - 112 + currentPeel = out
  113 + return currentPeel
108 # def sliceUp(self): 114 # def sliceUp(self):
109 # # Warp using the normals 115 # # Warp using the normals
110 # warp = vtk.vtkWarpVector() 116 # warp = vtk.vtkWarpVector()
@@ -122,8 +128,8 @@ class Brain: @@ -122,8 +128,8 @@ class Brain:
122 # 128 #
123 # currentPeel = out 129 # currentPeel = out
124 130
125 - def mapImageOnCurrentPeel(self):  
126 - self.xyz2_refImageSpace.SetInputData(self.currentPeel) 131 + def MapImageOnCurrentPeel(self, currentPeel):
  132 + self.xyz2_refImageSpace.SetInputData(currentPeel)
127 self.xyz2_refImageSpace.Update() 133 self.xyz2_refImageSpace.Update()
128 134
129 probe = vtk.vtkProbeFilter() 135 probe = vtk.vtkProbeFilter()
@@ -134,25 +140,36 @@ class Brain: @@ -134,25 +140,36 @@ class Brain:
134 self.refImageSpace2_xyz.SetInputData(probe.GetOutput()) 140 self.refImageSpace2_xyz.SetInputData(probe.GetOutput())
135 self.refImageSpace2_xyz.Update() 141 self.refImageSpace2_xyz.Update()
136 142
137 - self.currentPeel = self.refImageSpace2_xyz.GetOutput() 143 + currentPeel = self.refImageSpace2_xyz.GetOutput()
  144 + return currentPeel
138 145
139 - def peelDown(self): 146 + def PeelDown(self, currentPeel):
140 for i in range(0, self.numberOfPeels): 147 for i in range(0, self.numberOfPeels):
141 - self.sliceDown()  
142 - self.mapImageOnCurrentPeel() 148 + currentPeel = self.SliceDown(currentPeel)
  149 + currentPeel = self.MapImageOnCurrentPeel(currentPeel)
143 150
144 newPeel = vtk.vtkPolyData() 151 newPeel = vtk.vtkPolyData()
145 - newPeel.DeepCopy(self.currentPeel) 152 + newPeel.DeepCopy(currentPeel)
146 self.peel.append(newPeel) 153 self.peel.append(newPeel)
147 154
148 - # getCurrentPeelActor() 155 + # GetCurrentPeelActor()
149 # newPeelActor = vtk.vtkActor() 156 # newPeelActor = vtk.vtkActor()
150 # newPeelActor = currentPeelActor 157 # newPeelActor = currentPeelActor
151 # peelActors.push_back(newPeelActor) 158 # peelActors.push_back(newPeelActor)
152 159
153 self.currentPeelNo += 1 160 self.currentPeelNo += 1
154 161
155 - def getPeelActor(self, p): 162 + def TransformPeelPosition(self, p, affine_vtk):
  163 + peel_transform = vtk.vtkTransform()
  164 + peel_transform.SetMatrix(affine_vtk)
  165 + refpeelspace = vtk.vtkTransformPolyDataFilter()
  166 + refpeelspace.SetInputData(self.peel[p])
  167 + refpeelspace.SetTransform(peel_transform)
  168 + refpeelspace.Update()
  169 + currentPeel = refpeelspace.GetOutput()
  170 + return currentPeel
  171 +
  172 + def GetPeelActor(self, p, affine_vtk):
156 colors = vtk.vtkNamedColors() 173 colors = vtk.vtkNamedColors()
157 # Create the color map 174 # Create the color map
158 colorLookupTable = vtk.vtkLookupTable() 175 colorLookupTable = vtk.vtkLookupTable()
@@ -179,9 +196,16 @@ class Brain: @@ -179,9 +196,16 @@ class Brain:
179 # Set actor 196 # Set actor
180 self.currentPeelActor.SetMapper(mapper) 197 self.currentPeelActor.SetMapper(mapper)
181 198
  199 + currentPeel = self.TransformPeelPosition(p, affine_vtk)
  200 +
  201 + self.locator.SetDataSet(currentPeel)
  202 + self.locator.BuildLocator()
  203 + self.GetCenters(currentPeel)
  204 + self.GetNormals(currentPeel)
  205 +
182 return self.currentPeelActor 206 return self.currentPeelActor
183 207
184 - def getCurrentPeelActor(self): 208 + def GetCurrentPeelActor(self, currentPeel):
185 colors = vtk.vtkNamedColors() 209 colors = vtk.vtkNamedColors()
186 210
187 # Create the color map 211 # Create the color map
@@ -198,7 +222,7 @@ class Brain: @@ -198,7 +222,7 @@ class Brain:
198 222
199 # Set mapper auto 223 # Set mapper auto
200 mapper = vtk.vtkOpenGLPolyDataMapper() 224 mapper = vtk.vtkOpenGLPolyDataMapper()
201 - mapper.SetInputData(self.currentPeel) 225 + mapper.SetInputData(currentPeel)
202 # mapper.SetScalarRange(0, 1000) 226 # mapper.SetScalarRange(0, 1000)
203 # mapper.SetScalarRange(0, 250) 227 # mapper.SetScalarRange(0, 250)
204 mapper.SetScalarRange(0, 200) 228 mapper.SetScalarRange(0, 200)
@@ -213,6 +237,26 @@ class Brain: @@ -213,6 +237,26 @@ class Brain:
213 237
214 return self.currentPeelActor 238 return self.currentPeelActor
215 239
  240 + def GetCenters(self, currentPeel):
  241 + # Compute centers of triangles
  242 + centerComputer = vtk.vtkCellCenters() # This computes centers of the triangles on the peel
  243 + centerComputer.SetInputData(currentPeel)
  244 + centerComputer.Update()
  245 + # This stores the centers for easy access
  246 + peel_centers = centerComputer.GetOutput()
  247 + self.peel_centers = peel_centers
  248 +
  249 + def GetNormals(self, currentPeel):
  250 + # Compute normals of triangles
  251 + normalComputer = vtk.vtkPolyDataNormals() # This computes normals of the triangles on the peel
  252 + normalComputer.SetInputData(currentPeel)
  253 + normalComputer.ComputePointNormalsOff()
  254 + normalComputer.ComputeCellNormalsOn()
  255 + normalComputer.Update()
  256 + # This converts to the normals to an array for easy access
  257 + peel_normals = normalComputer.GetOutput().GetCellData().GetNormals()
  258 + self.peel_normals = peel_normals
  259 +
216 260
217 def cleanMesh(inp): 261 def cleanMesh(inp):
218 cleaned = vtk.vtkCleanPolyData() 262 cleaned = vtk.vtkCleanPolyData()
invesalius/data/viewer_volume.py
@@ -173,6 +173,8 @@ class Viewer(wx.Panel): @@ -173,6 +173,8 @@ class Viewer(wx.Panel):
173 self.y_actor = None 173 self.y_actor = None
174 self.z_actor = None 174 self.z_actor = None
175 self.mark_actor = None 175 self.mark_actor = None
  176 + self.obj_projection_arrow_actor = None
  177 + self.object_orientation_disk_actor = None
176 178
177 self._mode_cross = False 179 self._mode_cross = False
178 self._to_show_ball = 0 180 self._to_show_ball = 0
@@ -192,6 +194,7 @@ class Viewer(wx.Panel): @@ -192,6 +194,7 @@ class Viewer(wx.Panel):
192 self.polydata = None 194 self.polydata = None
193 self.anglethreshold = const.COIL_ANGLES_THRESHOLD 195 self.anglethreshold = const.COIL_ANGLES_THRESHOLD
194 self.distthreshold = const.COIL_COORD_THRESHOLD 196 self.distthreshold = const.COIL_COORD_THRESHOLD
  197 + self.angle_arrow_projection_threshold = const.COIL_ANGLE_ARROW_PROJECTION_THRESHOLD
195 198
196 self.actor_tracts = None 199 self.actor_tracts = None
197 self.actor_peel = None 200 self.actor_peel = None
@@ -293,6 +296,7 @@ class Viewer(wx.Panel): @@ -293,6 +296,7 @@ class Viewer(wx.Panel):
293 # Related to object tracking during neuronavigation 296 # Related to object tracking during neuronavigation
294 Publisher.subscribe(self.OnNavigationStatus, 'Navigation status') 297 Publisher.subscribe(self.OnNavigationStatus, 'Navigation status')
295 Publisher.subscribe(self.UpdateObjectOrientation, 'Update object matrix') 298 Publisher.subscribe(self.UpdateObjectOrientation, 'Update object matrix')
  299 + Publisher.subscribe(self.UpdateObjectArrowOrientation, 'Update object arrow matrix')
296 Publisher.subscribe(self.UpdateTrackObjectState, 'Update track object state') 300 Publisher.subscribe(self.UpdateTrackObjectState, 'Update track object state')
297 Publisher.subscribe(self.UpdateShowObjectState, 'Update show object state') 301 Publisher.subscribe(self.UpdateShowObjectState, 'Update show object state')
298 302
@@ -310,6 +314,8 @@ class Viewer(wx.Panel): @@ -310,6 +314,8 @@ class Viewer(wx.Panel):
310 Publisher.subscribe(self.UpdateMarkerOffsetState, 'Update marker offset state') 314 Publisher.subscribe(self.UpdateMarkerOffsetState, 'Update marker offset state')
311 Publisher.subscribe(self.UpdateMarkerOffsetPosition, 'Update marker offset') 315 Publisher.subscribe(self.UpdateMarkerOffsetPosition, 'Update marker offset')
312 Publisher.subscribe(self.AddPeeledSurface, 'Update peel') 316 Publisher.subscribe(self.AddPeeledSurface, 'Update peel')
  317 + Publisher.subscribe(self.GetPeelCenters, 'Get peel centers and normals')
  318 + Publisher.subscribe(self.Initlocator_viewer, 'Get init locator')
313 319
314 Publisher.subscribe(self.load_mask_preview, 'Load mask preview') 320 Publisher.subscribe(self.load_mask_preview, 'Load mask preview')
315 Publisher.subscribe(self.remove_mask_preview, 'Remove mask preview') 321 Publisher.subscribe(self.remove_mask_preview, 'Remove mask preview')
@@ -1320,11 +1326,18 @@ class Viewer(wx.Panel): @@ -1320,11 +1326,18 @@ class Viewer(wx.Panel):
1320 self.y_actor = self.add_line([0., 0., 0.], [0., 1., 0.], color=[.0, 1.0, .0]) 1326 self.y_actor = self.add_line([0., 0., 0.], [0., 1., 0.], color=[.0, 1.0, .0])
1321 self.z_actor = self.add_line([0., 0., 0.], [0., 0., 1.], color=[1.0, .0, .0]) 1327 self.z_actor = self.add_line([0., 0., 0.], [0., 0., 1.], color=[1.0, .0, .0])
1322 1328
  1329 + self.obj_projection_arrow_actor = self.Add_ObjectArrow([0., 0., 0.], [0., 0., 0.], vtk_colors.GetColor3d('Red'),
  1330 + 15)
  1331 + self.object_orientation_disk_actor = self.Add_Object_Orientation_Disk([0., 0., 0.], [0., 0., 0.],
  1332 + vtk_colors.GetColor3d('Red'))
  1333 + #self.obj_projection_arrow_actor.SetVisibility(False)
  1334 + #self.object_orientation_disk_actor.SetVisibility(False)
1323 self.ren.AddActor(self.obj_actor) 1335 self.ren.AddActor(self.obj_actor)
1324 self.ren.AddActor(self.x_actor) 1336 self.ren.AddActor(self.x_actor)
1325 self.ren.AddActor(self.y_actor) 1337 self.ren.AddActor(self.y_actor)
1326 self.ren.AddActor(self.z_actor) 1338 self.ren.AddActor(self.z_actor)
1327 - 1339 + #self.ren.AddActor(self.obj_projection_arrow_actor)
  1340 + #self.ren.AddActor(self.object_orientation_disk_actor)
1328 # self.obj_axes = vtk.vtkAxesActor() 1341 # self.obj_axes = vtk.vtkAxesActor()
1329 # self.obj_axes.SetShaftTypeToCylinder() 1342 # self.obj_axes.SetShaftTypeToCylinder()
1330 # self.obj_axes.SetXAxisLabelText("x") 1343 # self.obj_axes.SetXAxisLabelText("x")
@@ -1334,6 +1347,63 @@ class Viewer(wx.Panel): @@ -1334,6 +1347,63 @@ class Viewer(wx.Panel):
1334 1347
1335 # self.ren.AddActor(self.obj_axes) 1348 # self.ren.AddActor(self.obj_axes)
1336 1349
  1350 + def Add_Object_Orientation_Disk(self, position, orientation, color=[0.0, 0.0, 1.0]):
  1351 + # Create a disk to show target
  1352 + disk = vtk.vtkDiskSource()
  1353 + disk.SetInnerRadius(5)
  1354 + disk.SetOuterRadius(15)
  1355 + disk.SetRadialResolution(100)
  1356 + disk.SetCircumferentialResolution(100)
  1357 + disk.Update()
  1358 +
  1359 + disk_mapper = vtk.vtkPolyDataMapper()
  1360 + disk_mapper.SetInputData(disk.GetOutput())
  1361 + disk_actor = vtk.vtkActor()
  1362 + disk_actor.SetMapper(disk_mapper)
  1363 + disk_actor.GetProperty().SetColor(color)
  1364 + disk_actor.GetProperty().SetOpacity(1)
  1365 + disk_actor.SetPosition(position)
  1366 + disk_actor.SetOrientation(orientation)
  1367 +
  1368 + return disk_actor
  1369 +
  1370 + def Add_ObjectArrow(self, direction, orientation, color=[0.0, 0.0, 1.0], size=2):
  1371 + vtk_colors = vtk.vtkNamedColors()
  1372 +
  1373 + arrow = vtk.vtkArrowSource()
  1374 +
  1375 + mapper = vtk.vtkPolyDataMapper()
  1376 + mapper.SetInputConnection(arrow.GetOutputPort())
  1377 +
  1378 + actor = vtk.vtkActor()
  1379 + actor.SetMapper(mapper)
  1380 + actor.GetProperty().SetColor(color)
  1381 + actor.GetProperty().SetLineWidth(5)
  1382 + actor.AddPosition(0, 0, 0)
  1383 + actor.SetScale(size)
  1384 + actor.SetPosition(direction)
  1385 + actor.SetOrientation(orientation)
  1386 +
  1387 + return actor
  1388 +
  1389 + def ObjectArrowLocation(self, m_img, coord):
  1390 + # m_img[:3, 0] is from posterior to anterior direction of the coil
  1391 + # m_img[:3, 1] is from left to right direction of the coil
  1392 + # m_img[:3, 2] is from bottom to up direction of the coil
  1393 + vec_length = 70
  1394 + m_img_flip = m_img.copy()
  1395 + m_img_flip[1, -1] = -m_img_flip[1, -1]
  1396 + p1 = m_img_flip[:-1, -1] # coil center
  1397 + coil_dir = m_img_flip[:-1, 0]
  1398 + coil_face = m_img_flip[:-1, 1]
  1399 +
  1400 + coil_norm = np.cross(coil_dir, coil_face)
  1401 + p2_norm = p1 - vec_length * coil_norm # point normal to the coil away from the center by vec_length
  1402 + coil_dir = np.array([coord[3], coord[4], coord[5]])
  1403 +
  1404 + return coil_dir, p2_norm, coil_norm, p1
  1405 +
  1406 +
1337 def add_line(self, p1, p2, color=[0.0, 0.0, 1.0]): 1407 def add_line(self, p1, p2, color=[0.0, 0.0, 1.0]):
1338 line = vtk.vtkLineSource() 1408 line = vtk.vtkLineSource()
1339 line.SetPoint1(p1) 1409 line.SetPoint1(p1)
@@ -1357,6 +1427,80 @@ class Viewer(wx.Panel): @@ -1357,6 +1427,80 @@ class Viewer(wx.Panel):
1357 self.actor_peel = actor 1427 self.actor_peel = actor
1358 self.Refresh() 1428 self.Refresh()
1359 1429
  1430 + def GetPeelCenters(self, centers, normals):
  1431 + self.peel_centers = centers
  1432 + self.peel_normals = normals
  1433 +
  1434 + self.Refresh()
  1435 +
  1436 + def Initlocator_viewer(self, locator):
  1437 + self.locator = locator
  1438 + self.Refresh()
  1439 +
  1440 + def GetCellIntersection(self, p1, p2, coil_norm, coil_dir):
  1441 +
  1442 + vtk_colors = vtk.vtkNamedColors()
  1443 + # This find store the triangles that intersect the coil's normal
  1444 + intersectingCellIds = vtk.vtkIdList()
  1445 +
  1446 + #for debugging
  1447 + self.x_actor = self.add_line(p1,p2,vtk_colors.GetColor3d('Blue'))
  1448 + #self.ren.AddActor(self.x_actor) # remove comment for testing
  1449 +
  1450 + self.locator.FindCellsAlongLine(p1, p2, .001, intersectingCellIds)
  1451 +
  1452 + closestDist = 50
  1453 +
  1454 + #if find intersection , calculate angle and add actors
  1455 + if intersectingCellIds.GetNumberOfIds() != 0:
  1456 + for i in range(intersectingCellIds.GetNumberOfIds()):
  1457 + cellId = intersectingCellIds.GetId(i)
  1458 + point = np.array(self.peel_centers.GetPoint(cellId))
  1459 + distance = np.linalg.norm(point - p1)
  1460 +
  1461 + #print('distance:', distance, point - p1)
  1462 + self.ren.RemoveActor(self.y_actor)
  1463 +
  1464 + if distance < closestDist:
  1465 + closestDist = distance
  1466 + closestPoint = point
  1467 + pointnormal = np.array(self.peel_normals.GetTuple(cellId))
  1468 + angle = np.rad2deg(np.arccos(np.dot(pointnormal, coil_norm)))
  1469 + #print('the angle:', angle)
  1470 +
  1471 + #for debbuging
  1472 + self.y_actor = self.add_line(closestPoint, closestPoint + 75 * pointnormal,
  1473 + vtk_colors.GetColor3d('Yellow'))
  1474 +
  1475 + #self.ren.AddActor(self.y_actor)# remove comment for testing
  1476 +
  1477 +
  1478 + self.ren.AddActor(self.obj_projection_arrow_actor)
  1479 + self.ren.AddActor(self.object_orientation_disk_actor)
  1480 +
  1481 + self.obj_projection_arrow_actor.SetPosition(closestPoint)
  1482 + self.obj_projection_arrow_actor.SetOrientation(coil_dir)
  1483 +
  1484 + self.object_orientation_disk_actor.SetPosition(closestPoint)
  1485 + self.object_orientation_disk_actor.SetOrientation(coil_dir)
  1486 +
  1487 + # change color of arrow and disk according to angle
  1488 + if angle < self.angle_arrow_projection_threshold:
  1489 + self.object_orientation_disk_actor.GetProperty().SetColor(vtk_colors.GetColor3d('Green'))
  1490 + self.obj_projection_arrow_actor.GetProperty().SetColor(vtk_colors.GetColor3d('Green'))
  1491 + else:
  1492 + self.object_orientation_disk_actor.GetProperty().SetColor(vtk_colors.GetColor3d('indian_red'))
  1493 + self.obj_projection_arrow_actor.GetProperty().SetColor(vtk_colors.GetColor3d('indian_red'))
  1494 + else:
  1495 + self.ren.RemoveActor(self.y_actor)
  1496 +
  1497 + else:
  1498 + self.ren.RemoveActor(self.obj_projection_arrow_actor)
  1499 + self.ren.RemoveActor(self.object_orientation_disk_actor)
  1500 + self.ren.RemoveActor(self.x_actor)
  1501 + #self.ren.RemoveActor(self.y_actor)
  1502 + self.Refresh()
  1503 +
1360 def OnNavigationStatus(self, nav_status, vis_status): 1504 def OnNavigationStatus(self, nav_status, vis_status):
1361 self.nav_status = nav_status 1505 self.nav_status = nav_status
1362 self.tracts_status = vis_status[1] 1506 self.tracts_status = vis_status[1]
@@ -1368,7 +1512,8 @@ class Viewer(wx.Panel): @@ -1368,7 +1512,8 @@ class Viewer(wx.Panel):
1368 self.x_actor.SetVisibility(self.obj_state) 1512 self.x_actor.SetVisibility(self.obj_state)
1369 self.y_actor.SetVisibility(self.obj_state) 1513 self.y_actor.SetVisibility(self.obj_state)
1370 self.z_actor.SetVisibility(self.obj_state) 1514 self.z_actor.SetVisibility(self.obj_state)
1371 - 1515 + #self.object_orientation_disk_actor.SetVisibility(self.obj_state)
  1516 + #self.obj_projection_arrow_actor.SetVisibility(self.obj_state)
1372 self.Refresh() 1517 self.Refresh()
1373 1518
1374 def UpdateSeedOffset(self, data): 1519 def UpdateSeedOffset(self, data):
@@ -1419,6 +1564,19 @@ class Viewer(wx.Panel): @@ -1419,6 +1564,19 @@ class Viewer(wx.Panel):
1419 1564
1420 self.Refresh() 1565 self.Refresh()
1421 1566
  1567 +
  1568 + def UpdateObjectArrowOrientation(self, m_img, coord, flag):
  1569 +
  1570 + [coil_dir, norm, coil_norm, p1 ]= self.ObjectArrowLocation(m_img,coord)
  1571 +
  1572 + if flag:
  1573 + self.ren.RemoveActor(self.x_actor)
  1574 + #self.ren.RemoveActor(self.y_actor)
  1575 + self.ren.RemoveActor(self.obj_projection_arrow_actor)
  1576 + self.ren.RemoveActor(self.object_orientation_disk_actor)
  1577 + self.GetCellIntersection(p1, norm, coil_norm, coil_dir)
  1578 + self.Refresh()
  1579 +
1422 def UpdateTrackObjectState(self, evt=None, flag=None, obj_name=None, polydata=None): 1580 def UpdateTrackObjectState(self, evt=None, flag=None, obj_name=None, polydata=None):
1423 if flag: 1581 if flag:
1424 self.obj_name = obj_name 1582 self.obj_name = obj_name
@@ -1432,11 +1590,15 @@ class Viewer(wx.Panel): @@ -1432,11 +1590,15 @@ class Viewer(wx.Panel):
1432 self.ren.RemoveActor(self.y_actor) 1590 self.ren.RemoveActor(self.y_actor)
1433 self.ren.RemoveActor(self.z_actor) 1591 self.ren.RemoveActor(self.z_actor)
1434 self.ren.RemoveActor(self.mark_actor) 1592 self.ren.RemoveActor(self.mark_actor)
  1593 + self.ren.RemoveActor(self.obj_projection_arrow_actor)
  1594 + self.ren.RemoveActor(self.object_orientation_disk_actor)
1435 self.obj_actor = None 1595 self.obj_actor = None
1436 self.x_actor = None 1596 self.x_actor = None
1437 self.y_actor = None 1597 self.y_actor = None
1438 self.z_actor = None 1598 self.z_actor = None
1439 self.mark_actor = None 1599 self.mark_actor = None
  1600 + self.obj_projection_arrow_actor = None
  1601 + self.object_orientation_disk_actor=None
1440 self.Refresh() 1602 self.Refresh()
1441 1603
1442 def UpdateShowObjectState(self, state): 1604 def UpdateShowObjectState(self, state):
invesalius/gui/task_navigator.py
@@ -326,6 +326,7 @@ class Navigation(): @@ -326,6 +326,7 @@ class Navigation():
326 self.trekker = None 326 self.trekker = None
327 self.n_threads = None 327 self.n_threads = None
328 self.view_tracts = False 328 self.view_tracts = False
  329 + self.peel_loaded = False
329 self.enable_act = False 330 self.enable_act = False
330 self.act_data = None 331 self.act_data = None
331 self.n_tracts = const.N_TRACTS 332 self.n_tracts = const.N_TRACTS
@@ -363,7 +364,7 @@ class Navigation(): @@ -363,7 +364,7 @@ class Navigation():
363 if self.event.is_set(): 364 if self.event.is_set():
364 self.event.clear() 365 self.event.clear()
365 366
366 - vis_components = [self.trigger_state, self.view_tracts] 367 + vis_components = [self.trigger_state, self.view_tracts, self.peel_loaded]
367 vis_queues = [self.coord_queue, self.trigger_queue, self.tracts_queue, self.icp_queue] 368 vis_queues = [self.coord_queue, self.trigger_queue, self.tracts_queue, self.icp_queue]
368 369
369 Publisher.sendMessage("Navigation status", nav_status=True, vis_status=vis_components) 370 Publisher.sendMessage("Navigation status", nav_status=True, vis_status=vis_components)
@@ -458,7 +459,7 @@ class Navigation(): @@ -458,7 +459,7 @@ class Navigation():
458 self.tracts_queue.clear() 459 self.tracts_queue.clear()
459 self.tracts_queue.join() 460 self.tracts_queue.join()
460 461
461 - vis_components = [self.trigger_state, self.view_tracts] 462 + vis_components = [self.trigger_state, self.view_tracts, self.peel_loaded]
462 Publisher.sendMessage("Navigation status", nav_status=False, vis_status=vis_components) 463 Publisher.sendMessage("Navigation status", nav_status=False, vis_status=vis_components)
463 464
464 465
@@ -870,6 +871,7 @@ class NeuronavigationPanel(wx.Panel): @@ -870,6 +871,7 @@ class NeuronavigationPanel(wx.Panel):
870 Publisher.subscribe(self.UpdateSleep, 'Update sleep') 871 Publisher.subscribe(self.UpdateSleep, 'Update sleep')
871 Publisher.subscribe(self.UpdateNumberThreads, 'Update number of threads') 872 Publisher.subscribe(self.UpdateNumberThreads, 'Update number of threads')
872 Publisher.subscribe(self.UpdateTractsVisualization, 'Update tracts visualization') 873 Publisher.subscribe(self.UpdateTractsVisualization, 'Update tracts visualization')
  874 + Publisher.subscribe(self.UpdatePeelVisualization, 'Update peel visualization')
873 Publisher.subscribe(self.EnableACT, 'Enable ACT') 875 Publisher.subscribe(self.EnableACT, 'Enable ACT')
874 Publisher.subscribe(self.UpdateACTData, 'Update ACT data') 876 Publisher.subscribe(self.UpdateACTData, 'Update ACT data')
875 Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status') 877 Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status')
@@ -918,6 +920,10 @@ class NeuronavigationPanel(wx.Panel): @@ -918,6 +920,10 @@ class NeuronavigationPanel(wx.Panel):
918 self.ResetICP() 920 self.ResetICP()
919 self.tracker.UpdateUI(self.select_tracker_elem, self.numctrls_coord[3:6], self.txtctrl_fre) 921 self.tracker.UpdateUI(self.select_tracker_elem, self.numctrls_coord[3:6], self.txtctrl_fre)
920 922
  923 +
  924 + def UpdatePeelVisualization(self, data):
  925 + self.navigation.peel_loaded = data
  926 +
921 def UpdateNavigationStatus(self, nav_status, vis_status): 927 def UpdateNavigationStatus(self, nav_status, vis_status):
922 self.nav_status = nav_status 928 self.nav_status = nav_status
923 if nav_status and self.icp.m_icp is not None: 929 if nav_status and self.icp.m_icp is not None:
@@ -1044,6 +1050,7 @@ class NeuronavigationPanel(wx.Panel): @@ -1044,6 +1050,7 @@ class NeuronavigationPanel(wx.Panel):
1044 self.navigation.UpdateFiducialRegistrationError(self.tracker) 1050 self.navigation.UpdateFiducialRegistrationError(self.tracker)
1045 fre, fre_ok = self.navigation.GetFiducialRegistrationError(self.icp) 1051 fre, fre_ok = self.navigation.GetFiducialRegistrationError(self.icp)
1046 1052
  1053 +
1047 self.txtctrl_fre.SetValue(str(round(fre, 2))) 1054 self.txtctrl_fre.SetValue(str(round(fre, 2)))
1048 if fre_ok: 1055 if fre_ok:
1049 self.txtctrl_fre.SetBackgroundColour('GREEN') 1056 self.txtctrl_fre.SetBackgroundColour('GREEN')
@@ -1862,7 +1869,7 @@ class TractographyPanel(wx.Panel): @@ -1862,7 +1869,7 @@ class TractographyPanel(wx.Panel):
1862 self.tracts_run = None 1869 self.tracts_run = None
1863 self.trekker_cfg = const.TREKKER_CONFIG 1870 self.trekker_cfg = const.TREKKER_CONFIG
1864 self.nav_status = False 1871 self.nav_status = False
1865 - 1872 + self.peel_loaded = False
1866 self.SetAutoLayout(1) 1873 self.SetAutoLayout(1)
1867 self.__bind_events() 1874 self.__bind_events()
1868 1875
@@ -2040,9 +2047,12 @@ class TractographyPanel(wx.Panel): @@ -2040,9 +2047,12 @@ class TractographyPanel(wx.Panel):
2040 def OnSelectPeelingDepth(self, evt, ctrl): 2047 def OnSelectPeelingDepth(self, evt, ctrl):
2041 self.peel_depth = ctrl.GetValue() 2048 self.peel_depth = ctrl.GetValue()
2042 if self.checkpeeling.GetValue(): 2049 if self.checkpeeling.GetValue():
2043 - actor = self.brain_peel.get_actor(self.peel_depth) 2050 + actor = self.brain_peel.get_actor(self.peel_depth, self.affine_vtk)
2044 Publisher.sendMessage('Update peel', flag=True, actor=actor) 2051 Publisher.sendMessage('Update peel', flag=True, actor=actor)
2045 - 2052 + Publisher.sendMessage('Get peel centers and normals', centers=self.brain_peel.peel_centers,
  2053 + normals=self.brain_peel.peel_normals)
  2054 + Publisher.sendMessage('Get init locator', locator=self.brain_peel.locator)
  2055 + self.peel_loaded = True
2046 def OnSelectNumTracts(self, evt, ctrl): 2056 def OnSelectNumTracts(self, evt, ctrl):
2047 self.n_tracts = ctrl.GetValue() 2057 self.n_tracts = ctrl.GetValue()
2048 # self.tract.n_tracts = ctrl.GetValue() 2058 # self.tract.n_tracts = ctrl.GetValue()
@@ -2070,7 +2080,7 @@ class TractographyPanel(wx.Panel): @@ -2070,7 +2080,7 @@ class TractographyPanel(wx.Panel):
2070 def OnShowPeeling(self, evt, ctrl): 2080 def OnShowPeeling(self, evt, ctrl):
2071 # self.view_peeling = ctrl.GetValue() 2081 # self.view_peeling = ctrl.GetValue()
2072 if ctrl.GetValue(): 2082 if ctrl.GetValue():
2073 - actor = self.brain_peel.get_actor(self.peel_depth) 2083 + actor = self.brain_peel.get_actor(self.peel_depth, self.affine_vtk)
2074 else: 2084 else:
2075 actor = None 2085 actor = None
2076 Publisher.sendMessage('Update peel', flag=ctrl.GetValue(), actor=actor) 2086 Publisher.sendMessage('Update peel', flag=ctrl.GetValue(), actor=actor)
@@ -2114,13 +2124,18 @@ class TractographyPanel(wx.Panel): @@ -2114,13 +2124,18 @@ class TractographyPanel(wx.Panel):
2114 2124
2115 try: 2125 try:
2116 self.brain_peel = brain.Brain(img_path, mask_path, self.n_peels, self.affine_vtk) 2126 self.brain_peel = brain.Brain(img_path, mask_path, self.n_peels, self.affine_vtk)
2117 - self.brain_actor = self.brain_peel.get_actor(self.peel_depth) 2127 + self.brain_actor = self.brain_peel.get_actor(self.peel_depth, self.affine_vtk)
2118 self.brain_actor.GetProperty().SetOpacity(self.brain_opacity) 2128 self.brain_actor.GetProperty().SetOpacity(self.brain_opacity)
2119 Publisher.sendMessage('Update peel', flag=True, actor=self.brain_actor) 2129 Publisher.sendMessage('Update peel', flag=True, actor=self.brain_actor)
  2130 + Publisher.sendMessage('Get peel centers and normals', centers=self.brain_peel.peel_centers,
  2131 + normals=self.brain_peel.peel_normals)
  2132 + Publisher.sendMessage('Get init locator', locator=self.brain_peel.locator)
2120 self.checkpeeling.Enable(1) 2133 self.checkpeeling.Enable(1)
2121 self.checkpeeling.SetValue(True) 2134 self.checkpeeling.SetValue(True)
2122 self.spin_opacity.Enable(1) 2135 self.spin_opacity.Enable(1)
2123 Publisher.sendMessage('Update status text in GUI', label=_("Brain model loaded")) 2136 Publisher.sendMessage('Update status text in GUI', label=_("Brain model loaded"))
  2137 + self.peel_loaded = True
  2138 + Publisher.sendMessage('Update peel visualization', data= self.peel_loaded)
2124 except: 2139 except:
2125 wx.MessageBox(_("Unable to load brain mask."), _("InVesalius 3")) 2140 wx.MessageBox(_("Unable to load brain mask."), _("InVesalius 3"))
2126 2141
@@ -2308,7 +2323,7 @@ class UpdateNavigationScene(threading.Thread): @@ -2308,7 +2323,7 @@ class UpdateNavigationScene(threading.Thread):
2308 """ 2323 """
2309 2324
2310 threading.Thread.__init__(self, name='UpdateScene') 2325 threading.Thread.__init__(self, name='UpdateScene')
2311 - self.trigger_state, self.view_tracts = vis_components 2326 + self.trigger_state, self.view_tracts, self.peel_loaded = vis_components
2312 self.coord_queue, self.trigger_queue, self.tracts_queue, self.icp_queue = vis_queues 2327 self.coord_queue, self.trigger_queue, self.tracts_queue, self.icp_queue = vis_queues
2313 self.sle = sle 2328 self.sle = sle
2314 self.event = event 2329 self.event = event
@@ -2347,7 +2362,7 @@ class UpdateNavigationScene(threading.Thread): @@ -2347,7 +2362,7 @@ class UpdateNavigationScene(threading.Thread):
2347 2362
2348 if view_obj: 2363 if view_obj:
2349 wx.CallAfter(Publisher.sendMessage, 'Update object matrix', m_img=m_img, coord=coord) 2364 wx.CallAfter(Publisher.sendMessage, 'Update object matrix', m_img=m_img, coord=coord)
2350 - 2365 + wx.CallAfter(Publisher.sendMessage, 'Update object arrow matrix',m_img=m_img, coord=coord, flag= self.peel_loaded)
2351 self.coord_queue.task_done() 2366 self.coord_queue.task_done()
2352 # print('UpdateScene: done {}'.format(count)) 2367 # print('UpdateScene: done {}'.format(count))
2353 # count += 1 2368 # count += 1