Source code for mrinufft.density.geometry_based

"""Compute density compensation weights using geometry-based methods."""

import numpy as np
from scipy.spatial import Voronoi

from .utils import flat_traj, normalize_weights, register_density


def _vol3d(points):
    """Compute the volume of a convex 3D polygon.

    Parameters
    ----------
    points: array_like
        array of shape (N, 3) containing the coordinates of the points.

    Returns
    -------
    volume: float
    """
    base_point = points[0]
    A = points[:-2] - base_point
    B = points[1:-1] - base_point
    C = points[2:] - base_point
    return np.sum(np.abs(np.dot(np.cross(B, C), A.T))) / 6.0


def _vol2d(points):
    """Compute the area of a convex 2D polygon.

    Parameters
    ----------
    points: array_like
        array of shape (N, 2) containing the coordinates of the points.

    Returns
    -------
    area: float
    """
    # https://stackoverflow.com/questions/451426/how-do-i-calculate-the-area-of-a-2d-polygon
    area = 0
    for i in range(1, len(points) - 1):
        area += points[i, 0] * (points[i + 1, 1] - points[i - 1, 1])
    area += points[-1, 0] * (points[0, 1] - points[-2, 1])
    # we actually don't provide the last point, so we have to do another edge case.
    area += points[0, 0] * (points[1, 1] - points[-1, 1])
    return abs(area) / 2.0


[docs] @register_density @flat_traj def voronoi_unique(traj, *args, **kwargs): """Estimate density compensation weight using voronoi parcellation. This assume unicity of the point in the kspace. Parameters ---------- kspace: array_like array of shape (M, 2) or (M, 3) containing the coordinates of the points. *args, **kwargs: Dummy arguments to be compatible with other methods. Returns ------- wi: array_like array of shape (M,) containing the density compensation weights. """ M = traj.shape[0] if traj.shape[1] == 2: vol = _vol2d else: vol = _vol3d wi = np.zeros(M) v = Voronoi(traj) for mm in range(M): idx_vertices = v.regions[v.point_region[mm]] if np.all([i != -1 for i in idx_vertices]): wi[mm] = vol(v.vertices[idx_vertices]) else: wi[mm] = np.inf # some voronoi cell are considered closed, but have a too big area. # (They are closing near infinity). # we classify them as open cells as well. outlier_thresh = np.percentile(wi, 95) wi[wi > outlier_thresh] = np.inf # For edge point (infinite voronoi cells) we extrapolate from neighbours # Initial implementation in Jeff Fessler's MIRT rho = np.sum(traj**2, axis=1) igood = (rho > 0.6 * np.max(rho)) & ~np.isinf(wi) if len(igood) < 10: print("dubious extrapolation with", len(igood), "points") poly = np.polynomial.Polynomial.fit(rho[igood], wi[igood], 3) wi[np.isinf(wi)] = poly(rho[np.isinf(wi)]) return wi
[docs] @register_density @flat_traj def voronoi(traj, *args, **kwargs): """Estimate density compensation weight using voronoi parcellation. In case of multiple point in the center of kspace, the weight is split evenly. Parameters ---------- traj: array_like array of shape (M, 2) or (M, 3) containing the coordinates of the points. *args, **kwargs: Dummy arguments to be compatible with other methods. References ---------- Based on the MATLAB implementation in MIRT: https://github.com/JeffFessler/mirt/blob/main/mri/ir_mri_density_comp.m """ # deduplication only works for the 0,0 coordinate !! i0 = np.sum(np.abs(traj), axis=1) == 0 if np.any(i0): i0f = np.where(i0) i0f = i0f[0] i0[i0f] = False wi = np.zeros(len(traj)) wi[~i0] = voronoi_unique(traj[~i0]) i0[i0f] = True wi[i0] = wi[i0f] / np.sum(i0) else: wi = voronoi_unique(traj) return 1 / normalize_weights(wi)
[docs] @register_density @flat_traj def cell_count(traj, shape, osf=1.0): """ Compute the number of points in each cell of the grid. Parameters ---------- traj: array_like array of shape (M, 2) or (M, 3) containing the coordinates of the points. shape: tuple shape of the grid. osf: float oversampling factor for the grid. default 1 Returns ------- weights: array_like array of shape (M,) containing the density compensation weights. """ bins = [np.linspace(-0.5, 0.5, int(osf * s) + 1) for s in shape] h, edges = np.histogramdd(traj, bins) if len(shape) == 2: hsum = [np.sum(h, axis=1).astype(int), np.sum(h, axis=0).astype(int)] else: hsum = [ np.sum(h, axis=(1, 2)).astype(int), np.sum(h, axis=(0, 2)).astype(int), np.sum(h, axis=(0, 1)).astype(int), ] # indices of ascending coordinate in each dimension. locs_sorted = [np.argsort(traj[:, i]) for i in range(len(shape))] weights = np.ones(len(traj)) set_xyz = [[], [], []] for i in range(len(hsum)): ind = 0 for binsize in hsum[i]: s = set(locs_sorted[i][ind : ind + binsize]) if s: set_xyz[i].append(s) ind += binsize for sx in set_xyz[0]: for sy in set_xyz[1]: sxy = sx.intersection(sy) if not sxy: continue if len(shape) == 2: weights[list(sxy)] = len(sxy) continue for sz in set_xyz[2]: sxyz = sxy.intersection(sz) if sxyz: weights[list(sxyz)] = len(sxyz) return normalize_weights(weights)