Commit 9bbd209de12c2ccadfac82110eeba1fae4c7319d

Authored by Thiago Franco de Moraes
2 parents 621c80bc 265cab23
Exists in master

Merge remote-tracking branch 'upstream/master'

invesalius/data/imagedata_utils.py
@@ -34,6 +34,7 @@ from vtk.util import numpy_support @@ -34,6 +34,7 @@ from vtk.util import numpy_support
34 34
35 import invesalius.constants as const 35 import invesalius.constants as const
36 import invesalius.data.converters as converters 36 import invesalius.data.converters as converters
  37 +import invesalius.data.coordinates as dco
37 import invesalius.data.slice_ as sl 38 import invesalius.data.slice_ as sl
38 import invesalius.data.transformations as tr 39 import invesalius.data.transformations as tr
39 import invesalius.reader.bitmap_reader as bitmap_reader 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,18 +556,23 @@ def image_normalize(image, min_=0.0, max_=1.0, output_dtype=np.int16):
555 return output 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 def convert_world_to_voxel(xyz, affine): 564 def convert_world_to_voxel(xyz, affine):
559 """ 565 """
560 Convert a coordinate from the world space ((x, y, z); scanner space; millimeters) to the 566 Convert a coordinate from the world space ((x, y, z); scanner space; millimeters) to the
561 voxel space ((i, j, k)). This is achieved by multiplying a coordinate by the inverse 567 voxel space ((i, j, k)). This is achieved by multiplying a coordinate by the inverse
562 of the affine transformation. 568 of the affine transformation.
  569 +
563 More information: https://nipy.org/nibabel/coordinate_systems.html 570 More information: https://nipy.org/nibabel/coordinate_systems.html
  571 +
564 :param xyz: a list or array of 3 coordinates (x, y, z) in the world coordinates 572 :param xyz: a list or array of 3 coordinates (x, y, z) in the world coordinates
565 :param affine: a 4x4 array containing the image affine transformation in homogeneous coordinates 573 :param affine: a 4x4 array containing the image affine transformation in homogeneous coordinates
566 :return: a 1x3 array with the point coordinates in image space (i, j, k) 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 # convert xyz coordinate to 1x4 homogeneous coordinates array 576 # convert xyz coordinate to 1x4 homogeneous coordinates array
571 xyz_homo = np.hstack((xyz, 1.0)).reshape([4, 1]) 577 xyz_homo = np.hstack((xyz, 1.0)).reshape([4, 1])
572 ijk_homo = np.linalg.inv(affine) @ xyz_homo 578 ijk_homo = np.linalg.inv(affine) @ xyz_homo
@@ -575,6 +581,65 @@ def convert_world_to_voxel(xyz, affine): @@ -575,6 +581,65 @@ def convert_world_to_voxel(xyz, affine):
575 return ijk 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 = np.linalg.inv(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 def create_grid(xy_range, z_range, z_offset, spacing): 643 def create_grid(xy_range, z_range, z_offset, spacing):
579 x = np.arange(xy_range[0], xy_range[1] + 1, spacing) 644 x = np.arange(xy_range[0], xy_range[1] + 1, spacing)
580 y = np.arange(xy_range[0], xy_range[1] + 1, spacing) 645 y = np.arange(xy_range[0], xy_range[1] + 1, spacing)
invesalius/gui/task_navigator.py
@@ -46,6 +46,7 @@ import invesalius.constants as const @@ -46,6 +46,7 @@ import invesalius.constants as const
46 if has_trekker: 46 if has_trekker:
47 import invesalius.data.brainmesh_handler as brain 47 import invesalius.data.brainmesh_handler as brain
48 48
  49 +import invesalius.data.imagedata_utils as imagedata_utils
49 import invesalius.data.slice_ as sl 50 import invesalius.data.slice_ as sl
50 import invesalius.data.tractography as dti 51 import invesalius.data.tractography as dti
51 import invesalius.data.record_coords as rec 52 import invesalius.data.record_coords as rec
@@ -664,7 +665,7 @@ class NeuronavigationPanel(wx.Panel): @@ -664,7 +665,7 @@ class NeuronavigationPanel(wx.Panel):
664 seed = 3 * [0.] 665 seed = 3 * [0.]
665 666
666 Publisher.sendMessage('Create marker', coord=coord, colour=colour, size=size, 667 Publisher.sendMessage('Create marker', coord=coord, colour=colour, size=size,
667 - marker_id=label, seed=seed) 668 + label=label, seed=seed)
668 else: 669 else:
669 for m in [0, 1, 2]: 670 for m in [0, 1, 2]:
670 self.numctrls_fiducial[n][m].SetValue(float(self.current_coord[m])) 671 self.numctrls_fiducial[n][m].SetValue(float(self.current_coord[m]))
@@ -1445,11 +1446,11 @@ class MarkersPanel(wx.Panel): @@ -1445,11 +1446,11 @@ class MarkersPanel(wx.Panel):
1445 target = None 1446 target = None
1446 1447
1447 coord = [float(s) for s in line[:6]] 1448 coord = [float(s) for s in line[:6]]
1448 - colour = [float(s) for s in line[6:9]]  
1449 - size = float(line[9])  
1450 - marker_id = line[10] 1449 + colour = [float(s) for s in line[12:15]]
  1450 + size = float(line[15])
  1451 + marker_id = line[16]
1451 1452
1452 - seed = [float(s) for s in line[11:14]] 1453 + seed = [float(s) for s in line[17:20]]
1453 1454
1454 for i in const.BTNS_IMG_MARKERS: 1455 for i in const.BTNS_IMG_MARKERS:
1455 if marker_id in list(const.BTNS_IMG_MARKERS[i].values())[0]: 1456 if marker_id in list(const.BTNS_IMG_MARKERS[i].values())[0]:
@@ -1457,7 +1458,7 @@ class MarkersPanel(wx.Panel): @@ -1457,7 +1458,7 @@ class MarkersPanel(wx.Panel):
1457 elif marker_id == 'TARGET': 1458 elif marker_id == 'TARGET':
1458 target = count_line 1459 target = count_line
1459 1460
1460 - target_id = line[14] 1461 + target_id = line[20]
1461 1462
1462 self.CreateMarker(coord=coord, colour=colour, size=size, 1463 self.CreateMarker(coord=coord, colour=colour, size=size,
1463 label=marker_id, target_id=target_id, seed=seed) 1464 label=marker_id, target_id=target_id, seed=seed)
@@ -1494,8 +1495,9 @@ class MarkersPanel(wx.Panel): @@ -1494,8 +1495,9 @@ class MarkersPanel(wx.Panel):
1494 style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, 1495 style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT,
1495 default_filename=default_filename) 1496 default_filename=default_filename)
1496 1497
1497 - header_titles = ['x', 'y', 'z', 'alpha', 'beta', 'gamma', 'r', 'g', 'b',  
1498 - 'size', 'marker_id', 'x_seed', 'y_seed', 'z_seed', 'target_id'] 1498 + header_titles = ['x', 'y', 'z', 'alpha', 'beta', 'gamma',
  1499 + 'x_world', 'y_world', 'z_world', 'alpha_world', 'beta_world', 'gamma_world',
  1500 + 'r', 'g', 'b', 'size', 'marker_id', 'x_seed', 'y_seed', 'z_seed', 'target_id']
1499 1501
1500 if filename: 1502 if filename:
1501 if self.list_coord: 1503 if self.list_coord:
@@ -1521,6 +1523,11 @@ class MarkersPanel(wx.Panel): @@ -1521,6 +1523,11 @@ class MarkersPanel(wx.Panel):
1521 size = size or self.marker_size 1523 size = size or self.marker_size
1522 seed = seed or self.current_seed 1524 seed = seed or self.current_seed
1523 1525
  1526 + position_world, orientation_world = imagedata_utils.convert_invesalius_to_world(
  1527 + position=coord[:3],
  1528 + orientation=coord[3:],
  1529 + )
  1530 +
1524 # TODO: Use matrix coordinates and not world coordinates as current method. 1531 # TODO: Use matrix coordinates and not world coordinates as current method.
1525 # This makes easier for inter-software comprehension. 1532 # This makes easier for inter-software comprehension.
1526 1533
@@ -1529,6 +1536,8 @@ class MarkersPanel(wx.Panel): @@ -1529,6 +1536,8 @@ class MarkersPanel(wx.Panel):
1529 # List of lists with coordinates and properties of a marker 1536 # List of lists with coordinates and properties of a marker
1530 line = [] 1537 line = []
1531 line.extend(coord) 1538 line.extend(coord)
  1539 + line.extend(position_world)
  1540 + line.extend(orientation_world)
1532 line.extend(colour) 1541 line.extend(colour)
1533 line.append(size) 1542 line.append(size)
1534 line.append(label) 1543 line.append(label)