Commit e6cce6f8d99e23470088a65b61439ff11d1ffdc7
Exists in
master
Merge branch 'master' into icp
# Conflicts: # invesalius/constants.py # invesalius/data/coregistration.py # invesalius/gui/task_navigator.py
Showing
21 changed files
with
1019 additions
and
299 deletions
Show diff stats
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]) | ... | ... |
... | ... | @@ -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
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': | ... | ... |
... | ... | @@ -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 | ... | ... |