def test_getdirs2(self):
     ndirs=6
     gutest, nb0s = difunc.get_dirs(ndirs)
     gpred = np.array([[-0.283341,  -0.893706,  -0.347862],
                  [-0.434044,   0.799575,  -0.415074],
                  [0.961905,    0.095774,   0.256058],
                  [-0.663896,   0.491506,   0.563616],
                  [-0.570757,  -0.554998,   0.605156],
                  [-0.198848,  -0.056534,  -0.978399]])
     self.assertEqual(nb0s, 1)
     np.testing.assert_allclose(gpred, gutest)
 def test_getdirs3(self):
     ndirs=12
     gutest, nb0s = difunc.get_dirs(ndirs)
     gpred = np.array([[ 0.648514,   0.375307,   0.662249],
                  [-0.560493,   0.824711,   0.075496],
                  [-0.591977,  -0.304283,   0.746307],
                  [-0.084472,  -0.976168,   0.199902],
                  [-0.149626,  -0.006494,  -0.988721],
                  [-0.988211,  -0.056904,  -0.142130],
                  [0.864451,   -0.274379,  -0.421237],
                  [-0.173549,  -0.858586,  -0.482401],
                  [-0.039288,   0.644885,  -0.763269],
                  [0.729809,    0.673235,  -0.118882],
                  [0.698325,   -0.455759,   0.551929],
                  [-0.325340,   0.489774,   0.808873]])
     self.assertEqual(nb0s, 1)
     np.testing.assert_allclose(gpred, gutest)
slice_thickness = 2.5e-3
n_slices = 1

# Partial Fourier
pF = 0.75
Nyeff = int (pF*Ny)
te=84e-3
tr=1

pe_enable = 1

nbvals=0
ndirs=1
#gradient scaling
gscl=np.sqrt(np.linspace(0., 1., nbvals+1))
gdir, nb0s = difunc.get_dirs(ndirs)

#to avoid exceeding gradient limits
gdfact = 1.0

tr_per_slice = tr / n_slices

system = Opts(max_grad=32, grad_unit='mT/m', max_slew=130, slew_unit='T/m/s', rf_ringdown_time=30e-6,
              rf_dead_time=100e-6, adc_dead_time=20e-6)

b0 = 2.89
sat_ppm = -3.45
sat_freq = sat_ppm * 1e-6 * b0 * system.gamma
rf_fs, _, _ = make_gauss_pulse(flip_angle=110 * math.pi / 180, system=system, duration=8e-3, bandwidth=abs(sat_freq),
                               freq_offset=sat_freq)
gz_fs = make_trapezoid(channel='z', system=system, delay=calc_duration(rf_fs), area=1 / 1e-4)
 def test_getdirs1(self):
     ndirs=3
     gutest, nb0s = difunc.get_dirs(ndirs)
     gpred = np.array([[1, 0, 0],[0, 1, 0],[0, 0, 1]])
     self.assertEqual(nb0s, 1)
     np.testing.assert_allclose(gpred, gutest)
 def test_getdirs4(self):
     ndirs=60
     gutest, nb0s = difunc.get_dirs(ndirs)
     gpred = np.array([[-0.811556,  0.245996, -0.529964],
                   [-0.576784, -0.313126,  0.754502],
                   [-0.167946, -0.899364, -0.403655],
                   [0.755699, -0.512113,     -0.408238],
                   [0.116846,   0.962654,    -0.244221],
                   [0.495465,   0.208081,    0.843337],
                   [0.901459,   0.385831,    -0.196230],
                   [-0.248754,   0.420519,   -0.872516],
                   [-0.047525,   0.444671,   0.894432],
                   [- 0.508593,   0.857494, -0.077699],
                   [0.693558,    0.614042,    0.376737],
                   [0.990394, -0.134781,    -0.030898],
                   [0.019140, -0.684235,     0.729010],
                   [0.385221, - 0.339346, - 0.858166],
                   [- 0.440289, - 0.853536,   0.278609],
                   [0.680515, - 0.559825,   0.472752],
                   [- 0.146029,   0.872237,   0.466774],
                   [0.317352,   0.195118, - 0.928018],
                   [- 0.796280, - 0.129004, - 0.591013],
                   [- 0.711299,   0.249255,   0.657211],
                   [- 0.838383, - 0.538321, - 0.085587],
                   [0.202544, - 0.966710,   0.156357],
                   [-0.296747, - 0.476761, - 0.827430],
                   [0.545225,   0.637023, - 0.544914],
                   [-0.887097,   0.451265,   0.097048],
                   [0.034752, - 0.124211,   0.991647],
                   [0.469222, - 0.766720, - 0.438145],
                   [-0.948457, - 0.088803,   0.304209],
                   [-0.354311,   0.664176, - 0.658281],
                   [-0.462117, - 0.833550, - 0.302724],
                   [0.949202, - 0.022353,  0.313871],
                   [0.791248,   0.065354, - 0.607994],
                   [-0.004026,   0.992213,   0.124488],
                   [- 0.357034,   0.107223, - 0.927917],
                   [0.414504,   0.685422,   0.598651],
                   [-0.331743, - 0.552720,   0.764492],
                   [-0.749058,   0.641807, - 0.164305],
                   [0.238666, - 0.655691, - 0.716315],
                   [0.619125,   0.784982,   0.022082],
                   [0.123966, - 0.872070,   0.473419],
                   [-0.185240,   0.122014,   0.975089],
                   [-0.980282, - 0.189151, - 0.057166],
                   [0.637873, - 0.084335,   0.765510],
                   [-0.668960,   0.723273,   0.171375],
                   [-0.775822, - 0.381543,   0.502519],
                   [-0.636044, - 0.425049, - 0.644035],
                   [0.229220,   0.809364, - 0.540729],
                   [-0.538340,   0.531550,   0.653946],
                   [0.906105, - 0.354129,   0.231445],
                   [-0.166743, - 0.191681, - 0.967189],
                   [0.324636, - 0.927784, - 0.183922],
                   [0.551291, - 0.398459,   0.733014],
                   [0.537753, - 0.032690, - 0.842468],
                   [-0.306182, - 0.951456, - 0.031381],
                   [0.875976,   0.329209,   0.352545],
                   [0.902989, - 0.218632, - 0.369879],
                   [-0.456427,   0.801551, - 0.386251],
                   [0.089001,   0.716134,   0.692265],
                   [-0.714965, - 0.648438,   0.261444],
                   [0.076308,   0.420804, - 0.903936]])
     self.assertEqual(nb0s, 3)
     np.testing.assert_allclose(gpred, gutest)
Exemple #6
0
def generate_SeqFile_SpiralDiffusion(gx, gy, tr, n_shots, mg, ms, fA, n_slices,
                                     reps, st, tPlot, tReport, b_values,
                                     n_dirs, fov, Nx):

    #%% --- 1 - Create new Sequence Object + Parameters
    seq = Sequence()

    # =========
    # Parameters
    # =========
    i_raster_time = 100000
    assert 1 / i_raster_time == seq.grad_raster_time, "Manualy inputed inverse raster time does not match the actual value."

    # =========
    # Code parameters
    # =========
    fatsat_enable = 0  # Fat saturation
    kplot = 0

    # =========
    # Acquisition Parameters
    # =========
    TR = tr  # Spin-Echo parameters - TR in [s]
    n_TR = math.ceil(
        TR * i_raster_time)  # Spin-Echo parameters - number of points TR
    bvalue = b_values  # b-value [s/mm2]
    nbvals = np.shape(bvalue)[0]  # b-value parameters
    ndirs = n_dirs  # b-value parameters
    Ny = Nx
    slice_thickness = st  # Acquisition Parameters in [m]
    Nshots = n_shots

    # =========
    # Gradient Scaling
    # =========
    gscl = np.zeros(nbvals + 1)
    gscl[1:] = np.sqrt(bvalue / np.max(bvalue))
    gdir, nb0s = difunc.get_dirs(ndirs)

    # =========
    # Create system
    # =========
    system = Opts(max_grad=mg,
                  grad_unit='mT/m',
                  max_slew=ms,
                  slew_unit='T/m/s',
                  rf_ringdown_time=20e-6,
                  rf_dead_time=100e-6,
                  adc_dead_time=10e-6)

    #%% --- 2 - Fat saturation
    if fatsat_enable:
        fatsat_str = "_fatsat"
        b0 = 1.494
        sat_ppm = -3.45
        sat_freq = sat_ppm * 1e-6 * b0 * system.gamma
        rf_fs, _, _ = make_gauss_pulse(flip_angle=110 * math.pi / 180,
                                       system=system,
                                       duration=8e-3,
                                       bandwidth=abs(sat_freq),
                                       freq_offset=sat_freq)
        gz_fs = make_trapezoid(channel='z',
                               system=system,
                               delay=calc_duration(rf_fs),
                               area=1 / 1e-4)
    else:
        fatsat_str = ""

    #%% --- 3 - Slice Selection
    # =========
    # Create 90 degree slice selection pulse and gradient
    # =========
    flip90 = fA * pi / 180
    rf, gz, _ = make_sinc_pulse(flip_angle=flip90,
                                system=system,
                                duration=3e-3,
                                slice_thickness=slice_thickness,
                                apodization=0.5,
                                time_bw_product=4)

    # =========
    # Refocusing pulse with spoiling gradients
    # =========
    rf180, gz180, _ = make_sinc_pulse(flip_angle=math.pi,
                                      system=system,
                                      duration=5e-3,
                                      slice_thickness=slice_thickness,
                                      apodization=0.5,
                                      time_bw_product=4)
    rf180.phase_offset = math.pi / 2
    gz_spoil = make_trapezoid(channel='z',
                              system=system,
                              area=6 / slice_thickness,
                              duration=3e-3)

    #%% --- 4 - Gradients
    # =========
    # Spiral trajectory
    # =========
    G = gx + 1J * gy

    #%% --- 5 - ADCs / Readouts
    delta_k = 1 / fov
    adc_samples = math.floor(
        len(G) / 4
    ) * 4 - 2  # Apparently, on Siemens the number of samples needs to be divisible by 4...
    adc = make_adc(num_samples=adc_samples,
                   system=system,
                   duration=adc_samples / i_raster_time)

    # =========
    # Pre-phasing gradients
    # =========
    pre_time = 1e-3
    n_pre_time = math.ceil(pre_time * i_raster_time)
    gz_reph = make_trapezoid(channel='z',
                             system=system,
                             area=-gz.area / 2,
                             duration=pre_time)

    #%% --- 6 - Obtain TE and diffusion-weighting gradient waveform
    # For S&T monopolar waveforms
    # From an initial TE, check we satisfy all constraints -> otherwise increase TE.
    # Once all constraints are okay -> check b-value, if it is lower than the target one -> increase TE
    # Looks time-inefficient but it is fast enough to make it user-friendly.
    # TODO: Re-scale the waveform to the exact b-value because increasing the TE might produce slightly higher ones.

    # Calculate some times constant throughout the process
    # We need to compute the exact time sequence. For the normal SE-MONO-EPI sequence micro second differences
    # are not important, however, if we wanna import external gradients the allocated time for them needs to
    # be the same, and thus exact timing is mandatory. With this in mind, we establish the following rounding rules:
    # Duration of RFs + spoiling, and EPI time to the center of the k-space is always math.ceil().

    # The time(gy) refers to the number of blips, thus we substract 0.5 since the number of lines is always even.
    # The time(gx) refers to the time needed to read each line of the k-space. Thus, if Ny is even, it would take half of the lines plus another half.
    n_duration_center = 0  # The spiral starts right in 0 -- or ADC_dead_time??
    rf_center_with_delay = rf.delay + calc_rf_center(rf)[0]

    n_rf90r = math.ceil((calc_duration(gz) - rf_center_with_delay + pre_time) /
                        seq.grad_raster_time)
    n_rf180r = math.ceil((calc_duration(rf180) + 2 * calc_duration(gz_spoil)) /
                         2 / seq.grad_raster_time)
    n_rf180l = math.floor(
        (calc_duration(rf180) + 2 * calc_duration(gz_spoil)) / 2 /
        seq.grad_raster_time)

    # =========
    # Find minimum TE considering the readout times.
    # =========
    n_TE = math.ceil(20e-3 / seq.grad_raster_time)
    n_delay_te1 = -1
    while n_delay_te1 <= 0:
        n_TE = n_TE + 2

        n_tINV = math.floor(n_TE / 2)
        n_delay_te1 = n_tINV - n_rf90r - n_rf180l

    # =========
    # Find minimum TE for the target b-value
    # =========
    bvalue_tmp = 0
    while bvalue_tmp < np.max(bvalue):
        n_TE = n_TE + 2

        n_tINV = math.floor(n_TE / 2)
        n_delay_te1 = n_tINV - n_rf90r - n_rf180l
        delay_te1 = n_delay_te1 / i_raster_time
        n_delay_te2 = n_tINV - n_rf180r - n_duration_center
        delay_te2 = n_delay_te2 / i_raster_time

        # Waveform Ramp time
        n_gdiff_rt = math.ceil(system.max_grad / system.max_slew /
                               seq.grad_raster_time)

        # Select the shortest available time
        n_gdiff_delta = min(n_delay_te1, n_delay_te2)
        n_gdiff_Delta = n_delay_te1 + 2 * math.ceil(
            calc_duration(gz_spoil) / seq.grad_raster_time) + math.ceil(
                calc_duration(gz180) / seq.grad_raster_time)

        gdiff = make_trapezoid(channel='x',
                               system=system,
                               amplitude=system.max_grad,
                               duration=n_gdiff_delta / i_raster_time)

        # delta only corresponds to the rectangle.
        n_gdiff_delta = n_gdiff_delta - 2 * n_gdiff_rt

        bv = difunc.calc_bval(system.max_grad, n_gdiff_delta / i_raster_time,
                              n_gdiff_Delta / i_raster_time,
                              n_gdiff_rt / i_raster_time)
        bvalue_tmp = bv * 1e-6

    # =========
    # Show final TE and b-values:
    # =========
    print("TE:", round(n_TE / i_raster_time * 1e3, 2), "ms")
    for bv in range(1, nbvals + 1):
        print(
            round(
                difunc.calc_bval(system.max_grad * gscl[bv], n_gdiff_delta /
                                 i_raster_time, n_gdiff_Delta / i_raster_time,
                                 n_gdiff_rt / i_raster_time) * 1e-6, 2),
            "s/mm2")

    TE = n_TE / i_raster_time
    TR = n_TR / i_raster_time

    #%% --- 7 - Crusher gradients
    gx_crush = make_trapezoid(channel='x',
                              area=2 * Nx * delta_k,
                              system=system)
    gz_crush = make_trapezoid(channel='z',
                              area=4 / slice_thickness,
                              system=system)

    # TR delay - Takes everything into account
    # Distance between the center of the RF90s must be TR
    # The n_pre_time here is the time used to drive the Gx, and Gy spiral gradients to zero.
    n_spiral_time = adc_samples
    n_tr_per_slice = math.ceil(TR / n_slices * i_raster_time)
    if fatsat_enable:
        n_tr_delay = n_tr_per_slice - (n_TE - n_duration_center + n_spiral_time) \
                            - math.ceil(rf_center_with_delay * i_raster_time) \
                            - n_pre_time \
                            - math.ceil(calc_duration(gx_crush, gz_crush) * i_raster_time) \
                            - math.ceil(calc_duration(rf_fs, gz_fs) * i_raster_time)
    else:
        n_tr_delay = n_tr_per_slice - (n_TE - n_duration_center + n_spiral_time) \
                        - math.ceil(rf_center_with_delay * i_raster_time) \
                        - n_pre_time \
                        - math.ceil(calc_duration(gx_crush, gz_crush) * i_raster_time)
    tr_delay = n_tr_delay / i_raster_time

    #%% --- 8 - Checks
    # =========
    # Check TR delay time
    # =========
    assert n_tr_delay > 0, "Such parameter configuration needs longer TR."

    # =========
    # Delay time,
    # =========
    # Time between the gradient and the RF180. This time might be zero some times, although it is not normal.
    if n_delay_te1 > n_delay_te2:
        n_gap_te1 = n_delay_te1 - n_delay_te2
        gap_te1 = n_gap_te1 / i_raster_time
        gap_te2 = 0
    else:
        n_gap_te2 = n_delay_te2 - n_delay_te1
        gap_te2 = n_gap_te2 / i_raster_time
        gap_te1 = 0

    #%% --- 9 - b-zero acquisition
    for r in range(reps):
        for d in range(nb0s):
            for nshot in range(Nshots):
                for s in range(n_slices):
                    # Fat saturation
                    if fatsat_enable:
                        seq.add_block(rf_fs, gz_fs)

                    # RF90
                    rf.freq_offset = gz.amplitude * slice_thickness * (
                        s - (n_slices - 1) / 2)
                    seq.add_block(rf, gz)
                    seq.add_block(gz_reph)

                    # Delay for RF180
                    seq.add_block(make_delay(delay_te1))

                    # RF180
                    seq.add_block(gz_spoil)
                    rf180.freq_offset = gz180.amplitude * slice_thickness * (
                        s - (n_slices - 1) / 2)
                    seq.add_block(rf180, gz180)
                    seq.add_block(gz_spoil)

                    # Delay for spiral
                    seq.add_block(make_delay(delay_te2))

                    # Read k-space
                    # Imaging Gradient waveforms
                    gx = make_arbitrary_grad(channel='x',
                                             waveform=np.squeeze(
                                                 G[:, nshot].real),
                                             system=system)
                    gy = make_arbitrary_grad(channel='y',
                                             waveform=np.squeeze(
                                                 G[:, nshot].imag),
                                             system=system)
                    seq.add_block(gx, gy, adc)

                    # Make the spiral finish in zero - I use pre_time because I know for sure it's long enough.
                    # Furthermore, this is after readout and TR is supposed to be long.
                    amp_x = [G[:, nshot].real[-1], 0]
                    amp_y = [G[:, nshot].imag[-1], 0]
                    gx_to_zero = make_extended_trapezoid(channel='x',
                                                         amplitudes=amp_x,
                                                         times=[0, pre_time],
                                                         system=system)
                    gy_to_zero = make_extended_trapezoid(channel='y',
                                                         amplitudes=amp_y,
                                                         times=[0, pre_time],
                                                         system=system)
                    seq.add_block(gx_to_zero, gy_to_zero)

                    seq.add_block(gx_crush, gz_crush)

                    # Wait TR
                    if tr_delay > 0:
                        seq.add_block(make_delay(tr_delay))

    #%% --- 9 - DWI acquisition
    for r in range(reps):
        for bv in range(1, nbvals + 1):
            for d in range(ndirs):
                for nshot in range(Nshots):
                    for s in range(n_slices):
                        # Fat saturation
                        if fatsat_enable:
                            seq.add_block(rf_fs, gz_fs)

                        # RF90
                        rf.freq_offset = gz.amplitude * slice_thickness * (
                            s - (n_slices - 1) / 2)
                        seq.add_block(rf, gz)
                        seq.add_block(gz_reph)

                        # Diffusion-weighting gradient
                        gdiffx = make_trapezoid(channel='x',
                                                system=system,
                                                amplitude=system.max_grad *
                                                gscl[bv] * gdir[d, 0],
                                                duration=calc_duration(gdiff))
                        gdiffy = make_trapezoid(channel='y',
                                                system=system,
                                                amplitude=system.max_grad *
                                                gscl[bv] * gdir[d, 1],
                                                duration=calc_duration(gdiff))
                        gdiffz = make_trapezoid(channel='z',
                                                system=system,
                                                amplitude=system.max_grad *
                                                gscl[bv] * gdir[d, 2],
                                                duration=calc_duration(gdiff))

                        seq.add_block(gdiffx, gdiffy, gdiffz)

                        # Delay for RF180
                        seq.add_block(make_delay(gap_te1))

                        # RF180
                        seq.add_block(gz_spoil)
                        rf180.freq_offset = gz180.amplitude * slice_thickness * (
                            s - (n_slices - 1) / 2)
                        seq.add_block(rf180, gz180)
                        seq.add_block(gz_spoil)

                        # Diffusion-weighting gradient
                        seq.add_block(gdiffx, gdiffy, gdiffz)

                        # Delay for spiral
                        seq.add_block(make_delay(gap_te2))

                        # Read k-space
                        # Imaging Gradient waveforms
                        gx = make_arbitrary_grad(channel='x',
                                                 waveform=np.squeeze(
                                                     G[:, nshot].real),
                                                 system=system)
                        gy = make_arbitrary_grad(channel='y',
                                                 waveform=np.squeeze(
                                                     G[:, nshot].imag),
                                                 system=system)
                        seq.add_block(gx, gy, adc)

                        # Make the spiral finish in zero - I use pre_time because I know for sure it's long enough.
                        # Furthermore, this is after readout and TR is supposed to be long.
                        amp_x = [G[:, nshot].real[-1], 0]
                        amp_y = [G[:, nshot].imag[-1], 0]
                        gx_to_zero = make_extended_trapezoid(
                            channel='x',
                            amplitudes=amp_x,
                            times=[0, pre_time],
                            system=system)
                        gy_to_zero = make_extended_trapezoid(
                            channel='y',
                            amplitudes=amp_y,
                            times=[0, pre_time],
                            system=system)
                        seq.add_block(gx_to_zero, gy_to_zero)

                        seq.add_block(gx_crush, gz_crush)

                        # Wait TR
                        if tr_delay > 0:
                            seq.add_block(make_delay(tr_delay))

    if tPlot:
        seq.plot()

    if tReport:
        print(seq.test_report())
        seq.check_timing()

    return seq, TE, TR, fatsat_str
Exemple #7
0
def generate_SeqFile_EPIDiffusion(FOV, nx, ny, ns, mg, ms, reps, st, tr, fA,
                                  b_values, n_dirs, partialFourier, tPlot,
                                  tReport):

    #%% --- 1 - Create new Sequence Object + Parameters
    seq = Sequence()

    # =========
    # Parameters
    # =========
    i_raster_time = 100000
    assert 1 / i_raster_time == seq.grad_raster_time, "Manualy inputed inverse raster time does not match the actual value."

    # =========
    # Code parameters
    # =========
    fatsat_enable = 0  # Fat saturation
    kplot = 0

    # =========
    # Acquisition Parameters
    # =========
    fov = FOV
    Nx = nx
    Ny = ny
    n_slices = ns
    TR = tr  # Spin-Echo parameters - TR in [s]
    n_TR = math.ceil(
        TR * i_raster_time)  # Spin-Echo parameters - number of points TR
    bvalue = b_values  # b-value [s/mm2]
    nbvals = np.shape(bvalue)[0]  # b-value parameters
    ndirs = n_dirs  # b-value parameters
    slice_thickness = st  # Acquisition Parameters in [m]

    # =========
    # Partial Fourier
    # =========
    pF = partialFourier
    Nyeff = int(pF * Ny)  # Number of Ny samples acquired
    if pF is not 1:
        pF_str = "_" + str(pF) + "pF"
    else:
        pF_str = ""

    # =========
    # Gradient Scaling
    # =========
    gscl = np.zeros(nbvals + 1)
    gscl[1:] = np.sqrt(bvalue / np.max(bvalue))
    gdir, nb0s = difunc.get_dirs(ndirs)

    # =========
    # Create system
    # =========
    system = Opts(max_grad=mg,
                  grad_unit='mT/m',
                  max_slew=ms,
                  slew_unit='T/m/s',
                  rf_ringdown_time=20e-6,
                  rf_dead_time=100e-6,
                  adc_dead_time=10e-6)

    #%% --- 2 - Fat saturation
    if fatsat_enable:
        fatsat_str = "_fatsat"
        b0 = 1.494
        sat_ppm = -3.45
        sat_freq = sat_ppm * 1e-6 * b0 * system.gamma
        rf_fs, _, _ = make_gauss_pulse(flip_angle=110 * math.pi / 180,
                                       system=system,
                                       duration=8e-3,
                                       bandwidth=abs(sat_freq),
                                       freq_offset=sat_freq)
        gz_fs = make_trapezoid(channel='z',
                               system=system,
                               delay=calc_duration(rf_fs),
                               area=1 / 1e-4)
    else:
        fatsat_str = ""

    #%% --- 3 - Slice Selection
    # =========
    # Create 90 degree slice selection pulse and gradient
    # =========
    flip90 = fA * pi / 180
    rf, gz, _ = make_sinc_pulse(flip_angle=flip90,
                                system=system,
                                duration=3e-3,
                                slice_thickness=slice_thickness,
                                apodization=0.5,
                                time_bw_product=4)

    # =========
    # Refocusing pulse with spoiling gradients
    # =========
    rf180, gz180, _ = make_sinc_pulse(flip_angle=math.pi,
                                      system=system,
                                      duration=5e-3,
                                      slice_thickness=slice_thickness,
                                      apodization=0.5,
                                      time_bw_product=4)
    rf180.phase_offset = math.pi / 2
    gz_spoil = make_trapezoid(channel='z',
                              system=system,
                              area=6 / slice_thickness,
                              duration=3e-3)

    #%% --- 4 - Define other gradients and ADC events
    delta_k = 1 / fov
    k_width = Nx * delta_k
    dwell_time = seq.grad_raster_time  # Full receiver bandwith
    readout_time = Nx * dwell_time  # T_acq (acquisition time)
    flat_time = math.ceil(
        readout_time / seq.grad_raster_time) * seq.grad_raster_time
    gx = make_trapezoid(channel='x',
                        system=system,
                        amplitude=k_width / readout_time,
                        flat_time=flat_time)
    adc = make_adc(num_samples=Nx,
                   duration=readout_time,
                   delay=gx.rise_time + flat_time / 2 -
                   (readout_time - dwell_time) / 2)

    # =========
    # Pre-phasing gradients
    # =========
    pre_time = 1e-3
    gx_pre = make_trapezoid(channel='x',
                            system=system,
                            area=-gx.area / 2,
                            duration=pre_time)
    gz_reph = make_trapezoid(channel='z',
                             system=system,
                             area=-gz.area / 2,
                             duration=pre_time)
    gy_pre = make_trapezoid(
        channel='y',
        system=system,
        area=-(Ny / 2 - 0.5 - (Ny - Nyeff)) * delta_k,
        duration=pre_time
    )  # Es -0.5 y no +0.5 porque hay que pensar en areas, no en rayas!

    # =========
    # Phase blip in shortest possible time
    # =========
    gy = make_trapezoid(channel='y', system=system, area=delta_k)
    dur = math.ceil(
        calc_duration(gy) / seq.grad_raster_time) * seq.grad_raster_time

    #%% --- 5 - Obtain TE and diffusion-weighting gradient waveform
    # =========
    # Calculate some times constant throughout the process
    # =========
    duration_center = math.ceil(
        (calc_duration(gx) * (Ny / 2 + 0.5 -
                              (Ny - Nyeff)) + calc_duration(gy) *
         (Ny / 2 - 0.5 -
          (Ny - Nyeff))) / seq.grad_raster_time) * seq.grad_raster_time
    rf_center_with_delay = rf.delay + calc_rf_center(rf)[0]
    rf180_center_with_delay = rf180.delay + calc_rf_center(rf180)[0]

    # =========
    # Find minimum TE considering the readout times.
    # =========
    TE = 40e-3  # [s]
    delay_te2 = -1
    while delay_te2 <= 0:
        TE = TE + 0.02e-3  # [ms]
        delay_te2 = math.ceil((TE / 2 - calc_duration(rf180) + rf180_center_with_delay - calc_duration(gz_spoil) - \
                               calc_duration(gx_pre,
                                             gy_pre) - duration_center) / seq.grad_raster_time) * seq.grad_raster_time

    # =========
    # Find minimum TE for the target b-value
    # =========
    bvalue_tmp = 0
    while bvalue_tmp < np.max(bvalue):
        TE = TE + 2 * seq.grad_raster_time  # [ms]
        delay_te1 = math.ceil((TE / 2 - calc_duration(gz) + rf_center_with_delay - pre_time - calc_duration(gz_spoil) - \
                               rf180_center_with_delay) / seq.grad_raster_time) * seq.grad_raster_time
        delay_te2 = math.ceil((TE / 2 - calc_duration(rf180) + rf180_center_with_delay - calc_duration(gz_spoil) - \
                               calc_duration(gx_pre,
                                             gy_pre) - duration_center) / seq.grad_raster_time) * seq.grad_raster_time

        # Waveform Ramp time
        gdiff_rt = math.ceil(system.max_grad / system.max_slew /
                             seq.grad_raster_time) * seq.grad_raster_time

        # Select the shortest available time
        gdiff_delta = min(delay_te1, delay_te2)
        gdiff_Delta = math.ceil(
            (delay_te1 + 2 * calc_duration(gz_spoil) + calc_duration(gz180)) /
            seq.grad_raster_time) * seq.grad_raster_time

        gdiff = make_trapezoid(channel='x',
                               system=system,
                               amplitude=system.max_grad,
                               duration=gdiff_delta)

        # delta only corresponds to the rectangle.
        gdiff_delta = math.ceil((gdiff_delta - 2 * gdiff_rt) /
                                seq.grad_raster_time) * seq.grad_raster_time

        bv = difunc.calc_bval(system.max_grad, gdiff_delta, gdiff_Delta,
                              gdiff_rt)
        bvalue_tmp = bv * 1e-6

    # =========
    # Show final TE and b-values:
    # =========
    print("TE:", round(TE * 1e3, 2), "ms")
    for bv in range(1, nbvals + 1):
        print(
            round(
                difunc.calc_bval(system.max_grad * gscl[bv], gdiff_delta,
                                 gdiff_Delta, gdiff_rt) * 1e-6, 2), "s/mm2")

    # =========
    # Crusher gradients
    # =========
    gx_crush = make_trapezoid(channel='x',
                              area=2 * Nx * delta_k,
                              system=system)
    gz_crush = make_trapezoid(channel='z',
                              area=4 / slice_thickness,
                              system=system)

    #%% --- 6 - Delays
    # =========
    # TR delay - Takes everything into account
    # EPI reading time:
    # Distance between the center of the RF90s must be TR
    # =========
    EPI_time = calc_duration(gx) * Nyeff + calc_duration(gy) * (Nyeff - 1)
    if fatsat_enable:
        tr_delay = math.floor(
            (TR - (TE - duration_center + EPI_time) - rf_center_with_delay - calc_duration(gx_crush, gz_crush) \
             - calc_duration(rf_fs, gz_fs)) \
            / seq.grad_raster_time) * seq.grad_raster_time
    else:
        tr_delay = math.floor(
            (TR - (TE - duration_center + EPI_time) - rf_center_with_delay - calc_duration(gx_crush, gz_crush)) \
            / seq.grad_raster_time) * seq.grad_raster_time

    # =========
    # Check TR delay time
    # =========
    assert tr_delay > 0, "Such parameter configuration needs longer TR."

    # =========
    # Delay time
    # =========

    # =========
    # Time between the gradient and the RF180. This time might be zero some times, although it is not normal.
    # =========
    gap_te1 = math.ceil((delay_te1 - calc_duration(gdiff)) /
                        seq.grad_raster_time) * seq.grad_raster_time

    # =========
    # Time between the gradient and the locate k-space gradients.
    # =========
    gap_te2 = math.ceil((delay_te2 - calc_duration(gdiff)) /
                        seq.grad_raster_time) * seq.grad_raster_time

    #%% --- 9 - b-zero acquisition
    for d in range(nb0s):
        for s in range(n_slices):
            # Fat saturation
            if fatsat_enable:
                seq.add_block(rf_fs, gz_fs)

            # RF90
            rf.freq_offset = gz.amplitude * slice_thickness * (
                s - (n_slices - 1) / 2)
            seq.add_block(rf, gz)
            seq.add_block(gz_reph)

            # Delay for RF180
            seq.add_block(make_delay(delay_te1))

            # RF180
            seq.add_block(gz_spoil)
            rf180.freq_offset = gz180.amplitude * slice_thickness * (
                s - (n_slices - 1) / 2)
            seq.add_block(rf180, gz180)
            seq.add_block(gz_spoil)

            # Delay for EPI
            seq.add_block(make_delay(delay_te2))

            # Locate k-space
            seq.add_block(gx_pre, gy_pre)

            for i in range(Nyeff):
                seq.add_block(gx, adc)  # Read one line of k-space
                if i is not Nyeff - 1:
                    seq.add_block(gy)  # Phase blip
                gx.amplitude = -gx.amplitude  # Reverse polarity of read gradient

            seq.add_block(gx_crush, gz_crush)

            # Wait TR
            if tr_delay > 0:
                seq.add_block(make_delay(tr_delay))

    #%% --- 10 - DWI acquisition
    for bv in range(1, nbvals + 1):
        for d in range(ndirs):
            for s in range(n_slices):
                # Fat saturation
                if fatsat_enable:
                    seq.add_block(rf_fs, gz_fs)

                # RF90
                rf.freq_offset = gz.amplitude * slice_thickness * (
                    s - (n_slices - 1) / 2)
                seq.add_block(rf, gz)
                seq.add_block(gz_reph)

                # Diffusion-weighting gradient
                gdiffx = make_trapezoid(channel='x',
                                        system=system,
                                        amplitude=system.max_grad * gscl[bv] *
                                        gdir[d, 0],
                                        duration=calc_duration(gdiff))
                gdiffy = make_trapezoid(channel='y',
                                        system=system,
                                        amplitude=system.max_grad * gscl[bv] *
                                        gdir[d, 1],
                                        duration=calc_duration(gdiff))
                gdiffz = make_trapezoid(channel='z',
                                        system=system,
                                        amplitude=system.max_grad * gscl[bv] *
                                        gdir[d, 2],
                                        duration=calc_duration(gdiff))

                seq.add_block(gdiffx, gdiffy, gdiffz)

                # Delay for RF180
                seq.add_block(make_delay(gap_te1))

                # RF180
                seq.add_block(gz_spoil)
                rf180.freq_offset = gz180.amplitude * slice_thickness * (
                    s - (n_slices - 1) / 2)
                seq.add_block(rf180, gz180)
                seq.add_block(gz_spoil)

                # Diffusion-weighting gradient
                seq.add_block(gdiffx, gdiffy, gdiffz)

                # Delay for EPI
                seq.add_block(make_delay(gap_te2))

                # Locate k-space
                seq.add_block(gx_pre, gy_pre)

                for i in range(Nyeff):
                    seq.add_block(gx, adc)  # Read one line of k-space
                    if i is not Nyeff - 1:
                        seq.add_block(gy)  # Phase blip
                    gx.amplitude = -gx.amplitude  # Reverse polarity of read gradient

                seq.add_block(gx_crush, gz_crush)

                # Wait TR
                if tr_delay > 0:
                    seq.add_block(make_delay(tr_delay))

    if tPlot:
        seq.plot()

    if tReport:
        print(seq.test_report())
        seq.check_timing()

    return seq, TE, TR, fatsat_str