Menu

Deep learning for NeuroImaging in Python.

Source code for surfify.utils.coord

# -*- coding: utf-8 -*-
##########################################################################
# NSAp - Copyright (C) CEA, 2021
# Distributed under the terms of the CeCILL-B license, as published by
# the CEA-CNRS-INRIA. Refer to the LICENSE file or to
# http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
# for details.
##########################################################################

"""
Coordinate system tools.
"""

# Imports
import math
import warnings
import itertools
import numpy as np
from scipy.interpolate import griddata, NearestNDInterpolator
from scipy.spatial import transform


[docs] def cart2sph(x, y, z): """ Cartesian to spherical coordinate transform. See Also -------- sph2cart, text2grid, grid2text Parameters ---------- x: float or array_like. x-component of Cartesian coordinates y: float or array_like. y-component of Cartesian coordinates z: float or array_like. z-component of Cartesian coordinates Returns ------- alpha: float or `numpy.ndarray` Azimuth angle in radiants. The value of the angle is in the range [-pi pi]. beta: float or `numpy.ndarray` Elevation angle in radiants. The value of the angle is in the range [-pi/2, pi/2]. r: float or `numpy.ndarray` Radius. """ alpha = np.arctan2(y, x) beta = np.arctan2(z, np.sqrt(x**2 + y**2)) r = np.sqrt(x**2 + y**2 + z**2) return alpha, beta, r
[docs] def sph2cart(alpha, beta, r): """ Spherical to cartesian coordinate transform. See Also -------- cart2sph, text2grid, grid2text Parameters ---------- alpha: float or array_like Azimuth angle in radiants. beta: float or array_like Elevation angle in radiants. r: float or array_like Radius. Returns ------- x: float or `numpy.ndarray` x-component of Cartesian coordinates y: float or `numpy.ndarray` y-component of Cartesian coordinates z: float or `numpy.ndarray` z-component of Cartesian coordinates """ x = r * np.cos(alpha) * np.cos(beta) y = r * np.sin(alpha) * np.cos(beta) z = r * np.sin(beta) return x, y, z
[docs] def text2grid(vertices, texture, resx=192, resy=192): """ Convert a texture onto a spherical surface into an image by evenly resampling the spherical surface with respect to sin(e) and a, where e and a are elevation and azimuth, respectively. Nearest-neighbor interpolation is used to convert data from the 3-D surface to the 2-D grid. See Also -------- grid2text Examples -------- >>> from surfify.utils import icosahedron, text2grid >>> from surfify.datasets import make_classification >>> import matplotlib.pyplot as plt >>> from surfify.plotting import plot_trisurf >>> ico2_verts, ico2_tris = icosahedron(order=2) >>> X, y = make_classification(ico2_verts, n_samples=1, n_classes=3, scale=1, seed=42) >>> y_grid = text2grid(ico2_verts, y) >>> plt.imshow(y_grid) >>> plt.show() Parameters ---------- vertices: array (N, 3) x, y, z coordinates of an icosahedron. texture: array (N, ) the input icosahedron texture. resx: int, default 192 the generated image number of samples in the x direction. resy: int, default 192 the generated image number of samples in the y direction. Returns ------- proj: array (resx, resy) the projecteed texture. """ azimuth, elevation, radius = cart2sph(*vertices.T) points = np.stack((azimuth, np.sin(elevation))).T grid_x, grid_y = np.mgrid[-np.pi:np.pi:resx * 1j, -1:1:resy * 1j] return griddata(points, texture, (grid_x, grid_y), method="nearest")
[docs] def grid2text(vertices, proj): """ Convert a grided-texture into a spherical surface. Nearest-neighbor interpolation is used to convert data from the 2-D grid to the 3-D surface. See Also -------- text2grid Examples -------- >>> from surfify.utils import icosahedron, grid2text >>> import matplotlib.pyplot as plt >>> from surfify.plotting import plot_trisurf >>> ico2_verts, ico2_tris = icosahedron(order=2) >>> y_grid = np.zeros((192, 192), dtype=int) >>> y_grid[:, :96] = 1 >>> y = grid2text(ico2_verts, y_grid) >>> plot_trisurf(ico2_verts, triangles=ico2_tris, texture=y, is_label=True) >>> plt.show() Parameters ---------- vertices: array (N, 3) x, y, z coordinates of an icosahedron. proj: array (resx, resy) the grided-texture. Returns ------- texture: array (N, ) the input icosahedron texture. """ azimuth, elevation, radius = cart2sph(*vertices.T) points = np.stack((azimuth, np.sin(elevation))).T resx, resy = proj.shape grid_x, grid_y = np.mgrid[-np.pi:np.pi:resx * 1j, -1:1:resy * 1j] grid_points = np.stack((grid_x.flatten(), grid_y.flatten())).T proj_values = proj.flatten() interp = NearestNDInterpolator(grid_points, proj_values) return interp(points)
[docs] def ico2ico(vertices, ref_vertices): """ Find a mapping between two icosahedrons: a simple rotation is estimated by identifying 4 vertices with same coordinates up to their signs and then finding the best rotation using permutations. See Also -------- text2ico Examples -------- >>> from surfify.utils import icosahedron, ico2ico >>> import matplotlib.pyplot as plt >>> from surfify.plotting import plot_trisurf >>> ico2_verts, ico2_tris = icosahedron(order=2) >>> ico2_std_verts, ico2_std_tris = icosahedron(order=2, standard_ico=True) >>> rotation = ico2ico(ico2_verts, ico2_std_verts) >>> fig, ax = plt.subplots(1, 1, subplot_kw={ "projection": "3d", "aspect": "auto"}, figsize=(10, 10)) >>> plot_trisurf(ico2_std_verts, triangles=ico2_std_tris, colorbar=False, fig=fig, ax=ax, alpha=0.3, edgecolors="blue") >>> plot_trisurf(rotation.apply(ico2_verts), triangles=ico2_tris, colorbar=False, fig=fig, ax=ax, alpha=0.3, edgecolors="green") >>> plt.show() Parameters ---------- vertices: array (N, 3) the vertices to project. ref_vertices: array (N, 3) the reference/target vertices. Returns ------- rotation: scipy.spatial.tranform.Rotation the rotation that transforms the vertices to the reference. """ if len(vertices) != len(ref_vertices): raise ValueError("Input vertices must be of the same order.") vertices_of_interest = [] for _vertices in (vertices, ref_vertices): for idx in range(len(_vertices)): coords_of_interest = _vertices[idx] idx_of_interest = ( np.abs(_vertices) == np.abs(coords_of_interest)).all(axis=1) if idx_of_interest.sum() == 4: vertices_of_interest.append(_vertices[idx_of_interest]) break permutations = itertools.permutations(range(4)) n_permutations = math.factorial(4) rmse = 1000 it = 0 best_rmse = rmse best_rotation = None while rmse > 0 and it < n_permutations: it += 1 order = np.array(next(permutations)) with warnings.catch_warnings(): warnings.simplefilter("ignore", category=UserWarning) rotation, rmse = transform.Rotation.align_vectors( vertices_of_interest[1], vertices_of_interest[0][order]) if rmse < best_rmse: best_rmse = rmse best_rotation = rotation if it == n_permutations and best_rmse > 0: warnings.warn( "A proper mapping between the two icosahedrons could not be " "found. The closest rotation has a rmse of {0}.".format(rmse)) return best_rotation
[docs] def text2ico(texture, vertices, ref_vertices, atol=1e-4): """ Projects a texture associated to an icosahedron onto an other one. See Also -------- ico2ico Examples -------- >>> from surfify.utils import icosahedron, text2ico >>> from surfify.datasets import make_classification >>> import matplotlib.pyplot as plt >>> from surfify.plotting import plot_trisurf >>> ico2_verts, ico2_tris = icosahedron(order=2) >>> ico2_std_verts, ico2_std_tris = icosahedron(order=2, standard_ico=True) >>> X, y = make_classification(ico2_verts, n_samples=1, n_classes=3, scale=1, seed=42) >>> y_std = text2ico(y, ico2_verts, ico2_std_verts) >>> plot_trisurf(ico2_std_verts, triangles=ico2_std_tris, texture=y_std, is_label=True) >>> plt.show() Parameters ---------- texture: array (N, K) the input texture to project. vertices: array (N, 3) the vertices corresponding to the input texture. ref_vertices: array (N, 3) the reference/target vertices. atol: float, default 1e-4 tolerance when matching the vertices. Returns ------- texture: array (N, K) the texture projected on the reference icosahedron. """ rotation = ico2ico(vertices, ref_vertices) new_vertices = rotation.apply(vertices) new_order = find_corresponding_order( new_vertices, ref_vertices, atol=atol, axis=0) return texture[new_order]
[docs] def find_corresponding_order(array, ref_array, atol=1e-4, axis=0): """ Find unique match between two arrays: assume that arrays are the same up to a permutation. Parameters ---------- array: array (N, \*) the array to find the corresponding order for. ref_array: array (N, \*) the reference array on which the order is base. atol: float, default 1e-4 tolerance when matching the values. axis: int, default 0 axis along which to permute ordering. Returns ------- new_order: array (N, ) the indices to match the input array with the reference array. """ array = np.asarray(array) ref_array = np.asarray(ref_array) if not np.array_equal(array.shape, ref_array.shape): raise ValueError("The arrays must be permuted versions of each other " "and must have the same shape.") new_order = [] other_dims = list(range(array.ndim)) other_dims.remove(axis) other_dims = tuple(other_dims) for idx in range(len(array)): match = np.isclose(array, np.take(ref_array, idx, axis=axis), atol=atol).all(other_dims) idx = np.where(match)[0] if len(idx) != 1: raise ValueError( "The arrays must be permuted versions of each other and an " "element in the reference array was not found or found " "multiple times.") new_order.append(idx[0]) return new_order

Follow us

© 2025, nidl developers