Given input and output affine, compute appropriate shapes in the output space based on the input array's shape. This function also returns the offset to put the shape in a good position with respect to the world coordinate system. Args: spatial_shape: input array's shap
(
spatial_shape: np.ndarray | Sequence[int],
in_affine: NdarrayOrTensor,
out_affine: NdarrayOrTensor,
scale_extent: bool = False,
)
| 848 | |
| 849 | |
| 850 | def compute_shape_offset( |
| 851 | spatial_shape: np.ndarray | Sequence[int], |
| 852 | in_affine: NdarrayOrTensor, |
| 853 | out_affine: NdarrayOrTensor, |
| 854 | scale_extent: bool = False, |
| 855 | ) -> tuple[np.ndarray, np.ndarray]: |
| 856 | """ |
| 857 | Given input and output affine, compute appropriate shapes |
| 858 | in the output space based on the input array's shape. |
| 859 | This function also returns the offset to put the shape |
| 860 | in a good position with respect to the world coordinate system. |
| 861 | |
| 862 | Args: |
| 863 | spatial_shape: input array's shape |
| 864 | in_affine (matrix): 2D affine matrix |
| 865 | out_affine (matrix): 2D affine matrix |
| 866 | scale_extent: whether the scale is computed based on the spacing or the full extent of voxels, for example, for |
| 867 | a factor of 0.5 scaling: |
| 868 | |
| 869 | option 1, "o" represents a voxel, scaling the distance between voxels:: |
| 870 | |
| 871 | o--o--o |
| 872 | o-----o |
| 873 | |
| 874 | option 2, each voxel has a physical extent, scaling the full voxel extent:: |
| 875 | |
| 876 | | voxel 1 | voxel 2 | voxel 3 | voxel 4 | |
| 877 | | voxel 1 | voxel 2 | |
| 878 | |
| 879 | Option 1 may reduce the number of locations that requiring interpolation. Option 2 is more resolution |
| 880 | agnostic, that is, resampling coordinates depend on the scaling factor, not on the number of voxels. |
| 881 | Default is False, using option 1 to compute the shape and offset. |
| 882 | |
| 883 | """ |
| 884 | shape = np.array(tuple(spatial_shape), copy=True, dtype=float) |
| 885 | sr = len(shape) |
| 886 | in_affine_ = convert_data_type(to_affine_nd(sr, in_affine), np.ndarray)[0] |
| 887 | out_affine_ = convert_data_type(to_affine_nd(sr, out_affine), np.ndarray)[0] |
| 888 | in_coords = [(-0.5, dim - 0.5) if scale_extent else (0.0, dim - 1.0) for dim in shape] |
| 889 | corners: np.ndarray = np.asarray(np.meshgrid(*in_coords, indexing="ij")).reshape((len(shape), -1)) |
| 890 | corners = np.concatenate((corners, np.ones_like(corners[:1]))) |
| 891 | try: |
| 892 | corners_out = np.linalg.solve(out_affine_, in_affine_) @ corners |
| 893 | except np.linalg.LinAlgError as e: |
| 894 | raise ValueError(f"Affine {out_affine_} is not invertible") from e |
| 895 | corners = in_affine_ @ corners |
| 896 | all_dist = corners_out[:-1].copy() |
| 897 | corners_out = corners_out[:-1] / corners_out[-1] |
| 898 | out_shape = np.round(np.ptp(corners_out, axis=1)) if scale_extent else np.round(np.ptp(corners_out, axis=1) + 1.0) |
| 899 | offset = None |
| 900 | for i in range(corners.shape[1]): |
| 901 | min_corner = np.min(all_dist - all_dist[:, i : i + 1], 1) |
| 902 | if np.allclose(min_corner, 0.0, rtol=AFFINE_TOL): |
| 903 | offset = corners[:-1, i] # corner is the smallest, shift the corner to origin |
| 904 | break |
| 905 | if offset is None: # otherwise make output image center aligned with the input image center |
| 906 | offset = in_affine_[:-1, :-1] @ (shape / 2.0) + in_affine_[:-1, -1] - out_affine_[:-1, :-1] @ (out_shape / 2.0) |
| 907 | if scale_extent: |
searching dependent graphs…