From 1d6eb596493b2fc7487bdfa6cb37c32380a46ee8 Mon Sep 17 00:00:00 2001 From: John Wilkie <124276291+JBWilkie@users.noreply.github.com> Date: Fri, 8 Nov 2024 15:32:37 +0000 Subject: [PATCH] [DAR-4140][External] Fixed NifTI import orientation (#953) * Fixed NifTI import orientation * Simpler implementation * Update tests/darwin/importer/formats/import_nifti_test.py Co-authored-by: dorfmanrobert <108150810+dorfmanrobert@users.noreply.github.com> --------- Co-authored-by: dorfmanrobert <108150810+dorfmanrobert@users.noreply.github.com> --- darwin/importer/formats/nifti.py | 10 ++- .../importer/formats/import_nifti_test.py | 67 +++++++++++++++++- tests/data.zip | Bin 300936 -> 303807 bytes 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/darwin/importer/formats/nifti.py b/darwin/importer/formats/nifti.py index a86a70d89..3d6a32d17 100644 --- a/darwin/importer/formats/nifti.py +++ b/darwin/importer/formats/nifti.py @@ -103,7 +103,7 @@ def _parse_nifti( is_mpr: bool, legacy: bool = False, ) -> dt.AnnotationFile: - img, pixdims = process_nifti(nib.load(nifti_path), legacy=legacy) + img, pixdims = process_nifti(nib.load(nifti_path)) processed_class_map = process_class_map(class_map) video_annotations = [] @@ -513,11 +513,10 @@ def correct_nifti_header_if_necessary(img_nii): def process_nifti( input_data: nib.nifti1.Nifti1Image, ornt: Optional[List[List[float]]] = [[0.0, -1.0], [1.0, -1.0], [2.0, -1.0]], - legacy: bool = False, ) -> Tuple[np.ndarray, Tuple[float]]: """ - Function that converts a nifti object to RAS orientation (if legacy), then converts to the passed ornt orientation. - The default ornt is for LPI. + Function that converts a nifti object to the RAS orientation, then converts to the passed ornt orientation. + The default ornt is LPI. Args: input_data: nibabel nifti object. @@ -530,8 +529,7 @@ def process_nifti( pixdims: tuple of nifti header zoom values. """ img = correct_nifti_header_if_necessary(input_data) - if legacy: - img = nib.funcs.as_closest_canonical(img) + img = nib.funcs.as_closest_canonical(img) data_array = nib.orientations.apply_orientation(img.get_fdata(), ornt) pixdims = img.header.get_zooms() return data_array, pixdims diff --git a/tests/darwin/importer/formats/import_nifti_test.py b/tests/darwin/importer/formats/import_nifti_test.py index d59b2c9a9..f7045d55c 100644 --- a/tests/darwin/importer/formats/import_nifti_test.py +++ b/tests/darwin/importer/formats/import_nifti_test.py @@ -9,6 +9,7 @@ import numpy as np import pytest from scipy import ndimage +import nibabel as nib from darwin.datatypes import ( Annotation, @@ -17,7 +18,7 @@ SubAnnotation, VideoAnnotation, ) -from darwin.importer.formats.nifti import get_new_axial_size, parse_path +from darwin.importer.formats.nifti import get_new_axial_size, parse_path, process_nifti from tests.fixtures import * from darwin.utils.utils import parse_darwin_json @@ -239,6 +240,70 @@ def test_get_new_axial_size_with_isotropic(): assert new_size == (20, 10) +def test_process_nifti_orientation_ras_to_lpi(team_slug_darwin_json_v2): + """ + Test that an input NifTI annotation file in the RAS orientation is correctly + transformed to the LPI orientation. + + Do this by emulating the `process_nifti` function, which: + - 1: Transforms the input file into the RAS orientation + - 2: Transforms the transformed RAS file into the LPI orientation + """ + with tempfile.TemporaryDirectory() as tmpdir: + with ZipFile("tests/data.zip") as zfile: + zfile.extractall(tmpdir) + filepath = ( + Path(tmpdir) + / team_slug_darwin_json_v2 + / "nifti" + / "releases" + / "latest" + / "annotations" + / "vol0_brain.nii.gz" + ) + lpi_ornt = [[0.0, -1.0], [1.0, -1.0], [2.0, -1.0]] + ras_file = nib.load(filepath) + ras_transformed_file = nib.funcs.as_closest_canonical(ras_file) + lpi_transformed_file = nib.orientations.apply_orientation( + ras_transformed_file.get_fdata(), lpi_ornt + ) + processed_file, _ = process_nifti(input_data=ras_file) + assert not np.array_equal(processed_file, ras_file._dataobj) + assert np.array_equal(processed_file, lpi_transformed_file) + + +def test_process_nifti_orientation_las_to_lpi(team_slug_darwin_json_v2): + """ + Test that an input NifTI annotation file in the LAS orientation is correctly + transformed to the LPI orientation. + + Do this by emulating the `process_nifti` function, which: + - 1: Transforms the input file into the RAS orientation + - 2: Transforms the transformed RAS file into the LPI orientation + """ + with tempfile.TemporaryDirectory() as tmpdir: + with ZipFile("tests/data.zip") as zfile: + zfile.extractall(tmpdir) + filepath = ( + Path(tmpdir) + / team_slug_darwin_json_v2 + / "nifti" + / "releases" + / "latest" + / "annotations" + / "BRAINIX_NIFTI_ROI.nii.gz" + ) + lpi_ornt = [[0.0, -1.0], [1.0, -1.0], [2.0, -1.0]] + las_file = nib.load(filepath) + ras_transformed_file = nib.funcs.as_closest_canonical(las_file) + lpi_transformed_file = nib.orientations.apply_orientation( + ras_transformed_file.get_fdata(), lpi_ornt + ) + processed_file, _ = process_nifti(input_data=las_file) + assert not np.array_equal(processed_file, las_file._dataobj) + assert np.array_equal(processed_file, lpi_transformed_file) + + def serialise_annotation_file( annotation_file: AnnotationFile, as_dict ) -> Union[str, dict]: diff --git a/tests/data.zip b/tests/data.zip index fda8c0e0b496591404c7c8bfe3dc0afa266d8ad3..b38cedda19d9c005905bd897c52cb946925191d3 100644 GIT binary patch delta 20621 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

Tp5XX6SIQIe(N;+VSr{ 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 delta 17686 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