Commit e6cce6f8d99e23470088a65b61439ff11d1ffdc7

Authored by rmatsuda
2 parents 5451252f 864ae429
Exists in master

Merge branch 'master' into icp

# Conflicts:
#	invesalius/constants.py
#	invesalius/data/coregistration.py
#	invesalius/gui/task_navigator.py
invesalius/constants.py
... ... @@ -529,6 +529,7 @@ ID_MANUAL_WWWL = wx.NewId()
529 529 ID_TREKKER_MASK = wx.NewId()
530 530 ID_TREKKER_IMG = wx.NewId()
531 531 ID_TREKKER_FOD = wx.NewId()
  532 +ID_TREKKER_ACT = wx.NewId()
532 533  
533 534 #---------------------------------------------------------
534 535 STATE_DEFAULT = 1000
... ... @@ -752,7 +753,7 @@ PEEL_DEPTH = 5
752 753 MAX_PEEL_DEPTH = 30
753 754 SEED_OFFSET = 15
754 755 SEED_RADIUS = 1.5
755   -SLEEP_NAVIGATION = 0.175
  756 +SLEEP_NAVIGATION = 0.3
756 757 BRAIN_OPACITY = 0.5
757 758 N_CPU = psutil.cpu_count()
758 759 TREKKER_CONFIG = {'seed_max': 1, 'step_size': 0.1, 'min_fod': 0.1, 'probe_quality': 3,
... ...
invesalius/control.py
... ... @@ -1071,7 +1071,8 @@ class Controller():
1071 1071 if not os.path.isfile(path):
1072 1072 path = os.path.join(inv_paths.USER_RAYCASTING_PRESETS_DIRECTORY,
1073 1073 preset_name+".plist")
1074   - preset = plistlib.readPlist(path)
  1074 + with open(path, 'r+b') as f:
  1075 + preset = plistlib.load(f, fmt=plistlib.FMT_XML)
1075 1076 prj.Project().raycasting_preset = preset
1076 1077 # Notify volume
1077 1078 # TODO: Chamar grafico tb!
... ... @@ -1085,7 +1086,8 @@ class Controller():
1085 1086 preset['name'] = preset_name
1086 1087 preset_dir = os.path.join(inv_paths.USER_RAYCASTING_PRESETS_DIRECTORY,
1087 1088 preset_name + '.plist')
1088   - plistlib.writePlist(preset, preset_dir)
  1089 + with open(preset_dir, 'w+b') as f:
  1090 + plistlib.dump(preset, f)
1089 1091  
1090 1092 def ShowBooleanOpDialog(self):
1091 1093 dlg = dialogs.MaskBooleanDialog(prj.Project().mask_dict)
... ...
invesalius/data/bases.py
... ... @@ -203,45 +203,9 @@ def calculate_fre(fiducials_raw, fiducials, ref_mode_id, m_change, m_icp=None):
203 203 return float(np.sqrt(np.sum(dist ** 2) / 3))
204 204  
205 205  
206   -# def flip_x(point):
207   -# """
208   -# Flip coordinates of a vector according to X axis
209   -# Coronal Images do not require this transformation - 1 tested
210   -# and for this case, at navigation, the z axis is inverted
211   -#
212   -# It's necessary to multiply the z coordinate by (-1). Possibly
213   -# because the origin of coordinate system of imagedata is
214   -# located in superior left corner and the origin of VTK scene coordinate
215   -# system (polygonal surface) is in the interior left corner. Second
216   -# possibility is the order of slice stacking
217   -#
218   -# :param point: list of coordinates x, y and z
219   -# :return: flipped coordinates
220   -# """
221   -#
222   -# # TODO: check if the Flip function is related to the X or Y axis
223   -#
224   -# point = np.matrix(point + (0,))
225   -# point[0, 2] = -point[0, 2]
226   -#
227   -# m_rot = np.matrix([[1.0, 0.0, 0.0, 0.0],
228   -# [0.0, -1.0, 0.0, 0.0],
229   -# [0.0, 0.0, -1.0, 0.0],
230   -# [0.0, 0.0, 0.0, 1.0]])
231   -# m_trans = np.matrix([[1.0, 0, 0, -point[0, 0]],
232   -# [0.0, 1.0, 0, -point[0, 1]],
233   -# [0.0, 0.0, 1.0, -point[0, 2]],
234   -# [0.0, 0.0, 0.0, 1.0]])
235   -# m_trans_return = np.matrix([[1.0, 0, 0, point[0, 0]],
236   -# [0.0, 1.0, 0, point[0, 1]],
237   -# [0.0, 0.0, 1.0, point[0, 2]],
238   -# [0.0, 0.0, 0.0, 1.0]])
239   -#
240   -# point_rot = point*m_trans*m_rot*m_trans_return
241   -# x, y, z = point_rot.tolist()[0][:3]
242   -#
243   -# return x, y, z
244   -
  206 +# The function flip_x_m is deprecated and was replaced by a simple minus multiplication of the Y coordinate as follows:
  207 +# coord_flip = list(coord)
  208 +# coord_flip[1] = -coord_flip[1]
245 209  
246 210 # def flip_x_m(point):
247 211 # """
... ... @@ -257,38 +221,14 @@ def calculate_fre(fiducials_raw, fiducials, ref_mode_id, m_change, m_icp=None):
257 221 # :return: rotated coordinates
258 222 # """
259 223 #
260   -# point_4 = np.hstack((point, 1.)).reshape([4, 1])
  224 +# point_4 = np.hstack((point, 1.)).reshape(4, 1)
261 225 # point_4[2, 0] = -point_4[2, 0]
262 226 #
263   -# m_rot = tr.euler_matrix(pi, 0, 0)
  227 +# m_rot = tr.euler_matrix(np.pi, 0, 0)
264 228 #
265 229 # point_rot = m_rot @ point_4
266 230 #
267   -# return point_rot[0, 0], point_rot[1, 0], point_rot[2, 0]
268   -
269   -
270   -def flip_x_m(point):
271   - """
272   - Rotate coordinates of a vector by pi around X axis in static reference frame.
273   -
274   - InVesalius also require to multiply the z coordinate by (-1). Possibly
275   - because the origin of coordinate system of imagedata is
276   - located in superior left corner and the origin of VTK scene coordinate
277   - system (polygonal surface) is in the interior left corner. Second
278   - possibility is the order of slice stacking
279   -
280   - :param point: list of coordinates x, y and z
281   - :return: rotated coordinates
282   - """
283   -
284   - point_4 = np.hstack((point, 1.)).reshape(4, 1)
285   - point_4[2, 0] = -point_4[2, 0]
286   -
287   - m_rot = tr.euler_matrix(np.pi, 0, 0)
288   -
289   - point_rot = m_rot @ point_4
290   -
291   - return point_rot
  231 +# return point_rot
292 232  
293 233 def transform_icp(m_img, m_icp):
294 234 coord_img = flip_x_m([m_img[0, -1], m_img[1, -1], m_img[2, -1]])
... ... @@ -351,9 +291,10 @@ def object_registration(fiducials, orients, coord_raw, m_change):
351 291 M_img = m_change @ M_p
352 292  
353 293 angles_img = np.degrees(np.asarray(tr.euler_from_matrix(M_img, 'rzyx')))
354   - coord_img = flip_x_m(tr.translation_from_matrix(M_img))
  294 + coord_img = list(M_img[:3, -1])
  295 + coord_img[1] = -coord_img[1]
355 296  
356   - fids_img[ic, :] = np.hstack((coord_img[:3, 0], angles_img))
  297 + fids_img[ic, :] = np.hstack((coord_img, angles_img))
357 298  
358 299 # compute object base change in vtk head frame
359 300 base_obj_img, _ = base_creation(fids_img[:3, :3])
... ...
invesalius/data/brainmesh_handler.py 0 → 100644
... ... @@ -0,0 +1,304 @@
  1 +import vtk
  2 +import pyacvd
  3 +# import os
  4 +import pyvista
  5 +# import numpy as np
  6 +# import Trekker
  7 +
  8 +
  9 +class Brain:
  10 + def __init__(self, img_path, mask_path, n_peels, affine_vtk):
  11 + self.peel = []
  12 + self.peelActors = []
  13 +
  14 + T1_reader = vtk.vtkNIFTIImageReader()
  15 + T1_reader.SetFileName(img_path)
  16 + T1_reader.Update()
  17 +
  18 + # self.refImage = vtk.vtkImageData()
  19 + self.refImage = T1_reader.GetOutput()
  20 +
  21 + mask_reader = vtk.vtkNIFTIImageReader()
  22 + mask_reader.SetFileName(mask_path)
  23 + mask_reader.Update()
  24 +
  25 + mc = vtk.vtkContourFilter()
  26 + mc.SetInputConnection(mask_reader.GetOutputPort())
  27 + mc.SetValue(0, 1)
  28 + mc.Update()
  29 +
  30 + refSurface = vtk.vtkPolyData()
  31 + refSurface = mc.GetOutput()
  32 +
  33 + tmpPeel = vtk.vtkPolyData()
  34 + tmpPeel = downsample(refSurface)
  35 +
  36 + mask_sFormMatrix = vtk.vtkMatrix4x4()
  37 + mask_sFormMatrix = mask_reader.GetSFormMatrix()
  38 +
  39 + mask_ijk2xyz = vtk.vtkTransform()
  40 + mask_ijk2xyz.SetMatrix(mask_sFormMatrix)
  41 +
  42 + mask_ijk2xyz_filter = vtk.vtkTransformPolyDataFilter()
  43 + mask_ijk2xyz_filter.SetInputData(tmpPeel)
  44 + mask_ijk2xyz_filter.SetTransform(mask_ijk2xyz)
  45 + mask_ijk2xyz_filter.Update()
  46 +
  47 + tmpPeel = smooth(mask_ijk2xyz_filter.GetOutput())
  48 + tmpPeel = fixMesh(tmpPeel)
  49 + tmpPeel = cleanMesh(tmpPeel)
  50 + tmpPeel = upsample(tmpPeel)
  51 + tmpPeel = smooth(tmpPeel)
  52 + tmpPeel = fixMesh(tmpPeel)
  53 + tmpPeel = cleanMesh(tmpPeel)
  54 +
  55 + # sFormMatrix = vtk.vtkMatrix4x4()
  56 + qFormMatrix = T1_reader.GetQFormMatrix()
  57 + # sFormMatrix = T1_reader.GetSFormMatrix()
  58 +
  59 + refImageSpace2_xyz_transform = vtk.vtkTransform()
  60 + refImageSpace2_xyz_transform.SetMatrix(qFormMatrix)
  61 +
  62 + self.refImageSpace2_xyz = vtk.vtkTransformPolyDataFilter()
  63 + self.refImageSpace2_xyz.SetTransform(refImageSpace2_xyz_transform)
  64 +
  65 + xyz2_refImageSpace_transform = vtk.vtkTransform()
  66 + qFormMatrix.Invert()
  67 + xyz2_refImageSpace_transform.SetMatrix(qFormMatrix)
  68 +
  69 + self.xyz2_refImageSpace = vtk.vtkTransformPolyDataFilter()
  70 + self.xyz2_refImageSpace.SetTransform(xyz2_refImageSpace_transform)
  71 +
  72 + # self.currentPeel = vtk.vtkPolyData()
  73 + self.currentPeel = tmpPeel
  74 + self.currentPeelNo = 0
  75 + self.mapImageOnCurrentPeel()
  76 +
  77 + newPeel = vtk.vtkPolyData()
  78 + newPeel.DeepCopy(self.currentPeel)
  79 + self.peel.append(newPeel)
  80 + self.currentPeelActor = vtk.vtkActor()
  81 + self.currentPeelActor.SetUserMatrix(affine_vtk)
  82 + self.getCurrentPeelActor()
  83 + self.peelActors.append(self.currentPeelActor)
  84 +
  85 + self.numberOfPeels = n_peels
  86 + self.peelDown()
  87 +
  88 + def get_actor(self, n):
  89 + return self.getPeelActor(n)
  90 +
  91 + def sliceDown(self):
  92 + # Warp using the normals
  93 + warp = vtk.vtkWarpVector()
  94 + warp.SetInputData(fixMesh(downsample(self.currentPeel))) # fixMesh here updates normals needed for warping
  95 + warp.SetInputArrayToProcess(0, 0, 0, vtk.vtkDataObject().FIELD_ASSOCIATION_POINTS,
  96 + vtk.vtkDataSetAttributes().NORMALS)
  97 + warp.SetScaleFactor(-1)
  98 + warp.Update()
  99 +
  100 + out = vtk.vtkPolyData()
  101 + out = upsample(warp.GetPolyDataOutput())
  102 + out = smooth(out)
  103 + out = fixMesh(out)
  104 + out = cleanMesh(out)
  105 +
  106 + self.currentPeel = out
  107 +
  108 + # def sliceUp(self):
  109 + # # Warp using the normals
  110 + # warp = vtk.vtkWarpVector()
  111 + # # warp.SetInputData(fixMesh(downsample(currentPeel))) # fixMesh here updates normals needed for warping
  112 + # warp.SetInputArrayToProcess(0, 0, 0, vtk.vtkDataObject().FIELD_ASSOCIATION_POINTS,
  113 + # vtk.vtkDataSetAttributes().NORMALS)
  114 + # warp.SetScaleFactor(1)
  115 + # warp.Update()
  116 + #
  117 + # out = vtk.vtkPolyData()
  118 + # out = upsample(warp.GetPolyDataOutput())
  119 + # out = smooth(out)
  120 + # out = fixMesh(out)
  121 + # out = cleanMesh(out)
  122 + #
  123 + # currentPeel = out
  124 +
  125 + def mapImageOnCurrentPeel(self):
  126 + self.xyz2_refImageSpace.SetInputData(self.currentPeel)
  127 + self.xyz2_refImageSpace.Update()
  128 +
  129 + probe = vtk.vtkProbeFilter()
  130 + probe.SetInputData(self.xyz2_refImageSpace.GetOutput())
  131 + probe.SetSourceData(self.refImage)
  132 + probe.Update()
  133 +
  134 + self.refImageSpace2_xyz.SetInputData(probe.GetOutput())
  135 + self.refImageSpace2_xyz.Update()
  136 +
  137 + self.currentPeel = self.refImageSpace2_xyz.GetOutput()
  138 +
  139 + def peelDown(self):
  140 + for i in range(0, self.numberOfPeels):
  141 + self.sliceDown()
  142 + self.mapImageOnCurrentPeel()
  143 +
  144 + newPeel = vtk.vtkPolyData()
  145 + newPeel.DeepCopy(self.currentPeel)
  146 + self.peel.append(newPeel)
  147 +
  148 + # getCurrentPeelActor()
  149 + # newPeelActor = vtk.vtkActor()
  150 + # newPeelActor = currentPeelActor
  151 + # peelActors.push_back(newPeelActor)
  152 +
  153 + self.currentPeelNo += 1
  154 +
  155 + def getPeelActor(self, p):
  156 + colors = vtk.vtkNamedColors()
  157 + # Create the color map
  158 + colorLookupTable = vtk.vtkLookupTable()
  159 + colorLookupTable.SetNumberOfColors(512)
  160 + colorLookupTable.SetSaturationRange(0, 0)
  161 + colorLookupTable.SetHueRange(0, 0)
  162 + colorLookupTable.SetValueRange(0, 1)
  163 + # colorLookupTable.SetTableRange(0, 1000)
  164 + # colorLookupTable.SetTableRange(0, 250)
  165 + colorLookupTable.SetTableRange(0, 200)
  166 + # colorLookupTable.SetTableRange(0, 150)
  167 + colorLookupTable.Build()
  168 +
  169 + # Set mapper auto
  170 + mapper = vtk.vtkOpenGLPolyDataMapper()
  171 + mapper.SetInputData(self.peel[p])
  172 + # mapper.SetScalarRange(0, 1000)
  173 + # mapper.SetScalarRange(0, 250)
  174 + mapper.SetScalarRange(0, 200)
  175 + # mapper.SetScalarRange(0, 150)
  176 + mapper.SetLookupTable(colorLookupTable)
  177 + mapper.InterpolateScalarsBeforeMappingOn()
  178 +
  179 + # Set actor
  180 + self.currentPeelActor.SetMapper(mapper)
  181 +
  182 + return self.currentPeelActor
  183 +
  184 + def getCurrentPeelActor(self):
  185 + colors = vtk.vtkNamedColors()
  186 +
  187 + # Create the color map
  188 + colorLookupTable = vtk.vtkLookupTable()
  189 + colorLookupTable.SetNumberOfColors(512)
  190 + colorLookupTable.SetSaturationRange(0, 0)
  191 + colorLookupTable.SetHueRange(0, 0)
  192 + colorLookupTable.SetValueRange(0, 1)
  193 + # colorLookupTable.SetTableRange(0, 1000)
  194 + # colorLookupTable.SetTableRange(0, 250)
  195 + colorLookupTable.SetTableRange(0, 200)
  196 + # colorLookupTable.SetTableRange(0, 150)
  197 + colorLookupTable.Build()
  198 +
  199 + # Set mapper auto
  200 + mapper = vtk.vtkOpenGLPolyDataMapper()
  201 + mapper.SetInputData(self.currentPeel)
  202 + # mapper.SetScalarRange(0, 1000)
  203 + # mapper.SetScalarRange(0, 250)
  204 + mapper.SetScalarRange(0, 200)
  205 + # mapper.SetScalarRange(0, 150)
  206 + mapper.SetLookupTable(colorLookupTable)
  207 + mapper.InterpolateScalarsBeforeMappingOn()
  208 +
  209 + # Set actor
  210 + self.currentPeelActor.SetMapper(mapper)
  211 + self.currentPeelActor.GetProperty().SetBackfaceCulling(1)
  212 + self.currentPeelActor.GetProperty().SetOpacity(0.5)
  213 +
  214 + return self.currentPeelActor
  215 +
  216 +
  217 +def cleanMesh(inp):
  218 + cleaned = vtk.vtkCleanPolyData()
  219 + cleaned.SetInputData(inp)
  220 + cleaned.Update()
  221 +
  222 + return cleaned.GetOutput()
  223 +
  224 +
  225 +def fixMesh(inp):
  226 + normals = vtk.vtkPolyDataNormals()
  227 + normals.SetInputData(inp)
  228 + normals.SetFeatureAngle(160)
  229 + normals.SplittingOn()
  230 + normals.ConsistencyOn()
  231 + normals.AutoOrientNormalsOn()
  232 + normals.Update()
  233 +
  234 + return normals.GetOutput()
  235 +
  236 +
  237 +def upsample(inp):
  238 + triangles = vtk.vtkTriangleFilter()
  239 + triangles.SetInputData(inp)
  240 + triangles.Update()
  241 +
  242 + subdivisionFilter = vtk.vtkLinearSubdivisionFilter()
  243 + subdivisionFilter.SetInputData(triangles.GetOutput())
  244 + subdivisionFilter.SetNumberOfSubdivisions(2)
  245 + subdivisionFilter.Update()
  246 +
  247 + return subdivisionFilter.GetOutput()
  248 +
  249 +
  250 +def smooth(inp):
  251 + smoother = vtk.vtkWindowedSincPolyDataFilter()
  252 + smoother.SetInputData(inp)
  253 + smoother.SetNumberOfIterations(20)
  254 + smoother.BoundarySmoothingOn()
  255 + smoother.FeatureEdgeSmoothingOn()
  256 + smoother.SetFeatureAngle(175)
  257 + smoother.SetPassBand(0.1)
  258 + smoother.NonManifoldSmoothingOn()
  259 + smoother.NormalizeCoordinatesOn()
  260 + smoother.Update()
  261 +
  262 + return smoother.GetOutput()
  263 +
  264 +
  265 +def downsample(inp):
  266 + # surface = vtk.vtkSurface()
  267 + # surface.CreateFromPolyData(inp)
  268 + #
  269 + # areas = vtk.vtkDoubleArray()
  270 + # areas = surface.GetTrianglesAreas()
  271 + # surfaceArea = 0
  272 + #
  273 + # for i in range(0, areas.GetSize()):
  274 + # surfaceArea += areas.GetValue(i)
  275 + #
  276 + # clusterNumber = surfaceArea / 20
  277 +
  278 + mesh = pyvista.PolyData(inp)
  279 +
  280 + # Create clustering object
  281 + clus = pyacvd.Clustering(mesh)
  282 + # mesh is not dense enough for uniform remeshing
  283 + # clus.subdivide(3)
  284 + clus.cluster(3000)
  285 + Remesh = clus.create_mesh()
  286 +
  287 + # print(Remesh)
  288 +
  289 + # Remesh = vtk.vtkIsotropicDiscreteRemeshing()
  290 + # Remesh.SetInput(surface)
  291 + # Remesh.SetFileLoadSaveOption(0)
  292 + # Remesh.SetNumberOfClusters(clusterNumber)
  293 + # Remesh.SetConsoleOutput(0)
  294 + # Remesh.GetMetric().SetGradation(0)
  295 + # Remesh.SetDisplay(0)
  296 + # Remesh.Remesh()
  297 +
  298 + # out = vtk.vtkPolyData()
  299 + # out.SetPoints(Remesh.GetOutput().GetPoints())
  300 + # out.SetPolys(Remesh.GetOutput().GetPolys())
  301 +
  302 + return Remesh
  303 +
  304 +
... ...
invesalius/data/coregistration.py
... ... @@ -162,6 +162,7 @@ def corregistrate_dynamic(inp, coord_raw, ref_mode_id, icp):
162 162  
163 163 if icp[0]:
164 164 m_img = bases.transform_icp(m_img, icp[1])
  165 +
165 166 # compute rotation angles
166 167 _, _, angles, _, _ = tr.decompose_matrix(m_img)
167 168 # create output coordiante list
... ... @@ -205,8 +206,7 @@ class CoordinateCorregistrate(threading.Thread):
205 206 m_img_flip = m_img.copy()
206 207 m_img_flip[1, -1] = -m_img_flip[1, -1]
207 208 # self.pipeline.set_message(m_img_flip)
208   - print('ref')
209   - print(self.icp)
  209 +
210 210 if self.icp:
211 211 m_img = bases.transform_icp(m_img, self.m_icp)
212 212  
... ...
invesalius/data/imagedata_utils.py
... ... @@ -582,3 +582,44 @@ def convert_world_to_voxel(xyz, affine):
582 582 ijk = ijk_homo.T[np.newaxis, 0, :3]
583 583  
584 584 return ijk
  585 +
  586 +
  587 +def create_grid(xy_range, z_range, z_offset, spacing):
  588 + x = np.arange(xy_range[0], xy_range[1]+1, spacing)
  589 + y = np.arange(xy_range[0], xy_range[1]+1, spacing)
  590 + z = z_offset + np.arange(z_range[0], z_range[1]+1, spacing)
  591 + xv, yv, zv = np.meshgrid(x, y, -z)
  592 + coord_grid = np.array([xv, yv, zv])
  593 + # create grid of points
  594 + grid_number = x.shape[0]*y.shape[0]*z.shape[0]
  595 + coord_grid = coord_grid.reshape([3, grid_number]).T
  596 + # sort grid from distance to the origin/coil center
  597 + coord_list = coord_grid[np.argsort(np.linalg.norm(coord_grid, axis=1)), :]
  598 + # make the coordinates homogeneous
  599 + coord_list_w = np.append(coord_list.T, np.ones([1, grid_number]), axis=0)
  600 +
  601 + return coord_list_w
  602 +
  603 +
  604 +def create_spherical_grid(radius=10, subdivision=1):
  605 + x = np.linspace(-radius, radius, int(2*radius/subdivision)+1)
  606 + xv, yv, zv = np.meshgrid(x, x, x)
  607 + coord_grid = np.array([xv, yv, zv])
  608 + # create grid of points
  609 + grid_number = x.shape[0]**3
  610 + coord_grid = coord_grid.reshape([3, grid_number]).T
  611 +
  612 + sph_grid = coord_grid[np.linalg.norm(coord_grid, axis=1) < radius, :]
  613 + sph_sort = sph_grid[np.argsort(np.linalg.norm(sph_grid, axis=1)), :]
  614 +
  615 + return sph_sort
  616 +
  617 +
  618 +def random_sample_sphere(radius=3, size=100):
  619 + uvw = np.random.normal(0, 1, (size, 3))
  620 + norm = np.linalg.norm(uvw, axis=1, keepdims=True)
  621 + # Change/remove **(1./3) to make samples more concentrated around the center
  622 + r = np.random.uniform(0, 1, (size, 1)) ** 1.5
  623 + scale = radius * np.divide(r, norm)
  624 + xyz = scale * uvw
  625 + return xyz
585 626 \ No newline at end of file
... ...
invesalius/data/mask.py
... ... @@ -293,14 +293,16 @@ class Mask():
293 293 #plist_filepath = os.path.join(dir_temp, plist_filename)
294 294  
295 295 temp_plist = tempfile.mktemp()
296   - plistlib.writePlist(mask, temp_plist)
  296 + with open(temp_plist, 'w+b') as f:
  297 + plistlib.dump(mask, f)
297 298  
298 299 filelist[temp_plist] = plist_filename
299 300  
300 301 return plist_filename
301 302  
302 303 def OpenPList(self, filename):
303   - mask = plistlib.readPlist(filename)
  304 + with open(filename, 'r+b') as f:
  305 + mask = plistlib.load(f, fmt=plistlib.FMT_XML)
304 306  
305 307 self.index = mask['index']
306 308 self.name = mask['name']
... ...
invesalius/data/record_coords.py
... ... @@ -43,9 +43,9 @@ class Record(threading.Thread):
43 43  
44 44 def __bind_events(self):
45 45 # Publisher.subscribe(self.UpdateCurrentCoords, 'Co-registered points')
46   - Publisher.subscribe(self.UpdateCurrentCoords, 'Update cross position')
  46 + Publisher.subscribe(self.UpdateCurrentCoords, 'Set cross focal point')
47 47  
48   - def UpdateCurrentCoords(self, arg, position):
  48 + def UpdateCurrentCoords(self, position):
49 49 self.coord = asarray(position)
50 50  
51 51 def stop(self):
... ...
invesalius/data/styles.py
... ... @@ -473,18 +473,6 @@ class CrossInteractorStyle(DefaultInteractorStyle):
473 473 self.slice_actor = viewer.slice_data.actor
474 474 self.slice_data = viewer.slice_data
475 475  
476   - # tracts
477   - # self.seed = [0., 0., 0.]
478   - # slic = sl.Slice()
479   - # self.affine = slic.affine
480   - # self.tracker = slic.tracker
481   - #
482   - # self.affine_vtk = vtk.vtkMatrix4x4()
483   - # for row in range(0, 4):
484   - # for col in range(0, 4):
485   - # self.affine_vtk.SetElement(row, col, self.affine[row, col])
486   - # ---
487   -
488 476 self.picker = vtk.vtkWorldPointPicker()
489 477  
490 478 self.AddObserver("MouseMoveEvent", self.OnCrossMove)
... ... @@ -509,46 +497,24 @@ class CrossInteractorStyle(DefaultInteractorStyle):
509 497 def OnCrossMove(self, obj, evt):
510 498 # The user moved the mouse with left button pressed
511 499 if self.left_pressed:
512   - print("OnCrossMove interactor style")
513 500 iren = obj.GetInteractor()
514 501 self.ChangeCrossPosition(iren)
515 502  
516 503 def ChangeCrossPosition(self, iren):
517 504 mouse_x, mouse_y = iren.GetEventPosition()
518   - wx, wy, wz = self.viewer.get_coordinate_cursor(mouse_x, mouse_y, self.picker)
519   - px, py = self.viewer.get_slice_pixel_coord_by_world_pos(wx, wy, wz)
520   - coord = self.viewer.calcultate_scroll_position(px, py)
521   -
522   - # Tracts
523   - # pos_world_aux = np.ones([4, 1])
524   - # pos_world_aux[:3, -1] = bases.flip_x((wx, wy, wz))[:3]
525   - # pos_world = np.linalg.inv(self.affine) @ pos_world_aux
526   - # seed_aux = pos_world.reshape([1, 4])[0, :3]
527   - # self.seed = seed_aux[np.newaxis, :]
528   - # print("Check the seed: ", self.seed)
529   - #
530   -
531   - Publisher.sendMessage('Update cross position', arg=None, position=(wx, wy, wz, 0., 0., 0.))
532   - self.ScrollSlice(coord)
533   -
534   - # iren.Render()
535   -
536   - def ScrollSlice(self, coord):
537   - if self.orientation == "AXIAL":
538   - Publisher.sendMessage(('Set scroll position', 'SAGITAL'),
539   - index=coord[0])
540   - Publisher.sendMessage(('Set scroll position', 'CORONAL'),
541   - index=coord[1])
542   - elif self.orientation == "SAGITAL":
543   - Publisher.sendMessage(('Set scroll position', 'AXIAL'),
544   - index=coord[2])
545   - Publisher.sendMessage(('Set scroll position', 'CORONAL'),
546   - index=coord[1])
547   - elif self.orientation == "CORONAL":
548   - Publisher.sendMessage(('Set scroll position', 'AXIAL'),
549   - index=coord[2])
550   - Publisher.sendMessage(('Set scroll position', 'SAGITAL'),
551   - index=coord[0])
  505 + x, y, z = self.viewer.get_coordinate_cursor(mouse_x, mouse_y, self.picker)
  506 + self.viewer.UpdateSlicesPosition([x, y, z])
  507 + # This "Set cross" message is needed to update the cross in the other slices
  508 + Publisher.sendMessage('Set cross focal point', position=[x, y, z, 0., 0., 0.])
  509 + Publisher.sendMessage('Update slice viewer')
  510 +
  511 + def OnScrollBar(self, *args, **kwargs):
  512 + # Update other slice's cross according to the new focal point from
  513 + # the actual orientation.
  514 + x, y, z = self.viewer.cross.GetFocalPoint()
  515 + self.viewer.UpdateSlicesPosition([x, y, z])
  516 + Publisher.sendMessage('Set cross focal point', position=[x, y, z, 0., 0., 0.])
  517 + Publisher.sendMessage('Update slice viewer')
552 518  
553 519  
554 520 class TractsInteractorStyle(CrossInteractorStyle):
... ...
invesalius/data/surface.py
... ... @@ -115,14 +115,16 @@ class Surface():
115 115 plist_filename = filename + u'.plist'
116 116 #plist_filepath = os.path.join(dir_temp, filename + '.plist')
117 117 temp_plist = tempfile.mktemp()
118   - plistlib.writePlist(surface, temp_plist)
  118 + with open(temp_plist, 'w+b') as f:
  119 + plistlib.dump(surface, f)
119 120  
120 121 filelist[temp_plist] = plist_filename
121 122  
122 123 return plist_filename
123 124  
124 125 def OpenPList(self, filename):
125   - sp = plistlib.readPlist(filename)
  126 + with open(filename, 'r+b') as f:
  127 + sp = plistlib.load(f, fmt=plistlib.FMT_XML)
126 128 dirpath = os.path.abspath(os.path.split(filename)[0])
127 129 self.index = sp['index']
128 130 self.name = sp['name']
... ...
invesalius/data/tractography.py
... ... @@ -29,21 +29,26 @@ import time
29 29 import numpy as np
30 30 import queue
31 31 from pubsub import pub as Publisher
  32 +from scipy.stats import norm
32 33 import vtk
33 34  
34 35 import invesalius.constants as const
35 36 import invesalius.data.imagedata_utils as img_utils
36 37  
  38 +import invesalius.project as prj
  39 +
37 40 # Nice print for arrays
38 41 # np.set_printoptions(precision=2)
39 42 # np.set_printoptions(suppress=True)
40 43  
41 44  
42   -def compute_directions(trk_n):
43   - """Compute direction of a single tract in each point and return as an RGB color
  45 +def compute_directions(trk_n, alpha=255):
  46 + """Compute direction of a single tract in each point and return as an RGBA color
44 47  
45 48 :param trk_n: nx3 array of doubles (x, y, z) point coordinates composing the tract
46 49 :type trk_n: numpy.ndarray
  50 + :param alpha: opacity value in the interval [0, 255]. The 0 is no opacity (total transparency).
  51 + :type alpha: int
47 52 :return: nx3 array of int (x, y, z) RGB colors in the range 0 - 255
48 53 :rtype: numpy.ndarray
49 54 """
... ... @@ -54,6 +59,7 @@ def compute_directions(trk_n):
54 59 # check that linalg norm makes second norm
55 60 # https://stackoverflow.com/questions/21030391/how-to-normalize-an-array-in-numpy
56 61 direction = 255 * np.absolute((trk_d / np.linalg.norm(trk_d, axis=1)[:, None]))
  62 + direction = np.hstack([direction, alpha * np.ones([direction.shape[0], 1])])
57 63 return direction.astype(int)
58 64  
59 65  
... ... @@ -73,7 +79,7 @@ def compute_tubes(trk, direction):
73 79 lines = vtk.vtkCellArray()
74 80  
75 81 colors = vtk.vtkUnsignedCharArray()
76   - colors.SetNumberOfComponents(3)
  82 + colors.SetNumberOfComponents(4)
77 83  
78 84 k = 0
79 85 lines.InsertNextCell(numb_points)
... ... @@ -196,23 +202,25 @@ def compute_tracts(trekker, position, affine, affine_vtk, n_tracts):
196 202 if trk_list:
197 203 root = tracts_computation(trk_list, root, 0)
198 204 Publisher.sendMessage('Remove tracts')
199   - Publisher.sendMessage('Update tracts', flag=True, root=root, affine_vtk=affine_vtk)
  205 + Publisher.sendMessage('Update tracts', root=root, affine_vtk=affine_vtk, coord_offset=position)
200 206 else:
201 207 Publisher.sendMessage('Remove tracts')
202 208  
203 209  
204   -def tracts_computation_branch(trk_list):
  210 +def tracts_computation_branch(trk_list, alpha=255):
205 211 """Convert the list of all computed tracts given by Trekker run and returns a vtkMultiBlockDataSet
206 212  
207 213 :param trk_list: List of lists containing the computed tracts and corresponding coordinates
208 214 :type trk_list: list
  215 + :param alpha: opacity value in the interval [0, 255]. The 0 is no opacity (total transparency).
  216 + :type alpha: int
209 217 :return: The collection of tracts as a vtkMultiBlockDataSet
210 218 :rtype: vtkMultiBlockDataSet
211 219 """
212 220 # Transform tracts to array
213 221 trk_arr = [np.asarray(trk_n).T if trk_n else None for trk_n in trk_list]
214 222 # Compute the directions
215   - trk_dir = [compute_directions(trk_n) for trk_n in trk_arr]
  223 + trk_dir = [compute_directions(trk_n, alpha) for trk_n in trk_arr]
216 224 # Compute the vtk tubes
217 225 tube_list = [compute_tubes(trk_arr_n, trk_dir_n) for trk_arr_n, trk_dir_n in zip(trk_arr, trk_dir)]
218 226 branch = combine_tracts_branch(tube_list)
... ... @@ -222,7 +230,7 @@ def tracts_computation_branch(trk_list):
222 230  
223 231 class ComputeTractsThread(threading.Thread):
224 232  
225   - def __init__(self, inp, affine_vtk, coord_tracts_queue, tracts_queue, event, sle):
  233 + def __init__(self, inp, coord_tracts_queue, tracts_queue, event, sle):
226 234 """Class (threading) to compute real time tractography data for visualization.
227 235  
228 236 Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/)
... ... @@ -235,12 +243,10 @@ class ComputeTractsThread(threading.Thread):
235 243  
236 244 :param inp: List of inputs: trekker instance, affine numpy array, seed_offset, seed_radius, n_threads
237 245 :type inp: list
238   - :param affine_vtk: Affine matrix in vtkMatrix4x4 instance to update objects position in 3D scene
239   - :type affine_vtk: vtkMatrix4x4
240   - :param coord_queue: Queue instance that manage coordinates read from tracking device and coregistered
241   - :type coord_queue: queue.Queue
242   - :param visualization_queue: Queue instance that manage coordinates to be visualized
243   - :type visualization_queue: queue.Queue
  246 + :param coord_tracts_queue: Queue instance that manage co-registered coordinates
  247 + :type coord_tracts_queue: queue.Queue
  248 + :param tracts_queue: Queue instance that manage the tracts to be visualized
  249 + :type tracts_queue: queue.Queue
244 250 :param event: Threading event to coordinate when tasks as done and allow UI release
245 251 :type event: threading.Event
246 252 :param sle: Sleep pause in seconds
... ... @@ -249,7 +255,6 @@ class ComputeTractsThread(threading.Thread):
249 255  
250 256 threading.Thread.__init__(self, name='ComputeTractsThread')
251 257 self.inp = inp
252   - self.affine_vtk = affine_vtk
253 258 # self.coord_queue = coord_queue
254 259 self.coord_tracts_queue = coord_tracts_queue
255 260 self.tracts_queue = tracts_queue
... ... @@ -259,7 +264,7 @@ class ComputeTractsThread(threading.Thread):
259 264  
260 265 def run(self):
261 266  
262   - trekker, affine, offset, n_tracts_total, seed_radius, n_threads = self.inp
  267 + trekker, affine, offset, n_tracts_total, seed_radius, n_threads, act_data, affine_vtk, img_shift = self.inp
263 268 # n_threads = n_tracts_total
264 269 p_old = np.array([[0., 0., 0.]])
265 270 n_tracts = 0
... ... @@ -270,10 +275,12 @@ class ComputeTractsThread(threading.Thread):
270 275 try:
271 276 # print("Computing tracts")
272 277 # get from the queue the coordinates, coregistration transformation matrix, and flipped matrix
  278 + # print("Here")
273 279 m_img_flip = self.coord_tracts_queue.get_nowait()
274 280 # coord, m_img, m_img_flip = self.coord_queue.get_nowait()
275 281 # print('ComputeTractsThread: get {}'.format(count))
276 282  
  283 + # TODO: Remove this is not needed
277 284 # 20200402: in this new refactored version the m_img comes different than the position
278 285 # the new version m_img is already flixped in y, which means that Y is negative
279 286 # if only the Y is negative maybe no need for the flip_x funtcion at all in the navigation
... ... @@ -334,7 +341,233 @@ class ComputeTractsThread(threading.Thread):
334 341 # be more evident in slow computer or for heavier tract computations, it is better slow update
335 342 # than visualizing old data
336 343 # self.visualization_queue.put_nowait([coord, m_img, bundle])
337   - self.tracts_queue.put_nowait((bundle, self.affine_vtk, coord_offset))
  344 + self.tracts_queue.put_nowait((bundle, affine_vtk, coord_offset))
  345 + # print('ComputeTractsThread: put {}'.format(count))
  346 +
  347 + self.coord_tracts_queue.task_done()
  348 + # self.coord_queue.task_done()
  349 + # print('ComputeTractsThread: done {}'.format(count))
  350 +
  351 + # sleep required to prevent user interface from being unresponsive
  352 + time.sleep(self.sle)
  353 + # if no coordinates pass
  354 + except queue.Empty:
  355 + # print("Empty queue in tractography")
  356 + pass
  357 + # if queue is full mark as done (may not be needed in this new "nowait" method)
  358 + except queue.Full:
  359 + # self.coord_queue.task_done()
  360 + self.coord_tracts_queue.task_done()
  361 +
  362 +
  363 +class ComputeTractsACTThread(threading.Thread):
  364 +
  365 + def __init__(self, inp, coord_tracts_queue, tracts_queue, event, sle):
  366 + """Class (threading) to compute real time tractography data for visualization.
  367 +
  368 + Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/)
  369 + For VTK visualization, each tract (fiber) is a constructed as a tube and many tubes combined in one
  370 + vtkMultiBlockDataSet named as a branch. Several branches are combined in another vtkMultiBlockDataSet named as
  371 + bundle, to obtain fast computation and visualization. The bundle dataset is mapped to a single vtkActor.
  372 + Mapper and Actor are computer in the data/viewer_volume.py module for easier handling in the invesalius 3D scene.
  373 +
  374 + Sleep function in run method is used to avoid blocking GUI and more fluent, real-time navigation
  375 +
  376 + :param inp: List of inputs: trekker instance, affine numpy array, seed_offset, seed_radius, n_threads
  377 + :type inp: list
  378 + :param coord_tracts_queue: Queue instance that manage co-registered coordinates
  379 + :type coord_tracts_queue: queue.Queue
  380 + :param tracts_queue: Queue instance that manage the tracts to be visualized
  381 + :type tracts_queue: queue.Queue
  382 + :param event: Threading event to coordinate when tasks as done and allow UI release
  383 + :type event: threading.Event
  384 + :param sle: Sleep pause in seconds
  385 + :type sle: float
  386 + """
  387 +
  388 + threading.Thread.__init__(self, name='ComputeTractsThreadACT')
  389 + self.inp = inp
  390 + # self.coord_queue = coord_queue
  391 + self.coord_tracts_queue = coord_tracts_queue
  392 + self.tracts_queue = tracts_queue
  393 + # on first pilots (january 12, 2021) used (-4, 4)
  394 + self.coord_list_w = img_utils.create_grid((-2, 2), (0, 20), inp[2]-5, 1)
  395 + # self.coord_list_sph = img_utils.create_spherical_grid(10, 1)
  396 + # self.coord_list_sph = img_utils.create_spherical_grid(10, 1)
  397 + # x_norm = np.linspace(norm.ppf(0.01), norm.ppf(0.99), 2*self.coord_list_sph.shape[0])
  398 + # self.pdf = np.flipud(norm.pdf(x_norm[:self.coord_list_sph.shape[0]], loc=0, scale=2.))
  399 + # self.sph_idx = np.linspace(0, self.coord_list_sph.shape[0], num=self.coord_list_sph.shape[0], dtype=int)
  400 + # self.visualization_queue = visualization_queue
  401 + self.event = event
  402 + self.sle = sle
  403 +
  404 + # prj_data = prj.Project()
  405 + # matrix_shape = tuple(prj_data.matrix_shape)
  406 + # self.img_shift = matrix_shape[1]
  407 +
  408 + def run(self):
  409 +
  410 + # trekker, affine, offset, n_tracts_total, seed_radius, n_threads = self.inp
  411 + trekker, affine, offset, n_tracts_total, seed_radius, n_threads, act_data, affine_vtk, img_shift = self.inp
  412 +
  413 + # n_threads = n_tracts_total
  414 + p_old = np.array([[0., 0., 0.]])
  415 + p_old_pre = np.array([[0., 0., 0.]])
  416 + coord_offset = None
  417 + n_tracts = 0
  418 + n_branches = 0
  419 + bundle = None
  420 + sph_sampling = True
  421 + dist_radius = 1.5
  422 +
  423 + xyz = img_utils.random_sample_sphere(radius=seed_radius, size=100)
  424 + coord_list_sph = np.hstack([xyz, np.ones([xyz.shape[0], 1])]).T
  425 + m_seed = np.identity(4)
  426 + # Compute the tracts
  427 + # print('ComputeTractsThread: event {}'.format(self.event.is_set()))
  428 + while not self.event.is_set():
  429 + try:
  430 + # print("Computing tracts")
  431 + # get from the queue the coordinates, coregistration transformation matrix, and flipped matrix
  432 + m_img_flip = self.coord_tracts_queue.get_nowait()
  433 + # coord, m_img, m_img_flip = self.coord_queue.get_nowait()
  434 + # print('ComputeTractsThread: get {}'.format(count))
  435 +
  436 + # TODO: Remove this is not needed
  437 + # 20200402: in this new refactored version the m_img comes different than the position
  438 + # the new version m_img is already flixped in y, which means that Y is negative
  439 + # if only the Y is negative maybe no need for the flip_x funtcion at all in the navigation
  440 + # but check all coord_queue before why now the m_img comes different than position
  441 + # 20200403: indeed flip_x is just a -1 multiplication to the Y coordinate, remove function flip_x
  442 + # m_img_flip = m_img.copy()
  443 + # m_img_flip[1, -1] = -m_img_flip[1, -1]
  444 +
  445 + # DEBUG: Uncomment the m_img_flip below so that distance is fixed and tracts keep computing
  446 + # m_img_flip[:3, -1] = (5., 10., 12.)
  447 + dist = abs(np.linalg.norm(p_old_pre - np.asarray(m_img_flip[:3, -1])))
  448 + p_old_pre = m_img_flip[:3, -1].copy()
  449 +
  450 + # Uncertanity visualization --
  451 + # each tract branch is computed with one set of parameters ajusted from 1 to 10
  452 + n_param = 1 + (n_branches % 10)
  453 + # rescale the alpha value that defines the opacity of the branch
  454 + # the n interval is [1, 10] and the new interval is [51, 255]
  455 + # the new interval is defined to have no 0 opacity (minimum is 51, i.e., 20%)
  456 + alpha = (n_param - 1) * (255 - 51) / (10 - 1) + 51
  457 + trekker.minFODamp(n_param * 0.01)
  458 + trekker.dataSupportExponent(n_param * 0.1)
  459 + # ---
  460 +
  461 + # When moving the coil further than the seed_radius restart the bundle computation
  462 + # Currently, it stops to compute tracts when the maximum number of tracts is reached maybe keep
  463 + # computing even if reaches the maximum
  464 + if dist >= dist_radius:
  465 + # Anatomic constrained seed computation ---
  466 + # The original seed location is replaced by the gray-white matter interface that is closest to
  467 + # the coil center
  468 + try:
  469 + #TODO: Create a dialog error to say when the ACT data is not loaded and prevent
  470 + # the interface from freezing. Give the user a chance to load it (maybe in task_navigator)
  471 + coord_list_w_tr = m_img_flip @ self.coord_list_w
  472 + coord_offset = grid_offset(act_data, coord_list_w_tr, img_shift)
  473 + except IndexError:
  474 + # This error might be caused by the coordinate exceeding the image array dimensions.
  475 + # Needs further verification.
  476 + coord_offset = None
  477 + # ---
  478 +
  479 + # Translate the coordinate along the normal vector of the object/coil ---
  480 + if coord_offset is None:
  481 + # apply the coil transformation matrix
  482 + coord_offset = m_img_flip[:3, -1] - offset * m_img_flip[:3, 2]
  483 + # ---
  484 +
  485 + # convert the world coordinates to the voxel space for using as a seed in Trekker
  486 + # seed_trk.shape == [1, 3]
  487 + seed_trk = img_utils.convert_world_to_voxel(coord_offset, affine)
  488 + # print("Desired: {}".format(seed_trk.shape))
  489 +
  490 + # DEBUG: uncomment the seed_trk below
  491 + # Juuso's
  492 + # seed_trk = np.array([[-8.49, -8.39, 2.5]])
  493 + # Baran M1
  494 + # seed_trk = np.array([[27.53, -77.37, 46.42]])
  495 + # print("Given: {}".format(seed_trk.shape))
  496 + # print("Seed: {}".format(seed))
  497 +
  498 + # Spherical sampling of seed coordinates ---
  499 + if sph_sampling:
  500 + # CHECK: We use ACT only for the origin seed, but not for all the other coordinates.
  501 + # Check how this can be solved. Applying ACT to all coordinates is maybe too much.
  502 + # Maybe it doesn't matter because the ACT is just to help finding the closest location to
  503 + # the TMS coil center. Also, note that the spherical sampling is applied only when the coil
  504 + # location changes, all further iterations used the fixed n_threads samples to compute the
  505 + # remaining tracts.
  506 +
  507 + # samples = np.random.choice(self.sph_idx, size=n_threads, p=self.pdf)
  508 + # m_seed[:-1, -1] = seed_trk
  509 + # sph_seed = m_seed @ self.coord_list_sph
  510 + # seed_trk_r = sph_seed[samples, :]
  511 + samples = np.random.choice(coord_list_sph.shape[1], size=n_threads)
  512 + m_seed[:-1, -1] = seed_trk
  513 + seed_trk_r = m_seed @ coord_list_sph[:, samples]
  514 + seed_trk_r = seed_trk_r[:-1, :].T
  515 + else:
  516 + # set the seeds for trekker, one seed is repeated n_threads times
  517 + # shape (24, 3)
  518 + seed_trk_r = np.repeat(seed_trk, n_threads, axis=0)
  519 +
  520 + # ---
  521 +
  522 + # Trekker has internal multiprocessing approach done in C. Here the number of available threads is
  523 + # given, but in case a large number of tracts is requested, it will compute all in parallel
  524 + # automatically for a more fluent navigation, better to compute the maximum number the computer
  525 + # handles
  526 + trekker.seed_coordinates(seed_trk_r)
  527 + # trekker.seed_coordinates(np.repeat(seed_trk, n_threads, axis=0))
  528 +
  529 + # run the trekker, this is the slowest line of code, be careful to just use once!
  530 + trk_list = trekker.run()
  531 +
  532 + # check if any tract was found, otherwise doesn't count
  533 + if trk_list:
  534 + bundle = vtk.vtkMultiBlockDataSet()
  535 + branch = tracts_computation_branch(trk_list, alpha)
  536 + bundle.SetBlock(n_branches, branch)
  537 + n_branches = 1
  538 + n_tracts = branch.GetNumberOfBlocks()
  539 + else:
  540 + bundle = None
  541 + n_branches = 0
  542 + n_tracts = 0
  543 +
  544 + elif dist < dist_radius and n_tracts < n_tracts_total:
  545 + trk_list = trekker.run()
  546 + if trk_list:
  547 + # compute tract blocks and add to bundle until reaches the maximum number of tracts
  548 + # the alpha changes depending on the parameter set
  549 + branch = tracts_computation_branch(trk_list, alpha)
  550 + if bundle:
  551 + bundle.SetBlock(n_branches, branch)
  552 + n_tracts += branch.GetNumberOfBlocks()
  553 + n_branches += 1
  554 + else:
  555 + bundle = vtk.vtkMultiBlockDataSet()
  556 + bundle.SetBlock(n_branches, branch)
  557 + n_branches = 1
  558 + n_tracts = branch.GetNumberOfBlocks()
  559 + # else:
  560 + # bundle = None
  561 +
  562 + # else:
  563 + # bundle = None
  564 +
  565 + # rethink if this should be inside the if condition, it may lock the thread if no tracts are found
  566 + # use no wait to ensure maximum speed and avoid visualizing old tracts in the queue, this might
  567 + # be more evident in slow computer or for heavier tract computations, it is better slow update
  568 + # than visualizing old data
  569 + # self.visualization_queue.put_nowait([coord, m_img, bundle])
  570 + self.tracts_queue.put_nowait((bundle, affine_vtk, coord_offset))
338 571 # print('ComputeTractsThread: put {}'.format(count))
339 572  
340 573 self.coord_tracts_queue.task_done()
... ... @@ -469,3 +702,30 @@ def set_trekker_parameters(trekker, params):
469 702 trekker.numberOfThreads(n_threads)
470 703 # print("Trekker config updated: n_threads, {}; seed_max, {}".format(n_threads, params['seed_max']))
471 704 return trekker, n_threads
  705 +
  706 +
  707 +def grid_offset(data, coord_list_w_tr, img_shift):
  708 + # convert to int so coordinates can be used as indices in the MRI image space
  709 + coord_list_w_tr_mri = coord_list_w_tr[:3, :].T.astype(int) + np.array([[0, img_shift, 0]])
  710 +
  711 + #FIX: IndexError: index 269 is out of bounds for axis 2 with size 256
  712 + # error occurs when running line "labs = data[coord..."
  713 + # need to check why there is a coordinate outside the MRI bounds
  714 +
  715 + # extract the first occurrence of a specific label from the MRI image
  716 + labs = data[coord_list_w_tr_mri[..., 0], coord_list_w_tr_mri[..., 1], coord_list_w_tr_mri[..., 2]]
  717 + lab_first = np.argmax(labs == 1)
  718 + if labs[lab_first] == 1:
  719 + pt_found = coord_list_w_tr_mri[lab_first, :]
  720 + # convert coordinate back to invesalius 3D space
  721 + pt_found_inv = pt_found - np.array([0., img_shift, 0.])
  722 + else:
  723 + pt_found_inv = None
  724 +
  725 + # # convert to world coordinate space to use as seed for fiber tracking
  726 + # pt_found_tr = np.append(pt_found, 1)[np.newaxis, :].T
  727 + # # default affine in invesalius is actually the affine inverse
  728 + # pt_found_tr = np.linalg.inv(affine) @ pt_found_tr
  729 + # pt_found_tr = pt_found_tr[:3, 0, np.newaxis].T
  730 +
  731 + return pt_found_inv
... ...
invesalius/data/trigger.py
... ... @@ -118,7 +118,7 @@ class TriggerNew(threading.Thread):
118 118 self.trigger_init = None
119 119 self.stylusplh = False
120 120 # self.COM = False
121   - self.__bind_events()
  121 + # self.__bind_events()
122 122 try:
123 123 import serial
124 124  
... ...
invesalius/data/viewer_slice.py
... ... @@ -565,15 +565,31 @@ class Viewer(wx.Panel):
565 565 if self.slice_data.cursor:
566 566 self.slice_data.cursor.SetColour(colour_vtk)
567 567  
568   - def UpdateSlicesNavigation(self, arg, position):
  568 + def UpdateSlicesPosition(self, position):
569 569 # Get point from base change
570   - ux, uy, uz = position[:3]
571   - px, py = self.get_slice_pixel_coord_by_world_pos(ux, uy, uz)
  570 + px, py = self.get_slice_pixel_coord_by_world_pos(*position)
572 571 coord = self.calcultate_scroll_position(px, py)
  572 + # Debugging coordinates. For a 1.0 spacing axis the coord and position is the same,
  573 + # but for a spacing dimension =! 1, the coord and position are different
  574 + # print("\nPosition: {}".format(position))
  575 + # print("Scroll position: {}".format(coord))
  576 + # print("Slice actor bounds: {}".format(self.slice_data.actor.GetBounds()))
  577 + # print("Scroll from int of position: {}\n".format([round(s) for s in position]))
573 578  
574   - self.cross.SetFocalPoint((ux, uy, uz))
  579 + # this call did not affect the working code
  580 + # self.cross.SetFocalPoint(coord)
  581 +
  582 + # update the image slices in all three orientations
575 583 self.ScrollSlice(coord)
576   - self.interactor.Render()
  584 +
  585 + def SetCrossFocalPoint(self, position):
  586 + """
  587 + Sets the cross focal point for all slice panels (axial, coronal, sagittal). This function is also called via
  588 + pubsub messaging and may receive a list of 6 coordinates. Thus, limiting the number of list elements in the
  589 + SetFocalPoint call is required.
  590 + :param position: list of 6 coordinates in vtk world coordinate system wx, wy, wz
  591 + """
  592 + self.cross.SetFocalPoint(position[:3])
577 593  
578 594 def ScrollSlice(self, coord):
579 595 if self.orientation == "AXIAL":
... ... @@ -816,9 +832,12 @@ class Viewer(wx.Panel):
816 832 Publisher.subscribe(self.ChangeSliceNumber,
817 833 ('Set scroll position',
818 834 self.orientation))
819   - Publisher.subscribe(self.__update_cross_position,
820   - 'Update cross position')
821   - # Publisher.subscribe(self.UpdateSlicesNavigation,
  835 + # Publisher.subscribe(self.__update_cross_position,
  836 + # 'Update cross position')
  837 + # Publisher.subscribe(self.__update_cross_position,
  838 + # 'Update cross position %s' % self.orientation)
  839 + Publisher.subscribe(self.SetCrossFocalPoint, 'Set cross focal point')
  840 + # Publisher.subscribe(self.UpdateSlicesPosition,
822 841 # 'Co-registered points')
823 842 ###
824 843 # Publisher.subscribe(self.ChangeBrushColour,
... ... @@ -1136,9 +1155,9 @@ class Viewer(wx.Panel):
1136 1155  
1137 1156 renderer.AddActor(cross_actor)
1138 1157  
1139   - def __update_cross_position(self, arg, position):
1140   - # self.cross.SetFocalPoint(position[:3])
1141   - self.UpdateSlicesNavigation(None, position)
  1158 + # def __update_cross_position(self, arg, position):
  1159 + # # self.cross.SetFocalPoint(position[:3])
  1160 + # self.UpdateSlicesPosition(None, position)
1142 1161  
1143 1162 def _set_cross_visibility(self, visibility):
1144 1163 self.cross_actor.SetVisibility(visibility)
... ... @@ -1294,14 +1313,17 @@ class Viewer(wx.Panel):
1294 1313 self.set_slice_number(pos)
1295 1314 if update3D:
1296 1315 self.UpdateSlice3D(pos)
1297   - if self.state == const.SLICE_STATE_CROSS:
1298   - # Update other slice's cross according to the new focal point from
1299   - # the actual orientation.
1300   - x, y, z = self.cross.GetFocalPoint()
1301   - Publisher.sendMessage('Update cross position', arg=None, position=(x, y, z, 0., 0., 0.))
1302   - Publisher.sendMessage('Update slice viewer')
1303   - else:
1304   - self.interactor.Render()
  1316 +
  1317 + # This Render needs to come before the self.style.OnScrollBar, otherwise the GetFocalPoint will sometimes
  1318 + # provide the non-updated coordinate and the cross focal point will lag one pixel behind the actual
  1319 + # scroll position
  1320 + self.interactor.Render()
  1321 +
  1322 + try:
  1323 + self.style.OnScrollBar()
  1324 + except AttributeError:
  1325 + print("Do not have OnScrollBar")
  1326 +
1305 1327 if evt:
1306 1328 if self._flush_buffer:
1307 1329 self.slice_.apply_slice_buffer_to_mask(self.orientation)
... ...
invesalius/data/viewer_volume.py
... ... @@ -36,7 +36,6 @@ from scipy.spatial import distance
36 36 from imageio import imsave
37 37  
38 38 import invesalius.constants as const
39   -import invesalius.data.bases as bases
40 39 import invesalius.data.slice_ as sl
41 40 import invesalius.data.styles_3d as styles
42 41 import invesalius.data.transformations as tr
... ... @@ -194,19 +193,9 @@ class Viewer(wx.Panel):
194 193 self.anglethreshold = const.COIL_ANGLES_THRESHOLD
195 194 self.distthreshold = const.COIL_COORD_THRESHOLD
196 195  
197   - # for DTI support tests
198   - # self.ntimes = False
199   - # self._to_show_stream = True
200   - # data_dir = b'C:\Users\deoliv1\OneDrive\data\dti'
201   - # nii_path = b'sub-P0_dwi_FOD.nii'
202   - # trk_path = os.path.join(data_dir, nii_path)
203   - # self.tracker_FOD = Trekker.tracker(trk_path)
204   - # proj = prj.Project()
205   - # self.affine = np.identity(4)
206 196 self.actor_tracts = None
207 197 self.actor_peel = None
208 198 self.seed_offset = const.SEED_OFFSET
209   - # Publisher.sendMessage('Get affine matrix')
210 199  
211 200 # initialize Trekker parameters
212 201 slic = sl.Slice()
... ... @@ -281,8 +270,9 @@ class Viewer(wx.Panel):
281 270  
282 271 Publisher.subscribe(self.RemoveVolume, 'Remove Volume')
283 272  
284   - Publisher.subscribe(self.UpdateCameraBallPosition,
285   - 'Update cross position')
  273 + # Publisher.subscribe(self.UpdateCameraBallPosition,
  274 + # 'Update cross position')
  275 + Publisher.subscribe(self.UpdateCameraBallPosition, 'Set cross focal point')
286 276 Publisher.subscribe(self._check_ball_reference, 'Enable style')
287 277 Publisher.subscribe(self._uncheck_ball_reference, 'Disable style')
288 278  
... ... @@ -578,7 +568,8 @@ class Viewer(wx.Panel):
578 568 Markers created by navigation tools and rendered in volume viewer.
579 569 """
580 570 self.ball_id = ball_id
581   - coord_flip = bases.flip_x_m(coord)[:3, 0]
  571 + coord_flip = list(coord)
  572 + coord_flip[1] = -coord_flip[1]
582 573  
583 574 ball_ref = vtk.vtkSphereSource()
584 575 ball_ref.SetRadius(size)
... ... @@ -1200,14 +1191,14 @@ class Viewer(wx.Panel):
1200 1191  
1201 1192 self.ren.AddActor(self.ball_actor)
1202 1193  
1203   - def UpdateCameraBallPosition(self, arg, position):
1204   - coord_flip = bases.flip_x_m(position[:3])[:3, 0]
1205   - self.ball_actor.SetPosition(coord_flip)
1206   - self.SetVolumeCamera(coord_flip)
  1194 + # def SetCrossFocalPoint(self, position):
  1195 + # self.UpdateCameraBallPosition(None, position)
1207 1196  
1208   - def SetBallReferencePosition(self, position):
1209   - coord_flip = bases.flip_x_m(position[:3])[:3, 0]
  1197 + def UpdateCameraBallPosition(self, position):
  1198 + coord_flip = list(position[:3])
  1199 + coord_flip[1] = -coord_flip[1]
1210 1200 self.ball_actor.SetPosition(coord_flip)
  1201 + self.SetVolumeCamera(coord_flip)
1211 1202  
1212 1203 def CreateObjectPolyData(self, filename):
1213 1204 """
... ... @@ -1412,7 +1403,7 @@ class Viewer(wx.Panel):
1412 1403  
1413 1404 self.Refresh()
1414 1405  
1415   - def OnUpdateTracts(self, evt=None, flag=None, actor=None, root=None, affine_vtk=None, count=0):
  1406 + def OnUpdateTracts(self, root=None, affine_vtk=None, coord_offset=None):
1416 1407 mapper = vtk.vtkCompositePolyDataMapper2()
1417 1408 mapper.SetInputDataObject(root)
1418 1409  
... ... @@ -1421,6 +1412,8 @@ class Viewer(wx.Panel):
1421 1412 self.actor_tracts.SetUserMatrix(affine_vtk)
1422 1413  
1423 1414 self.ren.AddActor(self.actor_tracts)
  1415 + if self.mark_actor:
  1416 + self.mark_actor.SetPosition(coord_offset)
1424 1417 self.Refresh()
1425 1418  
1426 1419 def OnRemoveTracts(self):
... ...
invesalius/data/volume.py
... ... @@ -354,9 +354,11 @@ class Volume():
354 354 color_transfer.RemoveAllPoints()
355 355 color_preset = self.config['CLUT']
356 356 if color_preset != "No CLUT":
357   - p = plistlib.readPlist(
358   - os.path.join(inv_paths.RAYCASTING_PRESETS_DIRECTORY,
359   - 'color_list', color_preset + '.plist'))
  357 + path = os.path.join(inv_paths.RAYCASTING_PRESETS_DIRECTORY,
  358 + 'color_list', color_preset + '.plist')
  359 + with open(path, 'r+b') as f:
  360 + p = plistlib.load(f, fmt=plistlib.FMT_XML)
  361 +
360 362 r = p['Red']
361 363 g = p['Green']
362 364 b = p['Blue']
... ... @@ -768,8 +770,8 @@ class VolumeMask:
768 770 self._volume_property.SetScalarOpacity(self._piecewise_function)
769 771 self._volume_property.ShadeOn()
770 772 self._volume_property.SetInterpolationTypeToLinear()
771   - #vp.SetSpecular(1.75)
772   - #vp.SetSpecularPower(8)
  773 + self._volume_property.SetSpecular(0.75)
  774 + self._volume_property.SetSpecularPower(2)
773 775  
774 776 if not self._volume_mapper.IsA("vtkGPUVolumeRayCastMapper"):
775 777 self._volume_property.SetScalarOpacityUnitDistance(pix_diag)
... ...
invesalius/gui/dialogs.py
... ... @@ -378,30 +378,35 @@ def ShowImportBitmapDirDialog(self):
378 378 return path
379 379  
380 380  
381   -def ShowImportOtherFilesDialog(id_type):
  381 +def ShowImportOtherFilesDialog(id_type, msg='Import NIFTi 1 file'):
382 382 # Default system path
383 383 session = ses.Session()
384 384 last_directory = session.get('paths', 'last_directory_%d' % id_type, '')
385   - dlg = wx.FileDialog(None, message=_("Import Analyze 7.5 file"),
386   - defaultDir=last_directory,
387   - defaultFile="", wildcard=WILDCARD_ANALYZE,
  385 + dlg = wx.FileDialog(None, message=msg, defaultDir=last_directory,
  386 + defaultFile="", wildcard=WILDCARD_NIFTI,
388 387 style=wx.FD_OPEN | wx.FD_CHANGE_DIR)
389 388  
390   - if id_type == const.ID_NIFTI_IMPORT:
391   - dlg.SetMessage(_("Import NIFTi 1 file"))
392   - dlg.SetWildcard(WILDCARD_NIFTI)
393   - elif id_type == const.ID_TREKKER_MASK:
394   - dlg.SetMessage(_("Import Trekker mask"))
395   - dlg.SetWildcard(WILDCARD_NIFTI)
396   - elif id_type == const.ID_TREKKER_IMG:
397   - dlg.SetMessage(_("Import Trekker anatomical image"))
398   - dlg.SetWildcard(WILDCARD_NIFTI)
399   - elif id_type == const.ID_TREKKER_FOD:
400   - dlg.SetMessage(_("Import Trekker FOD"))
401   - dlg.SetWildcard(WILDCARD_NIFTI)
402   - elif id_type == const.ID_PARREC_IMPORT:
  389 + # if id_type == const.ID_NIFTI_IMPORT:
  390 + # dlg.SetMessage(_("Import NIFTi 1 file"))
  391 + # dlg.SetWildcard(WILDCARD_NIFTI)
  392 + # elif id_type == const.ID_TREKKER_MASK:
  393 + # dlg.SetMessage(_("Import Trekker mask"))
  394 + # dlg.SetWildcard(WILDCARD_NIFTI)
  395 + # elif id_type == const.ID_TREKKER_IMG:
  396 + # dlg.SetMessage(_("Import Trekker anatomical image"))
  397 + # dlg.SetWildcard(WILDCARD_NIFTI)
  398 + # elif id_type == const.ID_TREKKER_FOD:
  399 + # dlg.SetMessage(_("Import Trekker FOD"))
  400 + # dlg.SetWildcard(WILDCARD_NIFTI)
  401 + # elif id_type == const.ID_TREKKER_ACT:
  402 + # dlg.SetMessage(_("Import acantomical labels"))
  403 + # dlg.SetWildcard(WILDCARD_NIFTI)
  404 + if id_type == const.ID_PARREC_IMPORT:
403 405 dlg.SetMessage(_("Import PAR/REC file"))
404 406 dlg.SetWildcard(WILDCARD_PARREC)
  407 + elif id_type == const.ID_ANALYZE_IMPORT:
  408 + dlg.SetMessage(_("Import Analyze 7.5 file"))
  409 + dlg.SetWildcard(WILDCARD_ANALYZE)
405 410  
406 411 # inv3 filter is default
407 412 dlg.SetFilterIndex(0)
... ...
invesalius/gui/task_navigator.py
... ... @@ -18,13 +18,19 @@
18 18 #--------------------------------------------------------------------------
19 19  
20 20 from functools import partial
21   -# import os
  21 +import csv
  22 +import os
22 23 import queue
23 24 import sys
24 25 import threading
25 26  
  27 +import nibabel as nb
26 28 import numpy as np
27   -# import Trekker
  29 +try:
  30 + import Trekker
  31 + has_trekker = True
  32 +except ImportError:
  33 + has_trekker = False
28 34 import wx
29 35  
30 36 try:
... ... @@ -41,7 +47,10 @@ from time import sleep
41 47  
42 48 import invesalius.constants as const
43 49 import invesalius.data.bases as db
44   -# import invesalius.data.brainmesh_handler as brain
  50 +
  51 +if has_trekker:
  52 + import invesalius.data.brainmesh_handler as brain
  53 +
45 54 import invesalius.data.coordinates as dco
46 55 import invesalius.data.coregistration as dcr
47 56 import invesalius.data.slice_ as sl
... ... @@ -52,6 +61,7 @@ import invesalius.data.trigger as trig
52 61 import invesalius.data.record_coords as rec
53 62 import invesalius.data.vtk_utils as vtk_utils
54 63 import invesalius.gui.dialogs as dlg
  64 +import invesalius.project as prj
55 65 from invesalius import utils
56 66  
57 67 BTN_NEW = wx.NewId()
... ... @@ -173,13 +183,13 @@ class InnerFoldPanel(wx.Panel):
173 183 leftSpacing=0, rightSpacing=0)
174 184  
175 185 # Fold 4 - Tractography panel
176   - item = fold_panel.AddFoldPanel(_("Tractography"), collapsed=True)
177   - otw = TractographyPanel(item)
  186 + if has_trekker:
  187 + item = fold_panel.AddFoldPanel(_("Tractography"), collapsed=True)
  188 + otw = TractographyPanel(item)
178 189  
179   - fold_panel.ApplyCaptionStyle(item, style)
180   - fold_panel.AddFoldPanelWindow(item, otw, spacing=0,
181   - leftSpacing=0, rightSpacing=0)
182   - item.Hide()
  190 + fold_panel.ApplyCaptionStyle(item, style)
  191 + fold_panel.AddFoldPanelWindow(item, otw, spacing=0,
  192 + leftSpacing=0, rightSpacing=0)
183 193  
184 194 # Fold 5 - DBS
185 195 self.dbs_item = fold_panel.AddFoldPanel(_("Deep Brain Stimulation"), collapsed=True)
... ... @@ -327,6 +337,8 @@ class NeuronavigationPanel(wx.Panel):
327 337 self.trekker = None
328 338 self.n_threads = None
329 339 self.view_tracts = False
  340 + self.enable_act = False
  341 + self.act_data = None
330 342 self.n_tracts = const.N_TRACTS
331 343 self.seed_offset = const.SEED_OFFSET
332 344 self.seed_radius = const.SEED_RADIUS
... ... @@ -450,7 +462,7 @@ class NeuronavigationPanel(wx.Panel):
450 462 Publisher.subscribe(self.LoadImageFiducials, 'Load image fiducials')
451 463 Publisher.subscribe(self.UpdateTriggerState, 'Update trigger state')
452 464 Publisher.subscribe(self.UpdateTrackObjectState, 'Update track object state')
453   - Publisher.subscribe(self.UpdateImageCoordinates, 'Update cross position')
  465 + Publisher.subscribe(self.UpdateImageCoordinates, 'Set cross focal point')
454 466 Publisher.subscribe(self.OnDisconnectTracker, 'Disconnect tracker')
455 467 Publisher.subscribe(self.UpdateObjectRegistration, 'Update object registration')
456 468 Publisher.subscribe(self.OnCloseProject, 'Close project data')
... ... @@ -461,6 +473,8 @@ class NeuronavigationPanel(wx.Panel):
461 473 Publisher.subscribe(self.UpdateSleep, 'Update sleep')
462 474 Publisher.subscribe(self.UpdateNumberThreads, 'Update number of threads')
463 475 Publisher.subscribe(self.UpdateTractsVisualization, 'Update tracts visualization')
  476 + Publisher.subscribe(self.EnableACT, 'Enable ACT')
  477 + Publisher.subscribe(self.UpdateACTData, 'Update ACT data')
464 478 Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status')
465 479  
466 480 def LoadImageFiducials(self, marker_id, coord):
... ... @@ -481,7 +495,6 @@ class NeuronavigationPanel(wx.Panel):
481 495 self.checkicp.Enable(False)
482 496 #self.checkicp.SetValue(False)
483 497  
484   - #def UpdateImageCoordinates(self, position):
485 498 def UpdateFRE(self, fre):
486 499 # TODO: Exhibit FRE in a warning dialog and only starts navigation after user clicks ok
487 500 self.txtctrl_fre.SetValue(str(round(fre, 2)))
... ... @@ -512,7 +525,13 @@ class NeuronavigationPanel(wx.Panel):
512 525 def UpdateTractsVisualization(self, data):
513 526 self.view_tracts = data
514 527  
515   - def UpdateImageCoordinates(self, arg, position):
  528 + def UpdateACTData(self, data):
  529 + self.act_data = data
  530 +
  531 + def EnableACT(self, data):
  532 + self.enable_act = data
  533 +
  534 + def UpdateImageCoordinates(self, position):
516 535 # TODO: Change from world coordinates to matrix coordinates. They are better for multi software communication.
517 536 self.current_coord = position
518 537 for m in [0, 1, 2]:
... ... @@ -559,6 +578,7 @@ class NeuronavigationPanel(wx.Panel):
559 578 label=_("Disconnecting tracker..."))
560 579 Publisher.sendMessage('Remove sensors ID')
561 580 self.trk_init = dt.TrackerConnection(self.tracker_id, trck, 'disconnect')
  581 + Publisher.sendMessage('Remove object data')
562 582 self.tracker_id = choice
563 583 if not self.trk_init[0] and choice:
564 584 Publisher.sendMessage('Update status text in GUI',
... ... @@ -577,6 +597,7 @@ class NeuronavigationPanel(wx.Panel):
577 597 Publisher.sendMessage('Update status text in GUI',
578 598 label=_("Disconnecting tracker ..."))
579 599 Publisher.sendMessage('Remove sensors ID')
  600 + Publisher.sendMessage('Remove object data')
580 601 self.trk_init = dt.TrackerConnection(self.tracker_id, trck, 'disconnect')
581 602 if not self.trk_init[0]:
582 603 if evt is not False:
... ... @@ -847,14 +868,21 @@ class NeuronavigationPanel(wx.Panel):
847 868 if self.view_tracts:
848 869 # initialize Trekker parameters
849 870 slic = sl.Slice()
850   - affine = slic.affine
  871 + prj_data = prj.Project()
  872 + matrix_shape = tuple(prj_data.matrix_shape)
  873 + affine = slic.affine.copy()
  874 + affine[1, -1] -= matrix_shape[1]
851 875 affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(affine)
852 876 Publisher.sendMessage("Update marker offset state", create=True)
853   - self.trk_inp = self.trekker, affine, self.seed_offset, self.n_tracts, self.seed_radius, self.n_threads
  877 + self.trk_inp = self.trekker, affine, self.seed_offset, self.n_tracts, self.seed_radius,\
  878 + self.n_threads, self.act_data, affine_vtk, matrix_shape[1]
854 879 # print("Appending the tract computation thread!")
855   - jobs_list.append(dti.ComputeTractsThread(self.trk_inp, affine_vtk,
856   - self.coord_tracts_queue, self.tracts_queue,
857   - self.event, self.sleep_nav))
  880 + if self.enable_act:
  881 + jobs_list.append(dti.ComputeTractsACTThread(self.trk_inp, self.coord_tracts_queue,
  882 + self.tracts_queue, self.event, self.sleep_nav))
  883 + else:
  884 + jobs_list.append(dti.ComputeTractsThread(self.trk_inp, self.coord_tracts_queue,
  885 + self.tracts_queue, self.event, self.sleep_nav))
858 886  
859 887 jobs_list.append(UpdateNavigationScene(vis_queues, vis_components,
860 888 self.event, self.sleep_nav))
... ... @@ -1033,6 +1061,7 @@ class ObjectRegistrationPanel(wx.Panel):
1033 1061 Publisher.subscribe(self.UpdateTrackerInit, 'Update tracker initializer')
1034 1062 Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status')
1035 1063 Publisher.subscribe(self.OnCloseProject, 'Close project data')
  1064 + Publisher.subscribe(self.OnRemoveObject, 'Remove object data')
1036 1065  
1037 1066 def UpdateTrackerInit(self, nav_prop):
1038 1067 self.nav_prop = nav_prop
... ... @@ -1109,7 +1138,7 @@ class ObjectRegistrationPanel(wx.Panel):
1109 1138 def OnLinkLoad(self, event=None):
1110 1139 filename = dlg.ShowLoadSaveDialog(message=_(u"Load object registration"),
1111 1140 wildcard=_("Registration files (*.obr)|*.obr"))
1112   - # data_dir = os.environ.get('OneDrive') + r'\data\dti_navigation\baran\pilot_20200131'
  1141 + # data_dir = os.environ.get('OneDrive') + r'\data\dti_navigation\baran\anat_reg_improve_20200609'
1113 1142 # coil_path = 'magstim_coil_dell_laptop.obr'
1114 1143 # filename = os.path.join(data_dir, coil_path)
1115 1144  
... ... @@ -1155,6 +1184,9 @@ class ObjectRegistrationPanel(wx.Panel):
1155 1184 wx.MessageBox(_("Object file successfully saved"), _("Save"))
1156 1185  
1157 1186 def OnCloseProject(self):
  1187 + self.OnRemoveObject()
  1188 +
  1189 + def OnRemoveObject(self):
1158 1190 self.checkrecordcoords.SetValue(False)
1159 1191 self.checkrecordcoords.Enable(0)
1160 1192 self.checktrack.SetValue(False)
... ... @@ -1167,6 +1199,8 @@ class ObjectRegistrationPanel(wx.Panel):
1167 1199 self.obj_name = None
1168 1200 self.timestamp = const.TIMESTAMP
1169 1201  
  1202 + Publisher.sendMessage('Update track object state', flag=False, obj_name=False)
  1203 +
1170 1204  
1171 1205 class MarkersPanel(wx.Panel):
1172 1206 def __init__(self, parent):
... ... @@ -1183,6 +1217,7 @@ class MarkersPanel(wx.Panel):
1183 1217  
1184 1218 self.current_coord = 0, 0, 0, 0, 0, 0
1185 1219 self.current_angle = 0, 0, 0
  1220 + self.current_seed = 0, 0, 0
1186 1221 self.list_coord = []
1187 1222 self.marker_ind = 0
1188 1223 self.tgt_flag = self.tgt_index = None
... ... @@ -1265,14 +1300,15 @@ class MarkersPanel(wx.Panel):
1265 1300  
1266 1301 def __bind_events(self):
1267 1302 # Publisher.subscribe(self.UpdateCurrentCoord, 'Co-registered points')
1268   - Publisher.subscribe(self.UpdateCurrentCoord, 'Update cross position')
  1303 + Publisher.subscribe(self.UpdateCurrentCoord, 'Set cross focal point')
1269 1304 Publisher.subscribe(self.OnDeleteSingleMarker, 'Delete fiducial marker')
1270 1305 Publisher.subscribe(self.OnDeleteAllMarkers, 'Delete all markers')
1271 1306 Publisher.subscribe(self.OnCreateMarker, 'Create marker')
1272 1307 Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status')
  1308 + Publisher.subscribe(self.UpdateSeedCoordinates, 'Update tracts')
1273 1309  
1274   - def UpdateCurrentCoord(self, arg, position):
1275   - self.current_coord = position[:]
  1310 + def UpdateCurrentCoord(self, position):
  1311 + self.current_coord = position
1276 1312 #self.current_angle = pubsub_evt.data[1][3:]
1277 1313  
1278 1314 def UpdateNavigationStatus(self, nav_status, vis_status):
... ... @@ -1283,6 +1319,9 @@ class MarkersPanel(wx.Panel):
1283 1319 else:
1284 1320 self.nav_status = True
1285 1321  
  1322 + def UpdateSeedCoordinates(self, root=None, affine_vtk=None, coord_offset=(0, 0, 0)):
  1323 + self.current_seed = coord_offset
  1324 +
1286 1325 def OnMouseRightDown(self, evt):
1287 1326 # TODO: Enable the "Set as target" only when target is created with registered object
1288 1327 menu_id = wx.Menu()
... ... @@ -1430,30 +1469,49 @@ class MarkersPanel(wx.Panel):
1430 1469 coord = self.current_coord
1431 1470  
1432 1471 if evt is None:
1433   - self.CreateMarker(coord, colour, self.marker_size, marker_id)
  1472 + if coord:
  1473 + self.CreateMarker(coord=coord, colour=(0.0, 1.0, 0.0), size=self.marker_size,
  1474 + marker_id=marker_id, seed=self.current_seed)
  1475 + else:
  1476 + self.CreateMarker(coord=self.current_coord, colour=colour, size=self.marker_size,
  1477 + seed=self.current_seed)
1434 1478 else:
1435   - self.CreateMarker(self.current_coord, colour, self.marker_size, marker_id)
  1479 + self.CreateMarker(coord=self.current_coord, colour=colour, size=self.marker_size,
  1480 + seed=self.current_seed)
1436 1481  
1437 1482 def OnLoadMarkers(self, evt):
1438 1483 filename = dlg.ShowLoadSaveDialog(message=_(u"Load markers"),
1439 1484 wildcard=_("Markers files (*.mks)|*.mks"))
1440   - # data_dir = os.environ.get('OneDrive') + r'\data\dti_navigation\baran\pilot_20200131'
  1485 + # data_dir = os.environ.get('OneDrive') + r'\data\dti_navigation\baran\anat_reg_improve_20200609'
1441 1486 # marker_path = 'markers.mks'
1442 1487 # filename = os.path.join(data_dir, marker_path)
1443 1488  
1444 1489 if filename:
1445 1490 try:
1446 1491 count_line = self.lc.GetItemCount()
1447   - content = [s.rstrip() for s in open(filename)]
1448   - for data in content:
  1492 + # content = [s.rstrip() for s in open(filename)]
  1493 + with open(filename, 'r') as file:
  1494 + reader = csv.reader(file, delimiter='\t')
  1495 + content = [row for row in reader]
  1496 +
  1497 + for line in content:
1449 1498 target = None
1450   - line = [s for s in data.split()]
  1499 + # line = [s for s in data.split()]
1451 1500 if len(line) > 8:
1452   - coord = float(line[0]), float(line[1]), float(line[2]), float(line[3]), float(line[4]), float(line[5])
1453   - colour = float(line[6]), float(line[7]), float(line[8])
  1501 + coord = [float(s) for s in line[:6]]
  1502 + colour = [float(s) for s in line[6:9]]
1454 1503 size = float(line[9])
  1504 + # marker_id = line[10]
  1505 + if len(line) > 11:
  1506 + seed = [float(s) for s in line[11:14]]
  1507 + else:
  1508 + seed = 0., 0., 0.
  1509 +
  1510 + # coord = float(line[0]), float(line[1]), float(line[2]), float(line[3]), float(line[4]), float(line[5])
  1511 + # colour = float(line[6]), float(line[7]), float(line[8])
  1512 + # size = float(line[9])
1455 1513  
1456   - if len(line) == 11:
  1514 + if len(line) >= 11:
1457 1515 for i in const.BTNS_IMG_MKS:
1458 1516 if line[10] in list(const.BTNS_IMG_MKS[i].values())[0]:
1459 1517 Publisher.sendMessage('Load image fiducials', marker_id=line[10], coord=coord)
... ... @@ -1462,7 +1520,7 @@ class MarkersPanel(wx.Panel):
1462 1520 else:
1463 1521 line.append("")
1464 1522  
1465   - self.CreateMarker(coord, colour, size, line[10])
  1523 + self.CreateMarker(coord, colour, size, line[10], seed)
1466 1524 if target is not None:
1467 1525 self.OnMenuSetTarget(target)
1468 1526  
... ... @@ -1499,22 +1557,25 @@ class MarkersPanel(wx.Panel):
1499 1557  
1500 1558 if filename:
1501 1559 if self.list_coord:
1502   - text_file = open(filename, "w")
1503   - list_slice1 = self.list_coord[0]
1504   - coord = str('%.3f' %self.list_coord[0][0]) + "\t" + str('%.3f' %self.list_coord[0][1]) + "\t" + str('%.3f' %self.list_coord[0][2])
1505   - angles = str('%.3f' %self.list_coord[0][3]) + "\t" + str('%.3f' %self.list_coord[0][4]) + "\t" + str('%.3f' %self.list_coord[0][5])
1506   - properties = str('%.3f' %list_slice1[6]) + "\t" + str('%.3f' %list_slice1[7]) + "\t" + str('%.3f' %list_slice1[8]) + "\t" + str('%.1f' %list_slice1[9]) + "\t" + list_slice1[10]
1507   - line = coord + "\t" + angles + "\t" + properties + "\n"
1508   - list_slice = self.list_coord[1:]
1509   -
1510   - for value in list_slice:
1511   - coord = str('%.3f' %value[0]) + "\t" + str('%.3f' %value[1]) + "\t" + str('%.3f' %value[2])
1512   - angles = str('%.3f' % value[3]) + "\t" + str('%.3f' % value[4]) + "\t" + str('%.3f' % value[5])
1513   - properties = str('%.3f' %value[6]) + "\t" + str('%.3f' %value[7]) + "\t" + str('%.3f' %value[8]) + "\t" + str('%.1f' %value[9]) + "\t" + value[10]
1514   - line = line + coord + "\t" + angles + "\t" +properties + "\n"
1515   -
1516   - text_file.writelines(line)
1517   - text_file.close()
  1560 + with open(filename, 'w', newline='') as file:
  1561 + writer = csv.writer(file, delimiter='\t')
  1562 + writer.writerows(self.list_coord)
  1563 + # text_file = open(filename, "w")
  1564 + # list_slice1 = self.list_coord[0]
  1565 + # coord = str('%.3f' %self.list_coord[0][0]) + "\t" + str('%.3f' %self.list_coord[0][1]) + "\t" + str('%.3f' %self.list_coord[0][2])
  1566 + # angles = str('%.3f' %self.list_coord[0][3]) + "\t" + str('%.3f' %self.list_coord[0][4]) + "\t" + str('%.3f' %self.list_coord[0][5])
  1567 + # properties = str('%.3f' %list_slice1[6]) + "\t" + str('%.3f' %list_slice1[7]) + "\t" + str('%.3f' %list_slice1[8]) + "\t" + str('%.1f' %list_slice1[9]) + "\t" + list_slice1[10]
  1568 + # line = coord + "\t" + angles + "\t" + properties + "\n"
  1569 + # list_slice = self.list_coord[1:]
  1570 + #
  1571 + # for value in list_slice:
  1572 + # coord = str('%.3f' %value[0]) + "\t" + str('%.3f' %value[1]) + "\t" + str('%.3f' %value[2])
  1573 + # angles = str('%.3f' % value[3]) + "\t" + str('%.3f' % value[4]) + "\t" + str('%.3f' % value[5])
  1574 + # properties = str('%.3f' %value[6]) + "\t" + str('%.3f' %value[7]) + "\t" + str('%.3f' %value[8]) + "\t" + str('%.1f' %value[9]) + "\t" + value[10]
  1575 + # line = line + coord + "\t" + angles + "\t" +properties + "\n"
  1576 + #
  1577 + # text_file.writelines(line)
  1578 + # text_file.close()
1518 1579  
1519 1580 def OnSelectColour(self, evt, ctrl):
1520 1581 self.marker_colour = [colour/255.0 for colour in ctrl.GetValue()]
... ... @@ -1522,18 +1583,26 @@ class MarkersPanel(wx.Panel):
1522 1583 def OnSelectSize(self, evt, ctrl):
1523 1584 self.marker_size = ctrl.GetValue()
1524 1585  
1525   - def CreateMarker(self, coord, colour, size, marker_id=""):
  1586 + def CreateMarker(self, coord, colour, size, marker_id="x", seed=(0, 0, 0)):
1526 1587 # TODO: Use matrix coordinates and not world coordinates as current method.
1527 1588 # This makes easier for inter-software comprehension.
1528 1589  
1529 1590 Publisher.sendMessage('Add marker', ball_id=self.marker_ind, size=size, colour=colour, coord=coord[0:3])
1530 1591  
1531 1592 self.marker_ind += 1
1532   - if not marker_id:
1533   - marker_id = ""
1534   - # List of lists with coordinates and properties of a marker
1535 1593  
1536   - line = [coord[0], coord[1], coord[2], coord[3], coord[4], coord[5], colour[0], colour[1], colour[2], size, marker_id]
  1594 + # List of lists with coordinates and properties of a marker
  1595 + line = []
  1596 + line.extend(coord)
  1597 + line.extend(colour)
  1598 + line.append(size)
  1599 + line.append(marker_id)
  1600 + line.extend(seed)
  1601 +
  1602 + # line = [coord[0], coord[1], coord[2], coord[3], coord[4], coord[5], colour[0], colour[1], colour[2], size, marker_id]
  1603 + # line = [coord[0], coord[1], coord[2], coord[3], coord[4], coord[5],
  1604 + # colour[0], colour[1], colour[2], size, marker_id,
  1605 + # seed[0], seed[1], seed[2]]
1537 1606  
1538 1607 # Adding current line to a list of all markers already created
1539 1608 if not self.list_coord:
... ... @@ -1602,17 +1671,9 @@ class TractographyPanel(wx.Panel):
1602 1671 self.SetAutoLayout(1)
1603 1672 self.__bind_events()
1604 1673  
1605   - # Button for creating new coil
1606   - tooltip = wx.ToolTip(_("Load brain visualization"))
1607   - btn_mask = wx.Button(self, -1, _("Load brain"), size=wx.Size(65, 23))
1608   - btn_mask.SetToolTip(tooltip)
1609   - btn_mask.Enable(1)
1610   - btn_mask.Bind(wx.EVT_BUTTON, self.OnLinkBrain)
1611   - # self.btn_new = btn_new
1612   -
1613 1674 # Button for import config coil file
1614 1675 tooltip = wx.ToolTip(_("Load FOD"))
1615   - btn_load = wx.Button(self, -1, _("Load FOD"), size=wx.Size(65, 23))
  1676 + btn_load = wx.Button(self, -1, _("FOD"), size=wx.Size(50, 23))
1616 1677 btn_load.SetToolTip(tooltip)
1617 1678 btn_load.Enable(1)
1618 1679 btn_load.Bind(wx.EVT_BUTTON, self.OnLinkFOD)
... ... @@ -1626,11 +1687,28 @@ class TractographyPanel(wx.Panel):
1626 1687 btn_load_cfg.Bind(wx.EVT_BUTTON, self.OnLoadParameters)
1627 1688 # self.btn_load_cfg = btn_load_cfg
1628 1689  
  1690 + # Button for creating new coil
  1691 + tooltip = wx.ToolTip(_("Load brain visualization"))
  1692 + btn_mask = wx.Button(self, -1, _("Brain"), size=wx.Size(50, 23))
  1693 + btn_mask.SetToolTip(tooltip)
  1694 + btn_mask.Enable(1)
  1695 + btn_mask.Bind(wx.EVT_BUTTON, self.OnLinkBrain)
  1696 + # self.btn_new = btn_new
  1697 +
  1698 + # Button for creating new coil
  1699 + tooltip = wx.ToolTip(_("Load anatomical labels"))
  1700 + btn_act = wx.Button(self, -1, _("ACT"), size=wx.Size(50, 23))
  1701 + btn_act.SetToolTip(tooltip)
  1702 + btn_act.Enable(1)
  1703 + btn_act.Bind(wx.EVT_BUTTON, self.OnLoadACT)
  1704 + # self.btn_new = btn_new
  1705 +
1629 1706 # Create a horizontal sizer to represent button save
1630 1707 line_btns = wx.BoxSizer(wx.HORIZONTAL)
1631   - line_btns.Add(btn_load, 1, wx.LEFT | wx.TOP | wx.RIGHT, 4)
1632   - line_btns.Add(btn_load_cfg, 1, wx.LEFT | wx.TOP | wx.RIGHT, 4)
1633   - line_btns.Add(btn_mask, 1, wx.LEFT | wx.TOP | wx.RIGHT, 4)
  1708 + line_btns.Add(btn_load, 1, wx.LEFT | wx.TOP | wx.RIGHT, 2)
  1709 + line_btns.Add(btn_load_cfg, 1, wx.LEFT | wx.TOP | wx.RIGHT, 2)
  1710 + line_btns.Add(btn_mask, 1, wx.LEFT | wx.TOP | wx.RIGHT, 2)
  1711 + line_btns.Add(btn_act, 1, wx.LEFT | wx.TOP | wx.RIGHT, 2)
1634 1712  
1635 1713 # Change peeling depth
1636 1714 text_peel_depth = wx.StaticText(self, -1, _("Peeling depth (mm):"))
... ... @@ -1729,10 +1807,18 @@ class TractographyPanel(wx.Panel):
1729 1807 checkpeeling.Bind(wx.EVT_CHECKBOX, partial(self.OnShowPeeling, ctrl=checkpeeling))
1730 1808 self.checkpeeling = checkpeeling
1731 1809  
  1810 + # Check box to enable tract visualization
  1811 + checkACT = wx.CheckBox(self, -1, _('ACT'))
  1812 + checkACT.SetValue(False)
  1813 + checkACT.Enable(0)
  1814 + checkACT.Bind(wx.EVT_CHECKBOX, partial(self.OnEnableACT, ctrl=checkACT))
  1815 + self.checkACT = checkACT
  1816 +
1732 1817 border_last = 1
1733 1818 line_checks = wx.BoxSizer(wx.HORIZONTAL)
1734 1819 line_checks.Add(checktracts, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.LEFT, border_last)
1735   - line_checks.Add(checkpeeling, 0, wx.RIGHT | wx.LEFT, border_last)
  1820 + line_checks.Add(checkpeeling, 0, wx.ALIGN_CENTER | wx.RIGHT | wx.LEFT, border_last)
  1821 + line_checks.Add(checkACT, 0, wx.RIGHT | wx.LEFT, border_last)
1736 1822  
1737 1823 # Add line sizers into main sizer
1738 1824 border = 1
... ... @@ -1753,7 +1839,7 @@ class TractographyPanel(wx.Panel):
1753 1839  
1754 1840 def __bind_events(self):
1755 1841 Publisher.subscribe(self.OnCloseProject, 'Close project data')
1756   - Publisher.subscribe(self.OnUpdateTracts, 'Update cross position')
  1842 + Publisher.subscribe(self.OnUpdateTracts, 'Set cross focal point')
1757 1843 Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status')
1758 1844  
1759 1845 def OnSelectPeelingDepth(self, evt, ctrl):
... ... @@ -1801,23 +1887,34 @@ class TractographyPanel(wx.Panel):
1801 1887 Publisher.sendMessage('Remove tracts')
1802 1888 Publisher.sendMessage("Update marker offset state", create=False)
1803 1889  
  1890 + def OnEnableACT(self, evt, ctrl):
  1891 + # self.view_peeling = ctrl.GetValue()
  1892 + # if ctrl.GetValue():
  1893 + # act_data = self.brain_peel.get_actor(self.peel_depth)
  1894 + # else:
  1895 + # actor = None
  1896 + Publisher.sendMessage('Enable ACT', data=ctrl.GetValue())
  1897 +
1804 1898 def UpdateNavigationStatus(self, nav_status, vis_status):
1805 1899 self.nav_status = nav_status
1806 1900  
1807 1901 def OnLinkBrain(self, event=None):
1808 1902 Publisher.sendMessage('Update status text in GUI', label=_("Busy"))
1809 1903 Publisher.sendMessage('Begin busy cursor')
1810   - mask_path = dlg.ShowImportOtherFilesDialog(const.ID_TREKKER_MASK)
1811   - img_path = dlg.ShowImportOtherFilesDialog(const.ID_TREKKER_IMG)
1812   - # data_dir = os.environ.get('OneDriveConsumer') + '\\data\\dti'
1813   - # mask_file = 'sub-P0_dwi_mask.nii'
  1904 + mask_path = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, _("Import brain mask"))
  1905 + img_path = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, _("Import T1 anatomical image"))
  1906 + # data_dir = os.environ.get('OneDrive') + r'\data\dti_navigation\baran\anat_reg_improve_20200609'
  1907 + # mask_file = 'Baran_brain_mask.nii'
1814 1908 # mask_path = os.path.join(data_dir, mask_file)
1815   - # img_file = 'sub-P0_T1w_biascorrected.nii'
  1909 + # img_file = 'Baran_T1_inFODspace.nii'
1816 1910 # img_path = os.path.join(data_dir, img_file)
1817 1911  
1818 1912 if not self.affine_vtk:
1819 1913 slic = sl.Slice()
1820   - self.affine = slic.affine
  1914 + prj_data = prj.Project()
  1915 + matrix_shape = tuple(prj_data.matrix_shape)
  1916 + self.affine = slic.affine.copy()
  1917 + self.affine[1, -1] -= matrix_shape[1]
1821 1918 self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine)
1822 1919  
1823 1920 try:
... ... @@ -1837,18 +1934,26 @@ class TractographyPanel(wx.Panel):
1837 1934 def OnLinkFOD(self, event=None):
1838 1935 Publisher.sendMessage('Update status text in GUI', label=_("Busy"))
1839 1936 Publisher.sendMessage('Begin busy cursor')
1840   - filename = dlg.ShowImportOtherFilesDialog(const.ID_TREKKER_FOD)
  1937 + filename = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, msg=_("Import Trekker FOD"))
1841 1938 # Juuso
1842 1939 # data_dir = os.environ.get('OneDriveConsumer') + '\\data\\dti'
1843 1940 # FOD_path = 'sub-P0_dwi_FOD.nii'
1844 1941 # Baran
1845   - # data_dir = os.environ.get('OneDrive') + r'\data\dti_navigation\baran\pilot_20200131'
  1942 + # data_dir = os.environ.get('OneDrive') + r'\data\dti_navigation\baran\anat_reg_improve_20200609'
1846 1943 # FOD_path = 'Baran_FOD.nii'
1847 1944 # filename = os.path.join(data_dir, FOD_path)
1848 1945  
  1946 + # if not self.affine_vtk:
  1947 + # slic = sl.Slice()
  1948 + # self.affine = slic.affine
  1949 + # self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine)
  1950 +
1849 1951 if not self.affine_vtk:
1850 1952 slic = sl.Slice()
1851   - self.affine = slic.affine
  1953 + prj_data = prj.Project()
  1954 + matrix_shape = tuple(prj_data.matrix_shape)
  1955 + self.affine = slic.affine.copy()
  1956 + self.affine[1, -1] -= matrix_shape[1]
1852 1957 self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine)
1853 1958  
1854 1959 # try:
... ... @@ -1868,6 +1973,40 @@ class TractographyPanel(wx.Panel):
1868 1973  
1869 1974 Publisher.sendMessage('End busy cursor')
1870 1975  
  1976 + def OnLoadACT(self, event=None):
  1977 + Publisher.sendMessage('Update status text in GUI', label=_("Busy"))
  1978 + Publisher.sendMessage('Begin busy cursor')
  1979 + filename = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, msg=_("Import anatomical labels"))
  1980 + # Baran
  1981 + # data_dir = os.environ.get('OneDrive') + r'\data\dti_navigation\baran\anat_reg_improve_20200609'
  1982 + # act_path = 'Baran_trekkerACTlabels_inFODspace.nii'
  1983 + # filename = os.path.join(data_dir, act_path)
  1984 +
  1985 + act_data = nb.squeeze_image(nb.load(filename))
  1986 + act_data = nb.as_closest_canonical(act_data)
  1987 + act_data.update_header()
  1988 + act_data_arr = act_data.get_fdata()
  1989 +
  1990 + if not self.affine_vtk:
  1991 + slic = sl.Slice()
  1992 + prj_data = prj.Project()
  1993 + matrix_shape = tuple(prj_data.matrix_shape)
  1994 + self.affine = slic.affine.copy()
  1995 + self.affine[1, -1] -= matrix_shape[1]
  1996 + self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine)
  1997 +
  1998 + self.checkACT.Enable(1)
  1999 + self.checkACT.SetValue(True)
  2000 +
  2001 + Publisher.sendMessage('Update ACT data', data=act_data_arr)
  2002 + Publisher.sendMessage('Enable ACT', data=True)
  2003 + # Publisher.sendMessage('Create grid', data=act_data_arr, affine=self.affine)
  2004 + # Publisher.sendMessage('Update number of threads', data=n_threads)
  2005 + # Publisher.sendMessage('Update tracts visualization', data=1)
  2006 + Publisher.sendMessage('Update status text in GUI', label=_("Trekker ACT loaded"))
  2007 +
  2008 + Publisher.sendMessage('End busy cursor')
  2009 +
1871 2010 def OnLoadParameters(self, event=None):
1872 2011 import json
1873 2012 filename = dlg.ShowLoadSaveDialog(message=_(u"Load Trekker configuration"),
... ... @@ -1893,7 +2032,7 @@ class TractographyPanel(wx.Panel):
1893 2032 wx.MessageBox(_("File incompatible, using default configuration."), _("InVesalius 3"))
1894 2033 Publisher.sendMessage('Update status text in GUI', label="")
1895 2034  
1896   - def OnUpdateTracts(self, arg, position):
  2035 + def OnUpdateTracts(self, position):
1897 2036 """
1898 2037 Minimal working version of tract computation. Updates when cross sends Pubsub message to update.
1899 2038 Position refers to the coordinates in InVesalius 2D space. To represent the same coordinates in the 3D space,
... ... @@ -1907,7 +2046,8 @@ class TractographyPanel(wx.Panel):
1907 2046 # pass
1908 2047 if self.view_tracts and not self.nav_status:
1909 2048 # print("Running during navigation")
1910   - coord_flip = db.flip_x_m(position[:3])[:3, 0]
  2049 + coord_flip = list(position[:3])
  2050 + coord_flip[1] = -coord_flip[1]
1911 2051 dti.compute_tracts(self.trekker, coord_flip, self.affine, self.affine_vtk,
1912 2052 self.n_tracts)
1913 2053  
... ... @@ -1916,6 +2056,8 @@ class TractographyPanel(wx.Panel):
1916 2056 self.checktracts.Enable(0)
1917 2057 self.checkpeeling.SetValue(False)
1918 2058 self.checkpeeling.Enable(0)
  2059 + self.checkACT.SetValue(False)
  2060 + self.checkACT.Enable(0)
1919 2061  
1920 2062 self.spin_opacity.SetValue(const.BRAIN_OPACITY)
1921 2063 self.spin_opacity.Enable(0)
... ... @@ -1989,10 +2131,11 @@ class UpdateNavigationScene(threading.Thread):
1989 2131 # use of CallAfter is mandatory otherwise crashes the wx interface
1990 2132 if self.view_tracts:
1991 2133 bundle, affine_vtk, coord_offset = self.tracts_queue.get_nowait()
  2134 + #TODO: Check if possible to combine the Remove tracts with Update tracts in a single command
1992 2135 wx.CallAfter(Publisher.sendMessage, 'Remove tracts')
1993   - wx.CallAfter(Publisher.sendMessage, 'Update tracts', flag=True, root=bundle,
1994   - affine_vtk=affine_vtk)
1995   - wx.CallAfter(Publisher.sendMessage, 'Update marker offset', coord_offset=coord_offset)
  2136 + wx.CallAfter(Publisher.sendMessage, 'Update tracts', root=bundle,
  2137 + affine_vtk=affine_vtk, coord_offset=coord_offset)
  2138 + # wx.CallAfter(Publisher.sendMessage, 'Update marker offset', coord_offset=coord_offset)
1996 2139 self.tracts_queue.task_done()
1997 2140  
1998 2141 if self.trigger_state:
... ... @@ -2001,9 +2144,10 @@ class UpdateNavigationScene(threading.Thread):
2001 2144 wx.CallAfter(Publisher.sendMessage, 'Create marker')
2002 2145 self.trigger_queue.task_done()
2003 2146  
2004   - # TODO: If using the view_tracts substitute the raw coord from the offset coordinate, so the user
  2147 + #TODO: If using the view_tracts substitute the raw coord from the offset coordinate, so the user
2005 2148 # see the red cross in the position of the offset marker
2006   - wx.CallAfter(Publisher.sendMessage, 'Update cross position', arg=m_img, position=coord)
  2149 + wx.CallAfter(Publisher.sendMessage, 'Set cross focal point', position=coord)
  2150 + wx.CallAfter(Publisher.sendMessage, 'Update slice viewer')
2007 2151  
2008 2152 if view_obj:
2009 2153 wx.CallAfter(Publisher.sendMessage, 'Update object matrix', m_img=m_img, coord=coord)
... ...
invesalius/presets.py
... ... @@ -129,10 +129,11 @@ class Presets():
129 129 thresh_ct_new = {}
130 130 for name in self.thresh_ct.keys():
131 131 thresh_ct_new[translate_to_en[name]] = self.thresh_ct[name]
132   -
  132 +
133 133 preset['thresh_mri'] = thresh_mri_new
134 134 preset['thresh_ct'] = thresh_ct_new
135   - plistlib.writePlist(preset, filename)
  135 + with open(filename, 'w+b') as f:
  136 + plistlib.dump(preset, f)
136 137 return os.path.split(filename)[1]
137 138  
138 139 def OpenPlist(self, filename):
... ... @@ -154,7 +155,8 @@ class Presets():
154 155 "Custom":_("Custom")}
155 156  
156 157  
157   - p = plistlib.readPlist(filename)
  158 + with open(filename, 'r+b') as f:
  159 + p = plistlib.load(f, fmt=plistlib.FMT_XML)
158 160 thresh_mri = p['thresh_mri'].copy()
159 161 thresh_ct = p['thresh_ct'].copy()
160 162  
... ... @@ -181,7 +183,8 @@ def get_wwwl_presets():
181 183  
182 184  
183 185 def get_wwwl_preset_colours(pfile):
184   - preset = plistlib.readPlist(pfile)
  186 + with open(pfile, 'r+b') as f:
  187 + preset = plistlib.load(f, fmt=plistlib.FMT_XML)
185 188 ncolours = len(preset['Blue'])
186 189 colours = []
187 190 for i in range(ncolours):
... ...
invesalius/project.py
... ... @@ -188,7 +188,8 @@ class Project(metaclass=Singleton):
188 188  
189 189 def SetRaycastPreset(self, label):
190 190 path = os.path.join(RAYCASTING_PRESETS_DIRECTORY, label + '.plist')
191   - preset = plistlib.readPlist(path)
  191 + with open(path, 'r+b') as f:
  192 + preset = plistlib.load(f, fmt=plistlib.FMT_XML)
192 193 Publisher.sendMessage('Set raycasting preset', preset)
193 194  
194 195 def GetMeasuresDict(self):
... ... @@ -252,9 +253,9 @@ class Project(metaclass=Singleton):
252 253 # Saving the measurements
253 254 measurements = self.GetMeasuresDict()
254 255 measurements_filename = 'measurements.plist'
255   - temp_mplist = tempfile.mktemp()
256   - plistlib.writePlist(measurements,
257   - temp_mplist)
  256 + temp_mplist = tempfile.mktemp()
  257 + with open(temp_mplist, 'w+b') as f:
  258 + plistlib.dump(measurements, f)
258 259 filelist[temp_mplist] = measurements_filename
259 260 project['measurements'] = measurements_filename
260 261  
... ... @@ -263,7 +264,8 @@ class Project(metaclass=Singleton):
263 264  
264 265 # Saving the main plist
265 266 temp_plist = tempfile.mktemp()
266   - plistlib.writePlist(project, temp_plist)
  267 + with open(temp_plist, 'w+b') as f:
  268 + plistlib.dump(project, f)
267 269 filelist[temp_plist] = 'main.plist'
268 270  
269 271 # Compressing and generating the .inv3 file
... ... @@ -298,7 +300,8 @@ class Project(metaclass=Singleton):
298 300 import invesalius.data.surface as srf
299 301 # Opening the main file from invesalius 3 project
300 302 main_plist = os.path.join(dirpath ,'main.plist')
301   - project = plistlib.readPlist(main_plist)
  303 + with open(main_plist, 'r+b') as f:
  304 + project = plistlib.load(f, fmt=plistlib.FMT_XML)
302 305  
303 306 format_version = project["format_version"]
304 307 if format_version > const.INVESALIUS_ACTUAL_FORMAT_VERSION:
... ... @@ -350,7 +353,8 @@ class Project(metaclass=Singleton):
350 353 self.measurement_dict = {}
351 354 measures_file = os.path.join(dirpath, project.get("measurements", "measurements.plist"))
352 355 if os.path.exists(measures_file):
353   - measurements = plistlib.readPlist(measures_file)
  356 + with open(measures_file, 'r+b') as f:
  357 + measurements = plistlib.load(f, fmt=plistlib.FMT_XML)
354 358 for index in measurements:
355 359 if measurements[index]["type"] in (const.DENSITY_ELLIPSE, const.DENSITY_POLYGON):
356 360 measure = ms.DensityMeasurement()
... ... @@ -390,7 +394,10 @@ class Project(metaclass=Singleton):
390 394  
391 395 "matrix": matrix,
392 396 }
393   - plistlib.writePlist(project, os.path.join(folder, 'main.plist'))
  397 +
  398 + path = os.path.join(folder, 'main.plist')
  399 + with open(path, 'w+b') as f:
  400 + plistlib.dump(project, f)
394 401  
395 402  
396 403 def export_project(self, filename, save_masks=True):
... ...
invesalius/segmentation/brain/utils.py
... ... @@ -15,6 +15,14 @@ def prepare_plaidml():
15 15 if local_user_plaidml.exists():
16 16 os.environ["RUNFILES_DIR"] = str(local_user_plaidml)
17 17 os.environ["PLAIDML_NATIVE_PATH"] = str(pathlib.Path("/usr/local/lib/libplaidml.dylib").expanduser().absolute())
  18 + elif sys.platform == "win32":
  19 + if 'VIRTUAL_ENV' in os.environ:
  20 + local_user_plaidml = pathlib.Path(os.environ["VIRTUAL_ENV"]).joinpath("share/plaidml")
  21 + plaidml_dll = pathlib.Path(os.environ["VIRTUAL_ENV"]).joinpath("library/bin/plaidml.dll")
  22 + if local_user_plaidml.exists():
  23 + os.environ["RUNFILES_DIR"] = str(local_user_plaidml)
  24 + if plaidml_dll.exists():
  25 + os.environ["PLAIDML_NATIVE_PATH"] = str(plaidml_dll)
18 26  
19 27 def prepare_ambient(backend, device_id, use_gpu):
20 28 if backend.lower() == 'plaidml':
... ...
requirements.txt 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +Cython==0.29.22
  2 +Pillow==8.1.0
  3 +Pypubsub==4.0.3
  4 +configparser==5.0.1
  5 +h5py==2.10.0
  6 +imageio==2.9.0
  7 +nibabel==3.2.1
  8 +numpy==1.20.1
  9 +plaidml-keras==0.7.0
  10 +psutil==5.8.0
  11 +pyserial==3.5
  12 +python-gdcm==3.0.8.1
  13 +scikit-image==0.18.1
  14 +scipy==1.6.1
  15 +vtk==9.0.1
  16 +wxPython==4.1.0
  17 +Theano==1.0.5
... ...