Пример #1
0
def fse(pd,
        r1,
        r2=None,
        receive=None,
        gfactor=None,
        te=0.02,
        tr=5,
        sigma=None,
        device=None):
    """Simulate data generated by a (simplified) Fast Spin-Echo (FSE) sequence.

    Tissue parameters
    -----------------
    pd : tensor_like
        Proton density
    r1 : tensor_like
        Longitudinal relaxation rate, in 1/sec
    r2 : tensor_like, optional
        Transverse relaxation rate, in 1/sec.

    Fields
    ------
    receive : tensor_like, optional
        Receive B1 field
    gfactor : tensor_like, optional
        G-factor map.
        If provided and `sigma` is not `None`, the g-factor map is used
        to sample non-stationary noise.

    Sequence parameters
    -------------------
    te : float, default=3e-3
        Echo time, in sec
    tr : float default=2.3
        Repetition time, in sec.

    Noise
    -----
    sigma : float, optional
        Standard-deviation of the sampled noise (no sampling if `None`)

    Returns
    -------
    sim : tensor
        Simulated FSE image

    """

    pd, r1, r2, receive, gfactor \
        = utils.to_max_backend(pd, r1, r2, receive, gfactor)
    pd, r1, r2, receive, gfactor \
        = utils.to(pd, r1, r2, receive, gfactor, device=device)

    if receive is not None:
        pd = pd * receive
    del receive

    e1 = r1.mul(tr).neg_().exp_()
    e2 = r2.mul(te).neg_().exp_()

    signal = pd * (1 - e1) * e2

    # noise
    signal = add_noise(signal, std=sigma)
    return signal
Пример #2
0
def mp2rage_old(pd,
                r1,
                r2s=None,
                transmit=None,
                receive=None,
                gfactor=None,
                tr=6.25,
                ti1=0.8,
                ti2=2.2,
                tx=None,
                te=None,
                fa=(4, 5),
                n=160,
                eff=0.96,
                sigma=None,
                device=None,
                return_combined=True):
    """Simulate data generated by a (simplified) MP2RAGE sequence.

    The defaults are parameters used at 3T in the original MP2RAGE paper.
    However, I don't get a nice image with these parameters when applied
    to maps obtained at 3T with the hmri toolbox.
    Here are (unrealistic) parameters that seem to give a decent contrast:
    tr=6.25, ti1=1.4, ti2=4.5, tx=5.8e-3, fa=(4, 5), n=160, eff=0.96

    Tissue parameters
    -----------------
    pd : tensor_like
        Proton density
    r1 : tensor_like
        Longitudinal relaxation rate, in 1/sec
    r2s : tensor_like, optional
        Transverse relaxation rate, in 1/sec.
        If not provided, T2*-bias is not included.

    Fields
    ------
    transmit : tensor_like, optional
        Transmit B1 field
    receive : tensor_like, optional
        Receive B1 field
    gfactor : tensor_like, optional
        G-factor map.
        If provided and `sigma` is not `None`, the g-factor map is used
        to sample non-stationary noise.

    Sequence parameters
    -------------------
    tr : float default=6.25
        Full Repetition time, in sec.
        (Time between two inversion pulses)
    ti1 : float, default=0.8
        First inversion time, in sec.
        (Time between inversion pulse and middle of the first echo train)
    ti2 : float, default=2.2
        Second inversion time, in sec.
        (Time between inversion pulse and middle of the second echo train)
    tx : float, default=te*2 or 5.8e-3
        Excitation repetition time, in sec.
        (Time between two excitation pulses within the echo train)
    te : float, default=minitr/2
        Echo time, in sec.
    fa : float or (float, float), default=(4, 5)
        Flip angle of the first and second acquisition block, in deg
        If only one value, it is shared between the blocks.
    n : int, default=160
        Number of excitation pulses (= phase encoding steps) per train.
    eff : float, default=0.96
        Efficiency of the inversion pulse.

    Noise
    -----
    sigma : float, optional
        Standard-deviation of the sampled Rician noise (no sampling if `None`)

    Returns
    -------
    mp2rage : tensor, if return_combined is True
        Simulated MP2RAGE image

    image1 : tensor, if return_combined is False
        Image at first inversion time
    image2 : tensor, if return_combined is False
        Image at second inversion time

    References
    ----------
    ..[1] "MP2RAGE, a self bias-field corrected sequence for improved
        segmentation and T1-mapping at high field."
        Marques JP, Kober T, Krueger G, van der Zwaag W, Van de Moortele PF, Gruetter R.
        Neuroimage. 2010 Jan 15;49(2):1271-81.
        doi: 10.1016/j.neuroimage.2009.10.002

    """

    pd, r1, r2s, transmit, receive, gfactor \
        = utils.to_max_backend(pd, r1, r2s, transmit, receive, gfactor)
    pd, r1, r2s, transmit, receive, gfactor \
        = utils.to(pd, r1, r2s, transmit, receive, gfactor, device=device)
    backend = utils.backend(pd)

    if tx is None and te is None:
        tx = 5.8e-3
    tx = tx or 2 * te  # Time between excitation pulses
    te = te or tx / 2  # Echo time
    fa1, fa2 = py.make_list(fa, 2)
    fa1 = fa1 * constants.pi / 180  # Flip angle of first GRE block
    fa2 = fa2 * constants.pi / 180  # Flip angle of second GRE block
    n = n or min(pd.shape)  # Number of readouts (PE steps) per loop
    tr1 = n * tx  # First GRE block
    tr2 = n * tx  # Second GRE block
    tp = ti1 - tr1 / 2  # Preparation time
    tw = ti2 - tr2 / 2 - ti1 - tr1 / 2  # Wait time between GRE blocks
    td = tr - ti2 - tr2 / 2  # Recovery time
    m = n // 2  # Middle of echo train

    if transmit is not None:
        fa1 = transmit * fa1
        fa2 = transmit * fa2
    del transmit
    fa1 = torch.as_tensor(fa1, **backend)
    fa2 = torch.as_tensor(fa2, **backend)

    # precompute exponential terms
    ex = r1.mul(-tx).exp()
    ep = r1.mul(-tp).exp()
    ew = r1.mul(-tw).exp()
    ed = r1.mul(-td).exp()
    e1 = r1.mul(-tr).exp()
    c1 = fa1.cos()
    c2 = fa2.cos()

    # steady state
    mss = (1 - ep) * (c1 * ex).pow(n)
    mss = mss + (1 - ex) * (1 - (c1 * ex).pow(n)) / (1 - c1 * ex)
    mss = mss * ew + (1 - ew)
    mss = mss * (c2 * ex).pow(n)
    mss = mss + (1 - ex) * (1 - (c2 * ex).pow(n)) / (1 - c2 * ex)
    mss = mss * ed + (1 - ed)
    mss = mss * pd / (1 + eff * (c1 * c2).pow(n) * e1)

    # IR components
    mi1 = -eff * mss * ep / pd + (1 - ep)
    mi1 = mi1 * (c1 * ex).pow(m - 1)
    mi1 = mi1 + (1 - ex) * (1 - (c1 * ex).pow(m - 1)) / (1 - c1 * ex)
    mi1 = mi1 * fa1.sin()
    mi1 = mi1.abs()

    mi2 = (mss / pd - (1 - ed)) / (ed * (c2 * ex).pow(m))
    mi2 = mi2 + (1 - ex) * (1 - (c2 * ex).pow(-m)) / (1 - c2 * ex)
    mi2 = mi2 * fa2.sin()
    mi2 = mi2.abs()

    if return_combined and not sigma:
        m = (mi1 * mi2) / (mi1.square() + mi2.square())
        m = torch.where(~torch.isfinite(m), m.new_zeros([]), m)
        return m

    # Common component (pd, B1-, R2*)
    if receive is not None:
        pd = pd * receive
    del receive

    mi1 = mi1 * pd
    mi2 = mi2 * pd

    if r2s is not None:
        e2 = r2s.mul(-te).exp_()
        mi1 = mi1 * e2
        mi2 = mi2 * e2
    del r2s

    # noise
    mi1 = add_noise(mi1, std=sigma, gfactor=gfactor)
    mi2 = add_noise(mi2, std=sigma, gfactor=gfactor)

    if return_combined:
        m = (mi1 * mi2) / (mi1.square() + mi2.square())
        m = torch.where(~torch.isfinite(m), m.new_zeros([]), m)
        return m
    else:
        mi1 = torch.where(~torch.isfinite(mi1), mi1.new_zeros([]), mi1)
        mi2 = torch.where(~torch.isfinite(mi2), mi2.new_zeros([]), mi2)
        return mi1, mi2
Пример #3
0
def mp2rage(pd,
            r1,
            r2s=None,
            transmit=None,
            receive=None,
            gfactor=None,
            tr=6.25,
            ti1=0.8,
            ti2=2.2,
            tx=None,
            te=None,
            fa=(4, 5),
            n=160,
            eff=0.96,
            sigma=None,
            device=None,
            return_combined=True):
    """Simulate data generated by a (simplified) MP2RAGE sequence.

    The defaults are parameters used at 3T in the original MP2RAGE paper.
    However, I don't get a nice image with these parameters when applied
    to maps obtained at 3T with the hmri toolbox.
    Here are (unrealistic) parameters that seem to give a decent contrast:
    tr=6.25, ti1=1.4, ti2=4.5, tx=5.8e-3, fa=(4, 5), n=160, eff=0.96

    Tissue parameters
    -----------------
    pd : tensor_like
        Proton density
    r1 : tensor_like
        Longitudinal relaxation rate, in 1/sec
    r2s : tensor_like, optional
        Transverse relaxation rate, in 1/sec.
        If not provided, T2*-bias is not included.

    Fields
    ------
    transmit : tensor_like, optional
        Transmit B1 field
    receive : tensor_like, optional
        Receive B1 field
    gfactor : tensor_like, optional
        G-factor map.
        If provided and `sigma` is not `None`, the g-factor map is used
        to sample non-stationary noise.

    Sequence parameters
    -------------------
    tr : float default=6.25
        Full Repetition time, in sec.
        (Time between two inversion pulses)
    ti1 : float, default=0.8
        First inversion time, in sec.
        (Time between inversion pulse and middle of the first echo train)
    ti2 : float, default=2.2
        Second inversion time, in sec.
        (Time between inversion pulse and middle of the second echo train)
    tx : float, default=te*2 or 5.8e-3
        Excitation repetition time, in sec.
        (Time between two excitation pulses within the echo train)
    te : float, default=tx/2
        Echo time, in sec.
    fa : float or (float, float), default=(4, 5)
        Flip angle of the first and second acquisition block, in deg
        If only one value, it is shared between the blocks.
    n : int, default=160
        Number of excitation pulses (= phase encoding steps) per train.
    eff : float, default=0.96
        Efficiency of the inversion pulse.

    Noise
    -----
    sigma : float, optional
        Standard-deviation of the sampled Rician noise (no sampling if `None`)

    Returns
    -------
    mp2rage : tensor, if return_combined is True
        Simulated MP2RAGE image

    image1 : tensor, if return_combined is False
        Image at first inversion time
    image2 : tensor, if return_combined is False
        Image at second inversion time

    References
    ----------
    ..[1] "MP2RAGE, a self bias-field corrected sequence for improved
        segmentation and T1-mapping at high field."
        Marques JP, Kober T, Krueger G, van der Zwaag W, Van de Moortele PF, Gruetter R.
        Neuroimage. 2010 Jan 15;49(2):1271-81.
        doi: 10.1016/j.neuroimage.2009.10.002

    """

    pd, r1, r2s, transmit, receive, gfactor \
        = utils.to_max_backend(pd, r1, r2s, transmit, receive, gfactor)
    pd, r1, r2s, transmit, receive, gfactor \
        = utils.to(pd, r1, r2s, transmit, receive, gfactor, device=device)

    if tx is None and te is None:
        tx = 5.8e-3
    tx = tx or 2 * te  # Time between excitation pulses
    te = te or tx / 2  # Echo time
    fa1, fa2 = py.make_list(fa, 2)
    fa1 = fa1 * constants.pi / 180  # Flip angle of first GRE block
    fa2 = fa2 * constants.pi / 180  # Flip angle of second GRE block
    n = n or min(pd.shape)  # Number of readouts (PE steps) per loop
    tr1 = n * tx  # First GRE block
    tr2 = n * tx  # Second GRE block
    tp = ti1 - tr1 / 2  # Preparation time
    tw = ti2 - tr2 / 2 - ti1 - tr1 / 2  # Wait time between GRE blocks
    td = tr - ti2 - tr2 / 2  # Recovery time

    if return_combined and not sigma:
        m = mp2rage_nonoise(pd, r1, tx, tp, tw, td, tr, fa1, fa2, n, eff,
                            transmit)

        m = torch.where(~torch.isfinite(m), m.new_zeros([1]), m)
        return m

    mi1, mi2 = mp2rage_uncombined(pd, r1, r2s, tx, tp, tw, td, tr, te, fa1,
                                  fa2, n, eff, transmit, receive)

    # noise
    mi1 = add_noise(mi1, std=sigma, gfactor=gfactor)
    mi2 = add_noise(mi2, std=sigma, gfactor=gfactor)

    if return_combined:
        m = mp2rage_from_ir(mi1, mi2)
        m = torch.where(~torch.isfinite(m), m.new_zeros([1]), m)
        return m
    else:
        mi1 = torch.where(~torch.isfinite(mi1), mi1.new_zeros([]), mi1)
        mi2 = torch.where(~torch.isfinite(mi2), mi2.new_zeros([]), mi2)
        return mi1, mi2
Пример #4
0
def inpaint(*inputs,
            missing='nan',
            output=None,
            device=None,
            verbose=1,
            max_iter_rls=10,
            max_iter_cg=32,
            tol_rls=1e-5,
            tol_cg=1e-5):
    """Inpaint missing values by minimizing Joint Total Variation.

    Parameters
    ----------
    *inputs : str or tensor or (tensor, tensor)
        Either a path to a volume file or a tuple `(dat, affine)`, where
        the first element contains the volume data and the second contains
        the orientation matrix.
    missing : 'nan' or scalar or callable, default='nan'
        Mask of the missing data. If a scalar, all voxels with that value
        are considered missing. If a function, it should return the mask
        of missing values when applied to the multi-channel data. Else,
        non-finite values are assumed missing.
    output : [sequence of] str, optional
        Output filename(s).
        If the input is not a path, the unstacked data is not written
        on disk by default.
        If the input is a path, the default output filename is
        '{dir}/{base}.pool{ext}', where `dir`, `base` and `ext`
        are the directory, base name and extension of the input file,
        `i` is the coordinate (starting at 1) of the slice.
    verbose : int, default=1
    device : torch.device, optional
    max_iter_rls : int, default=10
    max_iter_cg : int, default=32
    tol_rls : float, default=1e-5
    tol_cg : float, default=1e-5

    Returns
    -------
    *output : str or (tensor, tensor)
        If the input is a path, the output path is returned.
        Else, the pooled data and orientation matrix are returned.

    """
    # Preprocess
    dirs = []
    bases = []
    exts = []
    fnames = []
    nchannels = []
    dat = []
    aff = None
    for i, inp in enumerate(inputs):
        is_file = isinstance(inp, str)
        if is_file:
            fname = inp
            dir, base, ext = py.fileparts(fname)
            fnames.append(inp)
            dirs.append(dir)
            bases.append(base)
            exts.append(ext)

            f = io.volumes.map(fname)
            if aff is None:
                aff = f.affine
            f = ensure_4d(f)
            dat.append(f.fdata(device=device))

        else:
            fnames.append(None)
            dirs.append('')
            bases.append(f'{i+1}')
            exts.append('.nii.gz')
            if isinstance(inp, (list, tuple)):
                if aff is None:
                    dat1, aff = inp
                else:
                    dat1, _ = inp
            else:
                dat1 = inp
            dat.append(torch.as_tensor(dat1, device=device))
            del dat1
        nchannels.append(dat[-1].shape[-1])
    dat = utils.to(*dat, dtype=torch.float, device=utils.max_device(dat))
    if not torch.is_tensor(dat):
        dat = torch.cat(dat, dim=-1)
    dat = utils.movedim(dat, -1, 0)  # (channels, *spatial)

    # Set missing data
    if missing != 'nan':
        if not callable(missing):
            missingval = utils.make_vector(missing,
                                           dtype=dat.dtype,
                                           device=dat.device)
            missing = lambda x: utils.isin(x, missingval)
        dat[missing(dat)] = nan
    dat[~torch.isfinite(dat)] = nan

    # Do it
    if aff is not None:
        vx = spatial.voxel_size(aff)
    else:
        vx = 1
    dat = do_inpaint(dat,
                     voxel_size=vx,
                     verbose=verbose,
                     max_iter_rls=max_iter_rls,
                     tol_rls=tol_rls,
                     max_iter_cg=max_iter_cg,
                     tol_cg=tol_cg)

    # Postprocess
    dat = utils.movedim(dat, 0, -1)
    dat = dat.split(nchannels, dim=-1)
    output = py.make_list(output, len(dat))
    for i in range(len(dat)):
        if fnames[i] and not output[i]:
            output[i] = '{dir}{sep}{base}.inpaint{ext}'
        if output[i]:
            if fnames[i]:
                output[i] = output[i].format(dir=dirs[i] or '.',
                                             base=bases[i],
                                             ext=exts[i],
                                             sep=os.path.sep)
                io.volumes.save(dat[i], output[i], like=fnames[i], affine=aff)
            else:
                output[i] = output[i].format(sep=os.path.sep)
                io.volumes.save(dat[i], output[i], affine=aff)

    dat = [
        output[i] if fnames[i] else
        (dat[i], aff) if aff is not None else dat[i] for i in range(len(dat))
    ]
    if len(dat) == 1:
        dat = dat[0]
    return dat
Пример #5
0
def spgr(pd,
         r1,
         r2s=None,
         mt=None,
         transmit=None,
         receive=None,
         gfactor=None,
         te=0,
         tr=25e-3,
         fa=20,
         sigma=None,
         device=None):
    """Simulate data generated by a Spoiled Gradient-Echo (SPGR/FLASH) sequence.

    Tissue parameters
    -----------------
    pd : tensor_like
        Proton density
    r1 : tensor_like
        Longitudinal relaxation rate, in 1/sec
    r2s : tensor_like, optional
        Transverse relaxation rate, in 1/sec. Mandatory if any `te > 0`.
    mt : tensor_like, optional
        MTsat. Mandatory if any `mtpulse == True`.

    Fields
    ------
    transmit : tensor_like, optional
        Transmit B1 field
    receive : tensor_like, optional
        Receive B1 field
    gfactor : tensor_like, optional
        G-factor map.
        If provided and `sigma` is not `None`, the g-factor map is used
        to sample non-stationary noise.

    Sequence parameters
    -------------------
    te : float, default=0
        Echo time, in sec
    tr : float default=2.5e-3
        Repetition time, in sec
    fa : float, default=20
        Flip angle, in deg

    Noise
    -----
    sigma : float, optional
        Standard-deviation of the sampled Rician noise (no sampling if `None`)
    Returns
    -------
    sim : tensor
        Simulated SPGR image

    """
    pd, r1, r2s, mt, transmit, receive, gfactor \
        = utils.to_max_backend(pd, r1, r2s, mt, transmit, receive, gfactor)
    pd, r1, r2s, mt, transmit, receive, gfactor \
        = utils.to(pd, r1, r2s, mt, transmit, receive, gfactor, device=device)
    backend = utils.backend(pd)

    fa = fa * constants.pi / 180.
    if transmit is not None:
        fa = fa * transmit
    del transmit
    fa = torch.as_tensor(fa, **backend)

    if receive is not None:
        pd = pd * receive
    del receive
    pd = pd * fa.sin()
    fa = fa.cos()

    e1, r1 = r1.mul(tr).neg_().exp(), None
    signal = pd * (1 - e1)

    if mt is not None:
        omt = mt.neg().add_(1)
        signal *= omt
        signal /= (1 - fa * omt * e1)
        del omt
    else:
        signal /= (1 - fa * e1)

    if r2s is not None:
        e2, r2s = r2s.mul(te).neg_().exp(), None
        signal *= e2
        del e2

    # noise
    signal = add_noise(signal, std=sigma)
    return signal
Пример #6
0
def mprage(pd,
           r1,
           r2s=None,
           transmit=None,
           receive=None,
           gfactor=None,
           tr=2.3,
           ti=0.9,
           tx=None,
           te=None,
           fa=9,
           n=160,
           eff=0.96,
           sigma=None,
           device=None):
    """Simulate data generated by a (simplified) MP-RAGE sequence.

    Default parameters mimic the ADNI-3 protocol on 3T Siemens scanners.
    Our Implementation is based on the MP2RAGE paper, where the sequence
    is stripped from the second GRE readout block.

    Tissue parameters
    -----------------
    pd : tensor_like
        Proton density
    r1 : tensor_like
        Longitudinal relaxation rate, in 1/sec
    r2s : tensor_like, optional
        Transverse relaxation rate, in 1/sec.
        If not provided, T2*-bias is not included.

    Fields
    ------
    transmit : tensor_like, optional
        Transmit B1 field
    receive : tensor_like, optional
        Receive B1 field
    gfactor : tensor_like, optional
        G-factor map.
        If provided and `sigma` is not `None`, the g-factor map is used
        to sample non-stationary noise.

    Sequence parameters
    -------------------
    tr : float default=2.3
        Repetition time, in sec.
        (Time between two inversion pulses)
    ti : float, default=0.9
        Inversion time, in sec.
        (Time between inversion pulse and middle of the echo train)
    tx : float, default=2*te or 6e-3
        Excitation repetition time, in sec
        (Time between two excitation pulses within the echo train)
    te : float, default=tx/2
        Echo time, in sec
    fa : float, default=9
        Flip angle, in deg
    n : int, default=160
        Number of excitation pulses (= phase encoding steps) per train.
    eff : float, default=0.96
        Efficiency of the inversion pulse.

    Noise
    -----
    sigma : float, optional
        Standard-deviation of the sampled Rician noise (no sampling if `None`)

    Returns
    -------
    sim : tensor
        Simulated MPRAGE image

    References
    ----------
    ..[1] "MP2RAGE, a self bias-field corrected sequence for improved
        segmentation and T1-mapping at high field."
        Marques JP, Kober T, Krueger G, van der Zwaag W, Van de Moortele PF, Gruetter R.
        Neuroimage. 2010 Jan 15;49(2):1271-81.
        doi: 10.1016/j.neuroimage.2009.10.002

    """

    pd, r1, r2s, transmit, receive, gfactor \
        = utils.to_max_backend(pd, r1, r2s, transmit, receive, gfactor)
    pd, r1, r2s, transmit, receive, gfactor \
        = utils.to(pd, r1, r2s, transmit, receive, gfactor, device=device)
    backend = utils.backend(pd)

    if tx is None and te is None:
        tx = 6e-3
    tx = tx or 2 * te  # Time between excitation pulses
    te = te or tx / 2  # Echo time
    fa = fa * constants.pi / 180  # Flip angle of GRE block
    n = n or min(pd.shape)  # Number of readouts (PE steps) per loop
    tr1 = n * tx  # GRE block
    tp = ti - tr1 / 2  # Preparation time
    td = tr - ti - tr1 / 2  # Recovery time
    m = n // 2  # Middle of echo train

    if transmit is not None:
        fa = transmit * fa
    del transmit
    fa = torch.as_tensor(fa, **backend)

    # precompute exponential terms
    ex = r1.mul(-tx).exp()
    ep = r1.mul(-tp).exp()
    ed = r1.mul(-td).exp()
    e1 = r1.mul(-tr).exp()
    c = fa.cos()

    # steady state
    s = (1 - ep) * (c * ex).pow(n)
    s = s + (1 - ex) * (1 - (c * ex).pow(n)) / (1 - c * ex)
    s = s * ed + (1 - ed)
    s = s * pd / (1 + eff * c.pow(n) * e1)

    # IR component
    s = -eff * s * ep / pd + (1 - ep)
    s = s * (c * ex).pow(m - 1)
    s = s + (1 - ex) * (1 - (c * ex).pow(m - 1)) / (1 - c * ex)
    s = s * fa.sin()
    s = s.abs()

    # Modulation (PD, B1-, R2*)
    if receive is not None:
        pd = pd * receive
    del receive

    s = s * pd

    if r2s is not None:
        e2 = r2s.mul(-te).exp_()
        s = s * e2
    del r2s

    # noise
    s = add_noise(s, std=sigma, gfactor=gfactor)
    return s