From 3b1f125538e4f49737b77b3a0b01196e0da76b84 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Mon, 28 Feb 2022 23:10:12 -0800 Subject: [PATCH 01/34] rel(22.0.0): Update CHANGES [skip ci] --- CHANGES.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b33046c7..6b5b65f2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,24 @@ +22.0.0 (February 28, 2022) +========================== +The first stable release of *NiTransforms* in 2022. +Contains all the new bug-fixes, features, and maintenance executed within the +context of the NiBabel EOSS4 grant from the CZI Foundation. + + * FIX: Implement AFNI's deoblique operations (#117) + * FIX: Ensure input dtype is kept after resampling (#153) + * FIX: Replace deprecated ``_read_mat`` with ``scipy.io.loadmat`` (#151) + * FIX: Add FSL-LTA-FSL regression tests (#146) + * FIX: Increase FSL serialization precision (#144) + * FIX: Refactor of LTA implementation (#145) + * FIX: Load arrays of linear transforms from AFNI files (#143) + * FIX: Load arrays of linear transforms from FSL files (#142) + * FIX: Double-check dtypes within tests and increase RMSE tolerance (#141) + * ENH: Base implementation of B-Spline transforms (#138) + * ENH: I/O of FSL displacements fields (#51) + * MAINT: Fix path to test summaries in CircleCI (#148) + * MAINT: Move testdata on to gin.g-node.org & datalad (#140) + * MAINT: scipy-1.8, numpy-1.22 require python 3.8 (#139) + 21.0.0 (September 10, 2021) =========================== A first release of *NiTransforms*. From 2f4c5bc2a5f99549850dee3a489e132fbcbaba54 Mon Sep 17 00:00:00 2001 From: Lea Waller Date: Tue, 1 Mar 2022 21:05:21 +0100 Subject: [PATCH 02/34] Add myself as a contributor to `.zenodo.json` --- .zenodo.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.zenodo.json b/.zenodo.json index 49e2eb56..b47d37d1 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -33,6 +33,13 @@ "orcid": "0000-0001-8435-6191" } ], + "contributors": [ + { + "affiliation": "Charite Universitatsmedizin Berlin, Germany", + "name": "Waller, Lea", + "orcid": "0000-0002-3239-6957" + } + ], "keywords": [ "neuroimaging", "spatial normalization" From b6923904325917aeb1e0935e779b575feef6ba2f Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 1 Mar 2022 23:57:16 +0100 Subject: [PATCH 03/34] enh: guess open linear transform formats EAFP implementation of loading linear transforms without specifying the format of the file. Resolves: #86. Resolves: #87. Resolves: #107. --- nitransforms/io/__init__.py | 24 ++++++- nitransforms/linear.py | 110 ++++++++++++------------------ nitransforms/tests/test_linear.py | 7 +- 3 files changed, 70 insertions(+), 71 deletions(-) diff --git a/nitransforms/io/__init__.py b/nitransforms/io/__init__.py index ab5afeac..26f59134 100644 --- a/nitransforms/io/__init__.py +++ b/nitransforms/io/__init__.py @@ -1,11 +1,33 @@ # emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: """Read and write transforms.""" -from . import afni, fsl, itk, lta +from nitransforms.io import afni, fsl, itk, lta +from nitransforms.io.base import TransformFileError __all__ = [ "afni", "fsl", "itk", "lta", + "get_linear_factory", + "TransformFileError", ] + +_IO_TYPES = { + "itk": (itk, "ITKLinearTransform"), + "ants": (itk, "ITKLinearTransform"), + "elastix": (itk, "ITKLinearTransform"), + "lta": (lta, "FSLinearTransform"), + "fs": (lta, "FSLinearTransform"), + "fsl": (fsl, "FSLLinearTransform"), + "afni": (afni, "AFNILinearTransform"), +} + + +def get_linear_factory(fmt, is_array=True): + """Return the type required by a given format.""" + if fmt.lower() not in _IO_TYPES: + raise TypeError(f"Unsupported transform format <{fmt}>.") + + module, classname = _IO_TYPES[fmt.lower()] + return getattr(module, f"{classname}{'Array' * is_array}") diff --git a/nitransforms/linear.py b/nitransforms/linear.py index e81ead61..5b427056 100644 --- a/nitransforms/linear.py +++ b/nitransforms/linear.py @@ -14,14 +14,14 @@ from nibabel.loadsave import load as _nbload -from .base import ( +from nitransforms.base import ( ImageGrid, TransformBase, SpatialReference, _as_homogeneous, EQUALITY_TOL, ) -from . import io +from nitransforms.io import get_linear_factory, TransformFileError class Affine(TransformBase): @@ -183,51 +183,40 @@ def _to_hdf5(self, x5_root): self.reference._to_hdf5(x5_root.create_group("Reference")) def to_filename(self, filename, fmt="X5", moving=None): - """Store the transform in BIDS-Transforms HDF5 file format (.x5).""" - if fmt.lower() in ["itk", "ants", "elastix"]: - itkobj = io.itk.ITKLinearTransform.from_ras(self.matrix) - itkobj.to_filename(filename) - return filename - - # Rest of the formats peek into moving and reference image grids - moving = ImageGrid(moving) if moving is not None else self.reference - - _factory = { - "afni": io.afni.AFNILinearTransform, - "fsl": io.fsl.FSLLinearTransform, - "lta": io.lta.FSLinearTransform, - "fs": io.lta.FSLinearTransform, - } - - if fmt not in _factory: - raise NotImplementedError(f"Unsupported format <{fmt}>") - - _factory[fmt].from_ras( - self.matrix, moving=moving, reference=self.reference - ).to_filename(filename) - return filename + """Store the transform in the requested output format.""" + writer = get_linear_factory(fmt, is_array=False) - @classmethod - def from_filename(cls, filename, fmt="X5", reference=None, moving=None): - """Create an affine from a transform file.""" if fmt.lower() in ("itk", "ants", "elastix"): - _factory = io.itk.ITKLinearTransformArray - elif fmt.lower() in ("lta", "fs"): - _factory = io.lta.FSLinearTransformArray - elif fmt.lower() == "fsl": - _factory = io.fsl.FSLLinearTransformArray - elif fmt.lower() == "afni": - _factory = io.afni.AFNILinearTransformArray + writer.from_ras(self.matrix).to_filename(filename) else: - raise NotImplementedError + # Rest of the formats peek into moving and reference image grids + writer.from_ras( + self.matrix, + reference=self.reference, + moving=ImageGrid(moving) if moving is not None else self.reference, + ).to_filename(filename) + return filename - struct = _factory.from_filename(filename) - matrix = struct.to_ras(reference=reference, moving=moving) - if cls == Affine: - if np.shape(matrix)[0] != 1: - raise TypeError("Cannot load transform array '%s'" % filename) - matrix = matrix[0] - return cls(matrix, reference=reference) + @classmethod + def from_filename(cls, filename, fmt=None, reference=None, moving=None): + """Create an affine from a transform file.""" + fmtlist = [fmt] if fmt is not None else ("itk", "lta", "afni", "fsl") + + for potential_fmt in fmtlist: + try: + struct = get_linear_factory(potential_fmt).from_filename(filename) + matrix = struct.to_ras(reference=reference, moving=moving) + if cls == Affine: + if np.shape(matrix)[0] != 1: + raise TypeError("Cannot load transform array '%s'" % filename) + matrix = matrix[0] + return cls(matrix, reference=reference) + except TransformFileError: + continue + + raise TransformFileError( + f"Could not open <{filename}> (formats tried: {', '.join(fmtlist)}." + ) def __repr__(self): """ @@ -353,31 +342,18 @@ def map(self, x, inverse=False): return np.swapaxes(affine.dot(coords), 1, 2) def to_filename(self, filename, fmt="X5", moving=None): - """Store the transform in BIDS-Transforms HDF5 file format (.x5).""" - if fmt.lower() in ("itk", "ants", "elastix"): - itkobj = io.itk.ITKLinearTransformArray.from_ras(self.matrix) - itkobj.to_filename(filename) - return filename + """Store the transform in the requested output format.""" + writer = get_linear_factory(fmt, is_array=True) - # Rest of the formats peek into moving and reference image grids - if moving is not None: - moving = ImageGrid(moving) + if fmt.lower() in ("itk", "ants", "elastix"): + writer.from_ras(self.matrix).to_filename(filename) else: - moving = self.reference - - _factory = { - "afni": io.afni.AFNILinearTransformArray, - "fsl": io.fsl.FSLLinearTransformArray, - "lta": io.lta.FSLinearTransformArray, - "fs": io.lta.FSLinearTransformArray, - } - - if fmt not in _factory: - raise NotImplementedError(f"Unsupported format <{fmt}>") - - _factory[fmt].from_ras( - self.matrix, moving=moving, reference=self.reference - ).to_filename(filename) + # Rest of the formats peek into moving and reference image grids + writer.from_ras( + self.matrix, + reference=self.reference, + moving=ImageGrid(moving) if moving is not None else self.reference, + ).to_filename(filename) return filename def apply( @@ -486,7 +462,7 @@ def apply( return resampled -def load(filename, fmt="X5", reference=None, moving=None): +def load(filename, fmt=None, reference=None, moving=None): """ Load a linear transform file. diff --git a/nitransforms/tests/test_linear.py b/nitransforms/tests/test_linear.py index 4462b212..0bd62d21 100644 --- a/nitransforms/tests/test_linear.py +++ b/nitransforms/tests/test_linear.py @@ -11,7 +11,8 @@ import nibabel as nb from nibabel.eulerangles import euler2mat from nibabel.affines import from_matvec -from .. import linear as nitl +from nitransforms import linear as nitl +from nitransforms import io from .utils import assert_affines_by_filename RMSE_TOL = 0.1 @@ -152,7 +153,7 @@ def test_linear_save(tmpdir, data_path, get_testdata, image_orientation, sw_tool xfm = ( nitl.Affine(T) if (sw_tool, image_orientation) != ("afni", "oblique") else # AFNI is special when moving or reference are oblique - let io do the magic - nitl.Affine(nitl.io.afni.AFNILinearTransform.from_ras(T).to_ras( + nitl.Affine(io.afni.AFNILinearTransform.from_ras(T).to_ras( reference=img, moving=img, )) @@ -199,7 +200,7 @@ def test_apply_linear_transform(tmpdir, get_testdata, get_testmask, image_orient xfm_fname = "M.%s%s" % (sw_tool, ext) # Change reference dataset for AFNI & oblique if (sw_tool, image_orientation) == ("afni", "oblique"): - nitl.io.afni.AFNILinearTransform.from_ras( + io.afni.AFNILinearTransform.from_ras( T, moving=img, reference=img, From 4b09a653f06c23c4e4a6d1dddad6bdf4dc787c11 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 1 Mar 2022 23:59:14 +0100 Subject: [PATCH 04/34] Update .zenodo.json --- .zenodo.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.zenodo.json b/.zenodo.json index b47d37d1..2cea96d1 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -37,7 +37,8 @@ { "affiliation": "Charite Universitatsmedizin Berlin, Germany", "name": "Waller, Lea", - "orcid": "0000-0002-3239-6957" + "orcid": "0000-0002-3239-6957", + "type": "Researcher" } ], "keywords": [ From 1fa7eebe1b750a68eb37511154fbed7aba83843d Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Wed, 2 Mar 2022 07:47:42 +0100 Subject: [PATCH 05/34] fix: extend tests and normalize error types --- nitransforms/io/__init__.py | 3 ++- nitransforms/io/afni.py | 14 +++++++---- nitransforms/io/base.py | 8 ++++-- nitransforms/io/fsl.py | 5 ++-- nitransforms/io/itk.py | 5 ++-- nitransforms/linear.py | 8 +++--- nitransforms/tests/test_io.py | 6 ++--- nitransforms/tests/test_linear.py | 42 +++++++++++++++++++++---------- 8 files changed, 59 insertions(+), 32 deletions(-) diff --git a/nitransforms/io/__init__.py b/nitransforms/io/__init__.py index 26f59134..c38d11c2 100644 --- a/nitransforms/io/__init__.py +++ b/nitransforms/io/__init__.py @@ -2,7 +2,7 @@ # vi: set ft=python sts=4 ts=4 sw=4 et: """Read and write transforms.""" from nitransforms.io import afni, fsl, itk, lta -from nitransforms.io.base import TransformFileError +from nitransforms.io.base import TransformIOError, TransformFileError __all__ = [ "afni", @@ -11,6 +11,7 @@ "lta", "get_linear_factory", "TransformFileError", + "TransformIOError", ] _IO_TYPES = { diff --git a/nitransforms/io/afni.py b/nitransforms/io/afni.py index 0b81e9b0..b7fc657b 100644 --- a/nitransforms/io/afni.py +++ b/nitransforms/io/afni.py @@ -95,12 +95,16 @@ def from_string(cls, string): if not lines: raise TransformFileError - parameters = np.vstack( - ( - np.genfromtxt([lines[0].encode()], dtype="f8").reshape((3, 4)), - (0.0, 0.0, 0.0, 1.0), + try: + parameters = np.vstack( + ( + np.genfromtxt([lines[0].encode()], dtype="f8").reshape((3, 4)), + (0.0, 0.0, 0.0, 1.0), + ) ) - ) + except ValueError as e: + raise TransformFileError from e + sa["parameters"] = parameters return tf diff --git a/nitransforms/io/base.py b/nitransforms/io/base.py index 284304b6..6d1a7c8e 100644 --- a/nitransforms/io/base.py +++ b/nitransforms/io/base.py @@ -6,8 +6,12 @@ from ..patched import LabeledWrapStruct -class TransformFileError(Exception): - """A custom exception for transform files.""" +class TransformIOError(IOError): + """General I/O exception while reading/writing transforms.""" + + +class TransformFileError(TransformIOError): + """Specific I/O exception when a file does not meet the expected format.""" class StringBasedStruct(LabeledWrapStruct): diff --git a/nitransforms/io/fsl.py b/nitransforms/io/fsl.py index 03252557..3bd4deb1 100644 --- a/nitransforms/io/fsl.py +++ b/nitransforms/io/fsl.py @@ -10,6 +10,7 @@ BaseLinearTransformList, LinearParameters, DisplacementsField, + TransformIOError, TransformFileError, _ensure_image, ) @@ -40,7 +41,7 @@ def from_ras(cls, ras, moving=None, reference=None): moving = reference if reference is None: - raise ValueError("Cannot build FSL linear transform without a reference") + raise TransformIOError("Cannot build FSL linear transform without a reference") reference = _ensure_image(reference) moving = _ensure_image(moving) @@ -77,7 +78,7 @@ def from_string(cls, string): def to_ras(self, moving=None, reference=None): """Return a nitransforms internal RAS+ matrix.""" if reference is None: - raise ValueError("Cannot build FSL linear transform without a reference") + raise TransformIOError("Cannot build FSL linear transform without a reference") if moving is None: warnings.warn( diff --git a/nitransforms/io/itk.py b/nitransforms/io/itk.py index b45a84de..4ab80f82 100644 --- a/nitransforms/io/itk.py +++ b/nitransforms/io/itk.py @@ -8,6 +8,7 @@ BaseLinearTransformList, DisplacementsField, LinearParameters, + TransformIOError, TransformFileError, ) @@ -306,7 +307,7 @@ def from_filename(cls, filename): from h5py import File as H5File if not str(filename).endswith(".h5"): - raise RuntimeError("Extension is not .h5") + raise TransformFileError("Extension is not .h5") with H5File(str(filename)) as f: return cls.from_h5obj(f) @@ -355,7 +356,7 @@ def from_h5obj(cls, fileobj, check=True): ) continue - raise NotImplementedError( + raise TransformIOError( f"Unsupported transform type {xfm['TransformType'][0]}" ) diff --git a/nitransforms/linear.py b/nitransforms/linear.py index 5b427056..00acfafb 100644 --- a/nitransforms/linear.py +++ b/nitransforms/linear.py @@ -211,11 +211,11 @@ def from_filename(cls, filename, fmt=None, reference=None, moving=None): raise TypeError("Cannot load transform array '%s'" % filename) matrix = matrix[0] return cls(matrix, reference=reference) - except TransformFileError: + except (TransformFileError, FileNotFoundError): continue raise TransformFileError( - f"Could not open <{filename}> (formats tried: {', '.join(fmtlist)}." + f"Could not open <{filename}> (formats tried: {', '.join(fmtlist)})." ) def __repr__(self): @@ -468,11 +468,11 @@ def load(filename, fmt=None, reference=None, moving=None): Examples -------- - >>> xfm = load(regress_dir / "affine-LAS.itk.tfm", fmt="itk") + >>> xfm = load(regress_dir / "affine-LAS.itk.tfm") >>> isinstance(xfm, Affine) True - >>> xfm = load(regress_dir / "itktflist.tfm", fmt="itk") + >>> xfm = load(regress_dir / "itktflist.tfm") >>> isinstance(xfm, LinearTransformsMapping) True diff --git a/nitransforms/tests/test_io.py b/nitransforms/tests/test_io.py index a2b9eaaf..39fe7c44 100644 --- a/nitransforms/tests/test_io.py +++ b/nitransforms/tests/test_io.py @@ -24,7 +24,7 @@ FSLinearTransform as LT, FSLinearTransformArray as LTA, ) -from ..io.base import LinearParameters, TransformFileError +from ..io.base import LinearParameters, TransformIOError, TransformFileError LPS = np.diag([-1, -1, 1, 1]) ITK_MAT = LPS.dot(np.ones((4, 4)).dot(LPS)) @@ -224,7 +224,7 @@ def test_Linear_common(tmpdir, data_path, sw, image_orientation, get_testdata): # Test without images if sw == "fsl": - with pytest.raises(ValueError): + with pytest.raises(TransformIOError): factory.from_ras(RAS) else: xfm = factory.from_ras(RAS) @@ -422,7 +422,7 @@ def test_itk_h5(testdata_path): == 2 ) - with pytest.raises(RuntimeError): + with pytest.raises(TransformFileError): list( itk.ITKCompositeH5.from_filename( testdata_path diff --git a/nitransforms/tests/test_linear.py b/nitransforms/tests/test_linear.py index 0bd62d21..aed4a148 100644 --- a/nitransforms/tests/test_linear.py +++ b/nitransforms/tests/test_linear.py @@ -54,6 +54,18 @@ def test_linear_valueerror(): nitl.Affine(np.ones((4, 4))) +def test_linear_load_unsupported(data_path): + """Exercise loading transform without I/O implementation.""" + with pytest.raises(TypeError): + nitl.load(data_path / "itktflist2.tfm", fmt="X5") + + +def test_linear_load_mistaken(data_path): + """Exercise loading transform without I/O implementation.""" + with pytest.raises(io.TransformFileError): + nitl.load(data_path / "itktflist2.tfm", fmt="afni") + + def test_loadsave_itk(tmp_path, data_path, testdata_path): """Test idempotency.""" ref_file = testdata_path / "someones_anatomy.nii.gz" @@ -73,9 +85,13 @@ def test_loadsave_itk(tmp_path, data_path, testdata_path): ) +@pytest.mark.parametrize("autofmt", (False, True)) @pytest.mark.parametrize("fmt", ["itk", "fsl", "afni", "lta"]) -def test_loadsave(tmp_path, data_path, testdata_path, fmt): +def test_loadsave(tmp_path, data_path, testdata_path, autofmt, fmt): """Test idempotency.""" + supplied_fmt = None if autofmt else fmt + + # Load reference transform ref_file = testdata_path / "someones_anatomy.nii.gz" xfm = nitl.load(data_path / "itktflist2.tfm", fmt="itk") xfm.reference = ref_file @@ -85,33 +101,33 @@ def test_loadsave(tmp_path, data_path, testdata_path, fmt): if fmt == "fsl": # FSL should not read a transform without reference - with pytest.raises(ValueError): - nitl.load(fname, fmt=fmt) - nitl.load(fname, fmt=fmt, moving=ref_file) + with pytest.raises(io.TransformIOError): + nitl.load(fname, fmt=supplied_fmt) + nitl.load(fname, fmt=supplied_fmt, moving=ref_file) with pytest.warns(UserWarning): assert np.allclose( xfm.matrix, - nitl.load(fname, fmt=fmt, reference=ref_file).matrix, + nitl.load(fname, fmt=supplied_fmt, reference=ref_file).matrix, ) assert np.allclose( xfm.matrix, - nitl.load(fname, fmt=fmt, reference=ref_file, moving=ref_file).matrix, + nitl.load(fname, fmt=supplied_fmt, reference=ref_file, moving=ref_file).matrix, ) else: - assert xfm == nitl.load(fname, fmt=fmt, reference=ref_file) + assert xfm == nitl.load(fname, fmt=supplied_fmt, reference=ref_file) xfm.to_filename(fname, fmt=fmt, moving=ref_file) if fmt == "fsl": assert np.allclose( xfm.matrix, - nitl.load(fname, fmt=fmt, reference=ref_file, moving=ref_file).matrix, + nitl.load(fname, fmt=supplied_fmt, reference=ref_file, moving=ref_file).matrix, rtol=1e-2, # FSL incurs into large errors due to rounding ) else: - assert xfm == nitl.load(fname, fmt=fmt, reference=ref_file) + assert xfm == nitl.load(fname, fmt=supplied_fmt, reference=ref_file) ref_file = testdata_path / "someones_anatomy.nii.gz" xfm = nitl.load(data_path / "affine-LAS.itk.tfm", fmt="itk") @@ -121,21 +137,21 @@ def test_loadsave(tmp_path, data_path, testdata_path, fmt): if fmt == "fsl": assert np.allclose( xfm.matrix, - nitl.load(fname, fmt=fmt, reference=ref_file, moving=ref_file).matrix, + nitl.load(fname, fmt=supplied_fmt, reference=ref_file, moving=ref_file).matrix, rtol=1e-2, # FSL incurs into large errors due to rounding ) else: - assert xfm == nitl.load(fname, fmt=fmt, reference=ref_file) + assert xfm == nitl.load(fname, fmt=supplied_fmt, reference=ref_file) xfm.to_filename(fname, fmt=fmt, moving=ref_file) if fmt == "fsl": assert np.allclose( xfm.matrix, - nitl.load(fname, fmt=fmt, reference=ref_file, moving=ref_file).matrix, + nitl.load(fname, fmt=supplied_fmt, reference=ref_file, moving=ref_file).matrix, rtol=1e-2, # FSL incurs into large errors due to rounding ) else: - assert xfm == nitl.load(fname, fmt=fmt, reference=ref_file) + assert xfm == nitl.load(fname, fmt=supplied_fmt, reference=ref_file) @pytest.mark.parametrize("image_orientation", ["RAS", "LAS", "LPS", "oblique"]) From bb8bf32ec3e38d48f43117c1541c1c2577547e4b Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Wed, 2 Mar 2022 09:21:55 +0100 Subject: [PATCH 06/34] enh: add itk i/o test --- nitransforms/tests/test_io.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/nitransforms/tests/test_io.py b/nitransforms/tests/test_io.py index 39fe7c44..6c153828 100644 --- a/nitransforms/tests/test_io.py +++ b/nitransforms/tests/test_io.py @@ -1,12 +1,14 @@ # emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: """I/O test cases.""" +import os from subprocess import check_call from io import StringIO import filecmp import shutil import numpy as np import pytest +from h5py import File as H5File import nibabel as nb from nibabel.eulerangles import euler2mat @@ -408,7 +410,7 @@ def test_afni_Displacements(): afni.AFNIDisplacementsField.from_image(field) -def test_itk_h5(testdata_path): +def test_itk_h5(tmpdir, testdata_path): """Test displacements fields.""" assert ( len( @@ -430,6 +432,21 @@ def test_itk_h5(testdata_path): ) ) + tmpdir.chdir() + shutil.copy( + testdata_path / "ds-005_sub-01_from-T1w_to-MNI152NLin2009cAsym_mode-image_xfm.h5", + "test.h5", + ) + os.chmod("test.h5", 0o666) + + with H5File("test.h5", "r+") as h5file: + h5group = h5file["TransformGroup"] + xfm = h5group[list(h5group.keys())[1]] + xfm["TransformType"][0] = b"InventTransform" + + with pytest.raises(TransformIOError): + itk.ITKCompositeH5.from_filename("test.h5") + @pytest.mark.parametrize( "file_type, test_file", [(LTA, "from-fsnative_to-scanner_mode-image.lta")] From 2c5603f00e7d3e995098ee35c7f616f65822bb15 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 15 Mar 2022 11:16:16 +0100 Subject: [PATCH 07/34] FIX: Wrong datatype used for offset when reading ITK's h5 fields. Offset can take negative values and does not need to be integer. Both assumptions were broken in the reader. Related: #137. --- nitransforms/io/itk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nitransforms/io/itk.py b/nitransforms/io/itk.py index b45a84de..fe74d64f 100644 --- a/nitransforms/io/itk.py +++ b/nitransforms/io/itk.py @@ -337,7 +337,7 @@ def from_h5obj(cls, fileobj, check=True): if xfm["TransformType"][0].startswith(b"DisplacementFieldTransform"): _fixed = np.asanyarray(xfm[f"{typo_fallback}FixedParameters"]) shape = _fixed[:3].astype("uint16").tolist() - offset = _fixed[3:6].astype("uint16") + offset = _fixed[3:6].astype("float") zooms = _fixed[6:9].astype("float") directions = _fixed[9:].astype("float").reshape((3, 3)) affine = from_matvec(directions * zooms, offset) From e924f5c76196d3167c9c4d6090fa3613afe58aa4 Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 15 Mar 2022 15:58:10 +0100 Subject: [PATCH 08/34] FIX: Orientation of displacements field and header when reading ITK's h5 Related: #137. --- nitransforms/io/itk.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nitransforms/io/itk.py b/nitransforms/io/itk.py index fe74d64f..0ec552a4 100644 --- a/nitransforms/io/itk.py +++ b/nitransforms/io/itk.py @@ -342,16 +342,15 @@ def from_h5obj(cls, fileobj, check=True): directions = _fixed[9:].astype("float").reshape((3, 3)) affine = from_matvec(directions * zooms, offset) field = np.asanyarray(xfm[f"{typo_fallback}Parameters"]).reshape( - tuple(shape + [1, -1]) + (*shape, 1, -1) ) + field[..., (0, 1)] *= -1.0 hdr = Nifti1Header() hdr.set_intent("vector") hdr.set_data_dtype("float") xfm_list.append( - ITKDisplacementsField.from_image( - Nifti1Image(field.astype("float"), affine, hdr) - ) + Nifti1Image(field.astype("float"), LPS @ affine @ LPS, hdr) ) continue From ad487a13cc553461e7418f02ea01132d39b96dbe Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Tue, 19 Apr 2022 15:34:16 +0200 Subject: [PATCH 09/34] maint: conclude migration poldracklab -> nipy --- .github/workflows/travis.yml | 2 +- CONTRIBUTING.rst | 2 +- Dockerfile | 2 +- README.md | 8 ++++---- docs/conf.py | 2 +- docs/index.rst | 14 +++++++------- docs/installation.rst | 4 ++-- docs/notebooks/isbi2020.ipynb | 4 ++-- joss/paper.md | 2 +- setup.cfg | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/travis.yml b/.github/workflows/travis.yml index 9f97a4db..19c6a2a1 100644 --- a/.github/workflows/travis.yml +++ b/.github/workflows/travis.yml @@ -4,7 +4,7 @@ on: [push] jobs: build-linux: - if: "!contains(github.event.head_commit.message, '[skip ci]' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'poldracklab/nitransforms'))" + if: "!contains(github.event.head_commit.message, '[skip ci]' && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'nipy/nitransforms'))" runs-on: ubuntu-latest env: TEST_DATA_HOME: /home/runner/testdata/nitransforms-tests diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index e94342a8..fdf1650c 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -11,4 +11,4 @@ These guidelines are designed to make it as easy as possible to get involved. If you have any questions that aren’t discussed in our documentation, or it’s difficult to find what you’re looking for, please let us know by opening an -`issue `__! +`issue `__! diff --git a/Dockerfile b/Dockerfile index 16f122c6..ae270b45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -154,6 +154,6 @@ ARG VCS_REF ARG VERSION LABEL org.label-schema.build-date=$BUILD_DATE \ org.label-schema.name="nitransforms" \ - org.label-schema.vcs-url="https://github.com/poldracklab/nitransforms" \ + org.label-schema.vcs-url="https://github.com/nipy/nitransforms" \ org.label-schema.version=$VERSION \ org.label-schema.schema-version="1.0" diff --git a/README.md b/README.md index 3f655a5d..b5a45349 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # NiTransforms [![DOI](https://joss.theoj.org/papers/10.21105/joss.03459/status.svg)](https://doi.org/10.21105/joss.03459) [![ISBI2020](https://img.shields.io/badge/doi-10.31219%2Fosf.io%2F8aq7b-blue.svg)](https://doi.org/10.31219/osf.io/8aq7b) -[![Deps & CI](https://github.com/poldracklab/nitransforms/actions/workflows/travis.yml/badge.svg)](https://github.com/poldracklab/nitransforms/actions/workflows/travis.yml) -[![CircleCI](https://circleci.com/gh/poldracklab/nitransforms.svg?style=svg)](https://circleci.com/gh/poldracklab/nitransforms) -[![codecov](https://codecov.io/gh/poldracklab/nitransforms/branch/master/graph/badge.svg)](https://codecov.io/gh/poldracklab/nitransforms) -[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/poldracklab/nitransforms/master?filepath=docs%2Fnotebooks%2F) +[![Deps & CI](https://github.com/nipy/nitransforms/actions/workflows/travis.yml/badge.svg)](https://github.com/nipy/nitransforms/actions/workflows/travis.yml) +[![CircleCI](https://circleci.com/gh/nipy/nitransforms.svg?style=svg)](https://circleci.com/gh/nipy/nitransforms) +[![codecov](https://codecov.io/gh/nipy/nitransforms/branch/master/graph/badge.svg)](https://codecov.io/gh/nipy/nitransforms) +[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/nipy/nitransforms/master?filepath=docs%2Fnotebooks%2F) [![Docs](https://readthedocs.org/projects/nitransforms/badge/?version=latest)](http://nitransforms.readthedocs.io/en/latest/?badge=latest) A development repo for [nipy/nibabel#656](https://github.com/nipy/nibabel/pull/656) diff --git a/docs/conf.py b/docs/conf.py index 02d270ad..18e532a1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -231,7 +231,7 @@
This page was generated from - {{ docname|e }}.