Commit 893d1a7018d91b534365487be6896e90b1a46a18

Authored by Olli-Pekka Kahilakoski
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.
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)
... ...