Пример #1
0
def sum_of_squares(arr, coil_axis=-1, verbose=D_VERB_LVL):
    """
    Sum-of-Squares coil combination method.

    Note: this function returns the same array used for input except for the
    normalization, therefore the `coil_axis` parameter is left unused.

    Args:
        arr (np.ndarray): The input array.
        coil_axis (int): The coil dimension.
            The dimension of `arr` along which single coil elements are stored.
        verbose (int): Set level of verbosity.

    Returns:
        result (tuple): The tuple
            contains:
             - combined (np.ndarray): The combined data.
             - sens (np.ndarray): The coil sensitivity.

    References:
        - Roemer, P.B., Edelstein, W.A., Hayes, C.E., Souza, S.P.,
          Mueller, O.M., 1990. The NMR phased array. Magn Reson Med 16,
          192–225. doi:10.1002/mrm.1910160203
    """
    # combined = np.sqrt(np.abs(np.sum(arr * arr.conj(), axis=coil_axis)))
    coil_axis = fc.valid_index(coil_axis, arr.ndim)
    broadcast_shape = [
        d if i != coil_axis else 1 for i, d in enumerate(arr.shape)
    ]
    combined = np.sum(np.abs(arr), axis=coil_axis)
    return combined, np.abs(arr) / combined.reshape(broadcast_shape)
Пример #2
0
def msense_1d(arr,
              acceleration=2,
              autocalib=16,
              acceleration_axis=0,
              coil_axis=-1):
    """
    Perform modified SENSE reconstruction with 1D-accelerated cartesian k-data.

    The coil sensitivity is estimated from the autocalibration lines.

    Args:
        arr (np.ndarray): The input array.
            Data is in k-space and missing k-space lines are zero-filled.
        acceleration (int): The acceleration factor (along 1 dimension).
        autocalib (int): The number of central k-space lines acquired.
        acceleration_axis (int): The accelerated dimension.
        coil_axis (int): The coil dimension.
            The dimension of `arr` along which single coil elements are stored.

    Returns:
        arr (np.ndarray): The output array.

    See Also:
        - Pruessmann, Klaas P., Markus Weiger, Markus B. Scheidegger, and
          Peter Boesiger. 1999. “SENSE: Sensitivity Encoding for Fast MRI.”
          Magnetic Resonance in Medicine 42 (5): 952–62.
          https://doi.org/10.1002/
          (SICI)1522-2594(199911)42:5<952::AID-MRM16>3.0.CO;2-S.
        - Griswold, Mark A., Felix Breuer, Martin Blaimer, Stephan
          Kannengiesser, Robin M. Heidemann, Matthias Mueller, Mathias Nittka,
          Vladimir Jellus, Berthold Kiefer, and Peter M. Jakob. 2006.
          “Autocalibrated Coil Sensitivity Estimation for Parallel Imaging.”
          NMR in Biomedicine 19 (3): 316–24. https://doi.org/10.1002/nbm.1048.
    """
    # : ensure coil axis is the last
    assert (-arr.ndim <= coil_axis < arr.ndim)
    coil_axis = fc.valid_index(coil_axis, arr.ndim)
    last_axis = -1 % arr.ndim
    if coil_axis != last_axis:
        arr = np.swapaxes(arr, coil_axis, last_axis)

    # : prepare parameters
    acc_factors = tuple(acceleration if i ==
                        acceleration_axis else 1 if i != coil_axis else None
                        for i in range(arr.ndim))
    acc_slicing = acceleration_slices(arr.shape, acc_factors)
    autocalib_slicing = autocalib_slices(arr.shape, autocalib, acc_factors)
    acc_arr = arr[acc_slicing]
    calib_arr = arr[autocalib_slicing]

    if coil_axis != last_axis:
        result = np.swapaxes(result, last_axis, coil_axis)
    return result
Пример #3
0
def i_split(arr, axis=-1):
    """
    Split an array along a specific axis into a list of arrays

    This is a generator version of `numpy.split()`.

    Args:
        arr (ndarray): The N-dim array to split.
        axis (int): Direction for the splitting of the array.

    Yields:
        arr (ndarray): The next (N-1)-dim array from the splitting.
            All yielded arrays have the same shape.
    """
    assert (-arr.ndim <= axis < arr.ndim)
    axis = fc.valid_index(axis, arr.ndim)
    for i in range(arr.shape[axis]):
        slicing = tuple(
            slice(None) if j != axis else i for j, d in enumerate(arr.shape))
        yield arr[slicing]
Пример #4
0
def i_stack(arrs, num, axis=-1):
    """
    Stack an iterable of arrays of the same size along a specific axis.

    This is equivalent to `numpy.stack()` but fetches the arrays from an
    iterable instead.

    This is useful for reducing the memory footprint of stacking compared
    to `numpy.stack()` and similar functions, since it does not require
    a full sequence of the data to reside in memory.

    Args:
        arrs (Iterable[ndarray]): The (N-1)-dim arrays to stack.
            These must have the same shape.
        num (int): The number of arrays to stack.
        axis (int): Direction along which to stack the arrays.
            Supports also negative values.

    Returns:
        result (ndarray): The concatenated N-dim array.
    """
    iter_arrs = iter(arrs)
    arr = next(iter_arrs)
    ndim = arr.ndim + 1
    assert (-ndim <= axis < ndim)
    axis = fc.valid_index(axis, ndim)
    base_shape = arr.shape
    shape = base_shape[:axis] + (num, ) + base_shape[axis:]
    result = np.empty(shape, dtype=arr.dtype)
    slicing = tuple(
        slice(None) if j != axis else 0 for j, d in enumerate(shape))
    result[slicing] = arr
    for i, arr in enumerate(iter_arrs, 1):
        slicing = tuple(
            slice(None) if j != axis else i for j, d in enumerate(shape))
        result[slicing] = arr
    return result
Пример #5
0
def grappa_1d(arr,
              acceleration=2,
              autocalib=16,
              kernel_span=1,
              acceleration_axis=0,
              coil_axis=-1):
    """
    Perform GRAPPA interpolation with 1D-accelerated cartesian k-data.

    Args:
        arr (np.ndarray): The input array.
            Data is in k-space and missing k-space lines are zero-filled.
        acceleration (int): The acceleration factor (along 1 dimension).
        autocalib (int): The number of central k-space lines acquired.
        kernel_span (int): The half-size of the kernel.
            The kernel window size in the non-accelerated dimension is given
            by: `kernel_size = kernel_span * 2 + 1`.
            Kernel span must be non-negative.
            The kernel window size in the accelerated dimension is equal to
            the `acceleration + 1`.
        acceleration_axis (int): The accelerated dimension.
        coil_axis (int): The coil dimension.
            The dimension of `arr` along which single coil elements are stored.

    Returns:
        arr (np.ndarray): The output array in k-space.

    See Also:
        - Griswold, Mark A., Peter M. Jakob, Robin M. Heidemann,
          Mathias Nittka, Vladimir Jellus, Jianmin Wang, Berthold Kiefer,
          and Axel Haase. “Generalized Autocalibrating Partially Parallel
          Acquisitions (GRAPPA).” Magnetic Resonance in Medicine 47, no. 6
          (June 1, 2002): 1202–10. https://doi.org/10.1002/mrm.10171.
    """
    # : ensure coil axis is the last
    assert (-arr.ndim <= coil_axis < arr.ndim)
    coil_axis = fc.valid_index(coil_axis, arr.ndim)
    last_axis = -1 % arr.ndim
    if coil_axis != last_axis:
        arr = np.swapaxes(arr, coil_axis, last_axis)

    # : prepare parameters
    acc_factors = tuple(acceleration if i ==
                        acceleration_axis else 1 if i != coil_axis else None
                        for i in range(arr.ndim))
    acc_slicing = acceleration_slices(arr.shape, acc_factors)
    autocalib_slicing = autocalib_slices(arr.shape, autocalib, acc_factors)
    acc_arr = arr[acc_slicing]
    calib_arr = arr[autocalib_slicing]
    kernel_size = kernel_span * 2 + 1
    kernel_window = tuple(
        1 if factor is None else kernel_size if factor == 1 else factor + 1
        for factor in acc_factors)
    kernel_calib_size = 2 * kernel_size
    # number of target points within a kernel window
    n_targets = acceleration - 1

    # : define target and calibration matrices
    calib_padded_arr = fcn.nd_windowing(calib_arr, kernel_window)
    target_slicing = \
        tuple(slice(None) for _ in calib_arr.shape) \
        + tuple((slice(None) if factor is None else
                 slice(kernel_size // 2,
                       kernel_size // 2 + 1) if factor == 1 else
                 slice(1, factor))
                for factor in acc_factors)
    target_arr = calib_padded_arr[target_slicing] \
        .reshape(-1, calib_arr.shape[-1] * n_targets)
    calib_mat_slicing = \
        tuple(slice(None) for _ in calib_arr.shape) \
        + tuple(
            (slice(None) if factor is None or factor == 1 else (0, factor))
            for factor in acc_factors)
    calib_mat_arr = calib_padded_arr[calib_mat_slicing] \
        .reshape(-1, calib_arr.shape[-1] * kernel_calib_size)

    # : compute calibration weights
    weights_arr, _, _, _ = np.linalg.lstsq(calib_mat_arr,
                                           target_arr,
                                           rcond=None)

    # : use weights to compute missing k-space values
    # todo: avoid computing useless lines instead of selecting missing lines
    source_padded_arr = fcn.rolling_window_nd(arr,
                                              kernel_window,
                                              1,
                                              out_mode='same')
    source_mat_arr = source_padded_arr[calib_mat_slicing] \
        .reshape(-1, calib_arr.shape[-1] * kernel_calib_size)
    unknown_arr = np.dot(source_mat_arr, weights_arr)

    # : fill-in GRAPPA-reconstructed missing points
    result = np.zeros_like(arr)
    unknown_shape = source_padded_arr.shape[:arr.ndim] + (n_targets, )
    unknown_arr = unknown_arr.reshape(unknown_shape)
    for i in range(n_targets):
        target_missing_slicing = tuple(
            slice(None) if factor is None else slice(kernel_span, -kernel_span)
            if factor == 1 else slice(i + 1, None, factor)
            for dim, factor in zip(arr.shape, acc_factors))
        source_missing_slicing = tuple(
            slice(None) if factor is None else slice(kernel_span, -kernel_span)
            if factor == 1 else slice(n_targets, dim - factor + n_targets +
                                      1, factor)
            for dim, factor in zip(arr.shape, acc_factors))
        result[target_missing_slicing] = \
            unknown_arr[..., i][source_missing_slicing]
    result[autocalib_slicing] = calib_arr
    result[acc_slicing] = acc_arr

    if coil_axis != last_axis:
        result = np.swapaxes(result, last_axis, coil_axis)
    return result
Пример #6
0
def combine(arr,
            method='block_adaptive_iter',
            method_kws=None,
            compression='compress_svd',
            compression_kws=None,
            coil_axis=-1,
            split_axis=None,
            verbose=D_VERB_LVL):
    """
    Calculate the combination of multiple coil elements.

    An optional coil compression preprocessing step can be used to reduce both
    the computational complexity and (eventually) the noise.

    Note coil combination can be seen as a particular case of coil compression
    where the coils are compressed to a single one.
    If this is the desired behavior, `complex_sum` should be used as `method`.
    However, coil compression methods are typically not suitable for coil
    combination.

    Args:
        arr (np.ndarray): The input array.
        method (str): The combination method.
            If str, uses the specified method as found in this module.
            Some methods require `ref` and/or `multi_axis` to be set in
            `method_kws`.
            Accepted values not requiring `ref` or `multi_axis` are:
             - 'complex_sum': use `pymrt.recipes.coils.complex_sum()`;
             - 'sum_of_squares': use `pymrt.recipes.coils.sum_of_squares()`;
             - 'adaptive': use `pymrt.recipes.coils.adaptive()`;
             - 'block_adaptive': use `pymrt.recipes.coils.block_adaptive()`;
             - 'adaptive_iter': use `pymrt.recipes.coils.adaptive_iter()`;
             - 'block_adaptive_iter': use
               `pymrt.recipes.coils.block_adaptive_iter()`;
            Accepted values requiring `ref` but not `multi_axis` are:
             Not implemented yet.
            Accepted values requiring `multi_axis` but not `ref` are:
             - 'multi_svd': use `pymrt.recipes.coils.mult_svd()`
            Accepted values requiring both `ref` and `multi_axis` are:
             Not implemented yet.

        method_kws (Mappable|None): Keyword arguments to pass to `method`.
            If None, only `coil_axis`, `split_axis`, `verbose` are passed.
        compression (callable|str|None): The compression method.
            This is passed as `method` to `compress`.
        compression_kws (Mappable|None): Keyword arguments to pass to
        `compression`.
            This is passed as `method_kwd` to `compress`.
        coil_axis (int): The coil dimension.
            The dimension of `arr` along which single coil elements are stored.
        split_axis (int|None): The split dimension.
            If int, indicates the dimension of `arr` along which the
            algorithm is sequentially applied to reduce memory usage,
            but at the cost of accuracy.
            If None, the algorithm is applied to the whole `arr` at once.
        verbose (int): Set level of verbosity.

    Returns:
        arr (np.ndarray): The combined array.
    """
    begin_time = datetime.datetime.now()

    sens_methods = ('complex_sum', 'sum_of_squares', 'adaptive',
                    'block_adaptive', 'adaptive_iter', 'block_adaptive_iter',
                    'multi_svd')
    methods = sens_methods + ('virtual_ref', 'multi_svd')

    if compression:
        arr = compress(arr,
                       method=compression,
                       method_kws=compression_kws,
                       coil_axis=coil_axis,
                       verbose=verbose)

    method = method.lower()
    msg('method={}'.format(method), verbose, VERB_LVL['medium'])
    method_kws = {} if method_kws is None else dict(method_kws)

    has_sens = method in sens_methods

    if method in methods:
        method = globals()[method]
    if not callable(method):
        text = ('Unknown method `{}` in `recipes.coils.combine(). ' +
                'Using fallback `{}`.'.format(method, methods[0]))
        warnings.warn(text)
        method = globals()[methods[0]]
        has_sens = True

    if split_axis is not None:
        assert (-arr.ndim <= coil_axis < arr.ndim)
        assert (-arr.ndim <= split_axis < arr.ndim)
        coil_axis = fc.valid_index(coil_axis, arr.ndim)
        shape = arr.shape
        combined = np.zeros(tuple(d for i, d in enumerate(shape)
                                  if i != coil_axis),
                            dtype=complex)
        split_axis = split_axis % arr.ndim
        combined = np.swapaxes(combined, split_axis, 0)
        arr = np.swapaxes(arr, split_axis, 0)
        msg(': split={}'.format(shape[split_axis]),
            verbose,
            VERB_LVL['medium'],
            end='\n',
            flush=True)
        for i in range(shape[split_axis]):
            msg('{}'.format(i + 1),
                verbose,
                VERB_LVL['high'],
                end=' ' if i + 1 < shape[split_axis] else '\n',
                flush=True)
            if has_sens:
                combined[i, ...], _ = method(arr[i, ...],
                                             coil_axis=coil_axis,
                                             verbose=verbose,
                                             **dict(method_kws))
                del _
            else:
                combined[i, ...] = method(arr[i, ...],
                                          coil_axis=coil_axis,
                                          verbose=verbose,
                                          **dict(method_kws))
        combined = np.swapaxes(combined, 0, split_axis)
        arr = np.swapaxes(arr, 0, split_axis)
    else:
        if has_sens:
            combined, _ = method(arr,
                                 coil_axis=coil_axis,
                                 verbose=verbose,
                                 **dict(method_kws))
            del _
        else:
            combined = method(arr,
                              coil_axis=coil_axis,
                              verbose=verbose,
                              **dict(method_kws))

    if np.isclose(np.mean(np.abs(np.angle(combined))), 0.0, equal_nan=True):
        combined = combined.astype(complex)
        msg('Adding summed phase.', verbose, VERB_LVL['medium'])
        combined *= np.exp(1j * np.angle(np.sum(arr, axis=coil_axis)))

    end_time = datetime.datetime.now()
    msg('ExecTime({}): {}'.format('coils.combine', end_time - begin_time),
        verbose, D_VERB_LVL)

    return combined
Пример #7
0
def adaptive_iter(arr,
                  filtering=None,
                  filtering_kws=None,
                  max_iter=16,
                  threshold=1e-8,
                  coil_axis=-1,
                  verbose=D_VERB_LVL):
    """
    Adaptive Iterative coil combination method.

    This is an iterative and faster implementation of the algorithm for
    computing 'adaptive' sensitivity, which allows for a simpler formulation
    for phase correction.

    Args:
        arr (np.ndarray): The input array.
        filtering (callable|None): The filtering function.
            If callable, it is used to separate the sensitivity from the input.
            Typically, a low-pass filter is used, under the assumption that
            the coil sensitivity is smooth compared to the sources.
            If None, no separation is performed.
        filtering_kws (Mappable|None): Keyword arguments to pass to `filtering`.
        max_iter (int): Maximum number of iterations.
            If `threshold` > 0, the algorithm may stop earlier.
        threshold (float): Threshold for next iteration.
            If the next iteration globally modifies the sensitivity by less
            than `threshold`, the algorithm stops.
        coil_axis (int): The coil dimension.
            The dimension of `arr` along which single coil elements are stored.
        verbose (int): Set level of verbosity.

    Returns:
        result (tuple): The tuple
            contains:
             - combined (np.ndarray): The combined data.
             - sens (np.ndarray): The coil sensitivity.

    References:
        - Walsh, D.O., Gmitro, A.F., Marcellin, M.W., 2000. Adaptive
          reconstruction of phased array MR imagery. Magn. Reson. Med. 43,
          682–690. doi:10.1002/(SICI)1522-2594(
          200005)43:5<682::AID-MRM10>3.0.CO;2-G
        - Inati, S.J., Hansen, M.S., Kellman, P., 2013. A Solution to the
          Phase Problem in Adaptive Coil Combination, in: Proceedings of the
          ISMRM 21st Annual Meeting & Exhibition. Presented at the 21st Annual
          Meeting & Exhibition of the International Society for Magnetic
          Resonance in Medicine, ISMRM, Salt Lake City, Utah, USA.
        - Inati, S.J., Hansen, M.S., Kellman, P., 2014. A Fast Optimal
          Method for Coil Sensitivity Estimation and Adaptive Coil
          Combination for Complex Images, in: Proceedings of the ISMRM 22nd
          Annual Meeting & Exhibition. Presented at the 22nd Annual Meeting
          & Exhibition of the International Society for Magnetic Resonance
          in Medicine, ISMRM, Milan, Italy.
    """
    assert (-arr.ndim <= coil_axis < arr.ndim)
    coil_axis = fc.valid_index(coil_axis, arr.ndim)
    last_axis = -1 % arr.ndim
    if coil_axis != last_axis:
        arr = np.swapaxes(arr, coil_axis, last_axis)

    msg('arr.shape={}'.format(arr.shape), verbose, VERB_LVL['debug'])
    msg('threshold={}'.format(threshold), verbose, VERB_LVL['debug'])
    msg('max_iter={}'.format(max_iter), verbose, VERB_LVL['debug'])

    epsilon = np.spacing(1.0)
    other_axes = tuple(range(0, arr.ndim - 1))

    with np.errstate(divide='ignore', invalid='ignore'):
        weights = np.sum(arr, other_axes)
        weights /= np.linalg.norm(weights)
        # combined == weighted
        combined = np.einsum('...i,i', arr, weights.conj())
        sens = np.zeros_like(arr, dtype=complex)
        delta = 1.0
        for i in range(max_iter):
            last_combined = combined.copy() if threshold > 0 else combined
            sens = arr * combined[..., None].conj()
            if filtering:
                sens = fcn.filter_cx(sens, filtering, (), filtering_kws)
            sens /= (np.sqrt(np.sum(sens * sens.conj(), -1)) + epsilon)[...,
                                                                        None]
            combined = np.sum(sens.conj() * arr, -1)
            # include the additional phase
            weights = np.sum(sens * combined[..., None], other_axes)
            weights /= np.linalg.norm(weights)
            weighted = np.einsum('...i,i', sens, weights.conj())
            weighted /= (np.abs(weighted) + epsilon)
            combined *= weighted
            sens *= weighted[..., None].conj()
            msg('{}'.format(i + 1),
                verbose,
                VERB_LVL['debug'],
                end=' ' if threshold else ', ',
                flush=True)
            if threshold > 0:
                last_delta = delta
                delta = (np.linalg.norm(combined - last_combined) /
                         np.linalg.norm(combined))
                msg('delta={}'.format(delta),
                    verbose,
                    VERB_LVL['debug'],
                    end=', ' if i + 1 < max_iter else '.\n',
                    flush=True)
                if delta < threshold or last_delta < delta:
                    break

    if coil_axis != last_axis:
        sens = np.swapaxes(sens, last_axis, coil_axis)
    return combined, sens
Пример #8
0
def adaptive(arr,
             filtering=None,
             filtering_kws=None,
             max_iter=16,
             threshold=1e-7,
             coil_axis=-1,
             verbose=D_VERB_LVL):
    """
    Adaptive coil combination method.

    Args:
        arr (np.ndarray): The input array.
        filtering (callable|None): The filtering function.
            If callable, it is used to separate the sensitivity from the input.
            Typically, a low-pass filter is used, under the assumption that
            the coil sensitivity is smooth compared to the sources.
            If None, no separation is performed.
        filtering_kws (Mappable|None): Keyword arguments to pass to `filtering`.
        max_iter (int): Maximum iterations in power algorithm.
            This is the maximum number of iterations used for determining the
            principal component (eigenvalue/vector) using the power algorithm.
        threshold (float): Threshold in power algorithm.
            If the next iteration modifies the eigenvalue (in absolute terms)
            less than the threshold, the power algorithm stops.
        coil_axis (int): The coil dimension.
            The dimension of `arr` along which single coil elements are stored.
        verbose (int): Set level of verbosity.

    Returns:
        result (tuple): The tuple
            contains:
             - combined (np.ndarray): The combined data.
             - sens (np.ndarray): The coil sensitivity.

    References:
        - Walsh, D.O., Gmitro, A.F., Marcellin, M.W., 2000. Adaptive
          reconstruction of phased array MR imagery. Magn. Reson. Med. 43,
          682–690. doi:10.1002/(SICI)1522-2594(
          200005)43:5<682::AID-MRM10>3.0.CO;2-G
        - Inati, S.J., Hansen, M.S., Kellman, P., 2013. A Solution to the
          Phase Problem in Adaptive Coil Combination, in: Proceedings of the
          ISMRM 21st Annual Meeting & Exhibition. Presented at the 21st Annual
          Meeting & Exhibition of the International Society for Magnetic
          Resonance in Medicine, ISMRM, Salt Lake City, Utah, USA.
    """
    assert (-arr.ndim <= coil_axis < arr.ndim)
    shape = arr.shape
    num_coils = shape[coil_axis]
    coil_axis = fc.valid_index(coil_axis, arr.ndim)
    last_axis = -1 % arr.ndim
    if coil_axis != last_axis:
        arr = np.swapaxes(arr, coil_axis, last_axis)

    base_shape = arr.shape[:-1]

    # calculate the coil covariance
    coil_cov = np.zeros(base_shape + (num_coils, ) * 2, dtype=complex)
    for i in range(num_coils):
        for j in range(num_coils):
            coil_cov[..., i, j] = arr[..., i] * arr[..., j].conj()

    if filtering:
        for i in range(num_coils):
            for j in range(num_coils):
                coil_cov[..., i, j] = fcn.filter_cx(coil_cov[..., i,
                                                             j], filtering, (),
                                                    filtering_kws)

    # calculate the principal eigenvector of the coil covariance
    # using the power method (pointwise through all spatial dimensions)
    sens = np.zeros(base_shape + (num_coils, ), dtype=complex)
    for i in itertools.product(*[range(k) for k in base_shape]):
        ii = tuple(j for j in i if i != slice(None)) + (slice(None), )
        iii = tuple(j for j in i if i != slice(None)) + (slice(None), ) * 2
        sensitivity_i = np.sum(coil_cov[iii], axis=-1)
        power_i = np.linalg.norm(sensitivity_i)
        if power_i:
            sensitivity_i = sensitivity_i / power_i
        else:
            sensitivity_i *= 0
            continue
        for _ in range(max_iter):
            sensitivity_i = np.dot(coil_cov[iii], sensitivity_i)
            last_power_i = power_i
            power_i = np.linalg.norm(sensitivity_i)
            if power_i:
                sensitivity_i = sensitivity_i / power_i
            else:
                sensitivity_i *= 0
                break
            if np.abs(last_power_i - power_i) < threshold:
                break
        sens[ii] = sensitivity_i
    if coil_axis != last_axis:
        sens = np.swapaxes(sens, last_axis, coil_axis)

    combined = combine_sens(arr, sens, coil_axis=coil_axis)
    return combined, sens
Пример #9
0
def compress_svd(arr,
                 k_svd='quad_weight',
                 orthonormal=False,
                 coil_axis=-1,
                 verbose=D_VERB_LVL):
    """
    Compress the coil data to the SVD principal components.

    Rearranges (diagonalize) the acquired single-channel data into virtual
    single-channel data sorted by eigenvalue magnitude.
    If the number of SVD components `k_svd` is smaller than the number of
    coils, this is useful both as a denoise method and for reducing the
    complexity of the problem and the memory usage.

    Args:
        arr (np.ndarray): The input array.
        k_svd (int|float|str): The number of principal components.
            See `fc.optimal_num_components()` for more details.
        orthonormal: Uses the orthonormal approximation.
            Uses the pseudo-inverse, instead of the hermitian conjugate,
            to form the signal compression matrix.
        coil_axis (int): The coil dimension.
            The dimension of `arr` along which single coil elements are stored.
        verbose (int): Set level of verbosity.

    Returns:
        arr (np.ndarray): The compressed coil array.

    References:
        - Buehrer, M., Pruessmann, K.P., Boesiger, P., Kozerke, S., 2007.
          Array compression for MRI with large coil arrays. Magn. Reson. Med.
          57, 1131–1139. doi:10.1002/mrm.21237
    """
    assert (-arr.ndim <= coil_axis < arr.ndim)
    shape = arr.shape
    num_coils = shape[coil_axis]
    coil_axis = fc.valid_index(coil_axis, arr.ndim)
    last_axis = -1 % arr.ndim
    if coil_axis != last_axis:
        arr = np.swapaxes(arr, coil_axis, last_axis)

    base_shape = arr.shape[:-1]

    arr = arr.reshape((-1, num_coils))
    if orthonormal:
        inv_arr = np.linalg.pinv(arr)

    square_arr = np.zeros((num_coils, num_coils), dtype=complex)
    for i in range(num_coils):
        if orthonormal:
            square_arr[i, :] = np.dot(inv_arr[i, :], arr)
        else:
            square_arr[i, :] = np.dot(arr[:, i].conj(), arr)

    if orthonormal:
        del inv_arr

    eigvals, right_eigvects = sp.linalg.eig(square_arr)
    eig_sort = np.argsort(np.abs(eigvals))[::-1]

    k_svd = fcn.auto_num_components(k_svd,
                                    np.abs(eigvals[eig_sort]) /
                                    np.max(np.abs(eigvals)),
                                    verbose=verbose)

    arr = np.dot(arr, right_eigvects[:, eig_sort][:, :k_svd])

    arr = arr.reshape(base_shape + (k_svd, ))
    if coil_axis != last_axis:
        arr = np.swapaxes(arr, last_axis, coil_axis)
    return arr