Skip to content

Commit d5c8a25

Browse files
authored
Merge pull request #203 from feilong/surface
SurfaceTransform class
2 parents d6170fb + f69faaf commit d5c8a25

File tree

8 files changed

+1004
-1
lines changed

8 files changed

+1004
-1
lines changed

docs/_api/surface.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
==================
2+
Surface Transforms
3+
==================
4+
5+
.. automodule:: nitransforms.surface
6+
:members:

docs/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ Information on specific functions, classes, and methods for developers.
1010
_api/linear
1111
_api/manip
1212
_api/nonlinear
13+
_api/surface
1314
_api/interp
1415
_api/patched

nitransforms/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
1717
transform
1818
"""
19-
from . import linear, manip, nonlinear
19+
from . import linear, manip, nonlinear, surface
2020
from .linear import Affine, LinearTransformsMapping
2121
from .nonlinear import DenseFieldTransform
2222
from .manip import TransformChain
@@ -37,6 +37,7 @@
3737
__copyright__ = "Copyright (c) 2021 The NiPy developers"
3838

3939
__all__ = [
40+
"surface",
4041
"linear",
4142
"manip",
4243
"nonlinear",

nitransforms/base.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from nibabel import funcs as _nbfuncs
1616
from nibabel.nifti1 import intent_codes as INTENT_CODES
1717
from nibabel.cifti2 import Cifti2Image
18+
import nibabel as nb
1819

1920
EQUALITY_TOL = 1e-5
2021

@@ -88,6 +89,76 @@ def shape(self):
8889
return self._shape
8990

9091

92+
class SurfaceMesh(SampledSpatialData):
93+
"""Class to represent surface meshes."""
94+
95+
__slots__ = ["_triangles"]
96+
97+
def __init__(self, dataset):
98+
"""Create a sampling reference."""
99+
self._shape = None
100+
101+
if isinstance(dataset, SurfaceMesh):
102+
self._coords = dataset._coords
103+
self._triangles = dataset._triangles
104+
self._ndim = dataset._ndim
105+
self._npoints = dataset._npoints
106+
self._shape = dataset._shape
107+
return
108+
109+
if isinstance(dataset, (str, Path)):
110+
dataset = _nbload(str(dataset))
111+
112+
if hasattr(dataset, "numDA"): # Looks like a Gifti file
113+
_das = dataset.get_arrays_from_intent(INTENT_CODES["pointset"])
114+
if not _das:
115+
raise TypeError(
116+
"Input Gifti file does not contain reference coordinates."
117+
)
118+
self._coords = np.vstack([da.data for da in _das])
119+
_tris = dataset.get_arrays_from_intent(INTENT_CODES["triangle"])
120+
self._triangles = np.vstack([da.data for da in _tris])
121+
self._npoints, self._ndim = self._coords.shape
122+
self._shape = self._coords.shape
123+
return
124+
125+
if isinstance(dataset, Cifti2Image):
126+
raise NotImplementedError
127+
128+
raise ValueError("Dataset could not be interpreted as an irregular sample.")
129+
130+
def check_sphere(self, tolerance=1.001):
131+
"""Check sphericity of surface.
132+
Based on https://github.com/Washington-University/workbench/blob/\
133+
7ba3345d161d567a4b628ceb02ab4471fc96cb20/src/Files/SurfaceResamplingHelper.cxx#L503
134+
"""
135+
dists = np.linalg.norm(self._coords, axis=1)
136+
return (dists.min() * tolerance) > dists.max()
137+
138+
def set_radius(self, radius=100):
139+
if not self.check_sphere():
140+
raise ValueError("You should only set the radius on spherical surfaces.")
141+
dists = np.linalg.norm(self._coords, axis=1)
142+
self._coords = self._coords * (radius / dists).reshape((-1, 1))
143+
144+
@classmethod
145+
def from_arrays(cls, coordinates, triangles):
146+
darrays = [
147+
nb.gifti.GiftiDataArray(
148+
coordinates.astype(np.float32),
149+
intent=nb.nifti1.intent_codes['NIFTI_INTENT_POINTSET'],
150+
datatype=nb.nifti1.data_type_codes['NIFTI_TYPE_FLOAT32'],
151+
),
152+
nb.gifti.GiftiDataArray(
153+
triangles.astype(np.int32),
154+
intent=nb.nifti1.intent_codes['NIFTI_INTENT_TRIANGLE'],
155+
datatype=nb.nifti1.data_type_codes['NIFTI_TYPE_INT32'],
156+
),
157+
]
158+
gii = nb.gifti.GiftiImage(darrays=darrays)
159+
return cls(gii)
160+
161+
91162
class ImageGrid(SampledSpatialData):
92163
"""Class to represent spaces of gridded data (images)."""
93164

0 commit comments

Comments
 (0)