From 4c9a3ed622c3b1b76d2e0e677ab5ebebe38779c2 Mon Sep 17 00:00:00 2001 From: Julien Marabotto Date: Tue, 7 May 2024 12:19:30 +0200 Subject: [PATCH 01/10] X5.py created (cherry picked from commit 49bf4c36306e682b3b2889afcdd78b0bf4f9b4a8) --- nitransforms/io/__init__.py | 3 ++- nitransforms/io/x5.py | 53 +++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 nitransforms/io/x5.py diff --git a/nitransforms/io/__init__.py b/nitransforms/io/__init__.py index c38d11c2..f456a6d1 100644 --- a/nitransforms/io/__init__.py +++ b/nitransforms/io/__init__.py @@ -1,7 +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: """Read and write transforms.""" -from nitransforms.io import afni, fsl, itk, lta +from nitransforms.io import afni, fsl, itk, lta, x5 from nitransforms.io.base import TransformIOError, TransformFileError __all__ = [ @@ -22,6 +22,7 @@ "fs": (lta, "FSLinearTransform"), "fsl": (fsl, "FSLLinearTransform"), "afni": (afni, "AFNILinearTransform"), + "x5": (x5, "X5LinearTransform") } diff --git a/nitransforms/io/x5.py b/nitransforms/io/x5.py new file mode 100644 index 00000000..b38ef9ab --- /dev/null +++ b/nitransforms/io/x5.py @@ -0,0 +1,53 @@ +"""Read/write x5 transforms.""" +import warnings +import numpy as np +from scipy.io import loadmat as _read_mat, savemat as _save_mat +from h5py import File as H5File +from nibabel import Nifti1Header, Nifti1Image +from nibabel.affines import from_matvec +from nitransforms.io.base import ( + BaseLinearTransformList, + DisplacementsField, + LinearParameters, + TransformIOError, + TransformFileError, +) + +LPS = np.diag([-1, -1, 1, 1]) + +class X5LinearTransform(LinearParameters): + """A string-based structure for X5 linear transforms.""" + + template_dtype = np.dtype( + [ + ("type", "i4"), + ("index", "i4"), + ("parameters", "f8", (4, 4)), + ("offset", "f4", 3), # Center of rotation + ] + ) + dtype = template_dtype + + def __init__(self, parameters=None, offset=None): + return + + def __str__(self): + return + + def to_filename(self, filename): + """Store this transform to a file with the appropriate format.""" + sa = self.structarr + affine = np.array( + np.hstack( + (sa["parameters"][:3, :3].reshape(-1), sa["parameters"][:3, 3]) + )[..., np.newaxis], + dtype="f8", + ) + return + + @classmethod + def from_filename(cls, filename): + """Read the struct from a X5 file given its path.""" + if str(filename).endswith(".h5"): + with H5File(str(filename)) as f: + return cls.from_h5obj(f) From fce65f2d30e9c0d4ecff66fe08602d170263b403 Mon Sep 17 00:00:00 2001 From: Julien Marabotto Date: Fri, 10 May 2024 09:32:23 +0200 Subject: [PATCH 02/10] enh: update implementation of X5 (cherry picked from commit 74090ac7a7ea492b5ebba848f67996dcc587ad82) --- nitransforms/io/x5.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/nitransforms/io/x5.py b/nitransforms/io/x5.py index b38ef9ab..11403c92 100644 --- a/nitransforms/io/x5.py +++ b/nitransforms/io/x5.py @@ -51,3 +51,19 @@ def from_filename(cls, filename): if str(filename).endswith(".h5"): with H5File(str(filename)) as f: return cls.from_h5obj(f) + +class X5LinearTransformArray(BaseLinearTransformList): + """A string-based structure for series of X5 linear transforms.""" + + _inner_type = X5LinearTransform + + @property + def xforms(self): + """Get the list of internal ITKLinearTransforms.""" + return self._xforms + + @xforms.setter + def xforms(self, value): + self._xforms = list(value) + for i, val in enumerate(self.xforms): + val["index"] = i \ No newline at end of file From 5b540b7f81565e91703543519bf57c38b375dad2 Mon Sep 17 00:00:00 2001 From: Julien Marabotto Date: Fri, 10 May 2024 14:49:03 +0200 Subject: [PATCH 03/10] enh: update implementation of X5 (cherry picked from commit ba8a389156728c7b300ff0f6b59cfc9a63f50775) --- nitransforms/io/x5.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/nitransforms/io/x5.py b/nitransforms/io/x5.py index 11403c92..264df3d7 100644 --- a/nitransforms/io/x5.py +++ b/nitransforms/io/x5.py @@ -35,14 +35,9 @@ def __str__(self): return def to_filename(self, filename): - """Store this transform to a file with the appropriate format.""" + '''store this transform to a file with the X5 format''' sa = self.structarr - affine = np.array( - np.hstack( - (sa["parameters"][:3, :3].reshape(-1), sa["parameters"][:3, 3]) - )[..., np.newaxis], - dtype="f8", - ) + affine = '''some affine that will return a 4x4 array''' return @classmethod @@ -61,9 +56,3 @@ class X5LinearTransformArray(BaseLinearTransformList): def xforms(self): """Get the list of internal ITKLinearTransforms.""" return self._xforms - - @xforms.setter - def xforms(self, value): - self._xforms = list(value) - for i, val in enumerate(self.xforms): - val["index"] = i \ No newline at end of file From 92e932cef591dd359d27016f74327b0543907794 Mon Sep 17 00:00:00 2001 From: Julien Marabotto Date: Fri, 17 May 2024 16:17:51 +0200 Subject: [PATCH 04/10] enh: x5 implementation (cherry picked from commit e4e3f44c0951704d2a1a5653aedd9ad6dab64958) --- nitransforms/io/__init__.py | 2 +- nitransforms/io/x5.py | 41 +++++++++++++++++-------------------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/nitransforms/io/__init__.py b/nitransforms/io/__init__.py index f456a6d1..3627fd34 100644 --- a/nitransforms/io/__init__.py +++ b/nitransforms/io/__init__.py @@ -22,7 +22,7 @@ "fs": (lta, "FSLinearTransform"), "fsl": (fsl, "FSLLinearTransform"), "afni": (afni, "AFNILinearTransform"), - "x5": (x5, "X5LinearTransform") + "x5": (x5, "X5Transform") } diff --git a/nitransforms/io/x5.py b/nitransforms/io/x5.py index 264df3d7..72b08a0f 100644 --- a/nitransforms/io/x5.py +++ b/nitransforms/io/x5.py @@ -13,20 +13,10 @@ TransformFileError, ) -LPS = np.diag([-1, -1, 1, 1]) - -class X5LinearTransform(LinearParameters): +class X5Transform: """A string-based structure for X5 linear transforms.""" - template_dtype = np.dtype( - [ - ("type", "i4"), - ("index", "i4"), - ("parameters", "f8", (4, 4)), - ("offset", "f4", 3), # Center of rotation - ] - ) - dtype = template_dtype + _transform = None def __init__(self, parameters=None, offset=None): return @@ -34,25 +24,32 @@ def __init__(self, parameters=None, offset=None): def __str__(self): return - def to_filename(self, filename): - '''store this transform to a file with the X5 format''' - sa = self.structarr - affine = '''some affine that will return a 4x4 array''' - return - @classmethod def from_filename(cls, filename): """Read the struct from a X5 file given its path.""" if str(filename).endswith(".h5"): - with H5File(str(filename)) as f: - return cls.from_h5obj(f) + with H5File(str(filename), 'r') as hdf: + return cls.from_h5obj(hdf) + + @classmethod + def from_h5obj(cls, h5obj): + """Read the transformations in an X5 file.""" + xfm_list = list(h5obj.keys()) + + xfm = xfm_list["Transform"] + inv = xfm_list["Inverse"] + coords = xfm_list["Size"] + map = xfm_list["Mapping"] + + return xfm, inv, coords, map + class X5LinearTransformArray(BaseLinearTransformList): """A string-based structure for series of X5 linear transforms.""" - _inner_type = X5LinearTransform + _inner_type = X5Transform @property def xforms(self): - """Get the list of internal ITKLinearTransforms.""" + """Get the list of internal X5LinearTransforms.""" return self._xforms From d092ea1bb402d1d07280b0290e6f8c61cb3029b2 Mon Sep 17 00:00:00 2001 From: sgiavasis Date: Thu, 18 Jul 2024 00:58:43 -0400 Subject: [PATCH 05/10] Set up I/O functions to be expandable, deferring some design decisions to the future. Started the beginning of some xfm_utils for quicker testing and validation. (cherry picked from commit 7ba96c3bb559556caab6c5b9fea423a4dc027068) --- nitransforms/io/base.py | 126 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/nitransforms/io/base.py b/nitransforms/io/base.py index 3c923426..78c28827 100644 --- a/nitransforms/io/base.py +++ b/nitransforms/io/base.py @@ -1,11 +1,137 @@ """Read/write linear transforms.""" from pathlib import Path import numpy as np +import nibabel as nb from nibabel import load as loadimg +import h5py + from ..patched import LabeledWrapStruct +def get_xfm_filetype(xfm_file): + path = Path(xfm_file) + ext = path.suffix + if ext == '.gz' and path.name.endswith('.nii.gz'): + return 'nifti' + + file_types = { + '.nii': 'nifti', + '.h5': 'hdf5', + '.x5': 'x5', + '.txt': 'txt', + '.mat': 'txt' + } + return file_types.get(ext, 'unknown') + +def gather_fields(x5=None, hdf5=None, nifti=None, shape=None, affine=None, header=None): + xfm_fields = { + "x5": x5, + "hdf5": hdf5, + "nifti": nifti, + "header": header, + "shape": shape, + "affine": affine + } + return xfm_fields + +def load_nifti(nifti_file): + nifti_xfm = nb.load(nifti_file) + xfm_data = nifti_xfm.get_fdata() + shape = nifti_xfm.shape + affine = nifti_xfm.affine + header = getattr(nifti_xfm, "header", None) + return gather_fields(nifti=xfm_data, shape=shape, affine=affine, header=header) + +def load_hdf5(hdf5_file): + storage = {} + + def get_hdf5_items(name, x5_root): + if isinstance(x5_root, h5py.Dataset): + storage[name] = { + 'type': 'dataset', + 'attrs': dict(x5_root.attrs), + 'shape': x5_root.shape, + 'data': x5_root[()] + } + elif isinstance(x5_root, h5py.Group): + storage[name] = { + 'type': 'group', + 'attrs': dict(x5_root.attrs), + 'members': {} + } + + with h5py.File(hdf5_file, 'r') as f: + f.visititems(get_hdf5_items) + if storage: + hdf5_storage = {'hdf5': storage} + return hdf5_storage + +def load_x5(x5_file): + load_hdf5(x5_file) + +def load_mat(mat_file): + affine_matrix = np.loadtxt(mat_file) + affine = nb.affines.from_matvec(affine_matrix[:,:3], affine_matrix[:,3]) + return gather_fields(affine=affine) + +def xfm_loader(xfm_file): + loaders = { + 'nifti': load_nifti, + 'hdf5': load_hdf5, + 'x5': load_x5, + 'txt': load_mat, + 'mat': load_mat + } + xfm_filetype = get_xfm_filetype(xfm_file) + loader = loaders.get(xfm_filetype) + if loader is None: + raise ValueError(f"Unsupported file type: {xfm_filetype}") + return loader(xfm_file) + +def to_filename(self, filename, fmt="X5"): + """Store the transform in BIDS-Transforms HDF5 file format (.x5).""" + 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 + +def _to_hdf5(self, x5_root): + """Serialize this object into the x5 file format.""" + transform_group = x5_root.create_group("TransformGroup") + + """Group '0' containing Affine transform""" + transform_0 = transform_group.create_group("0") + + transform_0.attrs["Type"] = "Affine" + transform_0.create_dataset("Transform", data=self._affine) + transform_0.create_dataset("Inverse", data=np.linalg.inv(self._affine)) + + metadata = {"key": "value"} + transform_0.attrs["Metadata"] = str(metadata) + + """sub-group 'Domain' contained within group '0' """ + domain_group = transform_0.create_group("Domain") + #domain_group.attrs["Grid"] = self._grid + #domain_group.create_dataset("Size", data=_as_homogeneous(self._reference.shape)) + #domain_group.create_dataset("Mapping", data=self.mapping) + +def _from_x5(self, x5_root): + variables = {} + + x5_root.visititems(lambda name, x5_root: loader(name, x5_root, variables)) + + _transform = variables["TransformGroup/0/Transform"] + _inverse = variables["TransformGroup/0/Inverse"] + _size = variables["TransformGroup/0/Domain/Size"] + _mapping = variables["TransformGroup/0/Domain/Mapping"] + + return _transform, _inverse, _size, _map + + class TransformIOError(IOError): """General I/O exception while reading/writing transforms.""" From 988346803873924f21f79b91d008cad0244d6a01 Mon Sep 17 00:00:00 2001 From: Julien Marabotto Date: Tue, 7 May 2024 12:19:30 +0200 Subject: [PATCH 06/10] X5.py created (cherry picked from commit 0e213cb259030a92af81eb54dc0bd792bed4abf2) --- nitransforms/io/x5.py | 52 +++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/nitransforms/io/x5.py b/nitransforms/io/x5.py index 72b08a0f..b38ef9ab 100644 --- a/nitransforms/io/x5.py +++ b/nitransforms/io/x5.py @@ -13,10 +13,20 @@ TransformFileError, ) -class X5Transform: +LPS = np.diag([-1, -1, 1, 1]) + +class X5LinearTransform(LinearParameters): """A string-based structure for X5 linear transforms.""" - _transform = None + template_dtype = np.dtype( + [ + ("type", "i4"), + ("index", "i4"), + ("parameters", "f8", (4, 4)), + ("offset", "f4", 3), # Center of rotation + ] + ) + dtype = template_dtype def __init__(self, parameters=None, offset=None): return @@ -24,32 +34,20 @@ def __init__(self, parameters=None, offset=None): def __str__(self): return + def to_filename(self, filename): + """Store this transform to a file with the appropriate format.""" + sa = self.structarr + affine = np.array( + np.hstack( + (sa["parameters"][:3, :3].reshape(-1), sa["parameters"][:3, 3]) + )[..., np.newaxis], + dtype="f8", + ) + return + @classmethod def from_filename(cls, filename): """Read the struct from a X5 file given its path.""" if str(filename).endswith(".h5"): - with H5File(str(filename), 'r') as hdf: - return cls.from_h5obj(hdf) - - @classmethod - def from_h5obj(cls, h5obj): - """Read the transformations in an X5 file.""" - xfm_list = list(h5obj.keys()) - - xfm = xfm_list["Transform"] - inv = xfm_list["Inverse"] - coords = xfm_list["Size"] - map = xfm_list["Mapping"] - - return xfm, inv, coords, map - - -class X5LinearTransformArray(BaseLinearTransformList): - """A string-based structure for series of X5 linear transforms.""" - - _inner_type = X5Transform - - @property - def xforms(self): - """Get the list of internal X5LinearTransforms.""" - return self._xforms + with H5File(str(filename)) as f: + return cls.from_h5obj(f) From 5b93bcc2336b7d9e61df13152c6315c5af15c1f7 Mon Sep 17 00:00:00 2001 From: Julien Marabotto Date: Fri, 10 May 2024 09:32:23 +0200 Subject: [PATCH 07/10] enh: update implementation of X5 (cherry picked from commit 3d263bb36d74e21dc461987f4cf055af22f0dab8) --- nitransforms/io/x5.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/nitransforms/io/x5.py b/nitransforms/io/x5.py index b38ef9ab..11403c92 100644 --- a/nitransforms/io/x5.py +++ b/nitransforms/io/x5.py @@ -51,3 +51,19 @@ def from_filename(cls, filename): if str(filename).endswith(".h5"): with H5File(str(filename)) as f: return cls.from_h5obj(f) + +class X5LinearTransformArray(BaseLinearTransformList): + """A string-based structure for series of X5 linear transforms.""" + + _inner_type = X5LinearTransform + + @property + def xforms(self): + """Get the list of internal ITKLinearTransforms.""" + return self._xforms + + @xforms.setter + def xforms(self, value): + self._xforms = list(value) + for i, val in enumerate(self.xforms): + val["index"] = i \ No newline at end of file From b4b5af4b2baf095034a541b0f099f0935c1c43ff Mon Sep 17 00:00:00 2001 From: Julien Marabotto Date: Fri, 10 May 2024 14:49:03 +0200 Subject: [PATCH 08/10] enh: update implementation of X5 (cherry picked from commit a102f1e2bee031c55708dc6dc4e2598c94353a39) --- nitransforms/io/x5.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/nitransforms/io/x5.py b/nitransforms/io/x5.py index 11403c92..264df3d7 100644 --- a/nitransforms/io/x5.py +++ b/nitransforms/io/x5.py @@ -35,14 +35,9 @@ def __str__(self): return def to_filename(self, filename): - """Store this transform to a file with the appropriate format.""" + '''store this transform to a file with the X5 format''' sa = self.structarr - affine = np.array( - np.hstack( - (sa["parameters"][:3, :3].reshape(-1), sa["parameters"][:3, 3]) - )[..., np.newaxis], - dtype="f8", - ) + affine = '''some affine that will return a 4x4 array''' return @classmethod @@ -61,9 +56,3 @@ class X5LinearTransformArray(BaseLinearTransformList): def xforms(self): """Get the list of internal ITKLinearTransforms.""" return self._xforms - - @xforms.setter - def xforms(self, value): - self._xforms = list(value) - for i, val in enumerate(self.xforms): - val["index"] = i \ No newline at end of file From d34a6693408a4eaee8d70e72d77615db023d4e85 Mon Sep 17 00:00:00 2001 From: Julien Marabotto Date: Fri, 17 May 2024 16:17:51 +0200 Subject: [PATCH 09/10] enh: x5 implementation (cherry picked from commit e465332c8ec71b1a05aaa4cbb928b8a287586712) --- nitransforms/io/x5.py | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/nitransforms/io/x5.py b/nitransforms/io/x5.py index 264df3d7..72b08a0f 100644 --- a/nitransforms/io/x5.py +++ b/nitransforms/io/x5.py @@ -13,20 +13,10 @@ TransformFileError, ) -LPS = np.diag([-1, -1, 1, 1]) - -class X5LinearTransform(LinearParameters): +class X5Transform: """A string-based structure for X5 linear transforms.""" - template_dtype = np.dtype( - [ - ("type", "i4"), - ("index", "i4"), - ("parameters", "f8", (4, 4)), - ("offset", "f4", 3), # Center of rotation - ] - ) - dtype = template_dtype + _transform = None def __init__(self, parameters=None, offset=None): return @@ -34,25 +24,32 @@ def __init__(self, parameters=None, offset=None): def __str__(self): return - def to_filename(self, filename): - '''store this transform to a file with the X5 format''' - sa = self.structarr - affine = '''some affine that will return a 4x4 array''' - return - @classmethod def from_filename(cls, filename): """Read the struct from a X5 file given its path.""" if str(filename).endswith(".h5"): - with H5File(str(filename)) as f: - return cls.from_h5obj(f) + with H5File(str(filename), 'r') as hdf: + return cls.from_h5obj(hdf) + + @classmethod + def from_h5obj(cls, h5obj): + """Read the transformations in an X5 file.""" + xfm_list = list(h5obj.keys()) + + xfm = xfm_list["Transform"] + inv = xfm_list["Inverse"] + coords = xfm_list["Size"] + map = xfm_list["Mapping"] + + return xfm, inv, coords, map + class X5LinearTransformArray(BaseLinearTransformList): """A string-based structure for series of X5 linear transforms.""" - _inner_type = X5LinearTransform + _inner_type = X5Transform @property def xforms(self): - """Get the list of internal ITKLinearTransforms.""" + """Get the list of internal X5LinearTransforms.""" return self._xforms From b008d88c6e06d7fbf0d0ec26c6b3e8bffaa7aada Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Thu, 17 Jul 2025 11:21:08 +0200 Subject: [PATCH 10/10] fix: formatting and undefined variables --- nitransforms/io/__init__.py | 3 +- nitransforms/io/base.py | 126 ------------------------------------ nitransforms/io/x5.py | 88 +++++++++++-------------- 3 files changed, 38 insertions(+), 179 deletions(-) diff --git a/nitransforms/io/__init__.py b/nitransforms/io/__init__.py index 3627fd34..857f92f6 100644 --- a/nitransforms/io/__init__.py +++ b/nitransforms/io/__init__.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: """Read and write transforms.""" + from nitransforms.io import afni, fsl, itk, lta, x5 from nitransforms.io.base import TransformIOError, TransformFileError @@ -22,7 +23,7 @@ "fs": (lta, "FSLinearTransform"), "fsl": (fsl, "FSLLinearTransform"), "afni": (afni, "AFNILinearTransform"), - "x5": (x5, "X5Transform") + "x5": (x5, "X5LinearTransform"), } diff --git a/nitransforms/io/base.py b/nitransforms/io/base.py index 78c28827..3c923426 100644 --- a/nitransforms/io/base.py +++ b/nitransforms/io/base.py @@ -1,137 +1,11 @@ """Read/write linear transforms.""" from pathlib import Path import numpy as np -import nibabel as nb from nibabel import load as loadimg -import h5py - from ..patched import LabeledWrapStruct -def get_xfm_filetype(xfm_file): - path = Path(xfm_file) - ext = path.suffix - if ext == '.gz' and path.name.endswith('.nii.gz'): - return 'nifti' - - file_types = { - '.nii': 'nifti', - '.h5': 'hdf5', - '.x5': 'x5', - '.txt': 'txt', - '.mat': 'txt' - } - return file_types.get(ext, 'unknown') - -def gather_fields(x5=None, hdf5=None, nifti=None, shape=None, affine=None, header=None): - xfm_fields = { - "x5": x5, - "hdf5": hdf5, - "nifti": nifti, - "header": header, - "shape": shape, - "affine": affine - } - return xfm_fields - -def load_nifti(nifti_file): - nifti_xfm = nb.load(nifti_file) - xfm_data = nifti_xfm.get_fdata() - shape = nifti_xfm.shape - affine = nifti_xfm.affine - header = getattr(nifti_xfm, "header", None) - return gather_fields(nifti=xfm_data, shape=shape, affine=affine, header=header) - -def load_hdf5(hdf5_file): - storage = {} - - def get_hdf5_items(name, x5_root): - if isinstance(x5_root, h5py.Dataset): - storage[name] = { - 'type': 'dataset', - 'attrs': dict(x5_root.attrs), - 'shape': x5_root.shape, - 'data': x5_root[()] - } - elif isinstance(x5_root, h5py.Group): - storage[name] = { - 'type': 'group', - 'attrs': dict(x5_root.attrs), - 'members': {} - } - - with h5py.File(hdf5_file, 'r') as f: - f.visititems(get_hdf5_items) - if storage: - hdf5_storage = {'hdf5': storage} - return hdf5_storage - -def load_x5(x5_file): - load_hdf5(x5_file) - -def load_mat(mat_file): - affine_matrix = np.loadtxt(mat_file) - affine = nb.affines.from_matvec(affine_matrix[:,:3], affine_matrix[:,3]) - return gather_fields(affine=affine) - -def xfm_loader(xfm_file): - loaders = { - 'nifti': load_nifti, - 'hdf5': load_hdf5, - 'x5': load_x5, - 'txt': load_mat, - 'mat': load_mat - } - xfm_filetype = get_xfm_filetype(xfm_file) - loader = loaders.get(xfm_filetype) - if loader is None: - raise ValueError(f"Unsupported file type: {xfm_filetype}") - return loader(xfm_file) - -def to_filename(self, filename, fmt="X5"): - """Store the transform in BIDS-Transforms HDF5 file format (.x5).""" - 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 - -def _to_hdf5(self, x5_root): - """Serialize this object into the x5 file format.""" - transform_group = x5_root.create_group("TransformGroup") - - """Group '0' containing Affine transform""" - transform_0 = transform_group.create_group("0") - - transform_0.attrs["Type"] = "Affine" - transform_0.create_dataset("Transform", data=self._affine) - transform_0.create_dataset("Inverse", data=np.linalg.inv(self._affine)) - - metadata = {"key": "value"} - transform_0.attrs["Metadata"] = str(metadata) - - """sub-group 'Domain' contained within group '0' """ - domain_group = transform_0.create_group("Domain") - #domain_group.attrs["Grid"] = self._grid - #domain_group.create_dataset("Size", data=_as_homogeneous(self._reference.shape)) - #domain_group.create_dataset("Mapping", data=self.mapping) - -def _from_x5(self, x5_root): - variables = {} - - x5_root.visititems(lambda name, x5_root: loader(name, x5_root, variables)) - - _transform = variables["TransformGroup/0/Transform"] - _inverse = variables["TransformGroup/0/Inverse"] - _size = variables["TransformGroup/0/Domain/Size"] - _mapping = variables["TransformGroup/0/Domain/Mapping"] - - return _transform, _inverse, _size, _map - - class TransformIOError(IOError): """General I/O exception while reading/writing transforms.""" diff --git a/nitransforms/io/x5.py b/nitransforms/io/x5.py index 72b08a0f..419575f9 100644 --- a/nitransforms/io/x5.py +++ b/nitransforms/io/x5.py @@ -1,55 +1,39 @@ +# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- +# vi: set ft=python sts=4 ts=4 sw=4 et: +### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## +# +# See COPYING file distributed along with the NiBabel package for the +# copyright and license terms. +# +### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## """Read/write x5 transforms.""" -import warnings + import numpy as np -from scipy.io import loadmat as _read_mat, savemat as _save_mat from h5py import File as H5File -from nibabel import Nifti1Header, Nifti1Image -from nibabel.affines import from_matvec -from nitransforms.io.base import ( - BaseLinearTransformList, - DisplacementsField, - LinearParameters, - TransformIOError, - TransformFileError, -) - -class X5Transform: - """A string-based structure for X5 linear transforms.""" - - _transform = None - - def __init__(self, parameters=None, offset=None): - return - - def __str__(self): - return - - @classmethod - def from_filename(cls, filename): - """Read the struct from a X5 file given its path.""" - if str(filename).endswith(".h5"): - with H5File(str(filename), 'r') as hdf: - return cls.from_h5obj(hdf) - - @classmethod - def from_h5obj(cls, h5obj): - """Read the transformations in an X5 file.""" - xfm_list = list(h5obj.keys()) - - xfm = xfm_list["Transform"] - inv = xfm_list["Inverse"] - coords = xfm_list["Size"] - map = xfm_list["Mapping"] - - return xfm, inv, coords, map - - -class X5LinearTransformArray(BaseLinearTransformList): - """A string-based structure for series of X5 linear transforms.""" - - _inner_type = X5Transform - - @property - def xforms(self): - """Get the list of internal X5LinearTransforms.""" - return self._xforms + + +def to_filename(fname, xfm, moving=None): + """Store the transform in BIDS-Transforms HDF5 file format (.x5).""" + with H5File(fname, "w") as out_file: + out_file.attrs["Format"] = "X5" + out_file.attrs["Version"] = np.uint16(1) + x5_root = out_file.create_group("/0") + + # Serialize this object into the x5 file format. + transform_group = x5_root.create_group("TransformGroup") + + # Group '0' containing Affine transform + transform_0 = transform_group.create_group("0") + + transform_0.attrs["Type"] = "Affine" + transform_0.create_dataset("Transform", data=xfm.matrix) + transform_0.create_dataset("Inverse", data=~xfm) + + metadata = {"key": "value"} + transform_0.attrs["Metadata"] = str(metadata) + + # sub-group 'Domain' contained within group '0' + transform_0.create_group("Domain") + # domain_group.attrs["Grid"] = self._grid + # domain_group.create_dataset("Size", data=_as_homogeneous(self._reference.shape)) + # domain_group.create_dataset("Mapping", data=self.mapping)