Commit 9bbd209de12c2ccadfac82110eeba1fae4c7319d
Exists in
master
Merge remote-tracking branch 'upstream/master'
Showing
2 changed files
with
84 additions
and
10 deletions
Show diff stats
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) |