def main():
    # :: handle program parameters
    arg_parser = handle_arg()
    args = arg_parser.parse_args()
    # fix verbosity in case of 'quiet'
    if args.quiet:
        args.verbose = VERB_LVL['none']
    # :: print debug info
    if args.verbose >= VERB_LVL['debug']:
        arg_parser.print_help()
        msg('\nARGS: ' + str(vars(args)), args.verbose, VERB_LVL['debug'])

    if not args.indirect:
        numex.interactive_tk_mpl.plotting(plot_rho_b1t_mp2rage_seq,
                                          SEQ_INTERACTIVES,
                                          resources_path=PATH['resources'],
                                          title=TITLE,
                                          about=__doc__)
    else:
        numex.interactive_tk_mpl.plotting(plot_rho_b1t_mp2rage_acq,
                                          ACQ_INTERACTIVES,
                                          resources_path=PATH['resources'],
                                          title=TITLE,
                                          about=__doc__)

    elapsed(__file__[len(PATH['base']) + 1:])
    msg(report())
Example #2
0
def _to_be_checked():
    msg(__doc__.strip())

    test()

    # todo: move this test to unittest
    s = '/scr/beryllium1/mr16/RM/lcmLONGc_128avg' \
        '/LONGc_128avg160419_RL6T_MET20_Step01_WAT.txt'

    d = read_output(s)
    for k, v in sorted(d['metabolites'].items()):
        print('{} : {}'.format(k, v))
    print()

    for k, v in sorted(d['extra'].items()):
        print('{} : {}'.format(k, v))
    print()
    if 'data_cs' in d:
        import matplotlib.pyplot as plt

        plt.figure()
        plt.plot(d['data_cs'], d['data_s'], '-b')
        plt.plot(d['data_cs'], d['data_fit'], '-r')
        plt.plot(d['data_cs'], d['data_bg'], '-y')
        plt.show()

    s = '/scr/beryllium1/mr16/RM/lcmLONGc_112avg' \
        '/LONGc_112avg160419_RL6T_MET20_Step01'
    c = read_input(s)
    print(c['data'].shape, c)

    elapsed('test lcmodel i/o')
    msg(report())
    print()
Example #3
0
def main():
    # :: handle program parameters
    arg_parser = handle_arg()
    args = arg_parser.parse_args()
    # fix verbosity in case of 'quiet'
    if args.quiet:
        args.verbose = VERB_LVL['none']
    # :: print debug info
    if args.verbose >= VERB_LVL['debug']:
        arg_parser.print_help()
        msg('\nARGS: ' + str(vars(args)), args.verbose, VERB_LVL['debug'])

    if not args.name:
        msg('Choose your playground:')
        msg('Available playgrounds: {AVAILABLES}'.format_map(globals()))
        args.name = input(': ')
    if args.name in AVAILABLES:
        numex.interactive_tk_mpl.plotting(globals()[PREFIX + args.name],
                                          PARAMS[args.name],
                                          title=TITLE_BASE + ' - ' +
                                          TITLE[args.name],
                                          about=__doc__)
        elapsed(__file__[len(PATH['base']) + 1:])
        msg(report())
    else:
        msg(fmtm('Plot `{args.name}` not valid.'))
Example #4
0
def multi_svd(arr,
              num_proc=None,
              multi_axis=-2,
              coil_axis=-1,
              verbose=D_VERB_LVL):
    """
    Coil sensitivity for the 'conjugate_hermitian' combination method.

    Note: the input itself is used as sensitivity. Therefore, this function
    actually returns the same array used for input, and the `coil_axis`
    parameter is left unused.

    Args:
        arr (np.ndarray): The input array.
        num_proc (int|None): The number of parallel processes.
            If 1, the execution is sequential.
            If 0 or None, the number of workers is determined automatically.
            Otherwise, uses the specified number of workers.
            If the number of workers is > 1, the execution is in parallel.
        multi_axis (int): The echo dimension.
            The dimension of `arr` along which different echoes are stored.
        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 estimated coil sensitivity.
    """
    shape = arr.shape
    num_coils = shape[coil_axis]
    num_multi = shape[multi_axis]
    arr = np.swapaxes(arr, coil_axis, -1)
    arr = np.swapaxes(arr, multi_axis, -2)
    base_shape = arr.shape[:-2]

    arr = arr.reshape((-1, num_multi, num_coils))
    num_points = arr.shape[0]

    combined = np.zeros((num_points, num_multi), dtype=complex)
    sens = np.zeros((num_points, num_coils), dtype=complex)
    if not num_proc:
        num_proc = multiprocessing.cpu_count() + 1

    msg('num_proc={}'.format(num_proc), verbose, VERB_LVL['debug'])
    if num_proc == 1:
        for i in range(num_points):
            u, s, v = np.linalg.svd(arr[i, ...])
            combined[i, :] = u[:, 0] * s[0]
            sens[i, :] = v[0, :]
    else:
        chunksize = 2 * multiprocessing.cpu_count()
        pool = multiprocessing.Pool(num_proc)
        for i, res in enumerate(pool.map(np.linalg.svd, arr, chunksize)):
            u, s, v = res
            combined[i, :] = u[:, 0] * s[0]
            sens[i, :] = v[0, :]

    combined = combined.reshape(base_shape + (num_multi, ))
    return combined, sens
Example #5
0
def block_adaptive(arr,
                   block=5,
                   max_iter=16,
                   threshold=1e-7,
                   coil_axis=-1,
                   verbose=D_VERB_LVL):
    """
    Block Adaptive coil combination method.

    Args:
        arr (np.ndarray): The input array.
        block (int|float|Iterable[int|float]): The size of the block in px.
            Smooth the coil covariance using a uniform filter with the
            specified block size.
            If int or float, the block is isotropic in all non-coil dimensions.
            If Iterable, each size is applied to the corresponding dimension
            and its size must match the number of non-coil dimensions.
            If set to 0, no smoothing is performed and the algorithm
            reduces to non-block adaptive.
        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.
    """
    if isinstance(block, (int, float)):
        block = (block, ) * (arr.ndim - 1) + (0, )
    else:
        assert (len(block) + 1 == arr.ndim)
    msg('block={}'.format(block), verbose, VERB_LVL['debug'])

    return adaptive_iter(arr,
                         filtering=sp.ndimage.uniform_filter,
                         filtering_kws=dict(size=block),
                         max_iter=max_iter,
                         threshold=threshold,
                         coil_axis=coil_axis,
                         verbose=verbose)
Example #6
0
def main():
    # :: handle program parameters
    arg_parser = handle_arg()
    args = arg_parser.parse_args()
    # fix verbosity in case of 'quiet'
    if args.quiet:
        args.verbose = VERB_LVL['none']
    # :: print debug info
    if args.verbose >= VERB_LVL['debug']:
        arg_parser.print_help()
        msg('\nARGS: ' + str(vars(args)), args.verbose, VERB_LVL['debug'])

    if args.verbose >= VERB_LVL['medium']:
        print("II: Using method/options: '{}' / '{}'".format(
            args.method, args.options))

    if args.method:
        preset_func_name = _func_name('preset', args.method)
        sources_func_name = _func_name('sources', args.method)
        compute_func_name = _func_name('compute', args.method)

        # use preset if available
        opts = json.loads(args.options) if args.options else {}
        if preset_func_name in vars(pmc):
            new_opts = vars(pmc)[preset_func_name]()
            new_opts.update(opts)
            opts = new_opts
        # source extraction
        sources_func = vars(pmc)[sources_func_name] \
            if sources_func_name in vars(pmc) else pmc.sources_generic
        sources_args = [opts, args.force, args.verbose]
        sources_kwargs = {}
        # computation
        compute_func = vars(pmc)[compute_func_name] \
            if compute_func_name in vars(pmc) else pmc.compute_generic
        compute_args = [opts, args.force, args.verbose]
        compute_kwargs = {}
        # inform on the actual functions
        if args.verbose > VERB_LVL['none']:
            print('II: Mode: {} / {} / {}'.format(args.method,
                                                  sources_func.__name__,
                                                  compute_func.__name__))
            print('II: Opts: {}'.format(json.dumps(opts)))
        # proceed with computation on selected sources
        if opts != {}:
            pmc.compute(sources_func, sources_args, sources_kwargs,
                        compute_func, compute_args, compute_kwargs, args.input,
                        args.output, args.recursive, args.meta_subpath,
                        args.data_subpath, args.verbose)
        else:
            print('EE: Mode / options combination not supported.')
    else:
        print('WW: Method not specified.')

    fc.elapsed('compute')
    msg(report(), args.verbose, VERB_LVL['medium'])
Example #7
0
def main():
    # :: handle program parameters
    arg_parser = handle_arg()
    args = arg_parser.parse_args()
    # fix verbosity in case of 'quiet'
    if args.quiet:
        args.verbose = VERB_LVL['none']
    # :: print debug info
    if args.verbose >= VERB_LVL['debug']:
        arg_parser.print_help()
        msg('\nARGS: ' + str(vars(args)), args.verbose, VERB_LVL['debug'])
    msg(__doc__.strip())

    val, name, units = pymrt.sequences.flash.ernst_calc(
        args.t1, args.tr, args.fa)
    print('{}={} {}'.format(name, val, units))
Example #8
0
def read(filename,
         dirpath='.',
         exclude_keys=lambda s: s.startswith('__'),
         verbose=D_VERB_LVL):
    """
    Read a MATLAB file.

    Args:
        filename (str): The input filename.
            The '.mat' extension can be omitted.
        dirpath (str): The working directory.
        exclude_keys (callable): The function used to exclude unwanted keys.
            Defaults to excluding potential private keys.
        verbose (int): Set level of verbosity.

    Returns:
        data (dict): A dictionary containing the data read.

    Examples:

    """
    if not filename.lower().endswith('.mat'):
        filename += '.mat'
    filepath = os.path.join(dirpath, filename)

    if not exclude_keys:

        def exclude_keys(s):
            return True

    try:
        # load traditional MATLAB files
        data = sp.io.loadmat(filepath)
        data = {k: np.array(v) for k, v in data.items() if not exclude_keys(k)}
        msg('Loaded using MATLAB v4 l1, v6, v7 <= v7.2 compatibility layer.',
            verbose, VERB_LVL['debug'])
    except NotImplementedError:
        # load new-style MATLAB v7.3+ files (effectively HDF5)
        data = {}
        h5file = h5py.File(filepath, 'r')
        for k, v in h5file.items():
            if not exclude_keys(k):
                data[k] = np.array(v)
        msg('Loaded using MATLAB v7.3+ compatibility layer.', verbose,
            VERB_LVL['debug'])
    return data
Example #9
0
def main():
    # :: handle program parameters
    arg_parser = handle_arg()
    args = arg_parser.parse_args()
    # fix verbosity in case of 'quiet'
    if args.quiet:
        args.verbose = VERB_LVL['none']
    # :: print debug info
    if args.verbose >= VERB_LVL['debug']:
        arg_parser.print_help()
        msg('\nARGS: ' + str(vars(args)), args.verbose, VERB_LVL['debug'])
    msg(__doc__.strip())
    begin_time = datetime.datetime.now()

    if not args.output:
        root, base, ext = fc.split_path(args.input)
        args.output = fc.join_path([root, 'mask__' + base, ext])

    kws = fc.set_func_kws(mrt.segmentation.auto_mask, vars(args))

    kws['threshold_kws'] = json.loads(args.threshold_opts)
    kws['threshold'] = fc.auto_convert(kws['threshold'])

    data, meta = mrt.input_output.load(args.input, meta=True)
    data = mrt.segmentation.auto_mask(data, **kws).astype(args.dtype)
    mrt.input_output.save(args.output, data, **dict(meta.items()))

    end_time = datetime.datetime.now()
    if args.verbose > VERB_LVL['low']:
        msg('ExecTime: {}'.format(end_time - begin_time))
Example #10
0
def write(data, filename, dirpath='.', verbose=D_VERB_LVL):
    """
    Write a MATLAB file.

    Args:
        data (dict): A dictionary of arrays to save.
            The dict key is used for the variable name on load.
        filename (str): The input filename.
            The '.mat' extension can be omitted.
        dirpath (str): The working directory.
        verbose (int): Set level of verbosity.

    Returns:
        None.
    """
    if not filename.lower().endswith('.mat'):
        filename += '.mat'
    filepath = os.path.join(dirpath, filename)

    sp.io.savemat(filepath, data)
    msg('Saved using MATLAB v4 l1, v6, v7 <= v7.2 compatibility layer.',
        verbose, VERB_LVL['debug'])
Example #11
0
def main():
    # :: handle program parameters
    arg_parser = handle_arg()
    args = arg_parser.parse_args()
    # fix verbosity in case of 'quiet'
    if args.quiet:
        args.verbose = VERB_LVL['none']
    # :: print debug info
    if args.verbose >= VERB_LVL['debug']:
        arg_parser.print_help()
        msg('\nARGS: ' + str(vars(args)), args.verbose, VERB_LVL['debug'])
    else:
        msg(__doc__.strip())
    begin_time = datetime.datetime.now()

    kws = vars(args)
    kws.pop('quiet')
    coil_combine(**kws)

    end_time = datetime.datetime.now()
    if args.verbose > VERB_LVL['low']:
        print('ExecTime: {}'.format(end_time - begin_time))
Example #12
0
def main():
    # :: handle program parameters
    arg_parser = handle_arg()
    args = arg_parser.parse_args()
    # fix verbosity in case of 'quiet'
    if args.quiet:
        args.verbose = VERB_LVL['none']
    # :: print debug info
    if args.verbose >= VERB_LVL['debug']:
        arg_parser.print_help()
        msg('\nARGS: ' + str(vars(args)), args.verbose, VERB_LVL['debug'])

    x_vars = set([x.lower() for x in args.mode])

    filtered_interactives = INTERACTIVES.copy()
    for k in list(filtered_interactives.keys()):
        if k[:2] not in x_vars:
            filtered_interactives.pop(k)

    if x_vars == {'t1', 'tr'}:
        numex.interactive_tk_mpl.plotting(
            plot_flash_ernst_angle_t1_tr,
            filtered_interactives, resources_path=PATH['resources'],
            title=TITLE, about=__doc__)
    elif x_vars == {'fa', 't1'}:
        numex.interactive_tk_mpl.plotting(
            plot_flash_ernst_angle_fa_t1,
            filtered_interactives, resources_path=PATH['resources'],
            title=TITLE, about=__doc__)
    elif x_vars == {'fa', 't1'}:
        numex.interactive_tk_mpl.plotting(
            filtered_interactives, resources_path=PATH['resources'],
            title=TITLE, about=__doc__)

    elapsed(__file__[len(PATH['base']) + 1:])
    msg(report())
Example #13
0
def compress(arr,
             method='compress_svd',
             method_kws=None,
             coil_axis=-1,
             verbose=D_VERB_LVL):
    """
    Compress multiple coil elements into fewer coil elements.

    Args:
        arr (np.ndarray): The input array.
        method (str|None): The compression method.
            If str, uses the specified method as found in this module.
            Accepted values are:
             - 'compress_svd': use `pymrt.recipes.coils.compress_svd()`.
            If None, no coil compression is performed.
        method_kws (Mappable|None): Keyword arguments to pass to `method`.
            If None, only `coil_axis`, `verbose` are passed.
        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.
    """
    begin_time = datetime.datetime.now()

    methods = ('compress_svd', )

    msg('compression', verbose, VERB_LVL['debug'])

    method = method.lower()
    msg('method={}'.format(method), verbose, VERB_LVL['debug'])

    if method in methods:
        method = globals()[method]
    method_kws = {} if method_kws is None else dict(method_kws)

    if callable(method):
        arr = method(arr,
                     coil_axis=coil_axis,
                     verbose=verbose,
                     **dict(method_kws))
    else:
        text = 'Unknown compression method. None performed.'
        warnings.warn(text)

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

    return arr
Example #14
0
def main():
    # :: handle program parameters
    arg_parser = handle_arg()
    args = arg_parser.parse_args()
    # fix verbosity in case of 'quiet'
    if args.quiet:
        args.verbose = VERB_LVL['none']
    # :: print debug info
    if args.verbose >= VERB_LVL['debug']:
        arg_parser.print_help()
        msg('\nARGS: ' + str(vars(args)), args.verbose, VERB_LVL['debug'])
    msg(__doc__.strip())

    extract_nifti(args.dir, args.extradir, args.force, args.verbose)

    elapsed('extract_nifit_bruker')
    msg(report())
Example #15
0
def main():
    # :: handle program parameters
    arg_parser = handle_arg()
    args = arg_parser.parse_args()
    # fix verbosity in case of 'quiet'
    if args.quiet:
        args.verbose = VERB_LVL['none']
    # :: print debug info
    if args.verbose >= VERB_LVL['debug']:
        arg_parser.print_help()
        msg('\nARGS: ' + str(vars(args)), args.verbose, VERB_LVL['debug'])
    msg(__doc__.strip())

    kws = vars(args)
    kws.pop('quiet')
    pml.check_correlation(**kws)

    elapsed(__file__[len(PATH['base']) + 1:])
    msg(report())
Example #16
0
        for sources, params in zip(sources_list, params_list):
            compute_func(sources, out_dirpath, params, *compute_args,
                         **compute_kwargs)
            fc.elapsed('Time: ')
            if verbose >= VERB_LVL['medium']:
                fc.report(only_last=True)
    else:
        recursive = True

    # descend into subdirectories
    if recursive:
        recursive = recursive or bool(sources_list)
        subdirs = [
            subdir for subdir in os.listdir(in_dirpath)
            if os.path.isdir(os.path.join(in_dirpath, subdir))
        ]
        for subdir in subdirs:
            new_in_dirpath = os.path.join(in_dirpath, subdir)
            new_out_dirpath = os.path.join(out_dirpath, subdir)
            compute(sources_func, sources_args, sources_kwargs, compute_func,
                    compute_args, compute_kwargs, new_in_dirpath,
                    new_out_dirpath, recursive, meta_subpath, data_subpath,
                    verbose)


# ======================================================================
if __name__ == '__main__':
    msg(__doc__.strip())

fc.elapsed('pymrt.computation')
Example #17
0
File: reco.py Project: norok2/pymrt
def pseudo_multi_replica(raw_arr,
                         mask,
                         reco_func,
                         reco_args=None,
                         reco_kws=None,
                         noise_level=1,
                         num=64,
                         verbose=D_VERB_LVL):
    """
    Estimate SNR and g-factor with the multi-replica method.

    This a Monte Carlo method, effectively consisting of computing the
    standard deviation for multiple instances of the difference between
    the images reconstructed with and without additional Gaussian noise
    in the complex raw time-domain data.

    This is then used to compute the signal-to-noise (SNR) map and the
    geometric noise amplification factor (g-factor).

    Uses a specific `mask` to describe the undersampling pattern.

    SNR = img / sd_noise
    g_factor = sd_noised_img

    Args:
        raw_arr (np.ndarray): The input raw data.
            This does not need to be fully sampled, but it must have the
            correct size for fully sampled data.
            The values that will be masked can be zero-ed.
        mask (np.ndarray[bool]|slice|Iterable[slice]): The undersampling mask.
        reco_func (callable): The reconstruction function.
            Must accept:
             - the raw data array as first argument;
             - the mask/undersampling scheme as second argument.
        reco_args (Iterable|None): Positional arguments for `reco_func`.
        reco_kws (Mappable|None): Keyword arguments for `reco_func`.
        noise_level (int|float|str|None): The noise level.
            This is used to determine the st.dev of the Gaussian noise
            being added at each iteration.
            If int or float, the value is the st.dev of the Gaussian noise.
            If str and the value is a percentage, the st.dev is computed as
            the peak-to-peak value multiplied by `noise_level` percentage
            (the peak-to-peak value is the maximum of the peak-to-peak value
            for real and imaginary data separately).
            If None, the noise level is estimated using
             `pymrt.correction.estimate_noise_sigma()`.
        num (int): The number of repetitions.
        verbose (int): Set level of verbosity.

    Returns:
        result (tuple): The tuple
            contains:
            - snr_arr (np.ndarray): The estimated SNR map.
            - g_factor_arr (np.ndarray): The estimated g-factor map.

    References:
        - Robson, Philip M., Aaron K. Grant, Ananth J. Madhuranthakam,
          Riccardo Lattanzi, Daniel K. Sodickson, and Charles A. McKenzie.
          “Comprehensive Quantification of Signal-to-Noise Ratio and g-Factor
          for Image-Based and k-Space-Based Parallel Imaging Reconstructions.”
          Magnetic Resonance in Medicine 60, no. 4 (2008): 895–907.
          https://doi.org/10.1002/mrm.21728.
    """
    reco_args = tuple(reco_args) if reco_args else ()
    reco_kws = dict(reco_kws) if reco_kws else {}

    # noise-less reco
    reco_arr = reco_func(raw_arr, *reco_args, **reco_kws)

    mean_noised_reco_arr = np.zeros_like(reco_arr, dtype=float)
    sosd_noised_reco_arr = np.zeros_like(reco_arr, dtype=float)
    mean_noised_optim_arr = np.zeros_like(reco_arr, dtype=float)
    sosd_noised_optim_arr = np.zeros_like(reco_arr, dtype=float)

    # compute desired noise std
    if noise_level is None:
        noise_std_val = mrt.correction.estimate_noise_sigma(raw_arr)
    elif isinstance(noise_level, (int, float)):
        noise_std_val = noise_level
    else:
        noise_std_val = fc.to_percent(noise_level)
        if noise_std_val:
            cx_ptp = max(np.ptp(np.real(raw_arr)), np.ptp(np.imag(raw_arr)))
            noise_std_val = cx_ptp * noise_level
        else:
            noise_std_val = 1
    msg('Noise St.Dev.: {} (Level: {:.0%})'.format(noise_std_val, noise_level),
        verbose, VERB_LVL['debug'])

    # compute the effective acceleration factor
    sampling_ratio = raw_arr[mask].size / raw_arr.size

    for i in range(num):
        msg('Replica #{}'.format(i), verbose, VERB_LVL['debug'])
        noise_arr = np.random.normal(0, noise_std_val, raw_arr.shape)
        noised_reco_arr = reco_func(raw_arr + noise_arr, mask, *reco_args,
                                    **reco_kws)
        # new uncorrelated noise
        noise_arr = np.random.normal(0, noise_std_val, reco_arr.shape)
        noised_optim_arr = reco_func(noise_arr, None, *reco_args, **reco_kws)

        mean_noised_reco_arr, sosd_noised_reco_arr, _ = \
            fc.next_mean_and_sosd(
                np.real(noised_reco_arr),
                mean_noised_reco_arr, sosd_noised_reco_arr, i)
        mean_noised_optim_arr, sosd_noised_optim_arr, _ = \
            fc.next_mean_and_sosd(
                np.real(noised_optim_arr),
                mean_noised_optim_arr, sosd_noised_optim_arr, i)

    noise_reco_arr = fc.sosd2stdev(sosd_noised_reco_arr, num)
    noise_optim_arr = fc.sosd2stdev(sosd_noised_optim_arr, num)

    snr_arr = np.abs(reco_arr) / noise_reco_arr
    g_factor_arr = g_factor(noise_optim_arr, noise_reco_arr, sampling_ratio)
    return snr_arr, g_factor_arr
Example #18
0
File: reco.py Project: norok2/pymrt
def noise(raw_arr,
          reco_func,
          reco_args=None,
          reco_kws=None,
          noise_level=1,
          num=64,
          verbose=D_VERB_LVL):
    """
    Estimate the noise for reco using a pseudo-multi-replica approach.

    This a Monte Carlo method, effectively consisting of computing the
    standard deviation for multiple instances of the image reconstructed after
    the addition of white noise in the complex raw data.

    This is can be used to compute the signal-to-noise (SNR) and the
    geometric noise amplification factor (g-factor).

    The SNR can be computed by:
    snr = reco_arr / noise_arr

    with:
    reco_arr = reco_func(raw_arr, *reco_args, **reco_kws)

    Args:
        raw_arr (np.ndarray): The input raw data as acquired (k-space).
        reco_func (callable): The reconstruction function.
            Must accept the raw data array as first argument.
        reco_args (Iterable|None): Positional arguments for `reco_func`.
        reco_kws (Mappable|None): Keyword arguments for `reco_func`.
        noise_level (int|float|str|None): The noise level.
            This is used to determine the st.dev of the Gaussian noise
            being added at each iteration.
            If int or float, the value is the st.dev of the Gaussian noise.
            If str and the value is a percentage, the st.dev is computed as
            the peak-to-peak value multiplied by `noise_level` percentage
            (the peak-to-peak value is the maximum of the peak-to-peak value
            for real and imaginary data separately).
            If None, the noise level is estimated using
             `pymrt.correction.estimate_noise_sigma()`.
        num (int): The number of repetitions.
        verbose (int): Set level of verbosity.

    Returns:
        result (tuple): The tuple
            contains:
            - noise_arr (np.ndarray): The st.dev. of noised reconstructions.
            - reco_arr (np.ndarray): The reco from data without extra noise.

    """
    reco_args = tuple(reco_args) if reco_args else ()
    reco_kws = dict(reco_kws) if reco_kws else {}

    # noise-less reco
    reco_arr = reco_func(raw_arr, *reco_args, **reco_kws)

    # compute desired noise std
    if noise_level is None:
        noise_std_val = mrt.correction.estimate_noise_sigma(raw_arr)
    elif isinstance(noise_level, (int, float)):
        noise_std_val = noise_level
    else:
        noise_std_val = fc.to_percent(noise_level)
        if noise_std_val:
            cx_ptp = max(np.ptp(np.real(raw_arr)), np.ptp(np.imag(raw_arr)))
            noise_std_val = cx_ptp * noise_std_val
        else:
            noise_std_val = 1
    msg(fmtm('Noise St.Dev.: {noise_std_val} (Level: {noise_level})'), verbose,
        VERB_LVL['debug'])

    mean_noised_arr = np.zeros_like(reco_arr, dtype=float)
    sosd_noised_arr = np.zeros_like(reco_arr, dtype=float)
    for i in range(num):
        msg(fmtm('Replica #{i}'), verbose, VERB_LVL['debug'])
        noise_raw_arr = np.random.normal(0, noise_std_val, raw_arr.shape)
        noised_arr = reco_func(raw_arr + noise_raw_arr, *reco_args, **reco_kws)
        mean_noised_arr, sosd_noised_arr, _ = fc.next_mean_and_sosd(
            np.real(noised_arr), mean_noised_arr, sosd_noised_arr, i)
    noise_arr = fc.sosd2stdev(sosd_noised_arr, num)
    return noise_arr, reco_arr
Example #19
0
def coil_combine(in_filepaths,
                 out_filepaths,
                 method='block_adaptive_iter',
                 method_kws=None,
                 compression='compress_svd',
                 compression_kws=None,
                 coil_axis=-1,
                 split_axis=None,
                 q_filepath=None,
                 force=False,
                 verbose=D_VERB_LVL):
    in_mag_filepath, in_phs_filepath = in_filepaths
    out_mag_filepath, out_phs_filepath = out_filepaths
    msg('Input-MAG: {}'.format(in_mag_filepath))
    msg('Input-PHS: {}'.format(in_phs_filepath))
    msg('Output-MAG: {}'.format(out_mag_filepath))
    msg('Output-PHS: {}'.format(out_phs_filepath))
    msg('Method: {}'.format(method))
    msg('Compression: {}'.format(compression))
    msg('Quality: {}'.format(q_filepath))
    # in_filepaths = [in_mag_filepath, in_phs_filepath]
    # out_filepaths = [out_mag_filepath, out_phs_filepath]
    if fc.check_redo(in_filepaths, out_filepaths, force):
        mag_coils_arr, meta = mrt.input_output.load(in_mag_filepath, meta=True)
        phs_coils_arr = mrt.input_output.load(in_phs_filepath)
        coils_arr = fcn.polar2complex(mag_coils_arr, phs_coils_arr)
        del mag_coils_arr, phs_coils_arr
        arr = coils.combine(coils_arr,
                            method=method,
                            method_kws=method_kws,
                            compression=compression,
                            compression_kws=compression_kws,
                            coil_axis=coil_axis,
                            split_axis=split_axis,
                            verbose=verbose)
        mag_arr, phs_arr = fc.complex2polar(arr)
        mrt.input_output.save(out_mag_filepath, mag_arr, **meta)
        mrt.input_output.save(out_phs_filepath, phs_arr, **meta)
        if fc.check_redo(out_filepaths, q_filepath):
            q_arr = coils.quality(coils_arr,
                                  arr,
                                  coil_axis=coil_axis,
                                  verbose=verbose)
            mrt.input_output.save(q_filepath, q_arr)
Example #20
0
def batch_extract(
        dirpath,
        out_filename='niz/{scan_num}__{acq_method}_{scan_name}_{reco_flag}',
        out_dirpath=None,
        custom_reco=None,
        custom_reco_kws=None,
        fid_name='fid',
        dseq_name='2dseq',
        acqp_name='acqp',
        method_name='method',
        reco_name='reco',
        allowed_ext=('', 'gz'),
        force=False,
        verbose=D_VERB_LVL):
    """
    Extract images from experiment folder.

    EXPERIMENTAL!

    Args:
        dirpath (str):
        out_filename (str|None):
        out_dirpath (str|None):
        custom_reco (str|None):
            Determines how results will be saved.
            Accepted values are:
             - 'mag_phs': saves magnitude and phase.
             - 're_im': saves real and imaginary parts.
             - 'cx': saves the complex data.
        custom_reco_kws (Mappable|None):
        fid_name ():
        dseq_name ():
        acqp_name ():
        method_name ():
        reco_name ():
        allowed_ext ():
        force ():
        verbose ():

    Returns:

    """
    text = '\n'.join(
        ('EXPERIMENTAL!', 'Use at your own risk!', 'Known issues:',
         ' - orientation not adjusted to method (i.e. 0->RO, 1->PE, 2->SL)',
         ' - FOV is centered out', ' - voxel size is not set', ''))
    warnings.warn(text)

    if allowed_ext is None:
        allowed_ext = ''
    elif isinstance(allowed_ext, str):
        allowed_ext = (allowed_ext, )
    fid_filepaths = sorted(
        fc.flistdir(_to_patterns(fid_name, allowed_ext), dirpath))

    for fid_filepath in sorted(fid_filepaths):
        msg('FID: {}'.format(fid_filepath), verbose, D_VERB_LVL)
        fid_dirpath = os.path.dirname(fid_filepath)
        if out_dirpath is None:
            out_dirpath = dirpath
        out_filepath = os.path.join(out_dirpath, out_filename)

        acqp_filepath = _get_single(fid_dirpath, acqp_name, allowed_ext)
        method_filepath = _get_single(fid_dirpath, method_name, allowed_ext)

        dseq_filepaths = sorted(
            fc.flistdir(_to_patterns(dseq_name, allowed_ext), fid_dirpath))
        reco_filepaths = sorted(
            fc.flistdir(_to_patterns(reco_name, allowed_ext), fid_dirpath))

        acqp_s, acqp, acqp_c = jcampdx.read(acqp_filepath)
        method_s, method, method_c = jcampdx.read(method_filepath)
        scan_num, sample_id = _get_scan_num_sample_id(acqp_c)
        scan_name = fc.safe_filename(acqp['ACQ_scan_name'])
        acq_method = fc.safe_filename(acqp['ACQ_method'])
        reco_flag = mrt.naming.NEW_RECO_ID

        if custom_reco:
            load_info = _get_load_bin_info_fid(acqp, method)

            if custom_reco == 'cx':
                reco_flag = mrt.naming.ITYPES['cx']
                cx_filepath = fc.change_ext(fmtm(out_filepath),
                                            mrt.util.EXT['niz'])
                if not os.path.isdir(os.path.dirname(cx_filepath)):
                    os.makedirs(os.path.dirname(cx_filepath))

                if fc.check_redo(
                    [fid_filepath, acqp_filepath, method_filepath],
                    [cx_filepath], force):
                    arr = _load_bin(fid_filepath, **load_info)
                    arr = _reco_from_fid(arr, acqp, method, verbose=verbose)
                    mrt.input_output.save(cx_filepath, arr)
                    msg('CX:  {}'.format(os.path.basename(cx_filepath)),
                        verbose, D_VERB_LVL)

            elif custom_reco == 'mag_phs':
                reco_flag = mrt.naming.ITYPES['mag']
                mag_filepath = fc.change_ext(fmtm(out_filepath),
                                             mrt.util.EXT['niz'])
                if not os.path.isdir(os.path.dirname(mag_filepath)):
                    os.makedirs(os.path.dirname(mag_filepath))

                reco_flag = mrt.naming.ITYPES['phs']
                phs_filepath = fc.change_ext(fmtm(out_filepath),
                                             mrt.util.EXT['niz'])
                if not os.path.isdir(os.path.dirname(phs_filepath)):
                    os.makedirs(os.path.dirname(phs_filepath))

                if fc.check_redo(
                    [fid_filepath, acqp_filepath, method_filepath],
                    [mag_filepath, phs_filepath], force):
                    reco_flag = mrt.naming.ITYPES['mag']

                    arr = _load_bin(fid_filepath, **load_info)
                    arr = _reco_from_fid(arr, acqp, method, verbose=verbose)
                    mrt.input_output.save(mag_filepath, np.abs(arr))
                    msg('MAG: {}'.format(os.path.basename(mag_filepath)),
                        verbose, D_VERB_LVL)
                    mrt.input_output.save(phs_filepath, np.angle(arr))
                    msg('PHS: {}'.format(os.path.basename(phs_filepath)),
                        verbose, D_VERB_LVL)

            elif custom_reco == 're_im':
                reco_flag = mrt.naming.ITYPES['re']
                re_filepath = fc.change_ext(fmtm(out_filepath),
                                            mrt.util.EXT['niz'])
                if not os.path.isdir(os.path.dirname(re_filepath)):
                    os.makedirs(os.path.dirname(re_filepath))

                reco_flag = mrt.naming.ITYPES['im']
                im_filepath = fc.change_ext(fmtm(out_filepath),
                                            mrt.util.EXT['niz'])
                if not os.path.isdir(os.path.dirname(im_filepath)):
                    os.makedirs(os.path.dirname(im_filepath))

                if fc.check_redo(
                    [fid_filepath, acqp_filepath, method_filepath],
                    [re_filepath, im_filepath], force):
                    arr = _load_bin(fid_filepath, **load_info)
                    arr = _reco_from_fid(arr, acqp, method, verbose=verbose)
                    mrt.input_output.save(re_filepath, np.abs(arr))
                    msg('RE: {}'.format(os.path.basename(re_filepath)),
                        verbose, D_VERB_LVL)
                    mrt.input_output.save(im_filepath, np.angle(arr))
                    msg('IM: {}'.format(os.path.basename(im_filepath)),
                        verbose, D_VERB_LVL)

        else:
            text = 'Voxel data and shapes may be incorrect.'
            warnings.warn(text)
            for dseq_filepath, reco_filepath \
                    in zip(dseq_filepaths, reco_filepaths):
                reco_s, reco, reco_c = jcampdx.read(reco_filepath)
                reco_flag = _get_reco_num(reco_c)

                cx_filepath = fc.change_ext(fmtm(out_filepath),
                                            mrt.util.EXT['niz'])
                if not os.path.isdir(os.path.dirname(cx_filepath)):
                    os.makedirs(os.path.dirname(cx_filepath))

                load_info = _get_load_bin_info_reco(reco, method)

                if fc.check_redo([dseq_filepath, reco_filepath], [cx_filepath],
                                 force):
                    arr = _load_bin(dseq_filepath, **load_info)
                    arr = _reco_from_bin(arr, reco, method, verbose=verbose)
                    mrt.input_output.save(cx_filepath, arr)
                    msg('NIZ: {}'.format(os.path.basename(cx_filepath)),
                        verbose, D_VERB_LVL)
Example #21
0
def _reco_from_fid(arr,
                   acqp,
                   method,
                   coil_axis=-1,
                   images_axis=-2,
                   rep_axis=-3,
                   avg_axis=-4,
                   verbose=D_VERB_LVL):
    is_cartesian = True
    if is_cartesian:
        load_info = _get_load_bin_info_fid(acqp, method)
        dtype_size = struct.calcsize(load_info['mode'] +
                                     fc.DTYPE_STR[load_info['dtype']])
        block_size = acqp['GO_block_size']
        if block_size == 'continuous':
            block_size = 1
        elif block_size == 'Standard_KBlock_Format':
            block_size = (1024 // dtype_size // 2)
        else:
            block_size = int(block_size)

        # number of images per experiment (e.g. multi-echo)
        num_images = acqp['NI']
        msg('num_images={}'.format(num_images), verbose, VERB_LVL['debug'])

        # inner cycle repetition (before phase-encoding) to be combined
        num_accum = acqp['NAE']
        msg('num_accum={}'.format(num_accum), verbose, VERB_LVL['debug'])

        # outer cycle repetition (after phase-encoding) to be combined
        num_avg = acqp['NA']
        msg('num_avg={}'.format(num_avg), verbose, VERB_LVL['debug'])

        # image repetitions that are NOT to be averaged
        num_rep = acqp['NR']
        msg('num_rep={}'.format(num_rep), verbose, VERB_LVL['debug'])

        # number of dummy scans
        # num_ds = acqp['DS']
        # msg('num_ds={}'.format(num_ds), verbose, VERB_LVL['debug'])

        # phase encoding factor
        pe_factor = acqp['ACQ_phase_factor']
        msg('pe_factor={}'.format(pe_factor), verbose, VERB_LVL['debug'])

        acq_shape = acqp['ACQ_size']
        msg('acq_shape={}'.format(acq_shape), verbose, VERB_LVL['debug'])

        base_shape = method['PVM_Matrix']
        msg('base_shape={}'.format(base_shape), verbose, VERB_LVL['debug'])

        ref_gains = acqp['ACQ_CalibratedRG'].ravel()
        msg('ref_gains={}'.format(ref_gains), verbose, VERB_LVL['debug'])

        # number of coils
        num_coils = len([ref_gain for ref_gain in ref_gains if ref_gain > 0])
        # num_coils = acq_shape[0] / base_shape[0]
        msg('num_coils={}'.format(num_coils), verbose, VERB_LVL['debug'])

        try:
            # fp = '/home/raid1/metere/hd3/sandbox/hmri/_/test_{s}.nii.gz'

            msg('fid_size={}'.format(arr.size), verbose, VERB_LVL['debug'])
            fid_shape = (fc.align(base_shape[0],
                                  block_size // num_coils), num_coils,
                         num_images, fc.align(base_shape[1], pe_factor,
                                              'lower'),
                         acq_shape[2] if len(acq_shape) == 3 else 1, num_avg,
                         num_rep, -1)
            msg('fid_shape={}'.format(fid_shape), verbose, VERB_LVL['debug'])

            arr = arr.reshape(fid_shape, order='F')
            arr = np.moveaxis(arr, (1, 2), (coil_axis, images_axis))
            # remove singleton dimensions
            arr = np.squeeze(arr)

            # remove additional zeros from redout block alignment
            arr = np.delete(arr, slice(base_shape[0], None), 0)
            # remove additional zeros from over-slices
            if len(acq_shape) == 3:
                arr = np.delete(arr, slice(base_shape[2], None), 2)

            # sort and reshape phase encoding steps
            if pe_factor > 1:
                msg('arr_shape={}'.format(arr.shape), verbose,
                    VERB_LVL['debug'])
                pe_size = arr.shape[1]
                pe_step = pe_size // pe_factor
                i = np.argsort(
                    list(
                        itertools.chain(*[
                            range(j, pe_size, pe_step) for j in range(pe_step)
                        ])))
                tmp_arr = arr[:, i, ...]
                arr = np.zeros(tuple(base_shape) +
                               tuple(arr.shape[len(base_shape):]),
                               dtype=complex)
                arr[:, :pe_size, ...] = tmp_arr

            # todo: fix phases?

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

            # perform spatial FFT
            # warning: incorrect fft shifts result in checkerboard artifact
            ft_axes = tuple(range(len(base_shape)))
            arr = np.fft.ifftshift(np.fft.ifftn(np.fft.fftshift(arr,
                                                                axes=ft_axes),
                                                axes=ft_axes),
                                   axes=ft_axes)

            # combine coils
            if num_coils > 1:
                if num_images == 1:
                    coils_combine_kws = (
                        ('method', 'block_adaptive_iter'),
                        ('compression_kws', dict(
                            (('k_svd', 'quad_weight'), ))),
                        ('split_axis', None),
                    )
                else:
                    coils_combine_kws = (
                        ('method', 'multi_svd'),
                        ('compression', None),
                        ('split_axis', None),
                    )
                    # coils_combine_kws = (
                    #     ('method', 'adaptive_iter'),
                    #     ('method_kws', dict((('block', 8),))),
                    #     ('compression_kws', dict((('k_svd', 'quad_weight'),
                    # ))),
                    #     ('split_axis', images_axis),)
                combined_arr = coils.combine(arr,
                                             coil_axis=coil_axis,
                                             verbose=verbose,
                                             **dict(coils_combine_kws))

                qq_arr = coils.quality(arr, combined_arr)
                # mrt.input_output.save(fp.format(s='Q'), qq_arr)
                arr = combined_arr
            if num_avg > 1:
                arr = np.sum(arr, axis=avg_axis)
            if num_rep > 1:
                arr = np.sum(arr, axis=rep_axis)

                # mrt.input_output.save(fp.format(s='M'), np.abs(arr))
                # print('MAG')
                # mrt.input_output.save(fp.format(s='P'), np.angle(arr))
                # print('PHS')

        # except ValueError:
        except NotImplementedError as e:
            msg('Failed at: {}'.format(e))
            fid_shape = fc.get_k_factors(arr.size, 3)
            warning = ('Could not determine correct shape for FID. '
                       'Using `{}`'.format(fid_shape))
            warnings.warn(warning)
            arr = arr.reshape(fid_shape)
    else:
        raise NotImplementedError
    return arr
Example #22
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
Example #23
0
def sensitivity(arr,
                method='block_adaptive_iter',
                method_kws=None,
                coil_axis=-1,
                split_axis=None,
                verbose=D_VERB_LVL):
    """
    Estimate the coil sensitivity.

    Args:
        arr (np.ndarray): The input array.
        method (str): The coil sensitivity method.
            If str, uses the specified method as found in this module.
            Accepted values are:
             - 'complex_sum';
             - 'sum_of_squares';
             - 'adaptive';
             - 'block_adaptive';
             - 'adaptive_iter';
             - 'block_adaptive_iter';
        method_kws (Mappable|None): Keyword arguments to pass to `method`.
            If None, only `coil_axis` and `split_axis` are passed to `method`.
        coil_axis (int): The coil dimension.
            The dimension of `arr` along which single coil elements are stored.
            This is passed to `method`.
        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:
        sens (arr): The coil sensitivity.
    """
    methods = ('complex_sum', 'sum_of_squares', 'adaptive', 'block_adaptive',
               'adaptive_iter', 'block_adaptive_iter', 'multi_svd')

    msg('compression', verbose, VERB_LVL['debug'])

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

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

    if split_axis is not None:
        shape = arr.shape
        sens = np.zeros(shape, dtype=complex)
        arr = np.swapaxes(arr, split_axis, 0)
        sens = np.swapaxes(sens, 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)
            _, sens[i, ...] = method(arr[i, ...],
                                     coil_axis=coil_axis,
                                     verbose=verbose,
                                     **dict(method_kws))
            del _
        sens = np.swapaxes(sens, 0, split_axis)
    else:
        msg('', verbose, VERB_LVL['medium'])
        sens = method(arr,
                      coil_axis=coil_axis,
                      verbose=verbose,
                      **dict(method_kws))
    return sens
Example #24
0
def trajectory_2d(
        trajectory,
        duration=10000,
        last_frame_duration=3000,
        support_intervals=None,
        plot_kws=(('marker', 'o'), ('linewidth', 1)),
        ticks_limit=None,
        title=None,
        more_texts=None,
        more_elements=None,
        ax=None,
        save_filepath=None,
        save_kws=None,
        force=False,
        verbose=D_VERB_LVL):
    n_dims, n_points = trajectory.shape
    if n_dims != 2:
        raise IndexError('2D trajectory required')
    if ax is None:
        fig = plt.figure()
        ax = fig.gca()
    else:
        fig = plt.gcf()
    if title:
        ax.set_title(title)
    if support_intervals is None:
        support_intervals = (
            fcn.minmax(trajectory[0]), fcn.minmax(trajectory[1]))

    n_frames = int(n_points * (1 + last_frame_duration / duration))
    data = trajectory

    if plot_kws is None:
        plot_kws = {}

    line, = ax.plot([], [], **dict(plot_kws))
    # points = ax.scatter([], [])
    ax.grid()
    x_data, y_data = [], []

    def data_gen():
        for i in range(n_points):
            yield trajectory[0, i], trajectory[1, i]
        for i in range(n_frames - n_points):
            yield None, None

    def init():
        xlim_size = np.ptp(support_intervals[0])
        ylim_size = np.ptp(support_intervals[1])
        ax.set_xlim(
            (support_intervals[0][0] - 0.1 * xlim_size,
             support_intervals[0][1] + 0.1 * xlim_size))
        ax.set_ylim(
            (support_intervals[1][0] - 0.1 * ylim_size,
             support_intervals[1][1] + 0.1 * ylim_size))
        del x_data[:]
        del y_data[:]
        line.set_data(x_data, y_data)
        # points.set_offsets(np.c_[x_data, y_data])
        return line,  # points

    def run(data_generator):
        # update the data
        x, y = data_generator
        x_data.append(x)
        y_data.append(y)
        line.set_data(x_data, y_data)
        # points.set_offsets(np.c_[x_data, y_data])
        return line,  # points

    mrt.plot._more_texts(more_texts, ax)
    mrt.plot._more_elements(more_elements, ax)
    mov = mpl.animation.FuncAnimation(
        fig, run, data_gen, init_func=init, save_count=n_frames,
        blit=False, repeat=False, repeat_delay=None,
        interval=duration / n_frames)

    if save_filepath and fc.check_redo(None, [save_filepath], force):
        fig.tight_layout()
        save_kws = dict(
            fps=n_frames / duration / MSEC_IN_SEC, save_count=n_frames)
        if save_kws is None:
            save_kws = {}
        save_kws.update(save_kws)
        mov.save(save_filepath, **dict(save_kws))
        msg('Anim: {}'.format(save_filepath, verbose, VERB_LVL['medium']))
        # plt.close(fig)
    return trajectory, fig, mov
Example #25
0
def sample2d(
        arr,
        axis=None,
        start=None,
        stop=None,
        step=None,
        duration=10000,
        title=None,
        array_interval=None,
        ticks_limit=None,
        orientation=None,
        flip_ud=False,
        flip_lr=False,
        cmap=None,
        cbar_kws=None,
        cbar_txt=None,
        text_color=None,
        resolution=None,
        size_info=None,
        more_texts=None,
        more_elements=None,
        ax=None,
        save_filepath=None,
        save_kws=None,
        force=False,
        verbose=D_VERB_LVL):
    """
    Plot a 2D sample image of a 3D array.

    Parameters
    ==========
    arr : ndarray
        The original 3D array.
    axis : int (optional)
        The slicing axis. If None, use the shortest one.
    step : int (optional)
        The slicing index. Must be 1 or more.
    title : str (optional)
        The title of the plot.
    array_interval : 2-tuple (optional)
        The (min, max) values interval.
    cmap : MatPlotLib ColorMap (optional)
        The colormap to be used for displaying the histogram.
    use_new_figure : bool (optional)
        Plot the histogram in a new figure.
    close_figure : bool (optional)
        Close the figure after saving (useful for batch processing).
    save_filepath : str (optional)
        The path to which the plot is to be saved. If unset, no output.

    Returns
    =======
    sample : ndarray
        The sliced (N-1)D-array.
    plot : matplotlib.pyplot.Figure
        The figure object containing the plot.

    """
    if arr.ndim != 3:
        raise IndexError('3D array required')

    fig, ax = mrt.plot._ensure_fig_ax(ax)

    n_frames = arr.shape[axis]

    if start is None:
        start = 0
    if stop is None:
        stop = n_frames
    if step is None:
        step = 1

    # prepare data
    sample = fcn.ndim_slice(arr, axis, start)

    if title:
        ax.set_title(title)

    if array_interval is None:
        array_interval = fcn.minmax(arr)

    if not text_color:
        text_color = 'k'

    ax.set_aspect('equal')

    mrt.plot._manage_ticks_limit(ticks_limit, ax)

    plots = []
    datas = []
    for i in range(start, stop, step):
        data = mrt.plot._reorient_2d(
            fcn.ndim_slice(arr, axis, i), orientation, flip_ud, flip_lr)
        pax = ax.imshow(
            data, cmap=cmap,
            vmin=array_interval[0], vmax=array_interval[1], animated=True)
        # include additional text
        if more_texts is not None:
            for text_kws in more_texts:
                ax.text(**dict(text_kws))
        datas.append(data)
        if len(plots) <= 0:
            mrt.plot._manage_colorbar(cbar_kws, cbar_txt, ax, pax)
        plots.append([pax])

        # print resolution information and draw a ruler
        mrt.plot._manage_resolution_info(
            size_info, resolution, data.shape, text_color, ax)

        mrt.plot._more_texts(more_texts, ax)
        mrt.plot._more_elements(more_elements, ax)

    mov = mpl.animation.ArtistAnimation(fig, plots, blit=False)
    if save_filepath and fc.check_redo(None, [save_filepath], force):
        fig.tight_layout()
        save_kws = {'fps': n_frames / step / duration / MSEC_IN_SEC}
        if save_kws is None:
            save_kws = {}
        save_kws.update(save_kws)
        mov.save(save_filepath, **dict(save_kws))
        msg('Anim: {}'.format(save_filepath, verbose, VERB_LVL['medium']))
        plt.close(fig)
    return datas, fig, mov
Example #26
0
def wip():
    # one-step QSM.. need for bias field removal?
    # convert input angles to radians
    # theta = np.deg2rad(theta)
    # phi = np.deg2rad(phi)
    #
    # k_x, k_y, k_z = coord(arr_cx.shape)
    # k_2 = (k_x ** 2 + k_y ** 2 + k_z ** 2)
    # cc = (k_z * np.cos(theta) * np.cos(phi) -
    #       k_y * np.sin(theta) * np.cos(phi) +
    #       k_x * np.sin(phi)) ** 2
    # dd = 1 / (k_2 - cc)
    # dd = subst(dd)
    # chi_arr = np.abs(idftn(3 * k_2 * dd * dftn(phs_arr)))

    import os
    import datetime
    import pymrt.input_output

    begin_time = datetime.datetime.now()

    force = False

    base_path = '/home/raid1/metere/hd3/cache/qsm_coil_reco/COIL_RECO/' \
                'HJJT161103_nifti/0091_as_gre_nifti_TE17ms'

    mag_filepath = os.path.join(base_path, 'bai_mag.nii.gz')
    phs_filepath = os.path.join(base_path, 'bai_phs.nii.gz')
    msk_filepath = os.path.join(base_path, 'mask.nii.gz')

    mag_arr, meta = mrt.input_output.load(mag_filepath, meta=True)
    phs_arr = mrt.input_output.load(phs_filepath)
    msk_arr = mrt.input_output.load(msk_filepath).astype(bool)

    uphs_filepath = os.path.join(base_path, 'bai_uphs.nii.gz')
    if fc.check_redo(phs_filepath, uphs_filepath, force):
        from pymrt.recipes import phs

        uphs_arr = phs.unwrap(phs_arr)
        mrt.input_output.save(uphs_filepath, uphs_arr)
    else:
        uphs_arr = mrt.input_output.load(uphs_filepath)

    dphs_filepath = os.path.join(base_path, 'bai_dphs.nii.gz')
    if fc.check_redo(phs_filepath, dphs_filepath, force):
        from pymrt.recipes import phs

        dphs_arr = phs.phs_to_dphs(phs_arr, 20.0)
        mrt.input_output.save(dphs_filepath, uphs_arr)
    else:
        dphs_arr = mrt.input_output.load(dphs_filepath)

    db0_filepath = os.path.join(base_path, 'bai_db0.nii.gz')
    if fc.check_redo(dphs_filepath, db0_filepath, force):
        from pymrt.recipes import db0

        db0_arr = db0.dphs_to_db0(dphs_arr, b0=2.89362)
        mrt.input_output.save(db0_filepath, db0_arr)
    else:
        db0_arr = mrt.input_output.load(db0_filepath)

    # milf_filepath = os.path.join(base_path, 'bai_db0i_milf.nii.gz')
    # if fc.check_redo(db0_filepath, milf_filepath, force):
    #     from pymrt.recipes import phs
    #
    #     milf_arr = qsm_remove_background_milf(uphs_arr, msk_arr)
    #     mrt.input_output.save(milf_filepath, milf_arr)
    #     msg('MILF')
    # else:
    #     milf_arr = mrt.input_output.load(milf_filepath)

    # sharp_filepath = os.path.join(base_path, 'bai_db0i_sharp.nii.gz')
    # if fc.check_redo(uphs_filepath, sharp_filepath, force):
    #     from pymrt.recipes import phs
    #     import scipy.ndimage
    #
    #     radius = 5
    #     mask_arr = sp.ndimage.binary_erosion(msk_arr, iterations=radius * 4)
    #     sharp_arr = qsm_remove_background_sharp(
    #         uphs_arr, mask_arr, radius, rel_radius=False)
    #     mrt.input_output.save(sharp_filepath, sharp_arr)
    #     msg('SHARP')
    # else:
    #     sharp_arr = mrt.input_output.load(sharp_filepath)

    chi_filepath = os.path.join(base_path, 'bai_chi_ptfi_minres_i0128.nii.gz')
    if fc.check_redo(db0_filepath, chi_filepath, force):
        from pymrt.recipes import db0

        mask = mag_arr > 0.5
        w_arr = mag_arr**2
        # 1 / (chi_x / chi_water)
        # non-water is assumed to be air
        pc_arr = np.full(mag_arr.shape, abs(CHI_V['water'] / CHI_V['air']))
        # mask is assumed to be mostly water
        pc_arr[mask] = abs(CHI_V['water'] / CHI_V['water'])

        chi_arr = qsm_total_field_inversion(db0_arr,
                                            w_arr,
                                            msk_arr,
                                            pc_arr,
                                            linsolve_iter_kws=dict(
                                                method='minres', max_iter=128))
        mrt.input_output.save(chi_filepath, chi_arr)
    else:
        chi_arr = mrt.input_output.load(chi_filepath)

    # chi_filepath = os.path.join(base_path, 'bai_chi_tfi_lsmr.nii.gz')
    # if fc.check_redo(db0_filepath, chi_filepath, force):
    #     from pymrt.recipes import db0
    #
    #     chi_arr = qsm_total_field_inversion(
    #         db0_arr, mag_arr, msk_arr,
    #         linsolve_iter_kws=dict(method='lsmr', max_iter=256))
    #     mrt.input_output.save(chi_filepath, chi_arr)
    # else:
    #     chi_arr = mrt.input_output.load(chi_filepath)

    msg('TotTime: {}'.format(datetime.datetime.now() - begin_time))
Example #27
0
File: reco.py Project: norok2/pymrt
def gen_pseudo_multi_replica(raw_arr,
                             reco_func,
                             reco_args=None,
                             reco_kws=None,
                             optim_func=None,
                             optim_args=None,
                             optim_kws=None,
                             noise_level=1,
                             num=64,
                             verbose=D_VERB_LVL):
    """
    Estimate the SNR and g-factor using the generalized pseudo-multi-replica.

    This a Monte Carlo method, effectively consisting of computing the
    standard deviation for multiple instances of the image reconstructed after
    the addition of white noise in the complex raw data (`sd_noised_arr`), and
    the same reconstruction applied to noise-only data (`sd_noise_arr`).

    This is then used to compute the signal-to-noise (SNR) map and the
    geometric noise amplification factor (g-factor).

    R*: effective undersampling or acceleration factor

    reco = reconstructed image without noise addition
    R = reco.size / raw.size

    SNR = reco / sd_noised_reco
    g_factor = sd_noised_reco / sd_noised_optim / sqrt(R)

    Args:
        raw_arr (np.ndarray): The input raw data as acquired (k-space).
        reco_func (callable): The reconstruction function.
            Must accept the raw data array as first argument.
        reco_args (Iterable|None): Positional arguments for `reco_func`.
        reco_kws (Mappable|None): Keyword arguments for `reco_func`.
        optim_func (callable): The reconstruction function.
            Must accept the raw data array as first argument.
        optim_args (Iterable|None): Positional arguments for `reco_func`.
            This are used to generate the optimal reconstruction.
        optim_kws (Mappable|None): Keyword arguments for `reco_func`.
            This are used to generate the optimal reconstruction.
        noise_level (int|float|str|None): The noise level.
            This is used to determine the st.dev of the Gaussian noise
            being added at each iteration.
            If int or float, the value is the st.dev of the Gaussian noise.
            If str and the value is a percentage, the st.dev is computed as
            the peak-to-peak value multiplied by `noise_level` percentage
            (the peak-to-peak value is the maximum of the peak-to-peak value
            for real and imaginary data separately).
            If None, the noise level is estimated using
             `pymrt.correction.estimate_noise_sigma()`.
        num (int): The number of repetitions.
        verbose (int): Set level of verbosity.

    Returns:
        result (tuple): The tuple
            contains:
            - snr_arr (np.ndarray): The estimated SNR map.
            - g_factor_arr (np.ndarray): The estimated g-factor map.

    References:
        - Robson, Philip M., Aaron K. Grant, Ananth J. Madhuranthakam,
          Riccardo Lattanzi, Daniel K. Sodickson, and Charles A. McKenzie.
          “Comprehensive Quantification of Signal-to-Noise Ratio and g-Factor
          for Image-Based and k-Space-Based Parallel Imaging Reconstructions.”
          Magnetic Resonance in Medicine 60, no. 4 (2008): 895–907.
          https://doi.org/10.1002/mrm.21728.
    """
    #TODO: difference with `pseudo_multi_replica()`?
    reco_args = tuple(reco_args) if reco_args else ()
    reco_kws = dict(reco_kws) if reco_kws else {}
    if not optim_func:
        optim_func = reco_func
    optim_args = tuple(optim_args) if optim_args else ()
    optim_kws = dict(optim_kws) if optim_kws else {}

    # "noiseless" reco
    reco_arr = reco_func(raw_arr, *reco_args, **reco_kws)

    mean_noised_reco_arr = np.zeros_like(reco_arr, dtype=float)
    sosd_noised_reco_arr = np.zeros_like(reco_arr, dtype=float)
    mean_noised_optim_arr = np.zeros_like(reco_arr, dtype=float)
    sosd_noised_optim_arr = np.zeros_like(reco_arr, dtype=float)

    # compute desired noise std
    if noise_level is None:
        noise_std_val = mrt.correction.estimate_noise_sigma(raw_arr)
    elif isinstance(noise_level, (int, float)):
        noise_std_val = noise_level
    else:
        noise_std_val = fc.to_percent(noise_level)
        if noise_std_val:
            cx_ptp = max(np.ptp(np.real(raw_arr)), np.ptp(np.imag(raw_arr)))
            noise_std_val = cx_ptp * noise_level
        else:
            noise_std_val = 1
    msg('Noise St.Dev.: {} (Level: {:.0%})'.format(noise_std_val, noise_level),
        verbose, VERB_LVL['debug'])

    # compute the effective acceleration factor
    sampling_ratio = reco_arr.size / raw_arr.size

    for i in range(num):
        msg('Replica #{}'.format(i), verbose, VERB_LVL['debug'])
        noise_arr = np.random.normal(0, noise_std_val, raw_arr.shape)
        noised_reco_arr = reco_func(raw_arr + noise_arr, *reco_args,
                                    **reco_kws)
        # new uncorrelated noise
        noise_arr = np.random.normal(0, noise_std_val, reco_arr.shape)
        noised_optim_arr = optim_func(noise_arr, *optim_args, **optim_kws)

        mean_noised_reco_arr, sosd_noised_reco_arr, _ = \
            fc.next_mean_and_sosd(
                np.real(noised_reco_arr),
                mean_noised_reco_arr, sosd_noised_reco_arr, i)
        mean_noised_optim_arr, sosd_noised_optim_arr, _ = \
            fc.next_mean_and_sosd(
                np.real(noised_optim_arr),
                mean_noised_optim_arr, sosd_noised_optim_arr, i)

    noise_reco_arr = fc.sosd2stdev(sosd_noised_reco_arr, num)
    noise_optim_arr = fc.sosd2stdev(sosd_noised_optim_arr, num)

    snr_arr = np.abs(reco_arr) / noise_reco_arr
    g_factor_arr = g_factor(noise_optim_arr, noise_reco_arr, sampling_ratio)
    return snr_arr, g_factor_arr
Example #28
0
def _test(use_cache=True):
    # x = np.linspace(1, 40, 5)
    x = np.array([2, 5, 7, 20, 40])
    tau_arr = np.linspace(2, 1000, 4000)
    a_arr = np.linspace(500, 4000, 4000)

    import pymrt.util
    import os

    base_dir = fc.realpath('~/hd1/TEMP')
    filepath = os.path.join(base_dir, 'tau_arr.npz')
    if os.path.isfile(filepath) and use_cache:
        y = np.load(filepath)['y']
    else:
        y = np.zeros((len(tau_arr), len(a_arr), len(x)))
        for i, a in enumerate(a_arr):
            for j, tau in enumerate(tau_arr):
                y[j, i] = func_exp_decay(x, tau, a)
        np.savez(filepath, y=y)

    def eval_dist(a, b, axis=-1):
        mu = np.nanmean(a, axis) - b
        std = np.nanstd(a, axis)
        return np.mean(mu), np.mean(std)

    elapsed('gen_tau_phantom')

    snr = 20
    p = 1 / snr
    n = np.max(a_arr) * p * (np.random.random(y.shape) - 0.5)

    m = [True, True, False, False, False]

    # print(fit_exp_loglin(y + n, x)['tau'])
    # print(fit_exp_loglin(y + n, x, weighted=False)['tau'])
    # print(fit_exp_tau_quadr(y + n, x))

    print('quad', eval_dist(fit_exp_quad(y + n, x, m)['tau'], tau_arr))
    elapsed('quad')

    print('diff', eval_dist(fit_exp_diff(y + n, x, m)['tau'], tau_arr))
    elapsed('diff')

    print('quadr', eval_dist(fit_exp_quadr(y + n, x, m)['tau'], tau_arr))
    elapsed('quadr')

    print('quadr_w2',
          eval_dist(fit_exp_quadr(y + n, x, m, window_size=2)['tau'], tau_arr))
    elapsed('quadr_w2')

    print('quadr_w3',
          eval_dist(fit_exp_quadr(y + n, x, m, window_size=3)['tau'], tau_arr))
    elapsed('quadr_w3')

    print('arlo', eval_dist(fit_exp_arlo(y + n, x, m)['tau'], tau_arr))
    elapsed('arlo')

    print('loglin', eval_dist(fit_exp_loglin(y + n, x, m)['tau'], tau_arr))
    elapsed('loglin')

    print(
        'loglin_w',
        eval_dist(
            fit_exp_loglin(y + n, x, m, variant='weighted_reverse')['tau'],
            tau_arr))
    elapsed('loglin_w')

    # print('leasq',
    #       eval_dist(fit_exp_curve_fit(y + n, x, init=[5, 4000])['tau'],
    # tau_arr))
    # elapsed('leasq')

    msg(report())
                        cmap=plt.cm.hot,
                        rstride=1,
                        cstride=1,
                        linewidth=0.005,
                        antialiased=False)
        # ax.plot_surface(
        #     X, Y, measured, cmap=plt.cm.ocean,
        #     rstride=1, cstride=1, linewidth=0.01, antialiased=False)
        # ax.plot_surface(
        #     X, Y, fitted, cmap=plt.cm.bone,
        #     rstride=1, cstride=1, linewidth=0.01, antialiased=False)


# ======================================================================
if __name__ == '__main__':
    msg(__doc__.strip())
    # check_dynamics_operator_symbolic()
    # fc.elapsed('check_symbolic')

    # check_dynamics_operator()
    # fc.elapsed'check_dynamics_operator')

    # check_mt_sequence()
    # fc.elapsed'check_mt_sequence')

    # check_approx_propagator()
    # fc.elapsed'check_approx_propagator')

    # check_z_spectrum(
    #     SpinModel(100.0, (0.5, 0.3, 0.1, 0.1), (GAMMA['1H'] * B0,) * 4,
    #               (0.25, 0.8, 0.001, 1.0), (20.0, 60.0, 8e4, 5e4),
Example #30
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