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 | 19 | |
| 20 | 20 | import os.path |
| 21 | 21 | import platform |
| 22 | +import psutil | |
| 22 | 23 | import sys |
| 23 | 24 | import wx |
| 24 | 25 | import itertools |
| ... | ... | @@ -521,6 +522,11 @@ ID_GOTO_COORD = wx.NewId() |
| 521 | 522 | |
| 522 | 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 | 531 | STATE_DEFAULT = 1000 |
| 526 | 532 | STATE_WL = 1001 |
| ... | ... | @@ -545,6 +551,7 @@ SLICE_STATE_REMOVE_MASK_PARTS = 3012 |
| 545 | 551 | SLICE_STATE_SELECT_MASK_PARTS = 3013 |
| 546 | 552 | SLICE_STATE_FFILL_SEGMENTATION = 3014 |
| 547 | 553 | SLICE_STATE_CROP_MASK = 3015 |
| 554 | +SLICE_STATE_TRACTS = 3016 | |
| 548 | 555 | |
| 549 | 556 | VOLUME_STATE_SEED = 2001 |
| 550 | 557 | # STATE_LINEAR_MEASURE = 3001 |
| ... | ... | @@ -559,7 +566,7 @@ TOOL_STATES = [STATE_WL, STATE_SPIN, STATE_ZOOM, |
| 559 | 566 | |
| 560 | 567 | |
| 561 | 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 | 572 | SLICE_STYLES = TOOL_STATES + TOOL_SLICE_STATES |
| ... | ... | @@ -651,6 +658,8 @@ BOOLEAN_XOR = 4 |
| 651 | 658 | |
| 652 | 659 | #------------ Navigation defaults ------------------- |
| 653 | 660 | |
| 661 | +MARKER_COLOUR = (1.0, 1.0, 0.) | |
| 662 | +MARKER_SIZE = 2 | |
| 654 | 663 | SELECT = 0 |
| 655 | 664 | MTC = 1 |
| 656 | 665 | FASTRAK = 2 |
| ... | ... | @@ -738,3 +747,17 @@ COIL_COORD_THRESHOLD = 3 |
| 738 | 747 | TIMESTAMP = 2.0 |
| 739 | 748 | |
| 740 | 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 | 32 | import invesalius.data.measures as measures |
| 33 | 33 | import invesalius.data.slice_ as sl |
| 34 | 34 | import invesalius.data.surface as srf |
| 35 | +import invesalius.data.transformations as tr | |
| 35 | 36 | import invesalius.data.volume as volume |
| 36 | 37 | import invesalius.gui.dialogs as dialog |
| 37 | 38 | import invesalius.project as prj |
| ... | ... | @@ -57,7 +58,6 @@ class Controller(): |
| 57 | 58 | def __init__(self, frame): |
| 58 | 59 | self.surface_manager = srf.SurfaceManager() |
| 59 | 60 | self.volume = volume.Volume() |
| 60 | - self.plugin_manager = plugins.PluginManager() | |
| 61 | 61 | self.__bind_events() |
| 62 | 62 | self.frame = frame |
| 63 | 63 | self.progress_dialog = None |
| ... | ... | @@ -76,12 +76,9 @@ class Controller(): |
| 76 | 76 | |
| 77 | 77 | Publisher.sendMessage('Load Preferences') |
| 78 | 78 | |
| 79 | - self.plugin_manager.find_plugins() | |
| 80 | - | |
| 81 | 79 | def __bind_events(self): |
| 82 | 80 | Publisher.subscribe(self.OnImportMedicalImages, 'Import directory') |
| 83 | 81 | Publisher.subscribe(self.OnImportGroup, 'Import group') |
| 84 | - Publisher.subscribe(self.OnImportFolder, 'Import folder') | |
| 85 | 82 | Publisher.subscribe(self.OnShowDialogImportDirectory, |
| 86 | 83 | 'Show import directory dialog') |
| 87 | 84 | Publisher.subscribe(self.OnShowDialogImportOtherFiles, |
| ... | ... | @@ -123,8 +120,6 @@ class Controller(): |
| 123 | 120 | |
| 124 | 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 | 123 | def SetBitmapSpacing(self, spacing): |
| 129 | 124 | proj = prj.Project() |
| 130 | 125 | proj.spacing = spacing |
| ... | ... | @@ -336,6 +331,10 @@ class Controller(): |
| 336 | 331 | |
| 337 | 332 | self.Slice.window_level = proj.level |
| 338 | 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 | 339 | Publisher.sendMessage('Update threshold limits list', |
| 341 | 340 | threshold_range=proj.threshold_range) |
| ... | ... | @@ -504,32 +503,7 @@ class Controller(): |
| 504 | 503 | self.LoadProject() |
| 505 | 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 | 508 | def LoadProject(self): |
| 535 | 509 | proj = prj.Project() |
| ... | ... | @@ -688,6 +662,9 @@ class Controller(): |
| 688 | 662 | proj.matrix_dtype = matrix.dtype.name |
| 689 | 663 | proj.matrix_filename = matrix_filename |
| 690 | 664 | |
| 665 | + if self.affine is not None: | |
| 666 | + proj.affine = self.affine.tolist() | |
| 667 | + | |
| 691 | 668 | # Orientation must be CORONAL in order to as_closes_canonical and |
| 692 | 669 | # swap axis in img2memmap to work in a standardized way. |
| 693 | 670 | # TODO: Create standard import image for all acquisition orientations |
| ... | ... | @@ -700,7 +677,6 @@ class Controller(): |
| 700 | 677 | proj.level = self.Slice.window_level |
| 701 | 678 | proj.threshold_range = int(matrix.min()), int(matrix.max()) |
| 702 | 679 | proj.spacing = self.Slice.spacing |
| 703 | - proj.affine = self.affine.tolist() | |
| 704 | 680 | |
| 705 | 681 | ###### |
| 706 | 682 | session = ses.Session() |
| ... | ... | @@ -872,11 +848,13 @@ class Controller(): |
| 872 | 848 | def OnOpenOtherFiles(self, filepath): |
| 873 | 849 | filepath = utils.decode(filepath, const.FS_ENCODE) |
| 874 | 850 | if not(filepath) == None: |
| 875 | - name = os.path.basename(filepath).split(".")[0] | |
| 851 | + name = filepath.rpartition('\\')[-1].split('.') | |
| 852 | + | |
| 876 | 853 | group = oth.ReadOthers(filepath) |
| 854 | + | |
| 877 | 855 | if group: |
| 878 | 856 | matrix, matrix_filename = self.OpenOtherFiles(group) |
| 879 | - self.CreateOtherProject(name, matrix, matrix_filename) | |
| 857 | + self.CreateOtherProject(str(name[0]), matrix, matrix_filename) | |
| 880 | 858 | self.LoadProject() |
| 881 | 859 | Publisher.sendMessage("Enable state project", state=True) |
| 882 | 860 | else: |
| ... | ... | @@ -981,10 +959,10 @@ class Controller(): |
| 981 | 959 | self.matrix, scalar_range, self.filename = image_utils.img2memmap(group) |
| 982 | 960 | |
| 983 | 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 | 966 | hdr.set_data_dtype('int16') |
| 989 | 967 | dims = hdr.get_zooms() |
| 990 | 968 | dimsf = tuple([float(s) for s in dims]) |
| ... | ... | @@ -1000,6 +978,18 @@ class Controller(): |
| 1000 | 978 | self.Slice.window_level = wl |
| 1001 | 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 | 993 | scalar_range = int(scalar_range[0]), int(scalar_range[1]) |
| 1004 | 994 | Publisher.sendMessage('Update threshold limits list', |
| 1005 | 995 | threshold_range=scalar_range) |
| ... | ... | @@ -1066,24 +1056,3 @@ class Controller(): |
| 1066 | 1056 | |
| 1067 | 1057 | def ApplyReorientation(self): |
| 1068 | 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 | 1 | import numpy as np |
| 3 | 2 | import invesalius.data.coordinates as dco |
| 4 | 3 | import invesalius.data.transformations as tr |
| ... | ... | @@ -23,8 +22,7 @@ def angle_calculation(ap_axis, coil_axis): |
| 23 | 22 | |
| 24 | 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 | 26 | q: origin of coordinate system |
| 29 | 27 | g1, g2, g3: orthogonal vectors of coordinate system |
| 30 | 28 | |
| ... | ... | @@ -49,9 +47,9 @@ def base_creation_old(fiducials): |
| 49 | 47 | |
| 50 | 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 | 54 | m = np.matrix([[g1[0], g1[1], g1[2]], |
| 57 | 55 | [g2[0], g2[1], g2[2]], |
| ... | ... | @@ -79,7 +77,7 @@ def base_creation(fiducials): |
| 79 | 77 | |
| 80 | 78 | sub1 = p2 - p1 |
| 81 | 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 | 82 | q = p1 + lamb*sub1 |
| 85 | 83 | g1 = p3 - q |
| ... | ... | @@ -90,20 +88,52 @@ def base_creation(fiducials): |
| 90 | 88 | |
| 91 | 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 | 138 | Calculate the Fiducial Registration Error for neuronavigation. |
| 109 | 139 | |
| ... | ... | @@ -115,19 +145,29 @@ def calculate_fre(fiducials, minv, n, q, o): |
| 115 | 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 | 158 | img = np.zeros([3, 3]) |
| 119 | 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 | 172 | dist[0] = np.sqrt(np.sum(np.power((img[0, :] - fiducials[0, :]), 2))) |
| 133 | 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 | 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 | 243 | def flip_x_m(point): |
| ... | ... | @@ -190,14 +254,14 @@ def flip_x_m(point): |
| 190 | 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 | 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 | 267 | def object_registration(fiducials, orients, coord_raw, m_change): |
| ... | ... | @@ -224,48 +288,48 @@ def object_registration(fiducials, orients, coord_raw, m_change): |
| 224 | 288 | fids_raw[ic, :] = dco.dynamic_reference_m2(coords[ic, :], coords[3, :])[:3] |
| 225 | 289 | |
| 226 | 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 | 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 | 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 | 303 | for ic in range(0, 4): |
| 240 | 304 | if coord_raw.any(): |
| 241 | 305 | # compute object fiducials in reference frame |
| 242 | 306 | fids_dyn[ic, :] = dco.dynamic_reference_m2(coords[ic, :], coord_raw[1, :]) |
| 243 | - fids_dyn[ic, 2] = -fids_dyn[ic, 2] | |
| 244 | 307 | else: |
| 245 | 308 | # compute object fiducials in source frame |
| 246 | 309 | fids_dyn[ic, :] = coords[ic, :] |
| 310 | + fids_dyn[ic, 2] = -fids_dyn[ic, 2] | |
| 247 | 311 | |
| 248 | 312 | # compute object fiducials in vtk head frame |
| 249 | 313 | a, b, g = np.radians(fids_dyn[ic, 3:]) |
| 250 | 314 | T_p = tr.translation_matrix(fids_dyn[ic, :3]) |
| 251 | 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 | 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 | 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 | 327 | r_obj_img[:3, :3] = base_obj_img[:3, :3] |
| 264 | 328 | |
| 265 | 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 | 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 | 20 | from math import sin, cos |
| 21 | 21 | import numpy as np |
| 22 | 22 | |
| 23 | +import invesalius.data.bases as db | |
| 23 | 24 | import invesalius.data.transformations as tr |
| 24 | 25 | import invesalius.constants as const |
| 25 | 26 | |
| ... | ... | @@ -74,7 +75,7 @@ def PolarisCoord(trck_init, trck_id, ref_mode): |
| 74 | 75 | coord3 = np.hstack((trans_obj, angles_obj)) |
| 75 | 76 | |
| 76 | 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 | 80 | return coord |
| 80 | 81 | |
| ... | ... | @@ -232,19 +233,45 @@ def DebugCoord(trk_init, trck_id, ref_mode): |
| 232 | 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 | 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 | 324 | :param reference: sensor two defined as reference |
| 298 | 325 | :return: rotated and translated coordinates |
| 299 | 326 | """ |
| 300 | - | |
| 301 | 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 | 342 | def dynamic_reference_m2(probe, reference): |
| 315 | 343 | """ |
| ... | ... | @@ -331,70 +359,18 @@ def dynamic_reference_m2(probe, reference): |
| 331 | 359 | T_p = tr.translation_matrix(probe[:3]) |
| 332 | 360 | R = tr.euler_matrix(a, b, g, 'rzyx') |
| 333 | 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 | 367 | al, be, ga = tr.euler_from_matrix(M_dyn, 'rzyx') |
| 342 | 368 | coord_rot = tr.translation_from_matrix(M_dyn) |
| 343 | 369 | |
| 344 | 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 | 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 | 375 | def str2float(data): |
| 400 | 376 | """ |
| ... | ... | @@ -414,3 +390,15 @@ def str2float(data): |
| 414 | 390 | data = [float(s) for s in data[1:len(data)]] |
| 415 | 391 | |
| 416 | 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 | 17 | # detalhes. |
| 18 | 18 | #-------------------------------------------------------------------------- |
| 19 | 19 | |
| 20 | +import numpy as np | |
| 21 | +import queue | |
| 20 | 22 | import threading |
| 21 | 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 | 25 | import invesalius.data.coordinates as dco |
| 28 | 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 | 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 | 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 | 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 | 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 | 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 | 37 | import invesalius.reader.bitmap_reader as bitmap_reader |
| 38 | 38 | import invesalius.utils as utils |
| 39 | 39 | import invesalius.data.converters as converters |
| 40 | +import invesalius.data.slice_ as sl | |
| 41 | +import invesalius.data.transformations as tr | |
| 40 | 42 | |
| 41 | 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 | 518 | imin, imax = image.min(), image.max() |
| 517 | 519 | output[:] = (image - imin) * ((max_ - min_) / (imax - imin)) + min_ |
| 518 | 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 | 42 | self.start() |
| 43 | 43 | |
| 44 | 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 | 48 | def UpdateCurrentCoords(self, arg, position): |
| 48 | 49 | self.coord = asarray(position) |
| ... | ... | @@ -50,7 +51,10 @@ class Record(threading.Thread): |
| 50 | 51 | def stop(self): |
| 51 | 52 | self._pause_ = True |
| 52 | 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 | 58 | if filename: |
| 55 | 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 | 82 | self.blend_filter = None |
| 83 | 83 | self.histogram = None |
| 84 | 84 | self._matrix = None |
| 85 | + self._affine = np.identity(4) | |
| 86 | + self._n_tracts = 0 | |
| 87 | + self._tracker = None | |
| 85 | 88 | self.aux_matrices = {} |
| 86 | 89 | self.aux_matrices_colours = {} |
| 87 | 90 | self.state = const.STATE_DEFAULT |
| ... | ... | @@ -144,6 +147,30 @@ class Slice(metaclass=utils.Singleton): |
| 144 | 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 | 174 | def __bind_events(self): |
| 148 | 175 | # General slice control |
| 149 | 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 | 47 | |
| 48 | 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 | 58 | ORIENTATIONS = { |
| 51 | 59 | "AXIAL": const.AXIAL, |
| 52 | 60 | "CORONAL": const.CORONAL, |
| ... | ... | @@ -465,6 +473,18 @@ class CrossInteractorStyle(DefaultInteractorStyle): |
| 465 | 473 | self.slice_actor = viewer.slice_data.actor |
| 466 | 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 | 488 | self.picker = vtk.vtkWorldPointPicker() |
| 469 | 489 | |
| 470 | 490 | self.AddObserver("MouseMoveEvent", self.OnCrossMove) |
| ... | ... | @@ -478,6 +498,7 @@ class CrossInteractorStyle(DefaultInteractorStyle): |
| 478 | 498 | |
| 479 | 499 | def CleanUp(self): |
| 480 | 500 | self.viewer._set_cross_visibility(0) |
| 501 | + Publisher.sendMessage('Remove tracts') | |
| 481 | 502 | Publisher.sendMessage('Toggle toolbar item', |
| 482 | 503 | _id=self.state_code, value=False) |
| 483 | 504 | |
| ... | ... | @@ -497,12 +518,20 @@ class CrossInteractorStyle(DefaultInteractorStyle): |
| 497 | 518 | wx, wy, wz = self.viewer.get_coordinate_cursor(mouse_x, mouse_y, self.picker) |
| 498 | 519 | px, py = self.viewer.get_slice_pixel_coord_by_world_pos(wx, wy, wz) |
| 499 | 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 | 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 | 536 | def ScrollSlice(self, coord): |
| 508 | 537 | if self.orientation == "AXIAL": |
| ... | ... | @@ -522,6 +551,92 @@ class CrossInteractorStyle(DefaultInteractorStyle): |
| 522 | 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 | 640 | class WWWLInteractorStyle(DefaultInteractorStyle): |
| 526 | 641 | """ |
| 527 | 642 | Interactor style responsible for Window Level & Width functionality. |
| ... | ... | @@ -2784,6 +2899,7 @@ class Styles: |
| 2784 | 2899 | const.SLICE_STATE_SELECT_MASK_PARTS: SelectMaskPartsInteractorStyle, |
| 2785 | 2900 | const.SLICE_STATE_FFILL_SEGMENTATION: FloodFillSegmentInteractorStyle, |
| 2786 | 2901 | const.SLICE_STATE_CROP_MASK: CropMaskInteractorStyle, |
| 2902 | + const.SLICE_STATE_TRACTS: TractsInteractorStyle, | |
| 2787 | 2903 | } |
| 2788 | 2904 | |
| 2789 | 2905 | @classmethod | ... | ... |
invesalius/data/surface.py
| ... | ... | @@ -383,6 +383,7 @@ class SurfaceManager(): |
| 383 | 383 | |
| 384 | 384 | actor = vtk.vtkActor() |
| 385 | 385 | actor.SetMapper(mapper) |
| 386 | + actor.GetProperty().SetBackfaceCulling(1) | |
| 386 | 387 | |
| 387 | 388 | print("BOunds", actor.GetBounds()) |
| 388 | 389 | |
| ... | ... | @@ -494,6 +495,7 @@ class SurfaceManager(): |
| 494 | 495 | |
| 495 | 496 | # Represent an object (geometry & properties) in the rendered scene |
| 496 | 497 | actor = vtk.vtkActor() |
| 498 | + actor.GetProperty().SetBackfaceCulling(1) | |
| 497 | 499 | actor.SetMapper(mapper) |
| 498 | 500 | |
| 499 | 501 | # Set actor colour and transparency |
| ... | ... | @@ -536,6 +538,7 @@ class SurfaceManager(): |
| 536 | 538 | |
| 537 | 539 | # Represent an object (geometry & properties) in the rendered scene |
| 538 | 540 | actor = vtk.vtkActor() |
| 541 | + actor.GetProperty().SetBackfaceCulling(1) | |
| 539 | 542 | actor.SetMapper(mapper) |
| 540 | 543 | del mapper |
| 541 | 544 | #Create Surface instance | ... | ... |
invesalius/data/trackers.py
| ... | ... | @@ -62,6 +62,7 @@ def DefaultTracker(tracker_id): |
| 62 | 62 | # return tracker initialization variable and type of connection |
| 63 | 63 | return trck_init, 'wrapper' |
| 64 | 64 | |
| 65 | + | |
| 65 | 66 | def PolarisTracker(tracker_id): |
| 66 | 67 | from wx import ID_OK |
| 67 | 68 | trck_init = None |
| ... | ... | @@ -91,6 +92,7 @@ def PolarisTracker(tracker_id): |
| 91 | 92 | # return tracker initialization variable and type of connection |
| 92 | 93 | return trck_init, lib_mode |
| 93 | 94 | |
| 95 | + | |
| 94 | 96 | def CameraTracker(tracker_id): |
| 95 | 97 | trck_init = None |
| 96 | 98 | try: |
| ... | ... | @@ -105,6 +107,7 @@ def CameraTracker(tracker_id): |
| 105 | 107 | # return tracker initialization variable and type of connection |
| 106 | 108 | return trck_init, 'wrapper' |
| 107 | 109 | |
| 110 | + | |
| 108 | 111 | def ClaronTracker(tracker_id): |
| 109 | 112 | import invesalius.constants as const |
| 110 | 113 | from invesalius import inv_paths | ... | ... |
| ... | ... | @@ -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 | 39 | try: |
| 40 | 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 | 43 | self.COM = True |
| 44 | 44 | |
| 45 | 45 | except: |
| ... | ... | @@ -82,3 +82,91 @@ class Trigger(threading.Thread): |
| 82 | 82 | if self.trigger_init: |
| 83 | 83 | self.trigger_init.close() |
| 84 | 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 | 573 | |
| 574 | 574 | self.cross.SetFocalPoint((ux, uy, uz)) |
| 575 | 575 | self.ScrollSlice(coord) |
| 576 | - Publisher.sendMessage('Set ball reference position', position=(ux, uy, uz)) | |
| 576 | + self.interactor.Render() | |
| 577 | 577 | |
| 578 | 578 | def ScrollSlice(self, coord): |
| 579 | 579 | if self.orientation == "AXIAL": |
| ... | ... | @@ -817,9 +817,9 @@ class Viewer(wx.Panel): |
| 817 | 817 | ('Set scroll position', |
| 818 | 818 | self.orientation)) |
| 819 | 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 | 824 | # Publisher.subscribe(self.ChangeBrushColour, |
| 825 | 825 | # 'Add mask') |
| ... | ... | @@ -1136,8 +1136,9 @@ class Viewer(wx.Panel): |
| 1136 | 1136 | |
| 1137 | 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 | 1143 | def _set_cross_visibility(self, visibility): |
| 1143 | 1144 | self.cross_actor.SetVisibility(visibility) |
| ... | ... | @@ -1296,8 +1297,8 @@ class Viewer(wx.Panel): |
| 1296 | 1297 | if self.state == const.SLICE_STATE_CROSS: |
| 1297 | 1298 | # Update other slice's cross according to the new focal point from |
| 1298 | 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 | 1302 | Publisher.sendMessage('Update slice viewer') |
| 1302 | 1303 | else: |
| 1303 | 1304 | self.interactor.Render() |
| ... | ... | @@ -1509,7 +1510,6 @@ class Viewer(wx.Panel): |
| 1509 | 1510 | |
| 1510 | 1511 | def UpdateCross(self, coord): |
| 1511 | 1512 | self.cross.SetFocalPoint(coord) |
| 1512 | - Publisher.sendMessage('Set ball reference position', position=self.cross.GetFocalPoint()) | |
| 1513 | 1513 | Publisher.sendMessage('Co-registered points', arg=None, position=(coord[0], coord[1], coord[2], 0., 0., 0.)) |
| 1514 | 1514 | self.OnScrollBar() |
| 1515 | 1515 | self.interactor.Render() | ... | ... |
invesalius/data/viewer_volume.py
| ... | ... | @@ -19,9 +19,10 @@ |
| 19 | 19 | # detalhes. |
| 20 | 20 | #-------------------------------------------------------------------------- |
| 21 | 21 | |
| 22 | -from math import cos, sin | |
| 22 | +# from math import cos, sin | |
| 23 | 23 | import os |
| 24 | 24 | import sys |
| 25 | +import time | |
| 25 | 26 | |
| 26 | 27 | import numpy as np |
| 27 | 28 | from numpy.core.umath_tests import inner1d |
| ... | ... | @@ -36,6 +37,7 @@ from imageio import imsave |
| 36 | 37 | |
| 37 | 38 | import invesalius.constants as const |
| 38 | 39 | import invesalius.data.bases as bases |
| 40 | +import invesalius.data.slice_ as sl | |
| 39 | 41 | import invesalius.data.transformations as tr |
| 40 | 42 | import invesalius.data.vtk_utils as vtku |
| 41 | 43 | import invesalius.project as prj |
| ... | ... | @@ -168,6 +170,10 @@ class Viewer(wx.Panel): |
| 168 | 170 | #self.pTarget = [0., 0., 0.] |
| 169 | 171 | |
| 170 | 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 | 178 | self._mode_cross = False |
| 173 | 179 | self._to_show_ball = 0 |
| ... | ... | @@ -188,12 +194,31 @@ class Viewer(wx.Panel): |
| 188 | 194 | self.anglethreshold = const.COIL_ANGLES_THRESHOLD |
| 189 | 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 | 216 | def __bind_events(self): |
| 192 | 217 | Publisher.subscribe(self.LoadActor, |
| 193 | 218 | 'Load surface actor into viewer') |
| 194 | 219 | Publisher.subscribe(self.RemoveActor, |
| 195 | 220 | 'Remove surface actor from viewer') |
| 196 | - Publisher.subscribe(self.OnShowSurface, 'Show surface') | |
| 221 | + # Publisher.subscribe(self.OnShowSurface, 'Show surface') | |
| 197 | 222 | Publisher.subscribe(self.UpdateRender, |
| 198 | 223 | 'Render volume viewer') |
| 199 | 224 | Publisher.subscribe(self.ChangeBackgroundColour, |
| ... | ... | @@ -225,7 +250,8 @@ class Viewer(wx.Panel): |
| 225 | 250 | Publisher.subscribe(self.LoadSlicePlane, 'Load slice plane') |
| 226 | 251 | |
| 227 | 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 | 255 | # Publisher.subscribe(self.SetVolumeCamera, 'Set camera in volume') |
| 230 | 256 | Publisher.subscribe(self.SetVolumeCameraState, 'Update volume camera state') |
| 231 | 257 | |
| ... | ... | @@ -255,8 +281,8 @@ class Viewer(wx.Panel): |
| 255 | 281 | |
| 256 | 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 | 286 | Publisher.subscribe(self._check_ball_reference, 'Enable style') |
| 261 | 287 | Publisher.subscribe(self._uncheck_ball_reference, 'Disable style') |
| 262 | 288 | |
| ... | ... | @@ -283,11 +309,17 @@ class Viewer(wx.Panel): |
| 283 | 309 | Publisher.subscribe(self.OnUpdateObjectTargetGuide, 'Update object matrix') |
| 284 | 310 | Publisher.subscribe(self.OnUpdateTargetCoordinates, 'Update target') |
| 285 | 311 | Publisher.subscribe(self.OnRemoveTarget, 'Disable or enable coil tracker') |
| 286 | - # Publisher.subscribe(self.UpdateObjectTargetView, 'Co-registered points') | |
| 287 | 312 | Publisher.subscribe(self.OnTargetMarkerTransparency, 'Set target transparency') |
| 288 | 313 | Publisher.subscribe(self.OnUpdateAngleThreshold, 'Update angle threshold') |
| 289 | 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 | 323 | def SetStereoMode(self, mode): |
| 292 | 324 | ren_win = self.interactor.GetRenderWindow() |
| 293 | 325 | |
| ... | ... | @@ -319,13 +351,23 @@ class Viewer(wx.Panel): |
| 319 | 351 | def _check_ball_reference(self, style): |
| 320 | 352 | if style == const.SLICE_STATE_CROSS: |
| 321 | 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 | 360 | self.interactor.Render() |
| 324 | 361 | |
| 325 | 362 | def _uncheck_ball_reference(self, style): |
| 326 | 363 | if style == const.SLICE_STATE_CROSS: |
| 327 | 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 | 371 | self.interactor.Render() |
| 330 | 372 | |
| 331 | 373 | def OnSensors(self, probe_id, ref_id, obj_id=0): |
| ... | ... | @@ -385,12 +427,12 @@ class Viewer(wx.Panel): |
| 385 | 427 | self.probe = self.ref = self.obj = False |
| 386 | 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 | 437 | def OnStartSeed(self): |
| 396 | 438 | self.seed_points = [] |
| ... | ... | @@ -485,8 +527,8 @@ class Viewer(wx.Panel): |
| 485 | 527 | if (volumes.GetNumberOfItems()): |
| 486 | 528 | self.ren.RemoveVolume(volumes.GetLastProp()) |
| 487 | 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 | 533 | def RemoveActors(self, actors): |
| 492 | 534 | "Remove a list of actors" |
| ... | ... | @@ -534,11 +576,11 @@ class Viewer(wx.Panel): |
| 534 | 576 | Markers created by navigation tools and rendered in volume viewer. |
| 535 | 577 | """ |
| 536 | 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 | 581 | ball_ref = vtk.vtkSphereSource() |
| 540 | 582 | ball_ref.SetRadius(size) |
| 541 | - ball_ref.SetCenter(x, y, z) | |
| 583 | + ball_ref.SetCenter(coord_flip) | |
| 542 | 584 | |
| 543 | 585 | mapper = vtk.vtkPolyDataMapper() |
| 544 | 586 | mapper.SetInputConnection(ball_ref.GetOutputPort()) |
| ... | ... | @@ -557,6 +599,35 @@ class Viewer(wx.Panel): |
| 557 | 599 | #self.UpdateRender() |
| 558 | 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 | 631 | def HideAllMarkers(self, indexes): |
| 561 | 632 | ballid = indexes |
| 562 | 633 | for i in range(0, ballid): |
| ... | ... | @@ -610,7 +681,6 @@ class Viewer(wx.Panel): |
| 610 | 681 | self.staticballs[index].GetProperty().SetColor(color) |
| 611 | 682 | self.Refresh() |
| 612 | 683 | |
| 613 | - | |
| 614 | 684 | def OnTargetMarkerTransparency(self, status, index): |
| 615 | 685 | if status: |
| 616 | 686 | self.staticballs[index].GetProperty().SetOpacity(1) |
| ... | ... | @@ -1104,7 +1174,6 @@ class Viewer(wx.Panel): |
| 1104 | 1174 | cam.SetFocalPoint(cam_focus) |
| 1105 | 1175 | cam.SetPosition(cam_pos) |
| 1106 | 1176 | |
| 1107 | - | |
| 1108 | 1177 | def CreateBallReference(self): |
| 1109 | 1178 | """ |
| 1110 | 1179 | Red sphere on volume visualization to reference center of |
| ... | ... | @@ -1129,31 +1198,14 @@ class Viewer(wx.Panel): |
| 1129 | 1198 | |
| 1130 | 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 | 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 | 1210 | def CreateObjectPolyData(self, filename): |
| 1159 | 1211 | """ |
| ... | ... | @@ -1225,7 +1277,14 @@ class Viewer(wx.Panel): |
| 1225 | 1277 | self.obj_actor.GetProperty().SetOpacity(.4) |
| 1226 | 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 | 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 | 1289 | # self.obj_axes = vtk.vtkAxesActor() |
| 1231 | 1290 | # self.obj_axes.SetShaftTypeToCylinder() |
| ... | ... | @@ -1236,25 +1295,88 @@ class Viewer(wx.Panel): |
| 1236 | 1295 | |
| 1237 | 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 | 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 | 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 | 1375 | self.obj_actor.SetUserMatrix(m_img_vtk) |
| 1257 | 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 | 1381 | self.Refresh() |
| 1260 | 1382 | |
| ... | ... | @@ -1267,13 +1389,42 @@ class Viewer(wx.Panel): |
| 1267 | 1389 | else: |
| 1268 | 1390 | if self.obj_actor: |
| 1269 | 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 | 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 | 1401 | self.Refresh() |
| 1272 | 1402 | |
| 1273 | 1403 | def UpdateShowObjectState(self, state): |
| 1274 | 1404 | self.obj_state = state |
| 1275 | 1405 | if self.obj_actor and not self.obj_state: |
| 1276 | 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 | 1428 | self.Refresh() |
| 1278 | 1429 | |
| 1279 | 1430 | def __bind_events_wx(self): |
| ... | ... | @@ -1448,10 +1599,12 @@ class Viewer(wx.Panel): |
| 1448 | 1599 | def SetVolumeCameraState(self, camera_state): |
| 1449 | 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 | 1604 | if self.camera_state: |
| 1453 | 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 | 1608 | cam = self.ren.GetActiveCamera() |
| 1456 | 1609 | |
| 1457 | 1610 | if self.initial_focus is None: |
| ... | ... | @@ -1545,16 +1698,16 @@ class Viewer(wx.Panel): |
| 1545 | 1698 | def OnShowRaycasting(self): |
| 1546 | 1699 | if not self.raycasting_volume: |
| 1547 | 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 | 1703 | if self.on_wl: |
| 1551 | 1704 | self.text.Show() |
| 1552 | 1705 | |
| 1553 | 1706 | def OnHideRaycasting(self): |
| 1554 | 1707 | self.raycasting_volume = False |
| 1555 | 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 | 1712 | def OnSize(self, evt): |
| 1560 | 1713 | self.UpdateRender() |
| ... | ... | @@ -1581,16 +1734,16 @@ class Viewer(wx.Panel): |
| 1581 | 1734 | |
| 1582 | 1735 | #self.ShowOrientationCube() |
| 1583 | 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 | 1740 | def RemoveActor(self, actor): |
| 1588 | 1741 | utils.debug("RemoveActor") |
| 1589 | 1742 | ren = self.ren |
| 1590 | 1743 | ren.RemoveActor(actor) |
| 1591 | 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 | 1748 | def RemoveAllActor(self): |
| 1596 | 1749 | utils.debug("RemoveAllActor") |
| ... | ... | @@ -1602,7 +1755,8 @@ class Viewer(wx.Panel): |
| 1602 | 1755 | |
| 1603 | 1756 | def LoadVolume(self, volume, colour, ww, wl): |
| 1604 | 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 | 1761 | self.light = self.ren.GetLights().GetNextItem() |
| 1608 | 1762 | |
| ... | ... | @@ -1622,15 +1776,14 @@ class Viewer(wx.Panel): |
| 1622 | 1776 | self.ren.ResetCamera() |
| 1623 | 1777 | self.ren.ResetCameraClippingRange() |
| 1624 | 1778 | |
| 1625 | - self._check_and_set_ball_visibility() | |
| 1626 | 1779 | self.UpdateRender() |
| 1627 | 1780 | |
| 1628 | 1781 | def UnloadVolume(self, volume): |
| 1629 | 1782 | self.ren.RemoveVolume(volume) |
| 1630 | 1783 | del volume |
| 1631 | 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 | 1788 | def OnSetViewAngle(self, view): |
| 1636 | 1789 | self.SetViewAngle(view) |
| ... | ... | @@ -1777,16 +1930,17 @@ class Viewer(wx.Panel): |
| 1777 | 1930 | self.SetViewAngle(const.VOL_ISO) |
| 1778 | 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 | 1945 | class SlicePlane: |
| 1792 | 1946 | def __init__(self): | ... | ... |
invesalius/data/vtk_utils.py
| ... | ... | @@ -287,3 +287,21 @@ class TextZero(object): |
| 287 | 287 | # font.SetWeight(wx.FONTWEIGHT_BOLD) |
| 288 | 288 | font.SetSymbolicSize(self.symbolic_syze) |
| 289 | 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 | 387 | if id_type == const.ID_NIFTI_IMPORT: |
| 388 | 388 | dlg.SetMessage(_("Import NIFTi 1 file")) |
| 389 | 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 | 399 | elif id_type == const.ID_PARREC_IMPORT: |
| 391 | 400 | dlg.SetMessage(_("Import PAR/REC file")) |
| 392 | 401 | dlg.SetWildcard(WILDCARD_PARREC) |
| ... | ... | @@ -508,79 +517,11 @@ def ShowSaveAsProjectDialog(default_filename=None): |
| 508 | 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 | 526 | # Show the dialog and retrieve the user response. If it is the OK response, |
| 586 | 527 | # process the data. |
| ... | ... | @@ -589,73 +530,25 @@ def ShowLoadMarkersDialog(): |
| 589 | 530 | if dlg.ShowModal() == wx.ID_OK: |
| 590 | 531 | # This returns a Python list of files that were selected. |
| 591 | 532 | filepath = dlg.GetPath() |
| 533 | + ok_press = 1 | |
| 534 | + else: | |
| 535 | + ok_press = 0 | |
| 592 | 536 | except(wx._core.PyAssertionError): # FIX: win64 |
| 593 | 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 | 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 | 547 | # Destroy the dialog. Don't do this until you are done with it! |
| 656 | 548 | # BAD things can happen otherwise! |
| 657 | 549 | dlg.Destroy() |
| 658 | 550 | os.chdir(current_dir) |
| 551 | + | |
| 659 | 552 | return filepath |
| 660 | 553 | |
| 661 | 554 | |
| ... | ... | @@ -920,31 +813,9 @@ def SurfaceSelectionRequiredForDuplication(): |
| 920 | 813 | |
| 921 | 814 | |
| 922 | 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 | 820 | Spatial Tracker connection error |
| 950 | 821 | """ |
| ... | ... | @@ -976,85 +847,46 @@ def NavigationTrackerWarning(trck_id, lib_mode): |
| 976 | 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 | 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 | 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 | 856 | dlg.ShowModal() |
| 857 | + result = dlg.GetValue() | |
| 1000 | 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 | 864 | if sys.platform == 'darwin': |
| 1006 | 865 | dlg = wx.MessageDialog(None, "", msg, |
| 1007 | 866 | wx.OK | wx.CANCEL | wx.ICON_QUESTION) |
| 1008 | 867 | else: |
| 1009 | - dlg = wx.MessageDialog(None, msg, "InVesalius 3 - Neuronavigator", | |
| 868 | + dlg = wx.MessageDialog(None, msg, "InVesalius 3", | |
| 1010 | 869 | wx.OK | wx.CANCEL | wx.ICON_QUESTION) |
| 1011 | 870 | result = dlg.ShowModal() |
| 1012 | 871 | dlg.Destroy() |
| 1013 | 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 | 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 | 886 | dlg.Destroy() |
| 1057 | - return result | |
| 887 | + return color_new | |
| 888 | + | |
| 889 | +# ---------------------------------- | |
| 1058 | 890 | |
| 1059 | 891 | |
| 1060 | 892 | class NewMask(wx.Dialog): |
| ... | ... | @@ -3569,7 +3401,10 @@ class ObjectCalibrationDialog(wx.Dialog): |
| 3569 | 3401 | else: |
| 3570 | 3402 | coord = coord_raw[0, :] |
| 3571 | 3403 | else: |
| 3572 | - NavigationTrackerWarning(0, 'choose') | |
| 3404 | + ShowNavigationTrackerWarning(0, 'choose') | |
| 3405 | + | |
| 3406 | + if btn_id == 3: | |
| 3407 | + coord = np.zeros([6,]) | |
| 3573 | 3408 | |
| 3574 | 3409 | # Update text controls with tracker coordinates |
| 3575 | 3410 | if coord is not None or np.sum(coord) != 0.0: |
| ... | ... | @@ -3582,7 +3417,7 @@ class ObjectCalibrationDialog(wx.Dialog): |
| 3582 | 3417 | self.ball_actors[btn_id].GetProperty().SetColor(0.0, 1.0, 0.0) |
| 3583 | 3418 | self.Refresh() |
| 3584 | 3419 | else: |
| 3585 | - NavigationTrackerWarning(0, 'choose') | |
| 3420 | + ShowNavigationTrackerWarning(0, 'choose') | |
| 3586 | 3421 | |
| 3587 | 3422 | def OnChoiceRefMode(self, evt): |
| 3588 | 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 | 1185 | else: |
| 1186 | 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 | 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 | 1195 | if value: |
| 1195 | 1196 | self.FindItemById(const.ID_MODE_NAVIGATION).Enable(False) |
| 1196 | 1197 | else: | ... | ... |
invesalius/gui/task_navigator.py
| ... | ... | @@ -18,10 +18,13 @@ |
| 18 | 18 | #-------------------------------------------------------------------------- |
| 19 | 19 | |
| 20 | 20 | from functools import partial |
| 21 | +# import os | |
| 22 | +import queue | |
| 21 | 23 | import sys |
| 22 | -import os | |
| 24 | +import threading | |
| 23 | 25 | |
| 24 | 26 | import numpy as np |
| 27 | +# import Trekker | |
| 25 | 28 | import wx |
| 26 | 29 | |
| 27 | 30 | try: |
| ... | ... | @@ -31,22 +34,23 @@ except ImportError: |
| 31 | 34 | import wx.lib.hyperlink as hl |
| 32 | 35 | import wx.lib.foldpanelbar as fpb |
| 33 | 36 | |
| 37 | +import wx.lib.colourselect as csel | |
| 34 | 38 | import wx.lib.masked.numctrl |
| 35 | 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 | 40 | from time import sleep |
| 41 | 41 | |
| 42 | -import invesalius.data.transformations as tr | |
| 43 | 42 | import invesalius.constants as const |
| 44 | 43 | import invesalius.data.bases as db |
| 44 | +# import invesalius.data.brainmesh_handler as brain | |
| 45 | 45 | import invesalius.data.coordinates as dco |
| 46 | 46 | import invesalius.data.coregistration as dcr |
| 47 | +import invesalius.data.slice_ as sl | |
| 47 | 48 | import invesalius.data.trackers as dt |
| 49 | +import invesalius.data.tractography as dti | |
| 50 | +import invesalius.data.transformations as tr | |
| 48 | 51 | import invesalius.data.trigger as trig |
| 49 | 52 | import invesalius.data.record_coords as rec |
| 53 | +import invesalius.data.vtk_utils as vtk_utils | |
| 50 | 54 | import invesalius.gui.dialogs as dlg |
| 51 | 55 | from invesalius import utils |
| 52 | 56 | |
| ... | ... | @@ -136,7 +140,7 @@ class InnerFoldPanel(wx.Panel): |
| 136 | 140 | # Study this. |
| 137 | 141 | |
| 138 | 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 | 144 | # Fold panel style |
| 141 | 145 | style = fpb.CaptionBarStyle() |
| 142 | 146 | style.SetCaptionStyle(fpb.CAPTIONBAR_GRADIENT_V) |
| ... | ... | @@ -161,15 +165,23 @@ class InnerFoldPanel(wx.Panel): |
| 161 | 165 | leftSpacing=0, rightSpacing=0) |
| 162 | 166 | |
| 163 | 167 | # Fold 3 - Markers panel |
| 164 | - item = fold_panel.AddFoldPanel(_("Extra tools"), collapsed=True) | |
| 168 | + item = fold_panel.AddFoldPanel(_("Markers"), collapsed=True) | |
| 165 | 169 | mtw = MarkersPanel(item) |
| 166 | 170 | |
| 167 | 171 | fold_panel.ApplyCaptionStyle(item, style) |
| 168 | 172 | fold_panel.AddFoldPanelWindow(item, mtw, spacing= 0, |
| 169 | 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 | 185 | self.dbs_item = fold_panel.AddFoldPanel(_("Deep Brain Stimulation"), collapsed=True) |
| 174 | 186 | dtw = DbsPanel(self.dbs_item) #Atribuir nova var, criar panel |
| 175 | 187 | |
| ... | ... | @@ -239,8 +251,8 @@ class InnerFoldPanel(wx.Panel): |
| 239 | 251 | def OnHideDbs(self): |
| 240 | 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 | 256 | self.checktrigger.Enable(False) |
| 245 | 257 | self.checkobj.Enable(False) |
| 246 | 258 | else: |
| ... | ... | @@ -295,6 +307,23 @@ class NeuronavigationPanel(wx.Panel): |
| 295 | 307 | self.obj_reg = None |
| 296 | 308 | self.obj_reg_status = False |
| 297 | 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 | 328 | self.tracker_id = const.DEFAULT_TRACKER |
| 300 | 329 | self.ref_mode_id = const.DEFAULT_REF_MODE |
| ... | ... | @@ -405,10 +434,17 @@ class NeuronavigationPanel(wx.Panel): |
| 405 | 434 | Publisher.subscribe(self.LoadImageFiducials, 'Load image fiducials') |
| 406 | 435 | Publisher.subscribe(self.UpdateTriggerState, 'Update trigger state') |
| 407 | 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 | 438 | Publisher.subscribe(self.OnDisconnectTracker, 'Disconnect tracker') |
| 410 | 439 | Publisher.subscribe(self.UpdateObjectRegistration, 'Update object registration') |
| 411 | 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 | 449 | def LoadImageFiducials(self, marker_id, coord): |
| 414 | 450 | for n in const.BTNS_IMG_MKS: |
| ... | ... | @@ -420,7 +456,37 @@ class NeuronavigationPanel(wx.Panel): |
| 420 | 456 | for m in [0, 1, 2]: |
| 421 | 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 | 490 | # TODO: Change from world coordinates to matrix coordinates. They are better for multi software communication. |
| 425 | 491 | self.current_coord = position |
| 426 | 492 | for m in [0, 1, 2]: |
| ... | ... | @@ -473,7 +539,7 @@ class NeuronavigationPanel(wx.Panel): |
| 473 | 539 | label=_("Tracker disconnected successfully")) |
| 474 | 540 | self.trk_init = dt.TrackerConnection(self.tracker_id, None, 'connect') |
| 475 | 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 | 543 | ctrl.SetSelection(0) |
| 478 | 544 | print("Tracker not connected!") |
| 479 | 545 | else: |
| ... | ... | @@ -488,7 +554,7 @@ class NeuronavigationPanel(wx.Panel): |
| 488 | 554 | self.trk_init = dt.TrackerConnection(self.tracker_id, trck, 'disconnect') |
| 489 | 555 | if not self.trk_init[0]: |
| 490 | 556 | if evt is not False: |
| 491 | - dlg.NavigationTrackerWarning(self.tracker_id, 'disconnect') | |
| 557 | + dlg.ShowNavigationTrackerWarning(self.tracker_id, 'disconnect') | |
| 492 | 558 | self.tracker_id = 0 |
| 493 | 559 | ctrl.SetSelection(self.tracker_id) |
| 494 | 560 | Publisher.sendMessage('Update status text in GUI', |
| ... | ... | @@ -507,7 +573,7 @@ class NeuronavigationPanel(wx.Panel): |
| 507 | 573 | self.tracker_id = choice |
| 508 | 574 | self.trk_init = dt.TrackerConnection(self.tracker_id, None, 'connect') |
| 509 | 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 | 577 | self.tracker_id = 0 |
| 512 | 578 | ctrl.SetSelection(self.tracker_id) |
| 513 | 579 | |
| ... | ... | @@ -549,7 +615,18 @@ class NeuronavigationPanel(wx.Panel): |
| 549 | 615 | coord = None |
| 550 | 616 | |
| 551 | 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 | 628 | coord_raw = dco.GetCoordinates(self.trk_init, self.tracker_id, self.ref_mode_id) |
| 629 | + | |
| 553 | 630 | if self.ref_mode_id: |
| 554 | 631 | coord = dco.dynamic_reference_m(coord_raw[0, :], coord_raw[1, :]) |
| 555 | 632 | else: |
| ... | ... | @@ -557,7 +634,7 @@ class NeuronavigationPanel(wx.Panel): |
| 557 | 634 | coord[2] = -coord[2] |
| 558 | 635 | |
| 559 | 636 | else: |
| 560 | - dlg.NavigationTrackerWarning(0, 'choose') | |
| 637 | + dlg.ShowNavigationTrackerWarning(0, 'choose') | |
| 561 | 638 | |
| 562 | 639 | # Update number controls with tracker coordinates |
| 563 | 640 | if coord is not None: |
| ... | ... | @@ -569,112 +646,152 @@ class NeuronavigationPanel(wx.Panel): |
| 569 | 646 | btn_nav = btn[0] |
| 570 | 647 | choice_trck = btn[1] |
| 571 | 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 | 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 | 701 | if np.isnan(self.fiducials).any(): |
| 576 | - dlg.InvalidFiducials() | |
| 702 | + wx.MessageBox(_("Invalid fiducials, select all coordinates."), _("InVesalius 3")) | |
| 577 | 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 | 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 | 717 | tooltip = wx.ToolTip(_("Stop neuronavigation")) |
| 584 | 718 | btn_nav.SetToolTip(tooltip) |
| 585 | 719 | |
| 586 | - # Disable all navigation buttons | |
| 720 | + # disable all navigation buttons | |
| 587 | 721 | choice_ref.Enable(False) |
| 588 | 722 | choice_trck.Enable(False) |
| 589 | 723 | for btn_c in self.btns_coord: |
| 590 | 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 | 727 | m_change = tr.affine_matrix_from_points(self.fiducials[3:, :].T, self.fiducials[:3, :].T, |
| 604 | 728 | shear=False, scale=False) |
| 605 | - | |
| 606 | - # coreg_data = [m_change, m_head] | |
| 607 | - | |
| 729 | + # initialize spatial tracker parameters | |
| 608 | 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 | 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 | 746 | # obj_reg[0] is object 3x3 fiducial matrix and obj_reg[1] is 3x3 orientation matrix |
| 629 | 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 | 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 | 796 | def ResetImageFiducials(self): |
| 680 | 797 | for m in range(0, 3): |
| ... | ... | @@ -699,6 +816,8 @@ class NeuronavigationPanel(wx.Panel): |
| 699 | 816 | Publisher.sendMessage('Update object registration') |
| 700 | 817 | Publisher.sendMessage('Update track object state', flag=False, obj_name=False) |
| 701 | 818 | Publisher.sendMessage('Delete all markers') |
| 819 | + Publisher.sendMessage("Update marker offset state", create=False) | |
| 820 | + Publisher.sendMessage("Remove tracts") | |
| 702 | 821 | # TODO: Reset camera initial focus |
| 703 | 822 | Publisher.sendMessage('Reset cam clipping range') |
| 704 | 823 | |
| ... | ... | @@ -830,8 +949,7 @@ class ObjectRegistrationPanel(wx.Panel): |
| 830 | 949 | def UpdateTrackerInit(self, nav_prop): |
| 831 | 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 | 953 | if nav_status: |
| 836 | 954 | self.checkrecordcoords.Enable(1) |
| 837 | 955 | self.checktrack.Enable(0) |
| ... | ... | @@ -898,37 +1016,50 @@ class ObjectRegistrationPanel(wx.Panel): |
| 898 | 1016 | pass |
| 899 | 1017 | |
| 900 | 1018 | else: |
| 901 | - dlg.NavigationTrackerWarning(0, 'choose') | |
| 1019 | + dlg.ShowNavigationTrackerWarning(0, 'choose') | |
| 902 | 1020 | |
| 903 | 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 | 1055 | def ShowSaveObjectDialog(self, evt): |
| 928 | 1056 | if np.isnan(self.obj_fiducials).any() or np.isnan(self.obj_orients).any(): |
| 929 | 1057 | wx.MessageBox(_("Digitize all object fiducials before saving"), _("Save error")) |
| 930 | 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 | 1063 | if filename: |
| 933 | 1064 | hdr = 'Object' + "\t" + utils.decode(self.obj_name, const.FS_ENCODE) + "\t" + 'Reference' + "\t" + str('%d' % self.obj_ref_mode) |
| 934 | 1065 | data = np.hstack([self.obj_fiducials, self.obj_orients]) |
| ... | ... | @@ -969,8 +1100,8 @@ class MarkersPanel(wx.Panel): |
| 969 | 1100 | self.tgt_flag = self.tgt_index = None |
| 970 | 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 | 1106 | # Change marker size |
| 976 | 1107 | spin_size = wx.SpinCtrl(self, -1, "", size=wx.Size(40, 23)) |
| ... | ... | @@ -1045,7 +1176,8 @@ class MarkersPanel(wx.Panel): |
| 1045 | 1176 | self.Update() |
| 1046 | 1177 | |
| 1047 | 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 | 1181 | Publisher.subscribe(self.OnDeleteSingleMarker, 'Delete fiducial marker') |
| 1050 | 1182 | Publisher.subscribe(self.OnDeleteAllMarkers, 'Delete all markers') |
| 1051 | 1183 | Publisher.subscribe(self.OnCreateMarker, 'Create marker') |
| ... | ... | @@ -1055,8 +1187,8 @@ class MarkersPanel(wx.Panel): |
| 1055 | 1187 | self.current_coord = position[:] |
| 1056 | 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 | 1192 | sleep(0.5) |
| 1061 | 1193 | #self.current_coord[3:] = 0, 0, 0 |
| 1062 | 1194 | self.nav_status = False |
| ... | ... | @@ -1073,6 +1205,9 @@ class MarkersPanel(wx.Panel): |
| 1073 | 1205 | menu_id.AppendSeparator() |
| 1074 | 1206 | target_menu = menu_id.Append(1, _('Set as target')) |
| 1075 | 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 | 1212 | target_menu.Enable(True) |
| 1078 | 1213 | self.PopupMenu(menu_id) |
| ... | ... | @@ -1089,10 +1224,10 @@ class MarkersPanel(wx.Panel): |
| 1089 | 1224 | if evt == 'TARGET': |
| 1090 | 1225 | id_label = evt |
| 1091 | 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 | 1228 | if id_label == 'TARGET': |
| 1094 | 1229 | id_label = '' |
| 1095 | - dlg.InvalidTargetID() | |
| 1230 | + wx.MessageBox(_("Invalid TARGET ID."), _("InVesalius 3")) | |
| 1096 | 1231 | self.lc.SetItem(list_index, 4, id_label) |
| 1097 | 1232 | # Add the new ID to exported list |
| 1098 | 1233 | if len(self.list_coord[list_index]) > 8: |
| ... | ... | @@ -1122,34 +1257,29 @@ class MarkersPanel(wx.Panel): |
| 1122 | 1257 | Publisher.sendMessage('Disable or enable coil tracker', status=True) |
| 1123 | 1258 | self.OnMenuEditMarkerId('TARGET') |
| 1124 | 1259 | self.tgt_flag = True |
| 1125 | - dlg.NewTarget() | |
| 1260 | + wx.MessageBox(_("New target selected."), _("InVesalius 3")) | |
| 1126 | 1261 | |
| 1127 | 1262 | def OnMenuSetColor(self, evt): |
| 1128 | 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 | 1276 | def OnDeleteAllMarkers(self, evt=None): |
| 1148 | 1277 | if self.list_coord: |
| 1149 | 1278 | if evt is None: |
| 1150 | 1279 | result = wx.ID_OK |
| 1151 | 1280 | else: |
| 1152 | - result = dlg.DeleteAllMarkers() | |
| 1281 | + # result = dlg.DeleteAllMarkers() | |
| 1282 | + result = dlg.ShowConfirmationDialog(msg=_("Remove all markers? Cannot be undone.")) | |
| 1153 | 1283 | |
| 1154 | 1284 | if result == wx.ID_OK: |
| 1155 | 1285 | self.list_coord = [] |
| ... | ... | @@ -1162,7 +1292,7 @@ class MarkersPanel(wx.Panel): |
| 1162 | 1292 | self.tgt_flag = self.tgt_index = None |
| 1163 | 1293 | Publisher.sendMessage('Disable or enable coil tracker', status=False) |
| 1164 | 1294 | if not hasattr(evt, 'data'): |
| 1165 | - dlg.DeleteTarget() | |
| 1295 | + wx.MessageBox(_("Target deleted."), _("InVesalius 3")) | |
| 1166 | 1296 | |
| 1167 | 1297 | def OnDeleteSingleMarker(self, evt=None, marker_id=None): |
| 1168 | 1298 | # OnDeleteSingleMarker is used for both pubsub and button click events |
| ... | ... | @@ -1183,14 +1313,16 @@ class MarkersPanel(wx.Panel): |
| 1183 | 1313 | else: |
| 1184 | 1314 | index = None |
| 1185 | 1315 | |
| 1316 | + #TODO: There are bugs when no marker is selected, test and improve | |
| 1186 | 1317 | if index: |
| 1187 | 1318 | if self.tgt_flag and self.tgt_index == index[0]: |
| 1188 | 1319 | self.tgt_flag = self.tgt_index = None |
| 1189 | 1320 | Publisher.sendMessage('Disable or enable coil tracker', status=False) |
| 1190 | - dlg.DeleteTarget() | |
| 1321 | + wx.MessageBox(_("No data selected."), _("InVesalius 3")) | |
| 1322 | + | |
| 1191 | 1323 | self.DeleteMarker(index) |
| 1192 | 1324 | else: |
| 1193 | - dlg.NoMarkerSelected() | |
| 1325 | + wx.MessageBox(_("Target deleted."), _("InVesalius 3")) | |
| 1194 | 1326 | |
| 1195 | 1327 | def DeleteMarker(self, index): |
| 1196 | 1328 | for i in reversed(index): |
| ... | ... | @@ -1213,12 +1345,16 @@ class MarkersPanel(wx.Panel): |
| 1213 | 1345 | self.CreateMarker(self.current_coord, self.marker_colour, self.marker_size) |
| 1214 | 1346 | |
| 1215 | 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 | 1355 | try: |
| 1220 | 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 | 1358 | for data in content: |
| 1223 | 1359 | target = None |
| 1224 | 1360 | line = [s for s in data.split()] |
| ... | ... | @@ -1254,7 +1390,7 @@ class MarkersPanel(wx.Panel): |
| 1254 | 1390 | self.CreateMarker(coord, colour, size, line[7]) |
| 1255 | 1391 | count_line += 1 |
| 1256 | 1392 | except: |
| 1257 | - dlg.InvalidMarkersFile() | |
| 1393 | + wx.MessageBox(_("Invalid markers file."), _("InVesalius 3")) | |
| 1258 | 1394 | |
| 1259 | 1395 | def OnMarkersVisibility(self, evt, ctrl): |
| 1260 | 1396 | |
| ... | ... | @@ -1266,7 +1402,11 @@ class MarkersPanel(wx.Panel): |
| 1266 | 1402 | ctrl.SetLabel('Hide') |
| 1267 | 1403 | |
| 1268 | 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 | 1410 | if filename: |
| 1271 | 1411 | if self.list_coord: |
| 1272 | 1412 | text_file = open(filename, "w") |
| ... | ... | @@ -1339,3 +1479,459 @@ class DbsPanel(wx.Panel): |
| 1339 | 1479 | except AttributeError: |
| 1340 | 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 | 313 | self.spacing = project["spacing"] |
| 314 | 314 | if project.get("affine", ""): |
| 315 | 315 | self.affine = project["affine"] |
| 316 | + # self.affine = project.get("affine") | |
| 316 | 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 | 320 | self.compress = project.get("compress", True) |
| 320 | 321 | ... | ... |