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

6985 metatensor_to_itk_image compatible space #7000

Merged
merged 4 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions monai/data/image_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -1316,6 +1316,8 @@ def get_data(self, img: NrrdImage | list[NrrdImage]) -> tuple[np.ndarray, dict]:

if self.affine_lps_to_ras:
header = self._switch_lps_ras(header)
if header.get(MetaKeys.SPACE, "left-posterior-superior") == "left-posterior-superior":
header[MetaKeys.SPACE] = SpaceKeys.LPS # assuming LPS if not specified

header[MetaKeys.AFFINE] = header[MetaKeys.ORIGINAL_AFFINE].copy()
header[MetaKeys.SPATIAL_SHAPE] = header["sizes"]
Expand Down
11 changes: 9 additions & 2 deletions monai/data/itk_torch_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
from monai.config.type_definitions import DtypeLike
from monai.data import ITKReader, ITKWriter
from monai.data.meta_tensor import MetaTensor
from monai.data.utils import orientation_ras_lps
from monai.transforms import EnsureChannelFirst
from monai.utils import convert_to_dst_type, optional_import
from monai.utils import MetaKeys, SpaceKeys, convert_to_dst_type, optional_import

if TYPE_CHECKING:
import itk
Expand Down Expand Up @@ -83,12 +84,18 @@ def metatensor_to_itk_image(

See also: :py:func:`ITKWriter.create_backend_obj`
"""
if meta_tensor.meta.get(MetaKeys.SPACE, SpaceKeys.LPS) == SpaceKeys.RAS:
_meta_tensor = meta_tensor.clone()
_meta_tensor.affine = orientation_ras_lps(meta_tensor.affine)
_meta_tensor.meta[MetaKeys.SPACE] = SpaceKeys.LPS
else:
_meta_tensor = meta_tensor
writer = ITKWriter(output_dtype=dtype, affine_lps_to_ras=False)
writer.set_data_array(data_array=meta_tensor.data, channel_dim=channel_dim, squeeze_end_dims=True)
return writer.create_backend_obj(
writer.data_obj,
channel_dim=writer.channel_dim,
affine=meta_tensor.affine,
affine=_meta_tensor.affine,
affine_lps_to_ras=False, # False if the affine is in itk convention
dtype=writer.output_dtype,
kwargs=kwargs,
Expand Down
40 changes: 38 additions & 2 deletions tests/test_itk_torch_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@

from __future__ import annotations

import itertools
import os
import tempfile
import unittest

import numpy as np
import torch
from parameterized import parameterized

import monai
import monai.transforms as mt
from monai.apps import download_url
from monai.data import ITKReader
from monai.data.itk_torch_bridge import (
Expand All @@ -31,14 +35,17 @@
from monai.networks.blocks import Warp
from monai.transforms import Affine
from monai.utils import optional_import, set_determinism
from tests.utils import skip_if_downloading_fails, skip_if_quick, test_is_quick, testing_data_config
from tests.utils import assert_allclose, skip_if_downloading_fails, skip_if_quick, test_is_quick, testing_data_config

itk, has_itk = optional_import("itk")
_, has_nib = optional_import("nibabel")

TESTS = ["CT_2D_head_fixed.mha", "CT_2D_head_moving.mha"]
if not test_is_quick():
TESTS += ["copd1_highres_INSP_STD_COPD_img.nii.gz", "copd1_highres_EXP_STD_COPD_img.nii.gz"]

RW_TESTS = TESTS + ["nrrd_example.nrrd"]


@unittest.skipUnless(has_itk, "Requires `itk` package.")
class TestITKTorchAffineMatrixBridge(unittest.TestCase):
Expand All @@ -47,7 +54,7 @@ def setUp(self):
self.data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "testing_data")
self.reader = ITKReader(pixel_type=itk.F)

for file_name in TESTS:
for file_name in RW_TESTS:
path = os.path.join(self.data_dir, file_name)
if not os.path.exists(path):
with skip_if_downloading_fails():
Expand Down Expand Up @@ -482,5 +489,34 @@ def test_use_reference_space(self, ref_filepath, filepath):
np.testing.assert_allclose(output_array_monai, output_array_itk, rtol=1e-3, atol=1e-3)


@unittest.skipUnless(has_itk, "Requires `itk` package.")
@unittest.skipUnless(has_nib, "Requires `nibabel` package.")
@skip_if_quick
class TestITKTorchRW(unittest.TestCase):
def setUp(self):
TestITKTorchAffineMatrixBridge.setUp(self)

def tearDown(self):
TestITKTorchAffineMatrixBridge.setUp(self)

@parameterized.expand(list(itertools.product(RW_TESTS, ["ITKReader", "NrrdReader"], [True, False])))
def test_rw_itk(self, filepath, reader, flip):
"""reading and convert: filepath, reader, flip"""
print(filepath, reader, flip)
fname = os.path.join(self.data_dir, filepath)
xform = mt.LoadImageD("img", image_only=True, ensure_channel_first=True, affine_lps_to_ras=flip, reader=reader)
out = xform({"img": fname})["img"]
itk_image = metatensor_to_itk_image(out, channel_dim=0, dtype=float)
with tempfile.TemporaryDirectory() as tempdir:
tname = os.path.join(tempdir, filepath) + (".nii.gz" if not filepath.endswith(".nii.gz") else "")
itk.imwrite(itk_image, tname, True)
ref = mt.LoadImage(image_only=True, ensure_channel_first=True, reader="NibabelReader")(tname)
if out.meta["space"] != ref.meta["space"]:
ref.affine = monai.data.utils.orientation_ras_lps(ref.affine)
assert_allclose(
out.affine, monai.data.utils.to_affine_nd(len(out.affine) - 1, ref.affine), rtol=1e-3, atol=1e-3
)


if __name__ == "__main__":
unittest.main()
5 changes: 5 additions & 0 deletions tests/testing_data/data_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@
"url": "https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/CT_DICOM_SINGLE.zip",
"hash_type": "sha256",
"hash_val": "a41f6e93d2e3d68956144f9a847273041d36441da12377d6a1d5ae610e0a7023"
},
"nrrd_example": {
"url": "https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/CT_IMAGE_cropped.nrrd",
"hash_type": "sha256",
"hash_val": "66971ad17f0bac50e6082ed6a4dc1ae7093c30517137e53327b15a752327a1c0"
}
},
"videos": {
Expand Down