Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image space and World Space convert transforms #7942

Merged
merged 67 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
c4a67d8
add image and world space convert transform
KumoLiu Jul 23, 2024
558a1da
Merge remote-tracking branch 'origin/geometric' into world2image
KumoLiu Jul 24, 2024
87550de
combine to one transform
KumoLiu Jul 25, 2024
46ec145
add dictionary version
KumoLiu Jul 25, 2024
7930bed
add enum
KumoLiu Jul 25, 2024
52f4253
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 25, 2024
25c3686
support invert
KumoLiu Jul 25, 2024
7ed5f97
include demo notebook
KumoLiu Jul 25, 2024
3b29c2e
Merge branch 'world2image' of https://github.com/KumoLiu/MONAI into w…
KumoLiu Jul 25, 2024
6e06d65
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 25, 2024
a9f1704
fix format
KumoLiu Jul 25, 2024
5647a8d
upload link
KumoLiu Jul 26, 2024
3d3bacd
remove invert
KumoLiu Jul 26, 2024
4f4942c
ensure affine
KumoLiu Jul 30, 2024
0dfdf7e
update notebook
KumoLiu Jul 30, 2024
be7c897
add coronal and sagittal cases
KumoLiu Aug 1, 2024
e7b3e88
add `apply_affine_to_points`
KumoLiu Aug 1, 2024
9b6e01c
update test cases
KumoLiu Aug 2, 2024
2651de4
add luna pipeline
KumoLiu Aug 8, 2024
a819491
add `get_dtype_string`
KumoLiu Aug 15, 2024
9425248
add type hint
KumoLiu Aug 15, 2024
83de050
add type hint
KumoLiu Aug 15, 2024
c0b3bb9
add shape check
KumoLiu Aug 15, 2024
d196339
add warning
KumoLiu Aug 15, 2024
7d4cd68
Merge branch 'geometric' into world2image
KumoLiu Aug 19, 2024
83f59b4
support multi-channel
KumoLiu Aug 22, 2024
87b0c47
support 2d points
KumoLiu Aug 22, 2024
524d509
enhance coordinate trans
KumoLiu Aug 22, 2024
6421c0b
minor modify
KumoLiu Aug 22, 2024
a4a14c5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 22, 2024
4853f25
update
KumoLiu Aug 22, 2024
608816b
Merge branch 'world2image' of https://github.com/KumoLiu/MONAI into w…
KumoLiu Aug 22, 2024
5c4637a
minor fix
KumoLiu Aug 22, 2024
93afb03
add test
KumoLiu Aug 22, 2024
5e6fa65
minor modify
KumoLiu Aug 22, 2024
5ed2db6
minor fix
KumoLiu Aug 22, 2024
690a3c1
address comments
KumoLiu Aug 23, 2024
54422e5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 23, 2024
3a18f10
add unittests
KumoLiu Aug 23, 2024
6d676ca
Merge branch 'world2image' of https://github.com/KumoLiu/MONAI into w…
KumoLiu Aug 23, 2024
d2319ad
modify test cases
KumoLiu Aug 23, 2024
5761a7b
fix format
KumoLiu Aug 23, 2024
6eb23b9
address comments
KumoLiu Aug 26, 2024
34d64c1
address comments
KumoLiu Aug 26, 2024
d535b4a
format fix
KumoLiu Aug 26, 2024
16f14a1
Update monai/transforms/utility/array.py
KumoLiu Aug 27, 2024
b9e3984
address comments
KumoLiu Aug 27, 2024
d6cc8f0
Update monai/transforms/utility/array.py
KumoLiu Aug 27, 2024
48f97b5
enhance docstring
KumoLiu Aug 27, 2024
0b200bd
address comments
KumoLiu Aug 27, 2024
7a91b1e
fix format
KumoLiu Aug 27, 2024
1469af7
remove notebook
KumoLiu Aug 27, 2024
13441b0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 27, 2024
4d258d1
fix format
KumoLiu Aug 27, 2024
b1b5a87
enhance docstring
KumoLiu Aug 27, 2024
815b702
fix mypy
KumoLiu Aug 27, 2024
0724878
fix format
KumoLiu Aug 27, 2024
ffab390
address comments
KumoLiu Aug 28, 2024
5808439
Update monai/transforms/utility/array.py
KumoLiu Aug 28, 2024
729f11b
address comments
KumoLiu Aug 28, 2024
6f34194
Merge branch 'world2image' of https://github.com/KumoLiu/MONAI into w…
KumoLiu Aug 28, 2024
48707ed
fix format
KumoLiu Aug 28, 2024
a7d201b
add in init
KumoLiu Aug 28, 2024
f21f967
fix format
KumoLiu Aug 28, 2024
c078fe2
fix format
KumoLiu Aug 28, 2024
0c548c4
fix docstring format
KumoLiu Aug 28, 2024
ee2ac77
enhance docstring
KumoLiu Aug 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 85 additions & 4 deletions monai/transforms/utility/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from monai.config.type_definitions import NdarrayOrTensor
from monai.data.meta_obj import get_track_meta
from monai.data.meta_tensor import MetaTensor
from monai.data.utils import is_no_channel, no_collation
from monai.data.utils import is_no_channel, no_collation, orientation_ras_lps
from monai.networks.layers.simplelayers import (
ApplyFilter,
EllipticalFilter,
Expand All @@ -42,7 +42,7 @@
SharpenFilter,
median_filter,
)
from monai.transforms.inverse import InvertibleTransform
from monai.transforms.inverse import InvertibleTransform, TraceableTransform
from monai.transforms.traits import MultiSampleTrait
from monai.transforms.transform import Randomizable, RandomizableTrait, RandomizableTransform, Transform
from monai.transforms.utils import (
Expand All @@ -51,7 +51,7 @@
map_binary_to_indices,
map_classes_to_indices,
)
from monai.transforms.utils_pytorch_numpy_unification import concatenate, in1d, moveaxis, unravel_indices
from monai.transforms.utils_pytorch_numpy_unification import concatenate, in1d, moveaxis, unravel_indices, linalg_inv
from monai.utils import (
MetaKeys,
TraceKeys,
Expand All @@ -64,7 +64,7 @@
min_version,
optional_import,
)
from monai.utils.enums import TransformBackends
from monai.utils.enums import TransformBackends, CoordinateTransformMode
from monai.utils.misc import is_module_ver_at_least
from monai.utils.type_conversion import convert_to_dst_type, get_equivalent_dtype

Expand Down Expand Up @@ -1715,3 +1715,84 @@ def __call__(self, img: NdarrayOrTensor, meta_dict: Mapping | None = None) -> Nd
if self._do_transform:
img = self.filter(img)
return img


class CoordinateTransform(InvertibleTransform, Transform):
KumoLiu marked this conversation as resolved.
Show resolved Hide resolved
mingxin-zheng marked this conversation as resolved.
Show resolved Hide resolved
"""
Transform points between image coordinates and world coordinates.
ericspod marked this conversation as resolved.
Show resolved Hide resolved

Args:
dtype: The desired data type for the output.
mode: Specifies the direction of transformation. This should be an instance of
:py:class:`CoordinateTransformMode` enum, with either 'IMAGE_TO_WORLD' or 'WORLD_TO_IMAGE' as the value.
affine_lps_to_ras: Defaults to ``False``. Set this to ``True`` if: 1) The image is read by ITKReader,
and 2) The ITKReader has `affine_lps_to_ras=True`, and 3) The data is in world coordinates.
This ensures that the affine transformation between LPS (left-posterior-superior) and RAS
(right-anterior-superior) coordinate systems is correctly applied.
"""

def __init__(self, dtype: DtypeLike = torch.float64, mode=CoordinateTransformMode.IMAGE_TO_WORLD, affine_lps_to_ras: bool = False) -> None:
self.dtype = dtype
self.mode = mode
self.affine_lps_to_ras = affine_lps_to_ras

def transform_coordinates(self, data, affine, invert=False):
"""
Transform coordinates using an affine transformation matrix.

Args:
data: The input coordinates.
affine: A 3x3 or 4x4 affine transformation matrix.
invert: Whether to invert the affine matrix.

Returns:
Transformed coordinates.
"""
data = convert_to_tensor(data, track_meta=get_track_meta())
data_: torch.Tensor = convert_to_tensor(data, track_meta=False, dtype=self.dtype)
KumoLiu marked this conversation as resolved.
Show resolved Hide resolved

if self.affine_lps_to_ras: # RAS affine
affine = orientation_ras_lps(affine)

affine_t = linalg_inv(affine)
_affine = affine_t if invert else affine

homogeneous = concatenate((data_, torch.ones((data_.shape[0], 1))), axis=1)
transformed_homogeneous = torch.matmul(_affine, homogeneous.T)
transformed_coordinates = transformed_homogeneous[:-1].T
out, *_ = convert_to_dst_type(transformed_coordinates, data, dtype=self.dtype)

extra_info = {
"mode": self.mode,
"dtype": str(self.dtype)[6:], # dtype as string; remove "torch": torch.float32 -> float32
KumoLiu marked this conversation as resolved.
Show resolved Hide resolved
"affine": affine if invert else affine_t,
mingxin-zheng marked this conversation as resolved.
Show resolved Hide resolved
"affine_lps_to_ras": self.affine_lps_to_ras,
}
meta_info = TraceableTransform.track_transform_meta(
data,
affine=affine,
extra_info=extra_info,
transform_info=self.get_transform_info(),
)

return out, meta_info

def __call__(self, data: torch.Tensor, affine: torch.Tensor) -> torch.Tensor:
invert = (self.mode == CoordinateTransformMode.WORLD_TO_IMAGE)
out, meta_info = self.transform_coordinates(data, affine, invert=invert)
return out.copy_meta_from(meta_info) if isinstance(out, MetaTensor) else out

def inverse(self, data: torch.Tensor) -> torch.Tensor:
transform = self.pop_transform(data)
# Create inverse transform
affine = transform[TraceKeys.EXTRA_INFO]["affine"]
dtype = transform[TraceKeys.EXTRA_INFO]["dtype"]
mode_ = transform[TraceKeys.EXTRA_INFO]["mode"]
affine_lps_to_ras = not transform[TraceKeys.EXTRA_INFO]["affine_lps_to_ras"]
mode = CoordinateTransformMode.WORLD_TO_IMAGE if mode_ == CoordinateTransformMode.IMAGE_TO_WORLD else CoordinateTransformMode.IMAGE_TO_WORLD
inverse_transform = CoordinateTransform(dtype=dtype, mode=mode, affine_lps_to_ras=affine_lps_to_ras)
# Apply inverse
with inverse_transform.trace_transform(False):
data = inverse_transform(data, affine=affine)

return data
57 changes: 56 additions & 1 deletion monai/transforms/utility/dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from __future__ import annotations

import re
import warnings
from collections.abc import Callable, Hashable, Mapping
from copy import deepcopy
from typing import Any, Sequence, cast
Expand Down Expand Up @@ -62,11 +63,12 @@
TorchVision,
ToTensor,
Transpose,
CoordinateTransform,
)
from monai.transforms.utils import extreme_points_to_image, get_extreme_points
from monai.transforms.utils_pytorch_numpy_unification import concatenate
from monai.utils import ensure_tuple, ensure_tuple_rep
from monai.utils.enums import PostFix, TraceKeys, TransformBackends
from monai.utils.enums import PostFix, TraceKeys, TransformBackends, CoordinateTransformMode
from monai.utils.type_conversion import convert_to_dst_type

__all__ = [
Expand Down Expand Up @@ -1740,6 +1742,59 @@ def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> dict[Hashable, N
return d


class CoordinateTransformd(MapTransform, InvertibleTransform):
ericspod marked this conversation as resolved.
Show resolved Hide resolved
"""
Dictionary-based wrapper of :py:class:`monai.transforms.CoordinateTransform`.

Args:
keys: keys of the corresponding items to be transformed.
See also: monai.transforms.MapTransform
refer_key: the key of the reference item used for transformation.
dtype: The desired data type for the output.
mode: Specifies the direction of transformation. This should be an instance of
:py:class:`CoordinateTransformMode` enum, with either 'IMAGE_TO_WORLD' or 'WORLD_TO_IMAGE' as the value.
affine_lps_to_ras: Defaults to ``False``. Set this to ``True`` if: 1) The image is read by ITKReader,
and 2) The ITKReader has `affine_lps_to_ras=True`, and 3) The data is in world coordinates.
This ensures that the affine transformation between LPS (left-posterior-superior) and RAS
(right-anterior-superior) coordinate systems is correctly applied.
allow_missing_keys: Don't raise exception if key is missing.
"""

def __init__(
self,
keys: KeysCollection,
refer_key: str,
dtype: DtypeLike = torch.float64,
mode: str = CoordinateTransformMode.IMAGE_TO_WORLD,
affine_lps_to_ras: bool = False,
allow_missing_keys: bool = False
):
MapTransform.__init__(self, keys, allow_missing_keys)
self.refer_key = refer_key
self.converter = CoordinateTransform(dtype=dtype, mode=mode, affine_lps_to_ras=affine_lps_to_ras)

def __call__(self, data: Mapping[Hashable, torch.Tensor]):
d = dict(data)
refer_data = d[self.refer_key]
if isinstance(refer_data, MetaTensor):
affine = refer_data.affine
else:
affine = MetaTensor.get_default_affine()
warnings.warn("No affine found in the reference data, use default affine.")
for key in self.key_iterator(d):
coords = d[key]
d[key] = self.converter(coords, affine)
return d

def inverse(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torch.Tensor]:
d = dict(data)
for key in self.key_iterator(d):
d[key] = self.converter.inverse(d[key])
return d




RandImageFilterD = RandImageFilterDict = RandImageFilterd
ImageFilterD = ImageFilterDict = ImageFilterd
IdentityD = IdentityDict = Identityd
Expand Down
160 changes: 160 additions & 0 deletions monai/transforms/utility/temp_test.ipynb

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions monai/utils/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -771,3 +771,8 @@ class OrderingTransformations(StrEnum):
ROTATE_90 = "rotate_90"
TRANSPOSE = "transpose"
REFLECT = "reflect"


class CoordinateTransformMode(StrEnum):
IMAGE_TO_WORLD = "image_to_world"
WORLD_TO_IMAGE = "world_to_image"
Loading