Commit 46e9180122ff30cd6e3d4c95c4a597e2fdd1ffab
Committed by
GitHub
1 parent
3b8f8e1f
Exists in
master
ENH: Update tractography parameters and fix config file load (#400)
* 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
with
401 additions
and
450 deletions
Show diff stats
environment.yml
@@ -2,7 +2,7 @@ channels: | @@ -2,7 +2,7 @@ channels: | ||
2 | - conda-forge | 2 | - conda-forge |
3 | - bioconda | 3 | - bioconda |
4 | dependencies: | 4 | dependencies: |
5 | - - python=3.7 | 5 | + - python=3.8 |
6 | - cython==0.29.24 | 6 | - cython==0.29.24 |
7 | - pillow==8.3.2 | 7 | - pillow==8.3.2 |
8 | - pypubsub==4.0.3 | 8 | - pypubsub==4.0.3 |
@@ -17,13 +17,21 @@ dependencies: | @@ -17,13 +17,21 @@ dependencies: | ||
17 | - scipy==1.7.1 | 17 | - scipy==1.7.1 |
18 | - vtk==9.0.3 | 18 | - vtk==9.0.3 |
19 | - wxpython==4.1.1 | 19 | - wxpython==4.1.1 |
20 | + - mido==1.2.10 | ||
21 | + - nest-asyncio==1.5.1 | ||
20 | - pip | 22 | - pip |
21 | - pip: | 23 | - pip: |
22 | - - python-gdcm==3.0.9.1 | 24 | + - aioconsole==0.3.2 |
25 | + - opencv-python==4.5.3.56 | ||
23 | - plaidml-keras==0.7.0 | 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 | - pyclaron | 31 | - pyclaron |
27 | - polhemusFT | 32 | - polhemusFT |
28 | - polhemus | 33 | - polhemus |
29 | - pypolaris | 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
@@ -823,23 +823,35 @@ COIL_ANGLE_ARROW_PROJECTION_THRESHOLD = 5 | @@ -823,23 +823,35 @@ COIL_ANGLE_ARROW_PROJECTION_THRESHOLD = 5 | ||
823 | CAM_MODE = True | 823 | CAM_MODE = True |
824 | 824 | ||
825 | # Tractography visualization | 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 | SEED_RADIUS = 1.5 | 830 | SEED_RADIUS = 1.5 |
831 | 831 | ||
832 | # Increased the default sleep parameter from 0.1 to 0.15 to decrease CPU load during navigation. | 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 | SLEEP_COORDINATES = 0.05 | 834 | SLEEP_COORDINATES = 0.05 |
835 | SLEEP_ROBOT = 0.01 | 835 | SLEEP_ROBOT = 0.01 |
836 | 836 | ||
837 | -BRAIN_OPACITY = 0.5 | 837 | +BRAIN_OPACITY = 0.6 |
838 | N_CPU = psutil.cpu_count() | 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 | MARKER_FILE_MAGICK_STRING = "##INVESALIUS3_MARKER_FILE_" | 856 | MARKER_FILE_MAGICK_STRING = "##INVESALIUS3_MARKER_FILE_" |
845 | CURRENT_MARKER_FILE_VERSION = 0 | 857 | CURRENT_MARKER_FILE_VERSION = 0 |
invesalius/control.py
@@ -69,7 +69,7 @@ class Controller(): | @@ -69,7 +69,7 @@ class Controller(): | ||
69 | #DICOM = 1 | 69 | #DICOM = 1 |
70 | #TIFF uCT = 2 | 70 | #TIFF uCT = 2 |
71 | self.img_type = 0 | 71 | self.img_type = 0 |
72 | - self.affine = None | 72 | + self.affine = np.identity(4) |
73 | 73 | ||
74 | #Init session | 74 | #Init session |
75 | session = ses.Session() | 75 | session = ses.Session() |
@@ -340,7 +340,7 @@ class Controller(): | @@ -340,7 +340,7 @@ class Controller(): | ||
340 | if proj.affine: | 340 | if proj.affine: |
341 | self.Slice.affine = np.asarray(proj.affine).reshape(4, 4) | 341 | self.Slice.affine = np.asarray(proj.affine).reshape(4, 4) |
342 | else: | 342 | else: |
343 | - self.Slice.affine = None | 343 | + self.Slice.affine = np.identity(4) |
344 | 344 | ||
345 | Publisher.sendMessage('Update threshold limits list', | 345 | Publisher.sendMessage('Update threshold limits list', |
346 | threshold_range=proj.threshold_range) | 346 | threshold_range=proj.threshold_range) |
@@ -623,6 +623,8 @@ class Controller(): | @@ -623,6 +623,8 @@ class Controller(): | ||
623 | Publisher.sendMessage(('Set scroll position', 'SAGITAL'),index=proj.matrix_shape[1]/2) | 623 | Publisher.sendMessage(('Set scroll position', 'SAGITAL'),index=proj.matrix_shape[1]/2) |
624 | Publisher.sendMessage(('Set scroll position', 'CORONAL'),index=proj.matrix_shape[2]/2) | 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 | if self.Slice.affine is not None: | 628 | if self.Slice.affine is not None: |
627 | Publisher.sendMessage('Enable Go-to-Coord', status=True) | 629 | Publisher.sendMessage('Enable Go-to-Coord', status=True) |
628 | else: | 630 | else: |
@@ -731,6 +733,8 @@ class Controller(): | @@ -731,6 +733,8 @@ class Controller(): | ||
731 | proj.level = self.Slice.window_level | 733 | proj.level = self.Slice.window_level |
732 | proj.threshold_range = int(matrix.min()), int(matrix.max()) | 734 | proj.threshold_range = int(matrix.min()), int(matrix.max()) |
733 | proj.spacing = self.Slice.spacing | 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 | if self.Slice.affine is not None: | 738 | if self.Slice.affine is not None: |
735 | proj.affine = self.Slice.affine.tolist() | 739 | proj.affine = self.Slice.affine.tolist() |
736 | 740 |
invesalius/data/coordinates.py
@@ -371,7 +371,7 @@ def DebugCoordRandom(trk_init, trck_id, ref_mode): | @@ -371,7 +371,7 @@ def DebugCoordRandom(trk_init, trck_id, ref_mode): | ||
371 | # | 371 | # |
372 | # else: | 372 | # else: |
373 | 373 | ||
374 | - dx = [-70, 70] | 374 | + dx = [-30, 30] |
375 | dt = [-180, 180] | 375 | dt = [-180, 180] |
376 | 376 | ||
377 | coord1 = np.array([uniform(*dx), uniform(*dx), uniform(*dx), | 377 | coord1 = np.array([uniform(*dx), uniform(*dx), uniform(*dx), |
invesalius/data/coregistration.py
@@ -427,7 +427,7 @@ class CoordinateCorregistrateNoObject(threading.Thread): | @@ -427,7 +427,7 @@ class CoordinateCorregistrateNoObject(threading.Thread): | ||
427 | # # seed_aux = pos_world.reshape([1, 4])[0, :3] | 427 | # # seed_aux = pos_world.reshape([1, 4])[0, :3] |
428 | # # seed = seed_aux[np.newaxis, :] | 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 | # # wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=m_img, position=coord) | 432 | # # wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=m_img, position=coord) |
433 | # wx.CallAfter(Publisher.sendMessage, 'Update cross position', arg=m_img, position=coord) | 433 | # wx.CallAfter(Publisher.sendMessage, 'Update cross position', arg=m_img, position=coord) |
@@ -654,7 +654,7 @@ class CoordinateCorregistrateNoObject(threading.Thread): | @@ -654,7 +654,7 @@ class CoordinateCorregistrateNoObject(threading.Thread): | ||
654 | # # seed_aux = pos_world.reshape([1, 4])[0, :3] | 654 | # # seed_aux = pos_world.reshape([1, 4])[0, :3] |
655 | # # seed = seed_aux[np.newaxis, :] | 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 | # # wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=m_img, position=coord) | 659 | # # wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=m_img, position=coord) |
660 | # wx.CallAfter(Publisher.sendMessage, 'Update cross position', arg=m_img, position=coord) | 660 | # wx.CallAfter(Publisher.sendMessage, 'Update cross position', arg=m_img, position=coord) |
invesalius/data/styles.py
@@ -544,7 +544,7 @@ class TractsInteractorStyle(CrossInteractorStyle): | @@ -544,7 +544,7 @@ class TractsInteractorStyle(CrossInteractorStyle): | ||
544 | 544 | ||
545 | def OnTractsMouseClick(self, obj, evt): | 545 | def OnTractsMouseClick(self, obj, evt): |
546 | # print("Single mouse click") | 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 | self.ChangeTracts(True) | 548 | self.ChangeTracts(True) |
549 | 549 | ||
550 | def OnTractsReleaseLeftButton(self, obj, evt): | 550 | def OnTractsReleaseLeftButton(self, obj, evt): |
@@ -554,7 +554,7 @@ class TractsInteractorStyle(CrossInteractorStyle): | @@ -554,7 +554,7 @@ class TractsInteractorStyle(CrossInteractorStyle): | ||
554 | 554 | ||
555 | def ChangeTracts(self, pressed): | 555 | def ChangeTracts(self, pressed): |
556 | # print("Trying to compute tracts") | 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 | # mouse_x, mouse_y = iren.GetEventPosition() | 558 | # mouse_x, mouse_y = iren.GetEventPosition() |
559 | # wx, wy, wz = self.viewer.get_coordinate_cursor(mouse_x, mouse_y, self.picker) | 559 | # wx, wy, wz = self.viewer.get_coordinate_cursor(mouse_x, mouse_y, self.picker) |
560 | # px, py = self.viewer.get_slice_pixel_coord_by_world_pos(wx, wy, wz) | 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,9 +365,16 @@ class SurfaceManager(): | ||
365 | if self.convert2inv: | 365 | if self.convert2inv: |
366 | # convert between invesalius and world space with shift in the Y coordinate | 366 | # convert between invesalius and world space with shift in the Y coordinate |
367 | affine = sl.Slice().affine | 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 | if affine is not None: | 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 | affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(affine) | 376 | affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(affine) |
377 | + | ||
371 | actor.SetUserMatrix(affine_vtk) | 378 | actor.SetUserMatrix(affine_vtk) |
372 | 379 | ||
373 | if overwrite: | 380 | if overwrite: |
invesalius/data/tractography.py
@@ -29,14 +29,11 @@ import time | @@ -29,14 +29,11 @@ import time | ||
29 | import numpy as np | 29 | import numpy as np |
30 | import queue | 30 | import queue |
31 | from invesalius.pubsub import pub as Publisher | 31 | from invesalius.pubsub import pub as Publisher |
32 | -from scipy.stats import norm | ||
33 | import vtk | 32 | import vtk |
34 | 33 | ||
35 | import invesalius.constants as const | 34 | import invesalius.constants as const |
36 | import invesalius.data.imagedata_utils as img_utils | 35 | import invesalius.data.imagedata_utils as img_utils |
37 | 36 | ||
38 | -import invesalius.project as prj | ||
39 | - | ||
40 | # Nice print for arrays | 37 | # Nice print for arrays |
41 | # np.set_printoptions(precision=2) | 38 | # np.set_printoptions(precision=2) |
42 | # np.set_printoptions(suppress=True) | 39 | # np.set_printoptions(suppress=True) |
@@ -104,75 +101,57 @@ def compute_tubes(trk, direction): | @@ -104,75 +101,57 @@ def compute_tubes(trk, direction): | ||
104 | return trk_tube | 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 | """Adds a set of tracts to given position in a given vtkMultiBlockDataSet | 105 | """Adds a set of tracts to given position in a given vtkMultiBlockDataSet |
109 | 106 | ||
110 | :param out_list: List of vtkTubeFilters representing the tracts | 107 | :param out_list: List of vtkTubeFilters representing the tracts |
111 | :type out_list: list | 108 | :type out_list: list |
112 | - :param root: A collection of tracts as a vtkMultiBlockDataSet | ||
113 | - :type root: vtkMultiBlockDataSet | ||
114 | :param n_block: The location in the given vtkMultiBlockDataSet to insert the new tracts | 109 | :param n_block: The location in the given vtkMultiBlockDataSet to insert the new tracts |
115 | :type n_block: int | 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 | :rtype: vtkMultiBlockDataSet | 112 | :rtype: vtkMultiBlockDataSet |
136 | """ | 113 | """ |
137 | 114 | ||
115 | + # create a branch and add the streamlines | ||
138 | branch = vtk.vtkMultiBlockDataSet() | 116 | branch = vtk.vtkMultiBlockDataSet() |
117 | + | ||
139 | # create tracts only when at least one was computed | 118 | # create tracts only when at least one was computed |
140 | # print("Len outlist in root: ", len(out_list)) | 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 | if not out_list.count(None) == len(out_list): | 122 | if not out_list.count(None) == len(out_list): |
142 | for n, tube in enumerate(out_list): | 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 | return branch | 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 | """Convert the list of all computed tracts given by Trekker run and returns a vtkMultiBlockDataSet | 130 | """Convert the list of all computed tracts given by Trekker run and returns a vtkMultiBlockDataSet |
150 | 131 | ||
151 | :param trk_list: List of lists containing the computed tracts and corresponding coordinates | 132 | :param trk_list: List of lists containing the computed tracts and corresponding coordinates |
152 | :type trk_list: list | 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 | :return: The updated collection of tracts as a vtkMultiBlockDataSet | 138 | :return: The updated collection of tracts as a vtkMultiBlockDataSet |
158 | :rtype: vtkMultiBlockDataSet | 139 | :rtype: vtkMultiBlockDataSet |
159 | """ | 140 | """ |
160 | 141 | ||
161 | # Transform tracts to array | 142 | # Transform tracts to array |
162 | trk_arr = [np.asarray(trk_n).T if trk_n else None for trk_n in trk_list] | 143 | trk_arr = [np.asarray(trk_n).T if trk_n else None for trk_n in trk_list] |
163 | - | ||
164 | # Compute the directions | 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 | # Compute the vtk tubes | 146 | # Compute the vtk tubes |
168 | out_list = [compute_tubes(trk_arr_n, trk_dir_n) for trk_arr_n, trk_dir_n in zip(trk_arr, trk_dir)] | 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 | """ Compute tractograms using the Trekker library. | 155 | """ Compute tractograms using the Trekker library. |
177 | 156 | ||
178 | :param trekker: Trekker library instance | 157 | :param trekker: Trekker library instance |
@@ -183,52 +162,51 @@ def compute_tracts(trekker, position, affine, affine_vtk, n_tracts): | @@ -183,52 +162,51 @@ def compute_tracts(trekker, position, affine, affine_vtk, n_tracts): | ||
183 | :type affine: numpy.ndarray | 162 | :type affine: numpy.ndarray |
184 | :param affine_vtk: vtkMatrix4x4 isntance with affine transformation matrix | 163 | :param affine_vtk: vtkMatrix4x4 isntance with affine transformation matrix |
185 | :type affine_vtk: vtkMatrix4x4 | 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 | # Juuso's | 170 | # Juuso's |
194 | # seed = np.array([[-8.49, -8.39, 2.5]]) | 171 | # seed = np.array([[-8.49, -8.39, 2.5]]) |
195 | # Baran M1 | 172 | # Baran M1 |
196 | # seed = np.array([[27.53, -77.37, 46.42]]) | 173 | # seed = np.array([[27.53, -77.37, 46.42]]) |
197 | seed_trk = img_utils.convert_world_to_voxel(position, affine) | 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 | class ComputeTractsThread(threading.Thread): | 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 | def __init__(self, inp, queues, event, sle): | 211 | def __init__(self, inp, queues, event, sle): |
234 | """Class (threading) to compute real time tractography data for visualization. | 212 | """Class (threading) to compute real time tractography data for visualization. |
@@ -265,6 +243,7 @@ class ComputeTractsThread(threading.Thread): | @@ -265,6 +243,7 @@ class ComputeTractsThread(threading.Thread): | ||
265 | 243 | ||
266 | trekker, affine, offset, n_tracts_total, seed_radius, n_threads, act_data, affine_vtk, img_shift = self.inp | 244 | trekker, affine, offset, n_tracts_total, seed_radius, n_threads, act_data, affine_vtk, img_shift = self.inp |
267 | # n_threads = n_tracts_total | 245 | # n_threads = n_tracts_total |
246 | + n_threads = int(n_threads/4) | ||
268 | p_old = np.array([[0., 0., 0.]]) | 247 | p_old = np.array([[0., 0., 0.]]) |
269 | n_tracts = 0 | 248 | n_tracts = 0 |
270 | 249 | ||
@@ -312,13 +291,13 @@ class ComputeTractsThread(threading.Thread): | @@ -312,13 +291,13 @@ class ComputeTractsThread(threading.Thread): | ||
312 | # run the trekker, this is the slowest line of code, be careful to just use once! | 291 | # run the trekker, this is the slowest line of code, be careful to just use once! |
313 | trk_list = trekker.run() | 292 | trk_list = trekker.run() |
314 | 293 | ||
315 | - if trk_list: | 294 | + if len(trk_list) > 2: |
316 | # print("dist: {}".format(dist)) | 295 | # print("dist: {}".format(dist)) |
317 | if dist >= seed_radius: | 296 | if dist >= seed_radius: |
318 | # when moving the coil further than the seed_radius restart the bundle computation | 297 | # when moving the coil further than the seed_radius restart the bundle computation |
319 | bundle = vtk.vtkMultiBlockDataSet() | 298 | bundle = vtk.vtkMultiBlockDataSet() |
320 | n_branches = 0 | 299 | n_branches = 0 |
321 | - branch = tracts_computation_branch(trk_list) | 300 | + branch = compute_tracts(trk_list, n_tract=0, alpha=255) |
322 | bundle.SetBlock(n_branches, branch) | 301 | bundle.SetBlock(n_branches, branch) |
323 | n_branches += 1 | 302 | n_branches += 1 |
324 | n_tracts = branch.GetNumberOfBlocks() | 303 | n_tracts = branch.GetNumberOfBlocks() |
@@ -326,7 +305,7 @@ class ComputeTractsThread(threading.Thread): | @@ -326,7 +305,7 @@ class ComputeTractsThread(threading.Thread): | ||
326 | # TODO: maybe keep computing even if reaches the maximum | 305 | # TODO: maybe keep computing even if reaches the maximum |
327 | elif dist < seed_radius and n_tracts < n_tracts_total: | 306 | elif dist < seed_radius and n_tracts < n_tracts_total: |
328 | # compute tracts blocks and add to bungle until reaches the maximum number of tracts | 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 | if bundle: | 309 | if bundle: |
331 | bundle.SetBlock(n_branches, branch) | 310 | bundle.SetBlock(n_branches, branch) |
332 | n_tracts += branch.GetNumberOfBlocks() | 311 | n_tracts += branch.GetNumberOfBlocks() |
@@ -361,314 +340,218 @@ class ComputeTractsThread(threading.Thread): | @@ -361,314 +340,218 @@ class ComputeTractsThread(threading.Thread): | ||
361 | 340 | ||
362 | class ComputeTractsACTThread(threading.Thread): | 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 | """Class (threading) to compute real time tractography data for visualization. | 344 | """Class (threading) to compute real time tractography data for visualization. |
366 | 345 | ||
367 | Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/) | 346 | Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/) |
368 | For VTK visualization, each tract (fiber) is a constructed as a tube and many tubes combined in one | 347 | For VTK visualization, each tract (fiber) is a constructed as a tube and many tubes combined in one |
369 | vtkMultiBlockDataSet named as a branch. Several branches are combined in another vtkMultiBlockDataSet named as | 348 | vtkMultiBlockDataSet named as a branch. Several branches are combined in another vtkMultiBlockDataSet named as |
370 | bundle, to obtain fast computation and visualization. The bundle dataset is mapped to a single vtkActor. | 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 | Sleep function in run method is used to avoid blocking GUI and more fluent, real-time navigation | 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 | :param queues: Queue list with coord_tracts_queue (Queue instance that manage co-registered coordinates) and | 359 | :param queues: Queue list with coord_tracts_queue (Queue instance that manage co-registered coordinates) and |
378 | tracts_queue (Queue instance that manage the tracts to be visualized) | 360 | tracts_queue (Queue instance that manage the tracts to be visualized) |
379 | :type queues: list[queue.Queue, queue.Queue] | 361 | :type queues: list[queue.Queue, queue.Queue] |
380 | :param event: Threading event to coordinate when tasks as done and allow UI release | 362 | :param event: Threading event to coordinate when tasks as done and allow UI release |
381 | :type event: threading.Event | 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 | threading.Thread.__init__(self, name='ComputeTractsThreadACT') | 368 | threading.Thread.__init__(self, name='ComputeTractsThreadACT') |
387 | - self.inp = inp | ||
388 | - # self.coord_queue = coord_queue | 369 | + self.input_list = input_list |
389 | self.coord_tracts_queue = queues[0] | 370 | self.coord_tracts_queue = queues[0] |
390 | self.tracts_queue = queues[1] | 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 | self.event = event | 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 | def run(self): | 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 | p_old = np.array([[0., 0., 0.]]) | 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 | bundle = None | 381 | bundle = None |
418 | - sph_sampling = True | ||
419 | dist_radius = 1.5 | 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 | m_seed = np.identity(4) | 392 | m_seed = np.identity(4) |
393 | + | ||
424 | # Compute the tracts | 394 | # Compute the tracts |
425 | - # print('ComputeTractsThread: event {}'.format(self.event.is_set())) | ||
426 | while not self.event.is_set(): | 395 | while not self.event.is_set(): |
427 | try: | 396 | try: |
428 | - # print("Computing tracts") | ||
429 | # get from the queue the coordinates, coregistration transformation matrix, and flipped matrix | 397 | # get from the queue the coordinates, coregistration transformation matrix, and flipped matrix |
430 | m_img_flip = self.coord_tracts_queue.get_nowait() | 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 | # DEBUG: Uncomment the m_img_flip below so that distance is fixed and tracts keep computing | 400 | # DEBUG: Uncomment the m_img_flip below so that distance is fixed and tracts keep computing |
444 | # m_img_flip[:3, -1] = (5., 10., 12.) | 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 | # rescale the alpha value that defines the opacity of the branch | 411 | # rescale the alpha value that defines the opacity of the branch |
452 | # the n interval is [1, 10] and the new interval is [51, 255] | 412 | # the n interval is [1, 10] and the new interval is [51, 255] |
453 | # the new interval is defined to have no 0 opacity (minimum is 51, i.e., 20%) | 413 | # the new interval is defined to have no 0 opacity (minimum is 51, i.e., 20%) |
454 | alpha = (n_param - 1) * (255 - 51) / (10 - 1) + 51 | 414 | alpha = (n_param - 1) * (255 - 51) / (10 - 1) + 51 |
455 | trekker.minFODamp(n_param * 0.01) | 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 | # When moving the coil further than the seed_radius restart the bundle computation | 482 | # When moving the coil further than the seed_radius restart the bundle computation |
460 | # Currently, it stops to compute tracts when the maximum number of tracts is reached maybe keep | 483 | # Currently, it stops to compute tracts when the maximum number of tracts is reached maybe keep |
461 | # computing even if reaches the maximum | 484 | # computing even if reaches the maximum |
462 | if dist >= dist_radius: | 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 | # run the trekker, this is the slowest line of code, be careful to just use once! | 494 | # run the trekker, this is the slowest line of code, be careful to just use once! |
528 | trk_list = trekker.run() | 495 | trk_list = trekker.run() |
529 | 496 | ||
530 | # check if any tract was found, otherwise doesn't count | 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 | bundle = vtk.vtkMultiBlockDataSet() | 505 | bundle = vtk.vtkMultiBlockDataSet() |
533 | - branch = tracts_computation_branch(trk_list, alpha) | ||
534 | bundle.SetBlock(n_branches, branch) | 506 | bundle.SetBlock(n_branches, branch) |
535 | n_branches = 1 | 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 | elif dist < dist_radius and n_tracts < n_tracts_total: | 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 | trk_list = trekker.run() | 523 | trk_list = trekker.run() |
544 | - if trk_list: | 524 | + |
525 | + if len(trk_list): | ||
545 | # compute tract blocks and add to bundle until reaches the maximum number of tracts | 526 | # compute tract blocks and add to bundle until reaches the maximum number of tracts |
546 | # the alpha changes depending on the parameter set | 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 | # be more evident in slow computer or for heavier tract computations, it is better slow update | 540 | # be more evident in slow computer or for heavier tract computations, it is better slow update |
566 | # than visualizing old data | 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 | self.coord_tracts_queue.task_done() | 543 | self.coord_tracts_queue.task_done() |
572 | - # self.coord_queue.task_done() | ||
573 | - # print('ComputeTractsThread: done {}'.format(count)) | ||
574 | 544 | ||
575 | # sleep required to prevent user interface from being unresponsive | 545 | # sleep required to prevent user interface from being unresponsive |
576 | - time.sleep(self.sle) | 546 | + time.sleep(self.sleep_thread) |
577 | # if no coordinates pass | 547 | # if no coordinates pass |
578 | except queue.Empty: | 548 | except queue.Empty: |
579 | - # print("Empty queue in tractography") | ||
580 | pass | 549 | pass |
581 | # if queue is full mark as done (may not be needed in this new "nowait" method) | 550 | # if queue is full mark as done (may not be needed in this new "nowait" method) |
582 | except queue.Full: | 551 | except queue.Full: |
583 | - # self.coord_queue.task_done() | ||
584 | self.coord_tracts_queue.task_done() | 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 | def set_trekker_parameters(trekker, params): | 555 | def set_trekker_parameters(trekker, params): |
673 | """Set all user-defined parameters for tractography computation using the Trekker library | 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,21 +563,27 @@ def set_trekker_parameters(trekker, params): | ||
680 | :rtype: list | 563 | :rtype: list |
681 | """ | 564 | """ |
682 | trekker.seed_maxTrials(params['seed_max']) | 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 | trekker.writeInterval(params['write_interval']) | 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 | trekker.maxSamplingPerStep(params['max_sampling_step']) | 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 | # check number if number of cores is valid in configuration file, | 583 | # check number if number of cores is valid in configuration file, |
695 | # otherwise use the maximum number of threads which is usually 2*N_CPUS | 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 | n_threads = params['numb_threads'] | 587 | n_threads = params['numb_threads'] |
699 | 588 | ||
700 | trekker.numberOfThreads(n_threads) | 589 | trekker.numberOfThreads(n_threads) |
@@ -712,13 +601,21 @@ def grid_offset(data, coord_list_w_tr, img_shift): | @@ -712,13 +601,21 @@ def grid_offset(data, coord_list_w_tr, img_shift): | ||
712 | 601 | ||
713 | # extract the first occurrence of a specific label from the MRI image | 602 | # extract the first occurrence of a specific label from the MRI image |
714 | labs = data[coord_list_w_tr_mri[..., 0], coord_list_w_tr_mri[..., 1], coord_list_w_tr_mri[..., 2]] | 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 | # convert coordinate back to invesalius 3D space | 609 | # convert coordinate back to invesalius 3D space |
719 | pt_found_inv = pt_found - np.array([0., img_shift, 0.]) | 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 | # # convert to world coordinate space to use as seed for fiber tracking | 620 | # # convert to world coordinate space to use as seed for fiber tracking |
724 | # pt_found_tr = np.append(pt_found, 1)[np.newaxis, :].T | 621 | # pt_found_tr = np.append(pt_found, 1)[np.newaxis, :].T |
invesalius/data/viewer_volume.py
@@ -307,7 +307,6 @@ class Viewer(wx.Panel): | @@ -307,7 +307,6 @@ class Viewer(wx.Panel): | ||
307 | Publisher.subscribe(self.OnRemoveTracts, 'Remove tracts') | 307 | Publisher.subscribe(self.OnRemoveTracts, 'Remove tracts') |
308 | Publisher.subscribe(self.UpdateSeedOffset, 'Update seed offset') | 308 | Publisher.subscribe(self.UpdateSeedOffset, 'Update seed offset') |
309 | Publisher.subscribe(self.UpdateMarkerOffsetState, 'Update marker offset state') | 309 | Publisher.subscribe(self.UpdateMarkerOffsetState, 'Update marker offset state') |
310 | - Publisher.subscribe(self.UpdateMarkerOffsetPosition, 'Update marker offset') | ||
311 | Publisher.subscribe(self.AddPeeledSurface, 'Update peel') | 310 | Publisher.subscribe(self.AddPeeledSurface, 'Update peel') |
312 | Publisher.subscribe(self.GetPeelCenters, 'Get peel centers and normals') | 311 | Publisher.subscribe(self.GetPeelCenters, 'Get peel centers and normals') |
313 | Publisher.subscribe(self.Initlocator_viewer, 'Get init locator') | 312 | Publisher.subscribe(self.Initlocator_viewer, 'Get init locator') |
@@ -857,8 +856,10 @@ class Viewer(wx.Panel): | @@ -857,8 +856,10 @@ class Viewer(wx.Panel): | ||
857 | else: | 856 | else: |
858 | self.DisableCoilTracker() | 857 | self.DisableCoilTracker() |
859 | if self.actor_peel: | 858 | if self.actor_peel: |
860 | - self.object_orientation_torus_actor.SetVisibility(1) | ||
861 | - 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) | ||
862 | 863 | ||
863 | def OnUpdateObjectTargetGuide(self, m_img, coord): | 864 | def OnUpdateObjectTargetGuide(self, m_img, coord): |
864 | 865 | ||
@@ -1607,10 +1608,6 @@ class Viewer(wx.Panel): | @@ -1607,10 +1608,6 @@ class Viewer(wx.Panel): | ||
1607 | self.ren.AddActor(self.mark_actor) | 1608 | self.ren.AddActor(self.mark_actor) |
1608 | self.Refresh() | 1609 | self.Refresh() |
1609 | 1610 | ||
1610 | - def UpdateMarkerOffsetPosition(self, coord_offset): | ||
1611 | - self.mark_actor.SetPosition(coord_offset) | ||
1612 | - self.Refresh() | ||
1613 | - | ||
1614 | def UpdateObjectOrientation(self, m_img, coord): | 1611 | def UpdateObjectOrientation(self, m_img, coord): |
1615 | # print("Update object orientation") | 1612 | # print("Update object orientation") |
1616 | 1613 | ||
@@ -1687,7 +1684,7 @@ class Viewer(wx.Panel): | @@ -1687,7 +1684,7 @@ class Viewer(wx.Panel): | ||
1687 | # self.ball_actor.SetVisibility(1) | 1684 | # self.ball_actor.SetVisibility(1) |
1688 | self.Refresh() | 1685 | self.Refresh() |
1689 | 1686 | ||
1690 | - 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): |
1691 | mapper = vtk.vtkCompositePolyDataMapper2() | 1688 | mapper = vtk.vtkCompositePolyDataMapper2() |
1692 | mapper.SetInputDataObject(root) | 1689 | mapper.SetInputDataObject(root) |
1693 | 1690 |
invesalius/gui/dialogs.py
@@ -4203,7 +4203,7 @@ class GoToDialogScannerCoord(wx.Dialog): | @@ -4203,7 +4203,7 @@ class GoToDialogScannerCoord(wx.Dialog): | ||
4203 | main_sizer.Fit(self) | 4203 | main_sizer.Fit(self) |
4204 | 4204 | ||
4205 | self.orientation = None | 4205 | self.orientation = None |
4206 | - self.affine = None | 4206 | + self.affine = np.identity(4) |
4207 | 4207 | ||
4208 | self.__bind_events() | 4208 | self.__bind_events() |
4209 | 4209 |
invesalius/gui/task_navigator.py
@@ -165,7 +165,7 @@ class InnerFoldPanel(wx.Panel): | @@ -165,7 +165,7 @@ class InnerFoldPanel(wx.Panel): | ||
165 | # Study this. | 165 | # Study this. |
166 | 166 | ||
167 | fold_panel = fpb.FoldPanelBar(self, -1, wx.DefaultPosition, | 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 | # Initialize Tracker and PedalConnection objects here so that they are available to several panels. | 170 | # Initialize Tracker and PedalConnection objects here so that they are available to several panels. |
171 | # | 171 | # |
@@ -1078,17 +1078,15 @@ class ObjectRegistrationPanel(wx.Panel): | @@ -1078,17 +1078,15 @@ class ObjectRegistrationPanel(wx.Panel): | ||
1078 | 1078 | ||
1079 | try: | 1079 | try: |
1080 | if filename: | 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 | self.checktrack.Enable(1) | 1091 | self.checktrack.Enable(1) |
1094 | self.checktrack.SetValue(True) | 1092 | self.checktrack.SetValue(True) |
@@ -1098,7 +1096,7 @@ class ObjectRegistrationPanel(wx.Panel): | @@ -1098,7 +1096,7 @@ class ObjectRegistrationPanel(wx.Panel): | ||
1098 | label=_("Object file successfully loaded")) | 1096 | label=_("Object file successfully loaded")) |
1099 | Publisher.sendMessage('Update track object state', flag=True, obj_name=self.obj_name) | 1097 | Publisher.sendMessage('Update track object state', flag=True, obj_name=self.obj_name) |
1100 | Publisher.sendMessage('Change camera checkbox', status=False) | 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 | except: | 1100 | except: |
1103 | wx.MessageBox(_("Object registration file incompatible."), _("InVesalius 3")) | 1101 | wx.MessageBox(_("Object registration file incompatible."), _("InVesalius 3")) |
1104 | Publisher.sendMessage('Update status text in GUI', label="") | 1102 | Publisher.sendMessage('Update status text in GUI', label="") |
@@ -1449,8 +1447,8 @@ class MarkersPanel(wx.Panel): | @@ -1449,8 +1447,8 @@ class MarkersPanel(wx.Panel): | ||
1449 | else: | 1447 | else: |
1450 | self.nav_status = True | 1448 | self.nav_status = True |
1451 | 1449 | ||
1452 | - def UpdateSeedCoordinates(self, root=None, affine_vtk=None, coord_offset=(0, 0, 0)): | ||
1453 | - 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 | ||
1454 | 1452 | ||
1455 | def UpdateRobotCoordinates(self, coordinates_raw, markers_flag): | 1453 | def UpdateRobotCoordinates(self, coordinates_raw, markers_flag): |
1456 | self.raw_target_robot = coordinates_raw[1], coordinates_raw[2] | 1454 | self.raw_target_robot = coordinates_raw[1], coordinates_raw[2] |
@@ -1735,7 +1733,7 @@ class TractographyPanel(wx.Panel): | @@ -1735,7 +1733,7 @@ class TractographyPanel(wx.Panel): | ||
1735 | default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR) | 1733 | default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR) |
1736 | self.SetBackgroundColour(default_colour) | 1734 | self.SetBackgroundColour(default_colour) |
1737 | 1735 | ||
1738 | - self.affine = None | 1736 | + self.affine = np.identity(4) |
1739 | self.affine_vtk = None | 1737 | self.affine_vtk = None |
1740 | self.trekker = None | 1738 | self.trekker = None |
1741 | self.n_tracts = const.N_TRACTS | 1739 | self.n_tracts = const.N_TRACTS |
@@ -1992,7 +1990,6 @@ class TractographyPanel(wx.Panel): | @@ -1992,7 +1990,6 @@ class TractographyPanel(wx.Panel): | ||
1992 | self.nav_status = nav_status | 1990 | self.nav_status = nav_status |
1993 | 1991 | ||
1994 | def OnLinkBrain(self, event=None): | 1992 | def OnLinkBrain(self, event=None): |
1995 | - Publisher.sendMessage('Update status text in GUI', label=_("Busy")) | ||
1996 | Publisher.sendMessage('Begin busy cursor') | 1993 | Publisher.sendMessage('Begin busy cursor') |
1997 | mask_path = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, _("Import brain mask")) | 1994 | mask_path = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, _("Import brain mask")) |
1998 | img_path = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, _("Import T1 anatomical image")) | 1995 | img_path = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, _("Import T1 anatomical image")) |
@@ -2006,31 +2003,37 @@ class TractographyPanel(wx.Panel): | @@ -2006,31 +2003,37 @@ class TractographyPanel(wx.Panel): | ||
2006 | slic = sl.Slice() | 2003 | slic = sl.Slice() |
2007 | prj_data = prj.Project() | 2004 | prj_data = prj.Project() |
2008 | matrix_shape = tuple(prj_data.matrix_shape) | 2005 | matrix_shape = tuple(prj_data.matrix_shape) |
2006 | + spacing = tuple(prj_data.spacing) | ||
2007 | + img_shift = spacing[1] * (matrix_shape[1] - 1) | ||
2009 | self.affine = slic.affine.copy() | 2008 | self.affine = slic.affine.copy() |
2010 | - self.affine[1, -1] -= matrix_shape[1] | 2009 | + self.affine[1, -1] -= img_shift |
2011 | self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine) | 2010 | self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine) |
2012 | 2011 | ||
2013 | - try: | ||
2014 | - self.brain_peel = brain.Brain(img_path, mask_path, self.n_peels, self.affine_vtk) | ||
2015 | - self.brain_actor = self.brain_peel.get_actor(self.peel_depth, self.affine_vtk) | ||
2016 | - self.brain_actor.GetProperty().SetOpacity(self.brain_opacity) | ||
2017 | - Publisher.sendMessage('Update peel', flag=True, actor=self.brain_actor) | ||
2018 | - Publisher.sendMessage('Get peel centers and normals', centers=self.brain_peel.peel_centers, | ||
2019 | - normals=self.brain_peel.peel_normals) | ||
2020 | - Publisher.sendMessage('Get init locator', locator=self.brain_peel.locator) | ||
2021 | - self.checkpeeling.Enable(1) | ||
2022 | - self.checkpeeling.SetValue(True) | ||
2023 | - self.spin_opacity.Enable(1) | ||
2024 | - Publisher.sendMessage('Update status text in GUI', label=_("Brain model loaded")) | ||
2025 | - self.peel_loaded = True | ||
2026 | - Publisher.sendMessage('Update peel visualization', data= self.peel_loaded) | ||
2027 | - except: | ||
2028 | - 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")) | ||
2029 | 2033 | ||
2030 | Publisher.sendMessage('End busy cursor') | 2034 | Publisher.sendMessage('End busy cursor') |
2031 | 2035 | ||
2032 | def OnLinkFOD(self, event=None): | 2036 | def OnLinkFOD(self, event=None): |
2033 | - Publisher.sendMessage('Update status text in GUI', label=_("Busy")) | ||
2034 | Publisher.sendMessage('Begin busy cursor') | 2037 | Publisher.sendMessage('Begin busy cursor') |
2035 | filename = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, msg=_("Import Trekker FOD")) | 2038 | filename = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, msg=_("Import Trekker FOD")) |
2036 | # Juuso | 2039 | # Juuso |
@@ -2041,69 +2044,83 @@ class TractographyPanel(wx.Panel): | @@ -2041,69 +2044,83 @@ class TractographyPanel(wx.Panel): | ||
2041 | # FOD_path = 'Baran_FOD.nii' | 2044 | # FOD_path = 'Baran_FOD.nii' |
2042 | # filename = os.path.join(data_dir, FOD_path) | 2045 | # filename = os.path.join(data_dir, FOD_path) |
2043 | 2046 | ||
2044 | - # if not self.affine_vtk: | ||
2045 | - # slic = sl.Slice() | ||
2046 | - # self.affine = slic.affine | ||
2047 | - # self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine) | ||
2048 | - | ||
2049 | if not self.affine_vtk: | 2047 | if not self.affine_vtk: |
2050 | slic = sl.Slice() | 2048 | slic = sl.Slice() |
2051 | prj_data = prj.Project() | 2049 | prj_data = prj.Project() |
2052 | matrix_shape = tuple(prj_data.matrix_shape) | 2050 | matrix_shape = tuple(prj_data.matrix_shape) |
2051 | + spacing = tuple(prj_data.spacing) | ||
2052 | + img_shift = spacing[1] * (matrix_shape[1] - 1) | ||
2053 | self.affine = slic.affine.copy() | 2053 | self.affine = slic.affine.copy() |
2054 | - self.affine[1, -1] -= matrix_shape[1] | 2054 | + self.affine[1, -1] -= img_shift |
2055 | self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine) | 2055 | self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine) |
2056 | 2056 | ||
2057 | - # try: | ||
2058 | - | ||
2059 | - self.trekker = Trekker.initialize(filename.encode('utf-8')) | ||
2060 | - self.trekker, n_threads = dti.set_trekker_parameters(self.trekker, self.trekker_cfg) | ||
2061 | - | ||
2062 | - self.checktracts.Enable(1) | ||
2063 | - self.checktracts.SetValue(True) | ||
2064 | - self.view_tracts = True | ||
2065 | - Publisher.sendMessage('Update Trekker object', data=self.trekker) | ||
2066 | - Publisher.sendMessage('Update number of threads', data=n_threads) | ||
2067 | - Publisher.sendMessage('Update tracts visualization', data=1) | ||
2068 | - Publisher.sendMessage('Update status text in GUI', label=_("Trekker initialized")) | ||
2069 | - # except: | ||
2070 | - # 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")) | ||
2071 | 2076 | ||
2072 | Publisher.sendMessage('End busy cursor') | 2077 | Publisher.sendMessage('End busy cursor') |
2073 | 2078 | ||
2074 | def OnLoadACT(self, event=None): | 2079 | def OnLoadACT(self, event=None): |
2075 | - Publisher.sendMessage('Update status text in GUI', label=_("Busy")) | ||
2076 | - Publisher.sendMessage('Begin busy cursor') | ||
2077 | - filename = dlg.ShowImportOtherFilesDialog(const.ID_NIFTI_IMPORT, msg=_("Import anatomical labels")) | ||
2078 | - # Baran | ||
2079 | - # data_dir = os.environ.get('OneDrive') + r'\data\dti_navigation\baran\anat_reg_improve_20200609' | ||
2080 | - # act_path = 'Baran_trekkerACTlabels_inFODspace.nii' | ||
2081 | - # filename = os.path.join(data_dir, act_path) | ||
2082 | - | ||
2083 | - act_data = nb.squeeze_image(nb.load(filename)) | ||
2084 | - act_data = nb.as_closest_canonical(act_data) | ||
2085 | - act_data.update_header() | ||
2086 | - act_data_arr = act_data.get_fdata() | 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) | ||
2087 | 2097 | ||
2088 | - if not self.affine_vtk: | ||
2089 | - slic = sl.Slice() | ||
2090 | - prj_data = prj.Project() | ||
2091 | - matrix_shape = tuple(prj_data.matrix_shape) | ||
2092 | - self.affine = slic.affine.copy() | ||
2093 | - self.affine[1, -1] -= matrix_shape[1] | ||
2094 | - self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine) | ||
2095 | - | ||
2096 | - self.checkACT.Enable(1) | ||
2097 | - self.checkACT.SetValue(True) | ||
2098 | - | ||
2099 | - Publisher.sendMessage('Update ACT data', data=act_data_arr) | ||
2100 | - Publisher.sendMessage('Enable ACT', data=True) | ||
2101 | - # Publisher.sendMessage('Create grid', data=act_data_arr, affine=self.affine) | ||
2102 | - # Publisher.sendMessage('Update number of threads', data=n_threads) | ||
2103 | - # Publisher.sendMessage('Update tracts visualization', data=1) | ||
2104 | - Publisher.sendMessage('Update status text in GUI', label=_("Trekker ACT loaded")) | ||
2105 | - | ||
2106 | - 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")) | ||
2107 | 2124 | ||
2108 | def OnLoadParameters(self, event=None): | 2125 | def OnLoadParameters(self, event=None): |
2109 | import json | 2126 | import json |
@@ -2146,10 +2163,13 @@ class TractographyPanel(wx.Panel): | @@ -2146,10 +2163,13 @@ class TractographyPanel(wx.Panel): | ||
2146 | # print("Running during navigation") | 2163 | # print("Running during navigation") |
2147 | coord_flip = list(position[:3]) | 2164 | coord_flip = list(position[:3]) |
2148 | coord_flip[1] = -coord_flip[1] | 2165 | coord_flip[1] = -coord_flip[1] |
2149 | - dti.compute_tracts(self.trekker, coord_flip, self.affine, self.affine_vtk, | ||
2150 | - self.n_tracts) | 2166 | + dti.compute_and_visualize_tracts(self.trekker, coord_flip, self.affine, self.affine_vtk, |
2167 | + self.n_tracts) | ||
2151 | 2168 | ||
2152 | def OnCloseProject(self): | 2169 | def OnCloseProject(self): |
2170 | + self.trekker = None | ||
2171 | + self.trekker_cfg = const.TREKKER_CONFIG | ||
2172 | + | ||
2153 | self.checktracts.SetValue(False) | 2173 | self.checktracts.SetValue(False) |
2154 | self.checktracts.Enable(0) | 2174 | self.checktracts.Enable(0) |
2155 | self.checkpeeling.SetValue(False) | 2175 | self.checkpeeling.SetValue(False) |
invesalius/navigation/navigation.py
@@ -27,7 +27,6 @@ import numpy as np | @@ -27,7 +27,6 @@ import numpy as np | ||
27 | import invesalius.constants as const | 27 | import invesalius.constants as const |
28 | import invesalius.project as prj | 28 | import invesalius.project as prj |
29 | import invesalius.data.bases as db | 29 | import invesalius.data.bases as db |
30 | -import invesalius.data.coordinates as dco | ||
31 | import invesalius.data.coregistration as dcr | 30 | import invesalius.data.coregistration as dcr |
32 | import invesalius.data.serial_port_connection as spc | 31 | import invesalius.data.serial_port_connection as spc |
33 | import invesalius.data.slice_ as sl | 32 | import invesalius.data.slice_ as sl |
@@ -98,12 +97,11 @@ class UpdateNavigationScene(threading.Thread): | @@ -98,12 +97,11 @@ class UpdateNavigationScene(threading.Thread): | ||
98 | 97 | ||
99 | # use of CallAfter is mandatory otherwise crashes the wx interface | 98 | # use of CallAfter is mandatory otherwise crashes the wx interface |
100 | if self.view_tracts: | 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 | #TODO: Check if possible to combine the Remove tracts with Update tracts in a single command | 101 | #TODO: Check if possible to combine the Remove tracts with Update tracts in a single command |
103 | wx.CallAfter(Publisher.sendMessage, 'Remove tracts') | 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 | self.tracts_queue.task_done() | 105 | self.tracts_queue.task_done() |
108 | 106 | ||
109 | if self.serial_port_enabled: | 107 | if self.serial_port_enabled: |
@@ -118,10 +116,11 @@ class UpdateNavigationScene(threading.Thread): | @@ -118,10 +116,11 @@ class UpdateNavigationScene(threading.Thread): | ||
118 | wx.CallAfter(Publisher.sendMessage, 'Set cross focal point', position=coord) | 116 | wx.CallAfter(Publisher.sendMessage, 'Set cross focal point', position=coord) |
119 | wx.CallAfter(Publisher.sendMessage, 'Update raw coordinates', coordinates_raw=coordinates_raw, markers_flag=markers_flag) | 117 | wx.CallAfter(Publisher.sendMessage, 'Update raw coordinates', coordinates_raw=coordinates_raw, markers_flag=markers_flag) |
120 | wx.CallAfter(Publisher.sendMessage, 'Update slice viewer') | 118 | wx.CallAfter(Publisher.sendMessage, 'Update slice viewer') |
119 | + wx.CallAfter(Publisher.sendMessage, 'Sensor ID', markers_flag=markers_flag) | ||
121 | 120 | ||
122 | if view_obj: | 121 | if view_obj: |
123 | wx.CallAfter(Publisher.sendMessage, 'Update object matrix', m_img=m_img, coord=coord) | 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 | self.coord_queue.task_done() | 124 | self.coord_queue.task_done() |
126 | # print('UpdateScene: done {}'.format(count)) | 125 | # print('UpdateScene: done {}'.format(count)) |
127 | # count += 1 | 126 | # count += 1 |
@@ -191,7 +190,7 @@ class Navigation(): | @@ -191,7 +190,7 @@ class Navigation(): | ||
191 | 190 | ||
192 | def UpdateSleep(self, sleep): | 191 | def UpdateSleep(self, sleep): |
193 | self.sleep_nav = sleep | 192 | self.sleep_nav = sleep |
194 | - self.serial_port_connection.sleep_nav = sleep | 193 | + # self.serial_port_connection.sleep_nav = sleep |
195 | 194 | ||
196 | def UpdateSerialPort(self, serial_port_in_use, com_port=None, baud_rate=None): | 195 | def UpdateSerialPort(self, serial_port_in_use, com_port=None, baud_rate=None): |
197 | self.serial_port_in_use = serial_port_in_use | 196 | self.serial_port_in_use = serial_port_in_use |
@@ -307,15 +306,22 @@ class Navigation(): | @@ -307,15 +306,22 @@ class Navigation(): | ||
307 | 306 | ||
308 | if self.view_tracts: | 307 | if self.view_tracts: |
309 | # initialize Trekker parameters | 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 | slic = sl.Slice() | 312 | slic = sl.Slice() |
311 | prj_data = prj.Project() | 313 | prj_data = prj.Project() |
312 | matrix_shape = tuple(prj_data.matrix_shape) | 314 | matrix_shape = tuple(prj_data.matrix_shape) |
315 | + spacing = tuple(prj_data.spacing) | ||
316 | + img_shift = spacing[1] * (matrix_shape[1] - 1) | ||
313 | affine = slic.affine.copy() | 317 | affine = slic.affine.copy() |
314 | - affine[1, -1] -= matrix_shape[1] | 318 | + affine[1, -1] -= img_shift |
315 | affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(affine) | 319 | affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(affine) |
320 | + | ||
316 | Publisher.sendMessage("Update marker offset state", create=True) | 321 | Publisher.sendMessage("Update marker offset state", create=True) |
322 | + | ||
317 | self.trk_inp = self.trekker, affine, self.seed_offset, self.n_tracts, self.seed_radius,\ | 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 | # print("Appending the tract computation thread!") | 325 | # print("Appending the tract computation thread!") |
320 | queues = [self.coord_tracts_queue, self.tracts_queue] | 326 | queues = [self.coord_tracts_queue, self.tracts_queue] |
321 | if self.enable_act: | 327 | if self.enable_act: |