Commit 0673940c840118edf2b74d57dd93d5dd648a61a0
Committed by
GitHub
1 parent
b7470270
Exists in
master
Refactor of thread management in neuronavigation (#242)
* ADD: Initial support for real time tracts visualization during navigation * ENH: Improved affine transformations for importing external polydata in world coordinates * ENH: Tracts visualization with cross move started * Create Trekker thread * Tracts style working and save and load project with affine * Tract computing incorporated to navigation pipeline * Add offset to coil normal to get coordinate inside brain * ADD: Pipeline for navigation with tract visualiztion and better debug tracker * ENH: Refactor the cross update feature * FIX: Fixed update cross during navigation and improved tract visualization * FIX: Seed flip when applying offset and added list to offset user control * ADD: Brain surface texture mapping with peeling depth * Improved sleep for navigation with tractography * ENH: Tract computation speed doubled and tracts colors fixed * ENH: Print removed in tract coloring * ENH: Improved visualization of tracts * ENH: Divide tract computation in chuncks * ADD: Threads using Queue for navigation * ADD: Thread computing with class and pipeline * ENH: Navigation working with separate threads and lock * ENH: Navigation with tracts running in producer-consumer method with locks * ENH: Refactoring tracts threads * Update tractography data exchange * ENH: Improved Trekker GUI and tractography navigation working * Update optimal Trekker parameters * ADD: Dialog to adjust Trekker parameters * ENH: Dialog to load Trekker JSON configuration file * ENH: Replaced Trekker default list by dictionary with values[C * ENH: Remove tract code comments and refactor navigation pipeline * ENH: Replaced dialog warnings for message box in navigation tab * ENH: Split the removal and update of tracts * ENH: Major changes in navigation array management and tracts cleaning - Replaced all matrix to array, now using python matrix operator - Refactored flip_x function - Improved cleaning of tracts * ENH: Tract visualization enabled in cross function and matrix operations replaced by arrays - Additional code cleaning and commenting - Refactoring of tip offset snippet * ADD: Control of brain mask opacity * FIX: Remove brain surface when closing project * ENH: Update trekker parameters after loading files and major refactor in flip_x - Additional refactor of FRE computation and use of base_creation * ENH: Improved object load dialog, OnNavigate pipeline and better tracts cleaning * FIX: Navigation with tractography working - Improved offset computation for seed - Improved flip_x. No need to use a function, only multiply Y coordinate by -1 * Refactored compute_seed, affine_to_vtk, and set_message for tracts computation - Cleaned unecessary comments - Providing m_img already flip_x while set_message * FIX: Tract removal with update cross position was disabled * ENH: Threading of navigation replaced to LifoQueue, working with dev_tracker but not tested in real tracker * ENH: Tractography running with new limited Queue and nowait - Tracts actors are computed all at once, no optimization for computing blocks * FIX: Update pubsub in tracker - Navgation with tractography running * ENH: Multiblock tract computation and fixed tract cleaning - Tractography navigation working at 0.1-s sleep * ENH: Documented tractography computation and cleaned code * FIX: Navigation was not running if tracts were not found - created thread in new format for trigger - removed comments of unused pipelines and threading - removed commented old FRE base creation * FIX: Generalize new navigation to objects even without tracts - Navigation with object working seamless with and without tracts - Navigation in other modes (single ref, no object) not working - Added one extra queue to generalize the pipeline in navigation * FIX: Static object navigation working and refactor navigation pipeline - Object corregistration split in functions - Cleaned comments on reference computation * FIX: Tractography navigation working with and without coil - Applied the new threading to the non-object navigations - Improved visualization of the offset marker for tractography - Cleaned commented code * ENH: Clean solved TODOs and commented unused navigation code * FIX: Fixed open and save dialogs for navigation purposes * FIX: Reading affine from old projects and replaced affine function form transformation * UPD: Removed surface back culling prints * DEL: Hided tractography panel and removed brain mesh mask
Showing
20 changed files
with
2711 additions
and
1011 deletions
Show diff stats
invesalius/constants.py
@@ -19,6 +19,7 @@ | @@ -19,6 +19,7 @@ | ||
19 | 19 | ||
20 | import os.path | 20 | import os.path |
21 | import platform | 21 | import platform |
22 | +import psutil | ||
22 | import sys | 23 | import sys |
23 | import wx | 24 | import wx |
24 | import itertools | 25 | import itertools |
@@ -521,6 +522,11 @@ ID_GOTO_COORD = wx.NewId() | @@ -521,6 +522,11 @@ ID_GOTO_COORD = wx.NewId() | ||
521 | 522 | ||
522 | ID_MANUAL_WWWL = wx.NewId() | 523 | ID_MANUAL_WWWL = wx.NewId() |
523 | 524 | ||
525 | +# Tractography with Trekker | ||
526 | +ID_TREKKER_MASK = wx.NewId() | ||
527 | +ID_TREKKER_IMG = wx.NewId() | ||
528 | +ID_TREKKER_FOD = wx.NewId() | ||
529 | + | ||
524 | #--------------------------------------------------------- | 530 | #--------------------------------------------------------- |
525 | STATE_DEFAULT = 1000 | 531 | STATE_DEFAULT = 1000 |
526 | STATE_WL = 1001 | 532 | STATE_WL = 1001 |
@@ -545,6 +551,7 @@ SLICE_STATE_REMOVE_MASK_PARTS = 3012 | @@ -545,6 +551,7 @@ SLICE_STATE_REMOVE_MASK_PARTS = 3012 | ||
545 | SLICE_STATE_SELECT_MASK_PARTS = 3013 | 551 | SLICE_STATE_SELECT_MASK_PARTS = 3013 |
546 | SLICE_STATE_FFILL_SEGMENTATION = 3014 | 552 | SLICE_STATE_FFILL_SEGMENTATION = 3014 |
547 | SLICE_STATE_CROP_MASK = 3015 | 553 | SLICE_STATE_CROP_MASK = 3015 |
554 | +SLICE_STATE_TRACTS = 3016 | ||
548 | 555 | ||
549 | VOLUME_STATE_SEED = 2001 | 556 | VOLUME_STATE_SEED = 2001 |
550 | # STATE_LINEAR_MEASURE = 3001 | 557 | # STATE_LINEAR_MEASURE = 3001 |
@@ -559,7 +566,7 @@ TOOL_STATES = [STATE_WL, STATE_SPIN, STATE_ZOOM, | @@ -559,7 +566,7 @@ TOOL_STATES = [STATE_WL, STATE_SPIN, STATE_ZOOM, | ||
559 | 566 | ||
560 | 567 | ||
561 | TOOL_SLICE_STATES = [SLICE_STATE_CROSS, SLICE_STATE_SCROLL, | 568 | TOOL_SLICE_STATES = [SLICE_STATE_CROSS, SLICE_STATE_SCROLL, |
562 | - SLICE_STATE_REORIENT] | 569 | + SLICE_STATE_REORIENT, SLICE_STATE_TRACTS] |
563 | 570 | ||
564 | 571 | ||
565 | SLICE_STYLES = TOOL_STATES + TOOL_SLICE_STATES | 572 | SLICE_STYLES = TOOL_STATES + TOOL_SLICE_STATES |
@@ -651,6 +658,8 @@ BOOLEAN_XOR = 4 | @@ -651,6 +658,8 @@ BOOLEAN_XOR = 4 | ||
651 | 658 | ||
652 | #------------ Navigation defaults ------------------- | 659 | #------------ Navigation defaults ------------------- |
653 | 660 | ||
661 | +MARKER_COLOUR = (1.0, 1.0, 0.) | ||
662 | +MARKER_SIZE = 2 | ||
654 | SELECT = 0 | 663 | SELECT = 0 |
655 | MTC = 1 | 664 | MTC = 1 |
656 | FASTRAK = 2 | 665 | FASTRAK = 2 |
@@ -738,3 +747,17 @@ COIL_COORD_THRESHOLD = 3 | @@ -738,3 +747,17 @@ COIL_COORD_THRESHOLD = 3 | ||
738 | TIMESTAMP = 2.0 | 747 | TIMESTAMP = 2.0 |
739 | 748 | ||
740 | CAM_MODE = True | 749 | CAM_MODE = True |
750 | + | ||
751 | +# Tractography visualization | ||
752 | +N_TRACTS = 100 | ||
753 | +PEEL_DEPTH = 5 | ||
754 | +MAX_PEEL_DEPTH = 30 | ||
755 | +SEED_OFFSET = 15 | ||
756 | +SEED_RADIUS = 1.5 | ||
757 | +SLEEP_NAVIGATION = 0.1 | ||
758 | +BRAIN_OPACITY = 0.5 | ||
759 | +N_CPU = psutil.cpu_count() | ||
760 | +TREKKER_CONFIG = {'seed_max': 1, 'step_size': 0.1, 'min_fod': 0.1, 'probe_quality': 3, | ||
761 | + 'max_interval': 1, 'min_radius_curv': 0.8, 'probe_length': 0.4, | ||
762 | + 'write_interval': 50, 'numb_threads': '', 'max_lenth': 200, | ||
763 | + 'min_lenth': 20, 'max_sampling_step': 100} |
invesalius/control.py
@@ -32,6 +32,7 @@ import invesalius.data.mask as msk | @@ -32,6 +32,7 @@ import invesalius.data.mask as msk | ||
32 | import invesalius.data.measures as measures | 32 | import invesalius.data.measures as measures |
33 | import invesalius.data.slice_ as sl | 33 | import invesalius.data.slice_ as sl |
34 | import invesalius.data.surface as srf | 34 | import invesalius.data.surface as srf |
35 | +import invesalius.data.transformations as tr | ||
35 | import invesalius.data.volume as volume | 36 | import invesalius.data.volume as volume |
36 | import invesalius.gui.dialogs as dialog | 37 | import invesalius.gui.dialogs as dialog |
37 | import invesalius.project as prj | 38 | import invesalius.project as prj |
@@ -57,7 +58,6 @@ class Controller(): | @@ -57,7 +58,6 @@ class Controller(): | ||
57 | def __init__(self, frame): | 58 | def __init__(self, frame): |
58 | self.surface_manager = srf.SurfaceManager() | 59 | self.surface_manager = srf.SurfaceManager() |
59 | self.volume = volume.Volume() | 60 | self.volume = volume.Volume() |
60 | - self.plugin_manager = plugins.PluginManager() | ||
61 | self.__bind_events() | 61 | self.__bind_events() |
62 | self.frame = frame | 62 | self.frame = frame |
63 | self.progress_dialog = None | 63 | self.progress_dialog = None |
@@ -76,12 +76,9 @@ class Controller(): | @@ -76,12 +76,9 @@ class Controller(): | ||
76 | 76 | ||
77 | Publisher.sendMessage('Load Preferences') | 77 | Publisher.sendMessage('Load Preferences') |
78 | 78 | ||
79 | - self.plugin_manager.find_plugins() | ||
80 | - | ||
81 | def __bind_events(self): | 79 | def __bind_events(self): |
82 | Publisher.subscribe(self.OnImportMedicalImages, 'Import directory') | 80 | Publisher.subscribe(self.OnImportMedicalImages, 'Import directory') |
83 | Publisher.subscribe(self.OnImportGroup, 'Import group') | 81 | Publisher.subscribe(self.OnImportGroup, 'Import group') |
84 | - Publisher.subscribe(self.OnImportFolder, 'Import folder') | ||
85 | Publisher.subscribe(self.OnShowDialogImportDirectory, | 82 | Publisher.subscribe(self.OnShowDialogImportDirectory, |
86 | 'Show import directory dialog') | 83 | 'Show import directory dialog') |
87 | Publisher.subscribe(self.OnShowDialogImportOtherFiles, | 84 | Publisher.subscribe(self.OnShowDialogImportOtherFiles, |
@@ -123,8 +120,6 @@ class Controller(): | @@ -123,8 +120,6 @@ class Controller(): | ||
123 | 120 | ||
124 | Publisher.subscribe(self.Send_affine, 'Get affine matrix') | 121 | Publisher.subscribe(self.Send_affine, 'Get affine matrix') |
125 | 122 | ||
126 | - Publisher.subscribe(self.create_project_from_matrix, 'Create project from matrix') | ||
127 | - | ||
128 | def SetBitmapSpacing(self, spacing): | 123 | def SetBitmapSpacing(self, spacing): |
129 | proj = prj.Project() | 124 | proj = prj.Project() |
130 | proj.spacing = spacing | 125 | proj.spacing = spacing |
@@ -336,6 +331,10 @@ class Controller(): | @@ -336,6 +331,10 @@ class Controller(): | ||
336 | 331 | ||
337 | self.Slice.window_level = proj.level | 332 | self.Slice.window_level = proj.level |
338 | self.Slice.window_width = proj.window | 333 | self.Slice.window_width = proj.window |
334 | + if proj.affine: | ||
335 | + self.Slice.affine = np.asarray(proj.affine).reshape(4, 4) | ||
336 | + else: | ||
337 | + self.Slice.affine = None | ||
339 | 338 | ||
340 | Publisher.sendMessage('Update threshold limits list', | 339 | Publisher.sendMessage('Update threshold limits list', |
341 | threshold_range=proj.threshold_range) | 340 | threshold_range=proj.threshold_range) |
@@ -504,32 +503,7 @@ class Controller(): | @@ -504,32 +503,7 @@ class Controller(): | ||
504 | self.LoadProject() | 503 | self.LoadProject() |
505 | Publisher.sendMessage("Enable state project", state=True) | 504 | Publisher.sendMessage("Enable state project", state=True) |
506 | 505 | ||
507 | - def OnImportFolder(self, folder): | ||
508 | - Publisher.sendMessage('Begin busy cursor') | ||
509 | - folder = os.path.abspath(folder) | ||
510 | - | ||
511 | - proj = prj.Project() | ||
512 | - proj.load_from_folder(folder) | ||
513 | - | ||
514 | - self.Slice = sl.Slice() | ||
515 | - self.Slice._open_image_matrix(proj.matrix_filename, | ||
516 | - tuple(proj.matrix_shape), | ||
517 | - proj.matrix_dtype) | ||
518 | - | ||
519 | - self.Slice.window_level = proj.level | ||
520 | - self.Slice.window_width = proj.window | ||
521 | - | ||
522 | - Publisher.sendMessage('Update threshold limits list', | ||
523 | - threshold_range=proj.threshold_range) | ||
524 | - | ||
525 | - session = ses.Session() | ||
526 | - filename = proj.name+".inv3" | ||
527 | - filename = filename.replace("/", "") #Fix problem case other/Skull_DICOM | ||
528 | - dirpath = session.CreateProject(filename) | ||
529 | - self.LoadProject() | ||
530 | - Publisher.sendMessage("Enable state project", state=True) | ||
531 | - | ||
532 | - Publisher.sendMessage('End busy cursor') | 506 | + #------------------------------------------------------------------------------------- |
533 | 507 | ||
534 | def LoadProject(self): | 508 | def LoadProject(self): |
535 | proj = prj.Project() | 509 | proj = prj.Project() |
@@ -688,6 +662,9 @@ class Controller(): | @@ -688,6 +662,9 @@ class Controller(): | ||
688 | proj.matrix_dtype = matrix.dtype.name | 662 | proj.matrix_dtype = matrix.dtype.name |
689 | proj.matrix_filename = matrix_filename | 663 | proj.matrix_filename = matrix_filename |
690 | 664 | ||
665 | + if self.affine is not None: | ||
666 | + proj.affine = self.affine.tolist() | ||
667 | + | ||
691 | # Orientation must be CORONAL in order to as_closes_canonical and | 668 | # Orientation must be CORONAL in order to as_closes_canonical and |
692 | # swap axis in img2memmap to work in a standardized way. | 669 | # swap axis in img2memmap to work in a standardized way. |
693 | # TODO: Create standard import image for all acquisition orientations | 670 | # TODO: Create standard import image for all acquisition orientations |
@@ -700,7 +677,6 @@ class Controller(): | @@ -700,7 +677,6 @@ class Controller(): | ||
700 | proj.level = self.Slice.window_level | 677 | proj.level = self.Slice.window_level |
701 | proj.threshold_range = int(matrix.min()), int(matrix.max()) | 678 | proj.threshold_range = int(matrix.min()), int(matrix.max()) |
702 | proj.spacing = self.Slice.spacing | 679 | proj.spacing = self.Slice.spacing |
703 | - proj.affine = self.affine.tolist() | ||
704 | 680 | ||
705 | ###### | 681 | ###### |
706 | session = ses.Session() | 682 | session = ses.Session() |
@@ -872,11 +848,13 @@ class Controller(): | @@ -872,11 +848,13 @@ class Controller(): | ||
872 | def OnOpenOtherFiles(self, filepath): | 848 | def OnOpenOtherFiles(self, filepath): |
873 | filepath = utils.decode(filepath, const.FS_ENCODE) | 849 | filepath = utils.decode(filepath, const.FS_ENCODE) |
874 | if not(filepath) == None: | 850 | if not(filepath) == None: |
875 | - name = os.path.basename(filepath).split(".")[0] | 851 | + name = filepath.rpartition('\\')[-1].split('.') |
852 | + | ||
876 | group = oth.ReadOthers(filepath) | 853 | group = oth.ReadOthers(filepath) |
854 | + | ||
877 | if group: | 855 | if group: |
878 | matrix, matrix_filename = self.OpenOtherFiles(group) | 856 | matrix, matrix_filename = self.OpenOtherFiles(group) |
879 | - self.CreateOtherProject(name, matrix, matrix_filename) | 857 | + self.CreateOtherProject(str(name[0]), matrix, matrix_filename) |
880 | self.LoadProject() | 858 | self.LoadProject() |
881 | Publisher.sendMessage("Enable state project", state=True) | 859 | Publisher.sendMessage("Enable state project", state=True) |
882 | else: | 860 | else: |
@@ -981,10 +959,10 @@ class Controller(): | @@ -981,10 +959,10 @@ class Controller(): | ||
981 | self.matrix, scalar_range, self.filename = image_utils.img2memmap(group) | 959 | self.matrix, scalar_range, self.filename = image_utils.img2memmap(group) |
982 | 960 | ||
983 | hdr = group.header | 961 | hdr = group.header |
984 | - if group.affine.any(): | ||
985 | - self.affine = group.affine | ||
986 | - Publisher.sendMessage('Update affine matrix', | ||
987 | - affine=self.affine, status=True) | 962 | + # if group.affine.any(): |
963 | + # self.affine = group.affine | ||
964 | + # Publisher.sendMessage('Update affine matrix', | ||
965 | + # affine=self.affine, status=True) | ||
988 | hdr.set_data_dtype('int16') | 966 | hdr.set_data_dtype('int16') |
989 | dims = hdr.get_zooms() | 967 | dims = hdr.get_zooms() |
990 | dimsf = tuple([float(s) for s in dims]) | 968 | dimsf = tuple([float(s) for s in dims]) |
@@ -1000,6 +978,18 @@ class Controller(): | @@ -1000,6 +978,18 @@ class Controller(): | ||
1000 | self.Slice.window_level = wl | 978 | self.Slice.window_level = wl |
1001 | self.Slice.window_width = ww | 979 | self.Slice.window_width = ww |
1002 | 980 | ||
981 | + if group.affine.any(): | ||
982 | + # TODO: replace the inverse of the affine by the actual affine in the whole code | ||
983 | + # remove scaling factor for non-unitary voxel dimensions | ||
984 | + # self.affine = image_utils.world2invspace(affine=group.affine) | ||
985 | + scale, shear, angs, trans, persp = tr.decompose_matrix(group.affine) | ||
986 | + self.affine = np.linalg.inv(tr.compose_matrix(scale=None, shear=shear, | ||
987 | + angles=angs, translate=trans, perspective=persp)) | ||
988 | + # print("repos_img: {}".format(repos_img)) | ||
989 | + self.Slice.affine = self.affine | ||
990 | + Publisher.sendMessage('Update affine matrix', | ||
991 | + affine=self.affine, status=True) | ||
992 | + | ||
1003 | scalar_range = int(scalar_range[0]), int(scalar_range[1]) | 993 | scalar_range = int(scalar_range[0]), int(scalar_range[1]) |
1004 | Publisher.sendMessage('Update threshold limits list', | 994 | Publisher.sendMessage('Update threshold limits list', |
1005 | threshold_range=scalar_range) | 995 | threshold_range=scalar_range) |
@@ -1066,24 +1056,3 @@ class Controller(): | @@ -1066,24 +1056,3 @@ class Controller(): | ||
1066 | 1056 | ||
1067 | def ApplyReorientation(self): | 1057 | def ApplyReorientation(self): |
1068 | self.Slice.apply_reorientation() | 1058 | self.Slice.apply_reorientation() |
1069 | - | ||
1070 | - def start_new_inv_instance(self, image, name, spacing, modality, orientation, window_width, window_level): | ||
1071 | - p = prj.Project() | ||
1072 | - project_folder = tempfile.mkdtemp() | ||
1073 | - p.create_project_file(name, spacing, modality, orientation, window_width, window_level, image, folder=project_folder) | ||
1074 | - err_msg = '' | ||
1075 | - try: | ||
1076 | - sp = subprocess.Popen([sys.executable, sys.argv[0], '--import-folder', project_folder], | ||
1077 | - stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=os.getcwd()) | ||
1078 | - except Exception as err: | ||
1079 | - err_msg = str(err) | ||
1080 | - else: | ||
1081 | - try: | ||
1082 | - if sp.wait(2): | ||
1083 | - err_msg = sp.stderr.read().decode('utf8') | ||
1084 | - sp.terminate() | ||
1085 | - except subprocess.TimeoutExpired: | ||
1086 | - pass | ||
1087 | - | ||
1088 | - if err_msg: | ||
1089 | - dialog.MessageBox(None, "It was not possible to launch new instance of InVesalius3 dsfa dfdsfa sdfas fdsaf asdfasf dsaa", err_msg) |
invesalius/data/bases.py
1 | -from math import sqrt, pi | ||
2 | import numpy as np | 1 | import numpy as np |
3 | import invesalius.data.coordinates as dco | 2 | import invesalius.data.coordinates as dco |
4 | import invesalius.data.transformations as tr | 3 | import invesalius.data.transformations as tr |
@@ -23,8 +22,7 @@ def angle_calculation(ap_axis, coil_axis): | @@ -23,8 +22,7 @@ def angle_calculation(ap_axis, coil_axis): | ||
23 | 22 | ||
24 | def base_creation_old(fiducials): | 23 | def base_creation_old(fiducials): |
25 | """ | 24 | """ |
26 | - Calculate the origin and matrix for coordinate system | ||
27 | - transformation. | 25 | + Calculate the origin and matrix for coordinate system transformation. |
28 | q: origin of coordinate system | 26 | q: origin of coordinate system |
29 | g1, g2, g3: orthogonal vectors of coordinate system | 27 | g1, g2, g3: orthogonal vectors of coordinate system |
30 | 28 | ||
@@ -49,9 +47,9 @@ def base_creation_old(fiducials): | @@ -49,9 +47,9 @@ def base_creation_old(fiducials): | ||
49 | 47 | ||
50 | g3 = np.cross(g2, g1) | 48 | g3 = np.cross(g2, g1) |
51 | 49 | ||
52 | - g1 = g1/sqrt(np.dot(g1, g1)) | ||
53 | - g2 = g2/sqrt(np.dot(g2, g2)) | ||
54 | - g3 = g3/sqrt(np.dot(g3, g3)) | 50 | + g1 = g1/np.sqrt(np.dot(g1, g1)) |
51 | + g2 = g2/np.sqrt(np.dot(g2, g2)) | ||
52 | + g3 = g3/np.sqrt(np.dot(g3, g3)) | ||
55 | 53 | ||
56 | m = np.matrix([[g1[0], g1[1], g1[2]], | 54 | m = np.matrix([[g1[0], g1[1], g1[2]], |
57 | [g2[0], g2[1], g2[2]], | 55 | [g2[0], g2[1], g2[2]], |
@@ -79,7 +77,7 @@ def base_creation(fiducials): | @@ -79,7 +77,7 @@ def base_creation(fiducials): | ||
79 | 77 | ||
80 | sub1 = p2 - p1 | 78 | sub1 = p2 - p1 |
81 | sub2 = p3 - p1 | 79 | sub2 = p3 - p1 |
82 | - lamb = (sub1[0]*sub2[0]+sub1[1]*sub2[1]+sub1[2]*sub2[2])/np.dot(sub1, sub1) | 80 | + lamb = np.dot(sub1, sub2)/np.dot(sub1, sub1) |
83 | 81 | ||
84 | q = p1 + lamb*sub1 | 82 | q = p1 + lamb*sub1 |
85 | g1 = p3 - q | 83 | g1 = p3 - q |
@@ -90,20 +88,52 @@ def base_creation(fiducials): | @@ -90,20 +88,52 @@ def base_creation(fiducials): | ||
90 | 88 | ||
91 | g3 = np.cross(g1, g2) | 89 | g3 = np.cross(g1, g2) |
92 | 90 | ||
93 | - g1 = g1/sqrt(np.dot(g1, g1)) | ||
94 | - g2 = g2/sqrt(np.dot(g2, g2)) | ||
95 | - g3 = g3/sqrt(np.dot(g3, g3)) | ||
96 | - | ||
97 | - m = np.matrix([[g1[0], g2[0], g3[0]], | ||
98 | - [g1[1], g2[1], g3[1]], | ||
99 | - [g1[2], g2[2], g3[2]]]) | ||
100 | - | ||
101 | - m_inv = m.I | ||
102 | - | ||
103 | - return m, q, m_inv | ||
104 | - | ||
105 | - | ||
106 | -def calculate_fre(fiducials, minv, n, q, o): | 91 | + g1 = g1/np.sqrt(np.dot(g1, g1)) |
92 | + g2 = g2/np.sqrt(np.dot(g2, g2)) | ||
93 | + g3 = g3/np.sqrt(np.dot(g3, g3)) | ||
94 | + | ||
95 | + m = np.zeros([3, 3]) | ||
96 | + m[:, 0] = g1/np.sqrt(np.dot(g1, g1)) | ||
97 | + m[:, 1] = g2/np.sqrt(np.dot(g2, g2)) | ||
98 | + m[:, 2] = g3/np.sqrt(np.dot(g3, g3)) | ||
99 | + | ||
100 | + return m, q | ||
101 | + | ||
102 | + | ||
103 | +# def calculate_fre(fiducials, minv, n, q, o): | ||
104 | +# """ | ||
105 | +# Calculate the Fiducial Registration Error for neuronavigation. | ||
106 | +# | ||
107 | +# :param fiducials: array of 6 rows (image and tracker fiducials) and 3 columns (x, y, z) with coordinates | ||
108 | +# :param minv: inverse matrix given by base creation | ||
109 | +# :param n: base change matrix given by base creation | ||
110 | +# :param q: origin of first base | ||
111 | +# :param o: origin of second base | ||
112 | +# :return: float number of fiducial registration error | ||
113 | +# """ | ||
114 | +# | ||
115 | +# img = np.zeros([3, 3]) | ||
116 | +# dist = np.zeros([3, 1]) | ||
117 | +# | ||
118 | +# q1 = np.mat(q).reshape(3, 1) | ||
119 | +# q2 = np.mat(o).reshape(3, 1) | ||
120 | +# | ||
121 | +# p1 = np.mat(fiducials[3, :]).reshape(3, 1) | ||
122 | +# p2 = np.mat(fiducials[4, :]).reshape(3, 1) | ||
123 | +# p3 = np.mat(fiducials[5, :]).reshape(3, 1) | ||
124 | +# | ||
125 | +# img[0, :] = np.asarray((q1 + (minv * n) * (p1 - q2)).reshape(1, 3)) | ||
126 | +# img[1, :] = np.asarray((q1 + (minv * n) * (p2 - q2)).reshape(1, 3)) | ||
127 | +# img[2, :] = np.asarray((q1 + (minv * n) * (p3 - q2)).reshape(1, 3)) | ||
128 | +# | ||
129 | +# dist[0] = np.sqrt(np.sum(np.power((img[0, :] - fiducials[0, :]), 2))) | ||
130 | +# dist[1] = np.sqrt(np.sum(np.power((img[1, :] - fiducials[1, :]), 2))) | ||
131 | +# dist[2] = np.sqrt(np.sum(np.power((img[2, :] - fiducials[2, :]), 2))) | ||
132 | +# | ||
133 | +# return float(np.sqrt(np.sum(dist ** 2) / 3)) | ||
134 | + | ||
135 | + | ||
136 | +def calculate_fre_m(fiducials): | ||
107 | """ | 137 | """ |
108 | Calculate the Fiducial Registration Error for neuronavigation. | 138 | Calculate the Fiducial Registration Error for neuronavigation. |
109 | 139 | ||
@@ -115,19 +145,29 @@ def calculate_fre(fiducials, minv, n, q, o): | @@ -115,19 +145,29 @@ def calculate_fre(fiducials, minv, n, q, o): | ||
115 | :return: float number of fiducial registration error | 145 | :return: float number of fiducial registration error |
116 | """ | 146 | """ |
117 | 147 | ||
148 | + m, q1, minv = base_creation_old(fiducials[:3, :]) | ||
149 | + n, q2, ninv = base_creation_old(fiducials[3:, :]) | ||
150 | + | ||
151 | + # TODO: replace the old by the new base creation | ||
152 | + # the values differ greatly if FRE is computed using the old or new base_creation | ||
153 | + # check the reason for the difference, because they should be the same | ||
154 | + # m, q1 = base_creation(fiducials[:3, :]) | ||
155 | + # n, q2 = base_creation(fiducials[3:, :]) | ||
156 | + # minv = np.linalg.inv(m) | ||
157 | + | ||
118 | img = np.zeros([3, 3]) | 158 | img = np.zeros([3, 3]) |
119 | dist = np.zeros([3, 1]) | 159 | dist = np.zeros([3, 1]) |
120 | 160 | ||
121 | - q1 = np.mat(q).reshape(3, 1) | ||
122 | - q2 = np.mat(o).reshape(3, 1) | 161 | + q1 = q1.reshape(3, 1) |
162 | + q2 = q2.reshape(3, 1) | ||
123 | 163 | ||
124 | - p1 = np.mat(fiducials[3, :]).reshape(3, 1) | ||
125 | - p2 = np.mat(fiducials[4, :]).reshape(3, 1) | ||
126 | - p3 = np.mat(fiducials[5, :]).reshape(3, 1) | 164 | + p1 = fiducials[3, :].reshape(3, 1) |
165 | + p2 = fiducials[4, :].reshape(3, 1) | ||
166 | + p3 = fiducials[5, :].reshape(3, 1) | ||
127 | 167 | ||
128 | - img[0, :] = np.asarray((q1 + (minv * n) * (p1 - q2)).reshape(1, 3)) | ||
129 | - img[1, :] = np.asarray((q1 + (minv * n) * (p2 - q2)).reshape(1, 3)) | ||
130 | - img[2, :] = np.asarray((q1 + (minv * n) * (p3 - q2)).reshape(1, 3)) | 168 | + img[0, :] = (q1 + (minv @ n) * (p1 - q2)).reshape(1, 3) |
169 | + img[1, :] = (q1 + (minv @ n) * (p2 - q2)).reshape(1, 3) | ||
170 | + img[2, :] = (q1 + (minv @ n) * (p3 - q2)).reshape(1, 3) | ||
131 | 171 | ||
132 | dist[0] = np.sqrt(np.sum(np.power((img[0, :] - fiducials[0, :]), 2))) | 172 | dist[0] = np.sqrt(np.sum(np.power((img[0, :] - fiducials[0, :]), 2))) |
133 | dist[1] = np.sqrt(np.sum(np.power((img[1, :] - fiducials[1, :]), 2))) | 173 | dist[1] = np.sqrt(np.sum(np.power((img[1, :] - fiducials[1, :]), 2))) |
@@ -136,44 +176,68 @@ def calculate_fre(fiducials, minv, n, q, o): | @@ -136,44 +176,68 @@ def calculate_fre(fiducials, minv, n, q, o): | ||
136 | return float(np.sqrt(np.sum(dist ** 2) / 3)) | 176 | return float(np.sqrt(np.sum(dist ** 2) / 3)) |
137 | 177 | ||
138 | 178 | ||
139 | -def flip_x(point): | ||
140 | - """ | ||
141 | - Flip coordinates of a vector according to X axis | ||
142 | - Coronal Images do not require this transformation - 1 tested | ||
143 | - and for this case, at navigation, the z axis is inverted | ||
144 | - | ||
145 | - It's necessary to multiply the z coordinate by (-1). Possibly | ||
146 | - because the origin of coordinate system of imagedata is | ||
147 | - located in superior left corner and the origin of VTK scene coordinate | ||
148 | - system (polygonal surface) is in the interior left corner. Second | ||
149 | - possibility is the order of slice stacking | ||
150 | - | ||
151 | - :param point: list of coordinates x, y and z | ||
152 | - :return: flipped coordinates | ||
153 | - """ | ||
154 | - | ||
155 | - # TODO: check if the Flip function is related to the X or Y axis | ||
156 | - | ||
157 | - point = np.matrix(point + (0,)) | ||
158 | - point[0, 2] = -point[0, 2] | ||
159 | - | ||
160 | - m_rot = np.matrix([[1.0, 0.0, 0.0, 0.0], | ||
161 | - [0.0, -1.0, 0.0, 0.0], | ||
162 | - [0.0, 0.0, -1.0, 0.0], | ||
163 | - [0.0, 0.0, 0.0, 1.0]]) | ||
164 | - m_trans = np.matrix([[1.0, 0, 0, -point[0, 0]], | ||
165 | - [0.0, 1.0, 0, -point[0, 1]], | ||
166 | - [0.0, 0.0, 1.0, -point[0, 2]], | ||
167 | - [0.0, 0.0, 0.0, 1.0]]) | ||
168 | - m_trans_return = np.matrix([[1.0, 0, 0, point[0, 0]], | ||
169 | - [0.0, 1.0, 0, point[0, 1]], | ||
170 | - [0.0, 0.0, 1.0, point[0, 2]], | ||
171 | - [0.0, 0.0, 0.0, 1.0]]) | ||
172 | - | ||
173 | - point_rot = point*m_trans*m_rot*m_trans_return | ||
174 | - x, y, z = point_rot.tolist()[0][:3] | ||
175 | - | ||
176 | - return x, y, z | 179 | +# def flip_x(point): |
180 | +# """ | ||
181 | +# Flip coordinates of a vector according to X axis | ||
182 | +# Coronal Images do not require this transformation - 1 tested | ||
183 | +# and for this case, at navigation, the z axis is inverted | ||
184 | +# | ||
185 | +# It's necessary to multiply the z coordinate by (-1). Possibly | ||
186 | +# because the origin of coordinate system of imagedata is | ||
187 | +# located in superior left corner and the origin of VTK scene coordinate | ||
188 | +# system (polygonal surface) is in the interior left corner. Second | ||
189 | +# possibility is the order of slice stacking | ||
190 | +# | ||
191 | +# :param point: list of coordinates x, y and z | ||
192 | +# :return: flipped coordinates | ||
193 | +# """ | ||
194 | +# | ||
195 | +# # TODO: check if the Flip function is related to the X or Y axis | ||
196 | +# | ||
197 | +# point = np.matrix(point + (0,)) | ||
198 | +# point[0, 2] = -point[0, 2] | ||
199 | +# | ||
200 | +# m_rot = np.matrix([[1.0, 0.0, 0.0, 0.0], | ||
201 | +# [0.0, -1.0, 0.0, 0.0], | ||
202 | +# [0.0, 0.0, -1.0, 0.0], | ||
203 | +# [0.0, 0.0, 0.0, 1.0]]) | ||
204 | +# m_trans = np.matrix([[1.0, 0, 0, -point[0, 0]], | ||
205 | +# [0.0, 1.0, 0, -point[0, 1]], | ||
206 | +# [0.0, 0.0, 1.0, -point[0, 2]], | ||
207 | +# [0.0, 0.0, 0.0, 1.0]]) | ||
208 | +# m_trans_return = np.matrix([[1.0, 0, 0, point[0, 0]], | ||
209 | +# [0.0, 1.0, 0, point[0, 1]], | ||
210 | +# [0.0, 0.0, 1.0, point[0, 2]], | ||
211 | +# [0.0, 0.0, 0.0, 1.0]]) | ||
212 | +# | ||
213 | +# point_rot = point*m_trans*m_rot*m_trans_return | ||
214 | +# x, y, z = point_rot.tolist()[0][:3] | ||
215 | +# | ||
216 | +# return x, y, z | ||
217 | + | ||
218 | + | ||
219 | +# def flip_x_m(point): | ||
220 | +# """ | ||
221 | +# Rotate coordinates of a vector by pi around X axis in static reference frame. | ||
222 | +# | ||
223 | +# InVesalius also require to multiply the z coordinate by (-1). Possibly | ||
224 | +# because the origin of coordinate system of imagedata is | ||
225 | +# located in superior left corner and the origin of VTK scene coordinate | ||
226 | +# system (polygonal surface) is in the interior left corner. Second | ||
227 | +# possibility is the order of slice stacking | ||
228 | +# | ||
229 | +# :param point: list of coordinates x, y and z | ||
230 | +# :return: rotated coordinates | ||
231 | +# """ | ||
232 | +# | ||
233 | +# point_4 = np.hstack((point, 1.)).reshape([4, 1]) | ||
234 | +# point_4[2, 0] = -point_4[2, 0] | ||
235 | +# | ||
236 | +# m_rot = tr.euler_matrix(pi, 0, 0) | ||
237 | +# | ||
238 | +# point_rot = m_rot @ point_4 | ||
239 | +# | ||
240 | +# return point_rot[0, 0], point_rot[1, 0], point_rot[2, 0] | ||
177 | 241 | ||
178 | 242 | ||
179 | def flip_x_m(point): | 243 | def flip_x_m(point): |
@@ -190,14 +254,14 @@ def flip_x_m(point): | @@ -190,14 +254,14 @@ def flip_x_m(point): | ||
190 | :return: rotated coordinates | 254 | :return: rotated coordinates |
191 | """ | 255 | """ |
192 | 256 | ||
193 | - point_4 = np.hstack((point, 1.)).reshape([4, 1]) | 257 | + point_4 = np.hstack((point, 1.)).reshape(4, 1) |
194 | point_4[2, 0] = -point_4[2, 0] | 258 | point_4[2, 0] = -point_4[2, 0] |
195 | 259 | ||
196 | - m_rot = np.asmatrix(tr.euler_matrix(pi, 0, 0)) | 260 | + m_rot = tr.euler_matrix(np.pi, 0, 0) |
197 | 261 | ||
198 | - point_rot = m_rot*point_4 | 262 | + point_rot = m_rot @ point_4 |
199 | 263 | ||
200 | - return point_rot[0, 0], point_rot[1, 0], point_rot[2, 0] | 264 | + return point_rot |
201 | 265 | ||
202 | 266 | ||
203 | def object_registration(fiducials, orients, coord_raw, m_change): | 267 | def object_registration(fiducials, orients, coord_raw, m_change): |
@@ -224,48 +288,48 @@ def object_registration(fiducials, orients, coord_raw, m_change): | @@ -224,48 +288,48 @@ def object_registration(fiducials, orients, coord_raw, m_change): | ||
224 | fids_raw[ic, :] = dco.dynamic_reference_m2(coords[ic, :], coords[3, :])[:3] | 288 | fids_raw[ic, :] = dco.dynamic_reference_m2(coords[ic, :], coords[3, :])[:3] |
225 | 289 | ||
226 | # compute initial alignment of probe fixed in the object in source frame | 290 | # compute initial alignment of probe fixed in the object in source frame |
227 | - t_s0_raw = np.asmatrix(tr.translation_matrix(coords[3, :3])) | ||
228 | - r_s0_raw = np.asmatrix(tr.euler_matrix(np.radians(coords[3, 3]), np.radians(coords[3, 4]), | ||
229 | - np.radians(coords[3, 5]), 'rzyx')) | ||
230 | - s0_raw = np.asmatrix(tr.concatenate_matrices(t_s0_raw, r_s0_raw)) | 291 | + t_s0_raw = tr.translation_matrix(coords[3, :3]) |
292 | + r_s0_raw = tr.euler_matrix(np.radians(coords[3, 3]), np.radians(coords[3, 4]), | ||
293 | + np.radians(coords[3, 5]), 'rzyx') | ||
294 | + s0_raw = tr.concatenate_matrices(t_s0_raw, r_s0_raw) | ||
231 | 295 | ||
232 | # compute change of basis for object fiducials in source frame | 296 | # compute change of basis for object fiducials in source frame |
233 | - base_obj_raw, q_obj_raw, base_inv_obj_raw = base_creation(fids_raw[:3, :3]) | ||
234 | - r_obj_raw = np.asmatrix(np.identity(4)) | 297 | + base_obj_raw, q_obj_raw = base_creation(fids_raw[:3, :3]) |
298 | + r_obj_raw = np.identity(4) | ||
235 | r_obj_raw[:3, :3] = base_obj_raw[:3, :3] | 299 | r_obj_raw[:3, :3] = base_obj_raw[:3, :3] |
236 | - t_obj_raw = np.asmatrix(tr.translation_matrix(q_obj_raw)) | ||
237 | - m_obj_raw = np.asmatrix(tr.concatenate_matrices(t_obj_raw, r_obj_raw)) | 300 | + t_obj_raw = tr.translation_matrix(q_obj_raw) |
301 | + m_obj_raw = tr.concatenate_matrices(t_obj_raw, r_obj_raw) | ||
238 | 302 | ||
239 | for ic in range(0, 4): | 303 | for ic in range(0, 4): |
240 | if coord_raw.any(): | 304 | if coord_raw.any(): |
241 | # compute object fiducials in reference frame | 305 | # compute object fiducials in reference frame |
242 | fids_dyn[ic, :] = dco.dynamic_reference_m2(coords[ic, :], coord_raw[1, :]) | 306 | fids_dyn[ic, :] = dco.dynamic_reference_m2(coords[ic, :], coord_raw[1, :]) |
243 | - fids_dyn[ic, 2] = -fids_dyn[ic, 2] | ||
244 | else: | 307 | else: |
245 | # compute object fiducials in source frame | 308 | # compute object fiducials in source frame |
246 | fids_dyn[ic, :] = coords[ic, :] | 309 | fids_dyn[ic, :] = coords[ic, :] |
310 | + fids_dyn[ic, 2] = -fids_dyn[ic, 2] | ||
247 | 311 | ||
248 | # compute object fiducials in vtk head frame | 312 | # compute object fiducials in vtk head frame |
249 | a, b, g = np.radians(fids_dyn[ic, 3:]) | 313 | a, b, g = np.radians(fids_dyn[ic, 3:]) |
250 | T_p = tr.translation_matrix(fids_dyn[ic, :3]) | 314 | T_p = tr.translation_matrix(fids_dyn[ic, :3]) |
251 | R_p = tr.euler_matrix(a, b, g, 'rzyx') | 315 | R_p = tr.euler_matrix(a, b, g, 'rzyx') |
252 | - M_p = np.asmatrix(tr.concatenate_matrices(T_p, R_p)) | ||
253 | - M_img = np.asmatrix(m_change) * M_p | 316 | + M_p = tr.concatenate_matrices(T_p, R_p) |
317 | + M_img = m_change @ M_p | ||
254 | 318 | ||
255 | angles_img = np.degrees(np.asarray(tr.euler_from_matrix(M_img, 'rzyx'))) | 319 | angles_img = np.degrees(np.asarray(tr.euler_from_matrix(M_img, 'rzyx'))) |
256 | - coord_img = np.asarray(flip_x_m(tr.translation_from_matrix(M_img))) | 320 | + coord_img = flip_x_m(tr.translation_from_matrix(M_img)) |
257 | 321 | ||
258 | - fids_img[ic, :] = np.hstack((coord_img, angles_img)) | 322 | + fids_img[ic, :] = np.hstack((coord_img[:3, 0], angles_img)) |
259 | 323 | ||
260 | # compute object base change in vtk head frame | 324 | # compute object base change in vtk head frame |
261 | - base_obj_img, q_obj_img, base_inv_obj_img = base_creation(fids_img[:3, :3]) | ||
262 | - r_obj_img = np.asmatrix(np.identity(4)) | 325 | + base_obj_img, _ = base_creation(fids_img[:3, :3]) |
326 | + r_obj_img = np.identity(4) | ||
263 | r_obj_img[:3, :3] = base_obj_img[:3, :3] | 327 | r_obj_img[:3, :3] = base_obj_img[:3, :3] |
264 | 328 | ||
265 | # compute initial alignment of probe fixed in the object in reference (or static) frame | 329 | # compute initial alignment of probe fixed in the object in reference (or static) frame |
266 | - s0_trans_dyn = np.asmatrix(tr.translation_matrix(fids_dyn[3, :3])) | ||
267 | - s0_rot_dyn = np.asmatrix(tr.euler_matrix(np.radians(fids_dyn[3, 3]), np.radians(fids_dyn[3, 4]), | ||
268 | - np.radians(fids_dyn[3, 5]), 'rzyx')) | ||
269 | - s0_dyn = np.asmatrix(tr.concatenate_matrices(s0_trans_dyn, s0_rot_dyn)) | 330 | + s0_trans_dyn = tr.translation_matrix(fids_dyn[3, :3]) |
331 | + s0_rot_dyn = tr.euler_matrix(np.radians(fids_dyn[3, 3]), np.radians(fids_dyn[3, 4]), | ||
332 | + np.radians(fids_dyn[3, 5]), 'rzyx') | ||
333 | + s0_dyn = tr.concatenate_matrices(s0_trans_dyn, s0_rot_dyn) | ||
270 | 334 | ||
271 | return t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img | 335 | return t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img |
invesalius/data/coordinates.py
@@ -20,6 +20,7 @@ | @@ -20,6 +20,7 @@ | ||
20 | from math import sin, cos | 20 | from math import sin, cos |
21 | import numpy as np | 21 | import numpy as np |
22 | 22 | ||
23 | +import invesalius.data.bases as db | ||
23 | import invesalius.data.transformations as tr | 24 | import invesalius.data.transformations as tr |
24 | import invesalius.constants as const | 25 | import invesalius.constants as const |
25 | 26 | ||
@@ -74,7 +75,7 @@ def PolarisCoord(trck_init, trck_id, ref_mode): | @@ -74,7 +75,7 @@ def PolarisCoord(trck_init, trck_id, ref_mode): | ||
74 | coord3 = np.hstack((trans_obj, angles_obj)) | 75 | coord3 = np.hstack((trans_obj, angles_obj)) |
75 | 76 | ||
76 | coord = np.vstack([coord1, coord2, coord3]) | 77 | coord = np.vstack([coord1, coord2, coord3]) |
77 | - Publisher.sendMessage('Sensors ID', probe_id=trck.probeID, ref_id=trck.refID, obj_id=trck.objID) | 78 | + # Publisher.sendMessage('Sensors ID', probe_id=trck.probeID, ref_id=trck.refID, obj_id=trck.objID) |
78 | 79 | ||
79 | return coord | 80 | return coord |
80 | 81 | ||
@@ -232,19 +233,45 @@ def DebugCoord(trk_init, trck_id, ref_mode): | @@ -232,19 +233,45 @@ def DebugCoord(trk_init, trck_id, ref_mode): | ||
232 | :return: six coordinates x, y, z, alfa, beta and gama | 233 | :return: six coordinates x, y, z, alfa, beta and gama |
233 | """ | 234 | """ |
234 | 235 | ||
235 | - sleep(0.05) | 236 | + # Started to take a more reasonable, limited random coordinate generator based on |
237 | + # the collected fiducials, but it is more complicated than this. It should account for the | ||
238 | + # dynamic reference computation | ||
239 | + # if trk_init: | ||
240 | + # fiducials = trk_init[3:, :] | ||
241 | + # fids_max = fiducials.max(axis=0) | ||
242 | + # fids_min = fiducials.min(axis=0) | ||
243 | + # fids_lim = np.hstack((fids_min[np.newaxis, :].T, fids_max[np.newaxis, :].T)) | ||
244 | + # | ||
245 | + # dx = fids_max[] | ||
246 | + # dt = [-180, 180] | ||
247 | + # | ||
248 | + # else: | ||
236 | 249 | ||
237 | - coord1 = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), | ||
238 | - uniform(-180.0, 180.0), uniform(-180.0, 180.0), uniform(-180.0, 180.0)]) | 250 | + dx = [-70, 70] |
251 | + dt = [-180, 180] | ||
239 | 252 | ||
240 | - coord2 = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), | ||
241 | - uniform(-180.0, 180.0), uniform(-180.0, 180.0), uniform(-180.0, 180.0)]) | 253 | + coord1 = np.array([uniform(*dx), uniform(*dx), uniform(*dx), |
254 | + uniform(*dt), uniform(*dt), uniform(*dt)]) | ||
255 | + coord2 = np.array([uniform(*dx), uniform(*dx), uniform(*dx), | ||
256 | + uniform(*dt), uniform(*dt), uniform(*dt)]) | ||
257 | + coord3 = np.array([uniform(*dx), uniform(*dx), uniform(*dx), | ||
258 | + uniform(*dt), uniform(*dt), uniform(*dt)]) | ||
259 | + coord4 = np.array([uniform(*dx), uniform(*dx), uniform(*dx), | ||
260 | + uniform(*dt), uniform(*dt), uniform(*dt)]) | ||
242 | 261 | ||
243 | - coord3 = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), | ||
244 | - uniform(-180.0, 180.0), uniform(-180.0, 180.0), uniform(-180.0, 180.0)]) | 262 | + sleep(0.05) |
245 | 263 | ||
246 | - coord4 = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), | ||
247 | - uniform(-180.0, 180.0), uniform(-180.0, 180.0), uniform(-180.0, 180.0)]) | 264 | + # coord1 = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), |
265 | + # uniform(-180.0, 180.0), uniform(-180.0, 180.0), uniform(-180.0, 180.0)]) | ||
266 | + # | ||
267 | + # coord2 = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), | ||
268 | + # uniform(-180.0, 180.0), uniform(-180.0, 180.0), uniform(-180.0, 180.0)]) | ||
269 | + # | ||
270 | + # coord3 = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), | ||
271 | + # uniform(-180.0, 180.0), uniform(-180.0, 180.0), uniform(-180.0, 180.0)]) | ||
272 | + # | ||
273 | + # coord4 = np.array([uniform(1, 200), uniform(1, 200), uniform(1, 200), | ||
274 | + # uniform(-180.0, 180.0), uniform(-180.0, 180.0), uniform(-180.0, 180.0)]) | ||
248 | 275 | ||
249 | Publisher.sendMessage('Sensors ID', probe_id=int(uniform(0, 5)), ref_id=int(uniform(0, 5)), obj_id=int(uniform(0, 5))) | 276 | Publisher.sendMessage('Sensors ID', probe_id=int(uniform(0, 5)), ref_id=int(uniform(0, 5)), obj_id=int(uniform(0, 5))) |
250 | 277 | ||
@@ -297,19 +324,20 @@ def dynamic_reference_m(probe, reference): | @@ -297,19 +324,20 @@ def dynamic_reference_m(probe, reference): | ||
297 | :param reference: sensor two defined as reference | 324 | :param reference: sensor two defined as reference |
298 | :return: rotated and translated coordinates | 325 | :return: rotated and translated coordinates |
299 | """ | 326 | """ |
300 | - | ||
301 | a, b, g = np.radians(reference[3:6]) | 327 | a, b, g = np.radians(reference[3:6]) |
302 | 328 | ||
303 | - T = tr.translation_matrix(reference[:3]) | ||
304 | - R = tr.euler_matrix(a, b, g, 'rzyx') | ||
305 | - M = np.asmatrix(tr.concatenate_matrices(T, R)) | ||
306 | - # M = tr.compose_matrix(angles=np.radians(reference[3:6]), translate=reference[:3]) | ||
307 | - # print M | ||
308 | - probe_4 = np.vstack((np.asmatrix(probe[:3]).reshape([3, 1]), 1.)) | ||
309 | - coord_rot = M.I * probe_4 | ||
310 | - coord_rot = np.squeeze(np.asarray(coord_rot)) | 329 | + trans = tr.translation_matrix(reference[:3]) |
330 | + rot = tr.euler_matrix(a, b, g, 'rzyx') | ||
331 | + affine = tr.concatenate_matrices(trans, rot) | ||
332 | + probe_4 = np.vstack((probe[:3].reshape([3, 1]), 1.)) | ||
333 | + coord_rot = np.linalg.inv(affine) @ probe_4 | ||
334 | + # minus sign to the z coordinate | ||
335 | + coord_rot[2, 0] = -coord_rot[2, 0] | ||
336 | + coord_rot = coord_rot[:3, 0].tolist() | ||
337 | + coord_rot.extend(probe[3:]) | ||
338 | + | ||
339 | + return coord_rot | ||
311 | 340 | ||
312 | - return coord_rot[0], coord_rot[1], -coord_rot[2], probe[3], probe[4], probe[5] | ||
313 | 341 | ||
314 | def dynamic_reference_m2(probe, reference): | 342 | def dynamic_reference_m2(probe, reference): |
315 | """ | 343 | """ |
@@ -331,70 +359,18 @@ def dynamic_reference_m2(probe, reference): | @@ -331,70 +359,18 @@ def dynamic_reference_m2(probe, reference): | ||
331 | T_p = tr.translation_matrix(probe[:3]) | 359 | T_p = tr.translation_matrix(probe[:3]) |
332 | R = tr.euler_matrix(a, b, g, 'rzyx') | 360 | R = tr.euler_matrix(a, b, g, 'rzyx') |
333 | R_p = tr.euler_matrix(a_p, b_p, g_p, 'rzyx') | 361 | R_p = tr.euler_matrix(a_p, b_p, g_p, 'rzyx') |
334 | - M = np.asmatrix(tr.concatenate_matrices(T, R)) | ||
335 | - M_p = np.asmatrix(tr.concatenate_matrices(T_p, R_p)) | ||
336 | - # M = tr.compose_matrix(angles=np.radians(reference[3:6]), translate=reference[:3]) | ||
337 | - # print M | 362 | + M = tr.concatenate_matrices(T, R) |
363 | + M_p = tr.concatenate_matrices(T_p, R_p) | ||
338 | 364 | ||
339 | - M_dyn = M.I * M_p | 365 | + M_dyn = np.linalg.inv(M) @ M_p |
340 | 366 | ||
341 | al, be, ga = tr.euler_from_matrix(M_dyn, 'rzyx') | 367 | al, be, ga = tr.euler_from_matrix(M_dyn, 'rzyx') |
342 | coord_rot = tr.translation_from_matrix(M_dyn) | 368 | coord_rot = tr.translation_from_matrix(M_dyn) |
343 | 369 | ||
344 | coord_rot = np.squeeze(coord_rot) | 370 | coord_rot = np.squeeze(coord_rot) |
345 | 371 | ||
346 | - # probe_4 = np.vstack((np.asmatrix(probe[:3]).reshape([3, 1]), 1.)) | ||
347 | - # coord_rot_test = M.I * probe_4 | ||
348 | - # coord_rot_test = np.squeeze(np.asarray(coord_rot_test)) | ||
349 | - # | ||
350 | - # print "coord_rot: ", coord_rot | ||
351 | - # print "coord_rot_test: ", coord_rot_test | ||
352 | - # print "test: ", np.allclose(coord_rot, coord_rot_test[:3]) | ||
353 | - | ||
354 | return coord_rot[0], coord_rot[1], coord_rot[2], np.degrees(al), np.degrees(be), np.degrees(ga) | 372 | return coord_rot[0], coord_rot[1], coord_rot[2], np.degrees(al), np.degrees(be), np.degrees(ga) |
355 | 373 | ||
356 | -# def dynamic_reference_m3(probe, reference): | ||
357 | -# """ | ||
358 | -# Apply dynamic reference correction to probe coordinates. Uses the alpha, beta and gama | ||
359 | -# rotation angles of reference to rotate the probe coordinate and returns the x, y, z | ||
360 | -# difference between probe and reference. Angles sequences and equation was extracted from | ||
361 | -# Polhemus manual and Attitude matrix in Wikipedia. | ||
362 | -# General equation is: | ||
363 | -# coord = Mrot * (probe - reference) | ||
364 | -# :param probe: sensor one defined as probe | ||
365 | -# :param reference: sensor two defined as reference | ||
366 | -# :return: rotated and translated coordinates | ||
367 | -# """ | ||
368 | -# | ||
369 | -# a, b, g = np.radians(reference[3:6]) | ||
370 | -# a_p, b_p, g_p = np.radians(probe[3:6]) | ||
371 | -# | ||
372 | -# T = tr.translation_matrix(reference[:3]) | ||
373 | -# T_p = tr.translation_matrix(probe[:3]) | ||
374 | -# R = tr.euler_matrix(a, b, g, 'rzyx') | ||
375 | -# R_p = tr.euler_matrix(a_p, b_p, g_p, 'rzyx') | ||
376 | -# M = np.asmatrix(tr.concatenate_matrices(T, R)) | ||
377 | -# M_p = np.asmatrix(tr.concatenate_matrices(T_p, R_p)) | ||
378 | -# # M = tr.compose_matrix(angles=np.radians(reference[3:6]), translate=reference[:3]) | ||
379 | -# # print M | ||
380 | -# | ||
381 | -# M_dyn = M.I * M_p | ||
382 | -# | ||
383 | -# # al, be, ga = tr.euler_from_matrix(M_dyn, 'rzyx') | ||
384 | -# # coord_rot = tr.translation_from_matrix(M_dyn) | ||
385 | -# # | ||
386 | -# # coord_rot = np.squeeze(coord_rot) | ||
387 | -# | ||
388 | -# # probe_4 = np.vstack((np.asmatrix(probe[:3]).reshape([3, 1]), 1.)) | ||
389 | -# # coord_rot_test = M.I * probe_4 | ||
390 | -# # coord_rot_test = np.squeeze(np.asarray(coord_rot_test)) | ||
391 | -# # | ||
392 | -# # print "coord_rot: ", coord_rot | ||
393 | -# # print "coord_rot_test: ", coord_rot_test | ||
394 | -# # print "test: ", np.allclose(coord_rot, coord_rot_test[:3]) | ||
395 | -# | ||
396 | -# return M_dyn | ||
397 | - | ||
398 | 374 | ||
399 | def str2float(data): | 375 | def str2float(data): |
400 | """ | 376 | """ |
@@ -414,3 +390,15 @@ def str2float(data): | @@ -414,3 +390,15 @@ def str2float(data): | ||
414 | data = [float(s) for s in data[1:len(data)]] | 390 | data = [float(s) for s in data[1:len(data)]] |
415 | 391 | ||
416 | return data | 392 | return data |
393 | + | ||
394 | + | ||
395 | +def offset_coordinate(p_old, norm_vec, offset): | ||
396 | + """ | ||
397 | + Translate the coordinates of a point along a vector | ||
398 | + :param p_old: (x, y, z) array with current point coordinates | ||
399 | + :param norm_vec: (vx, vy, vz) array with normal vector coordinates | ||
400 | + :param offset: double representing the magnitude of offset | ||
401 | + :return: (x_new, y_new, z_new) array of offset coordinates | ||
402 | + """ | ||
403 | + p_offset = p_old - offset * norm_vec | ||
404 | + return p_offset |
invesalius/data/coregistration.py
@@ -17,347 +17,620 @@ | @@ -17,347 +17,620 @@ | ||
17 | # detalhes. | 17 | # detalhes. |
18 | #-------------------------------------------------------------------------- | 18 | #-------------------------------------------------------------------------- |
19 | 19 | ||
20 | +import numpy as np | ||
21 | +import queue | ||
20 | import threading | 22 | import threading |
21 | from time import sleep | 23 | from time import sleep |
22 | 24 | ||
23 | -from numpy import asmatrix, mat, degrees, radians, identity | ||
24 | -import wx | ||
25 | -from pubsub import pub as Publisher | ||
26 | - | ||
27 | import invesalius.data.coordinates as dco | 25 | import invesalius.data.coordinates as dco |
28 | import invesalius.data.transformations as tr | 26 | import invesalius.data.transformations as tr |
29 | 27 | ||
30 | -# TODO: Optimize navigation thread. Remove the infinite loop and optimize sleep. | ||
31 | - | ||
32 | 28 | ||
33 | -class CoregistrationStatic(threading.Thread): | ||
34 | - """ | ||
35 | - Thread to update the coordinates with the fiducial points | ||
36 | - co-registration method while the Navigation Button is pressed. | ||
37 | - Sleep function in run method is used to avoid blocking GUI and | ||
38 | - for better real-time navigation | 29 | +# TODO: Replace the use of degrees by radians in every part of the navigation pipeline |
30 | + | ||
31 | +def object_marker_to_center(coord_raw, obj_ref_mode, t_obj_raw, s0_raw, r_s0_raw): | ||
32 | + """Translate and rotate the raw coordinate given by the tracking device to the reference system created during | ||
33 | + the object registration. | ||
34 | + | ||
35 | + :param coord_raw: Coordinates returned by the tracking device | ||
36 | + :type coord_raw: numpy.ndarray | ||
37 | + :param obj_ref_mode: | ||
38 | + :type obj_ref_mode: int | ||
39 | + :param t_obj_raw: | ||
40 | + :type t_obj_raw: numpy.ndarray | ||
41 | + :param s0_raw: | ||
42 | + :type s0_raw: numpy.ndarray | ||
43 | + :param r_s0_raw: rotation transformation from marker to object basis | ||
44 | + :type r_s0_raw: numpy.ndarray | ||
45 | + :return: 4 x 4 numpy double array | ||
46 | + :rtype: numpy.ndarray | ||
39 | """ | 47 | """ |
40 | 48 | ||
41 | - def __init__(self, coreg_data, nav_id, trck_info): | ||
42 | - threading.Thread.__init__(self) | ||
43 | - self.coreg_data = coreg_data | ||
44 | - self.nav_id = nav_id | ||
45 | - self.trck_info = trck_info | ||
46 | - self._pause_ = False | ||
47 | - self.start() | ||
48 | - | ||
49 | - def stop(self): | ||
50 | - self._pause_ = True | ||
51 | - | ||
52 | - def run(self): | ||
53 | - # m_change = self.coreg_data[0] | ||
54 | - # obj_ref_mode = self.coreg_data[2] | ||
55 | - # | ||
56 | - # trck_init = self.trck_info[0] | ||
57 | - # trck_id = self.trck_info[1] | ||
58 | - # trck_mode = self.trck_info[2] | ||
59 | - | ||
60 | - m_change, obj_ref_mode = self.coreg_data | ||
61 | - trck_init, trck_id, trck_mode = self.trck_info | ||
62 | - | ||
63 | - while self.nav_id: | ||
64 | - coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
65 | - | ||
66 | - psi, theta, phi = coord_raw[obj_ref_mode, 3:] | ||
67 | - t_probe_raw = asmatrix(tr.translation_matrix(coord_raw[obj_ref_mode, :3])) | 49 | + as1, bs1, gs1 = np.radians(coord_raw[obj_ref_mode, 3:]) |
50 | + r_probe = tr.euler_matrix(as1, bs1, gs1, 'rzyx') | ||
51 | + t_probe_raw = tr.translation_matrix(coord_raw[obj_ref_mode, :3]) | ||
52 | + t_offset_aux = np.linalg.inv(r_s0_raw) @ r_probe @ t_obj_raw | ||
53 | + t_offset = np.identity(4) | ||
54 | + t_offset[:, -1] = t_offset_aux[:, -1] | ||
55 | + t_probe = s0_raw @ t_offset @ np.linalg.inv(s0_raw) @ t_probe_raw | ||
56 | + m_probe = tr.concatenate_matrices(t_probe, r_probe) | ||
68 | 57 | ||
69 | - t_probe_raw[2, -1] = -t_probe_raw[2, -1] | 58 | + return m_probe |
70 | 59 | ||
71 | - m_img = m_change * t_probe_raw | ||
72 | 60 | ||
73 | - coord = m_img[0, -1], m_img[1, -1], m_img[2, -1], psi, theta, phi | 61 | +def object_to_reference(coord_raw, m_probe): |
62 | + """Compute affine transformation matrix to the reference basis | ||
74 | 63 | ||
75 | - wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=m_img, position=coord) | ||
76 | - | ||
77 | - # TODO: Optimize the value of sleep for each tracking device. | ||
78 | - sleep(0.175) | ||
79 | - | ||
80 | - if self._pause_: | ||
81 | - return | ||
82 | - | ||
83 | - | ||
84 | -class CoregistrationDynamic(threading.Thread): | ||
85 | - """ | ||
86 | - Thread to update the coordinates with the fiducial points | ||
87 | - co-registration method while the Navigation Button is pressed. | ||
88 | - Sleep function in run method is used to avoid blocking GUI and | ||
89 | - for better real-time navigation | 64 | + :param coord_raw: Coordinates returned by the tracking device |
65 | + :type coord_raw: numpy.ndarray | ||
66 | + :param m_probe: Probe coordinates | ||
67 | + :type m_probe: numpy.ndarray | ||
68 | + :return: 4 x 4 numpy double array | ||
69 | + :rtype: numpy.ndarray | ||
90 | """ | 70 | """ |
91 | 71 | ||
92 | - def __init__(self, coreg_data, nav_id, trck_info): | ||
93 | - threading.Thread.__init__(self) | ||
94 | - self.coreg_data = coreg_data | ||
95 | - self.nav_id = nav_id | ||
96 | - self.trck_info = trck_info | ||
97 | - self._pause_ = False | ||
98 | - self.start() | ||
99 | - | ||
100 | - def stop(self): | ||
101 | - self._pause_ = True | ||
102 | - | ||
103 | - def run(self): | ||
104 | - m_change, obj_ref_mode = self.coreg_data | ||
105 | - trck_init, trck_id, trck_mode = self.trck_info | ||
106 | - | ||
107 | - while self.nav_id: | ||
108 | - coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
109 | - | ||
110 | - psi, theta, phi = radians(coord_raw[obj_ref_mode, 3:]) | ||
111 | - r_probe = tr.euler_matrix(psi, theta, phi, 'rzyx') | ||
112 | - t_probe = tr.translation_matrix(coord_raw[obj_ref_mode, :3]) | ||
113 | - m_probe = asmatrix(tr.concatenate_matrices(t_probe, r_probe)) | ||
114 | - | ||
115 | - psi_ref, theta_ref, phi_ref = radians(coord_raw[1, 3:]) | ||
116 | - r_ref = tr.euler_matrix(psi_ref, theta_ref, phi_ref, 'rzyx') | ||
117 | - t_ref = tr.translation_matrix(coord_raw[1, :3]) | ||
118 | - m_ref = asmatrix(tr.concatenate_matrices(t_ref, r_ref)) | ||
119 | - | ||
120 | - m_dyn = m_ref.I * m_probe | ||
121 | - m_dyn[2, -1] = -m_dyn[2, -1] | ||
122 | - | ||
123 | - m_img = m_change * m_dyn | ||
124 | - | ||
125 | - scale, shear, angles, trans, persp = tr.decompose_matrix(m_img) | ||
126 | - | ||
127 | - coord = m_img[0, -1], m_img[1, -1], m_img[2, -1], \ | ||
128 | - degrees(angles[0]), degrees(angles[1]), degrees(angles[2]) | ||
129 | - | ||
130 | - wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=m_img, position=coord) | ||
131 | - | ||
132 | - # TODO: Optimize the value of sleep for each tracking device. | ||
133 | - sleep(0.175) | ||
134 | - | ||
135 | - if self._pause_: | ||
136 | - return | ||
137 | - | ||
138 | - | ||
139 | -class CoregistrationDynamic_old(threading.Thread): | ||
140 | - """ | ||
141 | - Thread to update the coordinates with the fiducial points | ||
142 | - co-registration method while the Navigation Button is pressed. | ||
143 | - Sleep function in run method is used to avoid blocking GUI and | ||
144 | - for better real-time navigation | 72 | + a, b, g = np.radians(coord_raw[1, 3:]) |
73 | + r_ref = tr.euler_matrix(a, b, g, 'rzyx') | ||
74 | + t_ref = tr.translation_matrix(coord_raw[1, :3]) | ||
75 | + m_ref = tr.concatenate_matrices(t_ref, r_ref) | ||
76 | + | ||
77 | + m_dyn = np.linalg.inv(m_ref) @ m_probe | ||
78 | + return m_dyn | ||
79 | + | ||
80 | + | ||
81 | +def tracker_to_image(m_change, m_probe_ref, r_obj_img, m_obj_raw, s0_dyn): | ||
82 | + """Compute affine transformation matrix to the reference basis | ||
83 | + | ||
84 | + :param m_change: Corregistration transformation obtained from fiducials | ||
85 | + :type m_change: numpy.ndarray | ||
86 | + :param m_probe_ref: Object or probe in reference coordinate system | ||
87 | + :type m_probe_ref: numpy.ndarray | ||
88 | + :param r_obj_img: Object coordinate system in image space (3d model) | ||
89 | + :type r_obj_img: numpy.ndarray | ||
90 | + :param m_obj_raw: Object basis in raw coordinates from tacker | ||
91 | + :type m_obj_raw: numpy.ndarray | ||
92 | + :param s0_dyn: | ||
93 | + :type s0_dyn: numpy.ndarray | ||
94 | + :return: 4 x 4 numpy double array | ||
95 | + :rtype: numpy.ndarray | ||
145 | """ | 96 | """ |
146 | 97 | ||
147 | - def __init__(self, bases, nav_id, trck_info): | ||
148 | - threading.Thread.__init__(self) | ||
149 | - self.bases = bases | ||
150 | - self.nav_id = nav_id | 98 | + m_img = m_change @ m_probe_ref |
99 | + r_obj = r_obj_img @ np.linalg.inv(m_obj_raw) @ np.linalg.inv(s0_dyn) @ m_probe_ref @ m_obj_raw | ||
100 | + m_img[:3, :3] = r_obj[:3, :3] | ||
101 | + return m_img | ||
102 | + | ||
103 | + | ||
104 | +def corregistrate_object_dynamic(inp, coord_raw, ref_mode_id): | ||
105 | + | ||
106 | + m_change, obj_ref_mode, t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img = inp | ||
107 | + | ||
108 | + # transform raw marker coordinate to object center | ||
109 | + m_probe = object_marker_to_center(coord_raw, obj_ref_mode, t_obj_raw, s0_raw, r_s0_raw) | ||
110 | + # transform object center to reference marker if specified as dynamic reference | ||
111 | + if ref_mode_id: | ||
112 | + m_probe_ref = object_to_reference(coord_raw, m_probe) | ||
113 | + else: | ||
114 | + m_probe_ref = m_probe | ||
115 | + # invert y coordinate | ||
116 | + m_probe_ref[2, -1] = -m_probe_ref[2, -1] | ||
117 | + # corregistrate from tracker to image space | ||
118 | + m_img = tracker_to_image(m_change, m_probe_ref, r_obj_img, m_obj_raw, s0_dyn) | ||
119 | + # compute rotation angles | ||
120 | + _, _, angles, _, _ = tr.decompose_matrix(m_img) | ||
121 | + # create output coordiante list | ||
122 | + coord = m_img[0, -1], m_img[1, -1], m_img[2, -1], \ | ||
123 | + np.degrees(angles[0]), np.degrees(angles[1]), np.degrees(angles[2]) | ||
124 | + | ||
125 | + return coord, m_img | ||
126 | + | ||
127 | + | ||
128 | +def compute_marker_transformation(coord_raw, obj_ref_mode): | ||
129 | + psi, theta, phi = np.radians(coord_raw[obj_ref_mode, 3:]) | ||
130 | + r_probe = tr.euler_matrix(psi, theta, phi, 'rzyx') | ||
131 | + t_probe = tr.translation_matrix(coord_raw[obj_ref_mode, :3]) | ||
132 | + m_probe = tr.concatenate_matrices(t_probe, r_probe) | ||
133 | + return m_probe | ||
134 | + | ||
135 | + | ||
136 | +def corregistrate_dynamic(inp, coord_raw, ref_mode_id): | ||
137 | + | ||
138 | + m_change, obj_ref_mode = inp | ||
139 | + | ||
140 | + # transform raw marker coordinate to object center | ||
141 | + m_probe = compute_marker_transformation(coord_raw, obj_ref_mode) | ||
142 | + # transform object center to reference marker if specified as dynamic reference | ||
143 | + if ref_mode_id: | ||
144 | + m_ref = compute_marker_transformation(coord_raw, 1) | ||
145 | + m_probe_ref = np.linalg.inv(m_ref) @ m_probe | ||
146 | + else: | ||
147 | + m_probe_ref = m_probe | ||
148 | + | ||
149 | + # invert y coordinate | ||
150 | + m_probe_ref[2, -1] = -m_probe_ref[2, -1] | ||
151 | + # corregistrate from tracker to image space | ||
152 | + m_img = m_change @ m_probe_ref | ||
153 | + # compute rotation angles | ||
154 | + _, _, angles, _, _ = tr.decompose_matrix(m_img) | ||
155 | + # create output coordiante list | ||
156 | + coord = m_img[0, -1], m_img[1, -1], m_img[2, -1],\ | ||
157 | + np.degrees(angles[0]), np.degrees(angles[1]), np.degrees(angles[2]) | ||
158 | + | ||
159 | + return coord, m_img | ||
160 | + | ||
161 | + | ||
162 | +class CoordinateCorregistrate(threading.Thread): | ||
163 | + def __init__(self, ref_mode_id, trck_info, coreg_data, coord_queue, view_tracts, coord_tracts_queue, event, sle): | ||
164 | + threading.Thread.__init__(self, name='CoordCoregObject') | ||
165 | + self.ref_mode_id = ref_mode_id | ||
151 | self.trck_info = trck_info | 166 | self.trck_info = trck_info |
152 | - self._pause_ = False | ||
153 | - self.start() | ||
154 | - | ||
155 | - def stop(self): | ||
156 | - self._pause_ = True | ||
157 | - | ||
158 | - def run(self): | ||
159 | - m_inv = self.bases[0] | ||
160 | - n = self.bases[1] | ||
161 | - q1 = self.bases[2] | ||
162 | - q2 = self.bases[3] | ||
163 | - trck_init = self.trck_info[0] | ||
164 | - trck_id = self.trck_info[1] | ||
165 | - trck_mode = self.trck_info[2] | ||
166 | - | ||
167 | - while self.nav_id: | ||
168 | - # trck_coord, probe, reference = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
169 | - coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
170 | - | ||
171 | - trck_coord = dco.dynamic_reference(coord_raw[0, :], coord_raw[1, :]) | ||
172 | - | ||
173 | - trck_xyz = mat([[trck_coord[0]], [trck_coord[1]], [trck_coord[2]]]) | ||
174 | - img = q1 + (m_inv * n) * (trck_xyz - q2) | ||
175 | - | ||
176 | - coord = (float(img[0]), float(img[1]), float(img[2]), trck_coord[3], | ||
177 | - trck_coord[4], trck_coord[5]) | ||
178 | - angles = coord_raw[0, 3:6] | ||
179 | - | ||
180 | - # Tried several combinations and different locations to send the messages, | ||
181 | - # however only this one does not block the GUI during navigation. | ||
182 | - wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=None, position=coord) | ||
183 | - wx.CallAfter(Publisher.sendMessage, 'Set camera in volume', coord) | ||
184 | - wx.CallAfter(Publisher.sendMessage, 'Update tracker angles', angles) | ||
185 | - | ||
186 | - # TODO: Optimize the value of sleep for each tracking device. | ||
187 | - # Debug tracker is not working with 0.175 so changed to 0.2 | ||
188 | - # However, 0.2 is too low update frequency ~5 Hz. Need optimization URGENTLY. | ||
189 | - # sleep(.3) | ||
190 | - sleep(0.175) | ||
191 | - | ||
192 | - if self._pause_: | ||
193 | - return | ||
194 | - | ||
195 | - | ||
196 | -class CoregistrationObjectStatic(threading.Thread): | ||
197 | - """ | ||
198 | - Thread to update the coordinates with the fiducial points | ||
199 | - co-registration method while the Navigation Button is pressed. | ||
200 | - Sleep function in run method is used to avoid blocking GUI and | ||
201 | - for better real-time navigation | ||
202 | - """ | ||
203 | - | ||
204 | - def __init__(self, coreg_data, nav_id, trck_info): | ||
205 | - threading.Thread.__init__(self) | ||
206 | self.coreg_data = coreg_data | 167 | self.coreg_data = coreg_data |
207 | - self.nav_id = nav_id | ||
208 | - self.trck_info = trck_info | ||
209 | - self._pause_ = False | ||
210 | - self.start() | ||
211 | - | ||
212 | - def stop(self): | ||
213 | - self._pause_ = True | 168 | + self.coord_queue = coord_queue |
169 | + self.view_tracts = view_tracts | ||
170 | + self.coord_tracts_queue = coord_tracts_queue | ||
171 | + self.event = event | ||
172 | + self.sle = sle | ||
214 | 173 | ||
215 | def run(self): | 174 | def run(self): |
216 | - # m_change = self.coreg_data[0] | ||
217 | - # t_obj_raw = self.coreg_data[1] | ||
218 | - # s0_raw = self.coreg_data[2] | ||
219 | - # r_s0_raw = self.coreg_data[3] | ||
220 | - # s0_dyn = self.coreg_data[4] | ||
221 | - # m_obj_raw = self.coreg_data[5] | ||
222 | - # r_obj_img = self.coreg_data[6] | ||
223 | - # obj_ref_mode = self.coreg_data[7] | ||
224 | - # | ||
225 | - # trck_init = self.trck_info[0] | ||
226 | - # trck_id = self.trck_info[1] | ||
227 | - # trck_mode = self.trck_info[2] | ||
228 | - | ||
229 | - m_change, obj_ref_mode, t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img = self.coreg_data | ||
230 | - trck_init, trck_id, trck_mode = self.trck_info | ||
231 | - | ||
232 | - while self.nav_id: | ||
233 | - coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
234 | - | ||
235 | - as1, bs1, gs1 = radians(coord_raw[obj_ref_mode, 3:]) | ||
236 | - r_probe = asmatrix(tr.euler_matrix(as1, bs1, gs1, 'rzyx')) | ||
237 | - t_probe_raw = asmatrix(tr.translation_matrix(coord_raw[obj_ref_mode, :3])) | ||
238 | - t_offset_aux = r_s0_raw.I * r_probe * t_obj_raw | ||
239 | - t_offset = asmatrix(identity(4)) | ||
240 | - t_offset[:, -1] = t_offset_aux[:, -1] | ||
241 | - t_probe = s0_raw * t_offset * s0_raw.I * t_probe_raw | ||
242 | - m_probe = asmatrix(tr.concatenate_matrices(t_probe, r_probe)) | ||
243 | - | ||
244 | - m_probe[2, -1] = -m_probe[2, -1] | ||
245 | - | ||
246 | - m_img = m_change * m_probe | ||
247 | - r_obj = r_obj_img * m_obj_raw.I * s0_dyn.I * m_probe * m_obj_raw | ||
248 | - | ||
249 | - m_img[:3, :3] = r_obj[:3, :3] | ||
250 | - | ||
251 | - scale, shear, angles, trans, persp = tr.decompose_matrix(m_img) | ||
252 | - | ||
253 | - coord = m_img[0, -1], m_img[1, -1], m_img[2, -1], \ | ||
254 | - degrees(angles[0]), degrees(angles[1]), degrees(angles[2]) | ||
255 | - | ||
256 | - wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=m_img, position=coord) | ||
257 | - wx.CallAfter(Publisher.sendMessage, 'Update object matrix', m_img=m_img, coord=coord) | ||
258 | - | ||
259 | - # TODO: Optimize the value of sleep for each tracking device. | ||
260 | - sleep(0.175) | ||
261 | - | ||
262 | - # Debug tracker is not working with 0.175 so changed to 0.2 | ||
263 | - # However, 0.2 is too low update frequency ~5 Hz. Need optimization URGENTLY. | ||
264 | - # sleep(.3) | ||
265 | - | ||
266 | - # partially working for translate and offset, | ||
267 | - # but offset is kept always in same axis, have to fix for rotation | ||
268 | - # M_dyn = M_reference.I * T_stylus | ||
269 | - # M_dyn[2, -1] = -M_dyn[2, -1] | ||
270 | - # M_dyn_ch = M_change * M_dyn | ||
271 | - # ddd = M_dyn_ch[0, -1], M_dyn_ch[1, -1], M_dyn_ch[2, -1] | ||
272 | - # M_dyn_ch[:3, -1] = asmatrix(db.flip_x_m(ddd)).reshape([3, 1]) | ||
273 | - # M_final = S0 * M_obj_trans_0 * S0.I * M_dyn_ch | ||
274 | - | ||
275 | - # this works for static reference object rotation | ||
276 | - # R_dyn = M_vtk * M_obj_rot_raw.I * S0_rot_raw.I * R_stylus * M_obj_rot_raw | ||
277 | - # this works for dynamic reference in rotation but not in translation | ||
278 | - # R_dyn = M_vtk * M_obj_rot_raw.I * S0_rot_dyn.I * R_reference.I * R_stylus * M_obj_rot_raw | ||
279 | - | ||
280 | - if self._pause_: | ||
281 | - return | ||
282 | - | ||
283 | - | ||
284 | -class CoregistrationObjectDynamic(threading.Thread): | ||
285 | - """ | ||
286 | - Thread to update the coordinates with the fiducial points | ||
287 | - co-registration method while the Navigation Button is pressed. | ||
288 | - Sleep function in run method is used to avoid blocking GUI and | ||
289 | - for better real-time navigation | ||
290 | - """ | ||
291 | - | ||
292 | - def __init__(self, coreg_data, nav_id, trck_info): | ||
293 | - threading.Thread.__init__(self) | ||
294 | - self.coreg_data = coreg_data | ||
295 | - self.nav_id = nav_id | 175 | + trck_info = self.trck_info |
176 | + coreg_data = self.coreg_data | ||
177 | + view_obj = 1 | ||
178 | + | ||
179 | + trck_init, trck_id, trck_mode = trck_info | ||
180 | + # print('CoordCoreg: event {}'.format(self.event.is_set())) | ||
181 | + while not self.event.is_set(): | ||
182 | + try: | ||
183 | + # print(f"Set the coordinate") | ||
184 | + coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
185 | + coord, m_img = corregistrate_object_dynamic(coreg_data, coord_raw, self.ref_mode_id) | ||
186 | + m_img_flip = m_img.copy() | ||
187 | + m_img_flip[1, -1] = -m_img_flip[1, -1] | ||
188 | + # self.pipeline.set_message(m_img_flip) | ||
189 | + self.coord_queue.put_nowait([coord, m_img, view_obj]) | ||
190 | + # print('CoordCoreg: put {}'.format(count)) | ||
191 | + # count += 1 | ||
192 | + | ||
193 | + if self.view_tracts: | ||
194 | + self.coord_tracts_queue.put_nowait(m_img_flip) | ||
195 | + | ||
196 | + # The sleep has to be in both threads | ||
197 | + sleep(self.sle) | ||
198 | + except queue.Full: | ||
199 | + pass | ||
200 | + | ||
201 | + | ||
202 | +class CoordinateCorregistrateNoObject(threading.Thread): | ||
203 | + def __init__(self, ref_mode_id, trck_info, coreg_data, coord_queue, view_tracts, coord_tracts_queue, event, sle): | ||
204 | + threading.Thread.__init__(self, name='CoordCoregNoObject') | ||
205 | + self.ref_mode_id = ref_mode_id | ||
296 | self.trck_info = trck_info | 206 | self.trck_info = trck_info |
297 | - self._pause_ = False | ||
298 | - self.start() | ||
299 | - | ||
300 | - def stop(self): | ||
301 | - self._pause_ = True | 207 | + self.coreg_data = coreg_data |
208 | + self.coord_queue = coord_queue | ||
209 | + self.view_tracts = view_tracts | ||
210 | + self.coord_tracts_queue = coord_tracts_queue | ||
211 | + self.event = event | ||
212 | + self.sle = sle | ||
302 | 213 | ||
303 | def run(self): | 214 | def run(self): |
215 | + trck_info = self.trck_info | ||
216 | + coreg_data = self.coreg_data | ||
217 | + view_obj = 0 | ||
218 | + | ||
219 | + trck_init, trck_id, trck_mode = trck_info | ||
220 | + # print('CoordCoreg: event {}'.format(self.event.is_set())) | ||
221 | + while not self.event.is_set(): | ||
222 | + try: | ||
223 | + # print(f"Set the coordinate") | ||
224 | + coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
225 | + coord, m_img = corregistrate_dynamic(coreg_data, coord_raw, self.ref_mode_id) | ||
226 | + # print("Coord: ", coord) | ||
227 | + m_img_flip = m_img.copy() | ||
228 | + m_img_flip[1, -1] = -m_img_flip[1, -1] | ||
229 | + self.coord_queue.put_nowait([coord, m_img, view_obj]) | ||
230 | + | ||
231 | + if self.view_tracts: | ||
232 | + self.coord_tracts_queue.put_nowait(m_img_flip) | ||
233 | + | ||
234 | + # The sleep has to be in both threads | ||
235 | + sleep(self.sle) | ||
236 | + except queue.Full: | ||
237 | + pass | ||
238 | + | ||
239 | + | ||
240 | +# class CoregistrationStatic(threading.Thread): | ||
241 | +# """ | ||
242 | +# Thread to update the coordinates with the fiducial points | ||
243 | +# co-registration method while the Navigation Button is pressed. | ||
244 | +# Sleep function in run method is used to avoid blocking GUI and | ||
245 | +# for better real-time navigation | ||
246 | +# """ | ||
247 | +# | ||
248 | +# def __init__(self, coreg_data, nav_id, trck_info): | ||
249 | +# threading.Thread.__init__(self) | ||
250 | +# self.coreg_data = coreg_data | ||
251 | +# self.nav_id = nav_id | ||
252 | +# self.trck_info = trck_info | ||
253 | +# self._pause_ = False | ||
254 | +# self.start() | ||
255 | +# | ||
256 | +# def stop(self): | ||
257 | +# self._pause_ = True | ||
258 | +# | ||
259 | +# def run(self): | ||
260 | +# # m_change = self.coreg_data[0] | ||
261 | +# # obj_ref_mode = self.coreg_data[2] | ||
262 | +# # | ||
263 | +# # trck_init = self.trck_info[0] | ||
264 | +# # trck_id = self.trck_info[1] | ||
265 | +# # trck_mode = self.trck_info[2] | ||
266 | +# | ||
267 | +# m_change, obj_ref_mode = self.coreg_data | ||
268 | +# trck_init, trck_id, trck_mode = self.trck_info | ||
269 | +# | ||
270 | +# while self.nav_id: | ||
271 | +# coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
272 | +# | ||
273 | +# psi, theta, phi = coord_raw[obj_ref_mode, 3:] | ||
274 | +# t_probe_raw = asmatrix(tr.translation_matrix(coord_raw[obj_ref_mode, :3])) | ||
275 | +# | ||
276 | +# t_probe_raw[2, -1] = -t_probe_raw[2, -1] | ||
277 | +# | ||
278 | +# m_img = m_change * t_probe_raw | ||
279 | +# | ||
280 | +# coord = m_img[0, -1], m_img[1, -1], m_img[2, -1], psi, theta, phi | ||
281 | +# | ||
282 | +# wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=m_img, position=coord) | ||
283 | +# | ||
284 | +# # TODO: Optimize the value of sleep for each tracking device. | ||
285 | +# sleep(0.175) | ||
286 | +# | ||
287 | +# if self._pause_: | ||
288 | +# return | ||
289 | +# | ||
290 | +# | ||
291 | +# class CoregistrationDynamic(threading.Thread): | ||
292 | +# """ | ||
293 | +# Thread to update the coordinates with the fiducial points | ||
294 | +# co-registration method while the Navigation Button is pressed. | ||
295 | +# Sleep function in run method is used to avoid blocking GUI and | ||
296 | +# for better real-time navigation | ||
297 | +# """ | ||
298 | +# | ||
299 | +# def __init__(self, coreg_data, nav_id, trck_info): | ||
300 | +# threading.Thread.__init__(self) | ||
301 | +# self.coreg_data = coreg_data | ||
302 | +# self.nav_id = nav_id | ||
303 | +# self.trck_info = trck_info | ||
304 | +# # self.tracts_info = tracts_info | ||
305 | +# # self.tracts = None | ||
306 | +# self._pause_ = False | ||
307 | +# # self.start() | ||
308 | +# | ||
309 | +# def stop(self): | ||
310 | +# # self.tracts.stop() | ||
311 | +# self._pause_ = True | ||
312 | +# | ||
313 | +# def run(self): | ||
314 | +# m_change, obj_ref_mode = self.coreg_data | ||
315 | +# trck_init, trck_id, trck_mode = self.trck_info | ||
316 | +# # seed, tracker, affine, affine_vtk = self.tracts_info | ||
317 | +# | ||
318 | +# while self.nav_id: | ||
319 | +# coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
320 | +# | ||
321 | +# psi, theta, phi = radians(coord_raw[obj_ref_mode, 3:]) | ||
322 | +# r_probe = tr.euler_matrix(psi, theta, phi, 'rzyx') | ||
323 | +# t_probe = tr.translation_matrix(coord_raw[obj_ref_mode, :3]) | ||
324 | +# m_probe = asmatrix(tr.concatenate_matrices(t_probe, r_probe)) | ||
325 | +# | ||
326 | +# psi_ref, theta_ref, phi_ref = radians(coord_raw[1, 3:]) | ||
327 | +# r_ref = tr.euler_matrix(psi_ref, theta_ref, phi_ref, 'rzyx') | ||
328 | +# t_ref = tr.translation_matrix(coord_raw[1, :3]) | ||
329 | +# m_ref = asmatrix(tr.concatenate_matrices(t_ref, r_ref)) | ||
330 | +# | ||
331 | +# m_dyn = m_ref.I * m_probe | ||
332 | +# m_dyn[2, -1] = -m_dyn[2, -1] | ||
333 | +# | ||
334 | +# m_img = m_change * m_dyn | ||
335 | +# | ||
336 | +# scale, shear, angles, trans, persp = tr.decompose_matrix(m_img) | ||
337 | +# | ||
338 | +# coord = m_img[0, -1], m_img[1, -1], m_img[2, -1], \ | ||
339 | +# degrees(angles[0]), degrees(angles[1]), degrees(angles[2]) | ||
340 | +# | ||
341 | +# # pos_world_aux = np.ones([4, 1]) | ||
342 | +# # pos_world_aux[:3, -1] = db.flip_x((m_img[0, -1], m_img[1, -1], m_img[2, -1]))[:3] | ||
343 | +# # pos_world = np.linalg.inv(affine) @ pos_world_aux | ||
344 | +# # seed_aux = pos_world.reshape([1, 4])[0, :3] | ||
345 | +# # seed = seed_aux[np.newaxis, :] | ||
346 | +# # | ||
347 | +# # self.tracts = dtr.compute_tracts(tracker, seed, affine_vtk, True) | ||
348 | +# | ||
349 | +# # wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=m_img, position=coord) | ||
350 | +# wx.CallAfter(Publisher.sendMessage, 'Update cross position', arg=m_img, position=coord) | ||
351 | +# | ||
352 | +# # TODO: Optimize the value of sleep for each tracking device. | ||
353 | +# sleep(3.175) | ||
354 | +# | ||
355 | +# if self._pause_: | ||
356 | +# return | ||
357 | +# | ||
358 | +# | ||
359 | +# class CoregistrationDynamic_old(threading.Thread): | ||
360 | +# """ | ||
361 | +# Thread to update the coordinates with the fiducial points | ||
362 | +# co-registration method while the Navigation Button is pressed. | ||
363 | +# Sleep function in run method is used to avoid blocking GUI and | ||
364 | +# for better real-time navigation | ||
365 | +# """ | ||
366 | +# | ||
367 | +# def __init__(self, bases, nav_id, trck_info): | ||
368 | +# threading.Thread.__init__(self) | ||
369 | +# self.bases = bases | ||
370 | +# self.nav_id = nav_id | ||
371 | +# self.trck_info = trck_info | ||
372 | +# self._pause_ = False | ||
373 | +# self.start() | ||
374 | +# | ||
375 | +# def stop(self): | ||
376 | +# self._pause_ = True | ||
377 | +# | ||
378 | +# def run(self): | ||
379 | +# m_inv = self.bases[0] | ||
380 | +# n = self.bases[1] | ||
381 | +# q1 = self.bases[2] | ||
382 | +# q2 = self.bases[3] | ||
383 | +# trck_init = self.trck_info[0] | ||
384 | +# trck_id = self.trck_info[1] | ||
385 | +# trck_mode = self.trck_info[2] | ||
386 | +# | ||
387 | +# while self.nav_id: | ||
388 | +# # trck_coord, probe, reference = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
389 | +# coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
390 | +# | ||
391 | +# trck_coord = dco.dynamic_reference(coord_raw[0, :], coord_raw[1, :]) | ||
392 | +# | ||
393 | +# trck_xyz = mat([[trck_coord[0]], [trck_coord[1]], [trck_coord[2]]]) | ||
394 | +# img = q1 + (m_inv * n) * (trck_xyz - q2) | ||
395 | +# | ||
396 | +# coord = (float(img[0]), float(img[1]), float(img[2]), trck_coord[3], | ||
397 | +# trck_coord[4], trck_coord[5]) | ||
398 | +# angles = coord_raw[0, 3:6] | ||
399 | +# | ||
400 | +# # Tried several combinations and different locations to send the messages, | ||
401 | +# # however only this one does not block the GUI during navigation. | ||
402 | +# wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=None, position=coord) | ||
403 | +# wx.CallAfter(Publisher.sendMessage, 'Set camera in volume', coord) | ||
404 | +# wx.CallAfter(Publisher.sendMessage, 'Update tracker angles', angles) | ||
405 | +# | ||
406 | +# # TODO: Optimize the value of sleep for each tracking device. | ||
407 | +# # Debug tracker is not working with 0.175 so changed to 0.2 | ||
408 | +# # However, 0.2 is too low update frequency ~5 Hz. Need optimization URGENTLY. | ||
409 | +# # sleep(.3) | ||
410 | +# sleep(0.175) | ||
411 | +# | ||
412 | +# if self._pause_: | ||
413 | +# return | ||
414 | +# | ||
415 | +# | ||
416 | +# class CoregistrationObjectStatic(threading.Thread): | ||
417 | +# """ | ||
418 | +# Thread to update the coordinates with the fiducial points | ||
419 | +# co-registration method while the Navigation Button is pressed. | ||
420 | +# Sleep function in run method is used to avoid blocking GUI and | ||
421 | +# for better real-time navigation | ||
422 | +# """ | ||
423 | +# | ||
424 | +# def __init__(self, coreg_data, nav_id, trck_info): | ||
425 | +# threading.Thread.__init__(self) | ||
426 | +# self.coreg_data = coreg_data | ||
427 | +# self.nav_id = nav_id | ||
428 | +# self.trck_info = trck_info | ||
429 | +# self._pause_ = False | ||
430 | +# self.start() | ||
431 | +# | ||
432 | +# def stop(self): | ||
433 | +# self._pause_ = True | ||
434 | +# | ||
435 | +# def run(self): | ||
436 | +# # m_change = self.coreg_data[0] | ||
437 | +# # t_obj_raw = self.coreg_data[1] | ||
438 | +# # s0_raw = self.coreg_data[2] | ||
439 | +# # r_s0_raw = self.coreg_data[3] | ||
440 | +# # s0_dyn = self.coreg_data[4] | ||
441 | +# # m_obj_raw = self.coreg_data[5] | ||
442 | +# # r_obj_img = self.coreg_data[6] | ||
443 | +# # obj_ref_mode = self.coreg_data[7] | ||
444 | +# # | ||
445 | +# # trck_init = self.trck_info[0] | ||
446 | +# # trck_id = self.trck_info[1] | ||
447 | +# # trck_mode = self.trck_info[2] | ||
448 | +# | ||
449 | +# m_change, obj_ref_mode, t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img = self.coreg_data | ||
450 | +# trck_init, trck_id, trck_mode = self.trck_info | ||
451 | +# | ||
452 | +# while self.nav_id: | ||
453 | +# coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
454 | +# | ||
455 | +# as1, bs1, gs1 = radians(coord_raw[obj_ref_mode, 3:]) | ||
456 | +# r_probe = asmatrix(tr.euler_matrix(as1, bs1, gs1, 'rzyx')) | ||
457 | +# t_probe_raw = asmatrix(tr.translation_matrix(coord_raw[obj_ref_mode, :3])) | ||
458 | +# t_offset_aux = r_s0_raw.I * r_probe * t_obj_raw | ||
459 | +# t_offset = asmatrix(identity(4)) | ||
460 | +# t_offset[:, -1] = t_offset_aux[:, -1] | ||
461 | +# t_probe = s0_raw * t_offset * s0_raw.I * t_probe_raw | ||
462 | +# m_probe = asmatrix(tr.concatenate_matrices(t_probe, r_probe)) | ||
463 | +# | ||
464 | +# m_probe[2, -1] = -m_probe[2, -1] | ||
465 | +# | ||
466 | +# m_img = m_change * m_probe | ||
467 | +# r_obj = r_obj_img * m_obj_raw.I * s0_dyn.I * m_probe * m_obj_raw | ||
468 | +# | ||
469 | +# m_img[:3, :3] = r_obj[:3, :3] | ||
470 | +# | ||
471 | +# scale, shear, angles, trans, persp = tr.decompose_matrix(m_img) | ||
472 | +# | ||
473 | +# coord = m_img[0, -1], m_img[1, -1], m_img[2, -1], \ | ||
474 | +# degrees(angles[0]), degrees(angles[1]), degrees(angles[2]) | ||
475 | +# | ||
476 | +# wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=m_img, position=coord) | ||
477 | +# wx.CallAfter(Publisher.sendMessage, 'Update object matrix', m_img=m_img, coord=coord) | ||
478 | +# | ||
479 | +# # TODO: Optimize the value of sleep for each tracking device. | ||
480 | +# sleep(0.175) | ||
481 | +# | ||
482 | +# # Debug tracker is not working with 0.175 so changed to 0.2 | ||
483 | +# # However, 0.2 is too low update frequency ~5 Hz. Need optimization URGENTLY. | ||
484 | +# # sleep(.3) | ||
485 | +# | ||
486 | +# # partially working for translate and offset, | ||
487 | +# # but offset is kept always in same axis, have to fix for rotation | ||
488 | +# # M_dyn = M_reference.I * T_stylus | ||
489 | +# # M_dyn[2, -1] = -M_dyn[2, -1] | ||
490 | +# # M_dyn_ch = M_change * M_dyn | ||
491 | +# # ddd = M_dyn_ch[0, -1], M_dyn_ch[1, -1], M_dyn_ch[2, -1] | ||
492 | +# # M_dyn_ch[:3, -1] = asmatrix(db.flip_x_m(ddd)).reshape([3, 1]) | ||
493 | +# # M_final = S0 * M_obj_trans_0 * S0.I * M_dyn_ch | ||
494 | +# | ||
495 | +# # this works for static reference object rotation | ||
496 | +# # R_dyn = M_vtk * M_obj_rot_raw.I * S0_rot_raw.I * R_stylus * M_obj_rot_raw | ||
497 | +# # this works for dynamic reference in rotation but not in translation | ||
498 | +# # R_dyn = M_vtk * M_obj_rot_raw.I * S0_rot_dyn.I * R_reference.I * R_stylus * M_obj_rot_raw | ||
499 | +# | ||
500 | +# if self._pause_: | ||
501 | +# return | ||
502 | +# | ||
503 | +# | ||
504 | +# class CoregistrationObjectDynamic(threading.Thread): | ||
505 | +# """ | ||
506 | +# Thread to update the coordinates with the fiducial points | ||
507 | +# co-registration method while the Navigation Button is pressed. | ||
508 | +# Sleep function in run method is used to avoid blocking GUI and | ||
509 | +# for better real-time navigation | ||
510 | +# """ | ||
511 | +# | ||
512 | +# def __init__(self, coreg_data, nav_id, trck_info, tracts_info): | ||
513 | +# threading.Thread.__init__(self) | ||
514 | +# self.coreg_data = coreg_data | ||
515 | +# self.nav_id = nav_id | ||
516 | +# self.trck_info = trck_info | ||
517 | +# # self.tracts_info = tracts_info | ||
518 | +# # self.tracts = None | ||
519 | +# self._pause_ = False | ||
520 | +# self.start() | ||
521 | +# | ||
522 | +# def stop(self): | ||
523 | +# # self.tracts.stop() | ||
524 | +# self._pause_ = True | ||
525 | +# | ||
526 | +# def run(self): | ||
527 | +# | ||
528 | +# m_change, obj_ref_mode, t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img = self.coreg_data | ||
529 | +# trck_init, trck_id, trck_mode = self.trck_info | ||
530 | +# # seed, tracker, affine, affine_vtk = self.tracts_info | ||
531 | +# | ||
532 | +# while self.nav_id: | ||
533 | +# coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
534 | +# | ||
535 | +# as1, bs1, gs1 = radians(coord_raw[obj_ref_mode, 3:]) | ||
536 | +# r_probe = asmatrix(tr.euler_matrix(as1, bs1, gs1, 'rzyx')) | ||
537 | +# t_probe_raw = asmatrix(tr.translation_matrix(coord_raw[obj_ref_mode, :3])) | ||
538 | +# t_offset_aux = r_s0_raw.I * r_probe * t_obj_raw | ||
539 | +# t_offset = asmatrix(identity(4)) | ||
540 | +# t_offset[:, -1] = t_offset_aux[:, -1] | ||
541 | +# t_probe = s0_raw * t_offset * s0_raw.I * t_probe_raw | ||
542 | +# m_probe = asmatrix(tr.concatenate_matrices(t_probe, r_probe)) | ||
543 | +# | ||
544 | +# a, b, g = radians(coord_raw[1, 3:]) | ||
545 | +# r_ref = tr.euler_matrix(a, b, g, 'rzyx') | ||
546 | +# t_ref = tr.translation_matrix(coord_raw[1, :3]) | ||
547 | +# m_ref = asmatrix(tr.concatenate_matrices(t_ref, r_ref)) | ||
548 | +# | ||
549 | +# m_dyn = m_ref.I * m_probe | ||
550 | +# m_dyn[2, -1] = -m_dyn[2, -1] | ||
551 | +# | ||
552 | +# m_img = m_change * m_dyn | ||
553 | +# r_obj = r_obj_img * m_obj_raw.I * s0_dyn.I * m_dyn * m_obj_raw | ||
554 | +# | ||
555 | +# m_img[:3, :3] = r_obj[:3, :3] | ||
556 | +# | ||
557 | +# scale, shear, angles, trans, persp = tr.decompose_matrix(m_img) | ||
558 | +# | ||
559 | +# coord = m_img[0, -1], m_img[1, -1], m_img[2, -1],\ | ||
560 | +# degrees(angles[0]), degrees(angles[1]), degrees(angles[2]) | ||
561 | +# | ||
562 | +# # norm_vec = m_img[:3, 2].reshape([1, 3]).tolist() | ||
563 | +# # p0 = m_img[:3, -1].reshape([1, 3]).tolist() | ||
564 | +# # p2 = [x - 30 * y for x, y in zip(p0[0], norm_vec[0])] | ||
565 | +# # m_tract = m_img.copy() | ||
566 | +# # m_tract[:3, -1] = np.reshape(np.asarray(p2)[np.newaxis, :], [3, 1]) | ||
567 | +# | ||
568 | +# # pos_world_aux = np.ones([4, 1]) | ||
569 | +# # pos_world_aux[:3, -1] = db.flip_x((p2[0], p2[1], p2[2]))[:3] | ||
570 | +# # pos_world = np.linalg.inv(affine) @ pos_world_aux | ||
571 | +# # seed_aux = pos_world.reshape([1, 4])[0, :3] | ||
572 | +# # seed = seed_aux[np.newaxis, :] | ||
573 | +# | ||
574 | +# # self.tracts = dtr.compute_tracts(tracker, seed, affine_vtk, True) | ||
575 | +# | ||
576 | +# # wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=m_img, position=coord) | ||
577 | +# wx.CallAfter(Publisher.sendMessage, 'Update cross position', arg=m_img, position=coord) | ||
578 | +# wx.CallAfter(Publisher.sendMessage, 'Update object matrix', m_img=m_img, coord=coord) | ||
579 | +# | ||
580 | +# # TODO: Optimize the value of sleep for each tracking device. | ||
581 | +# #sleep(2.175) | ||
582 | +# sleep(0.175) | ||
583 | +# | ||
584 | +# # Debug tracker is not working with 0.175 so changed to 0.2 | ||
585 | +# # However, 0.2 is too low update frequency ~5 Hz. Need optimization URGENTLY. | ||
586 | +# # sleep(.3) | ||
587 | +# | ||
588 | +# # partially working for translate and offset, | ||
589 | +# # but offset is kept always in same axis, have to fix for rotation | ||
590 | +# # M_dyn = M_reference.I * T_stylus | ||
591 | +# # M_dyn[2, -1] = -M_dyn[2, -1] | ||
592 | +# # M_dyn_ch = M_change * M_dyn | ||
593 | +# # ddd = M_dyn_ch[0, -1], M_dyn_ch[1, -1], M_dyn_ch[2, -1] | ||
594 | +# # M_dyn_ch[:3, -1] = asmatrix(db.flip_x_m(ddd)).reshape([3, 1]) | ||
595 | +# # M_final = S0 * M_obj_trans_0 * S0.I * M_dyn_ch | ||
596 | +# | ||
597 | +# # this works for static reference object rotation | ||
598 | +# # R_dyn = M_vtk * M_obj_rot_raw.I * S0_rot_raw.I * R_stylus * M_obj_rot_raw | ||
599 | +# # this works for dynamic reference in rotation but not in translation | ||
600 | +# # R_dyn = M_vtk * M_obj_rot_raw.I * S0_rot_dyn.I * R_reference.I * R_stylus * M_obj_rot_raw | ||
601 | +# | ||
602 | +# if self._pause_: | ||
603 | +# return | ||
604 | +# | ||
605 | +# | ||
606 | +# def corregistrate_object(inp, coord_raw): | ||
607 | +# m_change, obj_ref_mode, t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img = inp | ||
608 | +# as1, bs1, gs1 = radians(coord_raw[obj_ref_mode, 3:]) | ||
609 | +# r_probe = tr.euler_matrix(as1, bs1, gs1, 'rzyx') | ||
610 | +# t_probe_raw = tr.translation_matrix(coord_raw[obj_ref_mode, :3]) | ||
611 | +# t_offset_aux = np.linalg.inv(r_s0_raw) @ r_probe @ t_obj_raw | ||
612 | +# t_offset = identity(4) | ||
613 | +# t_offset[:, -1] = t_offset_aux[:, -1] | ||
614 | +# t_probe = s0_raw @ t_offset @ np.linalg.inv(s0_raw) @ t_probe_raw | ||
615 | +# m_probe = tr.concatenate_matrices(t_probe, r_probe) | ||
616 | +# | ||
617 | +# a, b, g = radians(coord_raw[1, 3:]) | ||
618 | +# r_ref = tr.euler_matrix(a, b, g, 'rzyx') | ||
619 | +# t_ref = tr.translation_matrix(coord_raw[1, :3]) | ||
620 | +# m_ref = tr.concatenate_matrices(t_ref, r_ref) | ||
621 | +# | ||
622 | +# m_dyn = np.linalg.inv(m_ref) @ m_probe | ||
623 | +# m_dyn[2, -1] = -m_dyn[2, -1] | ||
624 | +# | ||
625 | +# m_img = m_change @ m_dyn | ||
626 | +# r_obj = r_obj_img @ np.linalg.inv(m_obj_raw) @ np.linalg.inv(s0_dyn) @ m_dyn @ m_obj_raw | ||
627 | +# | ||
628 | +# m_img[:3, :3] = r_obj[:3, :3] | ||
629 | +# | ||
630 | +# scale, shear, angles, trans, persp = tr.decompose_matrix(m_img) | ||
631 | +# | ||
632 | +# coord = m_img[0, -1], m_img[1, -1], m_img[2, -1], \ | ||
633 | +# degrees(angles[0]), degrees(angles[1]), degrees(angles[2]) | ||
634 | +# | ||
635 | +# return coord, m_img | ||
304 | 636 | ||
305 | - m_change, obj_ref_mode, t_obj_raw, s0_raw, r_s0_raw, s0_dyn, m_obj_raw, r_obj_img = self.coreg_data | ||
306 | - trck_init, trck_id, trck_mode = self.trck_info | ||
307 | - | ||
308 | - while self.nav_id: | ||
309 | - coord_raw = dco.GetCoordinates(trck_init, trck_id, trck_mode) | ||
310 | - | ||
311 | - as1, bs1, gs1 = radians(coord_raw[obj_ref_mode, 3:]) | ||
312 | - r_probe = asmatrix(tr.euler_matrix(as1, bs1, gs1, 'rzyx')) | ||
313 | - t_probe_raw = asmatrix(tr.translation_matrix(coord_raw[obj_ref_mode, :3])) | ||
314 | - t_offset_aux = r_s0_raw.I * r_probe * t_obj_raw | ||
315 | - t_offset = asmatrix(identity(4)) | ||
316 | - t_offset[:, -1] = t_offset_aux[:, -1] | ||
317 | - t_probe = s0_raw * t_offset * s0_raw.I * t_probe_raw | ||
318 | - m_probe = asmatrix(tr.concatenate_matrices(t_probe, r_probe)) | ||
319 | - | ||
320 | - a, b, g = radians(coord_raw[1, 3:]) | ||
321 | - r_ref = tr.euler_matrix(a, b, g, 'rzyx') | ||
322 | - t_ref = tr.translation_matrix(coord_raw[1, :3]) | ||
323 | - m_ref = asmatrix(tr.concatenate_matrices(t_ref, r_ref)) | ||
324 | - | ||
325 | - m_dyn = m_ref.I * m_probe | ||
326 | - m_dyn[2, -1] = -m_dyn[2, -1] | ||
327 | - | ||
328 | - m_img = m_change * m_dyn | ||
329 | - r_obj = r_obj_img * m_obj_raw.I * s0_dyn.I * m_dyn * m_obj_raw | ||
330 | - | ||
331 | - m_img[:3, :3] = r_obj[:3, :3] | ||
332 | - | ||
333 | - scale, shear, angles, trans, persp = tr.decompose_matrix(m_img) | ||
334 | - | ||
335 | - coord = m_img[0, -1], m_img[1, -1], m_img[2, -1],\ | ||
336 | - degrees(angles[0]), degrees(angles[1]), degrees(angles[2]) | ||
337 | - | ||
338 | - wx.CallAfter(Publisher.sendMessage, 'Co-registered points', arg=m_img, position=coord) | ||
339 | - wx.CallAfter(Publisher.sendMessage, 'Update object matrix', m_img=m_img, coord=coord) | ||
340 | - | ||
341 | - # TODO: Optimize the value of sleep for each tracking device. | ||
342 | - sleep(0.175) | ||
343 | - | ||
344 | - # Debug tracker is not working with 0.175 so changed to 0.2 | ||
345 | - # However, 0.2 is too low update frequency ~5 Hz. Need optimization URGENTLY. | ||
346 | - # sleep(.3) | ||
347 | - | ||
348 | - # partially working for translate and offset, | ||
349 | - # but offset is kept always in same axis, have to fix for rotation | ||
350 | - # M_dyn = M_reference.I * T_stylus | ||
351 | - # M_dyn[2, -1] = -M_dyn[2, -1] | ||
352 | - # M_dyn_ch = M_change * M_dyn | ||
353 | - # ddd = M_dyn_ch[0, -1], M_dyn_ch[1, -1], M_dyn_ch[2, -1] | ||
354 | - # M_dyn_ch[:3, -1] = asmatrix(db.flip_x_m(ddd)).reshape([3, 1]) | ||
355 | - # M_final = S0 * M_obj_trans_0 * S0.I * M_dyn_ch | ||
356 | - | ||
357 | - # this works for static reference object rotation | ||
358 | - # R_dyn = M_vtk * M_obj_rot_raw.I * S0_rot_raw.I * R_stylus * M_obj_rot_raw | ||
359 | - # this works for dynamic reference in rotation but not in translation | ||
360 | - # R_dyn = M_vtk * M_obj_rot_raw.I * S0_rot_dyn.I * R_reference.I * R_stylus * M_obj_rot_raw | ||
361 | - | ||
362 | - if self._pause_: | ||
363 | - return |
invesalius/data/imagedata_utils.py
@@ -37,6 +37,8 @@ from invesalius.data import vtk_utils as vtk_utils | @@ -37,6 +37,8 @@ from invesalius.data import vtk_utils as vtk_utils | ||
37 | import invesalius.reader.bitmap_reader as bitmap_reader | 37 | import invesalius.reader.bitmap_reader as bitmap_reader |
38 | import invesalius.utils as utils | 38 | import invesalius.utils as utils |
39 | import invesalius.data.converters as converters | 39 | import invesalius.data.converters as converters |
40 | +import invesalius.data.slice_ as sl | ||
41 | +import invesalius.data.transformations as tr | ||
40 | 42 | ||
41 | from invesalius import inv_paths | 43 | from invesalius import inv_paths |
42 | 44 | ||
@@ -516,3 +518,67 @@ def image_normalize(image, min_=0.0, max_=1.0, output_dtype=np.int16): | @@ -516,3 +518,67 @@ def image_normalize(image, min_=0.0, max_=1.0, output_dtype=np.int16): | ||
516 | imin, imax = image.min(), image.max() | 518 | imin, imax = image.min(), image.max() |
517 | output[:] = (image - imin) * ((max_ - min_) / (imax - imin)) + min_ | 519 | output[:] = (image - imin) * ((max_ - min_) / (imax - imin)) + min_ |
518 | return output | 520 | return output |
521 | + | ||
522 | + | ||
523 | +def world2invspace(affine=None): | ||
524 | + """ | ||
525 | + Normalize image pixel intensity for int16 gray scale values. | ||
526 | + | ||
527 | + :param repos: list of translation and rotation [trans_x, trans_y, trans_z, rot_x, rot_y, rot_z] to reposition the | ||
528 | + vtk object prior to applying the affine matrix transformation. Note: rotation given in degrees | ||
529 | + :param user_matrix: affine matrix from image header, prefered QForm matrix | ||
530 | + :return: vtk transform filter for repositioning the polydata and affine matrix to be used as SetUserMatrix in actor | ||
531 | + """ | ||
532 | + | ||
533 | + # remove scaling factor for non-unitary voxel dimensions | ||
534 | + scale, shear, angs, trans, persp = tr.decompose_matrix(affine) | ||
535 | + affine_noscale = tr.compose_matrix(scale=None, shear=shear, angles=angs, translate=trans, perspective=persp) | ||
536 | + # repos_img = [0.] * 6 | ||
537 | + # repos_img[1] = -float(shape[1]) | ||
538 | + # | ||
539 | + # repos_mat = np.identity(4) | ||
540 | + # # translation | ||
541 | + # repos_mat[:3, -1] = repos_img[:3] | ||
542 | + # # rotation (in principle for invesalius space no rotation is needed) | ||
543 | + # repos_mat[:3, :3] = tr.euler_matrix(*np.deg2rad(repos_img[3:]), axes='sxyz')[:3, :3] | ||
544 | + | ||
545 | + # if repos: | ||
546 | + # transx, transy, transz, rotx, roty, rotz = repos | ||
547 | + # # create a transform that rotates the stl source | ||
548 | + # transform = vtk.vtkTransform() | ||
549 | + # transform.PostMultiply() | ||
550 | + # transform.RotateX(rotx) | ||
551 | + # transform.RotateY(roty) | ||
552 | + # transform.RotateZ(rotz) | ||
553 | + # transform.Translate(transx, transy, transz) | ||
554 | + # | ||
555 | + # transform_filt = vtk.vtkTransformPolyDataFilter() | ||
556 | + # transform_filt.SetTransform(transform) | ||
557 | + # transform_filt.Update() | ||
558 | + | ||
559 | + # assuming vtk default transformation order is PreMultiply, the user matrix is set so: | ||
560 | + # 1. Replace the object -> 2. Transform the object to desired position/orientation | ||
561 | + # PreMultiplty: M = M*A where M is current transformation and A is applied transformation | ||
562 | + # user_matrix = np.linalg.inv(user_matrix) @ repos_mat | ||
563 | + | ||
564 | + return np.linalg.inv(affine_noscale) | ||
565 | + | ||
566 | + | ||
567 | +def convert_world_to_voxel(xyz, affine): | ||
568 | + """ | ||
569 | + Convert a coordinate from the world space ((x, y, z); scanner space; millimeters) to the | ||
570 | + voxel space ((i, j, k)). This is achieved by multiplying a coordinate by the inverse | ||
571 | + of the affine transformation. | ||
572 | + More information: https://nipy.org/nibabel/coordinate_systems.html | ||
573 | + :param xyz: a list or array of 3 coordinates (x, y, z) in the world coordinates | ||
574 | + :param affine: a 4x4 array containing the image affine transformation in homogeneous coordinates | ||
575 | + :return: a 1x3 array with the point coordinates in image space (i, j, k) | ||
576 | + """ | ||
577 | + | ||
578 | + # print("xyz: ", xyz, "\naffine", affine) | ||
579 | + # convert xyz coordinate to 1x4 homogeneous coordinates array | ||
580 | + xyz_homo = np.hstack((xyz, 1.)).reshape([4, 1]) | ||
581 | + ijk_homo = np.linalg.inv(affine) @ xyz_homo | ||
582 | + ijk = ijk_homo.T[np.newaxis, 0, :3] | ||
583 | + | ||
584 | + return ijk |
invesalius/data/record_coords.py
@@ -42,7 +42,8 @@ class Record(threading.Thread): | @@ -42,7 +42,8 @@ class Record(threading.Thread): | ||
42 | self.start() | 42 | self.start() |
43 | 43 | ||
44 | def __bind_events(self): | 44 | def __bind_events(self): |
45 | - Publisher.subscribe(self.UpdateCurrentCoords, 'Co-registered points') | 45 | + # Publisher.subscribe(self.UpdateCurrentCoords, 'Co-registered points') |
46 | + Publisher.subscribe(self.UpdateCurrentCoords, 'Update cross position') | ||
46 | 47 | ||
47 | def UpdateCurrentCoords(self, arg, position): | 48 | def UpdateCurrentCoords(self, arg, position): |
48 | self.coord = asarray(position) | 49 | self.coord = asarray(position) |
@@ -50,7 +51,10 @@ class Record(threading.Thread): | @@ -50,7 +51,10 @@ class Record(threading.Thread): | ||
50 | def stop(self): | 51 | def stop(self): |
51 | self._pause_ = True | 52 | self._pause_ = True |
52 | #save coords dialog | 53 | #save coords dialog |
53 | - filename = dlg.ShowSaveCoordsDialog("coords.csv") | 54 | + filename = dlg.ShowLoadSaveDialog(message=_(u"Save coords as..."), |
55 | + wildcard=_("Coordinates files (*.csv)|*.csv"), | ||
56 | + style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, | ||
57 | + default_filename="coords.csv", save_ext="csv") | ||
54 | if filename: | 58 | if filename: |
55 | savetxt(filename, self.coord_list, delimiter=',', fmt='%.4f', header="time, x, y, z, a, b, g", comments="") | 59 | savetxt(filename, self.coord_list, delimiter=',', fmt='%.4f', header="time, x, y, z, a, b, g", comments="") |
56 | 60 |
invesalius/data/slice_.py
@@ -82,6 +82,9 @@ class Slice(metaclass=utils.Singleton): | @@ -82,6 +82,9 @@ class Slice(metaclass=utils.Singleton): | ||
82 | self.blend_filter = None | 82 | self.blend_filter = None |
83 | self.histogram = None | 83 | self.histogram = None |
84 | self._matrix = None | 84 | self._matrix = None |
85 | + self._affine = np.identity(4) | ||
86 | + self._n_tracts = 0 | ||
87 | + self._tracker = None | ||
85 | self.aux_matrices = {} | 88 | self.aux_matrices = {} |
86 | self.aux_matrices_colours = {} | 89 | self.aux_matrices_colours = {} |
87 | self.state = const.STATE_DEFAULT | 90 | self.state = const.STATE_DEFAULT |
@@ -144,6 +147,30 @@ class Slice(metaclass=utils.Singleton): | @@ -144,6 +147,30 @@ class Slice(metaclass=utils.Singleton): | ||
144 | (s * d / 2.0) for (d, s) in zip(self.matrix.shape[::-1], self.spacing) | 147 | (s * d / 2.0) for (d, s) in zip(self.matrix.shape[::-1], self.spacing) |
145 | ] | 148 | ] |
146 | 149 | ||
150 | + @property | ||
151 | + def affine(self): | ||
152 | + return self._affine | ||
153 | + | ||
154 | + @affine.setter | ||
155 | + def affine(self, value): | ||
156 | + self._affine = value | ||
157 | + | ||
158 | + @property | ||
159 | + def n_tracts(self): | ||
160 | + return self._n_tracts | ||
161 | + | ||
162 | + @n_tracts.setter | ||
163 | + def n_tracts(self, value): | ||
164 | + self._n_tracts = value | ||
165 | + | ||
166 | + @property | ||
167 | + def tracker(self): | ||
168 | + return self._tracker | ||
169 | + | ||
170 | + @tracker.setter | ||
171 | + def tracker(self, value): | ||
172 | + self._tracker = value | ||
173 | + | ||
147 | def __bind_events(self): | 174 | def __bind_events(self): |
148 | # General slice control | 175 | # General slice control |
149 | Publisher.subscribe(self.CreateSurfaceFromIndex, "Create surface from index") | 176 | Publisher.subscribe(self.CreateSurfaceFromIndex, "Create surface from index") |
invesalius/data/styles.py
@@ -47,6 +47,14 @@ from invesalius.data.measures import (CircleDensityMeasure, MeasureData, | @@ -47,6 +47,14 @@ from invesalius.data.measures import (CircleDensityMeasure, MeasureData, | ||
47 | 47 | ||
48 | from invesalius_cy import floodfill | 48 | from invesalius_cy import floodfill |
49 | 49 | ||
50 | +# For tracts | ||
51 | +import invesalius.data.tractography as dtr | ||
52 | +# import invesalius.project as prj | ||
53 | +import invesalius.data.slice_ as sl | ||
54 | +import invesalius.data.bases as bases | ||
55 | +# from time import sleep | ||
56 | +# --- | ||
57 | + | ||
50 | ORIENTATIONS = { | 58 | ORIENTATIONS = { |
51 | "AXIAL": const.AXIAL, | 59 | "AXIAL": const.AXIAL, |
52 | "CORONAL": const.CORONAL, | 60 | "CORONAL": const.CORONAL, |
@@ -465,6 +473,18 @@ class CrossInteractorStyle(DefaultInteractorStyle): | @@ -465,6 +473,18 @@ class CrossInteractorStyle(DefaultInteractorStyle): | ||
465 | self.slice_actor = viewer.slice_data.actor | 473 | self.slice_actor = viewer.slice_data.actor |
466 | self.slice_data = viewer.slice_data | 474 | self.slice_data = viewer.slice_data |
467 | 475 | ||
476 | + # tracts | ||
477 | + # self.seed = [0., 0., 0.] | ||
478 | + # slic = sl.Slice() | ||
479 | + # self.affine = slic.affine | ||
480 | + # self.tracker = slic.tracker | ||
481 | + # | ||
482 | + # self.affine_vtk = vtk.vtkMatrix4x4() | ||
483 | + # for row in range(0, 4): | ||
484 | + # for col in range(0, 4): | ||
485 | + # self.affine_vtk.SetElement(row, col, self.affine[row, col]) | ||
486 | + # --- | ||
487 | + | ||
468 | self.picker = vtk.vtkWorldPointPicker() | 488 | self.picker = vtk.vtkWorldPointPicker() |
469 | 489 | ||
470 | self.AddObserver("MouseMoveEvent", self.OnCrossMove) | 490 | self.AddObserver("MouseMoveEvent", self.OnCrossMove) |
@@ -478,6 +498,7 @@ class CrossInteractorStyle(DefaultInteractorStyle): | @@ -478,6 +498,7 @@ class CrossInteractorStyle(DefaultInteractorStyle): | ||
478 | 498 | ||
479 | def CleanUp(self): | 499 | def CleanUp(self): |
480 | self.viewer._set_cross_visibility(0) | 500 | self.viewer._set_cross_visibility(0) |
501 | + Publisher.sendMessage('Remove tracts') | ||
481 | Publisher.sendMessage('Toggle toolbar item', | 502 | Publisher.sendMessage('Toggle toolbar item', |
482 | _id=self.state_code, value=False) | 503 | _id=self.state_code, value=False) |
483 | 504 | ||
@@ -497,12 +518,20 @@ class CrossInteractorStyle(DefaultInteractorStyle): | @@ -497,12 +518,20 @@ class CrossInteractorStyle(DefaultInteractorStyle): | ||
497 | wx, wy, wz = self.viewer.get_coordinate_cursor(mouse_x, mouse_y, self.picker) | 518 | wx, wy, wz = self.viewer.get_coordinate_cursor(mouse_x, mouse_y, self.picker) |
498 | px, py = self.viewer.get_slice_pixel_coord_by_world_pos(wx, wy, wz) | 519 | px, py = self.viewer.get_slice_pixel_coord_by_world_pos(wx, wy, wz) |
499 | coord = self.viewer.calcultate_scroll_position(px, py) | 520 | coord = self.viewer.calcultate_scroll_position(px, py) |
500 | - Publisher.sendMessage('Update cross position', position=(wx, wy, wz)) | 521 | + |
522 | + # Tracts | ||
523 | + # pos_world_aux = np.ones([4, 1]) | ||
524 | + # pos_world_aux[:3, -1] = bases.flip_x((wx, wy, wz))[:3] | ||
525 | + # pos_world = np.linalg.inv(self.affine) @ pos_world_aux | ||
526 | + # seed_aux = pos_world.reshape([1, 4])[0, :3] | ||
527 | + # self.seed = seed_aux[np.newaxis, :] | ||
528 | + # print("Check the seed: ", self.seed) | ||
529 | + # | ||
530 | + | ||
531 | + Publisher.sendMessage('Update cross position', arg=None, position=(wx, wy, wz, 0., 0., 0.)) | ||
501 | self.ScrollSlice(coord) | 532 | self.ScrollSlice(coord) |
502 | - Publisher.sendMessage('Set ball reference position', position=(wx, wy, wz)) | ||
503 | - Publisher.sendMessage('Co-registered points', arg=None, position=(wx, wy, wz, 0., 0., 0.)) | ||
504 | 533 | ||
505 | - iren.Render() | 534 | + # iren.Render() |
506 | 535 | ||
507 | def ScrollSlice(self, coord): | 536 | def ScrollSlice(self, coord): |
508 | if self.orientation == "AXIAL": | 537 | if self.orientation == "AXIAL": |
@@ -522,6 +551,92 @@ class CrossInteractorStyle(DefaultInteractorStyle): | @@ -522,6 +551,92 @@ class CrossInteractorStyle(DefaultInteractorStyle): | ||
522 | index=coord[0]) | 551 | index=coord[0]) |
523 | 552 | ||
524 | 553 | ||
554 | +class TractsInteractorStyle(CrossInteractorStyle): | ||
555 | + """ | ||
556 | + Interactor style responsible for tracts visualization. | ||
557 | + """ | ||
558 | + def __init__(self, viewer): | ||
559 | + CrossInteractorStyle.__init__(self, viewer) | ||
560 | + | ||
561 | + # self.state_code = const.SLICE_STATE_TRACTS | ||
562 | + | ||
563 | + self.viewer = viewer | ||
564 | + # print("Im fucking brilliant!") | ||
565 | + self.tracts = None | ||
566 | + | ||
567 | + # data_dir = b'C:\Users\deoliv1\OneDrive\data\dti' | ||
568 | + # FOD_path = b"sub-P0_dwi_FOD.nii" | ||
569 | + # full_path = os.path.join(data_dir, FOD_path) | ||
570 | + # self.tracker = Trekker.tracker(full_path) | ||
571 | + # self.orientation = viewer.orientation | ||
572 | + # self.slice_actor = viewer.slice_data.actor | ||
573 | + # self.slice_data = viewer.slice_data | ||
574 | + | ||
575 | + # self.picker = vtk.vtkWorldPointPicker() | ||
576 | + | ||
577 | + self.AddObserver("MouseMoveEvent", self.OnTractsMove) | ||
578 | + self.AddObserver("LeftButtonPressEvent", self.OnTractsMouseClick) | ||
579 | + self.AddObserver("LeftButtonReleaseEvent", self.OnTractsReleaseLeftButton) | ||
580 | + | ||
581 | + # def SetUp(self): | ||
582 | + # self.viewer._set_cross_visibility(1) | ||
583 | + # Publisher.sendMessage('Toggle toolbar item', | ||
584 | + # _id=self.state_code, value=True) | ||
585 | + | ||
586 | + # def CleanUp(self): | ||
587 | + # self.viewer._set_cross_visibility(0) | ||
588 | + # Publisher.sendMessage('Toggle toolbar item', | ||
589 | + # _id=self.state_code, value=False) | ||
590 | + | ||
591 | + def OnTractsMove(self, obj, evt): | ||
592 | + # The user moved the mouse with left button pressed | ||
593 | + if self.left_pressed: | ||
594 | + # print("OnTractsMove interactor style") | ||
595 | + # iren = obj.GetInteractor() | ||
596 | + self.ChangeTracts(True) | ||
597 | + | ||
598 | + def OnTractsMouseClick(self, obj, evt): | ||
599 | + # print("Single mouse click") | ||
600 | + # self.tracts = dtr.compute_tracts(self.tracker, self.seed, self.left_pressed) | ||
601 | + self.ChangeTracts(True) | ||
602 | + | ||
603 | + def OnTractsReleaseLeftButton(self, obj, evt): | ||
604 | + # time.sleep(3.) | ||
605 | + self.tracts.stop() | ||
606 | + # self.ChangeCrossPosition(iren) | ||
607 | + | ||
608 | + def ChangeTracts(self, pressed): | ||
609 | + # print("Trying to compute tracts") | ||
610 | + self.tracts = dtr.compute_tracts(self.tracker, self.seed, self.affine_vtk, pressed) | ||
611 | + # mouse_x, mouse_y = iren.GetEventPosition() | ||
612 | + # wx, wy, wz = self.viewer.get_coordinate_cursor(mouse_x, mouse_y, self.picker) | ||
613 | + # px, py = self.viewer.get_slice_pixel_coord_by_world_pos(wx, wy, wz) | ||
614 | + # coord = self.viewer.calcultate_scroll_position(px, py) | ||
615 | + # Publisher.sendMessage('Update cross position', position=(wx, wy, wz)) | ||
616 | + # # self.ScrollSlice(coord) | ||
617 | + # Publisher.sendMessage('Set ball reference position', position=(wx, wy, wz)) | ||
618 | + # Publisher.sendMessage('Co-registered points', arg=None, position=(wx, wy, wz, 0., 0., 0.)) | ||
619 | + | ||
620 | + # iren.Render() | ||
621 | + | ||
622 | + # def ScrollSlice(self, coord): | ||
623 | + # if self.orientation == "AXIAL": | ||
624 | + # Publisher.sendMessage(('Set scroll position', 'SAGITAL'), | ||
625 | + # index=coord[0]) | ||
626 | + # Publisher.sendMessage(('Set scroll position', 'CORONAL'), | ||
627 | + # index=coord[1]) | ||
628 | + # elif self.orientation == "SAGITAL": | ||
629 | + # Publisher.sendMessage(('Set scroll position', 'AXIAL'), | ||
630 | + # index=coord[2]) | ||
631 | + # Publisher.sendMessage(('Set scroll position', 'CORONAL'), | ||
632 | + # index=coord[1]) | ||
633 | + # elif self.orientation == "CORONAL": | ||
634 | + # Publisher.sendMessage(('Set scroll position', 'AXIAL'), | ||
635 | + # index=coord[2]) | ||
636 | + # Publisher.sendMessage(('Set scroll position', 'SAGITAL'), | ||
637 | + # index=coord[0]) | ||
638 | + | ||
639 | + | ||
525 | class WWWLInteractorStyle(DefaultInteractorStyle): | 640 | class WWWLInteractorStyle(DefaultInteractorStyle): |
526 | """ | 641 | """ |
527 | Interactor style responsible for Window Level & Width functionality. | 642 | Interactor style responsible for Window Level & Width functionality. |
@@ -2784,6 +2899,7 @@ class Styles: | @@ -2784,6 +2899,7 @@ class Styles: | ||
2784 | const.SLICE_STATE_SELECT_MASK_PARTS: SelectMaskPartsInteractorStyle, | 2899 | const.SLICE_STATE_SELECT_MASK_PARTS: SelectMaskPartsInteractorStyle, |
2785 | const.SLICE_STATE_FFILL_SEGMENTATION: FloodFillSegmentInteractorStyle, | 2900 | const.SLICE_STATE_FFILL_SEGMENTATION: FloodFillSegmentInteractorStyle, |
2786 | const.SLICE_STATE_CROP_MASK: CropMaskInteractorStyle, | 2901 | const.SLICE_STATE_CROP_MASK: CropMaskInteractorStyle, |
2902 | + const.SLICE_STATE_TRACTS: TractsInteractorStyle, | ||
2787 | } | 2903 | } |
2788 | 2904 | ||
2789 | @classmethod | 2905 | @classmethod |
invesalius/data/surface.py
@@ -383,6 +383,7 @@ class SurfaceManager(): | @@ -383,6 +383,7 @@ class SurfaceManager(): | ||
383 | 383 | ||
384 | actor = vtk.vtkActor() | 384 | actor = vtk.vtkActor() |
385 | actor.SetMapper(mapper) | 385 | actor.SetMapper(mapper) |
386 | + actor.GetProperty().SetBackfaceCulling(1) | ||
386 | 387 | ||
387 | print("BOunds", actor.GetBounds()) | 388 | print("BOunds", actor.GetBounds()) |
388 | 389 | ||
@@ -494,6 +495,7 @@ class SurfaceManager(): | @@ -494,6 +495,7 @@ class SurfaceManager(): | ||
494 | 495 | ||
495 | # Represent an object (geometry & properties) in the rendered scene | 496 | # Represent an object (geometry & properties) in the rendered scene |
496 | actor = vtk.vtkActor() | 497 | actor = vtk.vtkActor() |
498 | + actor.GetProperty().SetBackfaceCulling(1) | ||
497 | actor.SetMapper(mapper) | 499 | actor.SetMapper(mapper) |
498 | 500 | ||
499 | # Set actor colour and transparency | 501 | # Set actor colour and transparency |
@@ -536,6 +538,7 @@ class SurfaceManager(): | @@ -536,6 +538,7 @@ class SurfaceManager(): | ||
536 | 538 | ||
537 | # Represent an object (geometry & properties) in the rendered scene | 539 | # Represent an object (geometry & properties) in the rendered scene |
538 | actor = vtk.vtkActor() | 540 | actor = vtk.vtkActor() |
541 | + actor.GetProperty().SetBackfaceCulling(1) | ||
539 | actor.SetMapper(mapper) | 542 | actor.SetMapper(mapper) |
540 | del mapper | 543 | del mapper |
541 | #Create Surface instance | 544 | #Create Surface instance |
invesalius/data/trackers.py
@@ -62,6 +62,7 @@ def DefaultTracker(tracker_id): | @@ -62,6 +62,7 @@ def DefaultTracker(tracker_id): | ||
62 | # return tracker initialization variable and type of connection | 62 | # return tracker initialization variable and type of connection |
63 | return trck_init, 'wrapper' | 63 | return trck_init, 'wrapper' |
64 | 64 | ||
65 | + | ||
65 | def PolarisTracker(tracker_id): | 66 | def PolarisTracker(tracker_id): |
66 | from wx import ID_OK | 67 | from wx import ID_OK |
67 | trck_init = None | 68 | trck_init = None |
@@ -91,6 +92,7 @@ def PolarisTracker(tracker_id): | @@ -91,6 +92,7 @@ def PolarisTracker(tracker_id): | ||
91 | # return tracker initialization variable and type of connection | 92 | # return tracker initialization variable and type of connection |
92 | return trck_init, lib_mode | 93 | return trck_init, lib_mode |
93 | 94 | ||
95 | + | ||
94 | def CameraTracker(tracker_id): | 96 | def CameraTracker(tracker_id): |
95 | trck_init = None | 97 | trck_init = None |
96 | try: | 98 | try: |
@@ -105,6 +107,7 @@ def CameraTracker(tracker_id): | @@ -105,6 +107,7 @@ def CameraTracker(tracker_id): | ||
105 | # return tracker initialization variable and type of connection | 107 | # return tracker initialization variable and type of connection |
106 | return trck_init, 'wrapper' | 108 | return trck_init, 'wrapper' |
107 | 109 | ||
110 | + | ||
108 | def ClaronTracker(tracker_id): | 111 | def ClaronTracker(tracker_id): |
109 | import invesalius.constants as const | 112 | import invesalius.constants as const |
110 | from invesalius import inv_paths | 113 | from invesalius import inv_paths |
@@ -0,0 +1,471 @@ | @@ -0,0 +1,471 @@ | ||
1 | +# -*- coding: utf-8 -*- | ||
2 | + | ||
3 | +#-------------------------------------------------------------------------- | ||
4 | +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas | ||
5 | +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer | ||
6 | +# Homepage: http://www.softwarepublico.gov.br | ||
7 | +# Contact: invesalius@cti.gov.br | ||
8 | +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) | ||
9 | +#-------------------------------------------------------------------------- | ||
10 | +# Este programa e software livre; voce pode redistribui-lo e/ou | ||
11 | +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme | ||
12 | +# publicada pela Free Software Foundation; de acordo com a versao 2 | ||
13 | +# da Licenca. | ||
14 | +# | ||
15 | +# Este programa eh distribuido na expectativa de ser util, mas SEM | ||
16 | +# QUALQUER GARANTIA; sem mesmo a garantia implicita de | ||
17 | +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM | ||
18 | +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais | ||
19 | +# detalhes. | ||
20 | +#-------------------------------------------------------------------------- | ||
21 | + | ||
22 | +# Author: Victor Hugo Souza (victorhos-at-hotmail.com) | ||
23 | +# Contributions: Dogu Baran Aydogan | ||
24 | +# Initial date: 8 May 2020 | ||
25 | + | ||
26 | +import threading | ||
27 | +import time | ||
28 | + | ||
29 | +import numpy as np | ||
30 | +import queue | ||
31 | +from pubsub import pub as Publisher | ||
32 | +import vtk | ||
33 | + | ||
34 | +import invesalius.constants as const | ||
35 | +import invesalius.data.imagedata_utils as img_utils | ||
36 | + | ||
37 | +# Nice print for arrays | ||
38 | +# np.set_printoptions(precision=2) | ||
39 | +# np.set_printoptions(suppress=True) | ||
40 | + | ||
41 | + | ||
42 | +def compute_directions(trk_n): | ||
43 | + """Compute direction of a single tract in each point and return as an RGB color | ||
44 | + | ||
45 | + :param trk_n: nx3 array of doubles (x, y, z) point coordinates composing the tract | ||
46 | + :type trk_n: numpy.ndarray | ||
47 | + :return: nx3 array of int (x, y, z) RGB colors in the range 0 - 255 | ||
48 | + :rtype: numpy.ndarray | ||
49 | + """ | ||
50 | + | ||
51 | + # trk_d = np.diff(trk_n, axis=0, append=2*trk_n[np.newaxis, -1, :]) | ||
52 | + trk_d = np.diff(trk_n, axis=0, append=trk_n[np.newaxis, -2, :]) | ||
53 | + trk_d[-1, :] *= -1 | ||
54 | + # check that linalg norm makes second norm | ||
55 | + # https://stackoverflow.com/questions/21030391/how-to-normalize-an-array-in-numpy | ||
56 | + direction = 255 * np.absolute((trk_d / np.linalg.norm(trk_d, axis=1)[:, None])) | ||
57 | + return direction.astype(int) | ||
58 | + | ||
59 | + | ||
60 | +def compute_tubes(trk, direction): | ||
61 | + """Compute and assign colors to a vtkTube for visualization of a single tract | ||
62 | + | ||
63 | + :param trk: nx3 array of doubles (x, y, z) point coordinates composing the tract | ||
64 | + :type trk: numpy.ndarray | ||
65 | + :param direction: nx3 array of int (x, y, z) RGB colors in the range 0 - 255 | ||
66 | + :type direction: numpy.ndarray | ||
67 | + :return: a vtkTubeFilter instance | ||
68 | + :rtype: vtkTubeFilter | ||
69 | + """ | ||
70 | + | ||
71 | + numb_points = trk.shape[0] | ||
72 | + points = vtk.vtkPoints() | ||
73 | + lines = vtk.vtkCellArray() | ||
74 | + | ||
75 | + colors = vtk.vtkUnsignedCharArray() | ||
76 | + colors.SetNumberOfComponents(3) | ||
77 | + | ||
78 | + k = 0 | ||
79 | + lines.InsertNextCell(numb_points) | ||
80 | + for j in range(numb_points): | ||
81 | + points.InsertNextPoint(trk[j, :]) | ||
82 | + colors.InsertNextTuple(direction[j, :]) | ||
83 | + lines.InsertCellPoint(k) | ||
84 | + k += 1 | ||
85 | + | ||
86 | + trk_data = vtk.vtkPolyData() | ||
87 | + trk_data.SetPoints(points) | ||
88 | + trk_data.SetLines(lines) | ||
89 | + trk_data.GetPointData().SetScalars(colors) | ||
90 | + | ||
91 | + # make it a tube | ||
92 | + trk_tube = vtk.vtkTubeFilter() | ||
93 | + trk_tube.SetRadius(0.5) | ||
94 | + trk_tube.SetNumberOfSides(4) | ||
95 | + trk_tube.SetInputData(trk_data) | ||
96 | + trk_tube.Update() | ||
97 | + | ||
98 | + return trk_tube | ||
99 | + | ||
100 | + | ||
101 | +def combine_tracts_root(out_list, root, n_block): | ||
102 | + """Adds a set of tracts to given position in a given vtkMultiBlockDataSet | ||
103 | + | ||
104 | + :param out_list: List of vtkTubeFilters representing the tracts | ||
105 | + :type out_list: list | ||
106 | + :param root: A collection of tracts as a vtkMultiBlockDataSet | ||
107 | + :type root: vtkMultiBlockDataSet | ||
108 | + :param n_block: The location in the given vtkMultiBlockDataSet to insert the new tracts | ||
109 | + :type n_block: int | ||
110 | + :return: The updated collection of tracts as a vtkMultiBlockDataSet | ||
111 | + :rtype: vtkMultiBlockDataSet | ||
112 | + """ | ||
113 | + | ||
114 | + # create tracts only when at least one was computed | ||
115 | + # print("Len outlist in root: ", len(out_list)) | ||
116 | + if not out_list.count(None) == len(out_list): | ||
117 | + for n, tube in enumerate(out_list): | ||
118 | + root.SetBlock(n_block + n, tube.GetOutput()) | ||
119 | + | ||
120 | + return root | ||
121 | + | ||
122 | + | ||
123 | +def combine_tracts_branch(out_list): | ||
124 | + """Combines a set of tracts in vtkMultiBlockDataSet | ||
125 | + | ||
126 | + :param out_list: List of vtkTubeFilters representing the tracts | ||
127 | + :type out_list: list | ||
128 | + :return: A collection of tracts as a vtkMultiBlockDataSet | ||
129 | + :rtype: vtkMultiBlockDataSet | ||
130 | + """ | ||
131 | + | ||
132 | + branch = vtk.vtkMultiBlockDataSet() | ||
133 | + # create tracts only when at least one was computed | ||
134 | + # print("Len outlist in root: ", len(out_list)) | ||
135 | + if not out_list.count(None) == len(out_list): | ||
136 | + for n, tube in enumerate(out_list): | ||
137 | + branch.SetBlock(n, tube.GetOutput()) | ||
138 | + | ||
139 | + return branch | ||
140 | + | ||
141 | + | ||
142 | +def tracts_computation(trk_list, root, n_tracts): | ||
143 | + """Convert the list of all computed tracts given by Trekker run and returns a vtkMultiBlockDataSet | ||
144 | + | ||
145 | + :param trk_list: List of lists containing the computed tracts and corresponding coordinates | ||
146 | + :type trk_list: list | ||
147 | + :param root: A collection of tracts as a vtkMultiBlockDataSet | ||
148 | + :type root: vtkMultiBlockDataSet | ||
149 | + :param n_tracts: | ||
150 | + :type n_tracts: int | ||
151 | + :return: The updated collection of tracts as a vtkMultiBlockDataSet | ||
152 | + :rtype: vtkMultiBlockDataSet | ||
153 | + """ | ||
154 | + | ||
155 | + # Transform tracts to array | ||
156 | + trk_arr = [np.asarray(trk_n).T if trk_n else None for trk_n in trk_list] | ||
157 | + | ||
158 | + # Compute the directions | ||
159 | + trk_dir = [compute_directions(trk_n) for trk_n in trk_arr] | ||
160 | + | ||
161 | + # Compute the vtk tubes | ||
162 | + out_list = [compute_tubes(trk_arr_n, trk_dir_n) for trk_arr_n, trk_dir_n in zip(trk_arr, trk_dir)] | ||
163 | + | ||
164 | + root = combine_tracts_root(out_list, root, n_tracts) | ||
165 | + | ||
166 | + return root | ||
167 | + | ||
168 | + | ||
169 | +def compute_tracts(trekker, position, affine, affine_vtk, n_tracts): | ||
170 | + """ Compute tractograms using the Trekker library. | ||
171 | + | ||
172 | + :param trekker: Trekker library instance | ||
173 | + :type trekker: Trekker.T | ||
174 | + :param position: 3 double coordinates (x, y, z) in list or array | ||
175 | + :type position: list | ||
176 | + :param affine: 4 x 4 numpy double array | ||
177 | + :type affine: numpy.ndarray | ||
178 | + :param affine_vtk: vtkMatrix4x4 isntance with affine transformation matrix | ||
179 | + :type affine_vtk: vtkMatrix4x4 | ||
180 | + :param n_tracts: number of tracts to compute | ||
181 | + :type n_tracts: int | ||
182 | + """ | ||
183 | + | ||
184 | + # during neuronavigation, root needs to be initialized outside the while loop so the new tracts | ||
185 | + # can be appended to the root block set | ||
186 | + root = vtk.vtkMultiBlockDataSet() | ||
187 | + # Juuso's | ||
188 | + # seed = np.array([[-8.49, -8.39, 2.5]]) | ||
189 | + # Baran M1 | ||
190 | + # seed = np.array([[27.53, -77.37, 46.42]]) | ||
191 | + seed_trk = img_utils.convert_world_to_voxel(position, affine) | ||
192 | + # print("seed example: {}".format(seed_trk)) | ||
193 | + trekker.seed_coordinates(np.repeat(seed_trk, n_tracts, axis=0)) | ||
194 | + # print("trk list len: ", len(trekker.run())) | ||
195 | + trk_list = trekker.run() | ||
196 | + if trk_list: | ||
197 | + root = tracts_computation(trk_list, root, 0) | ||
198 | + Publisher.sendMessage('Remove tracts') | ||
199 | + Publisher.sendMessage('Update tracts', flag=True, root=root, affine_vtk=affine_vtk) | ||
200 | + else: | ||
201 | + Publisher.sendMessage('Remove tracts') | ||
202 | + | ||
203 | + | ||
204 | +def tracts_computation_branch(trk_list): | ||
205 | + """Convert the list of all computed tracts given by Trekker run and returns a vtkMultiBlockDataSet | ||
206 | + | ||
207 | + :param trk_list: List of lists containing the computed tracts and corresponding coordinates | ||
208 | + :type trk_list: list | ||
209 | + :return: The collection of tracts as a vtkMultiBlockDataSet | ||
210 | + :rtype: vtkMultiBlockDataSet | ||
211 | + """ | ||
212 | + # Transform tracts to array | ||
213 | + trk_arr = [np.asarray(trk_n).T if trk_n else None for trk_n in trk_list] | ||
214 | + # Compute the directions | ||
215 | + trk_dir = [compute_directions(trk_n) for trk_n in trk_arr] | ||
216 | + # Compute the vtk tubes | ||
217 | + tube_list = [compute_tubes(trk_arr_n, trk_dir_n) for trk_arr_n, trk_dir_n in zip(trk_arr, trk_dir)] | ||
218 | + branch = combine_tracts_branch(tube_list) | ||
219 | + | ||
220 | + return branch | ||
221 | + | ||
222 | + | ||
223 | +class ComputeTractsThread(threading.Thread): | ||
224 | + | ||
225 | + def __init__(self, inp, affine_vtk, coord_tracts_queue, tracts_queue, event, sle): | ||
226 | + """Class (threading) to compute real time tractography data for visualization. | ||
227 | + | ||
228 | + Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/) | ||
229 | + For VTK visualization, each tract (fiber) is a constructed as a tube and many tubes combined in one | ||
230 | + vtkMultiBlockDataSet named as a branch. Several branches are combined in another vtkMultiBlockDataSet named as | ||
231 | + bundle, to obtain fast computation and visualization. The bundle dataset is mapped to a single vtkActor. | ||
232 | + Mapper and Actor are computer in the data/viewer_volume.py module for easier handling in the invesalius 3D scene. | ||
233 | + | ||
234 | + Sleep function in run method is used to avoid blocking GUI and more fluent, real-time navigation | ||
235 | + | ||
236 | + :param inp: List of inputs: trekker instance, affine numpy array, seed_offset, seed_radius, n_threads | ||
237 | + :type inp: list | ||
238 | + :param affine_vtk: Affine matrix in vtkMatrix4x4 instance to update objects position in 3D scene | ||
239 | + :type affine_vtk: vtkMatrix4x4 | ||
240 | + :param coord_queue: Queue instance that manage coordinates read from tracking device and coregistered | ||
241 | + :type coord_queue: queue.Queue | ||
242 | + :param visualization_queue: Queue instance that manage coordinates to be visualized | ||
243 | + :type visualization_queue: queue.Queue | ||
244 | + :param event: Threading event to coordinate when tasks as done and allow UI release | ||
245 | + :type event: threading.Event | ||
246 | + :param sle: Sleep pause in seconds | ||
247 | + :type sle: float | ||
248 | + """ | ||
249 | + | ||
250 | + threading.Thread.__init__(self, name='ComputeTractsThread') | ||
251 | + self.inp = inp | ||
252 | + self.affine_vtk = affine_vtk | ||
253 | + # self.coord_queue = coord_queue | ||
254 | + self.coord_tracts_queue = coord_tracts_queue | ||
255 | + self.tracts_queue = tracts_queue | ||
256 | + # self.visualization_queue = visualization_queue | ||
257 | + self.event = event | ||
258 | + self.sle = sle | ||
259 | + | ||
260 | + def run(self): | ||
261 | + | ||
262 | + trekker, affine, offset, n_tracts_total, seed_radius, n_threads = self.inp | ||
263 | + # n_threads = n_tracts_total | ||
264 | + p_old = np.array([[0., 0., 0.]]) | ||
265 | + n_tracts = 0 | ||
266 | + | ||
267 | + # Compute the tracts | ||
268 | + # print('ComputeTractsThread: event {}'.format(self.event.is_set())) | ||
269 | + while not self.event.is_set(): | ||
270 | + try: | ||
271 | + # print("Computing tracts") | ||
272 | + # get from the queue the coordinates, coregistration transformation matrix, and flipped matrix | ||
273 | + m_img_flip = self.coord_tracts_queue.get_nowait() | ||
274 | + # coord, m_img, m_img_flip = self.coord_queue.get_nowait() | ||
275 | + # print('ComputeTractsThread: get {}'.format(count)) | ||
276 | + | ||
277 | + # 20200402: in this new refactored version the m_img comes different than the position | ||
278 | + # the new version m_img is already flixped in y, which means that Y is negative | ||
279 | + # if only the Y is negative maybe no need for the flip_x funtcion at all in the navigation | ||
280 | + # but check all coord_queue before why now the m_img comes different than position | ||
281 | + # 20200403: indeed flip_x is just a -1 multiplication to the Y coordinate, remove function flip_x | ||
282 | + # m_img_flip = m_img.copy() | ||
283 | + # m_img_flip[1, -1] = -m_img_flip[1, -1] | ||
284 | + | ||
285 | + # translate the coordinate along the normal vector of the object/coil | ||
286 | + coord_offset = m_img_flip[:3, -1] - offset * m_img_flip[:3, 2] | ||
287 | + # coord_offset = np.array([[27.53, -77.37, 46.42]]) | ||
288 | + dist = abs(np.linalg.norm(p_old - np.asarray(coord_offset))) | ||
289 | + p_old = coord_offset.copy() | ||
290 | + | ||
291 | + # print("p_new_shape", coord_offset.shape) | ||
292 | + # print("m_img_flip_shape", m_img_flip.shape) | ||
293 | + seed_trk = img_utils.convert_world_to_voxel(coord_offset, affine) | ||
294 | + # Juuso's | ||
295 | + # seed_trk = np.array([[-8.49, -8.39, 2.5]]) | ||
296 | + # Baran M1 | ||
297 | + # seed_trk = np.array([[27.53, -77.37, 46.42]]) | ||
298 | + # print("Seed: {}".format(seed)) | ||
299 | + | ||
300 | + # set the seeds for trekker, one seed is repeated n_threads times | ||
301 | + # trekker has internal multiprocessing approach done in C. Here the number of available threads is give, | ||
302 | + # but in case a large number of tracts is requested, it will compute all in parallel automatically | ||
303 | + # for a more fluent navigation, better to compute the maximum number the computer handles | ||
304 | + trekker.seed_coordinates(np.repeat(seed_trk, n_threads, axis=0)) | ||
305 | + | ||
306 | + # run the trekker, this is the slowest line of code, be careful to just use once! | ||
307 | + trk_list = trekker.run() | ||
308 | + | ||
309 | + if trk_list: | ||
310 | + # print("dist: {}".format(dist)) | ||
311 | + if dist >= seed_radius: | ||
312 | + # when moving the coil further than the seed_radius restart the bundle computation | ||
313 | + bundle = vtk.vtkMultiBlockDataSet() | ||
314 | + n_branches = 0 | ||
315 | + branch = tracts_computation_branch(trk_list) | ||
316 | + bundle.SetBlock(n_branches, branch) | ||
317 | + n_branches += 1 | ||
318 | + n_tracts = branch.GetNumberOfBlocks() | ||
319 | + | ||
320 | + # TODO: maybe keep computing even if reaches the maximum | ||
321 | + elif dist < seed_radius and n_tracts < n_tracts_total: | ||
322 | + # compute tracts blocks and add to bungle until reaches the maximum number of tracts | ||
323 | + branch = tracts_computation_branch(trk_list) | ||
324 | + if bundle: | ||
325 | + bundle.SetBlock(n_branches, branch) | ||
326 | + n_tracts += branch.GetNumberOfBlocks() | ||
327 | + n_branches += 1 | ||
328 | + | ||
329 | + else: | ||
330 | + bundle = None | ||
331 | + | ||
332 | + # rethink if this should be inside the if condition, it may lock the thread if no tracts are found | ||
333 | + # use no wait to ensure maximum speed and avoid visualizing old tracts in the queue, this might | ||
334 | + # be more evident in slow computer or for heavier tract computations, it is better slow update | ||
335 | + # than visualizing old data | ||
336 | + # self.visualization_queue.put_nowait([coord, m_img, bundle]) | ||
337 | + self.tracts_queue.put_nowait((bundle, self.affine_vtk, coord_offset)) | ||
338 | + # print('ComputeTractsThread: put {}'.format(count)) | ||
339 | + | ||
340 | + self.coord_tracts_queue.task_done() | ||
341 | + # self.coord_queue.task_done() | ||
342 | + # print('ComputeTractsThread: done {}'.format(count)) | ||
343 | + | ||
344 | + # sleep required to prevent user interface from being unresponsive | ||
345 | + time.sleep(self.sle) | ||
346 | + # if no coordinates pass | ||
347 | + except queue.Empty: | ||
348 | + # print("Empty queue in tractography") | ||
349 | + pass | ||
350 | + # if queue is full mark as done (may not be needed in this new "nowait" method) | ||
351 | + except queue.Full: | ||
352 | + # self.coord_queue.task_done() | ||
353 | + self.coord_tracts_queue.task_done() | ||
354 | + | ||
355 | + | ||
356 | +class ComputeTractsThreadSingleBlock(threading.Thread): | ||
357 | + | ||
358 | + def __init__(self, inp, affine_vtk, coord_queue, visualization_queue, event, sle): | ||
359 | + """Class (threading) to compute real time tractography data for visualization in a single loop. | ||
360 | + | ||
361 | + Different than ComputeTractsThread because it does not keep adding tracts to the bundle until maximum, | ||
362 | + is reached. It actually compute all requested tracts at once. (Might be deleted in the future)! | ||
363 | + Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/) | ||
364 | + For VTK visualization, each tract (fiber) is a constructed as a tube and many tubes combined in one | ||
365 | + vtkMultiBlockDataSet named as a branch. Several branches are combined in another vtkMultiBlockDataSet named as | ||
366 | + bundle, to obtain fast computation and visualization. The bundle dataset is mapped to a single vtkActor. | ||
367 | + Mapper and Actor are computer in the data/viewer_volume.py module for easier handling in the invesalius 3D scene. | ||
368 | + | ||
369 | + Sleep function in run method is used to avoid blocking GUI and more fluent, real-time navigation | ||
370 | + | ||
371 | + :param inp: List of inputs: trekker instance, affine numpy array, seed_offset, seed_radius, n_threads | ||
372 | + :type inp: list | ||
373 | + :param affine_vtk: Affine matrix in vtkMatrix4x4 instance to update objects position in 3D scene | ||
374 | + :type affine_vtk: vtkMatrix4x4 | ||
375 | + :param coord_queue: Queue instance that manage coordinates read from tracking device and coregistered | ||
376 | + :type coord_queue: queue.Queue | ||
377 | + :param visualization_queue: Queue instance that manage coordinates to be visualized | ||
378 | + :type visualization_queue: queue.Queue | ||
379 | + :param event: Threading event to coordinate when tasks as done and allow UI release | ||
380 | + :type event: threading.Event | ||
381 | + :param sle: Sleep pause in seconds | ||
382 | + :type sle: float | ||
383 | + """ | ||
384 | + | ||
385 | + threading.Thread.__init__(self, name='ComputeTractsThread') | ||
386 | + self.inp = inp | ||
387 | + self.affine_vtk = affine_vtk | ||
388 | + self.coord_queue = coord_queue | ||
389 | + self.visualization_queue = visualization_queue | ||
390 | + self.event = event | ||
391 | + self.sle = sle | ||
392 | + | ||
393 | + def run(self): | ||
394 | + | ||
395 | + trekker, affine, offset, n_tracts_total, seed_radius, n_threads = self.inp | ||
396 | + # as a single block, computes all the maximum number of tracts at once, not optimal for navigation | ||
397 | + n_threads = n_tracts_total | ||
398 | + p_old = np.array([[0., 0., 0.]]) | ||
399 | + root = vtk.vtkMultiBlockDataSet() | ||
400 | + | ||
401 | + # Compute the tracts | ||
402 | + # print('ComputeTractsThread: event {}'.format(self.event.is_set())) | ||
403 | + while not self.event.is_set(): | ||
404 | + try: | ||
405 | + coord, m_img, m_img_flip = self.coord_queue.get_nowait() | ||
406 | + | ||
407 | + # translate the coordinate along the normal vector of the object/coil | ||
408 | + coord_offset = m_img_flip[:3, -1] - offset * m_img_flip[:3, 2] | ||
409 | + # coord_offset = np.array([[27.53, -77.37, 46.42]]) | ||
410 | + dist = abs(np.linalg.norm(p_old - np.asarray(coord_offset))) | ||
411 | + p_old = coord_offset.copy() | ||
412 | + seed_trk = img_utils.convert_world_to_voxel(coord_offset, affine) | ||
413 | + # Juuso's | ||
414 | + # seed_trk = np.array([[-8.49, -8.39, 2.5]]) | ||
415 | + # Baran M1 | ||
416 | + # seed_trk = np.array([[27.53, -77.37, 46.42]]) | ||
417 | + # print("Seed: {}".format(seed)) | ||
418 | + | ||
419 | + # set the seeds for trekker, one seed is repeated n_threads times | ||
420 | + # trekker has internal multiprocessing approach done in C. Here the number of available threads is give, | ||
421 | + # but in case a large number of tracts is requested, it will compute all in parallel automatically | ||
422 | + # for a more fluent navigation, better to compute the maximum number the computer handles | ||
423 | + trekker.seed_coordinates(np.repeat(seed_trk, n_threads, axis=0)) | ||
424 | + # run the trekker, this is the slowest line of code, be careful to just use once! | ||
425 | + trk_list = trekker.run() | ||
426 | + | ||
427 | + if trk_list: | ||
428 | + # if the seed is outside the defined radius, restart the bundle computation | ||
429 | + if dist >= seed_radius: | ||
430 | + root = tracts_computation(trk_list, root, 0) | ||
431 | + self.visualization_queue.put_nowait((coord, m_img, root)) | ||
432 | + | ||
433 | + self.coord_queue.task_done() | ||
434 | + time.sleep(self.sle) | ||
435 | + except queue.Empty: | ||
436 | + pass | ||
437 | + except queue.Full: | ||
438 | + self.coord_queue.task_done() | ||
439 | + | ||
440 | + | ||
441 | +def set_trekker_parameters(trekker, params): | ||
442 | + """Set all user-defined parameters for tractography computation using the Trekker library | ||
443 | + | ||
444 | + :param trekker: Trekker instance | ||
445 | + :type trekker: Trekker.T | ||
446 | + :param params: Dictionary containing the parameters values to set in Trekker. Initial values are in constants.py | ||
447 | + :type params: dict | ||
448 | + :return: List containing the Trekker instance and number of threads for parallel processing in the computer | ||
449 | + :rtype: list | ||
450 | + """ | ||
451 | + trekker.seed_maxTrials(params['seed_max']) | ||
452 | + # trekker.stepSize(params['step_size']) | ||
453 | + trekker.minFODamp(params['min_fod']) | ||
454 | + # trekker.probeQuality(params['probe_quality']) | ||
455 | + # trekker.maxEstInterval(params['max_interval']) | ||
456 | + # trekker.minRadiusOfCurvature(params['min_radius_curv']) | ||
457 | + # trekker.probeLength(params['probe_length']) | ||
458 | + trekker.writeInterval(params['write_interval']) | ||
459 | + trekker.maxLength(params['max_lenth']) | ||
460 | + trekker.minLength(params['min_lenth']) | ||
461 | + trekker.maxSamplingPerStep(params['max_sampling_step']) | ||
462 | + | ||
463 | + # check number if number of cores is valid in configuration file, | ||
464 | + # otherwise use the maximum number of threads which is usually 2*N_CPUS | ||
465 | + n_threads = 2 * const.N_CPU | ||
466 | + if isinstance((params['numb_threads']), int) and params['numb_threads'] <= 2*const.N_CPU: | ||
467 | + n_threads = params['numb_threads'] | ||
468 | + | ||
469 | + trekker.numberOfThreads(n_threads) | ||
470 | + # print("Trekker config updated: n_threads, {}; seed_max, {}".format(n_threads, params['seed_max'])) | ||
471 | + return trekker, n_threads |
invesalius/data/trigger.py
@@ -39,7 +39,7 @@ class Trigger(threading.Thread): | @@ -39,7 +39,7 @@ class Trigger(threading.Thread): | ||
39 | try: | 39 | try: |
40 | import serial | 40 | import serial |
41 | 41 | ||
42 | - self.trigger_init = serial.Serial('COM1', baudrate=9600, timeout=0) | 42 | + self.trigger_init = serial.Serial('COM5', baudrate=115200, timeout=0) |
43 | self.COM = True | 43 | self.COM = True |
44 | 44 | ||
45 | except: | 45 | except: |
@@ -82,3 +82,91 @@ class Trigger(threading.Thread): | @@ -82,3 +82,91 @@ class Trigger(threading.Thread): | ||
82 | if self.trigger_init: | 82 | if self.trigger_init: |
83 | self.trigger_init.close() | 83 | self.trigger_init.close() |
84 | return | 84 | return |
85 | + | ||
86 | + | ||
87 | +class TriggerNew(threading.Thread): | ||
88 | + | ||
89 | + def __init__(self, trigger_queue, event, sle): | ||
90 | + """Class (threading) to compute real time tractography data for visualization in a single loop. | ||
91 | + | ||
92 | + Different than ComputeTractsThread because it does not keep adding tracts to the bundle until maximum, | ||
93 | + is reached. It actually compute all requested tracts at once. (Might be deleted in the future)! | ||
94 | + Tracts are computed using the Trekker library by Baran Aydogan (https://dmritrekker.github.io/) | ||
95 | + For VTK visualization, each tract (fiber) is a constructed as a tube and many tubes combined in one | ||
96 | + vtkMultiBlockDataSet named as a branch. Several branches are combined in another vtkMultiBlockDataSet named as | ||
97 | + bundle, to obtain fast computation and visualization. The bundle dataset is mapped to a single vtkActor. | ||
98 | + Mapper and Actor are computer in the data/viewer_volume.py module for easier handling in the invesalius 3D scene. | ||
99 | + | ||
100 | + Sleep function in run method is used to avoid blocking GUI and more fluent, real-time navigation | ||
101 | + | ||
102 | + :param inp: List of inputs: trekker instance, affine numpy array, seed_offset, seed_radius, n_threads | ||
103 | + :type inp: list | ||
104 | + :param affine_vtk: Affine matrix in vtkMatrix4x4 instance to update objects position in 3D scene | ||
105 | + :type affine_vtk: vtkMatrix4x4 | ||
106 | + :param coord_queue: Queue instance that manage coordinates read from tracking device and coregistered | ||
107 | + :type coord_queue: queue.Queue | ||
108 | + :param visualization_queue: Queue instance that manage coordinates to be visualized | ||
109 | + :type visualization_queue: queue.Queue | ||
110 | + :param event: Threading event to coordinate when tasks as done and allow UI release | ||
111 | + :type event: threading.Event | ||
112 | + :param sle: Sleep pause in seconds | ||
113 | + :type sle: float | ||
114 | + """ | ||
115 | + | ||
116 | + threading.Thread.__init__(self, name='Trigger') | ||
117 | + | ||
118 | + self.trigger_init = None | ||
119 | + self.stylusplh = False | ||
120 | + # self.COM = False | ||
121 | + self.__bind_events() | ||
122 | + try: | ||
123 | + import serial | ||
124 | + | ||
125 | + self.trigger_init = serial.Serial('COM5', baudrate=115200, timeout=0) | ||
126 | + # self.COM = True | ||
127 | + | ||
128 | + except: | ||
129 | + #wx.MessageBox(_('Connection with port COM1 failed'), _('Communication error'), wx.OK | wx.ICON_ERROR) | ||
130 | + print("Trigger init error: Connection with port COM failed") | ||
131 | + # self.COM = False | ||
132 | + pass | ||
133 | + | ||
134 | + # self.coord_queue = coord_queue | ||
135 | + self.trigger_queue = trigger_queue | ||
136 | + self.event = event | ||
137 | + self.sle = sle | ||
138 | + | ||
139 | + def run(self): | ||
140 | + | ||
141 | + while not self.event.is_set(): | ||
142 | + trigger_on = False | ||
143 | + try: | ||
144 | + self.trigger_init.write(b'0') | ||
145 | + sleep(0.3) | ||
146 | + lines = self.trigger_init.readlines() | ||
147 | + # Following lines can simulate a trigger in 3 sec repetitions | ||
148 | + # sleep(3) | ||
149 | + # lines = True | ||
150 | + if lines: | ||
151 | + trigger_on = True | ||
152 | + # wx.CallAfter(Publisher.sendMessage, 'Create marker') | ||
153 | + | ||
154 | + if self.stylusplh: | ||
155 | + trigger_on = True | ||
156 | + # wx.CallAfter(Publisher.sendMessage, 'Create marker') | ||
157 | + self.stylusplh = False | ||
158 | + | ||
159 | + self.trigger_queue.put_nowait(trigger_on) | ||
160 | + sleep(self.sle) | ||
161 | + | ||
162 | + except: | ||
163 | + print("Trigger not read, error") | ||
164 | + pass | ||
165 | + | ||
166 | + # except queue.Empty: | ||
167 | + # pass | ||
168 | + # except queue.Full: | ||
169 | + # self.coord_queue.task_done() | ||
170 | + else: | ||
171 | + if self.trigger_init: | ||
172 | + self.trigger_init.close() |
invesalius/data/viewer_slice.py
@@ -573,7 +573,7 @@ class Viewer(wx.Panel): | @@ -573,7 +573,7 @@ class Viewer(wx.Panel): | ||
573 | 573 | ||
574 | self.cross.SetFocalPoint((ux, uy, uz)) | 574 | self.cross.SetFocalPoint((ux, uy, uz)) |
575 | self.ScrollSlice(coord) | 575 | self.ScrollSlice(coord) |
576 | - Publisher.sendMessage('Set ball reference position', position=(ux, uy, uz)) | 576 | + self.interactor.Render() |
577 | 577 | ||
578 | def ScrollSlice(self, coord): | 578 | def ScrollSlice(self, coord): |
579 | if self.orientation == "AXIAL": | 579 | if self.orientation == "AXIAL": |
@@ -817,9 +817,9 @@ class Viewer(wx.Panel): | @@ -817,9 +817,9 @@ class Viewer(wx.Panel): | ||
817 | ('Set scroll position', | 817 | ('Set scroll position', |
818 | self.orientation)) | 818 | self.orientation)) |
819 | Publisher.subscribe(self.__update_cross_position, | 819 | Publisher.subscribe(self.__update_cross_position, |
820 | - 'Update cross position') | ||
821 | - Publisher.subscribe(self.UpdateSlicesNavigation, | ||
822 | - 'Co-registered points') | 820 | + 'Update cross position') |
821 | + # Publisher.subscribe(self.UpdateSlicesNavigation, | ||
822 | + # 'Co-registered points') | ||
823 | ### | 823 | ### |
824 | # Publisher.subscribe(self.ChangeBrushColour, | 824 | # Publisher.subscribe(self.ChangeBrushColour, |
825 | # 'Add mask') | 825 | # 'Add mask') |
@@ -1136,8 +1136,9 @@ class Viewer(wx.Panel): | @@ -1136,8 +1136,9 @@ class Viewer(wx.Panel): | ||
1136 | 1136 | ||
1137 | renderer.AddActor(cross_actor) | 1137 | renderer.AddActor(cross_actor) |
1138 | 1138 | ||
1139 | - def __update_cross_position(self, position): | ||
1140 | - self.cross.SetFocalPoint(position) | 1139 | + def __update_cross_position(self, arg, position): |
1140 | + # self.cross.SetFocalPoint(position[:3]) | ||
1141 | + self.UpdateSlicesNavigation(None, position) | ||
1141 | 1142 | ||
1142 | def _set_cross_visibility(self, visibility): | 1143 | def _set_cross_visibility(self, visibility): |
1143 | self.cross_actor.SetVisibility(visibility) | 1144 | self.cross_actor.SetVisibility(visibility) |
@@ -1296,8 +1297,8 @@ class Viewer(wx.Panel): | @@ -1296,8 +1297,8 @@ class Viewer(wx.Panel): | ||
1296 | if self.state == const.SLICE_STATE_CROSS: | 1297 | if self.state == const.SLICE_STATE_CROSS: |
1297 | # Update other slice's cross according to the new focal point from | 1298 | # Update other slice's cross according to the new focal point from |
1298 | # the actual orientation. | 1299 | # the actual orientation. |
1299 | - focal_point = self.cross.GetFocalPoint() | ||
1300 | - Publisher.sendMessage('Update cross position', position=focal_point) | 1300 | + x, y, z = self.cross.GetFocalPoint() |
1301 | + Publisher.sendMessage('Update cross position', arg=None, position=(x, y, z, 0., 0., 0.)) | ||
1301 | Publisher.sendMessage('Update slice viewer') | 1302 | Publisher.sendMessage('Update slice viewer') |
1302 | else: | 1303 | else: |
1303 | self.interactor.Render() | 1304 | self.interactor.Render() |
@@ -1509,7 +1510,6 @@ class Viewer(wx.Panel): | @@ -1509,7 +1510,6 @@ class Viewer(wx.Panel): | ||
1509 | 1510 | ||
1510 | def UpdateCross(self, coord): | 1511 | def UpdateCross(self, coord): |
1511 | self.cross.SetFocalPoint(coord) | 1512 | self.cross.SetFocalPoint(coord) |
1512 | - Publisher.sendMessage('Set ball reference position', position=self.cross.GetFocalPoint()) | ||
1513 | Publisher.sendMessage('Co-registered points', arg=None, position=(coord[0], coord[1], coord[2], 0., 0., 0.)) | 1513 | Publisher.sendMessage('Co-registered points', arg=None, position=(coord[0], coord[1], coord[2], 0., 0., 0.)) |
1514 | self.OnScrollBar() | 1514 | self.OnScrollBar() |
1515 | self.interactor.Render() | 1515 | self.interactor.Render() |
invesalius/data/viewer_volume.py
@@ -19,9 +19,10 @@ | @@ -19,9 +19,10 @@ | ||
19 | # detalhes. | 19 | # detalhes. |
20 | #-------------------------------------------------------------------------- | 20 | #-------------------------------------------------------------------------- |
21 | 21 | ||
22 | -from math import cos, sin | 22 | +# from math import cos, sin |
23 | import os | 23 | import os |
24 | import sys | 24 | import sys |
25 | +import time | ||
25 | 26 | ||
26 | import numpy as np | 27 | import numpy as np |
27 | from numpy.core.umath_tests import inner1d | 28 | from numpy.core.umath_tests import inner1d |
@@ -36,6 +37,7 @@ from imageio import imsave | @@ -36,6 +37,7 @@ from imageio import imsave | ||
36 | 37 | ||
37 | import invesalius.constants as const | 38 | import invesalius.constants as const |
38 | import invesalius.data.bases as bases | 39 | import invesalius.data.bases as bases |
40 | +import invesalius.data.slice_ as sl | ||
39 | import invesalius.data.transformations as tr | 41 | import invesalius.data.transformations as tr |
40 | import invesalius.data.vtk_utils as vtku | 42 | import invesalius.data.vtk_utils as vtku |
41 | import invesalius.project as prj | 43 | import invesalius.project as prj |
@@ -168,6 +170,10 @@ class Viewer(wx.Panel): | @@ -168,6 +170,10 @@ class Viewer(wx.Panel): | ||
168 | #self.pTarget = [0., 0., 0.] | 170 | #self.pTarget = [0., 0., 0.] |
169 | 171 | ||
170 | # self.obj_axes = None | 172 | # self.obj_axes = None |
173 | + self.x_actor = None | ||
174 | + self.y_actor = None | ||
175 | + self.z_actor = None | ||
176 | + self.mark_actor = None | ||
171 | 177 | ||
172 | self._mode_cross = False | 178 | self._mode_cross = False |
173 | self._to_show_ball = 0 | 179 | self._to_show_ball = 0 |
@@ -188,12 +194,31 @@ class Viewer(wx.Panel): | @@ -188,12 +194,31 @@ class Viewer(wx.Panel): | ||
188 | self.anglethreshold = const.COIL_ANGLES_THRESHOLD | 194 | self.anglethreshold = const.COIL_ANGLES_THRESHOLD |
189 | self.distthreshold = const.COIL_COORD_THRESHOLD | 195 | self.distthreshold = const.COIL_COORD_THRESHOLD |
190 | 196 | ||
197 | + # for DTI support tests | ||
198 | + # self.ntimes = False | ||
199 | + # self._to_show_stream = True | ||
200 | + # data_dir = b'C:\Users\deoliv1\OneDrive\data\dti' | ||
201 | + # nii_path = b'sub-P0_dwi_FOD.nii' | ||
202 | + # trk_path = os.path.join(data_dir, nii_path) | ||
203 | + # self.tracker_FOD = Trekker.tracker(trk_path) | ||
204 | + # proj = prj.Project() | ||
205 | + # self.affine = np.identity(4) | ||
206 | + self.actor_tracts = None | ||
207 | + self.actor_peel = None | ||
208 | + self.seed_offset = const.SEED_OFFSET | ||
209 | + # Publisher.sendMessage('Get affine matrix') | ||
210 | + | ||
211 | + # initialize Trekker parameters | ||
212 | + slic = sl.Slice() | ||
213 | + affine = slic.affine | ||
214 | + self.affine_vtk = vtku.numpy_to_vtkMatrix4x4(affine) | ||
215 | + | ||
191 | def __bind_events(self): | 216 | def __bind_events(self): |
192 | Publisher.subscribe(self.LoadActor, | 217 | Publisher.subscribe(self.LoadActor, |
193 | 'Load surface actor into viewer') | 218 | 'Load surface actor into viewer') |
194 | Publisher.subscribe(self.RemoveActor, | 219 | Publisher.subscribe(self.RemoveActor, |
195 | 'Remove surface actor from viewer') | 220 | 'Remove surface actor from viewer') |
196 | - Publisher.subscribe(self.OnShowSurface, 'Show surface') | 221 | + # Publisher.subscribe(self.OnShowSurface, 'Show surface') |
197 | Publisher.subscribe(self.UpdateRender, | 222 | Publisher.subscribe(self.UpdateRender, |
198 | 'Render volume viewer') | 223 | 'Render volume viewer') |
199 | Publisher.subscribe(self.ChangeBackgroundColour, | 224 | Publisher.subscribe(self.ChangeBackgroundColour, |
@@ -225,7 +250,8 @@ class Viewer(wx.Panel): | @@ -225,7 +250,8 @@ class Viewer(wx.Panel): | ||
225 | Publisher.subscribe(self.LoadSlicePlane, 'Load slice plane') | 250 | Publisher.subscribe(self.LoadSlicePlane, 'Load slice plane') |
226 | 251 | ||
227 | Publisher.subscribe(self.ResetCamClippingRange, 'Reset cam clipping range') | 252 | Publisher.subscribe(self.ResetCamClippingRange, 'Reset cam clipping range') |
228 | - Publisher.subscribe(self.SetVolumeCamera, 'Co-registered points') | 253 | + # Publisher.subscribe(self.SetVolumeCamera, 'Update cross position') |
254 | + # Publisher.subscribe(self.SetVolumeCamera, 'Co-registered points') | ||
229 | # Publisher.subscribe(self.SetVolumeCamera, 'Set camera in volume') | 255 | # Publisher.subscribe(self.SetVolumeCamera, 'Set camera in volume') |
230 | Publisher.subscribe(self.SetVolumeCameraState, 'Update volume camera state') | 256 | Publisher.subscribe(self.SetVolumeCameraState, 'Update volume camera state') |
231 | 257 | ||
@@ -255,8 +281,8 @@ class Viewer(wx.Panel): | @@ -255,8 +281,8 @@ class Viewer(wx.Panel): | ||
255 | 281 | ||
256 | Publisher.subscribe(self.RemoveVolume, 'Remove Volume') | 282 | Publisher.subscribe(self.RemoveVolume, 'Remove Volume') |
257 | 283 | ||
258 | - Publisher.subscribe(self.SetBallReferencePosition, | ||
259 | - 'Set ball reference position') | 284 | + Publisher.subscribe(self.UpdateCameraBallPosition, |
285 | + 'Update cross position') | ||
260 | Publisher.subscribe(self._check_ball_reference, 'Enable style') | 286 | Publisher.subscribe(self._check_ball_reference, 'Enable style') |
261 | Publisher.subscribe(self._uncheck_ball_reference, 'Disable style') | 287 | Publisher.subscribe(self._uncheck_ball_reference, 'Disable style') |
262 | 288 | ||
@@ -283,11 +309,17 @@ class Viewer(wx.Panel): | @@ -283,11 +309,17 @@ class Viewer(wx.Panel): | ||
283 | Publisher.subscribe(self.OnUpdateObjectTargetGuide, 'Update object matrix') | 309 | Publisher.subscribe(self.OnUpdateObjectTargetGuide, 'Update object matrix') |
284 | Publisher.subscribe(self.OnUpdateTargetCoordinates, 'Update target') | 310 | Publisher.subscribe(self.OnUpdateTargetCoordinates, 'Update target') |
285 | Publisher.subscribe(self.OnRemoveTarget, 'Disable or enable coil tracker') | 311 | Publisher.subscribe(self.OnRemoveTarget, 'Disable or enable coil tracker') |
286 | - # Publisher.subscribe(self.UpdateObjectTargetView, 'Co-registered points') | ||
287 | Publisher.subscribe(self.OnTargetMarkerTransparency, 'Set target transparency') | 312 | Publisher.subscribe(self.OnTargetMarkerTransparency, 'Set target transparency') |
288 | Publisher.subscribe(self.OnUpdateAngleThreshold, 'Update angle threshold') | 313 | Publisher.subscribe(self.OnUpdateAngleThreshold, 'Update angle threshold') |
289 | Publisher.subscribe(self.OnUpdateDistThreshold, 'Update dist threshold') | 314 | Publisher.subscribe(self.OnUpdateDistThreshold, 'Update dist threshold') |
290 | 315 | ||
316 | + Publisher.subscribe(self.OnUpdateTracts, 'Update tracts') | ||
317 | + Publisher.subscribe(self.OnRemoveTracts, 'Remove tracts') | ||
318 | + Publisher.subscribe(self.UpdateSeedOffset, 'Update seed offset') | ||
319 | + Publisher.subscribe(self.UpdateMarkerOffsetState, 'Update marker offset state') | ||
320 | + Publisher.subscribe(self.UpdateMarkerOffsetPosition, 'Update marker offset') | ||
321 | + Publisher.subscribe(self.AddPeeledSurface, 'Update peel') | ||
322 | + | ||
291 | def SetStereoMode(self, mode): | 323 | def SetStereoMode(self, mode): |
292 | ren_win = self.interactor.GetRenderWindow() | 324 | ren_win = self.interactor.GetRenderWindow() |
293 | 325 | ||
@@ -319,13 +351,23 @@ class Viewer(wx.Panel): | @@ -319,13 +351,23 @@ class Viewer(wx.Panel): | ||
319 | def _check_ball_reference(self, style): | 351 | def _check_ball_reference(self, style): |
320 | if style == const.SLICE_STATE_CROSS: | 352 | if style == const.SLICE_STATE_CROSS: |
321 | self._mode_cross = True | 353 | self._mode_cross = True |
322 | - self._check_and_set_ball_visibility() | 354 | + # self._check_and_set_ball_visibility() |
355 | + self._ball_ref_visibility = True | ||
356 | + # if self._to_show_ball: | ||
357 | + if not self.ball_actor: | ||
358 | + self.CreateBallReference() | ||
359 | + | ||
323 | self.interactor.Render() | 360 | self.interactor.Render() |
324 | 361 | ||
325 | def _uncheck_ball_reference(self, style): | 362 | def _uncheck_ball_reference(self, style): |
326 | if style == const.SLICE_STATE_CROSS: | 363 | if style == const.SLICE_STATE_CROSS: |
327 | self._mode_cross = False | 364 | self._mode_cross = False |
328 | - self.RemoveBallReference() | 365 | + # self.RemoveBallReference() |
366 | + self._ball_ref_visibility = False | ||
367 | + if self.ball_actor: | ||
368 | + self.ren.RemoveActor(self.ball_actor) | ||
369 | + self.ball_actor = None | ||
370 | + | ||
329 | self.interactor.Render() | 371 | self.interactor.Render() |
330 | 372 | ||
331 | def OnSensors(self, probe_id, ref_id, obj_id=0): | 373 | def OnSensors(self, probe_id, ref_id, obj_id=0): |
@@ -385,12 +427,12 @@ class Viewer(wx.Panel): | @@ -385,12 +427,12 @@ class Viewer(wx.Panel): | ||
385 | self.probe = self.ref = self.obj = False | 427 | self.probe = self.ref = self.obj = False |
386 | self.interactor.Render() | 428 | self.interactor.Render() |
387 | 429 | ||
388 | - def OnShowSurface(self, index, visibility): | ||
389 | - if visibility: | ||
390 | - self._to_show_ball += 1 | ||
391 | - else: | ||
392 | - self._to_show_ball -= 1 | ||
393 | - self._check_and_set_ball_visibility() | 430 | + # def OnShowSurface(self, index, visibility): |
431 | + # if visibility: | ||
432 | + # self._to_show_ball += 1 | ||
433 | + # else: | ||
434 | + # self._to_show_ball -= 1 | ||
435 | + # self._check_and_set_ball_visibility() | ||
394 | 436 | ||
395 | def OnStartSeed(self): | 437 | def OnStartSeed(self): |
396 | self.seed_points = [] | 438 | self.seed_points = [] |
@@ -485,8 +527,8 @@ class Viewer(wx.Panel): | @@ -485,8 +527,8 @@ class Viewer(wx.Panel): | ||
485 | if (volumes.GetNumberOfItems()): | 527 | if (volumes.GetNumberOfItems()): |
486 | self.ren.RemoveVolume(volumes.GetLastProp()) | 528 | self.ren.RemoveVolume(volumes.GetLastProp()) |
487 | self.interactor.Render() | 529 | self.interactor.Render() |
488 | - self._to_show_ball -= 1 | ||
489 | - self._check_and_set_ball_visibility() | 530 | + # self._to_show_ball -= 1 |
531 | + # self._check_and_set_ball_visibility() | ||
490 | 532 | ||
491 | def RemoveActors(self, actors): | 533 | def RemoveActors(self, actors): |
492 | "Remove a list of actors" | 534 | "Remove a list of actors" |
@@ -534,11 +576,11 @@ class Viewer(wx.Panel): | @@ -534,11 +576,11 @@ class Viewer(wx.Panel): | ||
534 | Markers created by navigation tools and rendered in volume viewer. | 576 | Markers created by navigation tools and rendered in volume viewer. |
535 | """ | 577 | """ |
536 | self.ball_id = ball_id | 578 | self.ball_id = ball_id |
537 | - x, y, z = bases.flip_x(coord) | 579 | + coord_flip = bases.flip_x_m(coord)[:3, 0] |
538 | 580 | ||
539 | ball_ref = vtk.vtkSphereSource() | 581 | ball_ref = vtk.vtkSphereSource() |
540 | ball_ref.SetRadius(size) | 582 | ball_ref.SetRadius(size) |
541 | - ball_ref.SetCenter(x, y, z) | 583 | + ball_ref.SetCenter(coord_flip) |
542 | 584 | ||
543 | mapper = vtk.vtkPolyDataMapper() | 585 | mapper = vtk.vtkPolyDataMapper() |
544 | mapper.SetInputConnection(ball_ref.GetOutputPort()) | 586 | mapper.SetInputConnection(ball_ref.GetOutputPort()) |
@@ -557,6 +599,35 @@ class Viewer(wx.Panel): | @@ -557,6 +599,35 @@ class Viewer(wx.Panel): | ||
557 | #self.UpdateRender() | 599 | #self.UpdateRender() |
558 | self.Refresh() | 600 | self.Refresh() |
559 | 601 | ||
602 | + def add_marker(self, coord, color): | ||
603 | + """Simplified version for creating a spherical marker in the 3D scene | ||
604 | + | ||
605 | + :param coord: | ||
606 | + :param color: | ||
607 | + :return: vtkActor | ||
608 | + """ | ||
609 | + | ||
610 | + # x, y, z = coord | ||
611 | + | ||
612 | + ball_ref = vtk.vtkSphereSource() | ||
613 | + ball_ref.SetRadius(2) | ||
614 | + ball_ref.SetCenter(coord) | ||
615 | + | ||
616 | + mapper = vtk.vtkPolyDataMapper() | ||
617 | + mapper.SetInputConnection(ball_ref.GetOutputPort()) | ||
618 | + | ||
619 | + prop = vtk.vtkProperty() | ||
620 | + prop.SetColor(color) | ||
621 | + | ||
622 | + actor = vtk.vtkActor() | ||
623 | + actor.SetMapper(mapper) | ||
624 | + actor.SetProperty(prop) | ||
625 | + actor.GetProperty().SetOpacity(.5) | ||
626 | + | ||
627 | + # ren.AddActor(actor) | ||
628 | + | ||
629 | + return actor | ||
630 | + | ||
560 | def HideAllMarkers(self, indexes): | 631 | def HideAllMarkers(self, indexes): |
561 | ballid = indexes | 632 | ballid = indexes |
562 | for i in range(0, ballid): | 633 | for i in range(0, ballid): |
@@ -610,7 +681,6 @@ class Viewer(wx.Panel): | @@ -610,7 +681,6 @@ class Viewer(wx.Panel): | ||
610 | self.staticballs[index].GetProperty().SetColor(color) | 681 | self.staticballs[index].GetProperty().SetColor(color) |
611 | self.Refresh() | 682 | self.Refresh() |
612 | 683 | ||
613 | - | ||
614 | def OnTargetMarkerTransparency(self, status, index): | 684 | def OnTargetMarkerTransparency(self, status, index): |
615 | if status: | 685 | if status: |
616 | self.staticballs[index].GetProperty().SetOpacity(1) | 686 | self.staticballs[index].GetProperty().SetOpacity(1) |
@@ -1104,7 +1174,6 @@ class Viewer(wx.Panel): | @@ -1104,7 +1174,6 @@ class Viewer(wx.Panel): | ||
1104 | cam.SetFocalPoint(cam_focus) | 1174 | cam.SetFocalPoint(cam_focus) |
1105 | cam.SetPosition(cam_pos) | 1175 | cam.SetPosition(cam_pos) |
1106 | 1176 | ||
1107 | - | ||
1108 | def CreateBallReference(self): | 1177 | def CreateBallReference(self): |
1109 | """ | 1178 | """ |
1110 | Red sphere on volume visualization to reference center of | 1179 | Red sphere on volume visualization to reference center of |
@@ -1129,31 +1198,14 @@ class Viewer(wx.Panel): | @@ -1129,31 +1198,14 @@ class Viewer(wx.Panel): | ||
1129 | 1198 | ||
1130 | self.ren.AddActor(self.ball_actor) | 1199 | self.ren.AddActor(self.ball_actor) |
1131 | 1200 | ||
1132 | - def ActivateBallReference(self): | ||
1133 | - self._mode_cross = True | ||
1134 | - self._ball_ref_visibility = True | ||
1135 | - if self._to_show_ball: | ||
1136 | - if not self.ball_actor: | ||
1137 | - self.CreateBallReference() | ||
1138 | - | ||
1139 | - def RemoveBallReference(self): | ||
1140 | - self._mode_cross = False | ||
1141 | - self._ball_ref_visibility = False | ||
1142 | - if self.ball_actor: | ||
1143 | - self.ren.RemoveActor(self.ball_actor) | ||
1144 | - self.ball_actor = None | 1201 | + def UpdateCameraBallPosition(self, arg, position): |
1202 | + coord_flip = bases.flip_x_m(position[:3])[:3, 0] | ||
1203 | + self.ball_actor.SetPosition(coord_flip) | ||
1204 | + self.SetVolumeCamera(coord_flip) | ||
1145 | 1205 | ||
1146 | def SetBallReferencePosition(self, position): | 1206 | def SetBallReferencePosition(self, position): |
1147 | - if self._to_show_ball: | ||
1148 | - if not self.ball_actor: | ||
1149 | - self.ActivateBallReference() | ||
1150 | - | ||
1151 | - coord = position | ||
1152 | - x, y, z = bases.flip_x(coord) | ||
1153 | - self.ball_actor.SetPosition(x, y, z) | ||
1154 | - | ||
1155 | - else: | ||
1156 | - self.RemoveBallReference() | 1207 | + coord_flip = bases.flip_x_m(position[:3])[:3, 0] |
1208 | + self.ball_actor.SetPosition(coord_flip) | ||
1157 | 1209 | ||
1158 | def CreateObjectPolyData(self, filename): | 1210 | def CreateObjectPolyData(self, filename): |
1159 | """ | 1211 | """ |
@@ -1225,7 +1277,14 @@ class Viewer(wx.Panel): | @@ -1225,7 +1277,14 @@ class Viewer(wx.Panel): | ||
1225 | self.obj_actor.GetProperty().SetOpacity(.4) | 1277 | self.obj_actor.GetProperty().SetOpacity(.4) |
1226 | self.obj_actor.SetVisibility(0) | 1278 | self.obj_actor.SetVisibility(0) |
1227 | 1279 | ||
1280 | + self.x_actor = self.add_line([0., 0., 0.], [1., 0., 0.], color=[.0, .0, 1.0]) | ||
1281 | + self.y_actor = self.add_line([0., 0., 0.], [0., 1., 0.], color=[.0, 1.0, .0]) | ||
1282 | + self.z_actor = self.add_line([0., 0., 0.], [0., 0., 1.], color=[1.0, .0, .0]) | ||
1283 | + | ||
1228 | self.ren.AddActor(self.obj_actor) | 1284 | self.ren.AddActor(self.obj_actor) |
1285 | + self.ren.AddActor(self.x_actor) | ||
1286 | + self.ren.AddActor(self.y_actor) | ||
1287 | + self.ren.AddActor(self.z_actor) | ||
1229 | 1288 | ||
1230 | # self.obj_axes = vtk.vtkAxesActor() | 1289 | # self.obj_axes = vtk.vtkAxesActor() |
1231 | # self.obj_axes.SetShaftTypeToCylinder() | 1290 | # self.obj_axes.SetShaftTypeToCylinder() |
@@ -1236,25 +1295,88 @@ class Viewer(wx.Panel): | @@ -1236,25 +1295,88 @@ class Viewer(wx.Panel): | ||
1236 | 1295 | ||
1237 | # self.ren.AddActor(self.obj_axes) | 1296 | # self.ren.AddActor(self.obj_axes) |
1238 | 1297 | ||
1239 | - def OnNavigationStatus(self, status): | ||
1240 | - self.nav_status = status | 1298 | + def add_line(self, p1, p2, color=[0.0, 0.0, 1.0]): |
1299 | + line = vtk.vtkLineSource() | ||
1300 | + line.SetPoint1(p1) | ||
1301 | + line.SetPoint2(p2) | ||
1302 | + | ||
1303 | + mapper = vtk.vtkPolyDataMapper() | ||
1304 | + mapper.SetInputConnection(line.GetOutputPort()) | ||
1305 | + | ||
1306 | + actor = vtk.vtkActor() | ||
1307 | + actor.SetMapper(mapper) | ||
1308 | + actor.GetProperty().SetColor(color) | ||
1309 | + | ||
1310 | + return actor | ||
1311 | + | ||
1312 | + def AddPeeledSurface(self, flag, actor): | ||
1313 | + if self.actor_peel: | ||
1314 | + self.ren.RemoveActor(self.actor_peel) | ||
1315 | + self.actor_peel = None | ||
1316 | + if flag and actor: | ||
1317 | + self.ren.AddActor(actor) | ||
1318 | + self.actor_peel = actor | ||
1319 | + self.Refresh() | ||
1320 | + | ||
1321 | + def OnNavigationStatus(self, nav_status, vis_status): | ||
1322 | + self.nav_status = nav_status | ||
1323 | + self.tracts_status = vis_status[1] | ||
1241 | self.pTarget = self.CenterOfMass() | 1324 | self.pTarget = self.CenterOfMass() |
1242 | - if self.obj_actor and self.nav_status: | ||
1243 | - self.obj_actor.SetVisibility(self.obj_state) | ||
1244 | - if not self.obj_state: | ||
1245 | - self.Refresh() | 1325 | + |
1326 | + if self.nav_status: | ||
1327 | + if self.obj_actor: | ||
1328 | + self.obj_actor.SetVisibility(self.obj_state) | ||
1329 | + self.x_actor.SetVisibility(self.obj_state) | ||
1330 | + self.y_actor.SetVisibility(self.obj_state) | ||
1331 | + self.z_actor.SetVisibility(self.obj_state) | ||
1332 | + | ||
1333 | + self.Refresh() | ||
1334 | + | ||
1335 | + def UpdateSeedOffset(self, data): | ||
1336 | + self.seed_offset = data | ||
1337 | + | ||
1338 | + def UpdateMarkerOffsetState(self, create=False): | ||
1339 | + if create: | ||
1340 | + if not self.mark_actor: | ||
1341 | + self.mark_actor = self.add_marker([0., 0., 0.], color=[0., 1., 1.]) | ||
1342 | + self.ren.AddActor(self.mark_actor) | ||
1343 | + else: | ||
1344 | + if self.mark_actor: | ||
1345 | + self.ren.RemoveActor(self.mark_actor) | ||
1346 | + self.mark_actor = None | ||
1347 | + self.Refresh() | ||
1348 | + | ||
1349 | + def CreateMarkerOffset(self): | ||
1350 | + self.mark_actor = self.add_marker([0., 0., 0.], color=[0., 1., 1.]) | ||
1351 | + self.ren.AddActor(self.mark_actor) | ||
1352 | + self.Refresh() | ||
1353 | + | ||
1354 | + def UpdateMarkerOffsetPosition(self, coord_offset): | ||
1355 | + self.mark_actor.SetPosition(coord_offset) | ||
1356 | + self.Refresh() | ||
1246 | 1357 | ||
1247 | def UpdateObjectOrientation(self, m_img, coord): | 1358 | def UpdateObjectOrientation(self, m_img, coord): |
1248 | - m_img[:3, -1] = np.asmatrix(bases.flip_x_m((m_img[0, -1], m_img[1, -1], m_img[2, -1]))).reshape([3, 1]) | 1359 | + # print("Update object orientation") |
1249 | 1360 | ||
1250 | - m_img_vtk = vtk.vtkMatrix4x4() | 1361 | + m_img_flip = m_img.copy() |
1362 | + m_img_flip[1, -1] = -m_img_flip[1, -1] | ||
1251 | 1363 | ||
1252 | - for row in range(0, 4): | ||
1253 | - for col in range(0, 4): | ||
1254 | - m_img_vtk.SetElement(row, col, m_img[row, col]) | 1364 | + # translate coregistered coordinate to display a marker where Trekker seed is computed |
1365 | + # coord_offset = m_img_flip[:3, -1] - self.seed_offset * m_img_flip[:3, 2] | ||
1366 | + | ||
1367 | + # print("m_img copy in viewer_vol: {}".format(m_img_copy)) | ||
1368 | + | ||
1369 | + # m_img[:3, 0] is from posterior to anterior direction of the coil | ||
1370 | + # m_img[:3, 1] is from left to right direction of the coil | ||
1371 | + # m_img[:3, 2] is from bottom to up direction of the coil | ||
1372 | + | ||
1373 | + m_img_vtk = vtku.numpy_to_vtkMatrix4x4(m_img_flip) | ||
1255 | 1374 | ||
1256 | self.obj_actor.SetUserMatrix(m_img_vtk) | 1375 | self.obj_actor.SetUserMatrix(m_img_vtk) |
1257 | # self.obj_axes.SetUserMatrix(m_rot_vtk) | 1376 | # self.obj_axes.SetUserMatrix(m_rot_vtk) |
1377 | + self.x_actor.SetUserMatrix(m_img_vtk) | ||
1378 | + self.y_actor.SetUserMatrix(m_img_vtk) | ||
1379 | + self.z_actor.SetUserMatrix(m_img_vtk) | ||
1258 | 1380 | ||
1259 | self.Refresh() | 1381 | self.Refresh() |
1260 | 1382 | ||
@@ -1267,13 +1389,42 @@ class Viewer(wx.Panel): | @@ -1267,13 +1389,42 @@ class Viewer(wx.Panel): | ||
1267 | else: | 1389 | else: |
1268 | if self.obj_actor: | 1390 | if self.obj_actor: |
1269 | self.ren.RemoveActor(self.obj_actor) | 1391 | self.ren.RemoveActor(self.obj_actor) |
1392 | + self.ren.RemoveActor(self.x_actor) | ||
1393 | + self.ren.RemoveActor(self.y_actor) | ||
1394 | + self.ren.RemoveActor(self.z_actor) | ||
1395 | + self.ren.RemoveActor(self.mark_actor) | ||
1270 | self.obj_actor = None | 1396 | self.obj_actor = None |
1397 | + self.x_actor = None | ||
1398 | + self.y_actor = None | ||
1399 | + self.z_actor = None | ||
1400 | + self.mark_actor = None | ||
1271 | self.Refresh() | 1401 | self.Refresh() |
1272 | 1402 | ||
1273 | def UpdateShowObjectState(self, state): | 1403 | def UpdateShowObjectState(self, state): |
1274 | self.obj_state = state | 1404 | self.obj_state = state |
1275 | if self.obj_actor and not self.obj_state: | 1405 | if self.obj_actor and not self.obj_state: |
1276 | self.obj_actor.SetVisibility(self.obj_state) | 1406 | self.obj_actor.SetVisibility(self.obj_state) |
1407 | + self.x_actor.SetVisibility(self.obj_state) | ||
1408 | + self.y_actor.SetVisibility(self.obj_state) | ||
1409 | + self.z_actor.SetVisibility(self.obj_state) | ||
1410 | + | ||
1411 | + self.Refresh() | ||
1412 | + | ||
1413 | + def OnUpdateTracts(self, evt=None, flag=None, actor=None, root=None, affine_vtk=None, count=0): | ||
1414 | + mapper = vtk.vtkCompositePolyDataMapper2() | ||
1415 | + mapper.SetInputDataObject(root) | ||
1416 | + | ||
1417 | + self.actor_tracts = vtk.vtkActor() | ||
1418 | + self.actor_tracts.SetMapper(mapper) | ||
1419 | + self.actor_tracts.SetUserMatrix(affine_vtk) | ||
1420 | + | ||
1421 | + self.ren.AddActor(self.actor_tracts) | ||
1422 | + self.Refresh() | ||
1423 | + | ||
1424 | + def OnRemoveTracts(self): | ||
1425 | + if self.actor_tracts: | ||
1426 | + self.ren.RemoveActor(self.actor_tracts) | ||
1427 | + self.actor_tracts = None | ||
1277 | self.Refresh() | 1428 | self.Refresh() |
1278 | 1429 | ||
1279 | def __bind_events_wx(self): | 1430 | def __bind_events_wx(self): |
@@ -1448,10 +1599,12 @@ class Viewer(wx.Panel): | @@ -1448,10 +1599,12 @@ class Viewer(wx.Panel): | ||
1448 | def SetVolumeCameraState(self, camera_state): | 1599 | def SetVolumeCameraState(self, camera_state): |
1449 | self.camera_state = camera_state | 1600 | self.camera_state = camera_state |
1450 | 1601 | ||
1451 | - def SetVolumeCamera(self, arg, position): | 1602 | + # def SetVolumeCamera(self, arg, position): |
1603 | + def SetVolumeCamera(self, cam_focus): | ||
1452 | if self.camera_state: | 1604 | if self.camera_state: |
1453 | # TODO: exclude dependency on initial focus | 1605 | # TODO: exclude dependency on initial focus |
1454 | - cam_focus = np.array(bases.flip_x(position[:3])) | 1606 | + # cam_focus = np.array(bases.flip_x(position[:3])) |
1607 | + # cam_focus = np.array(bases.flip_x(position)) | ||
1455 | cam = self.ren.GetActiveCamera() | 1608 | cam = self.ren.GetActiveCamera() |
1456 | 1609 | ||
1457 | if self.initial_focus is None: | 1610 | if self.initial_focus is None: |
@@ -1545,16 +1698,16 @@ class Viewer(wx.Panel): | @@ -1545,16 +1698,16 @@ class Viewer(wx.Panel): | ||
1545 | def OnShowRaycasting(self): | 1698 | def OnShowRaycasting(self): |
1546 | if not self.raycasting_volume: | 1699 | if not self.raycasting_volume: |
1547 | self.raycasting_volume = True | 1700 | self.raycasting_volume = True |
1548 | - self._to_show_ball += 1 | ||
1549 | - self._check_and_set_ball_visibility() | 1701 | + # self._to_show_ball += 1 |
1702 | + # self._check_and_set_ball_visibility() | ||
1550 | if self.on_wl: | 1703 | if self.on_wl: |
1551 | self.text.Show() | 1704 | self.text.Show() |
1552 | 1705 | ||
1553 | def OnHideRaycasting(self): | 1706 | def OnHideRaycasting(self): |
1554 | self.raycasting_volume = False | 1707 | self.raycasting_volume = False |
1555 | self.text.Hide() | 1708 | self.text.Hide() |
1556 | - self._to_show_ball -= 1 | ||
1557 | - self._check_and_set_ball_visibility() | 1709 | + # self._to_show_ball -= 1 |
1710 | + # self._check_and_set_ball_visibility() | ||
1558 | 1711 | ||
1559 | def OnSize(self, evt): | 1712 | def OnSize(self, evt): |
1560 | self.UpdateRender() | 1713 | self.UpdateRender() |
@@ -1581,16 +1734,16 @@ class Viewer(wx.Panel): | @@ -1581,16 +1734,16 @@ class Viewer(wx.Panel): | ||
1581 | 1734 | ||
1582 | #self.ShowOrientationCube() | 1735 | #self.ShowOrientationCube() |
1583 | self.interactor.Render() | 1736 | self.interactor.Render() |
1584 | - self._to_show_ball += 1 | ||
1585 | - self._check_and_set_ball_visibility() | 1737 | + # self._to_show_ball += 1 |
1738 | + # self._check_and_set_ball_visibility() | ||
1586 | 1739 | ||
1587 | def RemoveActor(self, actor): | 1740 | def RemoveActor(self, actor): |
1588 | utils.debug("RemoveActor") | 1741 | utils.debug("RemoveActor") |
1589 | ren = self.ren | 1742 | ren = self.ren |
1590 | ren.RemoveActor(actor) | 1743 | ren.RemoveActor(actor) |
1591 | self.interactor.Render() | 1744 | self.interactor.Render() |
1592 | - self._to_show_ball -= 1 | ||
1593 | - self._check_and_set_ball_visibility() | 1745 | + # self._to_show_ball -= 1 |
1746 | + # self._check_and_set_ball_visibility() | ||
1594 | 1747 | ||
1595 | def RemoveAllActor(self): | 1748 | def RemoveAllActor(self): |
1596 | utils.debug("RemoveAllActor") | 1749 | utils.debug("RemoveAllActor") |
@@ -1602,7 +1755,8 @@ class Viewer(wx.Panel): | @@ -1602,7 +1755,8 @@ class Viewer(wx.Panel): | ||
1602 | 1755 | ||
1603 | def LoadVolume(self, volume, colour, ww, wl): | 1756 | def LoadVolume(self, volume, colour, ww, wl): |
1604 | self.raycasting_volume = True | 1757 | self.raycasting_volume = True |
1605 | - self._to_show_ball += 1 | 1758 | + # self._to_show_ball += 1 |
1759 | + # self._check_and_set_ball_visibility() | ||
1606 | 1760 | ||
1607 | self.light = self.ren.GetLights().GetNextItem() | 1761 | self.light = self.ren.GetLights().GetNextItem() |
1608 | 1762 | ||
@@ -1622,15 +1776,14 @@ class Viewer(wx.Panel): | @@ -1622,15 +1776,14 @@ class Viewer(wx.Panel): | ||
1622 | self.ren.ResetCamera() | 1776 | self.ren.ResetCamera() |
1623 | self.ren.ResetCameraClippingRange() | 1777 | self.ren.ResetCameraClippingRange() |
1624 | 1778 | ||
1625 | - self._check_and_set_ball_visibility() | ||
1626 | self.UpdateRender() | 1779 | self.UpdateRender() |
1627 | 1780 | ||
1628 | def UnloadVolume(self, volume): | 1781 | def UnloadVolume(self, volume): |
1629 | self.ren.RemoveVolume(volume) | 1782 | self.ren.RemoveVolume(volume) |
1630 | del volume | 1783 | del volume |
1631 | self.raycasting_volume = False | 1784 | self.raycasting_volume = False |
1632 | - self._to_show_ball -= 1 | ||
1633 | - self._check_and_set_ball_visibility() | 1785 | + # self._to_show_ball -= 1 |
1786 | + # self._check_and_set_ball_visibility() | ||
1634 | 1787 | ||
1635 | def OnSetViewAngle(self, view): | 1788 | def OnSetViewAngle(self, view): |
1636 | self.SetViewAngle(view) | 1789 | self.SetViewAngle(view) |
@@ -1777,16 +1930,17 @@ class Viewer(wx.Panel): | @@ -1777,16 +1930,17 @@ class Viewer(wx.Panel): | ||
1777 | self.SetViewAngle(const.VOL_ISO) | 1930 | self.SetViewAngle(const.VOL_ISO) |
1778 | self.repositioned_coronal_plan = 1 | 1931 | self.repositioned_coronal_plan = 1 |
1779 | 1932 | ||
1780 | - def _check_and_set_ball_visibility(self): | ||
1781 | - #TODO: When creating Raycasting volume and cross is pressed, it is not | ||
1782 | - # automatically creating the ball reference. | ||
1783 | - if self._mode_cross: | ||
1784 | - if self._to_show_ball > 0 and not self._ball_ref_visibility: | ||
1785 | - self.ActivateBallReference() | ||
1786 | - self.interactor.Render() | ||
1787 | - elif not self._to_show_ball and self._ball_ref_visibility: | ||
1788 | - self.RemoveBallReference() | ||
1789 | - self.interactor.Render() | 1933 | + # def _check_and_set_ball_visibility(self): |
1934 | + # #TODO: When creating Raycasting volume and cross is pressed, it is not | ||
1935 | + # # automatically creating the ball reference. | ||
1936 | + # # print("mode_cross, show_ball, ball_vis ", self._mode_cross, self._to_show_ball, self._ball_ref_visibility) | ||
1937 | + # if self._mode_cross: | ||
1938 | + # if self._to_show_ball > 0 and not self._ball_ref_visibility: | ||
1939 | + # self.ActivateBallReference() | ||
1940 | + # self.interactor.Render() | ||
1941 | + # elif not self._to_show_ball and self._ball_ref_visibility: | ||
1942 | + # self.RemoveBallReference() | ||
1943 | + # self.interactor.Render() | ||
1790 | 1944 | ||
1791 | class SlicePlane: | 1945 | class SlicePlane: |
1792 | def __init__(self): | 1946 | def __init__(self): |
invesalius/data/vtk_utils.py
@@ -287,3 +287,21 @@ class TextZero(object): | @@ -287,3 +287,21 @@ class TextZero(object): | ||
287 | # font.SetWeight(wx.FONTWEIGHT_BOLD) | 287 | # font.SetWeight(wx.FONTWEIGHT_BOLD) |
288 | font.SetSymbolicSize(self.symbolic_syze) | 288 | font.SetSymbolicSize(self.symbolic_syze) |
289 | canvas.draw_text(self.text, (x, y), font=font) | 289 | canvas.draw_text(self.text, (x, y), font=font) |
290 | + | ||
291 | + | ||
292 | +def numpy_to_vtkMatrix4x4(affine): | ||
293 | + """ | ||
294 | + Convert a numpy 4x4 array to a vtk 4x4 matrix | ||
295 | + :param affine: 4x4 array | ||
296 | + :return: vtkMatrix4x4 object representing the affine | ||
297 | + """ | ||
298 | + # test for type and shape of affine matrix | ||
299 | + # assert isinstance(affine, np.ndarray) | ||
300 | + assert affine.shape == (4, 4) | ||
301 | + | ||
302 | + affine_vtk = vtk.vtkMatrix4x4() | ||
303 | + for row in range(0, 4): | ||
304 | + for col in range(0, 4): | ||
305 | + affine_vtk.SetElement(row, col, affine[row, col]) | ||
306 | + | ||
307 | + return affine_vtk |
invesalius/gui/dialogs.py
@@ -387,6 +387,15 @@ def ShowImportOtherFilesDialog(id_type): | @@ -387,6 +387,15 @@ def ShowImportOtherFilesDialog(id_type): | ||
387 | if id_type == const.ID_NIFTI_IMPORT: | 387 | if id_type == const.ID_NIFTI_IMPORT: |
388 | dlg.SetMessage(_("Import NIFTi 1 file")) | 388 | dlg.SetMessage(_("Import NIFTi 1 file")) |
389 | dlg.SetWildcard(WILDCARD_NIFTI) | 389 | dlg.SetWildcard(WILDCARD_NIFTI) |
390 | + elif id_type == const.ID_TREKKER_MASK: | ||
391 | + dlg.SetMessage(_("Import Trekker mask")) | ||
392 | + dlg.SetWildcard(WILDCARD_NIFTI) | ||
393 | + elif id_type == const.ID_TREKKER_IMG: | ||
394 | + dlg.SetMessage(_("Import Trekker anatomical image")) | ||
395 | + dlg.SetWildcard(WILDCARD_NIFTI) | ||
396 | + elif id_type == const.ID_TREKKER_FOD: | ||
397 | + dlg.SetMessage(_("Import Trekker FOD")) | ||
398 | + dlg.SetWildcard(WILDCARD_NIFTI) | ||
390 | elif id_type == const.ID_PARREC_IMPORT: | 399 | elif id_type == const.ID_PARREC_IMPORT: |
391 | dlg.SetMessage(_("Import PAR/REC file")) | 400 | dlg.SetMessage(_("Import PAR/REC file")) |
392 | dlg.SetWildcard(WILDCARD_PARREC) | 401 | dlg.SetWildcard(WILDCARD_PARREC) |
@@ -508,79 +517,11 @@ def ShowSaveAsProjectDialog(default_filename=None): | @@ -508,79 +517,11 @@ def ShowSaveAsProjectDialog(default_filename=None): | ||
508 | return filename, wildcard == INV_COMPRESSED | 517 | return filename, wildcard == INV_COMPRESSED |
509 | 518 | ||
510 | 519 | ||
511 | -# Dialog for neuronavigation markers | ||
512 | -def ShowSaveMarkersDialog(default_filename=None): | ||
513 | - current_dir = os.path.abspath(".") | ||
514 | - dlg = wx.FileDialog(None, | ||
515 | - _("Save markers as..."), # title | ||
516 | - "", # last used directory | ||
517 | - default_filename, | ||
518 | - _("Markers files (*.mks)|*.mks"), | ||
519 | - wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) | ||
520 | - # dlg.SetFilterIndex(0) # default is VTI | ||
521 | - | ||
522 | - filename = None | ||
523 | - try: | ||
524 | - if dlg.ShowModal() == wx.ID_OK: | ||
525 | - filename = dlg.GetPath() | ||
526 | - ok = 1 | ||
527 | - else: | ||
528 | - ok = 0 | ||
529 | - except(wx._core.PyAssertionError): # TODO: fix win64 | ||
530 | - filename = dlg.GetPath() | ||
531 | - ok = 1 | ||
532 | - | ||
533 | - if (ok): | ||
534 | - extension = "mks" | ||
535 | - if sys.platform != 'win32': | ||
536 | - if filename.split(".")[-1] != extension: | ||
537 | - filename = filename + "." + extension | ||
538 | - | ||
539 | - os.chdir(current_dir) | ||
540 | - return filename | ||
541 | - | ||
542 | -def ShowSaveCoordsDialog(default_filename=None): | ||
543 | - current_dir = os.path.abspath(".") | ||
544 | - dlg = wx.FileDialog(None, | ||
545 | - _("Save coords as..."), # title | ||
546 | - "", # last used directory | ||
547 | - default_filename, | ||
548 | - _("Coordinates files (*.csv)|*.csv"), | ||
549 | - wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) | ||
550 | - # dlg.SetFilterIndex(0) # default is VTI | ||
551 | - | ||
552 | - filename = None | ||
553 | - try: | ||
554 | - if dlg.ShowModal() == wx.ID_OK: | ||
555 | - filename = dlg.GetPath() | ||
556 | - ok = 1 | ||
557 | - else: | ||
558 | - ok = 0 | ||
559 | - except(wx._core.PyAssertionError): # TODO: fix win64 | ||
560 | - filename = dlg.GetPath() | ||
561 | - ok = 1 | 520 | +def ShowLoadSaveDialog(message=_(u"Load File"), current_dir=os.path.abspath("."), style=wx.FD_OPEN | wx.FD_CHANGE_DIR, |
521 | + wildcard=_("Registration files (*.obr)|*.obr"), default_filename="", save_ext=None): | ||
562 | 522 | ||
563 | - if (ok): | ||
564 | - extension = "csv" | ||
565 | - if sys.platform != 'win32': | ||
566 | - if filename.split(".")[-1] != extension: | ||
567 | - filename = filename + "." + extension | ||
568 | - | ||
569 | - os.chdir(current_dir) | ||
570 | - return filename | ||
571 | - | ||
572 | - | ||
573 | -def ShowLoadMarkersDialog(): | ||
574 | - current_dir = os.path.abspath(".") | ||
575 | - | ||
576 | - dlg = wx.FileDialog(None, message=_("Load markers"), | ||
577 | - defaultDir="", | ||
578 | - defaultFile="", | ||
579 | - wildcard=_("Markers files (*.mks)|*.mks"), | ||
580 | - style=wx.FD_OPEN|wx.FD_CHANGE_DIR) | ||
581 | - | ||
582 | - # inv3 filter is default | ||
583 | - dlg.SetFilterIndex(0) | 523 | + dlg = wx.FileDialog(None, message=message, defaultDir="", defaultFile=default_filename, |
524 | + wildcard=wildcard, style=style) | ||
584 | 525 | ||
585 | # Show the dialog and retrieve the user response. If it is the OK response, | 526 | # Show the dialog and retrieve the user response. If it is the OK response, |
586 | # process the data. | 527 | # process the data. |
@@ -589,73 +530,25 @@ def ShowLoadMarkersDialog(): | @@ -589,73 +530,25 @@ def ShowLoadMarkersDialog(): | ||
589 | if dlg.ShowModal() == wx.ID_OK: | 530 | if dlg.ShowModal() == wx.ID_OK: |
590 | # This returns a Python list of files that were selected. | 531 | # This returns a Python list of files that were selected. |
591 | filepath = dlg.GetPath() | 532 | filepath = dlg.GetPath() |
533 | + ok_press = 1 | ||
534 | + else: | ||
535 | + ok_press = 0 | ||
592 | except(wx._core.PyAssertionError): # FIX: win64 | 536 | except(wx._core.PyAssertionError): # FIX: win64 |
593 | filepath = dlg.GetPath() | 537 | filepath = dlg.GetPath() |
538 | + ok_press = 1 | ||
594 | 539 | ||
595 | - # Destroy the dialog. Don't do this until you are done with it! | ||
596 | - # BAD things can happen otherwise! | ||
597 | - dlg.Destroy() | ||
598 | - os.chdir(current_dir) | ||
599 | - return filepath | ||
600 | - | ||
601 | - | ||
602 | -def ShowSaveRegistrationDialog(default_filename=None): | ||
603 | - current_dir = os.path.abspath(".") | ||
604 | - dlg = wx.FileDialog(None, | ||
605 | - _("Save object registration as..."), # title | ||
606 | - "", # last used directory | ||
607 | - default_filename, | ||
608 | - _("Registration files (*.obr)|*.obr"), | ||
609 | - wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) | ||
610 | - # dlg.SetFilterIndex(0) # default is VTI | ||
611 | - | ||
612 | - filename = None | ||
613 | - try: | ||
614 | - if dlg.ShowModal() == wx.ID_OK: | ||
615 | - filename = dlg.GetPath() | ||
616 | - ok = 1 | ||
617 | - else: | ||
618 | - ok = 0 | ||
619 | - except(wx._core.PyAssertionError): # TODO: fix win64 | ||
620 | - filename = dlg.GetPath() | ||
621 | - ok = 1 | ||
622 | - | ||
623 | - if (ok): | ||
624 | - extension = "obr" | 540 | + # fix the extension if set different than expected |
541 | + if save_ext and ok_press: | ||
542 | + extension = save_ext | ||
625 | if sys.platform != 'win32': | 543 | if sys.platform != 'win32': |
626 | - if filename.split(".")[-1] != extension: | ||
627 | - filename = filename + "." + extension | ||
628 | - | ||
629 | - os.chdir(current_dir) | ||
630 | - return filename | ||
631 | - | ||
632 | - | ||
633 | -def ShowLoadRegistrationDialog(): | ||
634 | - current_dir = os.path.abspath(".") | ||
635 | - | ||
636 | - dlg = wx.FileDialog(None, message=_("Load object registration"), | ||
637 | - defaultDir="", | ||
638 | - defaultFile="", | ||
639 | - wildcard=_("Registration files (*.obr)|*.obr"), | ||
640 | - style=wx.FD_OPEN|wx.FD_CHANGE_DIR) | ||
641 | - | ||
642 | - # inv3 filter is default | ||
643 | - dlg.SetFilterIndex(0) | ||
644 | - | ||
645 | - # Show the dialog and retrieve the user response. If it is the OK response, | ||
646 | - # process the data. | ||
647 | - filepath = None | ||
648 | - try: | ||
649 | - if dlg.ShowModal() == wx.ID_OK: | ||
650 | - # This returns a Python list of files that were selected. | ||
651 | - filepath = dlg.GetPath() | ||
652 | - except(wx._core.PyAssertionError): # FIX: win64 | ||
653 | - filepath = dlg.GetPath() | 544 | + if filepath.split(".")[-1] != extension: |
545 | + filepath = filepath + "." + extension | ||
654 | 546 | ||
655 | # Destroy the dialog. Don't do this until you are done with it! | 547 | # Destroy the dialog. Don't do this until you are done with it! |
656 | # BAD things can happen otherwise! | 548 | # BAD things can happen otherwise! |
657 | dlg.Destroy() | 549 | dlg.Destroy() |
658 | os.chdir(current_dir) | 550 | os.chdir(current_dir) |
551 | + | ||
659 | return filepath | 552 | return filepath |
660 | 553 | ||
661 | 554 | ||
@@ -920,31 +813,9 @@ def SurfaceSelectionRequiredForDuplication(): | @@ -920,31 +813,9 @@ def SurfaceSelectionRequiredForDuplication(): | ||
920 | 813 | ||
921 | 814 | ||
922 | # Dialogs for neuronavigation mode | 815 | # Dialogs for neuronavigation mode |
923 | -def InvalidFiducials(): | ||
924 | - msg = _("Fiducials are invalid. Select all coordinates.") | ||
925 | - if sys.platform == 'darwin': | ||
926 | - dlg = wx.MessageDialog(None, "", msg, | ||
927 | - wx.ICON_INFORMATION | wx.OK) | ||
928 | - else: | ||
929 | - dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", | ||
930 | - wx.ICON_INFORMATION | wx.OK) | ||
931 | - dlg.ShowModal() | ||
932 | - dlg.Destroy() | ||
933 | - | 816 | +# ---------------------------------- |
934 | 817 | ||
935 | -def InvalidObjectRegistration(): | ||
936 | - msg = _("Perform coil registration before navigation.") | ||
937 | - if sys.platform == 'darwin': | ||
938 | - dlg = wx.MessageDialog(None, "", msg, | ||
939 | - wx.ICON_INFORMATION | wx.OK) | ||
940 | - else: | ||
941 | - dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", | ||
942 | - wx.ICON_INFORMATION | wx.OK) | ||
943 | - dlg.ShowModal() | ||
944 | - dlg.Destroy() | ||
945 | - | ||
946 | - | ||
947 | -def NavigationTrackerWarning(trck_id, lib_mode): | 818 | +def ShowNavigationTrackerWarning(trck_id, lib_mode): |
948 | """ | 819 | """ |
949 | Spatial Tracker connection error | 820 | Spatial Tracker connection error |
950 | """ | 821 | """ |
@@ -976,85 +847,46 @@ def NavigationTrackerWarning(trck_id, lib_mode): | @@ -976,85 +847,46 @@ def NavigationTrackerWarning(trck_id, lib_mode): | ||
976 | dlg.Destroy() | 847 | dlg.Destroy() |
977 | 848 | ||
978 | 849 | ||
979 | -def InvalidMarkersFile(): | ||
980 | - msg = _("The TXT file is invalid.") | ||
981 | - if sys.platform == 'darwin': | ||
982 | - dlg = wx.MessageDialog(None, "", msg, | ||
983 | - wx.ICON_INFORMATION | wx.OK) | ||
984 | - else: | ||
985 | - dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", | ||
986 | - wx.ICON_INFORMATION | wx.OK) | ||
987 | - dlg.ShowModal() | ||
988 | - dlg.Destroy() | ||
989 | - | ||
990 | - | ||
991 | -def NoMarkerSelected(): | ||
992 | - msg = _("No data selected") | 850 | +def ShowEnterMarkerID(default): |
851 | + msg = _("Edit marker ID") | ||
993 | if sys.platform == 'darwin': | 852 | if sys.platform == 'darwin': |
994 | - dlg = wx.MessageDialog(None, "", msg, | ||
995 | - wx.ICON_INFORMATION | wx.OK) | 853 | + dlg = wx.TextEntryDialog(None, "", msg, defaultValue=default) |
996 | else: | 854 | else: |
997 | - dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", | ||
998 | - wx.ICON_INFORMATION | wx.OK) | 855 | + dlg = wx.TextEntryDialog(None, msg, "InVesalius 3", value=default) |
999 | dlg.ShowModal() | 856 | dlg.ShowModal() |
857 | + result = dlg.GetValue() | ||
1000 | dlg.Destroy() | 858 | dlg.Destroy() |
859 | + return result | ||
1001 | 860 | ||
1002 | 861 | ||
1003 | -def DeleteAllMarkers(): | ||
1004 | - msg = _("Do you really want to delete all markers?") | 862 | +def ShowConfirmationDialog(msg=_('Proceed?')): |
863 | + # msg = _("Do you want to delete all markers?") | ||
1005 | if sys.platform == 'darwin': | 864 | if sys.platform == 'darwin': |
1006 | dlg = wx.MessageDialog(None, "", msg, | 865 | dlg = wx.MessageDialog(None, "", msg, |
1007 | wx.OK | wx.CANCEL | wx.ICON_QUESTION) | 866 | wx.OK | wx.CANCEL | wx.ICON_QUESTION) |
1008 | else: | 867 | else: |
1009 | - dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", | 868 | + dlg = wx.MessageDialog(None, msg, "InVesalius 3", |
1010 | wx.OK | wx.CANCEL | wx.ICON_QUESTION) | 869 | wx.OK | wx.CANCEL | wx.ICON_QUESTION) |
1011 | result = dlg.ShowModal() | 870 | result = dlg.ShowModal() |
1012 | dlg.Destroy() | 871 | dlg.Destroy() |
1013 | return result | 872 | return result |
1014 | 873 | ||
1015 | -def DeleteTarget(): | ||
1016 | - msg = _("Target deleted") | ||
1017 | - if sys.platform == 'darwin': | ||
1018 | - dlg = wx.MessageDialog(None, "", msg, | ||
1019 | - wx.ICON_INFORMATION | wx.OK) | ||
1020 | - else: | ||
1021 | - dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", | ||
1022 | - wx.ICON_INFORMATION | wx.OK) | ||
1023 | - dlg.ShowModal() | ||
1024 | - dlg.Destroy() | ||
1025 | 874 | ||
1026 | -def NewTarget(): | ||
1027 | - msg = _("New target selected") | ||
1028 | - if sys.platform == 'darwin': | ||
1029 | - dlg = wx.MessageDialog(None, "", msg, | ||
1030 | - wx.ICON_INFORMATION | wx.OK) | ||
1031 | - else: | ||
1032 | - dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", | ||
1033 | - wx.ICON_INFORMATION | wx.OK) | ||
1034 | - dlg.ShowModal() | ||
1035 | - dlg.Destroy() | 875 | +def ShowColorDialog(color_current): |
876 | + cdata = wx.ColourData() | ||
877 | + cdata.SetColour(wx.Colour(color_current)) | ||
878 | + dlg = wx.ColourDialog(None, data=cdata) | ||
879 | + dlg.GetColourData().SetChooseFull(True) | ||
1036 | 880 | ||
1037 | -def InvalidTargetID(): | ||
1038 | - msg = _("Sorry, you cannot use 'TARGET' ID") | ||
1039 | - if sys.platform == 'darwin': | ||
1040 | - dlg = wx.MessageDialog(None, "", msg, | ||
1041 | - wx.ICON_INFORMATION | wx.OK) | 881 | + if dlg.ShowModal() == wx.ID_OK: |
882 | + color_new = dlg.GetColourData().GetColour().Get(includeAlpha=False) | ||
1042 | else: | 883 | else: |
1043 | - dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", | ||
1044 | - wx.ICON_INFORMATION | wx.OK) | ||
1045 | - dlg.ShowModal() | ||
1046 | - dlg.Destroy() | 884 | + color_new = None |
1047 | 885 | ||
1048 | -def EnterMarkerID(default): | ||
1049 | - msg = _("Edit marker ID") | ||
1050 | - if sys.platform == 'darwin': | ||
1051 | - dlg = wx.TextEntryDialog(None, "", msg, defaultValue=default) | ||
1052 | - else: | ||
1053 | - dlg = wx.TextEntryDialog(None, msg, "InVesalius 3", value=default) | ||
1054 | - dlg.ShowModal() | ||
1055 | - result = dlg.GetValue() | ||
1056 | dlg.Destroy() | 886 | dlg.Destroy() |
1057 | - return result | 887 | + return color_new |
888 | + | ||
889 | +# ---------------------------------- | ||
1058 | 890 | ||
1059 | 891 | ||
1060 | class NewMask(wx.Dialog): | 892 | class NewMask(wx.Dialog): |
@@ -3569,7 +3401,10 @@ class ObjectCalibrationDialog(wx.Dialog): | @@ -3569,7 +3401,10 @@ class ObjectCalibrationDialog(wx.Dialog): | ||
3569 | else: | 3401 | else: |
3570 | coord = coord_raw[0, :] | 3402 | coord = coord_raw[0, :] |
3571 | else: | 3403 | else: |
3572 | - NavigationTrackerWarning(0, 'choose') | 3404 | + ShowNavigationTrackerWarning(0, 'choose') |
3405 | + | ||
3406 | + if btn_id == 3: | ||
3407 | + coord = np.zeros([6,]) | ||
3573 | 3408 | ||
3574 | # Update text controls with tracker coordinates | 3409 | # Update text controls with tracker coordinates |
3575 | if coord is not None or np.sum(coord) != 0.0: | 3410 | if coord is not None or np.sum(coord) != 0.0: |
@@ -3582,7 +3417,7 @@ class ObjectCalibrationDialog(wx.Dialog): | @@ -3582,7 +3417,7 @@ class ObjectCalibrationDialog(wx.Dialog): | ||
3582 | self.ball_actors[btn_id].GetProperty().SetColor(0.0, 1.0, 0.0) | 3417 | self.ball_actors[btn_id].GetProperty().SetColor(0.0, 1.0, 0.0) |
3583 | self.Refresh() | 3418 | self.Refresh() |
3584 | else: | 3419 | else: |
3585 | - NavigationTrackerWarning(0, 'choose') | 3420 | + ShowNavigationTrackerWarning(0, 'choose') |
3586 | 3421 | ||
3587 | def OnChoiceRefMode(self, evt): | 3422 | def OnChoiceRefMode(self, evt): |
3588 | # When ref mode is changed the tracker coordinates are set to nan | 3423 | # When ref mode is changed the tracker coordinates are set to nan |
invesalius/gui/frame.py
@@ -1185,12 +1185,13 @@ class MenuBar(wx.MenuBar): | @@ -1185,12 +1185,13 @@ class MenuBar(wx.MenuBar): | ||
1185 | else: | 1185 | else: |
1186 | self.FindItemById(const.ID_GOTO_COORD).Enable(False) | 1186 | self.FindItemById(const.ID_GOTO_COORD).Enable(False) |
1187 | 1187 | ||
1188 | - def OnEnableNavigation(self, status): | 1188 | + def OnEnableNavigation(self, nav_status, vis_status): |
1189 | """ | 1189 | """ |
1190 | Disable mode menu when navigation is on. | 1190 | Disable mode menu when navigation is on. |
1191 | - :param status: Navigation status | 1191 | + :param nav_status: Navigation status |
1192 | + :param vis_status: Status of items visualization during navigation | ||
1192 | """ | 1193 | """ |
1193 | - value = status | 1194 | + value = nav_status |
1194 | if value: | 1195 | if value: |
1195 | self.FindItemById(const.ID_MODE_NAVIGATION).Enable(False) | 1196 | self.FindItemById(const.ID_MODE_NAVIGATION).Enable(False) |
1196 | else: | 1197 | else: |
invesalius/gui/task_navigator.py
@@ -18,10 +18,13 @@ | @@ -18,10 +18,13 @@ | ||
18 | #-------------------------------------------------------------------------- | 18 | #-------------------------------------------------------------------------- |
19 | 19 | ||
20 | from functools import partial | 20 | from functools import partial |
21 | +# import os | ||
22 | +import queue | ||
21 | import sys | 23 | import sys |
22 | -import os | 24 | +import threading |
23 | 25 | ||
24 | import numpy as np | 26 | import numpy as np |
27 | +# import Trekker | ||
25 | import wx | 28 | import wx |
26 | 29 | ||
27 | try: | 30 | try: |
@@ -31,22 +34,23 @@ except ImportError: | @@ -31,22 +34,23 @@ except ImportError: | ||
31 | import wx.lib.hyperlink as hl | 34 | import wx.lib.hyperlink as hl |
32 | import wx.lib.foldpanelbar as fpb | 35 | import wx.lib.foldpanelbar as fpb |
33 | 36 | ||
37 | +import wx.lib.colourselect as csel | ||
34 | import wx.lib.masked.numctrl | 38 | import wx.lib.masked.numctrl |
35 | from pubsub import pub as Publisher | 39 | from pubsub import pub as Publisher |
36 | -import wx.lib.colourselect as csel | ||
37 | -import wx.lib.platebtn as pbtn | ||
38 | - | ||
39 | -from math import cos, sin, pi | ||
40 | from time import sleep | 40 | from time import sleep |
41 | 41 | ||
42 | -import invesalius.data.transformations as tr | ||
43 | import invesalius.constants as const | 42 | import invesalius.constants as const |
44 | import invesalius.data.bases as db | 43 | import invesalius.data.bases as db |
44 | +# import invesalius.data.brainmesh_handler as brain | ||
45 | import invesalius.data.coordinates as dco | 45 | import invesalius.data.coordinates as dco |
46 | import invesalius.data.coregistration as dcr | 46 | import invesalius.data.coregistration as dcr |
47 | +import invesalius.data.slice_ as sl | ||
47 | import invesalius.data.trackers as dt | 48 | import invesalius.data.trackers as dt |
49 | +import invesalius.data.tractography as dti | ||
50 | +import invesalius.data.transformations as tr | ||
48 | import invesalius.data.trigger as trig | 51 | import invesalius.data.trigger as trig |
49 | import invesalius.data.record_coords as rec | 52 | import invesalius.data.record_coords as rec |
53 | +import invesalius.data.vtk_utils as vtk_utils | ||
50 | import invesalius.gui.dialogs as dlg | 54 | import invesalius.gui.dialogs as dlg |
51 | from invesalius import utils | 55 | from invesalius import utils |
52 | 56 | ||
@@ -136,7 +140,7 @@ class InnerFoldPanel(wx.Panel): | @@ -136,7 +140,7 @@ class InnerFoldPanel(wx.Panel): | ||
136 | # Study this. | 140 | # Study this. |
137 | 141 | ||
138 | fold_panel = fpb.FoldPanelBar(self, -1, wx.DefaultPosition, | 142 | fold_panel = fpb.FoldPanelBar(self, -1, wx.DefaultPosition, |
139 | - (10, 290), 0, fpb.FPB_SINGLE_FOLD) | 143 | + (10, 310), 0, fpb.FPB_SINGLE_FOLD) |
140 | # Fold panel style | 144 | # Fold panel style |
141 | style = fpb.CaptionBarStyle() | 145 | style = fpb.CaptionBarStyle() |
142 | style.SetCaptionStyle(fpb.CAPTIONBAR_GRADIENT_V) | 146 | style.SetCaptionStyle(fpb.CAPTIONBAR_GRADIENT_V) |
@@ -161,15 +165,23 @@ class InnerFoldPanel(wx.Panel): | @@ -161,15 +165,23 @@ class InnerFoldPanel(wx.Panel): | ||
161 | leftSpacing=0, rightSpacing=0) | 165 | leftSpacing=0, rightSpacing=0) |
162 | 166 | ||
163 | # Fold 3 - Markers panel | 167 | # Fold 3 - Markers panel |
164 | - item = fold_panel.AddFoldPanel(_("Extra tools"), collapsed=True) | 168 | + item = fold_panel.AddFoldPanel(_("Markers"), collapsed=True) |
165 | mtw = MarkersPanel(item) | 169 | mtw = MarkersPanel(item) |
166 | 170 | ||
167 | fold_panel.ApplyCaptionStyle(item, style) | 171 | fold_panel.ApplyCaptionStyle(item, style) |
168 | fold_panel.AddFoldPanelWindow(item, mtw, spacing= 0, | 172 | fold_panel.AddFoldPanelWindow(item, mtw, spacing= 0, |
169 | leftSpacing=0, rightSpacing=0) | 173 | leftSpacing=0, rightSpacing=0) |
170 | 174 | ||
171 | - # Fold 4 - DBS | 175 | + # Fold 4 - Tractography panel |
176 | + item = fold_panel.AddFoldPanel(_("Tractography"), collapsed=True) | ||
177 | + otw = TractographyPanel(item) | ||
172 | 178 | ||
179 | + fold_panel.ApplyCaptionStyle(item, style) | ||
180 | + fold_panel.AddFoldPanelWindow(item, otw, spacing=0, | ||
181 | + leftSpacing=0, rightSpacing=0) | ||
182 | + item.Hide() | ||
183 | + | ||
184 | + # Fold 5 - DBS | ||
173 | self.dbs_item = fold_panel.AddFoldPanel(_("Deep Brain Stimulation"), collapsed=True) | 185 | self.dbs_item = fold_panel.AddFoldPanel(_("Deep Brain Stimulation"), collapsed=True) |
174 | dtw = DbsPanel(self.dbs_item) #Atribuir nova var, criar panel | 186 | dtw = DbsPanel(self.dbs_item) #Atribuir nova var, criar panel |
175 | 187 | ||
@@ -239,8 +251,8 @@ class InnerFoldPanel(wx.Panel): | @@ -239,8 +251,8 @@ class InnerFoldPanel(wx.Panel): | ||
239 | def OnHideDbs(self): | 251 | def OnHideDbs(self): |
240 | self.dbs_item.Hide() | 252 | self.dbs_item.Hide() |
241 | 253 | ||
242 | - def OnCheckStatus(self, status): | ||
243 | - if status: | 254 | + def OnCheckStatus(self, nav_status, vis_status): |
255 | + if nav_status: | ||
244 | self.checktrigger.Enable(False) | 256 | self.checktrigger.Enable(False) |
245 | self.checkobj.Enable(False) | 257 | self.checkobj.Enable(False) |
246 | else: | 258 | else: |
@@ -295,6 +307,23 @@ class NeuronavigationPanel(wx.Panel): | @@ -295,6 +307,23 @@ class NeuronavigationPanel(wx.Panel): | ||
295 | self.obj_reg = None | 307 | self.obj_reg = None |
296 | self.obj_reg_status = False | 308 | self.obj_reg_status = False |
297 | self.track_obj = False | 309 | self.track_obj = False |
310 | + self.event = threading.Event() | ||
311 | + | ||
312 | + self.coord_queue = QueueCustom(maxsize=1) | ||
313 | + # self.visualization_queue = QueueCustom(maxsize=1) | ||
314 | + self.trigger_queue = QueueCustom(maxsize=1) | ||
315 | + self.coord_tracts_queue = QueueCustom(maxsize=1) | ||
316 | + self.tracts_queue = QueueCustom(maxsize=1) | ||
317 | + | ||
318 | + # Tractography parameters | ||
319 | + self.trk_inp = None | ||
320 | + self.trekker = None | ||
321 | + self.n_threads = None | ||
322 | + self.view_tracts = False | ||
323 | + self.n_tracts = const.N_TRACTS | ||
324 | + self.seed_offset = const.SEED_OFFSET | ||
325 | + self.seed_radius = const.SEED_RADIUS | ||
326 | + self.sleep_nav = const.SLEEP_NAVIGATION | ||
298 | 327 | ||
299 | self.tracker_id = const.DEFAULT_TRACKER | 328 | self.tracker_id = const.DEFAULT_TRACKER |
300 | self.ref_mode_id = const.DEFAULT_REF_MODE | 329 | self.ref_mode_id = const.DEFAULT_REF_MODE |
@@ -405,10 +434,17 @@ class NeuronavigationPanel(wx.Panel): | @@ -405,10 +434,17 @@ class NeuronavigationPanel(wx.Panel): | ||
405 | Publisher.subscribe(self.LoadImageFiducials, 'Load image fiducials') | 434 | Publisher.subscribe(self.LoadImageFiducials, 'Load image fiducials') |
406 | Publisher.subscribe(self.UpdateTriggerState, 'Update trigger state') | 435 | Publisher.subscribe(self.UpdateTriggerState, 'Update trigger state') |
407 | Publisher.subscribe(self.UpdateTrackObjectState, 'Update track object state') | 436 | Publisher.subscribe(self.UpdateTrackObjectState, 'Update track object state') |
408 | - Publisher.subscribe(self.UpdateImageCoordinates, 'Set ball reference position') | 437 | + Publisher.subscribe(self.UpdateImageCoordinates, 'Update cross position') |
409 | Publisher.subscribe(self.OnDisconnectTracker, 'Disconnect tracker') | 438 | Publisher.subscribe(self.OnDisconnectTracker, 'Disconnect tracker') |
410 | Publisher.subscribe(self.UpdateObjectRegistration, 'Update object registration') | 439 | Publisher.subscribe(self.UpdateObjectRegistration, 'Update object registration') |
411 | Publisher.subscribe(self.OnCloseProject, 'Close project data') | 440 | Publisher.subscribe(self.OnCloseProject, 'Close project data') |
441 | + Publisher.subscribe(self.UpdateTrekkerObject, 'Update Trekker object') | ||
442 | + Publisher.subscribe(self.UpdateNumTracts, 'Update number of tracts') | ||
443 | + Publisher.subscribe(self.UpdateSeedOffset, 'Update seed offset') | ||
444 | + Publisher.subscribe(self.UpdateSeedRadius, 'Update seed radius') | ||
445 | + Publisher.subscribe(self.UpdateSleep, 'Update sleep') | ||
446 | + Publisher.subscribe(self.UpdateNumberThreads, 'Update number of threads') | ||
447 | + Publisher.subscribe(self.UpdateTractsVisualization, 'Update tracts visualization') | ||
412 | 448 | ||
413 | def LoadImageFiducials(self, marker_id, coord): | 449 | def LoadImageFiducials(self, marker_id, coord): |
414 | for n in const.BTNS_IMG_MKS: | 450 | for n in const.BTNS_IMG_MKS: |
@@ -420,7 +456,37 @@ class NeuronavigationPanel(wx.Panel): | @@ -420,7 +456,37 @@ class NeuronavigationPanel(wx.Panel): | ||
420 | for m in [0, 1, 2]: | 456 | for m in [0, 1, 2]: |
421 | self.numctrls_coord[btn_id][m].SetValue(coord[m]) | 457 | self.numctrls_coord[btn_id][m].SetValue(coord[m]) |
422 | 458 | ||
423 | - def UpdateImageCoordinates(self, position): | 459 | + def UpdateFRE(self, fre): |
460 | + # TODO: Exhibit FRE in a warning dialog and only starts navigation after user clicks ok | ||
461 | + self.txtctrl_fre.SetValue(str(round(fre, 2))) | ||
462 | + if fre <= 3: | ||
463 | + self.txtctrl_fre.SetBackgroundColour('GREEN') | ||
464 | + else: | ||
465 | + self.txtctrl_fre.SetBackgroundColour('RED') | ||
466 | + | ||
467 | + def UpdateTrekkerObject(self, data): | ||
468 | + # self.trk_inp = data | ||
469 | + self.trekker = data | ||
470 | + | ||
471 | + def UpdateNumTracts(self, data): | ||
472 | + self.n_tracts = data | ||
473 | + | ||
474 | + def UpdateSeedOffset(self, data): | ||
475 | + self.seed_offset = data | ||
476 | + | ||
477 | + def UpdateSeedRadius(self, data): | ||
478 | + self.seed_radius = data | ||
479 | + | ||
480 | + def UpdateSleep(self, data): | ||
481 | + self.sleep_nav = data | ||
482 | + | ||
483 | + def UpdateNumberThreads(self, data): | ||
484 | + self.n_threads = data | ||
485 | + | ||
486 | + def UpdateTractsVisualization(self, data): | ||
487 | + self.view_tracts = data | ||
488 | + | ||
489 | + def UpdateImageCoordinates(self, arg, position): | ||
424 | # TODO: Change from world coordinates to matrix coordinates. They are better for multi software communication. | 490 | # TODO: Change from world coordinates to matrix coordinates. They are better for multi software communication. |
425 | self.current_coord = position | 491 | self.current_coord = position |
426 | for m in [0, 1, 2]: | 492 | for m in [0, 1, 2]: |
@@ -473,7 +539,7 @@ class NeuronavigationPanel(wx.Panel): | @@ -473,7 +539,7 @@ class NeuronavigationPanel(wx.Panel): | ||
473 | label=_("Tracker disconnected successfully")) | 539 | label=_("Tracker disconnected successfully")) |
474 | self.trk_init = dt.TrackerConnection(self.tracker_id, None, 'connect') | 540 | self.trk_init = dt.TrackerConnection(self.tracker_id, None, 'connect') |
475 | if not self.trk_init[0]: | 541 | if not self.trk_init[0]: |
476 | - dlg.NavigationTrackerWarning(self.tracker_id, self.trk_init[1]) | 542 | + dlg.ShowNavigationTrackerWarning(self.tracker_id, self.trk_init[1]) |
477 | ctrl.SetSelection(0) | 543 | ctrl.SetSelection(0) |
478 | print("Tracker not connected!") | 544 | print("Tracker not connected!") |
479 | else: | 545 | else: |
@@ -488,7 +554,7 @@ class NeuronavigationPanel(wx.Panel): | @@ -488,7 +554,7 @@ class NeuronavigationPanel(wx.Panel): | ||
488 | self.trk_init = dt.TrackerConnection(self.tracker_id, trck, 'disconnect') | 554 | self.trk_init = dt.TrackerConnection(self.tracker_id, trck, 'disconnect') |
489 | if not self.trk_init[0]: | 555 | if not self.trk_init[0]: |
490 | if evt is not False: | 556 | if evt is not False: |
491 | - dlg.NavigationTrackerWarning(self.tracker_id, 'disconnect') | 557 | + dlg.ShowNavigationTrackerWarning(self.tracker_id, 'disconnect') |
492 | self.tracker_id = 0 | 558 | self.tracker_id = 0 |
493 | ctrl.SetSelection(self.tracker_id) | 559 | ctrl.SetSelection(self.tracker_id) |
494 | Publisher.sendMessage('Update status text in GUI', | 560 | Publisher.sendMessage('Update status text in GUI', |
@@ -507,7 +573,7 @@ class NeuronavigationPanel(wx.Panel): | @@ -507,7 +573,7 @@ class NeuronavigationPanel(wx.Panel): | ||
507 | self.tracker_id = choice | 573 | self.tracker_id = choice |
508 | self.trk_init = dt.TrackerConnection(self.tracker_id, None, 'connect') | 574 | self.trk_init = dt.TrackerConnection(self.tracker_id, None, 'connect') |
509 | if not self.trk_init[0]: | 575 | if not self.trk_init[0]: |
510 | - dlg.NavigationTrackerWarning(self.tracker_id, self.trk_init[1]) | 576 | + dlg.ShowNavigationTrackerWarning(self.tracker_id, self.trk_init[1]) |
511 | self.tracker_id = 0 | 577 | self.tracker_id = 0 |
512 | ctrl.SetSelection(self.tracker_id) | 578 | ctrl.SetSelection(self.tracker_id) |
513 | 579 | ||
@@ -549,7 +615,18 @@ class NeuronavigationPanel(wx.Panel): | @@ -549,7 +615,18 @@ class NeuronavigationPanel(wx.Panel): | ||
549 | coord = None | 615 | coord = None |
550 | 616 | ||
551 | if self.trk_init and self.tracker_id: | 617 | if self.trk_init and self.tracker_id: |
618 | + # if self.tracker_id == const.DEBUGTRACK: | ||
619 | + # if btn_id == 3: | ||
620 | + # coord1 = np.array([-120., 0., 0., 0., 0., 0.]) | ||
621 | + # elif btn_id == 4: | ||
622 | + # coord1 = np.array([120., 0., 0., 0., 0., 0.]) | ||
623 | + # elif btn_id == 5: | ||
624 | + # coord1 = np.array([0., 120., 0., 0., 0., 0.]) | ||
625 | + # coord2 = np.zeros([3, 6]) | ||
626 | + # coord_raw = np.vstack([coord1, coord2]) | ||
627 | + # else: | ||
552 | coord_raw = dco.GetCoordinates(self.trk_init, self.tracker_id, self.ref_mode_id) | 628 | coord_raw = dco.GetCoordinates(self.trk_init, self.tracker_id, self.ref_mode_id) |
629 | + | ||
553 | if self.ref_mode_id: | 630 | if self.ref_mode_id: |
554 | coord = dco.dynamic_reference_m(coord_raw[0, :], coord_raw[1, :]) | 631 | coord = dco.dynamic_reference_m(coord_raw[0, :], coord_raw[1, :]) |
555 | else: | 632 | else: |
@@ -557,7 +634,7 @@ class NeuronavigationPanel(wx.Panel): | @@ -557,7 +634,7 @@ class NeuronavigationPanel(wx.Panel): | ||
557 | coord[2] = -coord[2] | 634 | coord[2] = -coord[2] |
558 | 635 | ||
559 | else: | 636 | else: |
560 | - dlg.NavigationTrackerWarning(0, 'choose') | 637 | + dlg.ShowNavigationTrackerWarning(0, 'choose') |
561 | 638 | ||
562 | # Update number controls with tracker coordinates | 639 | # Update number controls with tracker coordinates |
563 | if coord is not None: | 640 | if coord is not None: |
@@ -569,112 +646,152 @@ class NeuronavigationPanel(wx.Panel): | @@ -569,112 +646,152 @@ class NeuronavigationPanel(wx.Panel): | ||
569 | btn_nav = btn[0] | 646 | btn_nav = btn[0] |
570 | choice_trck = btn[1] | 647 | choice_trck = btn[1] |
571 | choice_ref = btn[2] | 648 | choice_ref = btn[2] |
649 | + errors = False | ||
650 | + | ||
651 | + # initialize jobs list | ||
652 | + jobs_list = [] | ||
653 | + vis_components = [self.trigger_state, self.view_tracts] | ||
654 | + vis_queues = [self.coord_queue, self.trigger_queue, self.tracts_queue] | ||
572 | 655 | ||
573 | nav_id = btn_nav.GetValue() | 656 | nav_id = btn_nav.GetValue() |
574 | - if nav_id: | 657 | + if not nav_id: |
658 | + self.event.set() | ||
659 | + | ||
660 | + # print("coord unfinished: {}, queue {}", self.coord_queue.unfinished_tasks, self.coord_queue.qsize()) | ||
661 | + # print("coord_tracts unfinished: {}, queue {}", self.coord_tracts_queue.unfinished_tasks, self.coord_tracts_queue.qsize()) | ||
662 | + # print("tracts unfinished: {}, queue {}", self.tracts_queue.unfinished_tasks, self.tracts_queue.qsize()) | ||
663 | + self.coord_queue.clear() | ||
664 | + # self.visualization_queue.clear() | ||
665 | + if self.trigger_state: | ||
666 | + self.trigger_queue.clear() | ||
667 | + if self.view_tracts: | ||
668 | + self.coord_tracts_queue.clear() | ||
669 | + self.tracts_queue.clear() | ||
670 | + | ||
671 | + # print("coord after unfinished: {}, queue {}", self.coord_queue.unfinished_tasks, self.coord_queue.qsize()) | ||
672 | + # print("coord_tracts after unfinished: {}, queue {}", self.coord_tracts_queue.unfinished_tasks, self.coord_tracts_queue.qsize()) | ||
673 | + # print("tracts after unfinished: {}, queue {}", self.tracts_queue.unfinished_tasks, self.tracts_queue.qsize()) | ||
674 | + self.coord_queue.join() | ||
675 | + # self.visualization_queue.join() | ||
676 | + if self.trigger_state: | ||
677 | + self.trigger_queue.join() | ||
678 | + if self.view_tracts: | ||
679 | + self.coord_tracts_queue.join() | ||
680 | + self.tracts_queue.join() | ||
681 | + | ||
682 | + # print("coord join unfinished: {}, queue {}", self.coord_queue.unfinished_tasks, self.coord_queue.qsize()) | ||
683 | + # print("vis join unfinished: {}, queue {}", self.visualization_queue.unfinished_tasks, self.visualization_queue.qsize()) | ||
684 | + | ||
685 | + tooltip = wx.ToolTip(_("Start neuronavigation")) | ||
686 | + btn_nav.SetToolTip(tooltip) | ||
687 | + | ||
688 | + # Enable all navigation buttons | ||
689 | + choice_ref.Enable(True) | ||
690 | + choice_trck.Enable(True) | ||
691 | + for btn_c in self.btns_coord: | ||
692 | + btn_c.Enable(True) | ||
693 | + | ||
694 | + # if self.trigger_state: | ||
695 | + # self.trigger.stop() | ||
696 | + | ||
697 | + Publisher.sendMessage("Navigation status", nav_status=False, vis_status=vis_components) | ||
698 | + | ||
699 | + else: | ||
700 | + | ||
575 | if np.isnan(self.fiducials).any(): | 701 | if np.isnan(self.fiducials).any(): |
576 | - dlg.InvalidFiducials() | 702 | + wx.MessageBox(_("Invalid fiducials, select all coordinates."), _("InVesalius 3")) |
577 | btn_nav.SetValue(False) | 703 | btn_nav.SetValue(False) |
578 | 704 | ||
579 | - elif not self.trk_init[0]: | ||
580 | - dlg.NavigationTrackerWarning(0, 'choose') | 705 | + elif not self.trk_init[0] or not self.tracker_id: |
706 | + dlg.ShowNavigationTrackerWarning(0, 'choose') | ||
707 | + errors = True | ||
581 | 708 | ||
582 | else: | 709 | else: |
710 | + if self.event.is_set(): | ||
711 | + self.event.clear() | ||
712 | + | ||
713 | + # prepare GUI for navigation | ||
714 | + Publisher.sendMessage("Navigation status", nav_status=True, vis_status=vis_components) | ||
715 | + Publisher.sendMessage("Toggle Cross", id=const.SLICE_STATE_CROSS) | ||
716 | + Publisher.sendMessage("Hide current mask") | ||
583 | tooltip = wx.ToolTip(_("Stop neuronavigation")) | 717 | tooltip = wx.ToolTip(_("Stop neuronavigation")) |
584 | btn_nav.SetToolTip(tooltip) | 718 | btn_nav.SetToolTip(tooltip) |
585 | 719 | ||
586 | - # Disable all navigation buttons | 720 | + # disable all navigation buttons |
587 | choice_ref.Enable(False) | 721 | choice_ref.Enable(False) |
588 | choice_trck.Enable(False) | 722 | choice_trck.Enable(False) |
589 | for btn_c in self.btns_coord: | 723 | for btn_c in self.btns_coord: |
590 | btn_c.Enable(False) | 724 | btn_c.Enable(False) |
591 | 725 | ||
592 | - # fids_head_img = np.zeros([3, 3]) | ||
593 | - # for ic in range(0, 3): | ||
594 | - # fids_head_img[ic, :] = np.asarray(db.flip_x_m(self.fiducials[ic, :])) | ||
595 | - # | ||
596 | - # m_head_aux, q_head_aux, m_inv_head_aux = db.base_creation(fids_head_img) | ||
597 | - # m_head = np.asmatrix(np.identity(4)) | ||
598 | - # m_head[:3, :3] = m_head_aux[:3, :3] | ||
599 | - | ||
600 | - m, q1, minv = db.base_creation_old(self.fiducials[:3, :]) | ||
601 | - n, q2, ninv = db.base_creation_old(self.fiducials[3:, :]) | ||
602 | - | 726 | + # fiducials matrix |
603 | m_change = tr.affine_matrix_from_points(self.fiducials[3:, :].T, self.fiducials[:3, :].T, | 727 | m_change = tr.affine_matrix_from_points(self.fiducials[3:, :].T, self.fiducials[:3, :].T, |
604 | shear=False, scale=False) | 728 | shear=False, scale=False) |
605 | - | ||
606 | - # coreg_data = [m_change, m_head] | ||
607 | - | 729 | + # initialize spatial tracker parameters |
608 | tracker_mode = self.trk_init, self.tracker_id, self.ref_mode_id | 730 | tracker_mode = self.trk_init, self.tracker_id, self.ref_mode_id |
609 | - # FIXME: FRE is taking long to calculate so it updates on GUI delayed to navigation - I think its fixed | ||
610 | - # TODO: Exhibit FRE in a warning dialog and only starts navigation after user clicks ok | ||
611 | - fre = db.calculate_fre(self.fiducials, minv, n, q1, q2) | ||
612 | - | ||
613 | - self.txtctrl_fre.SetValue(str(round(fre, 2))) | ||
614 | - if fre <= 3: | ||
615 | - self.txtctrl_fre.SetBackgroundColour('GREEN') | ||
616 | - else: | ||
617 | - self.txtctrl_fre.SetBackgroundColour('RED') | ||
618 | - | ||
619 | - if self.trigger_state: | ||
620 | - self.trigger = trig.Trigger(nav_id) | ||
621 | 731 | ||
622 | - Publisher.sendMessage("Navigation status", status=True) | ||
623 | - Publisher.sendMessage("Toggle Cross", id=const.SLICE_STATE_CROSS) | ||
624 | - Publisher.sendMessage("Hide current mask") | 732 | + # compute fiducial registration error (FRE) |
733 | + # this is the old way to compute the fre, left here to recheck if new works fine. | ||
734 | + # fre = db.calculate_fre(self.fiducials, minv, n, q1, q2) | ||
735 | + fre = db.calculate_fre_m(self.fiducials) | ||
736 | + self.UpdateFRE(fre) | ||
625 | 737 | ||
626 | if self.track_obj: | 738 | if self.track_obj: |
627 | - if self.obj_reg_status: | 739 | + # if object tracking is selected |
740 | + if not self.obj_reg_status: | ||
741 | + # check if object registration was performed | ||
742 | + wx.MessageBox(_("Perform coil registration before navigation."), _("InVesalius 3")) | ||
743 | + errors = True | ||
744 | + else: | ||
745 | + # if object registration was correctly performed continue with navigation | ||
628 | # obj_reg[0] is object 3x3 fiducial matrix and obj_reg[1] is 3x3 orientation matrix | 746 | # obj_reg[0] is object 3x3 fiducial matrix and obj_reg[1] is 3x3 orientation matrix |
629 | obj_fiducials, obj_orients, obj_ref_mode, obj_name = self.obj_reg | 747 | obj_fiducials, obj_orients, obj_ref_mode, obj_name = self.obj_reg |
630 | 748 | ||
631 | - if self.trk_init and self.tracker_id: | ||
632 | - | ||
633 | - coreg_data = [m_change, obj_ref_mode] | ||
634 | - | ||
635 | - if self.ref_mode_id: | ||
636 | - coord_raw = dco.GetCoordinates(self.trk_init, self.tracker_id, self.ref_mode_id) | ||
637 | - obj_data = db.object_registration(obj_fiducials, obj_orients, coord_raw, m_change) | ||
638 | - coreg_data.extend(obj_data) | ||
639 | - | ||
640 | - self.correg = dcr.CoregistrationObjectDynamic(coreg_data, nav_id, tracker_mode) | ||
641 | - else: | ||
642 | - coord_raw = np.array([None]) | ||
643 | - obj_data = db.object_registration(obj_fiducials, obj_orients, coord_raw, m_change) | ||
644 | - coreg_data.extend(obj_data) | ||
645 | - | ||
646 | - self.correg = dcr.CoregistrationObjectStatic(coreg_data, nav_id, tracker_mode) | 749 | + coreg_data = [m_change, obj_ref_mode] |
647 | 750 | ||
751 | + if self.ref_mode_id: | ||
752 | + coord_raw = dco.GetCoordinates(self.trk_init, self.tracker_id, self.ref_mode_id) | ||
648 | else: | 753 | else: |
649 | - dlg.NavigationTrackerWarning(0, 'choose') | ||
650 | - | ||
651 | - else: | ||
652 | - dlg.InvalidObjectRegistration() | ||
653 | - | ||
654 | - else: | ||
655 | - coreg_data = [m_change, 0] | ||
656 | - if self.ref_mode_id: | ||
657 | - # self.correg = dcr.CoregistrationDynamic_old(bases_coreg, nav_id, tracker_mode) | ||
658 | - self.correg = dcr.CoregistrationDynamic(coreg_data, nav_id, tracker_mode) | ||
659 | - else: | ||
660 | - self.correg = dcr.CoregistrationStatic(coreg_data, nav_id, tracker_mode) | 754 | + coord_raw = np.array([None]) |
661 | 755 | ||
662 | - else: | ||
663 | - tooltip = wx.ToolTip(_("Start neuronavigation")) | ||
664 | - btn_nav.SetToolTip(tooltip) | ||
665 | - | ||
666 | - # Enable all navigation buttons | ||
667 | - choice_ref.Enable(True) | ||
668 | - choice_trck.Enable(True) | ||
669 | - for btn_c in self.btns_coord: | ||
670 | - btn_c.Enable(True) | ||
671 | - | ||
672 | - if self.trigger_state: | ||
673 | - self.trigger.stop() | 756 | + obj_data = db.object_registration(obj_fiducials, obj_orients, coord_raw, m_change) |
757 | + coreg_data.extend(obj_data) | ||
674 | 758 | ||
675 | - self.correg.stop() | 759 | + jobs_list.append(dcr.CoordinateCorregistrate(self.ref_mode_id, tracker_mode, coreg_data, self.coord_queue, |
760 | + self.view_tracts, self.coord_tracts_queue, | ||
761 | + self.event, self.sleep_nav)) | ||
676 | 762 | ||
677 | - Publisher.sendMessage("Navigation status", status=False) | 763 | + else: |
764 | + coreg_data = (m_change, 0) | ||
765 | + jobs_list.append(dcr.CoordinateCorregistrateNoObject(self.ref_mode_id, tracker_mode, coreg_data, | ||
766 | + self.coord_queue, | ||
767 | + self.view_tracts, self.coord_tracts_queue, | ||
768 | + self.event, self.sleep_nav)) | ||
769 | + | ||
770 | + if not errors: | ||
771 | + #TODO: Test the trigger thread | ||
772 | + if self.trigger_state: | ||
773 | + # self.trigger = trig.Trigger(nav_id) | ||
774 | + jobs_list.append(trig.TriggerNew(self.trigger_queue, self.event, self.sleep_nav)) | ||
775 | + | ||
776 | + if self.view_tracts: | ||
777 | + # initialize Trekker parameters | ||
778 | + slic = sl.Slice() | ||
779 | + affine = slic.affine | ||
780 | + affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(affine) | ||
781 | + Publisher.sendMessage("Update marker offset state", create=True) | ||
782 | + self.trk_inp = self.trekker, affine, self.seed_offset, self.n_tracts, self.seed_radius, self.n_threads | ||
783 | + # print("Appending the tract computation thread!") | ||
784 | + jobs_list.append(dti.ComputeTractsThread(self.trk_inp, affine_vtk, | ||
785 | + self.coord_tracts_queue, self.tracts_queue, | ||
786 | + self.event, self.sleep_nav)) | ||
787 | + | ||
788 | + jobs_list.append(UpdateNavigationScene(vis_queues, vis_components, | ||
789 | + self.event, self.sleep_nav)) | ||
790 | + | ||
791 | + for jobs in jobs_list: | ||
792 | + # jobs.daemon = True | ||
793 | + jobs.start() | ||
794 | + # del jobs | ||
678 | 795 | ||
679 | def ResetImageFiducials(self): | 796 | def ResetImageFiducials(self): |
680 | for m in range(0, 3): | 797 | for m in range(0, 3): |
@@ -699,6 +816,8 @@ class NeuronavigationPanel(wx.Panel): | @@ -699,6 +816,8 @@ class NeuronavigationPanel(wx.Panel): | ||
699 | Publisher.sendMessage('Update object registration') | 816 | Publisher.sendMessage('Update object registration') |
700 | Publisher.sendMessage('Update track object state', flag=False, obj_name=False) | 817 | Publisher.sendMessage('Update track object state', flag=False, obj_name=False) |
701 | Publisher.sendMessage('Delete all markers') | 818 | Publisher.sendMessage('Delete all markers') |
819 | + Publisher.sendMessage("Update marker offset state", create=False) | ||
820 | + Publisher.sendMessage("Remove tracts") | ||
702 | # TODO: Reset camera initial focus | 821 | # TODO: Reset camera initial focus |
703 | Publisher.sendMessage('Reset cam clipping range') | 822 | Publisher.sendMessage('Reset cam clipping range') |
704 | 823 | ||
@@ -830,8 +949,7 @@ class ObjectRegistrationPanel(wx.Panel): | @@ -830,8 +949,7 @@ class ObjectRegistrationPanel(wx.Panel): | ||
830 | def UpdateTrackerInit(self, nav_prop): | 949 | def UpdateTrackerInit(self, nav_prop): |
831 | self.nav_prop = nav_prop | 950 | self.nav_prop = nav_prop |
832 | 951 | ||
833 | - def UpdateNavigationStatus(self, status): | ||
834 | - nav_status = status | 952 | + def UpdateNavigationStatus(self, nav_status, vis_status): |
835 | if nav_status: | 953 | if nav_status: |
836 | self.checkrecordcoords.Enable(1) | 954 | self.checkrecordcoords.Enable(1) |
837 | self.checktrack.Enable(0) | 955 | self.checktrack.Enable(0) |
@@ -898,37 +1016,50 @@ class ObjectRegistrationPanel(wx.Panel): | @@ -898,37 +1016,50 @@ class ObjectRegistrationPanel(wx.Panel): | ||
898 | pass | 1016 | pass |
899 | 1017 | ||
900 | else: | 1018 | else: |
901 | - dlg.NavigationTrackerWarning(0, 'choose') | 1019 | + dlg.ShowNavigationTrackerWarning(0, 'choose') |
902 | 1020 | ||
903 | def OnLinkLoad(self, event=None): | 1021 | def OnLinkLoad(self, event=None): |
904 | - filename = dlg.ShowLoadRegistrationDialog() | 1022 | + filename = dlg.ShowLoadSaveDialog(message=_(u"Load object registration"), |
1023 | + wildcard=_("Registration files (*.obr)|*.obr")) | ||
1024 | + # data_dir = os.environ.get('OneDrive') + r'\data\dti_navigation\baran\pilot_20200131' | ||
1025 | + # coil_path = 'magstim_coil_dell_laptop.obr' | ||
1026 | + # filename = os.path.join(data_dir, coil_path) | ||
905 | 1027 | ||
906 | - if filename: | ||
907 | - data = np.loadtxt(filename, delimiter='\t') | ||
908 | - self.obj_fiducials = data[:, :3] | ||
909 | - self.obj_orients = data[:, 3:] | ||
910 | - | ||
911 | - text_file = open(filename, "r") | ||
912 | - header = text_file.readline().split('\t') | ||
913 | - text_file.close() | ||
914 | - | ||
915 | - self.obj_name = header[1] | ||
916 | - self.obj_ref_mode = int(header[-1]) | ||
917 | - | ||
918 | - self.checktrack.Enable(1) | ||
919 | - Publisher.sendMessage('Update object registration', | ||
920 | - data=(self.obj_fiducials, self.obj_orients, self.obj_ref_mode, self.obj_name)) | ||
921 | - Publisher.sendMessage('Update status text in GUI', label=_("Ready")) | ||
922 | - self.checktrack.SetValue(True) | ||
923 | - Publisher.sendMessage('Update track object state', flag=True, obj_name=self.obj_name) | ||
924 | - Publisher.sendMessage('Change camera checkbox', status=False) | ||
925 | - wx.MessageBox(_("Object file successfully loaded"), _("Load")) | 1028 | + try: |
1029 | + if filename: | ||
1030 | + #TODO: Improve method to read the file, using "with" similar to OnLoadParameters | ||
1031 | + data = np.loadtxt(filename, delimiter='\t') | ||
1032 | + self.obj_fiducials = data[:, :3] | ||
1033 | + self.obj_orients = data[:, 3:] | ||
1034 | + | ||
1035 | + text_file = open(filename, "r") | ||
1036 | + header = text_file.readline().split('\t') | ||
1037 | + text_file.close() | ||
1038 | + | ||
1039 | + self.obj_name = header[1] | ||
1040 | + self.obj_ref_mode = int(header[-1]) | ||
1041 | + | ||
1042 | + self.checktrack.Enable(1) | ||
1043 | + self.checktrack.SetValue(True) | ||
1044 | + Publisher.sendMessage('Update object registration', | ||
1045 | + data=(self.obj_fiducials, self.obj_orients, self.obj_ref_mode, self.obj_name)) | ||
1046 | + Publisher.sendMessage('Update status text in GUI', | ||
1047 | + label=_("Object file successfully loaded")) | ||
1048 | + Publisher.sendMessage('Update track object state', flag=True, obj_name=self.obj_name) | ||
1049 | + Publisher.sendMessage('Change camera checkbox', status=False) | ||
1050 | + # wx.MessageBox(_("Object file successfully loaded"), _("Load")) | ||
1051 | + except: | ||
1052 | + wx.MessageBox(_("Object registration file incompatible."), _("InVesalius 3")) | ||
1053 | + Publisher.sendMessage('Update status text in GUI', label="") | ||
926 | 1054 | ||
927 | def ShowSaveObjectDialog(self, evt): | 1055 | def ShowSaveObjectDialog(self, evt): |
928 | if np.isnan(self.obj_fiducials).any() or np.isnan(self.obj_orients).any(): | 1056 | if np.isnan(self.obj_fiducials).any() or np.isnan(self.obj_orients).any(): |
929 | wx.MessageBox(_("Digitize all object fiducials before saving"), _("Save error")) | 1057 | wx.MessageBox(_("Digitize all object fiducials before saving"), _("Save error")) |
930 | else: | 1058 | else: |
931 | - filename = dlg.ShowSaveRegistrationDialog("object_registration.obr") | 1059 | + filename = dlg.ShowLoadSaveDialog(message=_(u"Save object registration as..."), |
1060 | + wildcard=_("Registration files (*.obr)|*.obr"), | ||
1061 | + style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, | ||
1062 | + default_filename="object_registration.obr", save_ext="obr") | ||
932 | if filename: | 1063 | if filename: |
933 | hdr = 'Object' + "\t" + utils.decode(self.obj_name, const.FS_ENCODE) + "\t" + 'Reference' + "\t" + str('%d' % self.obj_ref_mode) | 1064 | hdr = 'Object' + "\t" + utils.decode(self.obj_name, const.FS_ENCODE) + "\t" + 'Reference' + "\t" + str('%d' % self.obj_ref_mode) |
934 | data = np.hstack([self.obj_fiducials, self.obj_orients]) | 1065 | data = np.hstack([self.obj_fiducials, self.obj_orients]) |
@@ -969,8 +1100,8 @@ class MarkersPanel(wx.Panel): | @@ -969,8 +1100,8 @@ class MarkersPanel(wx.Panel): | ||
969 | self.tgt_flag = self.tgt_index = None | 1100 | self.tgt_flag = self.tgt_index = None |
970 | self.nav_status = False | 1101 | self.nav_status = False |
971 | 1102 | ||
972 | - self.marker_colour = (1.0, 1.0, 0.) | ||
973 | - self.marker_size = 3 | 1103 | + self.marker_colour = const.MARKER_COLOUR |
1104 | + self.marker_size = const.MARKER_SIZE | ||
974 | 1105 | ||
975 | # Change marker size | 1106 | # Change marker size |
976 | spin_size = wx.SpinCtrl(self, -1, "", size=wx.Size(40, 23)) | 1107 | spin_size = wx.SpinCtrl(self, -1, "", size=wx.Size(40, 23)) |
@@ -1045,7 +1176,8 @@ class MarkersPanel(wx.Panel): | @@ -1045,7 +1176,8 @@ class MarkersPanel(wx.Panel): | ||
1045 | self.Update() | 1176 | self.Update() |
1046 | 1177 | ||
1047 | def __bind_events(self): | 1178 | def __bind_events(self): |
1048 | - Publisher.subscribe(self.UpdateCurrentCoord, 'Co-registered points') | 1179 | + # Publisher.subscribe(self.UpdateCurrentCoord, 'Co-registered points') |
1180 | + Publisher.subscribe(self.UpdateCurrentCoord, 'Update cross position') | ||
1049 | Publisher.subscribe(self.OnDeleteSingleMarker, 'Delete fiducial marker') | 1181 | Publisher.subscribe(self.OnDeleteSingleMarker, 'Delete fiducial marker') |
1050 | Publisher.subscribe(self.OnDeleteAllMarkers, 'Delete all markers') | 1182 | Publisher.subscribe(self.OnDeleteAllMarkers, 'Delete all markers') |
1051 | Publisher.subscribe(self.OnCreateMarker, 'Create marker') | 1183 | Publisher.subscribe(self.OnCreateMarker, 'Create marker') |
@@ -1055,8 +1187,8 @@ class MarkersPanel(wx.Panel): | @@ -1055,8 +1187,8 @@ class MarkersPanel(wx.Panel): | ||
1055 | self.current_coord = position[:] | 1187 | self.current_coord = position[:] |
1056 | #self.current_angle = pubsub_evt.data[1][3:] | 1188 | #self.current_angle = pubsub_evt.data[1][3:] |
1057 | 1189 | ||
1058 | - def UpdateNavigationStatus(self, status): | ||
1059 | - if not status: | 1190 | + def UpdateNavigationStatus(self, nav_status, vis_status): |
1191 | + if not nav_status: | ||
1060 | sleep(0.5) | 1192 | sleep(0.5) |
1061 | #self.current_coord[3:] = 0, 0, 0 | 1193 | #self.current_coord[3:] = 0, 0, 0 |
1062 | self.nav_status = False | 1194 | self.nav_status = False |
@@ -1073,6 +1205,9 @@ class MarkersPanel(wx.Panel): | @@ -1073,6 +1205,9 @@ class MarkersPanel(wx.Panel): | ||
1073 | menu_id.AppendSeparator() | 1205 | menu_id.AppendSeparator() |
1074 | target_menu = menu_id.Append(1, _('Set as target')) | 1206 | target_menu = menu_id.Append(1, _('Set as target')) |
1075 | menu_id.Bind(wx.EVT_MENU, self.OnMenuSetTarget, target_menu) | 1207 | menu_id.Bind(wx.EVT_MENU, self.OnMenuSetTarget, target_menu) |
1208 | + # TODO: Create the remove target option so the user can disable the target without removing the marker | ||
1209 | + # target_menu_rem = menu_id.Append(3, _('Remove target')) | ||
1210 | + # menu_id.Bind(wx.EVT_MENU, self.OnMenuRemoveTarget, target_menu_rem) | ||
1076 | 1211 | ||
1077 | target_menu.Enable(True) | 1212 | target_menu.Enable(True) |
1078 | self.PopupMenu(menu_id) | 1213 | self.PopupMenu(menu_id) |
@@ -1089,10 +1224,10 @@ class MarkersPanel(wx.Panel): | @@ -1089,10 +1224,10 @@ class MarkersPanel(wx.Panel): | ||
1089 | if evt == 'TARGET': | 1224 | if evt == 'TARGET': |
1090 | id_label = evt | 1225 | id_label = evt |
1091 | else: | 1226 | else: |
1092 | - id_label = dlg.EnterMarkerID(self.lc.GetItemText(list_index, 4)) | 1227 | + id_label = dlg.ShowEnterMarkerID(self.lc.GetItemText(list_index, 4)) |
1093 | if id_label == 'TARGET': | 1228 | if id_label == 'TARGET': |
1094 | id_label = '' | 1229 | id_label = '' |
1095 | - dlg.InvalidTargetID() | 1230 | + wx.MessageBox(_("Invalid TARGET ID."), _("InVesalius 3")) |
1096 | self.lc.SetItem(list_index, 4, id_label) | 1231 | self.lc.SetItem(list_index, 4, id_label) |
1097 | # Add the new ID to exported list | 1232 | # Add the new ID to exported list |
1098 | if len(self.list_coord[list_index]) > 8: | 1233 | if len(self.list_coord[list_index]) > 8: |
@@ -1122,34 +1257,29 @@ class MarkersPanel(wx.Panel): | @@ -1122,34 +1257,29 @@ class MarkersPanel(wx.Panel): | ||
1122 | Publisher.sendMessage('Disable or enable coil tracker', status=True) | 1257 | Publisher.sendMessage('Disable or enable coil tracker', status=True) |
1123 | self.OnMenuEditMarkerId('TARGET') | 1258 | self.OnMenuEditMarkerId('TARGET') |
1124 | self.tgt_flag = True | 1259 | self.tgt_flag = True |
1125 | - dlg.NewTarget() | 1260 | + wx.MessageBox(_("New target selected."), _("InVesalius 3")) |
1126 | 1261 | ||
1127 | def OnMenuSetColor(self, evt): | 1262 | def OnMenuSetColor(self, evt): |
1128 | index = self.lc.GetFocusedItem() | 1263 | index = self.lc.GetFocusedItem() |
1129 | - cdata = wx.ColourData() | ||
1130 | - cdata.SetColour(wx.Colour(self.list_coord[index][6]*255,self.list_coord[index][7]*255,self.list_coord[index][8]*255)) | ||
1131 | - dlg = wx.ColourDialog(self, data=cdata) | ||
1132 | - dlg.GetColourData().SetChooseFull(True) | ||
1133 | - if dlg.ShowModal() == wx.ID_OK: | ||
1134 | - self.r, self.g, self.b = dlg.GetColourData().GetColour().Get(includeAlpha=False) | ||
1135 | - r = float(self.r) / 255.0 | ||
1136 | - g = float(self.g) / 255.0 | ||
1137 | - b = float(self.b) / 255.0 | ||
1138 | - dlg.Destroy() | ||
1139 | - color = [r,g,b] | ||
1140 | - | ||
1141 | - Publisher.sendMessage('Set new color', index=index, color=color) | ||
1142 | - | ||
1143 | - self.list_coord[index][6] = r | ||
1144 | - self.list_coord[index][7] = g | ||
1145 | - self.list_coord[index][8] = b | 1264 | + |
1265 | + color_current = [self.list_coord[index][n] * 255 for n in range(6, 9)] | ||
1266 | + | ||
1267 | + color_new = dlg.ShowColorDialog(color_current=color_current) | ||
1268 | + | ||
1269 | + if color_new: | ||
1270 | + assert len(color_new) == 3 | ||
1271 | + for n, col in enumerate(color_new): | ||
1272 | + self.list_coord[index][n+6] = col/255.0 | ||
1273 | + | ||
1274 | + Publisher.sendMessage('Set new color', index=index, color=color_new) | ||
1146 | 1275 | ||
1147 | def OnDeleteAllMarkers(self, evt=None): | 1276 | def OnDeleteAllMarkers(self, evt=None): |
1148 | if self.list_coord: | 1277 | if self.list_coord: |
1149 | if evt is None: | 1278 | if evt is None: |
1150 | result = wx.ID_OK | 1279 | result = wx.ID_OK |
1151 | else: | 1280 | else: |
1152 | - result = dlg.DeleteAllMarkers() | 1281 | + # result = dlg.DeleteAllMarkers() |
1282 | + result = dlg.ShowConfirmationDialog(msg=_("Remove all markers? Cannot be undone.")) | ||
1153 | 1283 | ||
1154 | if result == wx.ID_OK: | 1284 | if result == wx.ID_OK: |
1155 | self.list_coord = [] | 1285 | self.list_coord = [] |
@@ -1162,7 +1292,7 @@ class MarkersPanel(wx.Panel): | @@ -1162,7 +1292,7 @@ class MarkersPanel(wx.Panel): | ||
1162 | self.tgt_flag = self.tgt_index = None | 1292 | self.tgt_flag = self.tgt_index = None |
1163 | Publisher.sendMessage('Disable or enable coil tracker', status=False) | 1293 | Publisher.sendMessage('Disable or enable coil tracker', status=False) |
1164 | if not hasattr(evt, 'data'): | 1294 | if not hasattr(evt, 'data'): |
1165 | - dlg.DeleteTarget() | 1295 | + wx.MessageBox(_("Target deleted."), _("InVesalius 3")) |
1166 | 1296 | ||
1167 | def OnDeleteSingleMarker(self, evt=None, marker_id=None): | 1297 | def OnDeleteSingleMarker(self, evt=None, marker_id=None): |
1168 | # OnDeleteSingleMarker is used for both pubsub and button click events | 1298 | # OnDeleteSingleMarker is used for both pubsub and button click events |
@@ -1183,14 +1313,16 @@ class MarkersPanel(wx.Panel): | @@ -1183,14 +1313,16 @@ class MarkersPanel(wx.Panel): | ||
1183 | else: | 1313 | else: |
1184 | index = None | 1314 | index = None |
1185 | 1315 | ||
1316 | + #TODO: There are bugs when no marker is selected, test and improve | ||
1186 | if index: | 1317 | if index: |
1187 | if self.tgt_flag and self.tgt_index == index[0]: | 1318 | if self.tgt_flag and self.tgt_index == index[0]: |
1188 | self.tgt_flag = self.tgt_index = None | 1319 | self.tgt_flag = self.tgt_index = None |
1189 | Publisher.sendMessage('Disable or enable coil tracker', status=False) | 1320 | Publisher.sendMessage('Disable or enable coil tracker', status=False) |
1190 | - dlg.DeleteTarget() | 1321 | + wx.MessageBox(_("No data selected."), _("InVesalius 3")) |
1322 | + | ||
1191 | self.DeleteMarker(index) | 1323 | self.DeleteMarker(index) |
1192 | else: | 1324 | else: |
1193 | - dlg.NoMarkerSelected() | 1325 | + wx.MessageBox(_("Target deleted."), _("InVesalius 3")) |
1194 | 1326 | ||
1195 | def DeleteMarker(self, index): | 1327 | def DeleteMarker(self, index): |
1196 | for i in reversed(index): | 1328 | for i in reversed(index): |
@@ -1213,12 +1345,16 @@ class MarkersPanel(wx.Panel): | @@ -1213,12 +1345,16 @@ class MarkersPanel(wx.Panel): | ||
1213 | self.CreateMarker(self.current_coord, self.marker_colour, self.marker_size) | 1345 | self.CreateMarker(self.current_coord, self.marker_colour, self.marker_size) |
1214 | 1346 | ||
1215 | def OnLoadMarkers(self, evt): | 1347 | def OnLoadMarkers(self, evt): |
1216 | - filepath = dlg.ShowLoadMarkersDialog() | 1348 | + filename = dlg.ShowLoadSaveDialog(message=_(u"Load markers"), |
1349 | + wildcard=_("Markers files (*.mks)|*.mks")) | ||
1350 | + # data_dir = os.environ.get('OneDrive') + r'\data\dti_navigation\baran\pilot_20200131' | ||
1351 | + # marker_path = 'markers.mks' | ||
1352 | + # filename = os.path.join(data_dir, marker_path) | ||
1217 | 1353 | ||
1218 | - if filepath: | 1354 | + if filename: |
1219 | try: | 1355 | try: |
1220 | count_line = self.lc.GetItemCount() | 1356 | count_line = self.lc.GetItemCount() |
1221 | - content = [s.rstrip() for s in open(filepath)] | 1357 | + content = [s.rstrip() for s in open(filename)] |
1222 | for data in content: | 1358 | for data in content: |
1223 | target = None | 1359 | target = None |
1224 | line = [s for s in data.split()] | 1360 | line = [s for s in data.split()] |
@@ -1254,7 +1390,7 @@ class MarkersPanel(wx.Panel): | @@ -1254,7 +1390,7 @@ class MarkersPanel(wx.Panel): | ||
1254 | self.CreateMarker(coord, colour, size, line[7]) | 1390 | self.CreateMarker(coord, colour, size, line[7]) |
1255 | count_line += 1 | 1391 | count_line += 1 |
1256 | except: | 1392 | except: |
1257 | - dlg.InvalidMarkersFile() | 1393 | + wx.MessageBox(_("Invalid markers file."), _("InVesalius 3")) |
1258 | 1394 | ||
1259 | def OnMarkersVisibility(self, evt, ctrl): | 1395 | def OnMarkersVisibility(self, evt, ctrl): |
1260 | 1396 | ||
@@ -1266,7 +1402,11 @@ class MarkersPanel(wx.Panel): | @@ -1266,7 +1402,11 @@ class MarkersPanel(wx.Panel): | ||
1266 | ctrl.SetLabel('Hide') | 1402 | ctrl.SetLabel('Hide') |
1267 | 1403 | ||
1268 | def OnSaveMarkers(self, evt): | 1404 | def OnSaveMarkers(self, evt): |
1269 | - filename = dlg.ShowSaveMarkersDialog("markers.mks") | 1405 | + filename = dlg.ShowLoadSaveDialog(message=_(u"Save markers as..."), |
1406 | + wildcard=_("Marker files (*.mks)|*.mks"), | ||
1407 | + style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, | ||
1408 | + default_filename="markers.mks", save_ext="mks") | ||
1409 | + | ||
1270 | if filename: | 1410 | if filename: |
1271 | if self.list_coord: | 1411 | if self.list_coord: |
1272 | text_file = open(filename, "w") | 1412 | text_file = open(filename, "w") |
@@ -1339,3 +1479,459 @@ class DbsPanel(wx.Panel): | @@ -1339,3 +1479,459 @@ class DbsPanel(wx.Panel): | ||
1339 | except AttributeError: | 1479 | except AttributeError: |
1340 | default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR) | 1480 | default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR) |
1341 | 1481 | ||
1482 | + | ||
1483 | +class TractographyPanel(wx.Panel): | ||
1484 | + | ||
1485 | + def __init__(self, parent): | ||
1486 | + wx.Panel.__init__(self, parent) | ||
1487 | + try: | ||
1488 | + default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) | ||
1489 | + except AttributeError: | ||
1490 | + default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR) | ||
1491 | + self.SetBackgroundColour(default_colour) | ||
1492 | + | ||
1493 | + self.affine = None | ||
1494 | + self.affine_vtk = None | ||
1495 | + self.trekker = None | ||
1496 | + self.n_tracts = const.N_TRACTS | ||
1497 | + self.peel_depth = const.PEEL_DEPTH | ||
1498 | + self.view_tracts = False | ||
1499 | + self.seed_offset = const.SEED_OFFSET | ||
1500 | + self.seed_radius = const.SEED_RADIUS | ||
1501 | + self.sleep_nav = const.SLEEP_NAVIGATION | ||
1502 | + self.brain_opacity = const.BRAIN_OPACITY | ||
1503 | + self.brain_peel = None | ||
1504 | + self.brain_actor = None | ||
1505 | + self.n_peels = const.MAX_PEEL_DEPTH | ||
1506 | + self.p_old = np.array([[0., 0., 0.]]) | ||
1507 | + self.tracts_run = None | ||
1508 | + self.trekker_cfg = const.TREKKER_CONFIG | ||
1509 | + self.nav_status = False | ||
1510 | + | ||
1511 | + self.SetAutoLayout(1) | ||
1512 | + self.__bind_events() | ||
1513 | + | ||
1514 | + # Button for creating new coil | ||
1515 | + tooltip = wx.ToolTip(_("Load brain visualization")) | ||
1516 | + btn_mask = wx.Button(self, -1, _("Load brain"), size=wx.Size(65, 23)) | ||
1517 | + btn_mask.SetToolTip(tooltip) | ||
1518 | + btn_mask.Enable(1) | ||
1519 | + btn_mask.Bind(wx.EVT_BUTTON, self.OnLinkBrain) | ||
1520 | + # self.btn_new = btn_new | ||
1521 | + | ||
1522 | + # Button for import config coil file | ||
1523 | + tooltip = wx.ToolTip(_("Load FOD")) | ||
1524 | + btn_load = wx.Button(self, -1, _("Load FOD"), size=wx.Size(65, 23)) | ||
1525 | + btn_load.SetToolTip(tooltip) | ||
1526 | + btn_load.Enable(1) | ||
1527 | + btn_load.Bind(wx.EVT_BUTTON, self.OnLinkFOD) | ||
1528 | + # self.btn_load = btn_load | ||
1529 | + | ||
1530 | + # Save button for object registration | ||
1531 | + tooltip = wx.ToolTip(_(u"Load Trekker configuration parameters")) | ||
1532 | + btn_load_cfg = wx.Button(self, -1, _(u"Configure"), size=wx.Size(65, 23)) | ||
1533 | + btn_load_cfg.SetToolTip(tooltip) | ||
1534 | + btn_load_cfg.Enable(1) | ||
1535 | + btn_load_cfg.Bind(wx.EVT_BUTTON, self.OnLoadParameters) | ||
1536 | + # self.btn_load_cfg = btn_load_cfg | ||
1537 | + | ||
1538 | + # Create a horizontal sizer to represent button save | ||
1539 | + line_btns = wx.BoxSizer(wx.HORIZONTAL) | ||
1540 | + line_btns.Add(btn_load, 1, wx.LEFT | wx.TOP | wx.RIGHT, 4) | ||
1541 | + line_btns.Add(btn_load_cfg, 1, wx.LEFT | wx.TOP | wx.RIGHT, 4) | ||
1542 | + line_btns.Add(btn_mask, 1, wx.LEFT | wx.TOP | wx.RIGHT, 4) | ||
1543 | + | ||
1544 | + # Change peeling depth | ||
1545 | + text_peel_depth = wx.StaticText(self, -1, _("Peeling depth (mm):")) | ||
1546 | + spin_peel_depth = wx.SpinCtrl(self, -1, "", size=wx.Size(50, 23)) | ||
1547 | + spin_peel_depth.Enable(1) | ||
1548 | + spin_peel_depth.SetRange(0, const.MAX_PEEL_DEPTH) | ||
1549 | + spin_peel_depth.SetValue(const.PEEL_DEPTH) | ||
1550 | + spin_peel_depth.Bind(wx.EVT_TEXT, partial(self.OnSelectPeelingDepth, ctrl=spin_peel_depth)) | ||
1551 | + spin_peel_depth.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectPeelingDepth, ctrl=spin_peel_depth)) | ||
1552 | + | ||
1553 | + # Change number of tracts | ||
1554 | + text_ntracts = wx.StaticText(self, -1, _("Number tracts:")) | ||
1555 | + spin_ntracts = wx.SpinCtrl(self, -1, "", size=wx.Size(50, 23)) | ||
1556 | + spin_ntracts.Enable(1) | ||
1557 | + spin_ntracts.SetRange(1, 2000) | ||
1558 | + spin_ntracts.SetValue(const.N_TRACTS) | ||
1559 | + spin_ntracts.Bind(wx.EVT_TEXT, partial(self.OnSelectNumTracts, ctrl=spin_ntracts)) | ||
1560 | + spin_ntracts.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectNumTracts, ctrl=spin_ntracts)) | ||
1561 | + | ||
1562 | + # Change seed offset for computing tracts | ||
1563 | + text_offset = wx.StaticText(self, -1, _("Seed offset (mm):")) | ||
1564 | + spin_offset = wx.SpinCtrlDouble(self, -1, "", size=wx.Size(50, 23), inc = 0.1) | ||
1565 | + spin_offset.Enable(1) | ||
1566 | + spin_offset.SetRange(0, 100.0) | ||
1567 | + spin_offset.SetValue(self.seed_offset) | ||
1568 | + spin_offset.Bind(wx.EVT_TEXT, partial(self.OnSelectOffset, ctrl=spin_offset)) | ||
1569 | + spin_offset.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectOffset, ctrl=spin_offset)) | ||
1570 | + # self.spin_offset = spin_offset | ||
1571 | + | ||
1572 | + # Change seed radius for computing tracts | ||
1573 | + text_radius = wx.StaticText(self, -1, _("Seed radius (mm):")) | ||
1574 | + spin_radius = wx.SpinCtrlDouble(self, -1, "", size=wx.Size(50, 23), inc=0.1) | ||
1575 | + spin_radius.Enable(1) | ||
1576 | + spin_radius.SetRange(0, 100.0) | ||
1577 | + spin_radius.SetValue(self.seed_radius) | ||
1578 | + spin_radius.Bind(wx.EVT_TEXT, partial(self.OnSelectRadius, ctrl=spin_radius)) | ||
1579 | + spin_radius.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectRadius, ctrl=spin_radius)) | ||
1580 | + # self.spin_radius = spin_radius | ||
1581 | + | ||
1582 | + # Change sleep pause between navigation loops | ||
1583 | + text_sleep = wx.StaticText(self, -1, _("Sleep (s):")) | ||
1584 | + spin_sleep = wx.SpinCtrlDouble(self, -1, "", size=wx.Size(50, 23), inc=0.01) | ||
1585 | + spin_sleep.Enable(1) | ||
1586 | + spin_sleep.SetRange(0.01, 10.0) | ||
1587 | + spin_sleep.SetValue(self.sleep_nav) | ||
1588 | + spin_sleep.Bind(wx.EVT_TEXT, partial(self.OnSelectSleep, ctrl=spin_sleep)) | ||
1589 | + spin_sleep.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectSleep, ctrl=spin_sleep)) | ||
1590 | + | ||
1591 | + # Change opacity of brain mask visualization | ||
1592 | + text_opacity = wx.StaticText(self, -1, _("Brain opacity:")) | ||
1593 | + spin_opacity = wx.SpinCtrlDouble(self, -1, "", size=wx.Size(50, 23), inc=0.1) | ||
1594 | + spin_opacity.Enable(0) | ||
1595 | + spin_opacity.SetRange(0, 1.0) | ||
1596 | + spin_opacity.SetValue(self.brain_opacity) | ||
1597 | + spin_opacity.Bind(wx.EVT_TEXT, partial(self.OnSelectOpacity, ctrl=spin_opacity)) | ||
1598 | + spin_opacity.Bind(wx.EVT_SPINCTRL, partial(self.OnSelectOpacity, ctrl=spin_opacity)) | ||
1599 | + self.spin_opacity = spin_opacity | ||
1600 | + | ||
1601 | + # Create a horizontal sizer to threshold configs | ||
1602 | + border = 1 | ||
1603 | + line_peel_depth = wx.BoxSizer(wx.HORIZONTAL) | ||
1604 | + line_peel_depth.AddMany([(text_peel_depth, 1, wx.EXPAND | wx.GROW | wx.TOP | wx.RIGHT | wx.LEFT, border), | ||
1605 | + (spin_peel_depth, 0, wx.ALL | wx.EXPAND | wx.GROW, border)]) | ||
1606 | + | ||
1607 | + line_ntracts = wx.BoxSizer(wx.HORIZONTAL) | ||
1608 | + line_ntracts.AddMany([(text_ntracts, 1, wx.EXPAND | wx.GROW | wx.TOP | wx.RIGHT | wx.LEFT, border), | ||
1609 | + (spin_ntracts, 0, wx.ALL | wx.EXPAND | wx.GROW, border)]) | ||
1610 | + | ||
1611 | + line_offset = wx.BoxSizer(wx.HORIZONTAL) | ||
1612 | + line_offset.AddMany([(text_offset, 1, wx.EXPAND | wx.GROW | wx.TOP | wx.RIGHT | wx.LEFT, border), | ||
1613 | + (spin_offset, 0, wx.ALL | wx.EXPAND | wx.GROW, border)]) | ||
1614 | + | ||
1615 | + line_radius = wx.BoxSizer(wx.HORIZONTAL) | ||
1616 | + line_radius.AddMany([(text_radius, 1, wx.EXPAND | wx.GROW | wx.TOP | wx.RIGHT | wx.LEFT, border), | ||
1617 | + (spin_radius, 0, wx.ALL | wx.EXPAND | wx.GROW, border)]) | ||
1618 | + | ||
1619 | + line_sleep = wx.BoxSizer(wx.HORIZONTAL) | ||
1620 | + line_sleep.AddMany([(text_sleep, 1, wx.EXPAND | wx.GROW | wx.TOP | wx.RIGHT | wx.LEFT, border), | ||
1621 | + (spin_sleep, 0, wx.ALL | wx.EXPAND | wx.GROW, border)]) | ||
1622 | + | ||
1623 | + line_opacity = wx.BoxSizer(wx.HORIZONTAL) | ||
1624 | + line_opacity.AddMany([(text_opacity, 1, wx.EXPAND | wx.GROW | wx.TOP | wx.RIGHT | wx.LEFT, border), | ||
1625 | + (spin_opacity, 0, wx.ALL | wx.EXPAND | wx.GROW, border)]) | ||
1626 | + | ||
1627 | + # Check box to enable tract visualization | ||
1628 | + checktracts = wx.CheckBox(self, -1, _('Enable tracts')) | ||
1629 | + checktracts.SetValue(False) | ||
1630 | + checktracts.Enable(0) | ||
1631 | + checktracts.Bind(wx.EVT_CHECKBOX, partial(self.OnEnableTracts, ctrl=checktracts)) | ||
1632 | + self.checktracts = checktracts | ||
1633 | + | ||
1634 | + # Check box to enable surface peeling | ||
1635 | + checkpeeling = wx.CheckBox(self, -1, _('Peel surface')) | ||
1636 | + checkpeeling.SetValue(False) | ||
1637 | + checkpeeling.Enable(0) | ||
1638 | + checkpeeling.Bind(wx.EVT_CHECKBOX, partial(self.OnShowPeeling, ctrl=checkpeeling)) | ||
1639 | + self.checkpeeling = checkpeeling | ||
1640 | + | ||
1641 | + border_last = 1 | ||
1642 | + line_checks = wx.BoxSizer(wx.HORIZONTAL) | ||
1643 | + line_checks.Add(checktracts, 0, wx.ALIGN_LEFT | wx.RIGHT | wx.LEFT, border_last) | ||
1644 | + line_checks.Add(checkpeeling, 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.LEFT, border_last) | ||
1645 | + | ||
1646 | + # Add line sizers into main sizer | ||
1647 | + border = 1 | ||
1648 | + border_last = 10 | ||
1649 | + main_sizer = wx.BoxSizer(wx.VERTICAL) | ||
1650 | + main_sizer.Add(line_btns, 0, wx.BOTTOM | wx.ALIGN_CENTER_HORIZONTAL, border_last) | ||
1651 | + main_sizer.Add(line_peel_depth, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border) | ||
1652 | + main_sizer.Add(line_ntracts, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border) | ||
1653 | + main_sizer.Add(line_offset, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border) | ||
1654 | + main_sizer.Add(line_radius, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border) | ||
1655 | + main_sizer.Add(line_sleep, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border) | ||
1656 | + main_sizer.Add(line_opacity, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border) | ||
1657 | + main_sizer.Add(line_checks, 0, wx.GROW | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, border_last) | ||
1658 | + main_sizer.Fit(self) | ||
1659 | + | ||
1660 | + self.SetSizer(main_sizer) | ||
1661 | + self.Update() | ||
1662 | + | ||
1663 | + def __bind_events(self): | ||
1664 | + Publisher.subscribe(self.OnCloseProject, 'Close project data') | ||
1665 | + Publisher.subscribe(self.OnUpdateTracts, 'Update cross position') | ||
1666 | + Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status') | ||
1667 | + | ||
1668 | + def OnSelectPeelingDepth(self, evt, ctrl): | ||
1669 | + self.peel_depth = ctrl.GetValue() | ||
1670 | + if self.checkpeeling.GetValue(): | ||
1671 | + actor = self.brain_peel.get_actor(self.peel_depth) | ||
1672 | + Publisher.sendMessage('Update peel', flag=True, actor=actor) | ||
1673 | + | ||
1674 | + def OnSelectNumTracts(self, evt, ctrl): | ||
1675 | + self.n_tracts = ctrl.GetValue() | ||
1676 | + # self.tract.n_tracts = ctrl.GetValue() | ||
1677 | + Publisher.sendMessage('Update number of tracts', data=self.n_tracts) | ||
1678 | + | ||
1679 | + def OnSelectOffset(self, evt, ctrl): | ||
1680 | + self.seed_offset = ctrl.GetValue() | ||
1681 | + # self.tract.seed_offset = ctrl.GetValue() | ||
1682 | + Publisher.sendMessage('Update seed offset', data=self.seed_offset) | ||
1683 | + | ||
1684 | + def OnSelectRadius(self, evt, ctrl): | ||
1685 | + self.seed_radius = ctrl.GetValue() | ||
1686 | + # self.tract.seed_offset = ctrl.GetValue() | ||
1687 | + Publisher.sendMessage('Update seed radius', data=self.seed_radius) | ||
1688 | + | ||
1689 | + def OnSelectSleep(self, evt, ctrl): | ||
1690 | + self.sleep_nav = ctrl.GetValue() | ||
1691 | + # self.tract.seed_offset = ctrl.GetValue() | ||
1692 | + Publisher.sendMessage('Update sleep', data=self.sleep_nav) | ||
1693 | + | ||
1694 | + def OnSelectOpacity(self, evt, ctrl): | ||
1695 | + self.brain_actor.GetProperty().SetOpacity(ctrl.GetValue()) | ||
1696 | + Publisher.sendMessage('Update peel', flag=True, actor=self.brain_actor) | ||
1697 | + | ||
1698 | + def OnShowPeeling(self, evt, ctrl): | ||
1699 | + # self.view_peeling = ctrl.GetValue() | ||
1700 | + if ctrl.GetValue(): | ||
1701 | + actor = self.brain_peel.get_actor(self.peel_depth) | ||
1702 | + else: | ||
1703 | + actor = None | ||
1704 | + Publisher.sendMessage('Update peel', flag=ctrl.GetValue(), actor=actor) | ||
1705 | + | ||
1706 | + def OnEnableTracts(self, evt, ctrl): | ||
1707 | + self.view_tracts = ctrl.GetValue() | ||
1708 | + Publisher.sendMessage('Update tracts visualization', data=self.view_tracts) | ||
1709 | + if not self.view_tracts: | ||
1710 | + Publisher.sendMessage('Remove tracts') | ||
1711 | + Publisher.sendMessage("Update marker offset state", create=False) | ||
1712 | + | ||
1713 | + def UpdateNavigationStatus(self, nav_status, vis_status): | ||
1714 | + self.nav_status = nav_status | ||
1715 | + | ||
1716 | + def OnLinkBrain(self, event=None): | ||
1717 | + Publisher.sendMessage('Update status text in GUI', label=_("Busy")) | ||
1718 | + Publisher.sendMessage('Begin busy cursor') | ||
1719 | + mask_path = dlg.ShowImportOtherFilesDialog(const.ID_TREKKER_MASK) | ||
1720 | + img_path = dlg.ShowImportOtherFilesDialog(const.ID_TREKKER_IMG) | ||
1721 | + # data_dir = os.environ.get('OneDriveConsumer') + '\\data\\dti' | ||
1722 | + # mask_file = 'sub-P0_dwi_mask.nii' | ||
1723 | + # mask_path = os.path.join(data_dir, mask_file) | ||
1724 | + # img_file = 'sub-P0_T1w_biascorrected.nii' | ||
1725 | + # img_path = os.path.join(data_dir, img_file) | ||
1726 | + | ||
1727 | + if not self.affine_vtk: | ||
1728 | + slic = sl.Slice() | ||
1729 | + self.affine = slic.affine | ||
1730 | + self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine) | ||
1731 | + | ||
1732 | + try: | ||
1733 | + self.brain_peel = brain.Brain(img_path, mask_path, self.n_peels, self.affine_vtk) | ||
1734 | + self.brain_actor = self.brain_peel.get_actor(self.peel_depth) | ||
1735 | + self.brain_actor.GetProperty().SetOpacity(self.brain_opacity) | ||
1736 | + Publisher.sendMessage('Update peel', flag=True, actor=self.brain_actor) | ||
1737 | + self.checkpeeling.Enable(1) | ||
1738 | + self.checkpeeling.SetValue(True) | ||
1739 | + self.spin_opacity.Enable(1) | ||
1740 | + Publisher.sendMessage('Update status text in GUI', label=_("Brain model loaded")) | ||
1741 | + except: | ||
1742 | + wx.MessageBox(_("Unable to load brain mask."), _("InVesalius 3")) | ||
1743 | + | ||
1744 | + Publisher.sendMessage('End busy cursor') | ||
1745 | + | ||
1746 | + def OnLinkFOD(self, event=None): | ||
1747 | + Publisher.sendMessage('Update status text in GUI', label=_("Busy")) | ||
1748 | + Publisher.sendMessage('Begin busy cursor') | ||
1749 | + filename = dlg.ShowImportOtherFilesDialog(const.ID_TREKKER_FOD) | ||
1750 | + # Juuso | ||
1751 | + # data_dir = os.environ.get('OneDriveConsumer') + '\\data\\dti' | ||
1752 | + # FOD_path = 'sub-P0_dwi_FOD.nii' | ||
1753 | + # Baran | ||
1754 | + # data_dir = os.environ.get('OneDrive') + r'\data\dti_navigation\baran\pilot_20200131' | ||
1755 | + # FOD_path = 'Baran_FOD.nii' | ||
1756 | + # filename = os.path.join(data_dir, FOD_path) | ||
1757 | + | ||
1758 | + if not self.affine_vtk: | ||
1759 | + slic = sl.Slice() | ||
1760 | + self.affine = slic.affine | ||
1761 | + self.affine_vtk = vtk_utils.numpy_to_vtkMatrix4x4(self.affine) | ||
1762 | + | ||
1763 | + # try: | ||
1764 | + | ||
1765 | + self.trekker = Trekker.initialize(filename.encode('utf-8')) | ||
1766 | + self.trekker, n_threads = dti.set_trekker_parameters(self.trekker, self.trekker_cfg) | ||
1767 | + | ||
1768 | + self.checktracts.Enable(1) | ||
1769 | + self.checktracts.SetValue(True) | ||
1770 | + self.view_tracts = True | ||
1771 | + Publisher.sendMessage('Update Trekker object', data=self.trekker) | ||
1772 | + Publisher.sendMessage('Update number of threads', data=n_threads) | ||
1773 | + Publisher.sendMessage('Update tracts visualization', data=1) | ||
1774 | + Publisher.sendMessage('Update status text in GUI', label=_("Trekker initialized")) | ||
1775 | + # except: | ||
1776 | + # wx.MessageBox(_("Unable to initialize Trekker, check FOD and config files."), _("InVesalius 3")) | ||
1777 | + | ||
1778 | + Publisher.sendMessage('End busy cursor') | ||
1779 | + | ||
1780 | + def OnLoadParameters(self, event=None): | ||
1781 | + import json | ||
1782 | + filename = dlg.ShowLoadSaveDialog(message=_(u"Load Trekker configuration"), | ||
1783 | + wildcard=_("JSON file (*.json)|*.json")) | ||
1784 | + try: | ||
1785 | + # Check if filename exists, read the JSON file and check if all parameters match | ||
1786 | + # with the required list defined in the constants module | ||
1787 | + # if a parameter is missing, raise an error | ||
1788 | + if filename: | ||
1789 | + with open(filename) as json_file: | ||
1790 | + self.trekker_cfg = json.load(json_file) | ||
1791 | + assert all(name in self.trekker_cfg for name in const.TREKKER_CONFIG) | ||
1792 | + if self.trekker: | ||
1793 | + self.trekker, n_threads = dti.set_trekker_parameters(self.trekker, self.trekker_cfg) | ||
1794 | + Publisher.sendMessage('Update Trekker object', data=self.trekker) | ||
1795 | + Publisher.sendMessage('Update number of threads', data=n_threads) | ||
1796 | + | ||
1797 | + Publisher.sendMessage('Update status text in GUI', label=_("Trekker config loaded")) | ||
1798 | + | ||
1799 | + except (AssertionError, json.decoder.JSONDecodeError): | ||
1800 | + # Inform user that file is not compatible | ||
1801 | + self.trekker_cfg = const.TREKKER_CONFIG | ||
1802 | + wx.MessageBox(_("File incompatible, using default configuration."), _("InVesalius 3")) | ||
1803 | + Publisher.sendMessage('Update status text in GUI', label="") | ||
1804 | + | ||
1805 | + def OnUpdateTracts(self, arg, position): | ||
1806 | + """ | ||
1807 | + Minimal working version of tract computation. Updates when cross sends Pubsub message to update. | ||
1808 | + Position refers to the coordinates in InVesalius 2D space. To represent the same coordinates in the 3D space, | ||
1809 | + flip_x the coordinates and multiply the z coordinate by -1. This is all done in the flix_x function. | ||
1810 | + | ||
1811 | + :param arg: event for pubsub | ||
1812 | + :param position: list or array with the x, y, and z coordinates in InVesalius space | ||
1813 | + """ | ||
1814 | + # Minimal working version of tract computation | ||
1815 | + # It updates when cross updates | ||
1816 | + # pass | ||
1817 | + if self.view_tracts and not self.nav_status: | ||
1818 | + # print("Running during navigation") | ||
1819 | + coord_flip = db.flip_x_m(position[:3])[:3, 0] | ||
1820 | + dti.compute_tracts(self.trekker, coord_flip, self.affine, self.affine_vtk, | ||
1821 | + self.n_tracts) | ||
1822 | + | ||
1823 | + def OnCloseProject(self): | ||
1824 | + self.checktracts.SetValue(False) | ||
1825 | + self.checktracts.Enable(0) | ||
1826 | + self.checkpeeling.SetValue(False) | ||
1827 | + self.checkpeeling.Enable(0) | ||
1828 | + | ||
1829 | + self.spin_opacity.SetValue(const.BRAIN_OPACITY) | ||
1830 | + self.spin_opacity.Enable(0) | ||
1831 | + Publisher.sendMessage('Update peel', flag=False, actor=self.brain_actor) | ||
1832 | + | ||
1833 | + self.peel_depth = const.PEEL_DEPTH | ||
1834 | + self.n_tracts = const.N_TRACTS | ||
1835 | + | ||
1836 | + Publisher.sendMessage('Remove tracts') | ||
1837 | + | ||
1838 | + | ||
1839 | +class QueueCustom(queue.Queue): | ||
1840 | + """ | ||
1841 | + A custom queue subclass that provides a :meth:`clear` method. | ||
1842 | + https://stackoverflow.com/questions/6517953/clear-all-items-from-the-queue | ||
1843 | + Modified to a LIFO Queue type (Last-in-first-out). Seems to make sense for the navigation | ||
1844 | + threads, as the last added coordinate should be the first to be processed. | ||
1845 | + In the first tests in a short run, seems to increase the coord queue size considerably, | ||
1846 | + possibly limiting the queue size is good. | ||
1847 | + """ | ||
1848 | + | ||
1849 | + def clear(self): | ||
1850 | + """ | ||
1851 | + Clears all items from the queue. | ||
1852 | + """ | ||
1853 | + | ||
1854 | + with self.mutex: | ||
1855 | + unfinished = self.unfinished_tasks - len(self.queue) | ||
1856 | + if unfinished <= 0: | ||
1857 | + if unfinished < 0: | ||
1858 | + raise ValueError('task_done() called too many times') | ||
1859 | + self.all_tasks_done.notify_all() | ||
1860 | + self.unfinished_tasks = unfinished | ||
1861 | + self.queue.clear() | ||
1862 | + self.not_full.notify_all() | ||
1863 | + | ||
1864 | + | ||
1865 | +class UpdateNavigationScene(threading.Thread): | ||
1866 | + | ||
1867 | + def __init__(self, vis_queues, vis_components, event, sle): | ||
1868 | + """Class (threading) to update the navigation scene with all graphical elements. | ||
1869 | + | ||
1870 | + Sleep function in run method is used to avoid blocking GUI and more fluent, real-time navigation | ||
1871 | + | ||
1872 | + :param affine_vtk: Affine matrix in vtkMatrix4x4 instance to update objects position in 3D scene | ||
1873 | + :type affine_vtk: vtkMatrix4x4 | ||
1874 | + :param visualization_queue: Queue instance that manage coordinates to be visualized | ||
1875 | + :type visualization_queue: queue.Queue | ||
1876 | + :param event: Threading event to coordinate when tasks as done and allow UI release | ||
1877 | + :type event: threading.Event | ||
1878 | + :param sle: Sleep pause in seconds | ||
1879 | + :type sle: float | ||
1880 | + """ | ||
1881 | + | ||
1882 | + threading.Thread.__init__(self, name='UpdateScene') | ||
1883 | + self.trigger_state, self.view_tracts = vis_components | ||
1884 | + self.coord_queue, self.trigger_queue, self.tracts_queue = vis_queues | ||
1885 | + self.sle = sle | ||
1886 | + self.event = event | ||
1887 | + | ||
1888 | + def run(self): | ||
1889 | + # count = 0 | ||
1890 | + while not self.event.is_set(): | ||
1891 | + got_coords = False | ||
1892 | + try: | ||
1893 | + coord, m_img, view_obj = self.coord_queue.get_nowait() | ||
1894 | + got_coords = True | ||
1895 | + | ||
1896 | + # print('UpdateScene: get {}'.format(count)) | ||
1897 | + | ||
1898 | + # use of CallAfter is mandatory otherwise crashes the wx interface | ||
1899 | + if self.view_tracts: | ||
1900 | + bundle, affine_vtk, coord_offset = self.tracts_queue.get_nowait() | ||
1901 | + wx.CallAfter(Publisher.sendMessage, 'Remove tracts') | ||
1902 | + wx.CallAfter(Publisher.sendMessage, 'Update tracts', flag=True, root=bundle, | ||
1903 | + affine_vtk=affine_vtk) | ||
1904 | + wx.CallAfter(Publisher.sendMessage, 'Update marker offset', coord_offset=coord_offset) | ||
1905 | + self.tracts_queue.task_done() | ||
1906 | + | ||
1907 | + if self.trigger_state: | ||
1908 | + trigger_on = self.trigger_queue.get_nowait() | ||
1909 | + if trigger_on: | ||
1910 | + wx.CallAfter(Publisher.sendMessage, 'Create marker') | ||
1911 | + self.trigger_queue.task_done() | ||
1912 | + | ||
1913 | + # TODO: If using the view_tracts substitute the raw coord from the offset coordinate, so the user | ||
1914 | + # see the red cross in the position of the offset marker | ||
1915 | + wx.CallAfter(Publisher.sendMessage, 'Update cross position', arg=m_img, position=coord) | ||
1916 | + | ||
1917 | + if view_obj: | ||
1918 | + wx.CallAfter(Publisher.sendMessage, 'Update object matrix', m_img=m_img, coord=coord) | ||
1919 | + | ||
1920 | + self.coord_queue.task_done() | ||
1921 | + # print('UpdateScene: done {}'.format(count)) | ||
1922 | + # count += 1 | ||
1923 | + | ||
1924 | + sleep(self.sle) | ||
1925 | + except queue.Empty: | ||
1926 | + if got_coords: | ||
1927 | + self.coord_queue.task_done() | ||
1928 | + | ||
1929 | + | ||
1930 | +class InputAttributes(object): | ||
1931 | + # taken from https://stackoverflow.com/questions/2466191/set-attributes-from-dictionary-in-python | ||
1932 | + def __init__(self, *initial_data, **kwargs): | ||
1933 | + for dictionary in initial_data: | ||
1934 | + for key in dictionary: | ||
1935 | + setattr(self, key, dictionary[key]) | ||
1936 | + for key in kwargs: | ||
1937 | + setattr(self, key, kwargs[key]) |
invesalius/project.py
@@ -313,8 +313,9 @@ class Project(metaclass=Singleton): | @@ -313,8 +313,9 @@ class Project(metaclass=Singleton): | ||
313 | self.spacing = project["spacing"] | 313 | self.spacing = project["spacing"] |
314 | if project.get("affine", ""): | 314 | if project.get("affine", ""): |
315 | self.affine = project["affine"] | 315 | self.affine = project["affine"] |
316 | + # self.affine = project.get("affine") | ||
316 | Publisher.sendMessage('Update affine matrix', | 317 | Publisher.sendMessage('Update affine matrix', |
317 | - affine=self.affine, status=True) | 318 | + affine=np.asarray(self.affine).reshape(4, 4), status=True) |
318 | 319 | ||
319 | self.compress = project.get("compress", True) | 320 | self.compress = project.get("compress", True) |
320 | 321 |