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 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 = 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 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
... ... @@ -46,6 +46,7 @@ import invesalius.constants as const
46 46 if has_trekker:
47 47 import invesalius.data.brainmesh_handler as brain
48 48  
  49 +import invesalius.data.imagedata_utils as imagedata_utils
49 50 import invesalius.data.slice_ as sl
50 51 import invesalius.data.tractography as dti
51 52 import invesalius.data.record_coords as rec
... ... @@ -664,7 +665,7 @@ class NeuronavigationPanel(wx.Panel):
664 665 seed = 3 * [0.]
665 666  
666 667 Publisher.sendMessage('Create marker', coord=coord, colour=colour, size=size,
667   - marker_id=label, seed=seed)
  668 + label=label, seed=seed)
668 669 else:
669 670 for m in [0, 1, 2]:
670 671 self.numctrls_fiducial[n][m].SetValue(float(self.current_coord[m]))
... ... @@ -1445,11 +1446,11 @@ class MarkersPanel(wx.Panel):
1445 1446 target = None
1446 1447  
1447 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 1455 for i in const.BTNS_IMG_MARKERS:
1455 1456 if marker_id in list(const.BTNS_IMG_MARKERS[i].values())[0]:
... ... @@ -1457,7 +1458,7 @@ class MarkersPanel(wx.Panel):
1457 1458 elif marker_id == 'TARGET':
1458 1459 target = count_line
1459 1460  
1460   - target_id = line[14]
  1461 + target_id = line[20]
1461 1462  
1462 1463 self.CreateMarker(coord=coord, colour=colour, size=size,
1463 1464 label=marker_id, target_id=target_id, seed=seed)
... ... @@ -1494,8 +1495,9 @@ class MarkersPanel(wx.Panel):
1494 1495 style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT,
1495 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 1502 if filename:
1501 1503 if self.list_coord:
... ... @@ -1521,6 +1523,11 @@ class MarkersPanel(wx.Panel):
1521 1523 size = size or self.marker_size
1522 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 1531 # TODO: Use matrix coordinates and not world coordinates as current method.
1525 1532 # This makes easier for inter-software comprehension.
1526 1533  
... ... @@ -1529,6 +1536,8 @@ class MarkersPanel(wx.Panel):
1529 1536 # List of lists with coordinates and properties of a marker
1530 1537 line = []
1531 1538 line.extend(coord)
  1539 + line.extend(position_world)
  1540 + line.extend(orientation_world)
1532 1541 line.extend(colour)
1533 1542 line.append(size)
1534 1543 line.append(label)
... ...