Source code for pypulseqpp._calc_rf_power
"""Energy, peak power and RMS B1 of one RF event, in Pulseq's Hz units."""
from __future__ import annotations
import numpy as np
__all__ = ["calc_rf_power"]
def _channels(t: np.ndarray) -> int:
"""Return the number of transmit channels sharing the time base, as the reference interpreter counts them."""
if t.size == 0:
return 1
count = int(np.count_nonzero(t == t[0]))
if count < 2 or t.size % count:
return 1
per_channel = t.size // count
if not np.array_equal(t[per_channel:], t[:-per_channel]):
return 1
return count
[docs]
def calc_rf_power(rf, dt: float = 1e-6) -> tuple[float, float, float]:
"""Return an RF event's energy, peak power and RMS amplitude, as MATLAB Pulseq's ``calcRfPower``.
Parameters
----------
rf : RF event
Anything with ``t`` (s), ``signal`` (Hz) and ``shape_dur`` (s).
dt : float, default=1e-06
Resampling step, in seconds.
Returns
-------
total_energy : float
Integral of ``|rf|^2``, in Hz^2 s.
peak_pwr : float
Largest ``|rf|^2``, in Hz^2.
rf_rms : float
``sqrt(total_energy / shape_dur)``, in Hz.
Notes
-----
The pulse is resampled at the midpoints of a ``dt`` grid over its shape
duration, zero outside its samples. A dynamic pTx pulse is resampled channel
by channel on its shared time base, and ``|rf|^2`` is the sum of ``|b_c|^2``
over channels: its root-sum-square amplitude, which channels cannot cancel.
The quantities are relative: divide ``rf_rms`` by gamma for tesla and
energy by gamma squared for T^2 s.
"""
t = np.asarray(rf.t, dtype=float).ravel()
signal = np.asarray(rf.signal).ravel()
shape_dur = float(rf.shape_dur)
samples = int(np.round(shape_dur / dt))
grid = (np.arange(samples) + 0.5) * dt
channels = _channels(t)
per_channel = t.size // channels
power = np.zeros(samples)
for channel in range(channels):
part = slice(channel * per_channel, (channel + 1) * per_channel)
resampled = np.interp(grid, t[part], signal[part], left=0.0, right=0.0)
power += np.abs(resampled) ** 2
total_energy = float(power.sum() * dt)
peak_pwr = float(power.max()) if samples else 0.0
rf_rms = float(np.sqrt(total_energy / shape_dur)) if shape_dur > 0.0 else 0.0
return total_energy, peak_pwr, rf_rms