diff --git a/nitransforms/__init__.py b/nitransforms/__init__.py index 4ded59a0..a65fb01f 100644 --- a/nitransforms/__init__.py +++ b/nitransforms/__init__.py @@ -16,6 +16,7 @@ transform """ + from . import linear, manip, nonlinear, surface from .linear import Affine, LinearTransformsMapping from .nonlinear import DenseFieldTransform diff --git a/nitransforms/cli.py b/nitransforms/cli.py index 8f8f5ce0..aed0c9e2 100644 --- a/nitransforms/cli.py +++ b/nitransforms/cli.py @@ -32,8 +32,8 @@ def cli_apply(pargs): xfm = ( nlinload(pargs.transform, fmt=fmt) - if pargs.nonlinear else - linload(pargs.transform, fmt=fmt) + if pargs.nonlinear + else linload(pargs.transform, fmt=fmt) ) # ensure a reference is set diff --git a/nitransforms/conftest.py b/nitransforms/conftest.py index 70680882..f627fee6 100644 --- a/nitransforms/conftest.py +++ b/nitransforms/conftest.py @@ -1,4 +1,5 @@ """py.test configuration.""" + import os from pathlib import Path import numpy as np @@ -64,7 +65,9 @@ def _reorient(path): newaff = imgaff.copy() newaff[0, 0] *= -1.0 newaff[0, 3] = imgaff.dot(np.hstack((np.array(img.shape[:3]) - 1, 1.0)))[0] - _data["LAS"] = nb.Nifti1Image(np.flip(np.asanyarray(img.dataobj), 0), newaff, img.header) + _data["LAS"] = nb.Nifti1Image( + np.flip(np.asanyarray(img.dataobj), 0), newaff, img.header + ) _data["LAS"].set_data_dtype(img.get_data_dtype()) newaff = imgaff.copy() newaff[0, 0] *= -1.0 diff --git a/nitransforms/io/afni.py b/nitransforms/io/afni.py index 7c66d434..e09b68a7 100644 --- a/nitransforms/io/afni.py +++ b/nitransforms/io/afni.py @@ -1,4 +1,5 @@ """Read/write AFNI's transforms.""" + from math import pi import numpy as np from nibabel.affines import ( @@ -132,15 +133,16 @@ def to_ras(self, moving=None, reference=None): """Return a nitransforms' internal RAS matrix.""" pre_rotation = post_rotation = np.eye(4) - if reference is not None and _is_oblique(ref_aff := _ensure_image(reference).affine): + if reference is not None and _is_oblique( + ref_aff := _ensure_image(reference).affine + ): pre_rotation = _cardinal_rotation(ref_aff, True) if moving is not None and _is_oblique(mov_aff := _ensure_image(moving).affine): post_rotation = _cardinal_rotation(mov_aff, False) - return np.stack([ - post_rotation @ (xfm.to_ras() @ pre_rotation) - for xfm in self.xforms - ]) + return np.stack( + [post_rotation @ (xfm.to_ras() @ pre_rotation) for xfm in self.xforms] + ) def to_string(self): """Convert to a string directly writeable to file.""" @@ -161,7 +163,9 @@ def from_ras(cls, ras, moving=None, reference=None): pre_rotation = post_rotation = np.eye(4) - if reference is not None and _is_oblique(ref_aff := _ensure_image(reference).affine): + if reference is not None and _is_oblique( + ref_aff := _ensure_image(reference).affine + ): pre_rotation = _cardinal_rotation(ref_aff, False) if moving is not None and _is_oblique(mov_aff := _ensure_image(moving).affine): post_rotation = _cardinal_rotation(mov_aff, True) @@ -198,7 +202,7 @@ def from_image(cls, imgobj): hdr = imgobj.header.copy() shape = hdr.get_data_shape() - if len(shape) != 5 or shape[-2] != 1 or not shape[-1] in (2, 3): + if len(shape) != 5 or shape[-2] != 1 or shape[-1] not in (2, 3): raise TransformFileError( 'Displacements field "%s" does not come from AFNI.' % imgobj.file_map["image"].filename diff --git a/nitransforms/io/base.py b/nitransforms/io/base.py index 3c923426..c9f7d17b 100644 --- a/nitransforms/io/base.py +++ b/nitransforms/io/base.py @@ -1,4 +1,5 @@ """Read/write linear transforms.""" + from pathlib import Path import numpy as np from nibabel import load as loadimg diff --git a/nitransforms/io/fsl.py b/nitransforms/io/fsl.py index f454227e..5cf79e6c 100644 --- a/nitransforms/io/fsl.py +++ b/nitransforms/io/fsl.py @@ -1,4 +1,5 @@ """Read/write FSL's transforms.""" + import os import warnings import numpy as np @@ -41,7 +42,9 @@ def from_ras(cls, ras, moving=None, reference=None): moving = reference if reference is None: - raise TransformIOError("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) @@ -78,7 +81,9 @@ def from_string(cls, string): def to_ras(self, moving=None, reference=None): """Return a nitransforms internal RAS+ matrix.""" if reference is None: - raise TransformIOError("Cannot build FSL linear transform without a reference") + raise TransformIOError( + "Cannot build FSL linear transform without a reference" + ) if moving is None: warnings.warn( @@ -180,10 +185,11 @@ def from_image(cls, imgobj): hdr = imgobj.header.copy() shape = hdr.get_data_shape() - if len(shape) != 4 or not shape[-1] in (2, 3): + if len(shape) != 4 or shape[-1] not in (2, 3): raise TransformFileError( - 'Displacements field "%s" does not come from FSL.' % - imgobj.file_map['image'].filename) + 'Displacements field "%s" does not come from FSL.' + % imgobj.file_map["image"].filename + ) field = np.squeeze(np.asanyarray(imgobj.dataobj)) field[..., 0] *= -1.0 diff --git a/nitransforms/io/itk.py b/nitransforms/io/itk.py index afabfd98..b2a6df8a 100644 --- a/nitransforms/io/itk.py +++ b/nitransforms/io/itk.py @@ -1,4 +1,5 @@ """Read/write ITK transforms.""" + import warnings import numpy as np from scipy.io import loadmat as _read_mat, savemat as _save_mat @@ -138,8 +139,7 @@ def from_matlab_dict(cls, mdict, index=0): sa = tf.structarr affine = mdict.get( - "AffineTransform_double_3_3", - mdict.get("AffineTransform_float_3_3") + "AffineTransform_double_3_3", mdict.get("AffineTransform_float_3_3") ) if affine is None: @@ -337,7 +337,7 @@ def from_image(cls, imgobj): hdr = imgobj.header.copy() shape = hdr.get_data_shape() - if len(shape) != 5 or shape[-2] != 1 or not shape[-1] in (2, 3): + if len(shape) != 5 or shape[-2] != 1 or shape[-1] not in (2, 3): raise TransformFileError( 'Displacements field "%s" does not come from ITK.' % imgobj.file_map["image"].filename @@ -412,7 +412,9 @@ def from_h5obj(cls, fileobj, check=True, only_linear=False): # ITK uses Fortran ordering, like NIfTI, but with the vector dimension first field = np.moveaxis( np.reshape( - xfm[f"{typo_fallback}Parameters"], (3, *shape.astype(int)), order='F' + xfm[f"{typo_fallback}Parameters"], + (3, *shape.astype(int)), + order="F", ), 0, -1, @@ -422,9 +424,7 @@ def from_h5obj(cls, fileobj, check=True, only_linear=False): hdr.set_intent("vector") hdr.set_data_dtype("float") - xfm_list.append( - Nifti1Image(field.astype("float"), LPS @ affine, hdr) - ) + xfm_list.append(Nifti1Image(field.astype("float"), LPS @ affine, hdr)) continue raise TransformIOError( diff --git a/nitransforms/surface.py b/nitransforms/surface.py index 7e1e7116..0189325d 100644 --- a/nitransforms/surface.py +++ b/nitransforms/surface.py @@ -7,6 +7,7 @@ # ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Surface transforms.""" + import pathlib import warnings import h5py @@ -15,12 +16,10 @@ from scipy import sparse from scipy.spatial import KDTree from scipy.spatial.distance import cdist -from nitransforms.base import ( - SurfaceMesh -) +from nitransforms.base import SurfaceMesh -class SurfaceTransformBase(): +class SurfaceTransformBase: """Generic surface transformation class""" def __init__(self, reference, moving, spherical=False): @@ -64,7 +63,7 @@ def moving(self, surface): @classmethod def from_filename(cls, reference_path, moving_path): """Create an Surface Index Transformation from a pair of surfaces with corresponding - vertices.""" + vertices.""" reference = SurfaceMesh(nb.load(reference_path)) moving = SurfaceMesh(nb.load(moving_path)) return cls(reference, moving) @@ -72,9 +71,9 @@ def from_filename(cls, reference_path, moving_path): class SurfaceCoordinateTransform(SurfaceTransformBase): """Represents surface transformations in which the indices correspond and the coordinates - differ. This could be two surfaces representing difference structures from the same - hemisphere, like white matter and pial, or it could be a sphere and a deformed sphere that - moves those coordinates to a different location.""" + differ. This could be two surfaces representing difference structures from the same + hemisphere, like white matter and pial, or it could be a sphere and a deformed sphere that + moves those coordinates to a different location.""" __slots__ = ("_reference", "_moving") @@ -90,8 +89,9 @@ def __init__(self, reference, moving): super().__init__(reference=SurfaceMesh(reference), moving=SurfaceMesh(moving)) if np.all(self._reference._triangles != self._moving._triangles): - raise ValueError("Both surfaces for an index transform must have corresponding" - " vertices.") + raise ValueError( + "Both surfaces for an index transform must have corresponding vertices." + ) def map(self, x, inverse=False): if not inverse: @@ -104,8 +104,10 @@ def map(self, x, inverse=False): s_tree = KDTree(source._coords) dists, matches = s_tree.query(x) if not np.allclose(dists, 0): - raise NotImplementedError("Mapping on surfaces not implemented for coordinates that" - " aren't vertices") + raise NotImplementedError( + "Mapping on surfaces not implemented for coordinates that" + " aren't vertices" + ) return dest._coords[matches] def __add__(self, other): @@ -123,11 +125,11 @@ def _to_hdf5(self, x5_root): xform = x5_root.create_group("Transform") xform.attrs["Type"] = "SurfaceCoordinateTransform" reference = xform.create_group("Reference") - reference['Coordinates'] = h5py.SoftLink('/0/Coordinates/0') - reference['Triangles'] = h5py.SoftLink('/0/Triangles/0') + reference["Coordinates"] = h5py.SoftLink("/0/Coordinates/0") + reference["Triangles"] = h5py.SoftLink("/0/Triangles/0") moving = xform.create_group("Moving") - moving['Coordinates'] = h5py.SoftLink('/0/Coordinates/1') - moving['Triangles'] = h5py.SoftLink('/0/Triangles/0') + moving["Coordinates"] = h5py.SoftLink("/0/Coordinates/1") + moving["Triangles"] = h5py.SoftLink("/0/Triangles/0") def to_filename(self, filename, fmt=None): """Store the transform.""" @@ -148,15 +150,19 @@ def to_filename(self, filename, fmt=None): return filename @classmethod - def from_filename(cls, filename=None, reference_path=None, moving_path=None, - fmt=None): + def from_filename( + cls, filename=None, reference_path=None, moving_path=None, fmt=None + ): """Load transform from file.""" if filename is None: if reference_path is None or moving_path is None: - raise ValueError("You must pass either a X5 file or a pair of reference and moving" - " surfaces.") - return cls(SurfaceMesh(nb.load(reference_path)), - SurfaceMesh(nb.load(moving_path))) + raise ValueError( + "You must pass either a X5 file or a pair of reference and moving" + " surfaces." + ) + return cls( + SurfaceMesh(nb.load(reference_path)), SurfaceMesh(nb.load(moving_path)) + ) if fmt is None: try: @@ -175,13 +181,11 @@ def from_filename(cls, filename=None, reference_path=None, moving_path=None, assert f.attrs["Format"] == "X5" xform = f["/0/Transform"] reference = SurfaceMesh.from_arrays( - xform['Reference']['Coordinates'], - xform['Reference']['Triangles'] + xform["Reference"]["Coordinates"], xform["Reference"]["Triangles"] ) moving = SurfaceMesh.from_arrays( - xform['Moving']['Coordinates'], - xform['Moving']['Triangles'] + xform["Moving"]["Coordinates"], xform["Moving"]["Triangles"] ) return cls(reference, moving) @@ -196,9 +200,9 @@ class SurfaceResampler(SurfaceTransformBase): Then apply the transformation to sphere_unproject_from """ - __slots__ = ("_reference", "_moving", "mat", 'interpolation_method') + __slots__ = ("_reference", "_moving", "mat", "interpolation_method") - def __init__(self, reference, moving, interpolation_method='barycentric', mat=None): + def __init__(self, reference, moving, interpolation_method="barycentric", mat=None): """Initialize the resampling. Parameters @@ -218,7 +222,7 @@ def __init__(self, reference, moving, interpolation_method='barycentric', mat=No self.reference.set_radius() self.moving.set_radius() - if interpolation_method not in ['barycentric']: + if interpolation_method not in ["barycentric"]: raise NotImplementedError(f"{interpolation_method} is not implemented.") self.interpolation_method = interpolation_method @@ -267,15 +271,23 @@ def __init__(self, reference, moving, interpolation_method='barycentric', mat=No else: self.mat = sparse.csr_array(mat) # validate shape of the provided matrix - if (mat.shape[0] != moving._npoints) or (mat.shape[1] != reference._npoints): - msg = "Shape of provided mat does not match expectations based on " \ - "dimensions of moving and reference. \n" + if (mat.shape[0] != moving._npoints) or ( + mat.shape[1] != reference._npoints + ): + msg = ( + "Shape of provided mat does not match expectations based on " + "dimensions of moving and reference. \n" + ) if mat.shape[0] != moving._npoints: - msg += f" mat has {mat.shape[0]} rows but moving has {moving._npoints} " \ - f"vertices. \n" + msg += ( + f" mat has {mat.shape[0]} rows but moving has {moving._npoints} " + f"vertices. \n" + ) if mat.shape[1] != reference._npoints: - msg += f" mat has {mat.shape[1]} columns but reference has" \ - f" {reference._npoints} vertices." + msg += ( + f" mat has {mat.shape[1]} columns but reference has" + f" {reference._npoints} vertices." + ) raise ValueError(msg) def __calculate_mat(self): @@ -318,31 +330,34 @@ def map(self, x): return x def __add__(self, other): - if (isinstance(other, SurfaceResampler) - and (other.interpolation_method == self.interpolation_method)): + if isinstance(other, SurfaceResampler) and ( + other.interpolation_method == self.interpolation_method + ): return self.__class__( self.reference, other.moving, - interpolation_method=self.interpolation_method + interpolation_method=self.interpolation_method, ) raise NotImplementedError def __invert__(self): return self.__class__( - self.moving, - self.reference, - interpolation_method=self.interpolation_method + self.moving, self.reference, interpolation_method=self.interpolation_method ) @SurfaceTransformBase.reference.setter def reference(self, surface): - raise ValueError("Don't modify the reference of an existing resampling." - "Create a new one instead.") + raise ValueError( + "Don't modify the reference of an existing resampling." + "Create a new one instead." + ) @SurfaceTransformBase.moving.setter def moving(self, surface): - raise ValueError("Don't modify the moving of an existing resampling." - "Create a new one instead.") + raise ValueError( + "Don't modify the moving of an existing resampling." + "Create a new one instead." + ) def apply(self, x, inverse=False, normalize="element"): """Apply the transform to surface data. @@ -406,18 +421,18 @@ def _to_hdf5(self, x5_root): triangles.create_dataset("1", data=self.moving._triangles) xform = x5_root.create_group("Transform") xform.attrs["Type"] = "SurfaceResampling" - xform.attrs['InterpolationMethod'] = self.interpolation_method + xform.attrs["InterpolationMethod"] = self.interpolation_method mat = xform.create_group("IndexWeights") mat.create_dataset("Data", data=self.mat.data) mat.create_dataset("Indices", data=self.mat.indices) mat.create_dataset("Indptr", data=self.mat.indptr) mat.create_dataset("Shape", data=self.mat.shape) reference = xform.create_group("Reference") - reference['Coordinates'] = h5py.SoftLink('/0/Coordinates/0') - reference['Triangles'] = h5py.SoftLink('/0/Triangles/0') + reference["Coordinates"] = h5py.SoftLink("/0/Coordinates/0") + reference["Triangles"] = h5py.SoftLink("/0/Triangles/0") moving = xform.create_group("Moving") - moving['Coordinates'] = h5py.SoftLink('/0/Coordinates/1') - moving['Triangles'] = h5py.SoftLink('/0/Triangles/1') + moving["Coordinates"] = h5py.SoftLink("/0/Coordinates/1") + moving["Triangles"] = h5py.SoftLink("/0/Triangles/1") def to_filename(self, filename, fmt=None): """Store the transform.""" @@ -438,18 +453,28 @@ def to_filename(self, filename, fmt=None): return filename @classmethod - def from_filename(cls, filename=None, reference_path=None, moving_path=None, - fmt=None, interpolation_method=None): + def from_filename( + cls, + filename=None, + reference_path=None, + moving_path=None, + fmt=None, + interpolation_method=None, + ): """Load transform from file.""" if filename is None: if reference_path is None or moving_path is None: - raise ValueError("You must pass either a X5 file or a pair of reference and moving" - " surfaces.") + raise ValueError( + "You must pass either a X5 file or a pair of reference and moving" + " surfaces." + ) if interpolation_method is None: - interpolation_method = 'barycentric' - return cls(SurfaceMesh(nb.load(reference_path)), - SurfaceMesh(nb.load(moving_path)), - interpolation_method=interpolation_method) + interpolation_method = "barycentric" + return cls( + SurfaceMesh(nb.load(reference_path)), + SurfaceMesh(nb.load(moving_path)), + interpolation_method=interpolation_method, + ) if fmt is None: try: @@ -468,7 +493,7 @@ def from_filename(cls, filename=None, reference_path=None, moving_path=None, assert f.attrs["Format"] == "X5" xform = f["/0/Transform"] try: - iws = xform['IndexWeights'] + iws = xform["IndexWeights"] mat = sparse.csr_matrix( (iws["Data"][()], iws["Indices"][()], iws["Indptr"][()]), shape=iws["Shape"][()], @@ -476,43 +501,42 @@ def from_filename(cls, filename=None, reference_path=None, moving_path=None, except KeyError: mat = None reference = SurfaceMesh.from_arrays( - xform['Reference']['Coordinates'], - xform['Reference']['Triangles'] + xform["Reference"]["Coordinates"], xform["Reference"]["Triangles"] ) moving = SurfaceMesh.from_arrays( - xform['Moving']['Coordinates'], - xform['Moving']['Triangles'] + xform["Moving"]["Coordinates"], xform["Moving"]["Triangles"] ) - interpolation_method = xform.attrs['InterpolationMethod'] - return cls(reference, moving, interpolation_method=interpolation_method, mat=mat) + interpolation_method = xform.attrs["InterpolationMethod"] + return cls( + reference, moving, interpolation_method=interpolation_method, mat=mat + ) def _points_to_triangles(points, triangles): - """Implementation that vectorizes project of a point to a set of triangles. from: https://stackoverflow.com/a/32529589 """ - with np.errstate(all='ignore'): + with np.errstate(all="ignore"): # Unpack triangle points p0, p1, p2 = np.asarray(triangles).swapaxes(0, 1) # Calculate triangle edges e0 = p1 - p0 e1 = p2 - p0 - a = np.einsum('...i,...i', e0, e0) - b = np.einsum('...i,...i', e0, e1) - c = np.einsum('...i,...i', e1, e1) + a = np.einsum("...i,...i", e0, e0) + b = np.einsum("...i,...i", e0, e1) + c = np.einsum("...i,...i", e1, e1) # Calculate determinant and denominator det = a * c - b * b - inv_det = 1. / det + inv_det = 1.0 / det denom = a - 2 * b + c # Project to the edges p = p0 - points[:, np.newaxis] - d = np.einsum('...i,...i', e0, p) - e = np.einsum('...i,...i', e1, p) + d = np.einsum("...i,...i", e0, p) + e = np.einsum("...i,...i", e1, p) u = b * e - c * d v = b * d - a * e @@ -595,13 +619,15 @@ def _barycentric_weights(vecs, coords): det = coords[0] * vecs[3, 0] + coords[1] * vecs[3, 1] + coords[2] * vecs[3, 2] if det == 0: if vecs[3, 0] == 0 and vecs[3, 1] == 0 and vecs[3, 2] == 0: - warnings.warn("Zero cross product of two edges: " - "The three vertices are in the same line.") + warnings.warn( + "Zero cross product of two edges: " + "The three vertices are in the same line." + ) else: print(vecs[3]) y = coords - vecs[0] u, v = np.linalg.lstsq(vecs[1:3].T, y, rcond=None)[0] - t = 1. + t = 1.0 else: uu = coords[0] * vecs[4, 0] + coords[1] * vecs[4, 1] + coords[2] * vecs[4, 2] vv = coords[0] * vecs[5, 0] + coords[1] * vecs[5, 1] + coords[2] * vecs[5, 2] @@ -609,7 +635,7 @@ def _barycentric_weights(vecs, coords): v = vv / det tt = vecs[0, 0] * vecs[3, 0] + vecs[0, 1] * vecs[3, 1] + vecs[0, 2] * vecs[3, 2] t = tt / det - w = 1. - (u + v) + w = 1.0 - (u + v) return w, u, v, t @@ -637,7 +663,6 @@ def _find_weights(point, close_tris, d_tree): # Make sure point is actually inside triangle enclosing = True if np.all((point > closest_tri).sum(0) != 3): - enclosing = False _, ct_idxs = d_tree.query(closest_tri) a = closest_tri[0] @@ -646,7 +671,6 @@ def _find_weights(point, close_tris, d_tree): vecs = np.vstack([a, e1, e2, np.cross(e1, e2), np.cross(e2, a), np.cross(a, e1)]) res = {} res[ct_idxs[0]], res[ct_idxs[1]], res[ct_idxs[2]], _ = _barycentric_weights( - vecs, - point.squeeze() + vecs, point.squeeze() ) return res, enclosing diff --git a/nitransforms/tests/test_cli.py b/nitransforms/tests/test_cli.py index 58867131..40b911c5 100644 --- a/nitransforms/tests/test_cli.py +++ b/nitransforms/tests/test_cli.py @@ -6,8 +6,11 @@ from ..cli import cli_apply, main as ntcli if os.getenv("PYTEST_XDIST_WORKER"): - breaks_on_xdist = pytest.mark.skip(reason="xdist is active; rerun without to run this test.") + breaks_on_xdist = pytest.mark.skip( + reason="xdist is active; rerun without to run this test." + ) else: + def breaks_on_xdist(test): return test diff --git a/nitransforms/tests/test_conversions.py b/nitransforms/tests/test_conversions.py index 1229d49a..51f69aa0 100644 --- a/nitransforms/tests/test_conversions.py +++ b/nitransforms/tests/test_conversions.py @@ -1,6 +1,7 @@ # emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: """Conversions between formats.""" + import numpy as np import pytest from .. import linear as _l @@ -109,7 +110,10 @@ def test_lta2fsl_conversions(data_path, fromto, testdata_path): ], ) def test_fsl2lta_conversions( - data_path, testdata_path, tmp_path, fromto, + data_path, + testdata_path, + tmp_path, + fromto, ): """Check conversions between formats.""" filename = f"from-{fromto[0]}_to-{fromto[1]}_mode-image" @@ -119,7 +123,7 @@ def test_fsl2lta_conversions( data_path / "regressions" / f"{filename}.fsl", reference=testdata_path / f"T1w_{fromto[0]}.nii.gz", moving=testdata_path / refname, - fmt="fsl" + fmt="fsl", ) fsl.to_filename( tmp_path / "test.lta", @@ -134,4 +138,6 @@ def test_fsl2lta_conversions( expected_fname = data_path / "regressions" / "".join((filename, ".lta")) exp_lta = LTA.from_filename(expected_fname) - assert np.allclose(converted_lta["xforms"][0]["m_L"], exp_lta["xforms"][0]["m_L"], atol=1e-4) + assert np.allclose( + converted_lta["xforms"][0]["m_L"], exp_lta["xforms"][0]["m_L"], atol=1e-4 + ) diff --git a/nitransforms/tests/test_surface.py b/nitransforms/tests/test_surface.py index a210583e..815e4838 100644 --- a/nitransforms/tests/test_surface.py +++ b/nitransforms/tests/test_surface.py @@ -8,7 +8,7 @@ from nitransforms.surface import ( SurfaceTransformBase, SurfaceCoordinateTransform, - SurfaceResampler + SurfaceResampler, ) # def test_surface_transform_npz(): @@ -42,6 +42,7 @@ # assert y_none.sum() != y_element.sum() # assert y_none.sum() != y_sum.sum() + def test_SurfaceTransformBase(testdata_path): # note these transformations are a bit of a weird use of surface transformation, but I'm # just testing the base class and the io @@ -49,7 +50,9 @@ def test_SurfaceTransformBase(testdata_path): testdata_path / "sub-sid000005_ses-budapest_acq-MPRAGE_hemi-R_space-fsLR_desc-reg_sphere.surf.gii" ) - pial_path = testdata_path / "sub-sid000005_ses-budapest_acq-MPRAGE_hemi-R_pial.surf.gii" + pial_path = ( + testdata_path / "sub-sid000005_ses-budapest_acq-MPRAGE_hemi-R_pial.surf.gii" + ) sphere_reg = SurfaceMesh(nb.load(sphere_reg_path)) pial = SurfaceMesh(nb.load(pial_path)) @@ -78,7 +81,9 @@ def test_SurfaceCoordinateTransform(testdata_path): testdata_path / "sub-sid000005_ses-budapest_acq-MPRAGE_hemi-R_space-fsLR_desc-reg_sphere.surf.gii" ) - pial_path = testdata_path / "sub-sid000005_ses-budapest_acq-MPRAGE_hemi-R_pial.surf.gii" + pial_path = ( + testdata_path / "sub-sid000005_ses-budapest_acq-MPRAGE_hemi-R_pial.surf.gii" + ) fslr_sphere_path = testdata_path / "tpl-fsLR_hemi-R_den-32k_sphere.surf.gii" sphere_reg = SurfaceMesh(nb.load(sphere_reg_path)) @@ -91,12 +96,15 @@ def test_SurfaceCoordinateTransform(testdata_path): # test loading from filenames sct = SurfaceCoordinateTransform(pial, sphere_reg) - sctf = SurfaceCoordinateTransform.from_filename(reference_path=pial_path, - moving_path=sphere_reg_path) + sctf = SurfaceCoordinateTransform.from_filename( + reference_path=pial_path, moving_path=sphere_reg_path + ) assert sct == sctf # test mapping - assert np.all(sct.map(sct.moving._coords[:100], inverse=True) == sct.reference._coords[:100]) + assert np.all( + sct.map(sct.moving._coords[:100], inverse=True) == sct.reference._coords[:100] + ) assert np.all(sct.map(sct.reference._coords[:100]) == sct.moving._coords[:100]) with pytest.raises(NotImplementedError): sct.map(sct.moving._coords[0]) @@ -119,7 +127,9 @@ def test_SurfaceCoordinateTransformIO(testdata_path, tmpdir): testdata_path / "sub-sid000005_ses-budapest_acq-MPRAGE_hemi-R_space-fsLR_desc-reg_sphere.surf.gii" ) - pial_path = testdata_path / "sub-sid000005_ses-budapest_acq-MPRAGE_hemi-R_pial.surf.gii" + pial_path = ( + testdata_path / "sub-sid000005_ses-budapest_acq-MPRAGE_hemi-R_pial.surf.gii" + ) sct = SurfaceCoordinateTransform(pial_path, sphere_reg_path) fn = tempfile.mktemp(suffix=".h5") @@ -129,7 +139,6 @@ def test_SurfaceCoordinateTransformIO(testdata_path, tmpdir): def test_ProjectUnproject(testdata_path): - sphere_reg_path = ( testdata_path / "sub-sid000005_ses-budapest_acq-MPRAGE_hemi-R_space-fsLR_desc-reg_sphere.surf.gii" @@ -140,10 +149,11 @@ def test_ProjectUnproject(testdata_path): / "sub-sid000005_ses-budapest_acq-MPRAGE_hemi-R_space-fsaverage_desc-reg_sphere.surf.gii" ) fslr_fsaverage_sphere_path = ( - testdata_path - / "tpl-fsLR_space-fsaverage_hemi-R_den-32k_sphere.surf.gii" + testdata_path / "tpl-fsLR_space-fsaverage_hemi-R_den-32k_sphere.surf.gii" + ) + pial_path = ( + testdata_path / "sub-sid000005_ses-budapest_acq-MPRAGE_hemi-R_pial.surf.gii" ) - pial_path = testdata_path / "sub-sid000005_ses-budapest_acq-MPRAGE_hemi-R_pial.surf.gii" # test project-unproject funcitonality projunproj = SurfaceResampler(sphere_reg_path, fslr_sphere_path) @@ -157,10 +167,7 @@ def test_ProjectUnproject(testdata_path): def test_SurfaceResampler(testdata_path, tmpdir): dif_tol = 0.001 - fslr_sphere_path = ( - testdata_path - / "tpl-fsLR_hemi-R_den-32k_sphere.surf.gii" - ) + fslr_sphere_path = testdata_path / "tpl-fsLR_hemi-R_den-32k_sphere.surf.gii" shape_path = ( testdata_path / "sub-sid000005_ses-budapest_acq-MPRAGE_hemi-R_thickness.shape.gii" @@ -190,7 +197,9 @@ def test_SurfaceResampler(testdata_path, tmpdir): moving = sphere_reg # compare results to what connectome workbench produces resampling = SurfaceResampler(reference, moving) - resampled_thickness = resampling.apply(subj_thickness.agg_data(), normalize='element') + resampled_thickness = resampling.apply( + subj_thickness.agg_data(), normalize="element" + ) ref_resampled = nb.load(ref_resampled_thickness_path).agg_data() max_dif = np.abs(resampled_thickness.astype(np.float32) - ref_resampled).max() @@ -218,13 +227,17 @@ def test_SurfaceResampler(testdata_path, tmpdir): assert np.allclose(resampling2.reference._coords, resampling.reference._coords) assert np.all(resampling2.moving._triangles == resampling.moving._triangles) - resampled_thickness2 = resampling2.apply(subj_thickness.agg_data(), normalize='element') + resampled_thickness2 = resampling2.apply( + subj_thickness.agg_data(), normalize="element" + ) assert np.all(resampled_thickness2 == resampled_thickness) # test loading with a csr assert isinstance(resampling.mat, sparse.csr_array) resampling2a = SurfaceResampler(reference, moving, mat=resampling.mat) - resampled_thickness2a = resampling2a.apply(subj_thickness.agg_data(), normalize='element') + resampled_thickness2a = resampling2a.apply( + subj_thickness.agg_data(), normalize="element" + ) assert np.all(resampled_thickness2a == resampled_thickness) with pytest.raises(ValueError): @@ -234,8 +247,11 @@ def test_SurfaceResampler(testdata_path, tmpdir): assert np.all(resampling.map(np.array([[0, 0, 0]])) == np.array([[0, 0, 0]])) # test loading from surfaces - resampling3 = SurfaceResampler.from_filename(reference_path=fslr_sphere_path, - moving_path=sphere_reg_path) + resampling3 = SurfaceResampler.from_filename( + reference_path=fslr_sphere_path, moving_path=sphere_reg_path + ) assert resampling3 == resampling - resampled_thickness3 = resampling3.apply(subj_thickness.agg_data(), normalize='element') + resampled_thickness3 = resampling3.apply( + subj_thickness.agg_data(), normalize="element" + ) assert np.all(resampled_thickness3 == resampled_thickness) diff --git a/nitransforms/tests/test_version.py b/nitransforms/tests/test_version.py index 48a70ecf..bc2a8a9f 100644 --- a/nitransforms/tests/test_version.py +++ b/nitransforms/tests/test_version.py @@ -1,4 +1,5 @@ """Test _version.py.""" + import sys from importlib import reload import nitransforms diff --git a/nitransforms/tests/utils.py b/nitransforms/tests/utils.py index 39dd8e11..e653113e 100644 --- a/nitransforms/tests/utils.py +++ b/nitransforms/tests/utils.py @@ -1,4 +1,5 @@ """Utilities for testing.""" + from pathlib import Path import numpy as np