Compare View
Commits (15)
-
…D conditions for markers and balls during navigation
-
278 add visualization for the direction of the marker
-
* ENH: Update tractography parameters and fix config file load * FIX: Problem when loading the object registration after loading FOD * FIX: Fix torus bug when creating surface and update tractography parameters * FIX: Include spacing in the offset for tract computation * ADD: Update tractography parameters and image shift with spacing * ENH: Improve grid offset for tractography * WIP: Improved tractography computation and seed sampling * WIP: Refactored and cleaned ACT tractography needs testing in navigation * FIX: Bump conda environment packages to python 3.8 and tract parameters * FIX: Save the seed marker in world coordinate space * ENH: Affine is identity by default and not None Co-authored-by: Renan <renan_hiroshi@hotmail.com>
Showing
12 changed files
Show diff stats
environment.yml
... | ... | @@ -2,7 +2,7 @@ channels: |
2 | 2 | - conda-forge |
3 | 3 | - bioconda |
4 | 4 | dependencies: |
5 | - - python=3.7 | |
5 | + - python=3.8 | |
6 | 6 | - cython==0.29.24 |
7 | 7 | - pillow==8.3.2 |
8 | 8 | - pypubsub==4.0.3 |
... | ... | @@ -17,13 +17,21 @@ dependencies: |
17 | 17 | - scipy==1.7.1 |
18 | 18 | - vtk==9.0.3 |
19 | 19 | - wxpython==4.1.1 |
20 | + - mido==1.2.10 | |
21 | + - nest-asyncio==1.5.1 | |
20 | 22 | - pip |
21 | 23 | - pip: |
22 | - - python-gdcm==3.0.9.1 | |
24 | + - aioconsole==0.3.2 | |
25 | + - opencv-python==4.5.3.56 | |
23 | 26 | - plaidml-keras==0.7.0 |
24 | - - theano==1.0.5 | |
25 | - - pyacvd==0.2.3 | |
27 | + - python-gdcm==3.0.9.1 | |
28 | + - python-rtmidi==1.4.9 | |
29 | + - python-socketio[client]==5.3.0 | |
30 | + - pyacvd==0.2.7 | |
26 | 31 | - pyclaron |
27 | 32 | - polhemusFT |
28 | 33 | - polhemus |
29 | 34 | - pypolaris |
35 | + - theano==1.0.5 | |
36 | + - uvicorn[standard]==0.15.0 | |
37 | + - https://github.com/baranaydogan/trekker/raw/master/binaries/Trekker-0.9-cp38-cp38-win_amd64.whl | ... | ... |
invesalius/constants.py
... | ... | @@ -671,7 +671,7 @@ Z_COLUMN = 6 |
671 | 671 | |
672 | 672 | MARKER_COLOUR = (1.0, 1.0, 0.) |
673 | 673 | MARKER_SIZE = 2 |
674 | - | |
674 | +ARROW_MARKER_SIZE = 10 | |
675 | 675 | CALIBRATION_TRACKER_SAMPLES = 10 |
676 | 676 | FIDUCIAL_REGISTRATION_ERROR_THRESHOLD = 3.0 |
677 | 677 | |
... | ... | @@ -823,23 +823,35 @@ COIL_ANGLE_ARROW_PROJECTION_THRESHOLD = 5 |
823 | 823 | CAM_MODE = True |
824 | 824 | |
825 | 825 | # Tractography visualization |
826 | -N_TRACTS = 100 | |
827 | -PEEL_DEPTH = 5 | |
828 | -MAX_PEEL_DEPTH = 30 | |
829 | -SEED_OFFSET = 15 | |
826 | +N_TRACTS = 200 | |
827 | +PEEL_DEPTH = 10 | |
828 | +MAX_PEEL_DEPTH = 40 | |
829 | +SEED_OFFSET = 30 | |
830 | 830 | SEED_RADIUS = 1.5 |
831 | 831 | |
832 | 832 | # Increased the default sleep parameter from 0.1 to 0.15 to decrease CPU load during navigation. |
833 | -SLEEP_NAVIGATION = 0.15 | |
833 | +SLEEP_NAVIGATION = 0.2 | |
834 | 834 | SLEEP_COORDINATES = 0.05 |
835 | 835 | SLEEP_ROBOT = 0.01 |
836 | 836 | |
837 | -BRAIN_OPACITY = 0.5 | |
837 | +BRAIN_OPACITY = 0.6 | |
838 | 838 | N_CPU = psutil.cpu_count() |
839 | -TREKKER_CONFIG = {'seed_max': 1, 'step_size': 0.1, 'min_fod': 0.1, 'probe_quality': 3, | |
840 | - 'max_interval': 1, 'min_radius_curv': 0.8, 'probe_length': 0.4, | |
841 | - 'write_interval': 50, 'numb_threads': '', 'max_lenth': 200, | |
842 | - 'min_lenth': 20, 'max_sampling_step': 100} | |
839 | +# the max_sampling_step can be set to something different as well. Above 100 is probably not necessary | |
840 | +TREKKER_CONFIG = {'seed_max': 1, | |
841 | + 'step_size': 0.03125, | |
842 | + 'min_fod': 0.05, | |
843 | + 'probe_quality': 3, | |
844 | + 'max_interval': 1, | |
845 | + 'min_radius_curvature': 0.625, | |
846 | + 'probe_length': 0.15625, | |
847 | + 'write_interval': 50, | |
848 | + 'numb_threads': '', | |
849 | + 'max_length': 250, | |
850 | + 'min_length': 10, | |
851 | + 'max_sampling_step': 100, | |
852 | + 'data_support_exponent': 0.5, | |
853 | + 'use_best_init': True, | |
854 | + 'init_max_est_trials': 100} | |
843 | 855 | |
844 | 856 | MARKER_FILE_MAGICK_STRING = "##INVESALIUS3_MARKER_FILE_" |
845 | 857 | CURRENT_MARKER_FILE_VERSION = 0 | ... | ... |
invesalius/control.py
... | ... | @@ -69,7 +69,7 @@ class Controller(): |
69 | 69 | #DICOM = 1 |
70 | 70 | #TIFF uCT = 2 |
71 | 71 | self.img_type = 0 |
72 | - self.affine = None | |
72 | + self.affine = np.identity(4) | |
73 | 73 | |
74 | 74 | #Init session |
75 | 75 | session = ses.Session() |
... | ... | @@ -340,7 +340,7 @@ class Controller(): |
340 | 340 | if proj.affine: |
341 | 341 | self.Slice.affine = np.asarray(proj.affine).reshape(4, 4) |
342 | 342 | else: |
343 | - self.Slice.affine = None | |
343 | + self.Slice.affine = np.identity(4) | |
344 | 344 | |
345 | 345 | Publisher.sendMessage('Update threshold limits list', |
346 | 346 | threshold_range=proj.threshold_range) |
... | ... | @@ -623,6 +623,8 @@ class Controller(): |
623 | 623 | Publisher.sendMessage(('Set scroll position', 'SAGITAL'),index=proj.matrix_shape[1]/2) |
624 | 624 | Publisher.sendMessage(('Set scroll position', 'CORONAL'),index=proj.matrix_shape[2]/2) |
625 | 625 | |
626 | + # TODO: Check that this is needed with the new way of using affine | |
627 | + # now the affine should be at least the identity(4) and never None | |
626 | 628 | if self.Slice.affine is not None: |
627 | 629 | Publisher.sendMessage('Enable Go-to-Coord', status=True) |
628 | 630 | else: |
... | ... | @@ -731,6 +733,8 @@ class Controller(): |
731 | 733 | proj.level = self.Slice.window_level |
732 | 734 | proj.threshold_range = int(matrix.min()), int(matrix.max()) |
733 | 735 | proj.spacing = self.Slice.spacing |
736 | + # TODO: Check that this is needed with the new way of using affine | |
737 | + # now the affine should be at least the identity(4) and never None | |
734 | 738 | if self.Slice.affine is not None: |
735 | 739 | proj.affine = self.Slice.affine.tolist() |
736 | 740 | ... | ... |
invesalius/data/coordinates.py
invesalius/data/coregistration.py
... | ... | @@ -427,7 +427,7 @@ class CoordinateCorregistrateNoObject(threading.Thread): |
427 | 427 | # # seed_aux = pos_world.reshape([1, 4])[0, :3] |
428 | 428 | # # seed = seed_aux[np.newaxis, :] |
429 | 429 | # # |
430 | -# # self.tracts = dtr.compute_tracts(tracker, seed, affine_vtk, True) | |
430 | +# # self.tracts = dtr.compute_and_visualize_tracts(tracker, seed, affine_vtk, True) | |
431 | 431 | # |
432 | 432 | # # wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=m_img, position=coord) |
433 | 433 | # wx.CallAfter(Publisher.sendMessage, 'Update cross position', arg=m_img, position=coord) |
... | ... | @@ -654,7 +654,7 @@ class CoordinateCorregistrateNoObject(threading.Thread): |
654 | 654 | # # seed_aux = pos_world.reshape([1, 4])[0, :3] |
655 | 655 | # # seed = seed_aux[np.newaxis, :] |
656 | 656 | # |
657 | -# # self.tracts = dtr.compute_tracts(tracker, seed, affine_vtk, True) | |
657 | +# # self.tracts = dtr.compute_and_visualize_tracts(tracker, seed, affine_vtk, True) | |
658 | 658 | # |
659 | 659 | # # wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=m_img, position=coord) |
660 | 660 | # wx.CallAfter(Publisher.sendMessage, 'Update cross position', arg=m_img, position=coord) | ... | ... |
invesalius/data/styles.py
... | ... | @@ -486,7 +486,7 @@ class CrossInteractorStyle(DefaultInteractorStyle): |
486 | 486 | x, y, z = self.viewer.get_coordinate_cursor(mouse_x, mouse_y, self.picker) |
487 | 487 | self.viewer.UpdateSlicesPosition([x, y, z]) |
488 | 488 | # This "Set cross" message is needed to update the cross in the other slices |
489 | - Publisher.sendMessage('Set cross focal point', position=[x, y, z, 0., 0., 0.]) | |
489 | + Publisher.sendMessage('Set cross focal point', position=[x, y, z, None, None, None]) | |
490 | 490 | Publisher.sendMessage('Update slice viewer') |
491 | 491 | |
492 | 492 | def OnScrollBar(self, *args, **kwargs): |
... | ... | @@ -494,7 +494,7 @@ class CrossInteractorStyle(DefaultInteractorStyle): |
494 | 494 | # the actual orientation. |
495 | 495 | x, y, z = self.viewer.cross.GetFocalPoint() |
496 | 496 | self.viewer.UpdateSlicesPosition([x, y, z]) |
497 | - Publisher.sendMessage('Set cross focal point', position=[x, y, z, 0., 0., 0.]) | |
497 | + Publisher.sendMessage('Set cross focal point', position=[x, y, z, None, None, None]) | |
498 | 498 | Publisher.sendMessage('Update slice viewer') |
499 | 499 | |
500 | 500 | |
... | ... | @@ -544,7 +544,7 @@ class TractsInteractorStyle(CrossInteractorStyle): |
544 | 544 | |
545 | 545 | def OnTractsMouseClick(self, obj, evt): |
546 | 546 | # print("Single mouse click") |
547 | - # self.tracts = dtr.compute_tracts(self.tracker, self.seed, self.left_pressed) | |
547 | + # self.tracts = dtr.compute_and_visualize_tracts(self.tracker, self.seed, self.left_pressed) | |
548 | 548 | self.ChangeTracts(True) |
549 | 549 | |
550 | 550 | def OnTractsReleaseLeftButton(self, obj, evt): |
... | ... | @@ -554,7 +554,7 @@ class TractsInteractorStyle(CrossInteractorStyle): |
554 | 554 | |
555 | 555 | def ChangeTracts(self, pressed): |
556 | 556 | # print("Trying to compute tracts") |
557 | - self.tracts = dtr.compute_tracts(self.tracker, self.seed, self.affine_vtk, pressed) | |
557 | + self.tracts = dtr.compute_and_visualize_tracts(self.tracker, self.seed, self.affine_vtk, pressed) | |
558 | 558 | # mouse_x, mouse_y = iren.GetEventPosition() |
559 | 559 | # wx, wy, wz = self.viewer.get_coordinate_cursor(mouse_x, mouse_y, self.picker) |
560 | 560 | # px, py = self.viewer.get_slice_pixel_coord_by_world_pos(wx, wy, wz) | ... | ... |
invesalius/data/surface.py
... | ... | @@ -365,9 +365,16 @@ class SurfaceManager(): |
365 | 365 | if self.convert2inv: |
366 | 366 | # convert between invesalius and world space with shift in the Y coordinate |
367 | 367 | affine = sl.Slice().affine |
368 | + # TODO: Check that this is needed with the new way of using affine | |
369 | + # now the affine should be at least the identity(4) and never None | |
368 | 370 | if affine is not None: |
369 | - affine[1, -1] -= sl.Slice().spacing[1] * (sl.Slice().matrix.shape[1] - 1) | |
371 | + matrix_shape = sl.Slice().matrix.shape | |
372 | + spacing = sl.Slice().spacing | |
373 | + img_shift = spacing[1] * (matrix_shape[1] - 1) | |
374 | + affine = sl.Slice().affine.copy() | |
375 | + affine[1, -1] -= img_shift | |
370 | 376 | affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(affine) |
377 | + | |
371 | 378 | actor.SetUserMatrix(affine_vtk) |
372 | 379 | |
373 | 380 | if overwrite: | ... | ... |
invesalius/data/tractography.py
... | ... | @@ -29,14 +29,11 @@ import time |
29 | 29 | import numpy as np |
30 | 30 | import queue |
31 | 31 | from invesalius.pubsub import pub as Publisher |
32 | -from scipy.stats import norm | |
33 | 32 | import vtk |
34 | 33 | |
35 | 34 | import invesalius.constants as const |
36 | 35 | import invesalius.data.imagedata_utils as img_utils |
37 | 36 | |
38 | -import invesalius.project as prj | |
39 | - | |
40 | 37 | # Nice print for arrays |
41 | 38 | # np.set_printoptions(precision=2) |
42 | 39 | # np.set_printoptions(suppress=True) |
... | ... | @@ -104,75 +101,57 @@ def compute_tubes(trk, direction): |
104 | 101 | return trk_tube |
105 | 102 | |
106 | 103 | |
107 | -def combine_tracts_root(out_list, root, n_block): | |
104 | +def create_branch(out_list, n_block): | |
108 | 105 | """Adds a set of tracts to given position in a given vtkMultiBlockDataSet |
109 | 106 | |
110 | 107 | :param out_list: List of vtkTubeFilters representing the tracts |
111 | 108 | :type out_list: list |
112 | - :param root: A collection of tracts as a vtkMultiBlockDataSet | |
113 | - :type root: vtkMultiBlockDataSet | |
114 | 109 | :param n_block: The location in the given vtkMultiBlockDataSet to insert the new tracts |
115 | 110 | :type n_block: int |
116 | - :return: The updated collection of tracts as a vtkMultiBlockDataSet | |
117 | - :rtype: vtkMultiBlockDataSet | |
118 | - """ | |
119 | - | |
120 | - # create tracts only when at least one was computed | |
121 | - # print("Len outlist in root: ", len(out_list)) | |
122 | - if not out_list.count(None) == len(out_list): | |
123 | - for n, tube in enumerate(out_list): | |
124 | - root.SetBlock(n_block + n, tube.GetOutput()) | |
125 | - | |
126 | - return root | |
127 | - | |
128 | - | |
129 | -def combine_tracts_branch(out_list): | |
130 | - """Combines a set of tracts in vtkMultiBlockDataSet | |
131 | - | |
132 | - :param out_list: List of vtkTubeFilters representing the tracts | |
133 | - :type out_list: list | |
134 | - :return: A collection of tracts as a vtkMultiBlockDataSet | |
111 | + :return: The collection of tracts (streamlines) as a vtkMultiBlockDataSet | |
135 | 112 | :rtype: vtkMultiBlockDataSet |
136 | 113 | """ |
137 | 114 | |
115 | + # create a branch and add the streamlines | |
138 | 116 | branch = vtk.vtkMultiBlockDataSet() |
117 | + | |
139 | 118 | # create tracts only when at least one was computed |
140 | 119 | # print("Len outlist in root: ", len(out_list)) |
120 | + # TODO: check if this if statement is required, because we should | |
121 | + # call this function only when tracts exist | |
141 | 122 | if not out_list.count(None) == len(out_list): |
142 | 123 | for n, tube in enumerate(out_list): |
143 | - branch.SetBlock(n, tube.GetOutput()) | |
124 | + branch.SetBlock(n_block + n, tube.GetOutput()) | |
144 | 125 | |
145 | 126 | return branch |
146 | 127 | |
147 | 128 | |
148 | -def tracts_computation(trk_list, root, n_tracts): | |
129 | +def compute_tracts(trk_list, n_tract=0, alpha=255): | |
149 | 130 | """Convert the list of all computed tracts given by Trekker run and returns a vtkMultiBlockDataSet |
150 | 131 | |
151 | 132 | :param trk_list: List of lists containing the computed tracts and corresponding coordinates |
152 | 133 | :type trk_list: list |
153 | - :param root: A collection of tracts as a vtkMultiBlockDataSet | |
154 | - :type root: vtkMultiBlockDataSet | |
155 | - :param n_tracts: | |
156 | - :type n_tracts: int | |
134 | + :param n_tract: The integer ID of the block in the vtkMultiBlockDataSet | |
135 | + :type n_tract: int | |
136 | + :param alpha: The transparency of the streamlines from 0 to 255 (transparent to opaque) | |
137 | + :type alpha: int | |
157 | 138 | :return: The updated collection of tracts as a vtkMultiBlockDataSet |
158 | 139 | :rtype: vtkMultiBlockDataSet |
159 | 140 | """ |
160 | 141 | |
161 | 142 | # Transform tracts to array |
162 | 143 | trk_arr = [np.asarray(trk_n).T if trk_n else None for trk_n in trk_list] |
163 | - | |
164 | 144 | # Compute the directions |
165 | - trk_dir = [compute_directions(trk_n) for trk_n in trk_arr] | |
166 | - | |
145 | + trk_dir = [compute_directions(trk_n, alpha) for trk_n in trk_arr] | |
167 | 146 | # Compute the vtk tubes |
168 | 147 | out_list = [compute_tubes(trk_arr_n, trk_dir_n) for trk_arr_n, trk_dir_n in zip(trk_arr, trk_dir)] |
148 | + # create a branch and add the tracts | |
149 | + branch = create_branch(out_list, n_tract) | |
169 | 150 | |
170 | - root = combine_tracts_root(out_list, root, n_tracts) | |
171 | - | |
172 | - return root | |
151 | + return branch | |
173 | 152 | |
174 | 153 | |
175 | -def compute_tracts(trekker, position, affine, affine_vtk, n_tracts): | |
154 | +def compute_and_visualize_tracts(trekker, position, affine, affine_vtk, n_tracts_max): | |
176 | 155 | """ Compute tractograms using the Trekker library. |
177 | 156 | |
178 | 157 | :param trekker: Trekker library instance |
... | ... | @@ -183,52 +162,51 @@ def compute_tracts(trekker, position, affine, affine_vtk, n_tracts): |
183 | 162 | :type affine: numpy.ndarray |
184 | 163 | :param affine_vtk: vtkMatrix4x4 isntance with affine transformation matrix |
185 | 164 | :type affine_vtk: vtkMatrix4x4 |
186 | - :param n_tracts: number of tracts to compute | |
187 | - :type n_tracts: int | |
165 | + :param n_tracts_max: maximum number of tracts to compute | |
166 | + :type n_tracts_max: int | |
188 | 167 | """ |
189 | 168 | |
190 | - # during neuronavigation, root needs to be initialized outside the while loop so the new tracts | |
191 | - # can be appended to the root block set | |
192 | - root = vtk.vtkMultiBlockDataSet() | |
169 | + # root = vtk.vtkMultiBlockDataSet() | |
193 | 170 | # Juuso's |
194 | 171 | # seed = np.array([[-8.49, -8.39, 2.5]]) |
195 | 172 | # Baran M1 |
196 | 173 | # seed = np.array([[27.53, -77.37, 46.42]]) |
197 | 174 | seed_trk = img_utils.convert_world_to_voxel(position, affine) |
198 | - # print("seed example: {}".format(seed_trk)) | |
199 | - trekker.seed_coordinates(np.repeat(seed_trk, n_tracts, axis=0)) | |
200 | - # print("trk list len: ", len(trekker.run())) | |
201 | - trk_list = trekker.run() | |
202 | - if trk_list: | |
203 | - root = tracts_computation(trk_list, root, 0) | |
204 | - Publisher.sendMessage('Remove tracts') | |
205 | - Publisher.sendMessage('Update tracts', root=root, affine_vtk=affine_vtk, coord_offset=position) | |
206 | - else: | |
207 | - Publisher.sendMessage('Remove tracts') | |
208 | - | |
209 | - | |
210 | -def tracts_computation_branch(trk_list, alpha=255): | |
211 | - """Convert the list of all computed tracts given by Trekker run and returns a vtkMultiBlockDataSet | |
212 | - | |
213 | - :param trk_list: List of lists containing the computed tracts and corresponding coordinates | |
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 | |
217 | - :return: The collection of tracts as a vtkMultiBlockDataSet | |
218 | - :rtype: vtkMultiBlockDataSet | |
219 | - """ | |
220 | - # Transform tracts to array | |
221 | - trk_arr = [np.asarray(trk_n).T if trk_n else None for trk_n in trk_list] | |
222 | - # Compute the directions | |
223 | - trk_dir = [compute_directions(trk_n, alpha) for trk_n in trk_arr] | |
224 | - # Compute the vtk tubes | |
225 | - tube_list = [compute_tubes(trk_arr_n, trk_dir_n) for trk_arr_n, trk_dir_n in zip(trk_arr, trk_dir)] | |
226 | - branch = combine_tracts_branch(tube_list) | |
227 | - | |
228 | - return branch | |
175 | + bundle = vtk.vtkMultiBlockDataSet() | |
176 | + n_branches, n_tracts, count_loop = 0, 0, 0 | |
177 | + n_threads = 2 * const.N_CPU - 1 | |
178 | + | |
179 | + while n_tracts < n_tracts_max: | |
180 | + n_param = 1 + (count_loop % 10) | |
181 | + # rescale the alpha value that defines the opacity of the branch | |
182 | + # the n interval is [1, 10] and the new interval is [51, 255] | |
183 | + # the new interval is defined to have no 0 opacity (minimum is 51, i.e., 20%) | |
184 | + alpha = (n_param - 1) * (255 - 51) / (10 - 1) + 51 | |
185 | + trekker.minFODamp(n_param * 0.01) | |
186 | + | |
187 | + # print("seed example: {}".format(seed_trk)) | |
188 | + trekker.seed_coordinates(np.repeat(seed_trk, n_threads, axis=0)) | |
189 | + # print("trk list len: ", len(trekker.run())) | |
190 | + trk_list = trekker.run() | |
191 | + n_tracts += len(trk_list) | |
192 | + if len(trk_list): | |
193 | + branch = compute_tracts(trk_list, n_tract=0, alpha=alpha) | |
194 | + bundle.SetBlock(n_branches, branch) | |
195 | + n_branches += 1 | |
196 | + | |
197 | + count_loop += 1 | |
198 | + | |
199 | + if (count_loop == 20) and (n_tracts == 0): | |
200 | + break | |
201 | + | |
202 | + Publisher.sendMessage('Remove tracts') | |
203 | + if n_tracts: | |
204 | + Publisher.sendMessage('Update tracts', root=bundle, affine_vtk=affine_vtk, | |
205 | + coord_offset=position, coord_offset_w=seed_trk[0].tolist()) | |
229 | 206 | |
230 | 207 | |
231 | 208 | class ComputeTractsThread(threading.Thread): |
209 | + # TODO: Remove this class and create a case where no ACT is provided in the class ComputeTractsACTThread | |
232 | 210 | |
233 | 211 | def __init__(self, inp, queues, event, sle): |
234 | 212 | """Class (threading) to compute real time tractography data for visualization. |
... | ... | @@ -265,6 +243,7 @@ class ComputeTractsThread(threading.Thread): |
265 | 243 | |
266 | 244 | trekker, affine, offset, n_tracts_total, seed_radius, n_threads, act_data, affine_vtk, img_shift = self.inp |
267 | 245 | # n_threads = n_tracts_total |
246 | + n_threads = int(n_threads/4) | |
268 | 247 | p_old = np.array([[0., 0., 0.]]) |
269 | 248 | n_tracts = 0 |
270 | 249 | |
... | ... | @@ -312,13 +291,13 @@ class ComputeTractsThread(threading.Thread): |
312 | 291 | # run the trekker, this is the slowest line of code, be careful to just use once! |
313 | 292 | trk_list = trekker.run() |
314 | 293 | |
315 | - if trk_list: | |
294 | + if len(trk_list) > 2: | |
316 | 295 | # print("dist: {}".format(dist)) |
317 | 296 | if dist >= seed_radius: |
318 | 297 | # when moving the coil further than the seed_radius restart the bundle computation |
319 | 298 | bundle = vtk.vtkMultiBlockDataSet() |
320 | 299 | n_branches = 0 |
321 | - branch = tracts_computation_branch(trk_list) | |
300 | + branch = compute_tracts(trk_list, n_tract=0, alpha=255) | |
322 | 301 | bundle.SetBlock(n_branches, branch) |
323 | 302 | n_branches += 1 |
324 | 303 | n_tracts = branch.GetNumberOfBlocks() |
... | ... | @@ -326,7 +305,7 @@ class ComputeTractsThread(threading.Thread): |
326 | 305 | # TODO: maybe keep computing even if reaches the maximum |
327 | 306 | elif dist < seed_radius and n_tracts < n_tracts_total: |
328 | 307 | # compute tracts blocks and add to bungle until reaches the maximum number of tracts |
329 | - branch = tracts_computation_branch(trk_list) | |
308 | + branch = compute_tracts(trk_list, n_tract=0, alpha=255) | |
330 | 309 | if bundle: |
331 | 310 | bundle.SetBlock(n_branches, branch) |
332 | 311 | n_tracts += branch.GetNumberOfBlocks() |
... | ... | @@ -361,314 +340,218 @@ class ComputeTractsThread(threading.Thread): |
361 | 340 | |
362 | 341 | class ComputeTractsACTThread(threading.Thread): |
363 | 342 | |
364 | - def __init__(self, inp, queues, event, sle): | |
343 | + def __init__(self, input_list, queues, event, sleep_thread): | |
365 | 344 | """Class (threading) to compute real time tractography data for visualization. |
366 | 345 | |
367 | 346 | Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/) |
368 | 347 | For VTK visualization, each tract (fiber) is a constructed as a tube and many tubes combined in one |
369 | 348 | vtkMultiBlockDataSet named as a branch. Several branches are combined in another vtkMultiBlockDataSet named as |
370 | 349 | bundle, to obtain fast computation and visualization. The bundle dataset is mapped to a single vtkActor. |
371 | - Mapper and Actor are computer in the data/viewer_volume.py module for easier handling in the invesalius 3D scene. | |
350 | + Mapper and Actor are computer in the data/viewer_volume.py module for easier handling in the | |
351 | + invesalius 3D scene. | |
372 | 352 | |
373 | 353 | Sleep function in run method is used to avoid blocking GUI and more fluent, real-time navigation |
374 | 354 | |
375 | - :param inp: List of inputs: trekker instance, affine numpy array, seed_offset, seed_radius, n_threads | |
376 | - :type inp: list | |
355 | + :param input_list: List of inputs: trekker instance, affine numpy array, seed offset, total number of tracts, | |
356 | + seed radius, number of threads in computer, ACT data array, affine vtk matrix, | |
357 | + image shift for vtk to mri transformation | |
358 | + :type input_list: list | |
377 | 359 | :param queues: Queue list with coord_tracts_queue (Queue instance that manage co-registered coordinates) and |
378 | 360 | tracts_queue (Queue instance that manage the tracts to be visualized) |
379 | 361 | :type queues: list[queue.Queue, queue.Queue] |
380 | 362 | :param event: Threading event to coordinate when tasks as done and allow UI release |
381 | 363 | :type event: threading.Event |
382 | - :param sle: Sleep pause in seconds | |
383 | - :type sle: float | |
364 | + :param sleep_thread: Sleep pause in seconds | |
365 | + :type sleep_thread: float | |
384 | 366 | """ |
385 | 367 | |
386 | 368 | threading.Thread.__init__(self, name='ComputeTractsThreadACT') |
387 | - self.inp = inp | |
388 | - # self.coord_queue = coord_queue | |
369 | + self.input_list = input_list | |
389 | 370 | self.coord_tracts_queue = queues[0] |
390 | 371 | self.tracts_queue = queues[1] |
391 | - # on first pilots (january 12, 2021) used (-4, 4) | |
392 | - self.coord_list_w = img_utils.create_grid((-2, 2), (0, 20), inp[2]-5, 1) | |
393 | - # self.coord_list_sph = img_utils.create_spherical_grid(10, 1) | |
394 | - # self.coord_list_sph = img_utils.create_spherical_grid(10, 1) | |
395 | - # x_norm = np.linspace(norm.ppf(0.01), norm.ppf(0.99), 2*self.coord_list_sph.shape[0]) | |
396 | - # self.pdf = np.flipud(norm.pdf(x_norm[:self.coord_list_sph.shape[0]], loc=0, scale=2.)) | |
397 | - # self.sph_idx = np.linspace(0, self.coord_list_sph.shape[0], num=self.coord_list_sph.shape[0], dtype=int) | |
398 | - # self.visualization_queue = visualization_queue | |
399 | 372 | self.event = event |
400 | - self.sle = sle | |
401 | - | |
402 | - # prj_data = prj.Project() | |
403 | - # matrix_shape = tuple(prj_data.matrix_shape) | |
404 | - # self.img_shift = matrix_shape[1] | |
373 | + self.sleep_thread = sleep_thread | |
405 | 374 | |
406 | 375 | def run(self): |
407 | 376 | |
408 | - # trekker, affine, offset, n_tracts_total, seed_radius, n_threads = self.inp | |
409 | - trekker, affine, offset, n_tracts_total, seed_radius, n_threads, act_data, affine_vtk, img_shift = self.inp | |
377 | + trekker, affine, offset, n_tracts_total, seed_radius, n_threads, act_data, affine_vtk, img_shift = self.input_list | |
410 | 378 | |
411 | - # n_threads = n_tracts_total | |
412 | 379 | p_old = np.array([[0., 0., 0.]]) |
413 | - p_old_pre = np.array([[0., 0., 0.]]) | |
414 | - coord_offset = None | |
415 | - n_tracts = 0 | |
416 | - n_branches = 0 | |
380 | + n_branches, n_tracts, count_loop = 0, 0, 0 | |
417 | 381 | bundle = None |
418 | - sph_sampling = True | |
419 | 382 | dist_radius = 1.5 |
420 | 383 | |
421 | - xyz = img_utils.random_sample_sphere(radius=seed_radius, size=100) | |
422 | - coord_list_sph = np.hstack([xyz, np.ones([xyz.shape[0], 1])]).T | |
384 | + # TODO: Try a denser and bigger grid, because it's just a matrix multiplication | |
385 | + # maybe 15 mm below the coil offset by default and 5 cm deep | |
386 | + # create the rectangular grid to find the gray-white matter boundary | |
387 | + coord_list_w = img_utils.create_grid((-2, 2), (0, 20), offset - 5, 1) | |
388 | + | |
389 | + # create the spherical grid to sample around the seed location | |
390 | + samples_in_sphere = img_utils.random_sample_sphere(radius=seed_radius, size=100) | |
391 | + coord_list_sphere = np.hstack([samples_in_sphere, np.ones([samples_in_sphere.shape[0], 1])]).T | |
423 | 392 | m_seed = np.identity(4) |
393 | + | |
424 | 394 | # Compute the tracts |
425 | - # print('ComputeTractsThread: event {}'.format(self.event.is_set())) | |
426 | 395 | while not self.event.is_set(): |
427 | 396 | try: |
428 | - # print("Computing tracts") | |
429 | 397 | # get from the queue the coordinates, coregistration transformation matrix, and flipped matrix |
430 | 398 | m_img_flip = self.coord_tracts_queue.get_nowait() |
431 | - # coord, m_img, m_img_flip = self.coord_queue.get_nowait() | |
432 | - # print('ComputeTractsThread: get {}'.format(count)) | |
433 | - | |
434 | - # TODO: Remove this is not needed | |
435 | - # 20200402: in this new refactored version the m_img comes different than the position | |
436 | - # the new version m_img is already flixped in y, which means that Y is negative | |
437 | - # if only the Y is negative maybe no need for the flip_x funtcion at all in the navigation | |
438 | - # but check all coord_queue before why now the m_img comes different than position | |
439 | - # 20200403: indeed flip_x is just a -1 multiplication to the Y coordinate, remove function flip_x | |
440 | - # m_img_flip = m_img.copy() | |
441 | - # m_img_flip[1, -1] = -m_img_flip[1, -1] | |
442 | 399 | |
443 | 400 | # DEBUG: Uncomment the m_img_flip below so that distance is fixed and tracts keep computing |
444 | 401 | # m_img_flip[:3, -1] = (5., 10., 12.) |
445 | - dist = abs(np.linalg.norm(p_old_pre - np.asarray(m_img_flip[:3, -1]))) | |
446 | - p_old_pre = m_img_flip[:3, -1].copy() | |
447 | - | |
448 | - # Uncertanity visualization -- | |
449 | - # each tract branch is computed with one set of parameters ajusted from 1 to 10 | |
450 | - n_param = 1 + (n_branches % 10) | |
402 | + dist = abs(np.linalg.norm(p_old - np.asarray(m_img_flip[:3, -1]))) | |
403 | + p_old = m_img_flip[:3, -1].copy() | |
404 | + | |
405 | + # Uncertainty visualization -- | |
406 | + # each tract branch is computed with one minFODamp adjusted from 0.01 to 0.1 | |
407 | + # the higher the minFODamp the more the streamlines are faithful to the data, so they become more strict | |
408 | + # but also may loose some connections. | |
409 | + # the lower the more relaxed streamline also increases the chance of false positives | |
410 | + n_param = 1 + (count_loop % 10) | |
451 | 411 | # rescale the alpha value that defines the opacity of the branch |
452 | 412 | # the n interval is [1, 10] and the new interval is [51, 255] |
453 | 413 | # the new interval is defined to have no 0 opacity (minimum is 51, i.e., 20%) |
454 | 414 | alpha = (n_param - 1) * (255 - 51) / (10 - 1) + 51 |
455 | 415 | trekker.minFODamp(n_param * 0.01) |
456 | - trekker.dataSupportExponent(n_param * 0.1) | |
457 | 416 | # --- |
458 | 417 | |
418 | + try: | |
419 | + # The original seed location is replaced by the gray-white matter interface that is closest to | |
420 | + # the coil center | |
421 | + coord_list_w_tr = m_img_flip @ coord_list_w | |
422 | + coord_offset = grid_offset(act_data, coord_list_w_tr, img_shift) | |
423 | + except IndexError: | |
424 | + # This error might be caused by the coordinate exceeding the image array dimensions. | |
425 | + # This happens during navigation because the coil location can have coordinates outside the image | |
426 | + # boundaries | |
427 | + # Translate the coordinate along the normal vector of the object/coil | |
428 | + coord_offset = m_img_flip[:3, -1] - offset * m_img_flip[:3, 2] | |
429 | + # --- | |
430 | + | |
431 | + # Spherical sampling of seed coordinates --- | |
432 | + # compute the samples of a sphere centered on seed coordinate offset by the grid | |
433 | + # given in the invesalius-vtk space | |
434 | + samples = np.random.choice(coord_list_sphere.shape[1], size=100) | |
435 | + m_seed[:-1, -1] = coord_offset.copy() | |
436 | + # translate the spherical grid samples to the coil location in invesalius-vtk space | |
437 | + seed_trk_r_inv = m_seed @ coord_list_sphere[:, samples] | |
438 | + | |
439 | + coord_offset_w = np.hstack((coord_offset, 1.0)).reshape([4, 1]) | |
440 | + | |
441 | + try: | |
442 | + # Anatomic constrained seed computation --- | |
443 | + # find only the samples inside the white matter as with the ACT enabled in the Trekker, | |
444 | + # it does not compute any tracts outside the white matter | |
445 | + # convert from inveslaius-vtk to mri space | |
446 | + seed_trk_r_mri = seed_trk_r_inv[:3, :].T.astype(int) + np.array([[0, img_shift, 0]], dtype=np.int32) | |
447 | + labs = act_data[seed_trk_r_mri[..., 0], seed_trk_r_mri[..., 1], seed_trk_r_mri[..., 2]] | |
448 | + # find all samples in the white matter | |
449 | + labs_id = np.where(labs == 1) | |
450 | + # Trekker has internal multiprocessing approach done in C. Here the number of available threads - 1 | |
451 | + # is given, but in case a large number of tracts is requested, it will compute all in parallel | |
452 | + # automatically for a more fluent navigation, better to compute the maximum number the computer can | |
453 | + # handle otherwise gets too slow for the multithreading in Python | |
454 | + seed_trk_r_inv_sampled = seed_trk_r_inv[:, labs_id[0][:n_threads]] | |
455 | + | |
456 | + except IndexError: | |
457 | + # same as on the grid offset above, if the coil is too far from the mri volume the array indices | |
458 | + # are beyond the mri boundaries | |
459 | + # in this case use the grid center instead of the spherical samples | |
460 | + seed_trk_r_inv_sampled = coord_offset_w.copy() | |
461 | + | |
462 | + # convert to the world coordinate system for trekker | |
463 | + seed_trk_r_world_sampled = np.linalg.inv(affine) @ seed_trk_r_inv_sampled | |
464 | + seed_trk_r_world_sampled = seed_trk_r_world_sampled.T[:, :3] | |
465 | + | |
466 | + # convert to the world coordinate system for saving in the marker list | |
467 | + coord_offset_w = np.linalg.inv(affine) @ coord_offset_w | |
468 | + coord_offset_w = np.squeeze(coord_offset_w.T[:, :3]) | |
469 | + | |
470 | + # DEBUG: uncomment the seed_trk below | |
471 | + # seed_trk.shape == [1, 3] | |
472 | + # Juuso's | |
473 | + # seed_trk = np.array([[-8.49, -8.39, 2.5]]) | |
474 | + # Baran M1 | |
475 | + # seed_trk = np.array([[27.53, -77.37, 46.42]]) | |
476 | + # print("Given: {}".format(seed_trk.shape)) | |
477 | + # print("Seed: {}".format(seed)) | |
478 | + # joonas seed that has good tracts | |
479 | + # seed_trk = np.array([[29.12, -13.33, 31.65]]) | |
480 | + # seed_trk_img = np.array([[117, 127, 161]]) | |
481 | + | |
459 | 482 | # When moving the coil further than the seed_radius restart the bundle computation |
460 | 483 | # Currently, it stops to compute tracts when the maximum number of tracts is reached maybe keep |
461 | 484 | # computing even if reaches the maximum |
462 | 485 | if dist >= dist_radius: |
463 | - # Anatomic constrained seed computation --- | |
464 | - # The original seed location is replaced by the gray-white matter interface that is closest to | |
465 | - # the coil center | |
466 | - try: | |
467 | - #TODO: Create a dialog error to say when the ACT data is not loaded and prevent | |
468 | - # the interface from freezing. Give the user a chance to load it (maybe in task_navigator) | |
469 | - coord_list_w_tr = m_img_flip @ self.coord_list_w | |
470 | - coord_offset = grid_offset(act_data, coord_list_w_tr, img_shift) | |
471 | - except IndexError: | |
472 | - # This error might be caused by the coordinate exceeding the image array dimensions. | |
473 | - # Needs further verification. | |
474 | - coord_offset = None | |
475 | - # --- | |
476 | - | |
477 | - # Translate the coordinate along the normal vector of the object/coil --- | |
478 | - if coord_offset is None: | |
479 | - # apply the coil transformation matrix | |
480 | - coord_offset = m_img_flip[:3, -1] - offset * m_img_flip[:3, 2] | |
481 | - # --- | |
482 | - | |
483 | - # convert the world coordinates to the voxel space for using as a seed in Trekker | |
484 | - # seed_trk.shape == [1, 3] | |
485 | - seed_trk = img_utils.convert_world_to_voxel(coord_offset, affine) | |
486 | - # print("Desired: {}".format(seed_trk.shape)) | |
487 | - | |
488 | - # DEBUG: uncomment the seed_trk below | |
489 | - # Juuso's | |
490 | - # seed_trk = np.array([[-8.49, -8.39, 2.5]]) | |
491 | - # Baran M1 | |
492 | - # seed_trk = np.array([[27.53, -77.37, 46.42]]) | |
493 | - # print("Given: {}".format(seed_trk.shape)) | |
494 | - # print("Seed: {}".format(seed)) | |
495 | - | |
496 | - # Spherical sampling of seed coordinates --- | |
497 | - if sph_sampling: | |
498 | - # CHECK: We use ACT only for the origin seed, but not for all the other coordinates. | |
499 | - # Check how this can be solved. Applying ACT to all coordinates is maybe too much. | |
500 | - # Maybe it doesn't matter because the ACT is just to help finding the closest location to | |
501 | - # the TMS coil center. Also, note that the spherical sampling is applied only when the coil | |
502 | - # location changes, all further iterations used the fixed n_threads samples to compute the | |
503 | - # remaining tracts. | |
504 | - | |
505 | - # samples = np.random.choice(self.sph_idx, size=n_threads, p=self.pdf) | |
506 | - # m_seed[:-1, -1] = seed_trk | |
507 | - # sph_seed = m_seed @ self.coord_list_sph | |
508 | - # seed_trk_r = sph_seed[samples, :] | |
509 | - samples = np.random.choice(coord_list_sph.shape[1], size=n_threads) | |
510 | - m_seed[:-1, -1] = seed_trk | |
511 | - seed_trk_r = m_seed @ coord_list_sph[:, samples] | |
512 | - seed_trk_r = seed_trk_r[:-1, :].T | |
513 | - else: | |
514 | - # set the seeds for trekker, one seed is repeated n_threads times | |
515 | - # shape (24, 3) | |
516 | - seed_trk_r = np.repeat(seed_trk, n_threads, axis=0) | |
517 | - | |
518 | - # --- | |
519 | - | |
520 | - # Trekker has internal multiprocessing approach done in C. Here the number of available threads is | |
521 | - # given, but in case a large number of tracts is requested, it will compute all in parallel | |
522 | - # automatically for a more fluent navigation, better to compute the maximum number the computer | |
523 | - # handles | |
524 | - trekker.seed_coordinates(seed_trk_r) | |
525 | - # trekker.seed_coordinates(np.repeat(seed_trk, n_threads, axis=0)) | |
486 | + bundle = None | |
487 | + n_tracts, n_branches = 0, 0 | |
526 | 488 | |
489 | + # we noticed that usually the navigation lags or crashes when moving the coil location | |
490 | + # to reduce the overhead for when the coil is moving, we compute only half the number of tracts | |
491 | + # that should be computed when the coil is fixed in the same location | |
492 | + # required input is Nx3 array | |
493 | + trekker.seed_coordinates(seed_trk_r_world_sampled[::2, :]) | |
527 | 494 | # run the trekker, this is the slowest line of code, be careful to just use once! |
528 | 495 | trk_list = trekker.run() |
529 | 496 | |
530 | 497 | # check if any tract was found, otherwise doesn't count |
531 | - if trk_list: | |
498 | + if len(trk_list): | |
499 | + # a bundle consists for multiple branches and each branch consists of multiple streamlines | |
500 | + # every iteration in the main loop adds a branch to the bundle | |
501 | + branch = compute_tracts(trk_list, n_tract=0, alpha=alpha) | |
502 | + n_tracts = branch.GetNumberOfBlocks() | |
503 | + | |
504 | + # create and add branch to the bundle | |
532 | 505 | bundle = vtk.vtkMultiBlockDataSet() |
533 | - branch = tracts_computation_branch(trk_list, alpha) | |
534 | 506 | bundle.SetBlock(n_branches, branch) |
535 | 507 | n_branches = 1 |
536 | - n_tracts = branch.GetNumberOfBlocks() | |
537 | - else: | |
538 | - bundle = None | |
539 | - n_branches = 0 | |
540 | - n_tracts = 0 | |
541 | 508 | |
542 | 509 | elif dist < dist_radius and n_tracts < n_tracts_total: |
510 | + # when the coil is fixed in place and the number of tracts is smaller than the total | |
511 | + if not bundle: | |
512 | + # same as above, when creating the bundle (vtkMultiBlockDataSet) we only compute half the number | |
513 | + # of tracts to reduce the overhead | |
514 | + bundle = vtk.vtkMultiBlockDataSet() | |
515 | + # required input is Nx3 array | |
516 | + trekker.seed_coordinates(seed_trk_r_world_sampled[::2, :]) | |
517 | + n_tracts, n_branches = 0, 0 | |
518 | + else: | |
519 | + # if the bundle exists compute all tracts requested | |
520 | + # required input is Nx3 array | |
521 | + trekker.seed_coordinates(seed_trk_r_world_sampled) | |
522 | + | |
543 | 523 | trk_list = trekker.run() |
544 | - if trk_list: | |
524 | + | |
525 | + if len(trk_list): | |
545 | 526 | # compute tract blocks and add to bundle until reaches the maximum number of tracts |
546 | 527 | # the alpha changes depending on the parameter set |
547 | - branch = tracts_computation_branch(trk_list, alpha) | |
548 | - if bundle: | |
549 | - bundle.SetBlock(n_branches, branch) | |
550 | - n_tracts += branch.GetNumberOfBlocks() | |
551 | - n_branches += 1 | |
552 | - else: | |
553 | - bundle = vtk.vtkMultiBlockDataSet() | |
554 | - bundle.SetBlock(n_branches, branch) | |
555 | - n_branches = 1 | |
556 | - n_tracts = branch.GetNumberOfBlocks() | |
557 | - # else: | |
558 | - # bundle = None | |
528 | + branch = compute_tracts(trk_list, n_tract=0, alpha=alpha) | |
529 | + n_tracts += branch.GetNumberOfBlocks() | |
530 | + # add branch to the bundle | |
531 | + bundle.SetBlock(n_branches, branch) | |
532 | + n_branches += 1 | |
559 | 533 | |
560 | - # else: | |
561 | - # bundle = None | |
534 | + # keep adding to the number of loops even if the tracts were not find | |
535 | + # this will keep the minFODamp changing and new seed coordinates being tried which would allow | |
536 | + # higher probability of finding tracts | |
537 | + count_loop += 1 | |
562 | 538 | |
563 | - # rethink if this should be inside the if condition, it may lock the thread if no tracts are found | |
564 | - # use no wait to ensure maximum speed and avoid visualizing old tracts in the queue, this might | |
539 | + # use "nowait" to ensure maximum speed and avoid visualizing old tracts in the queue, this might | |
565 | 540 | # be more evident in slow computer or for heavier tract computations, it is better slow update |
566 | 541 | # than visualizing old data |
567 | - # self.visualization_queue.put_nowait([coord, m_img, bundle]) | |
568 | - self.tracts_queue.put_nowait((bundle, affine_vtk, coord_offset)) | |
569 | - # print('ComputeTractsThread: put {}'.format(count)) | |
570 | - | |
542 | + self.tracts_queue.put_nowait((bundle, affine_vtk, coord_offset, coord_offset_w)) | |
571 | 543 | self.coord_tracts_queue.task_done() |
572 | - # self.coord_queue.task_done() | |
573 | - # print('ComputeTractsThread: done {}'.format(count)) | |
574 | 544 | |
575 | 545 | # sleep required to prevent user interface from being unresponsive |
576 | - time.sleep(self.sle) | |
546 | + time.sleep(self.sleep_thread) | |
577 | 547 | # if no coordinates pass |
578 | 548 | except queue.Empty: |
579 | - # print("Empty queue in tractography") | |
580 | 549 | pass |
581 | 550 | # if queue is full mark as done (may not be needed in this new "nowait" method) |
582 | 551 | except queue.Full: |
583 | - # self.coord_queue.task_done() | |
584 | 552 | self.coord_tracts_queue.task_done() |
585 | 553 | |
586 | 554 | |
587 | -class ComputeTractsThreadSingleBlock(threading.Thread): | |
588 | - | |
589 | - def __init__(self, inp, affine_vtk, coord_queue, visualization_queue, event, sle): | |
590 | - """Class (threading) to compute real time tractography data for visualization in a single loop. | |
591 | - | |
592 | - Different than ComputeTractsThread because it does not keep adding tracts to the bundle until maximum, | |
593 | - is reached. It actually compute all requested tracts at once. (Might be deleted in the future)! | |
594 | - Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/) | |
595 | - For VTK visualization, each tract (fiber) is a constructed as a tube and many tubes combined in one | |
596 | - vtkMultiBlockDataSet named as a branch. Several branches are combined in another vtkMultiBlockDataSet named as | |
597 | - bundle, to obtain fast computation and visualization. The bundle dataset is mapped to a single vtkActor. | |
598 | - Mapper and Actor are computer in the data/viewer_volume.py module for easier handling in the invesalius 3D scene. | |
599 | - | |
600 | - Sleep function in run method is used to avoid blocking GUI and more fluent, real-time navigation | |
601 | - | |
602 | - :param inp: List of inputs: trekker instance, affine numpy array, seed_offset, seed_radius, n_threads | |
603 | - :type inp: list | |
604 | - :param affine_vtk: Affine matrix in vtkMatrix4x4 instance to update objects position in 3D scene | |
605 | - :type affine_vtk: vtkMatrix4x4 | |
606 | - :param coord_queue: Queue instance that manage coordinates read from tracking device and coregistered | |
607 | - :type coord_queue: queue.Queue | |
608 | - :param visualization_queue: Queue instance that manage coordinates to be visualized | |
609 | - :type visualization_queue: queue.Queue | |
610 | - :param event: Threading event to coordinate when tasks as done and allow UI release | |
611 | - :type event: threading.Event | |
612 | - :param sle: Sleep pause in seconds | |
613 | - :type sle: float | |
614 | - """ | |
615 | - | |
616 | - threading.Thread.__init__(self, name='ComputeTractsThread') | |
617 | - self.inp = inp | |
618 | - self.affine_vtk = affine_vtk | |
619 | - self.coord_queue = coord_queue | |
620 | - self.visualization_queue = visualization_queue | |
621 | - self.event = event | |
622 | - self.sle = sle | |
623 | - | |
624 | - def run(self): | |
625 | - | |
626 | - trekker, affine, offset, n_tracts_total, seed_radius, n_threads = self.inp | |
627 | - # as a single block, computes all the maximum number of tracts at once, not optimal for navigation | |
628 | - n_threads = n_tracts_total | |
629 | - p_old = np.array([[0., 0., 0.]]) | |
630 | - root = vtk.vtkMultiBlockDataSet() | |
631 | - | |
632 | - # Compute the tracts | |
633 | - # print('ComputeTractsThread: event {}'.format(self.event.is_set())) | |
634 | - while not self.event.is_set(): | |
635 | - try: | |
636 | - coord, [coord_raw, markers_flag], m_img, m_img_flip = self.coord_queue.get_nowait() | |
637 | - | |
638 | - # translate the coordinate along the normal vector of the object/coil | |
639 | - coord_offset = m_img_flip[:3, -1] - offset * m_img_flip[:3, 2] | |
640 | - # coord_offset = np.array([[27.53, -77.37, 46.42]]) | |
641 | - dist = abs(np.linalg.norm(p_old - np.asarray(coord_offset))) | |
642 | - p_old = coord_offset.copy() | |
643 | - seed_trk = img_utils.convert_world_to_voxel(coord_offset, affine) | |
644 | - # Juuso's | |
645 | - # seed_trk = np.array([[-8.49, -8.39, 2.5]]) | |
646 | - # Baran M1 | |
647 | - # seed_trk = np.array([[27.53, -77.37, 46.42]]) | |
648 | - # print("Seed: {}".format(seed)) | |
649 | - | |
650 | - # set the seeds for trekker, one seed is repeated n_threads times | |
651 | - # trekker has internal multiprocessing approach done in C. Here the number of available threads is give, | |
652 | - # but in case a large number of tracts is requested, it will compute all in parallel automatically | |
653 | - # for a more fluent navigation, better to compute the maximum number the computer handles | |
654 | - trekker.seed_coordinates(np.repeat(seed_trk, n_threads, axis=0)) | |
655 | - # run the trekker, this is the slowest line of code, be careful to just use once! | |
656 | - trk_list = trekker.run() | |
657 | - | |
658 | - if trk_list: | |
659 | - # if the seed is outside the defined radius, restart the bundle computation | |
660 | - if dist >= seed_radius: | |
661 | - root = tracts_computation(trk_list, root, 0) | |
662 | - self.visualization_queue.put_nowait((coord, m_img, root)) | |
663 | - | |
664 | - self.coord_queue.task_done() | |
665 | - time.sleep(self.sle) | |
666 | - except queue.Empty: | |
667 | - pass | |
668 | - except queue.Full: | |
669 | - self.coord_queue.task_done() | |
670 | - | |
671 | - | |
672 | 555 | def set_trekker_parameters(trekker, params): |
673 | 556 | """Set all user-defined parameters for tractography computation using the Trekker library |
674 | 557 | |
... | ... | @@ -680,21 +563,27 @@ def set_trekker_parameters(trekker, params): |
680 | 563 | :rtype: list |
681 | 564 | """ |
682 | 565 | trekker.seed_maxTrials(params['seed_max']) |
683 | - # trekker.stepSize(params['step_size']) | |
684 | - trekker.minFODamp(params['min_fod']) | |
685 | - # trekker.probeQuality(params['probe_quality']) | |
686 | - # trekker.maxEstInterval(params['max_interval']) | |
687 | - # trekker.minRadiusOfCurvature(params['min_radius_curv']) | |
688 | - # trekker.probeLength(params['probe_length']) | |
566 | + trekker.stepSize(params['step_size']) | |
567 | + # minFODamp is not set because it should vary in the loop to create the | |
568 | + # different transparency tracts | |
569 | + # trekker.minFODamp(params['min_fod']) | |
570 | + trekker.probeQuality(params['probe_quality']) | |
571 | + trekker.maxEstInterval(params['max_interval']) | |
572 | + trekker.minRadiusOfCurvature(params['min_radius_curvature']) | |
573 | + trekker.probeLength(params['probe_length']) | |
689 | 574 | trekker.writeInterval(params['write_interval']) |
690 | - trekker.maxLength(params['max_lenth']) | |
691 | - trekker.minLength(params['min_lenth']) | |
575 | + # these two does not need to be set in the new package | |
576 | + # trekker.maxLength(params['max_length']) | |
577 | + trekker.minLength(params['min_length']) | |
692 | 578 | trekker.maxSamplingPerStep(params['max_sampling_step']) |
579 | + trekker.dataSupportExponent(params['data_support_exponent']) | |
580 | + #trekker.useBestAtInit(params['use_best_init']) | |
581 | + #trekker.initMaxEstTrials(params['init_max_est_trials']) | |
693 | 582 | |
694 | 583 | # check number if number of cores is valid in configuration file, |
695 | 584 | # otherwise use the maximum number of threads which is usually 2*N_CPUS |
696 | - n_threads = 2 * const.N_CPU | |
697 | - if isinstance((params['numb_threads']), int) and params['numb_threads'] <= 2*const.N_CPU: | |
585 | + n_threads = 2 * const.N_CPU - 1 | |
586 | + if isinstance((params['numb_threads']), int) and params['numb_threads'] <= (2*const.N_CPU-1): | |
698 | 587 | n_threads = params['numb_threads'] |
699 | 588 | |
700 | 589 | trekker.numberOfThreads(n_threads) |
... | ... | @@ -712,13 +601,21 @@ def grid_offset(data, coord_list_w_tr, img_shift): |
712 | 601 | |
713 | 602 | # extract the first occurrence of a specific label from the MRI image |
714 | 603 | labs = data[coord_list_w_tr_mri[..., 0], coord_list_w_tr_mri[..., 1], coord_list_w_tr_mri[..., 2]] |
715 | - lab_first = np.argmax(labs == 1) | |
716 | - if labs[lab_first] == 1: | |
717 | - pt_found = coord_list_w_tr_mri[lab_first, :] | |
604 | + lab_first = np.where(labs == 1) | |
605 | + if not lab_first: | |
606 | + pt_found_inv = None | |
607 | + else: | |
608 | + pt_found = coord_list_w_tr[:, lab_first[0][0]][:3] | |
718 | 609 | # convert coordinate back to invesalius 3D space |
719 | 610 | pt_found_inv = pt_found - np.array([0., img_shift, 0.]) |
720 | - else: | |
721 | - pt_found_inv = None | |
611 | + | |
612 | + # lab_first = np.argmax(labs == 1) | |
613 | + # if labs[lab_first] == 1: | |
614 | + # pt_found = coord_list_w_tr_mri[lab_first, :] | |
615 | + # # convert coordinate back to invesalius 3D space | |
616 | + # pt_found_inv = pt_found - np.array([0., img_shift, 0.]) | |
617 | + # else: | |
618 | + # pt_found_inv = None | |
722 | 619 | |
723 | 620 | # # convert to world coordinate space to use as seed for fiber tracking |
724 | 621 | # pt_found_tr = np.append(pt_found, 1)[np.newaxis, :].T | ... | ... |
invesalius/data/viewer_volume.py
... | ... | @@ -70,8 +70,8 @@ class Viewer(wx.Panel): |
70 | 70 | |
71 | 71 | self.initial_focus = None |
72 | 72 | |
73 | - self.staticballs = [] | |
74 | - | |
73 | + self.static_markers = [] | |
74 | + self.static_arrows = [] | |
75 | 75 | self.style = None |
76 | 76 | |
77 | 77 | interactor = wxVTKRenderWindowInteractor(self, -1, size = self.GetSize()) |
... | ... | @@ -278,6 +278,7 @@ class Viewer(wx.Panel): |
278 | 278 | |
279 | 279 | # Related to marker creation in navigation tools |
280 | 280 | Publisher.subscribe(self.AddMarker, 'Add marker') |
281 | + Publisher.subscribe(self.AddMarkerwithOrientation, 'Add arrow marker') | |
281 | 282 | Publisher.subscribe(self.HideAllMarkers, 'Hide all markers') |
282 | 283 | Publisher.subscribe(self.ShowAllMarkers, 'Show all markers') |
283 | 284 | Publisher.subscribe(self.RemoveAllMarkers, 'Remove all markers') |
... | ... | @@ -306,7 +307,6 @@ class Viewer(wx.Panel): |
306 | 307 | Publisher.subscribe(self.OnRemoveTracts, 'Remove tracts') |
307 | 308 | Publisher.subscribe(self.UpdateSeedOffset, 'Update seed offset') |
308 | 309 | Publisher.subscribe(self.UpdateMarkerOffsetState, 'Update marker offset state') |
309 | - Publisher.subscribe(self.UpdateMarkerOffsetPosition, 'Update marker offset') | |
310 | 310 | Publisher.subscribe(self.AddPeeledSurface, 'Update peel') |
311 | 311 | Publisher.subscribe(self.GetPeelCenters, 'Get peel centers and normals') |
312 | 312 | Publisher.subscribe(self.Initlocator_viewer, 'Get init locator') |
... | ... | @@ -579,7 +579,7 @@ class Viewer(wx.Panel): |
579 | 579 | """ |
580 | 580 | Set all markers, overwriting the previous markers. |
581 | 581 | """ |
582 | - self.RemoveAllMarkers(len(self.staticballs)) | |
582 | + self.RemoveAllMarkers(len(self.static_markers)) | |
583 | 583 | |
584 | 584 | target_selected = False |
585 | 585 | for marker in markers: |
... | ... | @@ -607,6 +607,21 @@ class Viewer(wx.Panel): |
607 | 607 | |
608 | 608 | self.UpdateRender() |
609 | 609 | |
610 | + def AddMarkerwithOrientation(self, arrow_id, size, color, coord): | |
611 | + """ | |
612 | + Markers arrow with orientation created by navigation tools and rendered in volume viewer. | |
613 | + """ | |
614 | + self.arrow_marker_id = arrow_id | |
615 | + coord_flip = list(coord) | |
616 | + coord_flip[1] = -coord_flip[1] | |
617 | + | |
618 | + arrow_actor = self.Add_ObjectArrow(coord_flip[:3], coord_flip[3:6], color, size) | |
619 | + self.static_markers.append(arrow_actor) | |
620 | + self.ren.AddActor(self.static_markers[self.arrow_marker_id]) | |
621 | + self.arrow_marker_id += 1 | |
622 | + | |
623 | + self.Refresh() | |
624 | + | |
610 | 625 | def AddMarker(self, ball_id, size, colour, coord): |
611 | 626 | """ |
612 | 627 | Markers created by navigation tools and rendered in volume viewer. |
... | ... | @@ -626,12 +641,12 @@ class Viewer(wx.Panel): |
626 | 641 | prop.SetColor(colour) |
627 | 642 | |
628 | 643 | # adding a new actor for the present ball |
629 | - self.staticballs.append(vtk.vtkActor()) | |
644 | + self.static_markers.append(vtk.vtkActor()) | |
630 | 645 | |
631 | - self.staticballs[self.ball_id].SetMapper(mapper) | |
632 | - self.staticballs[self.ball_id].SetProperty(prop) | |
646 | + self.static_markers[self.ball_id].SetMapper(mapper) | |
647 | + self.static_markers[self.ball_id].SetProperty(prop) | |
633 | 648 | |
634 | - self.ren.AddActor(self.staticballs[self.ball_id]) | |
649 | + self.ren.AddActor(self.static_markers[self.ball_id]) | |
635 | 650 | self.ball_id += 1 |
636 | 651 | |
637 | 652 | #self.UpdateRender() |
... | ... | @@ -667,33 +682,33 @@ class Viewer(wx.Panel): |
667 | 682 | def HideAllMarkers(self, indexes): |
668 | 683 | ballid = indexes |
669 | 684 | for i in range(0, ballid): |
670 | - self.staticballs[i].SetVisibility(0) | |
685 | + self.static_markers[i].SetVisibility(0) | |
671 | 686 | self.UpdateRender() |
672 | 687 | |
673 | 688 | def ShowAllMarkers(self, indexes): |
674 | 689 | ballid = indexes |
675 | 690 | for i in range(0, ballid): |
676 | - self.staticballs[i].SetVisibility(1) | |
691 | + self.static_markers[i].SetVisibility(1) | |
677 | 692 | self.UpdateRender() |
678 | 693 | |
679 | 694 | def RemoveAllMarkers(self, indexes): |
680 | 695 | ballid = indexes |
681 | 696 | for i in range(0, ballid): |
682 | - self.ren.RemoveActor(self.staticballs[i]) | |
683 | - self.staticballs = [] | |
697 | + self.ren.RemoveActor(self.static_markers[i]) | |
698 | + self.static_markers = [] | |
684 | 699 | self.UpdateRender() |
685 | 700 | |
686 | 701 | def RemoveMultipleMarkers(self, index): |
687 | 702 | for i in reversed(index): |
688 | - self.ren.RemoveActor(self.staticballs[i]) | |
689 | - del self.staticballs[i] | |
703 | + self.ren.RemoveActor(self.static_markers[i]) | |
704 | + del self.static_markers[i] | |
690 | 705 | self.ball_id = self.ball_id - 1 |
691 | 706 | self.UpdateRender() |
692 | 707 | |
693 | 708 | def BlinkMarker(self, index): |
694 | 709 | if self.timer: |
695 | 710 | self.timer.Stop() |
696 | - self.staticballs[self.index].SetVisibility(1) | |
711 | + self.static_markers[self.index].SetVisibility(1) | |
697 | 712 | self.index = index |
698 | 713 | self.timer = wx.Timer(self) |
699 | 714 | self.Bind(wx.EVT_TIMER, self.OnBlinkMarker, self.timer) |
... | ... | @@ -701,7 +716,7 @@ class Viewer(wx.Panel): |
701 | 716 | self.timer_count = 0 |
702 | 717 | |
703 | 718 | def OnBlinkMarker(self, evt): |
704 | - self.staticballs[self.index].SetVisibility(int(self.timer_count % 2)) | |
719 | + self.static_markers[self.index].SetVisibility(int(self.timer_count % 2)) | |
705 | 720 | self.Refresh() |
706 | 721 | self.timer_count += 1 |
707 | 722 | |
... | ... | @@ -709,20 +724,20 @@ class Viewer(wx.Panel): |
709 | 724 | if self.timer: |
710 | 725 | self.timer.Stop() |
711 | 726 | if index is None: |
712 | - self.staticballs[self.index].SetVisibility(1) | |
727 | + self.static_markers[self.index].SetVisibility(1) | |
713 | 728 | self.Refresh() |
714 | 729 | self.index = False |
715 | 730 | |
716 | 731 | def SetNewColor(self, index, color): |
717 | - self.staticballs[index].GetProperty().SetColor([round(s/255.0, 3) for s in color]) | |
732 | + self.static_markers[index].GetProperty().SetColor([round(s / 255.0, 3) for s in color]) | |
718 | 733 | self.Refresh() |
719 | 734 | |
720 | 735 | def OnTargetMarkerTransparency(self, status, index): |
721 | 736 | if status: |
722 | - self.staticballs[index].GetProperty().SetOpacity(1) | |
737 | + self.static_markers[index].GetProperty().SetOpacity(1) | |
723 | 738 | # self.staticballs[index].GetProperty().SetOpacity(0.4) |
724 | 739 | else: |
725 | - self.staticballs[index].GetProperty().SetOpacity(1) | |
740 | + self.static_markers[index].GetProperty().SetOpacity(1) | |
726 | 741 | |
727 | 742 | def OnUpdateAngleThreshold(self, angle): |
728 | 743 | self.anglethreshold = angle |
... | ... | @@ -841,8 +856,10 @@ class Viewer(wx.Panel): |
841 | 856 | else: |
842 | 857 | self.DisableCoilTracker() |
843 | 858 | if self.actor_peel: |
844 | - self.object_orientation_torus_actor.SetVisibility(1) | |
845 | - self.obj_projection_arrow_actor.SetVisibility(1) | |
859 | + if self.object_orientation_torus_actor: | |
860 | + self.object_orientation_torus_actor.SetVisibility(1) | |
861 | + if self.obj_projection_arrow_actor: | |
862 | + self.obj_projection_arrow_actor.SetVisibility(1) | |
846 | 863 | |
847 | 864 | def OnUpdateObjectTargetGuide(self, m_img, coord): |
848 | 865 | |
... | ... | @@ -992,16 +1009,10 @@ class Viewer(wx.Panel): |
992 | 1009 | self.RemoveTarget() |
993 | 1010 | self.DisableCoilTracker() |
994 | 1011 | |
995 | - def CreateTargetAim(self): | |
996 | - if self.aim_actor: | |
997 | - self.RemoveTargetAim() | |
998 | - self.aim_actor = None | |
999 | - | |
1000 | - vtk_colors = vtk.vtkNamedColors() | |
1001 | - | |
1012 | + def CreateVTKObjectMatrix(self, direction, orientation): | |
1002 | 1013 | m_img = dco.coordinates_to_transformation_matrix( |
1003 | - position=self.target_coord[:3], | |
1004 | - orientation=self.target_coord[3:], | |
1014 | + position=direction, | |
1015 | + orientation=orientation, | |
1005 | 1016 | axes='sxyz', |
1006 | 1017 | ) |
1007 | 1018 | m_img = np.asmatrix(m_img) |
... | ... | @@ -1012,7 +1023,16 @@ class Viewer(wx.Panel): |
1012 | 1023 | for col in range(0, 4): |
1013 | 1024 | m_img_vtk.SetElement(row, col, m_img[row, col]) |
1014 | 1025 | |
1015 | - self.m_img_vtk = m_img_vtk | |
1026 | + return m_img_vtk | |
1027 | + | |
1028 | + def CreateTargetAim(self): | |
1029 | + if self.aim_actor: | |
1030 | + self.RemoveTargetAim() | |
1031 | + self.aim_actor = None | |
1032 | + | |
1033 | + vtk_colors = vtk.vtkNamedColors() | |
1034 | + | |
1035 | + self.m_img_vtk = self.CreateVTKObjectMatrix(self.target_coord[:3], self.target_coord[3:]) | |
1016 | 1036 | |
1017 | 1037 | filename = os.path.join(inv_paths.OBJ_DIR, "aim.stl") |
1018 | 1038 | |
... | ... | @@ -1023,7 +1043,7 @@ class Viewer(wx.Panel): |
1023 | 1043 | |
1024 | 1044 | # Transform the polydata |
1025 | 1045 | transform = vtk.vtkTransform() |
1026 | - transform.SetMatrix(m_img_vtk) | |
1046 | + transform.SetMatrix(self.m_img_vtk) | |
1027 | 1047 | transformPD = vtk.vtkTransformPolyDataFilter() |
1028 | 1048 | transformPD.SetTransform(transform) |
1029 | 1049 | transformPD.SetInputConnection(reader.GetOutputPort()) |
... | ... | @@ -1071,7 +1091,7 @@ class Viewer(wx.Panel): |
1071 | 1091 | self.dummy_coil_actor.GetProperty().SetSpecularPower(10) |
1072 | 1092 | self.dummy_coil_actor.GetProperty().SetOpacity(.3) |
1073 | 1093 | self.dummy_coil_actor.SetVisibility(1) |
1074 | - self.dummy_coil_actor.SetUserMatrix(m_img_vtk) | |
1094 | + self.dummy_coil_actor.SetUserMatrix(self.m_img_vtk) | |
1075 | 1095 | |
1076 | 1096 | self.ren.AddActor(self.dummy_coil_actor) |
1077 | 1097 | |
... | ... | @@ -1424,8 +1444,9 @@ class Viewer(wx.Panel): |
1424 | 1444 | actor.GetProperty().SetLineWidth(5) |
1425 | 1445 | actor.AddPosition(0, 0, 0) |
1426 | 1446 | actor.SetScale(size) |
1427 | - actor.SetPosition(direction) | |
1428 | - actor.SetOrientation(orientation) | |
1447 | + | |
1448 | + m_img_vtk = self.CreateVTKObjectMatrix(direction, orientation) | |
1449 | + actor.SetUserMatrix(m_img_vtk) | |
1429 | 1450 | |
1430 | 1451 | return actor |
1431 | 1452 | |
... | ... | @@ -1587,10 +1608,6 @@ class Viewer(wx.Panel): |
1587 | 1608 | self.ren.AddActor(self.mark_actor) |
1588 | 1609 | self.Refresh() |
1589 | 1610 | |
1590 | - def UpdateMarkerOffsetPosition(self, coord_offset): | |
1591 | - self.mark_actor.SetPosition(coord_offset) | |
1592 | - self.Refresh() | |
1593 | - | |
1594 | 1611 | def UpdateObjectOrientation(self, m_img, coord): |
1595 | 1612 | # print("Update object orientation") |
1596 | 1613 | |
... | ... | @@ -1667,7 +1684,7 @@ class Viewer(wx.Panel): |
1667 | 1684 | # self.ball_actor.SetVisibility(1) |
1668 | 1685 | self.Refresh() |
1669 | 1686 | |
1670 | - def OnUpdateTracts(self, root=None, affine_vtk=None, coord_offset=None): | |
1687 | + def OnUpdateTracts(self, root=None, affine_vtk=None, coord_offset=None, coord_offset_w=None): | |
1671 | 1688 | mapper = vtk.vtkCompositePolyDataMapper2() |
1672 | 1689 | mapper.SetInputDataObject(root) |
1673 | 1690 | ... | ... |
invesalius/gui/dialogs.py
invesalius/gui/task_navigator.py
... | ... | @@ -165,7 +165,7 @@ class InnerFoldPanel(wx.Panel): |
165 | 165 | # Study this. |
166 | 166 | |
167 | 167 | fold_panel = fpb.FoldPanelBar(self, -1, wx.DefaultPosition, |
168 | - (10, 310), 0, fpb.FPB_SINGLE_FOLD) | |
168 | + (10, 330), 0, fpb.FPB_SINGLE_FOLD) | |
169 | 169 | |
170 | 170 | # Initialize Tracker and PedalConnection objects here so that they are available to several panels. |
171 | 171 | # |
... | ... | @@ -359,7 +359,7 @@ class NeuronavigationPanel(wx.Panel): |
359 | 359 | |
360 | 360 | self.nav_status = False |
361 | 361 | self.tracker_fiducial_being_set = None |
362 | - self.current_coord = 0, 0, 0 | |
362 | + self.current_coord = 0, 0, 0, None, None, None | |
363 | 363 | |
364 | 364 | # Initialize list of buttons and numctrls for wx objects |
365 | 365 | self.btns_set_fiducial = [None, None, None, None, None, None] |
... | ... | @@ -704,7 +704,7 @@ class NeuronavigationPanel(wx.Panel): |
704 | 704 | if self.btns_set_fiducial[n].GetValue(): |
705 | 705 | coord = self.numctrls_fiducial[n][0].GetValue(),\ |
706 | 706 | self.numctrls_fiducial[n][1].GetValue(),\ |
707 | - self.numctrls_fiducial[n][2].GetValue(), 0, 0, 0 | |
707 | + self.numctrls_fiducial[n][2].GetValue(), None, None, None | |
708 | 708 | |
709 | 709 | Publisher.sendMessage('Set image fiducial', fiducial_name=fiducial_name, coord=coord[0:3]) |
710 | 710 | |
... | ... | @@ -1078,17 +1078,15 @@ class ObjectRegistrationPanel(wx.Panel): |
1078 | 1078 | |
1079 | 1079 | try: |
1080 | 1080 | if filename: |
1081 | - #TODO: Improve method to read the file, using "with" similar to OnLoadParameters | |
1082 | - data = np.loadtxt(filename, delimiter='\t') | |
1083 | - self.obj_fiducials = data[:, :3] | |
1084 | - self.obj_orients = data[:, 3:] | |
1081 | + with open(filename, 'r') as text_file: | |
1082 | + data = [s.split('\t') for s in text_file.readlines()] | |
1085 | 1083 | |
1086 | - text_file = open(filename, "r") | |
1087 | - header = text_file.readline().split('\t') | |
1088 | - text_file.close() | |
1084 | + registration_coordinates = np.array(data[1:]).astype(np.float32) | |
1085 | + self.obj_fiducials = registration_coordinates[:, :3] | |
1086 | + self.obj_orients = registration_coordinates[:, 3:] | |
1089 | 1087 | |
1090 | - self.obj_name = header[1] | |
1091 | - self.obj_ref_mode = int(header[-1]) | |
1088 | + self.obj_name = data[0][1] | |
1089 | + self.obj_ref_mode = int(data[0][-1]) | |
1092 | 1090 | |
1093 | 1091 | self.checktrack.Enable(1) |
1094 | 1092 | self.checktrack.SetValue(True) |
... | ... | @@ -1098,7 +1096,7 @@ class ObjectRegistrationPanel(wx.Panel): |
1098 | 1096 | label=_("Object file successfully loaded")) |
1099 | 1097 | Publisher.sendMessage('Update track object state', flag=True, obj_name=self.obj_name) |
1100 | 1098 | Publisher.sendMessage('Change camera checkbox', status=False) |
1101 | - # wx.MessageBox(_("Object file successfully loaded"), _("Load")) | |
1099 | + wx.MessageBox(_("Object file successfully loaded"), _("InVesalius 3")) | |
1102 | 1100 | except: |
1103 | 1101 | wx.MessageBox(_("Object registration file incompatible."), _("InVesalius 3")) |
1104 | 1102 | Publisher.sendMessage('Update status text in GUI', label="") |
... | ... | @@ -1144,9 +1142,9 @@ class MarkersPanel(wx.Panel): |
1144 | 1142 | x : float = 0 |
1145 | 1143 | y : float = 0 |
1146 | 1144 | z : float = 0 |
1147 | - alpha : float = 0 | |
1148 | - beta : float = 0 | |
1149 | - gamma : float = 0 | |
1145 | + alpha : float = dataclasses.field(default = None) | |
1146 | + beta : float = dataclasses.field(default = None) | |
1147 | + gamma : float = dataclasses.field(default = None) | |
1150 | 1148 | r : float = 0 |
1151 | 1149 | g : float = 1 |
1152 | 1150 | b : float = 0 |
... | ... | @@ -1161,7 +1159,7 @@ class MarkersPanel(wx.Panel): |
1161 | 1159 | # x, y, z, alpha, beta, gamma can be jointly accessed as coord |
1162 | 1160 | @property |
1163 | 1161 | def coord(self): |
1164 | - return list((self.x, self.y, self.z, self.alpha, self.beta, self.gamma),) | |
1162 | + return list((self.x, self.y, self.z, self.alpha, self.beta, self.gamma)) | |
1165 | 1163 | |
1166 | 1164 | @coord.setter |
1167 | 1165 | def coord(self, new_coord): |
... | ... | @@ -1201,11 +1199,18 @@ class MarkersPanel(wx.Panel): |
1201 | 1199 | else: |
1202 | 1200 | res += ('%s\t' % str(getattr(self, field.name))) |
1203 | 1201 | |
1204 | - # Add world coordinates (in addition to the internal ones). | |
1205 | - position_world, orientation_world = imagedata_utils.convert_invesalius_to_world( | |
1206 | - position=[self.x, self.y, self.z], | |
1207 | - orientation=[self.alpha, self.beta, self.gamma], | |
1208 | - ) | |
1202 | + if self.alpha is not None and self.beta is not None and self.gamma is not None: | |
1203 | + # Add world coordinates (in addition to the internal ones). | |
1204 | + position_world, orientation_world = imagedata_utils.convert_invesalius_to_world( | |
1205 | + position=[self.x, self.y, self.z], | |
1206 | + orientation=[self.alpha, self.beta, self.gamma], | |
1207 | + ) | |
1208 | + | |
1209 | + else: | |
1210 | + position_world, orientation_world = imagedata_utils.convert_invesalius_to_world( | |
1211 | + position=[self.x, self.y, self.z], | |
1212 | + orientation=[0,0,0], | |
1213 | + ) | |
1209 | 1214 | |
1210 | 1215 | res += '\t'.join(map(lambda x: 'N/A' if x is None else str(x), (*position_world, *orientation_world))) |
1211 | 1216 | return res |
... | ... | @@ -1215,8 +1220,10 @@ class MarkersPanel(wx.Panel): |
1215 | 1220 | properly formatted, might throw an exception and leave the object |
1216 | 1221 | in an inconsistent state.""" |
1217 | 1222 | for field, str_val in zip(dataclasses.fields(self.__class__), inp_str.split('\t')): |
1218 | - if field.type is float: | |
1223 | + if field.type is float and str_val != 'None': | |
1219 | 1224 | setattr(self, field.name, float(str_val)) |
1225 | + if field.type is float and str_val == 'None': | |
1226 | + setattr(self, field.name, None) | |
1220 | 1227 | if field.type is int: |
1221 | 1228 | setattr(self, field.name, int(str_val)) |
1222 | 1229 | if field.type is str: |
... | ... | @@ -1253,8 +1260,7 @@ class MarkersPanel(wx.Panel): |
1253 | 1260 | |
1254 | 1261 | self.session = ses.Session() |
1255 | 1262 | |
1256 | - self.current_coord = 0, 0, 0, 0, 0, 0 | |
1257 | - self.current_angle = 0, 0, 0 | |
1263 | + self.current_coord = 0, 0, 0, None, None, None | |
1258 | 1264 | self.current_seed = 0, 0, 0 |
1259 | 1265 | self.current_robot_target_matrix = [None] * 9 |
1260 | 1266 | self.markers = [] |
... | ... | @@ -1264,6 +1270,7 @@ class MarkersPanel(wx.Panel): |
1264 | 1270 | |
1265 | 1271 | self.marker_colour = const.MARKER_COLOUR |
1266 | 1272 | self.marker_size = const.MARKER_SIZE |
1273 | + self.arrow_marker_size = const.ARROW_MARKER_SIZE | |
1267 | 1274 | self.current_session = 1 |
1268 | 1275 | |
1269 | 1276 | # Change marker size |
... | ... | @@ -1431,19 +1438,17 @@ class MarkersPanel(wx.Panel): |
1431 | 1438 | return list(itertools.chain(*(const.BTNS_IMG_MARKERS[i].values() for i in const.BTNS_IMG_MARKERS))) |
1432 | 1439 | |
1433 | 1440 | def UpdateCurrentCoord(self, position): |
1434 | - self.current_coord = position | |
1435 | - #self.current_angle = pubsub_evt.data[1][3:] | |
1441 | + self.current_coord = list(position) | |
1436 | 1442 | |
1437 | 1443 | def UpdateNavigationStatus(self, nav_status, vis_status): |
1438 | 1444 | if not nav_status: |
1439 | - sleep(0.5) | |
1440 | - #self.current_coord[3:] = 0, 0, 0 | |
1445 | + self.current_coord[3:] = None, None, None | |
1441 | 1446 | self.nav_status = False |
1442 | 1447 | else: |
1443 | 1448 | self.nav_status = True |
1444 | 1449 | |
1445 | - def UpdateSeedCoordinates(self, root=None, affine_vtk=None, coord_offset=(0, 0, 0)): | |
1446 | - self.current_seed = coord_offset | |
1450 | + def UpdateSeedCoordinates(self, root=None, affine_vtk=None, coord_offset=(0, 0, 0), coord_offset_w=(0, 0, 0)): | |
1451 | + self.current_seed = coord_offset_w | |
1447 | 1452 | |
1448 | 1453 | def UpdateRobotCoordinates(self, coordinates_raw, markers_flag): |
1449 | 1454 | self.raw_target_robot = coordinates_raw[1], coordinates_raw[2] |
... | ... | @@ -1615,8 +1620,8 @@ class MarkersPanel(wx.Panel): |
1615 | 1620 | if marker.is_target: |
1616 | 1621 | self.__set_marker_as_target(len(self.markers) - 1) |
1617 | 1622 | |
1618 | - except: | |
1619 | - wx.MessageBox(_("Invalid markers file."), _("InVesalius 3")) | |
1623 | + except Exception as e: | |
1624 | + wx.MessageBox(_("Invalid markers file."), _("InVesalius 3")) | |
1620 | 1625 | |
1621 | 1626 | def OnMarkersVisibility(self, evt, ctrl): |
1622 | 1627 | if ctrl.GetValue(): |
... | ... | @@ -1681,10 +1686,18 @@ class MarkersPanel(wx.Panel): |
1681 | 1686 | new_robot_marker.robot_target_matrix = self.current_robot_target_matrix |
1682 | 1687 | |
1683 | 1688 | # Note that ball_id is zero-based, so we assign it len(self.markers) before the new marker is added |
1684 | - Publisher.sendMessage('Add marker', ball_id=len(self.markers), | |
1685 | - size=new_marker.size, | |
1686 | - colour=new_marker.colour, | |
1687 | - coord=new_marker.coord[:3]) | |
1689 | + if all([elem is not None for elem in new_marker.coord[3:]]): | |
1690 | + Publisher.sendMessage('Add arrow marker', arrow_id=len(self.markers), | |
1691 | + size=self.arrow_marker_size, | |
1692 | + color=new_marker.colour, | |
1693 | + coord=new_marker.coord) | |
1694 | + else: | |
1695 | + Publisher.sendMessage('Add marker', ball_id=len(self.markers), | |
1696 | + size=new_marker.size, | |
1697 | + colour=new_marker.colour, | |
1698 | + coord=new_marker.coord[:3]) | |
1699 | + | |
1700 | + | |
1688 | 1701 | self.markers.append(new_marker) |
1689 | 1702 | self.robot_markers.append(new_robot_marker) |
1690 | 1703 | |
... | ... | @@ -1720,7 +1733,7 @@ class TractographyPanel(wx.Panel): |
1720 | 1733 | default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR) |
1721 | 1734 | self.SetBackgroundColour(default_colour) |
1722 | 1735 | |
1723 | - self.affine = None | |
1736 | + self.affine = np.identity(4) | |
1724 | 1737 | self.affine_vtk = None |
1725 | 1738 | self.trekker = None |
1726 | 1739 | self.n_tracts = const.N_TRACTS |
... | ... | @@ -1977,7 +1990,6 @@ class TractographyPanel(wx.Panel): |
1977 | 1990 | self.nav_status = nav_status |
1978 | 1991 | |
1979 | 1992 | def OnLinkBrain(self, event=None): |
1980 | - Publisher.sendMessage('Update status text in GUI', label=_("Busy")) | |
1981 | 1993 | Publisher.sendMessage('Begin busy cursor') |
1982 | 1994 | mask_path = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, _("Import brain mask")) |
1983 | 1995 | img_path = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, _("Import T1 anatomical image")) |
... | ... | @@ -1991,31 +2003,37 @@ class TractographyPanel(wx.Panel): |
1991 | 2003 | slic = sl.Slice() |
1992 | 2004 | prj_data = prj.Project() |
1993 | 2005 | matrix_shape = tuple(prj_data.matrix_shape) |
2006 | + spacing = tuple(prj_data.spacing) | |
2007 | + img_shift = spacing[1] * (matrix_shape[1] - 1) | |
1994 | 2008 | self.affine = slic.affine.copy() |
1995 | - self.affine[1, -1] -= matrix_shape[1] | |
2009 | + self.affine[1, -1] -= img_shift | |
1996 | 2010 | self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine) |
1997 | 2011 | |
1998 | - try: | |
1999 | - self.brain_peel = brain.Brain(img_path, mask_path, self.n_peels, self.affine_vtk) | |
2000 | - self.brain_actor = self.brain_peel.get_actor(self.peel_depth, self.affine_vtk) | |
2001 | - self.brain_actor.GetProperty().SetOpacity(self.brain_opacity) | |
2002 | - Publisher.sendMessage('Update peel', flag=True, actor=self.brain_actor) | |
2003 | - Publisher.sendMessage('Get peel centers and normals', centers=self.brain_peel.peel_centers, | |
2004 | - normals=self.brain_peel.peel_normals) | |
2005 | - Publisher.sendMessage('Get init locator', locator=self.brain_peel.locator) | |
2006 | - self.checkpeeling.Enable(1) | |
2007 | - self.checkpeeling.SetValue(True) | |
2008 | - self.spin_opacity.Enable(1) | |
2009 | - Publisher.sendMessage('Update status text in GUI', label=_("Brain model loaded")) | |
2010 | - self.peel_loaded = True | |
2011 | - Publisher.sendMessage('Update peel visualization', data= self.peel_loaded) | |
2012 | - except: | |
2013 | - wx.MessageBox(_("Unable to load brain mask."), _("InVesalius 3")) | |
2012 | + if mask_path and img_path: | |
2013 | + Publisher.sendMessage('Update status text in GUI', label=_("Busy")) | |
2014 | + try: | |
2015 | + self.brain_peel = brain.Brain(img_path, mask_path, self.n_peels, self.affine_vtk) | |
2016 | + self.brain_actor = self.brain_peel.get_actor(self.peel_depth, self.affine_vtk) | |
2017 | + self.brain_actor.GetProperty().SetOpacity(self.brain_opacity) | |
2018 | + | |
2019 | + self.checkpeeling.Enable(1) | |
2020 | + self.checkpeeling.SetValue(True) | |
2021 | + self.spin_opacity.Enable(1) | |
2022 | + self.peel_loaded = True | |
2023 | + | |
2024 | + Publisher.sendMessage('Update peel', flag=True, actor=self.brain_actor) | |
2025 | + Publisher.sendMessage('Get peel centers and normals', centers=self.brain_peel.peel_centers, | |
2026 | + normals=self.brain_peel.peel_normals) | |
2027 | + Publisher.sendMessage('Get init locator', locator=self.brain_peel.locator) | |
2028 | + Publisher.sendMessage('Update status text in GUI', label=_("Brain model loaded")) | |
2029 | + Publisher.sendMessage('Update peel visualization', data= self.peel_loaded) | |
2030 | + except: | |
2031 | + Publisher.sendMessage('Update status text in GUI', label=_("Brain mask initialization failed.")) | |
2032 | + wx.MessageBox(_("Unable to load brain mask."), _("InVesalius 3")) | |
2014 | 2033 | |
2015 | 2034 | Publisher.sendMessage('End busy cursor') |
2016 | 2035 | |
2017 | 2036 | def OnLinkFOD(self, event=None): |
2018 | - Publisher.sendMessage('Update status text in GUI', label=_("Busy")) | |
2019 | 2037 | Publisher.sendMessage('Begin busy cursor') |
2020 | 2038 | filename = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, msg=_("Import Trekker FOD")) |
2021 | 2039 | # Juuso |
... | ... | @@ -2026,69 +2044,83 @@ class TractographyPanel(wx.Panel): |
2026 | 2044 | # FOD_path = 'Baran_FOD.nii' |
2027 | 2045 | # filename = os.path.join(data_dir, FOD_path) |
2028 | 2046 | |
2029 | - # if not self.affine_vtk: | |
2030 | - # slic = sl.Slice() | |
2031 | - # self.affine = slic.affine | |
2032 | - # self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine) | |
2033 | - | |
2034 | 2047 | if not self.affine_vtk: |
2035 | 2048 | slic = sl.Slice() |
2036 | 2049 | prj_data = prj.Project() |
2037 | 2050 | matrix_shape = tuple(prj_data.matrix_shape) |
2051 | + spacing = tuple(prj_data.spacing) | |
2052 | + img_shift = spacing[1] * (matrix_shape[1] - 1) | |
2038 | 2053 | self.affine = slic.affine.copy() |
2039 | - self.affine[1, -1] -= matrix_shape[1] | |
2054 | + self.affine[1, -1] -= img_shift | |
2040 | 2055 | self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine) |
2041 | 2056 | |
2042 | - # try: | |
2043 | - | |
2044 | - self.trekker = Trekker.initialize(filename.encode('utf-8')) | |
2045 | - self.trekker, n_threads = dti.set_trekker_parameters(self.trekker, self.trekker_cfg) | |
2046 | - | |
2047 | - self.checktracts.Enable(1) | |
2048 | - self.checktracts.SetValue(True) | |
2049 | - self.view_tracts = True | |
2050 | - Publisher.sendMessage('Update Trekker object', data=self.trekker) | |
2051 | - Publisher.sendMessage('Update number of threads', data=n_threads) | |
2052 | - Publisher.sendMessage('Update tracts visualization', data=1) | |
2053 | - Publisher.sendMessage('Update status text in GUI', label=_("Trekker initialized")) | |
2054 | - # except: | |
2055 | - # wx.MessageBox(_("Unable to initialize Trekker, check FOD and config files."), _("InVesalius 3")) | |
2057 | + if filename: | |
2058 | + Publisher.sendMessage('Update status text in GUI', label=_("Busy")) | |
2059 | + try: | |
2060 | + self.trekker = Trekker.initialize(filename.encode('utf-8')) | |
2061 | + self.trekker, n_threads = dti.set_trekker_parameters(self.trekker, self.trekker_cfg) | |
2062 | + | |
2063 | + self.checktracts.Enable(1) | |
2064 | + self.checktracts.SetValue(True) | |
2065 | + self.view_tracts = True | |
2066 | + | |
2067 | + Publisher.sendMessage('Update Trekker object', data=self.trekker) | |
2068 | + Publisher.sendMessage('Update number of threads', data=n_threads) | |
2069 | + Publisher.sendMessage('Update tracts visualization', data=1) | |
2070 | + Publisher.sendMessage('Update status text in GUI', label=_("Trekker initialized")) | |
2071 | + # except: | |
2072 | + # wx.MessageBox(_("Unable to initialize Trekker, check FOD and config files."), _("InVesalius 3")) | |
2073 | + except: | |
2074 | + Publisher.sendMessage('Update status text in GUI', label=_("Trekker initialization failed.")) | |
2075 | + wx.MessageBox(_("Unable to load FOD."), _("InVesalius 3")) | |
2056 | 2076 | |
2057 | 2077 | Publisher.sendMessage('End busy cursor') |
2058 | 2078 | |
2059 | 2079 | def OnLoadACT(self, event=None): |
2060 | - Publisher.sendMessage('Update status text in GUI', label=_("Busy")) | |
2061 | - Publisher.sendMessage('Begin busy cursor') | |
2062 | - filename = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, msg=_("Import anatomical labels")) | |
2063 | - # Baran | |
2064 | - # data_dir = os.environ.get('OneDrive') + r'\data\dti_navigation\baran\anat_reg_improve_20200609' | |
2065 | - # act_path = 'Baran_trekkerACTlabels_inFODspace.nii' | |
2066 | - # filename = os.path.join(data_dir, act_path) | |
2080 | + if self.trekker: | |
2081 | + Publisher.sendMessage('Begin busy cursor') | |
2082 | + filename = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, msg=_("Import anatomical labels")) | |
2083 | + # Baran | |
2084 | + # data_dir = os.environ.get('OneDrive') + r'\data\dti_navigation\baran\anat_reg_improve_20200609' | |
2085 | + # act_path = 'Baran_trekkerACTlabels_inFODspace.nii' | |
2086 | + # filename = os.path.join(data_dir, act_path) | |
2087 | + | |
2088 | + if not self.affine_vtk: | |
2089 | + slic = sl.Slice() | |
2090 | + prj_data = prj.Project() | |
2091 | + matrix_shape = tuple(prj_data.matrix_shape) | |
2092 | + spacing = tuple(prj_data.spacing) | |
2093 | + img_shift = spacing[1] * (matrix_shape[1] - 1) | |
2094 | + self.affine = slic.affine.copy() | |
2095 | + self.affine[1, -1] -= img_shift | |
2096 | + self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine) | |
2067 | 2097 | |
2068 | - act_data = nb.squeeze_image(nb.load(filename)) | |
2069 | - act_data = nb.as_closest_canonical(act_data) | |
2070 | - act_data.update_header() | |
2071 | - act_data_arr = act_data.get_fdata() | |
2072 | - | |
2073 | - if not self.affine_vtk: | |
2074 | - slic = sl.Slice() | |
2075 | - prj_data = prj.Project() | |
2076 | - matrix_shape = tuple(prj_data.matrix_shape) | |
2077 | - self.affine = slic.affine.copy() | |
2078 | - self.affine[1, -1] -= matrix_shape[1] | |
2079 | - self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine) | |
2080 | - | |
2081 | - self.checkACT.Enable(1) | |
2082 | - self.checkACT.SetValue(True) | |
2083 | - | |
2084 | - Publisher.sendMessage('Update ACT data', data=act_data_arr) | |
2085 | - Publisher.sendMessage('Enable ACT', data=True) | |
2086 | - # Publisher.sendMessage('Create grid', data=act_data_arr, affine=self.affine) | |
2087 | - # Publisher.sendMessage('Update number of threads', data=n_threads) | |
2088 | - # Publisher.sendMessage('Update tracts visualization', data=1) | |
2089 | - Publisher.sendMessage('Update status text in GUI', label=_("Trekker ACT loaded")) | |
2090 | - | |
2091 | - Publisher.sendMessage('End busy cursor') | |
2098 | + try: | |
2099 | + Publisher.sendMessage('Update status text in GUI', label=_("Busy")) | |
2100 | + if filename: | |
2101 | + act_data = nb.squeeze_image(nb.load(filename)) | |
2102 | + act_data = nb.as_closest_canonical(act_data) | |
2103 | + act_data.update_header() | |
2104 | + act_data_arr = act_data.get_fdata() | |
2105 | + | |
2106 | + self.checkACT.Enable(1) | |
2107 | + self.checkACT.SetValue(True) | |
2108 | + | |
2109 | + # ACT rules should be as follows: | |
2110 | + self.trekker.pathway_stop_at_entry(filename.encode('utf-8'), -1) # outside | |
2111 | + self.trekker.pathway_discard_if_ends_inside(filename.encode('utf-8'), 1) # wm | |
2112 | + self.trekker.pathway_discard_if_enters(filename.encode('utf-8'), 0) # csf | |
2113 | + | |
2114 | + Publisher.sendMessage('Update ACT data', data=act_data_arr) | |
2115 | + Publisher.sendMessage('Enable ACT', data=True) | |
2116 | + Publisher.sendMessage('Update status text in GUI', label=_("Trekker ACT loaded")) | |
2117 | + except: | |
2118 | + Publisher.sendMessage('Update status text in GUI', label=_("ACT initialization failed.")) | |
2119 | + wx.MessageBox(_("Unable to load ACT."), _("InVesalius 3")) | |
2120 | + | |
2121 | + Publisher.sendMessage('End busy cursor') | |
2122 | + else: | |
2123 | + wx.MessageBox(_("Load FOD image before the ACT."), _("InVesalius 3")) | |
2092 | 2124 | |
2093 | 2125 | def OnLoadParameters(self, event=None): |
2094 | 2126 | import json |
... | ... | @@ -2131,10 +2163,13 @@ class TractographyPanel(wx.Panel): |
2131 | 2163 | # print("Running during navigation") |
2132 | 2164 | coord_flip = list(position[:3]) |
2133 | 2165 | coord_flip[1] = -coord_flip[1] |
2134 | - dti.compute_tracts(self.trekker, coord_flip, self.affine, self.affine_vtk, | |
2135 | - self.n_tracts) | |
2166 | + dti.compute_and_visualize_tracts(self.trekker, coord_flip, self.affine, self.affine_vtk, | |
2167 | + self.n_tracts) | |
2136 | 2168 | |
2137 | 2169 | def OnCloseProject(self): |
2170 | + self.trekker = None | |
2171 | + self.trekker_cfg = const.TREKKER_CONFIG | |
2172 | + | |
2138 | 2173 | self.checktracts.SetValue(False) |
2139 | 2174 | self.checktracts.Enable(0) |
2140 | 2175 | self.checkpeeling.SetValue(False) | ... | ... |
invesalius/navigation/navigation.py
... | ... | @@ -27,7 +27,6 @@ import numpy as np |
27 | 27 | import invesalius.constants as const |
28 | 28 | import invesalius.project as prj |
29 | 29 | import invesalius.data.bases as db |
30 | -import invesalius.data.coordinates as dco | |
31 | 30 | import invesalius.data.coregistration as dcr |
32 | 31 | import invesalius.data.serial_port_connection as spc |
33 | 32 | import invesalius.data.slice_ as sl |
... | ... | @@ -98,12 +97,11 @@ class UpdateNavigationScene(threading.Thread): |
98 | 97 | |
99 | 98 | # use of CallAfter is mandatory otherwise crashes the wx interface |
100 | 99 | if self.view_tracts: |
101 | - bundle, affine_vtk, coord_offset = self.tracts_queue.get_nowait() | |
100 | + bundle, affine_vtk, coord_offset, coord_offset_w = self.tracts_queue.get_nowait() | |
102 | 101 | #TODO: Check if possible to combine the Remove tracts with Update tracts in a single command |
103 | 102 | wx.CallAfter(Publisher.sendMessage, 'Remove tracts') |
104 | - wx.CallAfter(Publisher.sendMessage, 'Update tracts', root=bundle, | |
105 | - affine_vtk=affine_vtk, coord_offset=coord_offset) | |
106 | - # wx.CallAfter(Publisher.sendMessage, 'Update marker offset', coord_offset=coord_offset) | |
103 | + wx.CallAfter(Publisher.sendMessage, 'Update tracts', root=bundle, affine_vtk=affine_vtk, | |
104 | + coord_offset=coord_offset, coord_offset_w=coord_offset_w) | |
107 | 105 | self.tracts_queue.task_done() |
108 | 106 | |
109 | 107 | if self.serial_port_enabled: |
... | ... | @@ -118,10 +116,11 @@ class UpdateNavigationScene(threading.Thread): |
118 | 116 | wx.CallAfter(Publisher.sendMessage, 'Set cross focal point', position=coord) |
119 | 117 | wx.CallAfter(Publisher.sendMessage, 'Update raw coordinates', coordinates_raw=coordinates_raw, markers_flag=markers_flag) |
120 | 118 | wx.CallAfter(Publisher.sendMessage, 'Update slice viewer') |
119 | + wx.CallAfter(Publisher.sendMessage, 'Sensor ID', markers_flag=markers_flag) | |
121 | 120 | |
122 | 121 | if view_obj: |
123 | 122 | wx.CallAfter(Publisher.sendMessage, 'Update object matrix', m_img=m_img, coord=coord) |
124 | - wx.CallAfter(Publisher.sendMessage, 'Update object arrow matrix',m_img=m_img, coord=coord, flag= self.peel_loaded) | |
123 | + wx.CallAfter(Publisher.sendMessage, 'Update object arrow matrix', m_img=m_img, coord=coord, flag= self.peel_loaded) | |
125 | 124 | self.coord_queue.task_done() |
126 | 125 | # print('UpdateScene: done {}'.format(count)) |
127 | 126 | # count += 1 |
... | ... | @@ -191,7 +190,7 @@ class Navigation(): |
191 | 190 | |
192 | 191 | def UpdateSleep(self, sleep): |
193 | 192 | self.sleep_nav = sleep |
194 | - self.serial_port_connection.sleep_nav = sleep | |
193 | + # self.serial_port_connection.sleep_nav = sleep | |
195 | 194 | |
196 | 195 | def UpdateSerialPort(self, serial_port_in_use, com_port=None, baud_rate=None): |
197 | 196 | self.serial_port_in_use = serial_port_in_use |
... | ... | @@ -307,15 +306,22 @@ class Navigation(): |
307 | 306 | |
308 | 307 | if self.view_tracts: |
309 | 308 | # initialize Trekker parameters |
309 | + # TODO: This affine and affine_vtk are created 4 times. To improve, create a new affine object inside | |
310 | + # Slice() that contains the transformation with the img_shift. Rename it to avoid confusion to the | |
311 | + # affine, for instance it can be: affine_world_to_invesalius_vtk | |
310 | 312 | slic = sl.Slice() |
311 | 313 | prj_data = prj.Project() |
312 | 314 | matrix_shape = tuple(prj_data.matrix_shape) |
315 | + spacing = tuple(prj_data.spacing) | |
316 | + img_shift = spacing[1] * (matrix_shape[1] - 1) | |
313 | 317 | affine = slic.affine.copy() |
314 | - affine[1, -1] -= matrix_shape[1] | |
318 | + affine[1, -1] -= img_shift | |
315 | 319 | affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(affine) |
320 | + | |
316 | 321 | Publisher.sendMessage("Update marker offset state", create=True) |
322 | + | |
317 | 323 | self.trk_inp = self.trekker, affine, self.seed_offset, self.n_tracts, self.seed_radius,\ |
318 | - self.n_threads, self.act_data, affine_vtk, matrix_shape[1] | |
324 | + self.n_threads, self.act_data, affine_vtk, img_shift | |
319 | 325 | # print("Appending the tract computation thread!") |
320 | 326 | queues = [self.coord_tracts_queue, self.tracts_queue] |
321 | 327 | if self.enable_act: | ... | ... |