Ejemplo n.º 1
0
def Analytical_Wasserstein(f,g):
    """
    Computes the analytical (exact) solution for the 2-Wasserstein distance between Gaussian distributions of any dimension. Note that while the inputs are Gaussian Mixture Models,
    these MUST have one component for the result to be correct.
    Inputs:
        f: source distribution (Gaussian_Mixture)
        g: target distribution (Gaussian_Mixture)
    Outputs:
        W2: exact 2-Wasserstein distance between f and g (float)
    """
    # firstly, check they are both 1-component mixtures
    if not ((f.n == 1) and (g.n == 1)):
        raise Exception('mixtures have multiple components: only one is allowed')
    else:
        # firstly, the mean (shift) component
        mean_dist = np.linalg.norm(f.m[0,:] - g.m[0,:])**2

        # now the covariance (reshape) component
        A1 = f.cov[0,:,:]
        B = g.cov[0,:,:]
        A2 = fmp(A1,0.5)
        C = fmp(A2 @ B @ A2, 0.5)
        bures = np.trace(A1 + B - 2 * C)

        # add them to get total work analytical
        W2 = mean_dist + bures

        return W2
Ejemplo n.º 2
0
def _correlation_alignment(s: daskarr, t: daskarr, nthreads: int) -> daskarr:
    from scipy.linalg import fractional_matrix_power as fmp
    from threadpoolctl import threadpool_limits

    s_cov = show_progress(_cov_diaged(s), f"CORAL: Computing source covariance", nthreads)
    t_cov = show_progress(_cov_diaged(t), f"CORAL: Computing target covariance", nthreads)
    logger.info("Calculating fractional power of covariance matrices. This might take a while... ")
    with threadpool_limits(limits=nthreads):
        a_coral = np.dot(fmp(s_cov, -0.5), fmp(t_cov, 0.5))
    logger.info("Fractional power calculation complete")
    return daskarr.dot(s, a_coral)
def qcbOptimalDecomposition(r1, r2, theta, s=1):
    """
    Experimenting to find optimal reverse chernoff quantity
    """
    rho = fmp(np.matrix([[1 / 2, 0], [0, 1 / 2]]), s)
    sigma = fmp(
        np.matrix([[1 / 2, 2 * r2 * np.sin(theta)],
                   [2 * r2 * np.sin(theta), 1 / 2]]), s)
    tau = fmp(
        np.matrix([[1 + 4 * r1 * np.cos(theta), 0],
                   [0, 1 - 4 * r1 * np.cos(theta)]]), 1 - s)
    return np.trace(logm(np.matmul(tau, sigma, tau)))
Ejemplo n.º 4
0
def MapstoRef(f):
    """
    Computes the optimal transport maps from each component of f to the intermediate Gaussian 
    distribution with mean zero vector and identity covariance matrix.
    Inputs:
        f: the GMM that is being mapped from (generally the prior) (Gaussian_Mixture)
    Outputs:
        ToRef: the collection of n affine maps that optimally map the corresponding component (Affine_Maps)
    """
    #so given a mixture, find each of the n alpha maps
    n = f.n
    d = f.d

    A = np.zeros([n, d, d])
    L = np.zeros([n, d, d])
    b = np.zeros([n, d])

    for i in range(n):
        A[i, :, :] = fmp(f.cov[i, :, :], -1 / 2)
        L[i, :, :] = np.linalg.cholesky(A[i, :, :])
        b[i, :] = -A[i, :, :] @ f.m[i, :]

    ToRef = Affine_Maps(L, b)

    return ToRef
Ejemplo n.º 5
0
    def __init__(self,f,g):
        if f.d != g.d:
            raise Exception('source and target distributions are of different dimension')
        
        self.mean = np.zeros([f.n,g.n,f.d])
        self.cov = np.zeros([f.n,g.n,f.d,f.d])
        for i in range(f.n):
            for j in range(g.n):
                # firstly, derivative w.r.t. mean vectors
                self.mean[i,j,:] = 2 * (f.m[i,:] - g.m[j,:])

                # now the more complicated case w.r.t. covariance matrices
                A1 = fmp(f.cov[i,:,:],-0.5)
                A2 = fmp(f.cov[i,:,:],0.5)
                B = g.cov[j,:,:]
                C = fmp(A2 @ B @ A2,0.5)
                D = A1 @ C @ A1
                self.cov[i,j,:,:] = np.eye(f.d) - D
Ejemplo n.º 6
0
def Inv_Alpha_Maps(f):
    """
    Computes the optimal transport maps from the intermediate distribution (Gaussian with zero mean 
    and identity covariance) to the GMM defined by f.
    Inputs:
        f: the mixture that is being mapped to from the intermediate (Gaussian_Mixture)
    Outputs:
        Inv_Alpha: the n affine maps that take the intermediate Gaussian to each of the mixture
        components of f (Affine_Maps)
    """
    n = f.n
    d = f.d

    A_inv = np.zeros([n, d, d])
    b_inv = np.zeros([n, d])

    for i in range(n):
        A_inv[i, :, :] = fmp(f.cov[i, :, :], 1 / 2)
        b_inv[i, :] = f.m[i, :]

    Inv_Alpha = Affine_Maps(A_inv, b_inv)

    return Inv_Alpha
Ejemplo n.º 7
0
def grog(kx,
         ky,
         k,
         N,
         M,
         Gx,
         Gy,
         precision=2,
         radius=.75,
         Dx=None,
         Dy=None,
         coil_axis=-1,
         ret_image=False,
         ret_dicts=False,
         flip_flop=False):
    '''GRAPPA operator gridding.

    Parameters
    ----------
    kx, ky : array_like
        k-space coordinates (kx, ky) of measured data k.  kx, ky
        should each be a 1D array.
    k : array_like
        Measured  k-space data at points (kx, ky).
    N, M : int
        Desired resolution of Cartesian grid.
    Gx, Gy : array_like
        Unit GRAPPA operators.
    precision : int, optional
        Number of decimal places to round fractional matrix powers to.
    radius : float, optional
        Radius of ball in k-space to from Cartesian targets from
        which to select source points.
    Dx, Dy : dict, optional
        Dictionaries of precomputed fractional matrix powers.
    coil_axis : int, optional
        Axis holding coil data.
    ret_image : bool, optional
        Return image space result instead of k-space.
    ret_dicts : bool, optional
        Return dictionaries of fractional matrix powers.
    flip_flop : bool, optional
        Randomly shift the order of Gx and Gy application to achieve
        commutativity on average.

    Returns
    -------
    res : array_like
        Cartesian gridded k-space (or image).
    Dx, Dy : dict, optional
        Fractional matrix power dictionary for both Gx and Gy.

    Notes
    -----
    Implements the GROG algorithm as described in [1]_.

    References
    ----------
    .. [1] Seiberlich, Nicole, et al. "Self‐calibrating GRAPPA
           operator gridding for radial and spiral trajectories."
           Magnetic Resonance in Medicine: An Official Journal of the
           International Society for Magnetic Resonance in Medicine
           59.4 (2008): 930-935.
    '''

    # Coils to the back
    k = np.moveaxis(k, coil_axis, -1)
    _ns, nc = k.shape[:]

    # We have samples at (kx, ky).  We want new samples on a
    # Cartesian grid at, say, (tx, ty). Let's also oversample by a
    # factor of 2:
    N, M = 2 * N, 2 * M
    tx, ty = np.meshgrid(np.linspace(np.min(kx), np.max(kx), N),
                         np.linspace(np.min(ky), np.max(ky), M))
    tx, ty = tx.flatten(), ty.flatten()

    # We only want to do work inside the region of support: estimate
    # as a circle for now, works well with radial
    outside = np.argwhere(np.sqrt(tx**2 + ty**2) > np.max(kx)).squeeze()
    inside = np.argwhere(np.sqrt(tx**2 + ty**2) <= np.max(kx)).squeeze()
    tx = np.delete(tx, outside)
    ty = np.delete(ty, outside)
    txy = np.concatenate((tx[:, None], ty[:, None]), axis=-1)

    # Grid
    kxy = np.concatenate((kx[:, None], ky[:, None]), axis=-1)
    kdtree = cKDTree(kxy)
    idx = kdtree.query_ball_point(txy, r=radius)
    res = np.zeros((N * M, nc), dtype=k.dtype)

    t0 = time()
    key_x, key_y = grog_powers(tx, ty, kx, ky, idx, precision)
    print('Took %g seconds to find required powers' % (time() - t0))

    # If we have provided dictionaries, whitle down the work to only
    # those powers not already computed
    t0 = time()
    if Dx:
        key_x = key_x - set(Dx.keys())
    else:
        Dx = {}
    if Dy:
        key_y = key_y - set(Dy.keys())
    else:
        Dy = {}

    # Precompute deficient matrix powers
    for key0 in tqdm(key_x, leave=False, desc='Dx'):
        Dx[np.abs(key0)] = fmp(Gx, np.abs(key0))
        if np.sign(key0) < 0:
            Dx[key0] = np.linalg.pinv(Dx[np.abs(key0)])
    for key0 in tqdm(key_y, leave=False, desc='Dy'):
        Dy[np.abs(key0)] = fmp(Gy, np.abs(key0))
        if np.sign(key0) < 0:
            Dy[key0] = np.linalg.pinv(Dy[np.abs(key0)])
    print('Took %g seconds to precompute fractional matrix powers' %
          (time() - t0))

    # res is modified inplace
    grog_gridding(tx, ty, kx, ky, k, idx, res, inside.astype(np.uint32), Dx,
                  Dy, precision)

    # Remove the oversampling factor and return in kspace
    N4, M4 = int(N / 4), int(M / 4)
    ax = (0, 1)
    im = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(res.reshape((N, M, nc),
                                                                   order='F'),
                                                       axes=ax),
                                      axes=ax),
                         axes=ax)[N4:-N4, M4:-M4, :]
    if ret_image:
        retVal = im
    else:
        retVal = np.fft.ifftshift(np.fft.fft2(np.fft.fftshift(im, axes=ax),
                                              axes=ax),
                                  axes=ax)

    # If the user asked for the precomputed dictionaries back, add
    # them to the tuple of returned values
    if ret_dicts:
        retVal = (retVal, Dx, Dy)

    return retVal
Ejemplo n.º 8
0
def grog(kx,
         ky,
         k,
         N,
         M,
         Gx,
         Gy,
         precision=2,
         radius=.75,
         Dx=None,
         Dy=None,
         coil_axis=-1,
         ret_image=False,
         ret_dicts=False,
         use_primefac=False,
         remove_os=True,
         inverse=False):
    '''GRAPPA operator gridding.

    Parameters
    ----------
    kx, ky : array_like
        k-space coordinates (kx, ky) of measured data k.  kx, ky
        should each be a 1D array.  Must both be either float or
        double.
    k : array_like
        Measured  k-space data at points (kx, ky).
    N, M : int
        Desired resolution of Cartesian grid.
    Gx, Gy : array_like
        Unit GRAPPA operators.
    precision : int, optional
        Number of decimal places to round fractional matrix powers to.
    radius : float, optional
        Radius of ball in k-space to from Cartesian targets from
        which to select source points.
    Dx, Dy : dict, optional
        Dictionaries of precomputed fractional matrix powers.
    coil_axis : int, optional
        Axis holding coil data.
    ret_image : bool, optional
        Return image space result instead of k-space.
    ret_dicts : bool, optional
        Return dictionaries of fractional matrix powers.
    use_primefac : bool, optional
        Use prime factorization to speed-up fractional matrix
        power precomputations.
    remove_os : bool, optional
        Remove oversampling factor.
    inverse : bool, optional
        Do the inverse gridding operation, i.e., Cartesian points to
        (kx, ky).

    Returns
    -------
    res : array_like
        Cartesian gridded k-space (or image).
    Dx, Dy : dict, optional
        Fractional matrix power dictionary for both Gx and Gy.

    Raises
    ------
    AssertionError
        When (kx, ky) have different types.
    AssertionError
        When (kx, ky) and k do not have matching types, i.e.,
        if (kx, ky) are float32, k must be complex64.

    Notes
    -----
    Implements the GROG algorithm as described in [1]_.

    References
    ----------
    .. [1] Seiberlich, Nicole, et al. "Self‐calibrating GRAPPA
           operator gridding for radial and spiral trajectories."
           Magnetic Resonance in Medicine: An Official Journal of the
           International Society for Magnetic Resonance in Medicine
           59.4 (2008): 930-935.
    '''

    # Make sure types are consistent before calling grog funcs
    assert kx.dtype == ky.dtype, (
        '(kx, ky) must both be either double or float!')
    assert (k.dtype == np.complex64 if kx.dtype == np.float32 else k.dtype
            == np.complex128), ('(kx, ky) and k must have matching types!')

    # Coils to the back
    k = np.moveaxis(k, coil_axis, -1)
    _ns, nc = k.shape[:]

    if not inverse:
        # We have samples at (kx, ky).  We want new samples on a
        # Cartesian grid at, say, (tx, ty). Let's also oversample:
        N, M = 2 * N, 2 * M

    # Create the target grid (or source grid for inverse gridding)
    tx, ty = np.meshgrid(
        np.linspace(np.min(kx), np.max(kx), N, dtype=kx.dtype),
        np.linspace(np.min(ky), np.max(ky), M, dtype=kx.dtype))
    tx, ty = tx.flatten(), ty.flatten()

    # We only want to do work inside the region of support:
    # estimate as a circle for now, works well with radial
    outside = np.argwhere(np.sqrt(tx**2 + ty**2) > np.max(kx)).squeeze()
    inside = np.argwhere(np.sqrt(tx**2 + ty**2) <= np.max(kx)).squeeze()
    tx = np.delete(tx, outside)
    ty = np.delete(ty, outside)

    if inverse:
        # We want to fill all non-cartesian locations, so the region
        # of support is the whole thing (all indices)
        k = np.delete(k, outside, axis=0)
        inside = np.arange(kx.size, dtype=int)

    # Swap coordinates if doing inverse (cartesian to radial)
    if inverse:
        kx, tx = tx, kx
        ky, ty = ty, ky
    kxy = np.concatenate((kx[:, None], ky[:, None]), axis=-1)
    txy = np.concatenate((tx[:, None], ty[:, None]), axis=-1)

    # Find all targets within radius of source points
    kdtree = cKDTree(kxy)
    idx = kdtree.query_ball_point(txy, r=radius)

    # The result will be shaped different if we are doing inverse
    # gridding:
    if inverse:
        res = np.zeros((tx.size, nc), dtype=k.dtype)
    else:
        res = np.zeros((N * M, nc), dtype=k.dtype)

    t0 = time()
    # Handle both single and double floating point calculations,
    # have to do it in separate functions because Cython...
    if tx.dtype == np.float32:
        key_x, key_y = grog_powers_float(tx, ty, kx, ky, idx, precision)
    else:
        key_x, key_y = grog_powers_double(tx, ty, kx, ky, idx, precision)
    print('Took %g seconds to find required powers' % (time() - t0))

    # If we have provided dictionaries, whitle down the work to only
    # those powers not already computed
    t0 = time()
    if Dx:
        key_x = key_x - set(Dx.keys())
    else:
        Dx = {}
    if Dy:
        key_y = key_y - set(Dy.keys())
    else:
        Dy = {}

    # Precompute deficient matrix powers
    if use_primefac:
        # Precompute matrix powers using prime factorization
        from primefac import factorint  # pylint: disable=E0401
        scale_fac = 10**precision

        # Start a dictionary of fractional matrix powers
        frac_mats_x = {}
        frac_mats_y = {}

        # First thing we need is the scale factor, note that we will
        # assume the inverse!
        lscale_fac = np.log(scale_fac)
        frac_mats_x[lscale_fac] = np.linalg.pinv(fmp(Gx, lscale_fac)).astype(
            k.dtype)
        frac_mats_y[lscale_fac] = np.linalg.pinv(fmp(Gy, lscale_fac)).astype(
            k.dtype)

        for keyx0, keyy0 in tqdm(zip(key_x, key_y),
                                 total=len(key_x),
                                 leave=False,
                                 desc='Dxy'):

            dx0 = np.exp(np.abs(keyx0)) * scale_fac
            dy0 = np.exp(np.abs(keyy0)) * scale_fac
            rx = factorint(int(dx0))
            ry = factorint(int(dy0))

            # Component fractional powers are log of prime factors;
            # add in the scale_fac term here so we get it during the
            # multi_dot later.  We explicitly cast to integer because
            # sometimes we run into an MPZ object that doesn't play
            # nice with numpy
            lpx = np.log(np.array([int(r) for r in rx.keys()] +
                                  [scale_fac])).squeeze()
            lpy = np.log(np.array([int(r) for r in ry.keys()] +
                                  [scale_fac])).squeeze()
            lpx_unique = np.unique(lpx)
            lpy_unique = np.unique(lpy)

            # Compute new fractional matrix powers we haven't seen
            for lpxu in lpx_unique:
                if lpxu not in frac_mats_x:
                    frac_mats_x[lpxu] = fmp(Gx, lpxu)
            for lpyu in lpy_unique:
                if lpyu not in frac_mats_y:
                    frac_mats_y[lpyu] = fmp(Gy, lpyu)

            # Now compose all the matrices together for this point
            nx = list(rx.values()) + [1]  # +1 to account for scale_fac
            ny = list(ry.values()) + [1]
            Dx[np.abs(keyx0)] = np.linalg.multi_dot([
                np.linalg.matrix_power(frac_mats_x[lpx0], n0).astype(k.dtype)
                for lpx0, n0 in zip(lpx, nx)
            ])
            Dy[np.abs(keyy0)] = np.linalg.multi_dot([
                np.linalg.matrix_power(frac_mats_y[lpy0], n0).astype(k.dtype)
                for lpy0, n0 in zip(lpy, ny)
            ])

            if np.sign(keyx0) < 0:
                Dx[keyx0] = np.linalg.pinv(Dx[np.abs(keyx0)]).astype(k.dtype)
            if np.sign(keyy0) < 0:
                Dy[keyy0] = np.linalg.pinv(Dy[np.abs(keyy0)]).astype(k.dtype)

    else:
        for key0 in tqdm(key_x, leave=False, desc='Dx'):
            Dx[np.abs(key0)] = fmp(Gx, np.abs(key0)).astype(k.dtype)
            if np.sign(key0) < 0:
                Dx[key0] = np.linalg.pinv(Dx[np.abs(key0)]).astype(k.dtype)
        for key0 in tqdm(key_y, leave=False, desc='Dy'):
            Dy[np.abs(key0)] = fmp(Gy, np.abs(key0)).astype(k.dtype)
            if np.sign(key0) < 0:
                Dy[key0] = np.linalg.pinv(Dy[np.abs(key0)]).astype(k.dtype)
    print('Took %g seconds to precompute fractional matrix powers' %
          (time() - t0))

    # res is modified inplace
    if res.dtype == np.complex64:
        grog_gridding_float(tx, ty, kx, ky, k, idx, res, inside, Dx, Dy,
                            precision)
    else:
        grog_gridding_double(tx, ty, kx, ky, k, idx, res, inside, Dx, Dy,
                             precision)

    if inverse:
        retVal = res
    else:
        # Remove the oversampling factor and return in kspace or
        # imspace
        ax = (0, 1)
        im = None
        if remove_os:
            N4, M4 = int(N / 4), int(M / 4)
            im = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(res.reshape(
                (N, M, nc), order='F'),
                                                               axes=ax),
                                              axes=ax),
                                 axes=ax)[N4:-N4, M4:-M4, :]
            res = np.fft.ifftshift(np.fft.fft2(np.fft.fftshift(im, axes=ax),
                                               axes=ax),
                                   axes=ax)

        if ret_image:
            if im is None:
                retVal = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(
                    res.reshape((N, M, nc), order='F'), axes=ax),
                                                      axes=ax),
                                         axes=ax)
            else:
                retVal = im
        else:
            retVal = res

    # If the user asked for the precomputed dictionaries back, add
    # them to the tuple of returned values
    if ret_dicts:
        retVal = (retVal, Dx, Dy)

    return retVal