import numpy as np
from matplotlib import pyplot as plt

import pypulseq as pp

"""
Read a sequence into MATLAB. The `Sequence` class provides an implementation of the _open file format_ for MR sequences 
described here: http://pulseq.github.io/specification.pdf. This example demonstrates parsing an MRI sequence stored in 
this format, accessing sequence parameters and visualising the sequence.
"""

# Read a sequence file - a sequence can be loaded from the open MR file format using the `read` method.
seq_name = "epi_rs.seq"

system = pp.Opts(
    B0=2.89
)  # Need system here if we want 'detectRFuse' to detect fat-sat pulses
seq = pp.Sequence(system)
seq.read(seq_name, detect_rf_use=True)

# Sanity check to see if the reading and writing are consistent
seq.write("read_test.seq")
# os_system(f'diff -s -u {seq_name} read_test.seq -echo')  # Linux only

"""
Access sequence parameters and blocks. Parameters defined with in the `[DEFINITIONS]` section of the sequence file 
are accessed with the `get_definition()` method. These are user-specified definitions and do not effect the execution of 
the sequence.
"""
seq_name = seq.get_definition("Name")

# Calculate and display real TE, TR as well as slew rates and gradient amplitudes
test_report = seq.test_report()
print(test_report)

# Sequence blocks are accessed with the `get_block()` method. As shown in the output the first block is a selective
# excitation block and contains an RF pulse and gradient and on the z-channel.
b1 = seq.get_block(1)

# Further information about each event can be obtained by accessing the appropriate fields of the block struct. In
# particular, the complex RF signal is stored in the field `signal`.
rf = b1.rf

plt.subplot(211)
plt.plot(rf.t, np.abs(rf.signal))
plt.ylabel("RF magnitude")

plt.subplot(212)
plt.plot(1e3 * rf.t, np.angle(rf.signal))
plt.xlabel("t (ms)")
plt.ylabel("RF phase")

# The next three blocks contain: three gradient events; a delay; and readout gradient with ADC event, each with
# corresponding fields defining the details of the events.
b2 = seq.get_block(2)
b3 = seq.get_block(3)
b4 = seq.get_block(4)

# Plot the sequence. Visualise the sequence using the `plot()` method of the class. This creates a new figure and shows
# ADC, RF and gradient events. The axes are linked so zooming is consistent. In this example, a simple gradient echo
# sequence for MRI is displayed.
# seq.plot()

"""
The details of individual pulses are not well-represented when the entire sequence is visualised. Interactive zooming 
is helpful here. Alternatively, a time range can be specified. An additional parameter also allows the display units to 
be changed for easy reading. Further, the handle of the created figure can be returned if required.
"""
# seq.plot(time_range=[0, 16e-3], time_disp='ms')

"""
Modifying sequence blocks. In addition to loading a sequence and accessing sequence blocks, blocks # can be modified. 
In this example, a Hamming window is applied to the # first RF pulse of the sequence and the flip angle is changed to 
45 degrees. The remaining RF pulses are unchanged.
"""
rf2 = rf
duration = rf2.t[-1]
t = rf2.t - duration / 2  # Centre time about 0
alpha = 0.5
BW = 4 / duration  # Time bandwidth product = 4
window = 1.0 - alpha + alpha * np.cos(2 * np.pi * t / duration)  # Hamming window
signal = window * np.sinc(BW * t)

# Normalise area to achieve 2*pi rotation
signal = signal / (seq.rf_raster_time * np.sum(np.real(signal)))

# Scale to 45 degree flip angle
rf2.signal = signal * 45 / 360

b1.rf = rf2
seq.set_block(1, b1)

# Second check to see what has changed
seq.write("read_test2.seq")
# os_system(f'diff -s -u {seq_name} read_test2.seq -echo')  # Linux only

# The amplitude of the first rf pulse is reduced due to the reduced flip-angle. Notice the reduction is not exactly a
# factor of two due to the windowing function.
amp1_in_Hz = max(abs(seq.get_block(1).rf.signal))
amp2_in_Hz = max(abs(seq.get_block(6).rf.signal))
