Exemplo n.º 1
0
def dz_ramp_beta(n, ratio, tb, d1, d2):
    r"""Creates a sloped beta filter for designing sloped profiles
    """
    ftw = dinf(d1, d2) / tb  # fractional transition width
    shift = n / 4

    f = np.array([
        0, shift - (1 + ftw) * tb / 2, shift - (1 - ftw) * tb / 2, shift +
        (1 - ftw) * tb / 2, shift + (1 + ftw) * tb / 2, n / 2
    ]) / (n / 2)  # edges, normalized to Nyquist

    # left amplitude is derived from the fact that the slope we want is
    # (ratio - 1)/tb, but we specify the amplitudes at the more closely spaced
    # edges which are (1 - ftw) * tb apart
    m = np.array([0, 0, (1 - ftw) * (ratio - 1) + 1, 1, 0, 0])  # amp @ edges

    w = np.array([d1 / d2, 1, d1 / d2])  # band error weights

    # design the filter, firls requires odd numtaps, so design 1 extra & trim
    b = signal.firls(n + 1, f, m, w)
    b = b[0:n]
    # hilbert transformation to suppress negative passband, and demod to DC
    b = signal.hilbert(b)
    b = b * np.exp(-1j * 2 * np.pi / n * shift * np.linspace(0, n - 1, n)) / 2
    b = b * np.exp(-1j * np.pi / n * shift)

    # return a normalized version in order to get 1 at DC
    return np.expand_dims(b / np.sum(b), 0)
Exemplo n.º 2
0
def dzlp(n=64, tb=4, d1=0.01, d2=0.01):
    di = dinf(d1, d2)
    w = di / tb
    f = np.asarray([0, (1 - w) * (tb / 2), (1 + w) * (tb / 2), (n / 2)]) / n
    m = [1, 0]
    w = [1, d1 / d2]

    h = signal.remez(n, f, m, w)

    return h
Exemplo n.º 3
0
def dzmp(n=64, tb=4, d1=0.01, d2=0.01):
    n2 = 2 * n - 1
    di = 0.5 * dinf(2 * d1, 0.5 * d2 * d2)
    w = di / tb
    f = np.asarray([0, (1 - w) * (tb / 2), (1 + w) * (tb / 2), (n / 2)]) / n
    m = [1, 0]
    w = [1, 2 * d1 / (0.5 * d2 * d2)]

    hl = signal.remez(n2, f, m, w)

    h = fmp(hl)

    return h
Exemplo n.º 4
0
def dzls(N=64, tb=4, d1=0.01, d2=0.01):
    di = dinf(d1, d2)
    w = di / tb
    f = np.asarray([0, (1 - w) * (tb / 2), (1 + w) * (tb / 2), (N / 2)])
    f = f / (N / 2)
    m = [1, 1, 0, 0]
    w = [1, d1 / d2]

    h = signal.firls(N + 1, f, m, w)
    # shift the filter half a sample to make it symmetric, like in MATLAB
    c = np.exp(
        1j * 2 * np.pi / (2 * (N + 1)) *
        np.concatenate([np.arange(0, N / 2 + 1, 1),
                        np.arange(-N / 2, 0, 1)]))
    h = np.real(np.fft.ifft(np.multiply(np.fft.fft(h), c)))
    # lop off extra sample
    h = h[:N]

    return h
Exemplo n.º 5
0
def dz_hadamard_b(n=128, g=5, gind=1, tb=4, d1=0.01, d2=0.01, shift=32):
    r"""Design a pulse with hadamard encoding

    Args:
        n (int): number of time points.
        g (int): order of the Hadamard matrix.
        gind (int): index of vector to use from Hadamard matrix for encoding.
        tb (int): time bandwidth product.
        d1 (float): passband ripple level in :math:'M_0^{-1}'.
        d2 (float): stopband ripple level in :math:'M_0^{-1}'.
        shift (int): n time points shift of pulse.

    Returns:
        b (array): SLR beta parameter.

    References:
            Souza, S.P., Szumowski, J., Dumoulin, C.L., Plewes, D.P. &
            Glover, G. 'Sima: Simultaneous multislice acquisition of MR images
            by hadamard - encoded excitation. J.Comput.Assist.Tomogr. 12,
            1026–1030(1988).
    """

    H = linalg.hadamard(g)
    encode = H[gind - 1, :]

    ftw = dinf(d1, d2) / tb  # fractional transition width of the slab profile

    if gind == 1:  # no sub-slices
        b = dzls(n, tb, d1, d2)
    else:
        # left stopband
        f = np.asarray([0, shift - (1 + ftw) * (tb / 2)])
        m = np.asarray([0, 0])
        w = np.asarray([d1 / d2])
        # first sub-band
        ii = 1
        gcent = shift + (ii - g / 2 - 1 / 2) * tb / g  # first band center
        # first band left edge
        f = np.append(f, gcent - (tb / g / 2 - ftw * (tb / 2)))
        m = np.append(m, encode[ii - 1])
        if encode[ii - 1] != encode[ii]:
            # add the first band's right edge and its amplitude, and a weight
            f = np.append(f, gcent + (tb / g / 2 - ftw * (tb / 2)))
            m = np.append(m, encode[ii - 1])
            w = np.append(w, 1)
        # middle sub-bands
        for ii in range(2, g):
            gcent = shift + (ii - g / 2 - 1 / 2) * tb / g  # center of band
            if encode[ii - 1] != encode[ii - 2]:
                # add a left edge and amp for this band
                f = np.append(f, gcent - (tb / g / 2 - ftw * (tb / 2)))
                m = np.append(m, encode[ii - 1])
            if encode[ii - 1] != encode[ii]:
                # add a right edge and its amp, and a weight for this band
                f = np.append(f, gcent + (tb / g / 2 - ftw * (tb / 2)))
                m = np.append(m, encode[ii - 1])
                w = np.append(w, 1)
        # last sub-band
        ii = g
        gcent = shift + (ii - g / 2 - 1 / 2) * tb / g  # center of last band
        if encode[ii - 1] != encode[ii - 2]:
            # add a left edge and amp for the last band
            f = np.append(f, gcent - (tb / g / 2 - ftw * (tb / 2)))
            m = np.append(m, encode[ii - 1])
        # add a right edge and its amp, and a weight for the last band
        f = np.append(f, gcent + (tb / g / 2 - ftw * (tb / 2)))
        m = np.append(m, encode[ii - 1])
        w = np.append(w, 1)
        # right stop-band
        f = np.append(f, (shift + (1 + ftw) * (tb / 2), (n / 2))) / (n / 2)
        m = np.append(m, [0, 0])
        w = np.append(w, d1 / d2)

        # separate the positive and negative bands
        mp = (m > 0).astype(float)
        mn = (m < 0).astype(float)

        # design the positive and negative filters
        c = np.exp(1j * 2 * np.pi / (2 * (n + 1)) * np.concatenate(
            [np.arange(0, n / 2 + 1, 1),
             np.arange(-n / 2, 0, 1)]))
        bp = signal.firls(n + 1, f, mp, w)  # the positive filter
        bn = signal.firls(n + 1, f, mn, w)  # the negative filter

        # combine the filters and demodulate
        b = sp.ifft(np.multiply(sp.fft(bp - bn, center=False), c),
                    center=False)

        b = np.real(b[:n])
        # hilbert transform to suppress negative passband
        b = signal.hilbert(b)
        # demodulate to DC
        c_shift = np.exp(-1j * 2 * np.pi / n * shift * np.arange(0, n, 1)) / 2
        c_shift *= np.exp(-1j * np.pi / n * shift)
        b = np.multiply(b, c_shift)

    return b
Exemplo n.º 6
0
def dz_gslider_b(n=128,
                 g=5,
                 gind=1,
                 tb=4,
                 d1=0.01,
                 d2=0.01,
                 phi=np.pi,
                 shift=32):
    r"""Design a g-slider pulse b

    Args:
        n (int): number of time points.
        g (int): number of sub-slices.
        gind (int): subslice index.
        tb (int): time bandwidth product.
        d1 (float): passband ripple level in :math:'M_0^{-1}'.
        d2 (float): stopband ripple level in :math:'M_0^{-1}'.
        phi (float): subslice phase.
        shift (int): n time points shift of pulse.

    Returns:
        b (array): SLR beta parameter.

    References:
        Setsompop, K. et al. 'High-resolution in vivo diffusion imaging of the
        human brain with generalized slice dithered enhanced resolution:
        Simultaneous multislice (gSlider-SMS). Magn. Reson. Med.79, 141–151
        (2018).
    """
    ftw = dinf(d1, d2) / tb  # fractional transition width of the slab profile

    if np.fmod(g, 2) and gind == int(np.ceil(g / 2)):  # centered sub-slice
        if g == 1:  # no sub-slices, as a sanity check
            b = dzls(n, tb, d1, d2)
        else:
            # Design 2 filters, to allow arbitrary phases on the subslice the
            # first is a wider notch filter with '0's where it the subslice
            # appears, and the second is the subslice. Multiply the subslice by
            # its phase and add the filters.
            f = np.asarray([
                0, (1 / g - ftw) * (tb / 2), (1 / g + ftw) * (tb / 2),
                (1 - ftw) * (tb / 2), (1 + ftw) * (tb / 2), (n / 2)
            ])
            f = f / (n / 2)
            m_notch = [0, 0, 1, 1, 0, 0]
            m_sub = [1, 1, 0, 0, 0, 0]
            w = [1, 1, d1 / d2]

            b_notch = signal.firls(n + 1, f, m_notch, w)  # the notched filter
            b_sub = signal.firls(n + 1, f, m_sub, w)  # the subslice filter
            # add them with the subslice phase
            b = np.add(b_notch, np.multiply(np.exp(1j * phi), b_sub))
            # shift the filter half a sample to make it symmetric,
            # like in MATLAB
            c = np.exp(1j * 2 * np.pi / (2 * (n + 1)) * np.concatenate(
                [np.arange(0, n / 2 + 1, 1),
                 np.arange(-n / 2, 0, 1)]))
            b = sp.ifft(np.multiply(sp.fft(b, center=False), c), center=False)
            # lop off extra sample
            b = b[:n]

    else:
        # design filters for the slab and the subslice, hilbert xform them
        # to suppress their left bands,
        # then demodulate the result back to DC
        gcent = shift + (gind - g / 2 - 1 / 2) * tb / g
        if gind > 1 and gind < g:
            # separate transition bands for slab+slice
            f = np.asarray([
                0, shift - (1 + ftw) * (tb / 2), shift - (1 - ftw) * (tb / 2),
                gcent - (tb / g / 2 + ftw * (tb / 2)),
                gcent - (tb / g / 2 - ftw * (tb / 2)),
                gcent + (tb / g / 2 - ftw * (tb / 2)),
                gcent + (tb / g / 2 + ftw * (tb / 2)),
                shift + (1 - ftw) * (tb / 2), shift + (1 + ftw) * (tb / 2),
                (n / 2)
            ])
            f = f / (n / 2)
            m_notch = [0, 0, 1, 1, 0, 0, 1, 1, 0, 0]
            m_sub = [0, 0, 0, 0, 1, 1, 0, 0, 0, 0]
            w = [d1 / d2, 1, 1, 1, d1 / d2]
        elif gind == 1:
            # the slab and slice share a left transition band
            f = np.asarray([
                0, shift - (1 + ftw) * (tb / 2), shift - (1 - ftw) * (tb / 2),
                gcent + (tb / g / 2 - ftw * (tb / 2)),
                gcent + (tb / g / 2 + ftw * (tb / 2)),
                shift + (1 - ftw) * (tb / 2), shift + (1 + ftw) * (tb / 2),
                (n / 2)
            ])
            f = f / (n / 2)
            m_notch = [0, 0, 0, 0, 1, 1, 0, 0]
            m_sub = [0, 0, 1, 1, 0, 0, 0, 0]
            w = [d1 / d2, 1, 1, d1 / d2]
        elif gind == g:
            # the slab and slice share a right transition band
            f = np.asarray([
                0, shift - (1 + ftw) * (tb / 2), shift - (1 - ftw) * (tb / 2),
                gcent - (tb / g / 2 + ftw * (tb / 2)),
                gcent - (tb / g / 2 - ftw * (tb / 2)),
                shift + (1 - ftw) * (tb / 2), shift + (1 + ftw) * (tb / 2),
                (n / 2)
            ])
            f = f / (n / 2)
            m_notch = [0, 0, 1, 1, 0, 0, 0, 0]
            m_sub = [0, 0, 0, 0, 1, 1, 0, 0]
            w = [d1 / d2, 1, 1, d1 / d2]

        c = np.exp(1j * 2 * np.pi / (2 * (n + 1)) * np.concatenate(
            [np.arange(0, n / 2 + 1, 1),
             np.arange(-n / 2, 0, 1)]))

        b_notch = signal.firls(n + 1, f, m_notch, w)  # the notched filter
        b_notch = sp.ifft(np.multiply(sp.fft(b_notch, center=False), c),
                          center=False)
        b_notch = np.real(b_notch[:n])
        # hilbert transform to suppress negative passband
        b_notch = signal.hilbert(b_notch)

        b_sub = signal.firls(n + 1, f, m_sub, w)  # the sub-band filter
        b_sub = sp.ifft(np.multiply(sp.fft(b_sub, center=False), c),
                        center=False)

        b_sub = np.real(b_sub[:n])
        # hilbert transform to suppress negative passband
        b_sub = signal.hilbert(b_sub)

        # add them with the subslice phase
        b = b_notch + np.exp(1j * phi) * b_sub

        # demodulate to DC
        c_shift = np.exp(-1j * 2 * np.pi / n * shift * np.arange(0, n, 1)) / 2
        c_shift *= np.exp(-1j * np.pi / n * shift)

        b = np.multiply(b, c_shift)

    return b
Exemplo n.º 7
0
def dz_b1_rf(dt=2e-6,
             tb=4,
             ptype='st',
             flip=np.pi / 6,
             pbw=0.3,
             pbc=2,
             d1=0.01,
             d2=0.01,
             os=8,
             split_and_reflect=True):
    """Design a :math:`B_1^{+}`-selective excitation pulse following Grissom \
    JMR 2014

    Args:
        dt (float): hardware sampling dwell time in s.
        tb (int): time-bandwidth product.
        ptype (string): pulse type, 'st' (small-tip excitation), 'ex' (pi/2
            excitation pulse), 'se' (spin-echo pulse), 'inv' (inversion), or
            'sat' (pi/2 saturation pulse).
        flip (float): flip angle, in radians.
        pbw (float): width of passband in Gauss.
        pbc (float): center of passband in Gauss.
        d1 (float): passband ripple level in :math:`M_0^{-1}`.
        d2 (float): stopband ripple level in :math:`M_0^{-1}`.
        os (int): matrix scaling factor.
        split_and_reflect (bool): option to split and reflect designed pulse.

    Split-and-reflect preserves pulse selectivity when scaled to excite large
    tip-angles.

    Returns:
        2-element tuple containing

        - **om1** (*array*): AM waveform.
        - **dom** (*array*): FM waveform (radians/s).

    References:
        Grissom, W., Cao, Z., & Does, M. (2014).
        :math:`B_1^{+}`-selective excitation pulse design using the Shinnar-Le
        Roux algorithm. Journal of Magnetic Resonance, 242, 189-196.
    """

    # calculate beta filter ripple
    [_, d1, d2] = slr.calc_ripples(ptype, d1, d2)

    # calculate pulse duration
    b = 4257 * pbw
    pulse_len = tb / b

    # calculate number of samples in pulse
    n = np.int(np.ceil(pulse_len / dt / 2) * 2)

    if pbc == 0:
        # we want passband as close to zero as possible.
        # do my own dual-band filter design to minimize interaction
        # between the left and right bands

        # build system matrix
        A = np.exp(1j * 2 * np.pi * np.outer(
            np.arange(-n * os / 2, n * os / 2), np.arange(-n / 2, n / 2)) /
                   (n * os))

        # build target pattern
        ii = np.arange(-n * os / 2, n * os / 2) / (n * os) * 2
        w = dinf(d1, d2) / tb
        f = np.asarray([0, (1 - w) * (tb / 2),
                        (1 + w) * (tb / 2), n / 2]) / (n / 2)
        d = np.double(np.abs(ii) < f[1])
        ds = np.double(np.abs(ii) > f[2])

        # shift the target pattern to minimum center position
        pbc = np.int(np.ceil((f[2] - f[1]) * n * os / 2 + f[1] * n * os / 2))
        dl = np.roll(d, pbc)
        dr = np.roll(d, -pbc)
        dsl = np.roll(ds, pbc)
        dsr = np.roll(ds, -pbc)

        # build error weight vector
        w = dl + dr + d1 / d2 * np.multiply(dsl, dsr)

        # solve for the dual-band filter
        AtA = A.conj().T @ np.multiply(np.reshape(w, (np.size(w), 1)), A)
        Atd = A.conj().T @ np.multiply(w, dr - dl)
        h = np.imag(np.linalg.pinv(AtA) @ Atd)

    else:  # normal design

        # design filter
        h = slr.dzls(n, tb, d1, d2)

        # dual-band-modulate the filter
        om = 2 * np.pi * 4257 * pbc  # modulation frequency
        t = np.arange(0, n) * pulse_len / n - pulse_len / 2
        h = 2 * h * np.sin(om * t)

    if split_and_reflect:
        # split and flip fm waveform to improve large-tip accuracy
        dom = np.concatenate((h[n // 2::-1], h, h[n:n // 2:-1])) / 2
    else:
        dom = np.concatenate((0 * h[n // 2::-1], h, 0 * h[n:n // 2:-1]))

    # scale to target flip, convert to Hz
    dom = dom * flip / (2 * np.pi * dt)

    # build am waveform
    om1 = np.concatenate((-np.ones(n // 2), np.ones(n), -np.ones(n // 2)))

    return om1, dom