Skip to content

Orientation transform does not work correctly for tensors in LPS space #8467

Open
@CPBridge

Description

@CPBridge

Describe the bug

The Orientation transform (and variants thereof) does not respect the "SPACE" in which the tensor's affine matrix is defined. When deciding how to flip/permute the affine matrix, it always assumes "RAS" space, even if the tensor's reference coordinate system is in LPS space. The result essentially is that it doesn't work correctly for tensors in LPS space, and outputs tensors that are not aligned with the requested orientation.

To Reproduce

import pydicom
import monai
import matplotlib.pyplot as plt

# Use a multiframe DICOM file from the pydicom test data (I do not believe that
# the image is important)
testfile = pydicom.data.get_testdata_file('eCT_Supplemental.dcm')

# Create two loaders, one which keeps DICOM's LPS definition of the reference
# coordinate system, and the other that switches to RAS definition
load_ras = monai.transforms.LoadImage(
    reader=monai.data.ITKReader(
        affine_lps_to_ras=True,
    ),
    ensure_channel_first=True,
)
load_lps = monai.transforms.LoadImage(
    reader=monai.data.ITKReader(
        affine_lps_to_ras=False,
    ),
    ensure_channel_first=True,
)

# A transforms to standardize the orientation
orient = monai.transforms.Orientation(axcodes="PLI")

metatensor_ras = load_ras(testfile)
metatensor_lps = load_lps(testfile)


metatensor_ras = orient(metatensor_ras)
print("RAS Tensor:")
print(metatensor_ras.meta["space"])
print(metatensor_ras.affine)
print()
plt.imshow(metatensor_ras[0, :, :, 0].numpy(), cmap="gray")
plt.show()


metatensor_lps = orient(metatensor_lps)
print("LPS Tensor:")
print(metatensor_lps.meta["space"])
print(metatensor_lps.affine)
plt.imshow(metatensor_lps[0, :, :, 0].numpy(), cmap="gray")
plt.show()

The first image displayed shows a brain consistent with the requested "PLI" orientation (with posterior at the bottom of the image and anatomical left on the right of the image). The second image displayed (for the tensor in LPS space) is clearly not consistent with the requested orientation and appears with the anterior at the bottom of the image, and the anatomical right on the right.

Expected behavior

The orientation of the image after the Orientation transform should be "PLI" regardless of the space used to define the reference coordinates.

Screenshots

The above code produces these screenshots:

RAS tensor behaves as expected:

Image

LPS tensor is not in the correct orientation:

Image

Environment

I'm fairly sure this isn't relevant, but

================================
Printing MONAI config...
================================
MONAI version: 1.4.0
Numpy version: 1.26.4
Pytorch version: 2.7.0
MONAI flags: HAS_EXT = False, USE_COMPILED = False, USE_META_DICT = False
MONAI rev id: 46a5272196a6c2590ca2589029eed8e4d56ff008
MONAI __file__: /Users/<username>/.pyenv/versions/dev/lib/python3.13/site-packages/monai/__init__.py

Optional dependencies:
Pytorch Ignite version: NOT INSTALLED or UNKNOWN VERSION.
ITK version: 5.4.3
Nibabel version: 5.3.2
scikit-image version: NOT INSTALLED or UNKNOWN VERSION.
scipy version: 1.15.2
Pillow version: 11.1.0
Tensorboard version: NOT INSTALLED or UNKNOWN VERSION.
gdown version: NOT INSTALLED or UNKNOWN VERSION.
TorchVision version: NOT INSTALLED or UNKNOWN VERSION.
tqdm version: 4.67.1
lmdb version: NOT INSTALLED or UNKNOWN VERSION.
psutil version: 7.0.0
pandas version: 2.2.3
einops version: NOT INSTALLED or UNKNOWN VERSION.
transformers version: NOT INSTALLED or UNKNOWN VERSION.
mlflow version: NOT INSTALLED or UNKNOWN VERSION.
pynrrd version: 1.1.3
clearml version: NOT INSTALLED or UNKNOWN VERSION.

For details about installing the optional dependencies, please visit:
    https://docs.monai.io/en/latest/installation.html#installing-the-recommended-dependencies


================================
Printing system config...
================================
System: Darwin
Mac version: 15.5
Platform: macOS-15.5-arm64-arm-64bit-Mach-O
Processor: arm
Machine: arm64
Python version: 3.13.2
Process name: python3.13
Command: ['/Users/cpb28/.pyenv/versions/dev/bin/python', '-c', 'import monai; monai.config.print_debug_info()']
Open files: []
Num physical CPUs: 10
Num logical CPUs: 10
Num usable CPUs: UNKNOWN for given OS
CPU usage (%): [18.8, 19.5, 66.5, 37.1, 56.8, 32.6, 2.3, 1.2, 0.6, 1.2]
CPU freq. (MHz): 3228
Load avg. in last 1, 5, 15 mins (%): [22.2, 25.4, 35.1]
Disk usage (%): 35.9
Avg. sensor temp. (Celsius): UNKNOWN for given OS
Total physical memory (GB): 64.0
Available memory (GB): 25.9
Used memory (GB): 28.4

================================
Printing GPU config...
================================
Num GPUs: 0
Has CUDA: False
cuDNN enabled: False
NVIDIA_TF32_OVERRIDE: None
TORCH_ALLOW_TF32_CUBLAS_OVERRIDE: None

Additional context

Tagging @sudomakeinstall, who helped figure this out.

The offending line of code appears to be this, which uses nibabel.orientations.axcodes2ornt to calculate the transform needed. I think the easiest way to hack around this would be to check for LPS space, and in that case pass an edited version of axcodes to that method, with R and L swapped, and P and A swapped. A little ugly but simple.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions