Commit 893d1a7018d91b534365487be6896e90b1a46a18
1 parent
fde33225
Exists in
master
MOD: Export world coordinates into marker file
* When unable to compute the world coordinates (the 'affine' matrix being missing from the project), output empty strings ("") as the coordinates and the angles.
Showing
2 changed files
with
83 additions
and
9 deletions
Show diff stats
invesalius/data/imagedata_utils.py
... | ... | @@ -34,6 +34,7 @@ from vtk.util import numpy_support |
34 | 34 | |
35 | 35 | import invesalius.constants as const |
36 | 36 | import invesalius.data.converters as converters |
37 | +import invesalius.data.coordinates as dco | |
37 | 38 | import invesalius.data.slice_ as sl |
38 | 39 | import invesalius.data.transformations as tr |
39 | 40 | import invesalius.reader.bitmap_reader as bitmap_reader |
... | ... | @@ -555,18 +556,23 @@ def image_normalize(image, min_=0.0, max_=1.0, output_dtype=np.int16): |
555 | 556 | return output |
556 | 557 | |
557 | 558 | |
559 | +# TODO: Add a description of different coordinate systems, namely: | |
560 | +# - the world coordinate system, | |
561 | +# - the voxel coordinate system. | |
562 | +# - InVesalius's internal coordinate system, | |
563 | +# | |
558 | 564 | def convert_world_to_voxel(xyz, affine): |
559 | 565 | """ |
560 | 566 | Convert a coordinate from the world space ((x, y, z); scanner space; millimeters) to the |
561 | 567 | voxel space ((i, j, k)). This is achieved by multiplying a coordinate by the inverse |
562 | 568 | of the affine transformation. |
569 | + | |
563 | 570 | More information: https://nipy.org/nibabel/coordinate_systems.html |
571 | + | |
564 | 572 | :param xyz: a list or array of 3 coordinates (x, y, z) in the world coordinates |
565 | 573 | :param affine: a 4x4 array containing the image affine transformation in homogeneous coordinates |
566 | 574 | :return: a 1x3 array with the point coordinates in image space (i, j, k) |
567 | 575 | """ |
568 | - | |
569 | - # print("xyz: ", xyz, "\naffine", affine) | |
570 | 576 | # convert xyz coordinate to 1x4 homogeneous coordinates array |
571 | 577 | xyz_homo = np.hstack((xyz, 1.0)).reshape([4, 1]) |
572 | 578 | ijk_homo = np.linalg.inv(affine) @ xyz_homo |
... | ... | @@ -575,6 +581,65 @@ def convert_world_to_voxel(xyz, affine): |
575 | 581 | return ijk |
576 | 582 | |
577 | 583 | |
584 | +def convert_invesalius_to_voxel(position): | |
585 | + """ | |
586 | + Convert position from InVesalius space to the voxel space. | |
587 | + | |
588 | + The two spaces are otherwise identical, but InVesalius space has a reverted y-axis | |
589 | + (increasing y-coordinate moves posterior in InVesalius space, but anterior in the voxel space). | |
590 | + | |
591 | + For instance, if the size of the voxel image is 256 x 256 x 160, the y-coordinate 0 in | |
592 | + InVesalius space corresponds to the y-coordinate 255 in the voxel space. | |
593 | + | |
594 | + :param position: a vector of 3 coordinates (x, y, z) in InVesalius space. | |
595 | + :return: a vector of 3 coordinates in the voxel space | |
596 | + """ | |
597 | + slice = sl.Slice() | |
598 | + return np.array((position[0], slice.matrix.shape[1] - position[1] - 1, position[2])) | |
599 | + | |
600 | + | |
601 | +def convert_invesalius_to_world(position, orientation): | |
602 | + """ | |
603 | + Convert position and orientation from InVesalius space to the world space. | |
604 | + | |
605 | + The axis definition for the Euler angles returned is 'sxyz', see transformations.py for more | |
606 | + information. | |
607 | + | |
608 | + Uses 'affine' matrix defined in the project created or opened by the user. If it is | |
609 | + undefined, return Nones as the coordinates for both position and orientation. | |
610 | + | |
611 | + More information: https://nipy.org/nibabel/coordinate_systems.html | |
612 | + | |
613 | + :param position: a vector of 3 coordinates in InVesalius space. | |
614 | + :param orientation: a vector of 3 Euler angles in InVesalius space. | |
615 | + :return: a pair consisting of 3 coordinates and 3 Euler angles in the world space, or Nones if | |
616 | + 'affine' matrix is not defined in the project. | |
617 | + """ | |
618 | + slice = sl.Slice() | |
619 | + | |
620 | + if slice.affine is None: | |
621 | + position_world = (None, None, None) | |
622 | + orientation_world = (None, None, None) | |
623 | + | |
624 | + return position_world, orientation_world | |
625 | + | |
626 | + position_voxel = convert_invesalius_to_voxel(position) | |
627 | + | |
628 | + M_invesalius = dco.coordinates_to_transformation_matrix( | |
629 | + position=position_voxel, | |
630 | + orientation=orientation, | |
631 | + axes='sxyz', | |
632 | + ) | |
633 | + M_world = slice.affine @ M_invesalius | |
634 | + | |
635 | + position_world, orientation_world = dco.transformation_matrix_to_coordinates( | |
636 | + M_world, | |
637 | + axes='sxyz', | |
638 | + ) | |
639 | + | |
640 | + return position_world, orientation_world | |
641 | + | |
642 | + | |
578 | 643 | def create_grid(xy_range, z_range, z_offset, spacing): |
579 | 644 | x = np.arange(xy_range[0], xy_range[1] + 1, spacing) |
580 | 645 | y = np.arange(xy_range[0], xy_range[1] + 1, spacing) | ... | ... |
invesalius/gui/task_navigator.py
... | ... | @@ -54,6 +54,7 @@ if has_trekker: |
54 | 54 | |
55 | 55 | import invesalius.data.coordinates as dco |
56 | 56 | import invesalius.data.coregistration as dcr |
57 | +import invesalius.data.imagedata_utils as imagedata_utils | |
57 | 58 | import invesalius.data.serial_port_connection as spc |
58 | 59 | import invesalius.data.slice_ as sl |
59 | 60 | import invesalius.data.trackers as dt |
... | ... | @@ -1831,11 +1832,11 @@ class MarkersPanel(wx.Panel): |
1831 | 1832 | target = None |
1832 | 1833 | |
1833 | 1834 | coord = [float(s) for s in line[:6]] |
1834 | - colour = [float(s) for s in line[6:9]] | |
1835 | - size = float(line[9]) | |
1836 | - marker_id = line[10] | |
1835 | + colour = [float(s) for s in line[12:15]] | |
1836 | + size = float(line[15]) | |
1837 | + marker_id = line[16] | |
1837 | 1838 | |
1838 | - seed = [float(s) for s in line[11:14]] | |
1839 | + seed = [float(s) for s in line[17:20]] | |
1839 | 1840 | |
1840 | 1841 | for i in const.BTNS_IMG_MARKERS: |
1841 | 1842 | if marker_id in list(const.BTNS_IMG_MARKERS[i].values())[0]: |
... | ... | @@ -1843,7 +1844,7 @@ class MarkersPanel(wx.Panel): |
1843 | 1844 | elif marker_id == 'TARGET': |
1844 | 1845 | target = count_line |
1845 | 1846 | |
1846 | - target_id = line[14] | |
1847 | + target_id = line[20] | |
1847 | 1848 | |
1848 | 1849 | self.CreateMarker(coord=coord, colour=colour, size=size, |
1849 | 1850 | marker_id=marker_id, target_id=target_id, seed=seed) |
... | ... | @@ -1880,8 +1881,9 @@ class MarkersPanel(wx.Panel): |
1880 | 1881 | style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, |
1881 | 1882 | default_filename=default_filename) |
1882 | 1883 | |
1883 | - header_titles = ['x', 'y', 'z', 'alpha', 'beta', 'gamma', 'r', 'g', 'b', | |
1884 | - 'size', 'marker_id', 'x_seed', 'y_seed', 'z_seed', 'target_id'] | |
1884 | + header_titles = ['x', 'y', 'z', 'alpha', 'beta', 'gamma', | |
1885 | + 'x_world', 'y_world', 'z_world', 'alpha_world', 'beta_world', 'gamma_world', | |
1886 | + 'r', 'g', 'b', 'size', 'marker_id', 'x_seed', 'y_seed', 'z_seed', 'target_id'] | |
1885 | 1887 | |
1886 | 1888 | if filename: |
1887 | 1889 | if self.list_coord: |
... | ... | @@ -1907,6 +1909,11 @@ class MarkersPanel(wx.Panel): |
1907 | 1909 | size = size or self.marker_size |
1908 | 1910 | seed = seed or self.current_seed |
1909 | 1911 | |
1912 | + position_world, orientation_world = imagedata_utils.convert_invesalius_to_world( | |
1913 | + position=coord[:3], | |
1914 | + orientation=coord[3:], | |
1915 | + ) | |
1916 | + | |
1910 | 1917 | # TODO: Use matrix coordinates and not world coordinates as current method. |
1911 | 1918 | # This makes easier for inter-software comprehension. |
1912 | 1919 | |
... | ... | @@ -1917,6 +1924,8 @@ class MarkersPanel(wx.Panel): |
1917 | 1924 | # List of lists with coordinates and properties of a marker |
1918 | 1925 | line = [] |
1919 | 1926 | line.extend(coord) |
1927 | + line.extend(position_world) | |
1928 | + line.extend(orientation_world) | |
1920 | 1929 | line.extend(colour) |
1921 | 1930 | line.append(size) |
1922 | 1931 | line.append(marker_id) | ... | ... |