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 | 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) | ... | ... |