Skip to content

Commit

Permalink
RF: reorganize x5 files
Browse files Browse the repository at this point in the history
  • Loading branch information
Shotgunosine committed Jun 21, 2024
1 parent 5e8b492 commit e6a63d9
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 27 deletions.
133 changes: 110 additions & 23 deletions nitransforms/surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,18 @@ def __init__(self, reference, moving):
Parameters
----------
reference: surface
Surface with the destination coordinates for each index.
moving: surface
Surface with the starting coordinates for each index.
moving: surface
Surface with the destination coordinates for each index.
"""

super().__init__(reference=reference, moving=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"

Check warning on line 93 in nitransforms/surface.py

View check run for this annotation

Codecov / codecov/patch

nitransforms/surface.py#L93

Added line #L93 was not covered by tests
" vertices.")

def map(self, x, inverse=False):
if inverse:
if not inverse:
source = self.reference
dest = self.moving
else:
Expand All @@ -113,6 +113,77 @@ def __add__(self, other):
return self.__class__(self.reference, other.moving)
raise NotImplementedError

def _to_hdf5(self, x5_root):
"""Write transform to HDF5 file."""
triangles = x5_root.create_group("Triangles")
coords = x5_root.create_group("Coordinates")
coords.create_dataset("0", data=self.reference._coords)
coords.create_dataset("1", data=self.moving._coords)
triangles.create_dataset("0", data=self.reference._triangles)
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')
moving = xform.create_group("Moving")
moving['Coordinates'] = h5py.SoftLink('/0/Coordinates/1')
moving['Triangles'] = h5py.SoftLink('/0/Triangles/0')

def to_filename(self, filename, fmt=None):
"""Store the transform."""
if fmt is None:
fmt = "npz" if filename.endswith(".npz") else "X5"

if fmt == "npz":
raise NotImplementedError
# sparse.save_npz(filename, self.mat)
# return filename

with h5py.File(filename, "w") as out_file:
out_file.attrs["Format"] = "X5"
out_file.attrs["Version"] = np.uint16(1)
root = out_file.create_group("/0")
self._to_hdf5(root)

return filename

@classmethod
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"

Check warning on line 156 in nitransforms/surface.py

View check run for this annotation

Codecov / codecov/patch

nitransforms/surface.py#L156

Added line #L156 was not covered by tests
" surfaces.")
return cls(SurfaceMesh(nb.load(reference_path)),
SurfaceMesh(nb.load(moving_path)))

if fmt is None:
try:
fmt = "npz" if filename.endswith(".npz") else "X5"
except AttributeError:
fmt = "npz" if filename.as_posix().endswith(".npz") else "X5"

Check warning on line 165 in nitransforms/surface.py

View check run for this annotation

Codecov / codecov/patch

nitransforms/surface.py#L164-L165

Added lines #L164 - L165 were not covered by tests

if fmt == "npz":
raise NotImplementedError
# return cls(sparse.load_npz(filename))

if fmt != "X5":
raise ValueError("Only npz and X5 formats are supported.")

Check warning on line 172 in nitransforms/surface.py

View check run for this annotation

Codecov / codecov/patch

nitransforms/surface.py#L172

Added line #L172 was not covered by tests

with h5py.File(filename, "r") as f:
assert f.attrs["Format"] == "X5"
xform = f["/0/Transform"]
reference = SurfaceMesh.from_arrays(
xform['Reference']['Coordinates'],
xform['Reference']['Triangles']
)

moving = SurfaceMesh.from_arrays(
xform['Moving']['Coordinates'],
xform['Moving']['Triangles']
)
return cls(reference, moving)

class SurfaceResampler(SurfaceTransformBase):
"""Represents transformations in which the coordinate space remains the same and the indicies
Expand Down Expand Up @@ -293,17 +364,26 @@ def apply(self, x, inverse=False, normalize="element"):

def _to_hdf5(self, x5_root):
"""Write transform to HDF5 file."""
triangles = x5_root.create_group("Triangles")
coords = x5_root.create_group("Coordinates")
coords.create_dataset("0", data=self.reference._coords)
coords.create_dataset("1", data=self.moving._coords)
triangles.create_dataset("0", data=self.reference._triangles)
triangles.create_dataset("1", data=self.moving._triangles)
xform = x5_root.create_group("Transform")
xform.attrs["Type"] = "SurfaceResampling"
xform.attrs['interpolation_method'] = self.interpolation_method
xform.create_dataset("mat_data", data=self.mat.data)
xform.create_dataset("mat_indices", data=self.mat.indices)
xform.create_dataset("mat_indptr", data=self.mat.indptr)
xform.create_dataset("mat_shape", data=self.mat.shape)
xform.create_dataset("reference_coordinates", data=self.reference._coords)
xform.create_dataset("reference_triangles", data=self.reference._triangles)
xform.create_dataset("moving_coordinates", data=self.moving._coords)
xform.create_dataset("moving_triangles", data=self.moving._triangles)
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')
moving = xform.create_group("Moving")
moving['Coordinates'] = h5py.SoftLink('/0/Coordinates/1')
moving['Triangles'] = h5py.SoftLink('/0/Triangles/1')

def to_filename(self, filename, fmt=None):
"""Store the transform."""
Expand Down Expand Up @@ -338,7 +418,10 @@ def from_filename(cls, filename=None, reference_path=None, moving_path=None,
interpolation_method=interpolation_method)

if fmt is None:
fmt = "npz" if filename.endswith(".npz") else "X5"
try:
fmt = "npz" if filename.endswith(".npz") else "X5"
except AttributeError:
fmt = "npz" if filename.as_posix().endswith(".npz") else "X5"

Check warning on line 424 in nitransforms/surface.py

View check run for this annotation

Codecov / codecov/patch

nitransforms/surface.py#L423-L424

Added lines #L423 - L424 were not covered by tests

if fmt == "npz":
raise NotImplementedError
Expand All @@ -350,20 +433,24 @@ def from_filename(cls, filename=None, reference_path=None, moving_path=None,
with h5py.File(filename, "r") as f:
assert f.attrs["Format"] == "X5"
xform = f["/0/Transform"]
mat = sparse.csr_matrix(
(xform["mat_data"][()], xform["mat_indices"][()], xform["mat_indptr"][()]),
shape=xform["mat_shape"][()],
)
try:
iws = xform['IndexWeights']
mat = sparse.csr_matrix(
(iws["Data"][()], iws["Indices"][()], iws["Indptr"][()]),
shape=iws["Shape"][()],
)
except KeyError:
mat=None

Check warning on line 443 in nitransforms/surface.py

View check run for this annotation

Codecov / codecov/patch

nitransforms/surface.py#L442-L443

Added lines #L442 - L443 were not covered by tests
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['interpolation_method']
interpolation_method = xform.attrs['InterpolationMethod']
return cls(reference, moving, interpolation_method=interpolation_method, mat=mat)


Expand Down
20 changes: 16 additions & 4 deletions nitransforms/tests/test_surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,15 @@ def test_SurfaceCoordinateTransform(testdata_path):

# test loading from filenames
sct = SurfaceCoordinateTransform(sphere_reg, pial)
sctf = SurfaceCoordinateTransform.from_filename(sphere_reg_path, pial_path)
sctf = SurfaceCoordinateTransform.from_filename(reference_path=sphere_reg_path,
moving_path=pial_path)
assert sct == sctf

# test mapping
assert np.all(sct.map(sct.moving._coords[:100]) == sct.reference._coords[:100])
assert np.all(sct.map(sct.reference._coords[:100], inverse=True) == sct.moving._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.reference._coords[0])
sct.map(sct.moving._coords[0])

# test inversion and addition
scti = ~sct
Expand All @@ -106,6 +107,17 @@ def test_SurfaceCoordinateTransform(testdata_path):
assert np.all(scti.reference._triangles == sct.reference._triangles)
assert scti == sct

def test_SurfaceCoordinateTransformIO(testdata_path, tmpdir):
sphere_reg_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"
fslr_sphere_path = testdata_path / "tpl-fsLR_hemi-R_den-32k_sphere.surf.gii"

sct = SurfaceCoordinateTransform(sphere_reg_path, pial_path)
fn = tempfile.mktemp(suffix=".h5")
sct.to_filename(fn)
sct2 = SurfaceCoordinateTransform.from_filename(fn)
assert sct == sct2

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"
Expand Down

0 comments on commit e6a63d9

Please sign in to comment.