Skip to content

Signal2D #

Signal2D(
    t1: FloatArray | None = None,
    t2: FloatArray | None = None,
    amplitudes: ndarray | None = None,
    t1_unit: TimeUnit = MICROSECOND,
    t2_unit: TimeUnit = MICROSECOND,
    metadata: dict[str, Any] | None = None,
    reference_frequency_hz: float | None = None,
    carrier_offset_hz_1: float | None = None,
    carrier_offset_hz_2: float | None = None,
    reference_signal_f1: Signal1D | None = None,
    reference_signal_f2: Signal1D | None = None,
    f1_reference_frequency_hz: float | None = None,
    f2_reference_frequency_hz: float | None = None,
)

2D time-domain signal for NMR experiments (e.g., COSY, HSQC).

Attributes:

Name Type Description
t1 FloatArray

Time coordinates for indirect dimension (axis 1)

t2 FloatArray

Time coordinates for direct dimension (axis 2)

amplitudes ndarray

2D complex amplitude array with shape (len(t1), len(t2))

t1_unit, t2_unit

Time units for each axis

processing_history class-attribute instance-attribute #

processing_history: ProcessingHistory | None = (
    betterproto2.field(
        10, betterproto2.TYPE_MESSAGE, optional=True
    )
)

Processing history - read-only audit log of all operations applied to this signal

history property #

history: Sequence[ProcessingHistoryEntry]

Read-only access to processing history entries.

Returns:

Type Description
Sequence[ProcessingHistoryEntry]

Immutable sequence of history entries. Returns empty tuple if

Sequence[ProcessingHistoryEntry]

no history has been recorded yet.

amplitudes property writable #

amplitudes: ndarray

metadata class-attribute instance-attribute #

metadata: dict[str, str] = betterproto2.field(
    4,
    betterproto2.TYPE_MAP,
    map_meta=betterproto2.map_meta(
        betterproto2.TYPE_STRING, betterproto2.TYPE_STRING
    ),
)

Optional metadata

nucleus_t1 property writable #

nucleus_t1: Nucleus

Nucleus type for the T1 (indirect) axis.

nucleus_t2 property writable #

nucleus_t2: Nucleus

Nucleus type for the T2 (direct) axis.

role_t1 property writable #

role_t1: AxisRole

Physical role of the T1 (indirect) axis.

role_t2 property writable #

role_t2: AxisRole

Physical role of the T2 (direct) axis.

_axis_1 class-attribute instance-attribute #

_axis_1: NdAxis | None = betterproto2.field(
    1, betterproto2.TYPE_MESSAGE, optional=True
)

Exactly two time/spatial axes (enforced by validation)

e.g., t1

_axis_2 class-attribute instance-attribute #

_axis_2: NdAxis | None = betterproto2.field(
    2, betterproto2.TYPE_MESSAGE, optional=True
)

e.g., t2

_visualization_handler class-attribute instance-attribute #

_visualization_handler: str = ''

_amplitudes class-attribute instance-attribute #

_amplitudes: NdComplexArray | None = betterproto2.field(
    3, betterproto2.TYPE_MESSAGE, optional=True
)

2D complex amplitude data (shape = [N, M])

_points_f1_view instance-attribute #

_points_f1_view: ndarray | None

_points_f2_view instance-attribute #

_points_f2_view: ndarray | None

_np_dirty_points_f1 instance-attribute #

_np_dirty_points_f1: bool

_np_dirty_points_f2 instance-attribute #

_np_dirty_points_f2: bool

points_f1 property writable #

points_f1: FloatArray

points_f2 property writable #

points_f2: FloatArray

visualization_handler property writable #

visualization_handler: str

Plugin visualization handler ID (e.g., 'nanalysis:cosy_2d').

x property writable #

x: FloatArray

x_units property #

x_units: TimeUnit

y property writable #

y: FloatArray

y_units property writable #

y_units: TimeUnit

z property writable #

z: FloatArray

__module__ class-attribute instance-attribute #

__module__ = GrpcSignal2D.__module__

t1_unit property writable #

t1_unit: TimeUnit

Time unit for t1 (indirect) dimension.

t2_unit property writable #

t2_unit: TimeUnit

Time unit for t2 (direct) dimension.

reference_frequency_hz instance-attribute #

reference_frequency_hz = float(reference_frequency_hz)

carrier_offset_hz_1 instance-attribute #

carrier_offset_hz_1 = float(carrier_offset_hz_1)

carrier_offset_hz_2 instance-attribute #

carrier_offset_hz_2 = float(carrier_offset_hz_2)

f1_reference_frequency_hz property writable #

f1_reference_frequency_hz: float | None

Reference frequency for F1 (indirect) dimension, for heteronuclear PPM conversion.

f2_reference_frequency_hz property writable #

f2_reference_frequency_hz: float | None

Reference frequency for F2 (direct) dimension, for heteronuclear PPM conversion.

reference_signal_f1 instance-attribute #

reference_signal_f1 = reference_signal_f1

reference_signal_f2 instance-attribute #

reference_signal_f2 = reference_signal_f2

t1 property writable #

t1: FloatArray

Time coordinates for indirect dimension (axis 1).

t2 property writable #

t2: FloatArray

Time coordinates for direct dimension (axis 2).

shape property #

shape: tuple[int, int]

is_complex property #

is_complex: bool

Whether the signal has complex amplitudes.

sampling_rate_hz_t1 property #

sampling_rate_hz_t1: float

Sampling rate in Hz for t1 (indirect) dimension.

sampling_rate_hz_t2 property #

sampling_rate_hz_t2: float

Sampling rate in Hz for t2 (direct) dimension.

Signal1D #

Signal1D(
    time_points: FloatArray | None = None,
    amplitudes: NumericArray | None = None,
    time_unit: TimeUnit = MICROSECOND,
    metadata: dict[str, Any] | None = None,
    reference_frequency_hz: float | None = None,
    carrier_offset_hz: float | None = None,
)

Time-domain 1D signal.

Attributes:

Name Type Description
time_points FloatNdArray

Time coordinates of the signal

amplitudes

Signal amplitudes (can be complex)

time_unit

Unit of time coordinates

metadata dict[str, str]

Optional metadata dictionary

processing_history class-attribute instance-attribute #

processing_history: ProcessingHistory | None = (
    betterproto2.field(
        6, betterproto2.TYPE_MESSAGE, optional=True
    )
)

Processing history - read-only audit log of all operations applied to this signal

history property #

history: Sequence[ProcessingHistoryEntry]

Read-only access to processing history entries.

Returns:

Type Description
Sequence[ProcessingHistoryEntry]

Immutable sequence of history entries. Returns empty tuple if

Sequence[ProcessingHistoryEntry]

no history has been recorded yet.

metadata class-attribute instance-attribute #

metadata: dict[str, str] = betterproto2.field(
    3,
    betterproto2.TYPE_MAP,
    map_meta=betterproto2.map_meta(
        betterproto2.TYPE_STRING, betterproto2.TYPE_STRING
    ),
)

Optional metadata

axis_role property writable #

axis_role: AxisRole

Physical role of this signal's time axis.

nucleus property writable #

nucleus: Nucleus

Nucleus type for this signal's time axis.

visualization_handler property writable #

visualization_handler: str

Plugin visualization handler ID (e.g., 'nanalysis:cosy_2d').

time_points instance-attribute #

time_points: FloatNdArray = np.asarray(
    time_points, dtype=np.float64
)

amplitudes instance-attribute #

amplitudes = np.asarray(amplitudes, dtype=np.complex128)

time_unit instance-attribute #

time_unit = time_unit

reference_frequency_hz instance-attribute #

reference_frequency_hz = float(reference_frequency_hz)

carrier_offset_hz instance-attribute #

carrier_offset_hz = float(carrier_offset_hz)

duration property #

duration: float

Total duration of the signal in current time units.

duration_seconds property #

duration_seconds: float

Total duration of the signal in seconds.

sampling_rate_hz property #

sampling_rate_hz: float

Sampling rate in Hz.

is_complex property #

is_complex: bool

Whether the signal has complex amplitudes.

extract_echo_amplitudes #

extract_echo_amplitudes(
    echo_spacing_ms: float, n_echoes: int | None = None
) -> tuple[ndarray, ndarray]

Extract echo peak amplitudes from raw CPMG signal.

For pre-processed data where each point IS an echo amplitude, this simply constructs the time axis from the echo spacing.

For raw interleaved data (full echo shapes), this finds the maximum of each echo window.

Parameters:

Name Type Description Default
echo_spacing_ms float

Time between echoes in milliseconds.

required
n_echoes int | None

Number of echoes to extract. If None, uses all data.

None

Returns:

Type Description
tuple[ndarray, ndarray]

(echo_times_s, echo_amplitudes) — times in seconds.

compute_t2_distribution #

compute_t2_distribution(
    echo_spacing_ms: float | None = None,
    t_min: float = 1e-05,
    t_max: float = 10.0,
    n_points: int = 200,
    alpha: float | None = None,
) -> RelaxationDistribution

Compute T2 distribution via Inverse Laplace Transform.

Parameters:

Name Type Description Default
echo_spacing_ms float | None

Echo spacing in ms. If None, inferred from time_points.

None
t_min float

Minimum T2 in seconds for the distribution grid.

1e-05
t_max float

Maximum T2 in seconds for the distribution grid.

10.0
n_points int

Number of points in the T2 grid.

200
alpha float | None

Regularization parameter (None = auto via GCV).

None

Returns:

Type Description
RelaxationDistribution

RelaxationDistribution with the T2 spectrum.

fit_multiexponential #

fit_multiexponential(
    echo_spacing_ms: float | None = None,
    n_components: int | Literal["auto"] = "auto",
    max_components: int = 4,
) -> MultiExponentialResult

Fit discrete multi-exponential decay to the echo train.

Parameters:

Name Type Description Default
echo_spacing_ms float | None

Echo spacing in ms. If None, inferred from time_points.

None
n_components int | Literal['auto']

Number of components, or "auto" for BIC-based selection.

'auto'
max_components int

Maximum components to try when n_components="auto".

4

Returns:

Type Description
MultiExponentialResult

MultiExponentialResult with fitted time constants and amplitudes.

pad_zeros #

pad_zeros(factor: float | None = None) -> Self

Pad signal with zeros to increase frequency resolution after FFT.

Parameters:

Name Type Description Default
factor float | None

Zero-fill factor. If None, pads to next power of 2.

None

Returns:

Type Description
Self

Self for method chaining

convert_time_unit #

convert_time_unit(target_unit: TimeUnit) -> Self

Convert signal to different time unit.

Parameters:

Name Type Description Default
target_unit TimeUnit

Target time unit

required

Returns:

Type Description
Self

Self for method chaining

trim_dead_time #

trim_dead_time(
    threshold_ratio: float = 0.1,
    max_dead_time: float | None = None,
    dead_time_unit: TimeUnit | None = None,
) -> Self

Remove dead time from the beginning of the signal.

Parameters:

Name Type Description Default
threshold_ratio float

Signal threshold as ratio of max amplitude

0.1
max_dead_time float | None

Maximum dead time to consider

None
dead_time_unit TimeUnit | None

Unit for max_dead_time (defaults to signal's time unit)

None

Returns:

Type Description
Self

Self for method chaining

get_upper_envelope #

get_upper_envelope(
    smoothing_kernel_factor: float = 70,
) -> Self

Create an upper-envelope of the signal.

Parameters:

Name Type Description Default
smoothing_kernel_factor float

Factor to determine smoothing kernel size

70

Returns:

Type Description
Self

New signal containing the upper envelope

align_to #

align_to(
    reference: Self,
    max_shift_seconds: float = 0.02,
    resolution: int = 3,
    fast: bool = False,
) -> Self

Cut the signal to align with the reference.

Cross-correlates data within region of interest at a precision of 1/res. If data is cross-correlated at native resolution (i.e. res=1), this function can only achieve integer precision.

Parameters:

Name Type Description Default
reference Self

Signal to align to.

required
max_shift_seconds float

Max shift in seconds. Defaults to 0.02.

0.02
resolution int

Resolution of phase alignment. Defaults to 3.

3
fast bool

Whether to use a faster version of the algorithm. Defaults to False.

False

Returns:

Type Description
Self

Self for method chaining

apodize #

apodize(func: Callable[[ndarray], ndarray]) -> Self
apodize(
    func: WindowType,
    *,
    lb: float = 1.0,
    sigma: float = 0.3,
    alpha: float = 5.0,
) -> Self
apodize(
    func: Callable[[ndarray], ndarray] | WindowType,
    *,
    lb: float = 1.0,
    sigma: float = 0.3,
    alpha: float = 5.0,
) -> Self

Apply apodization (window function) to the signal.

Parameters:

Name Type Description Default
func Callable[[ndarray], ndarray] | WindowType

Either a callable apodization function that takes range [0;1] and returns weights, or a string window type from: "none", "exponential", "gaussian", "hamming", "hann", "blackman", "blackmanharris", "kaiser", "bartlett", "cosine", "tukey"

required
lb float

Line broadening factor for exponential window (Hz). Default 1.0.

1.0
sigma float

Standard deviation for gaussian window (0-0.5 range). Default 0.3.

0.3
alpha float

Shape parameter for kaiser (beta) or tukey (alpha) windows. Default 5.0.

5.0

Returns:

Type Description
Self

Self for method chaining.

to_spectrum #

to_spectrum(
    fft_method: Literal[
        "real", "full", "standard"
    ] = "real",
    window: WindowType | None = None,
    window_lb: float = 1.0,
    window_sigma: float = 0.3,
    window_alpha: float = 5.0,
) -> Spectrum1D

Convert signal to frequency spectrum.

Parameters:

Name Type Description Default
fft_method Literal['real', 'full', 'standard']

FFT computation method ("real", "full", "standard").

'real'
window WindowType | None

Optional window function to apply before FFT. Options: "none", "exponential", "gaussian", "hamming", "hann", "blackman", "blackmanharris", "kaiser", "bartlett", "cosine", "tukey".

None
window_lb float

Line broadening for exponential window (Hz). Default 1.0.

1.0
window_sigma float

Sigma for gaussian window (0-0.5). Default 0.3.

0.3
window_alpha float

Alpha/beta for kaiser/tukey windows. Default 5.0.

5.0

Returns:

Type Description
Spectrum1D

Spectrum object.

apply_window #

apply_window(
    window_func: Callable[[ndarray], ndarray],
) -> Self

Apply windowing function to the signal.

Parameters:

Name Type Description Default
window_func Callable[[ndarray], ndarray]

Function that takes array of length N and returns window

required

Returns:

Type Description
Self

Self for method chaining

normalize #

normalize(
    method: Literal["max", "rms", "unit"] = "max",
) -> Self

Normalize signal amplitudes.

Parameters:

Name Type Description Default
method Literal['max', 'rms', 'unit']

Normalization method: - "max": Divide by maximum absolute value - "rms": Divide by RMS value - "unit": Divide by L2 norm

'max'

Returns:

Type Description
Self

Self for method chaining

load #

load(
    stream: SupportsRead[bytes], size: int | None = None
) -> Self

parse classmethod #

parse(data: bytes) -> Self

FromString classmethod #

FromString(s: bytes) -> Self

to_nd #

to_nd(visualization_handler: str | None = None) -> SignalNd

copy #

copy() -> Self

Create a deep copy of this signal.

from_proto classmethod #

from_proto(proto: Signal1D | SignalNd | None) -> Self

Shallow-copy underscored proto storage into our instance.

_init_history #

_init_history() -> None

Initialize the processing history.

Called from init of Signal1D/Spectrum1D. Always creates a ProcessingHistory instance - history is always enabled.

_record_history_entry #

_record_history_entry(
    operation: str,
    parameters: dict[str, str],
    shape_before: tuple[int, ...],
    shape_after: tuple[int, ...],
    source: str = "tqt_nmr",
) -> None

Record a processing operation to the history.

This is an internal method - not exposed to users. Called by @track_operation decorator and TrackedArray.

Parameters:

Name Type Description Default
operation str

Name of the operation (e.g., "correct_phase_manual").

required
parameters dict[str, str]

Operation parameters as string key-value pairs.

required
shape_before tuple[int, ...]

Shape of amplitudes before operation.

required
shape_after tuple[int, ...]

Shape of amplitudes after operation.

required
source str

Source of the operation (e.g., "tqt_nmr", "numpy").

'tqt_nmr'

_copy_history_to #

_copy_history_to(other: Self) -> None

Copy processing history to another object.

Used by copy() methods to preserve history lineage.

Parameters:

Name Type Description Default
other Self

Object to copy history to.

required

apodize #

apodize(
    window: WindowType = "exponential",
    axis: Literal["t1", "t2", "both"] = "both",
    *,
    lb: float = 1.0,
    sigma: float = 0.3,
    alpha: float = 5.0,
) -> Self

Apply apodization (window function) to the 2D signal.

Parameters:

Name Type Description Default
window WindowType

Window type from: "none", "exponential", "gaussian", "hamming", "hann", "blackman", "blackmanharris", "kaiser", "bartlett", "cosine", "tukey".

'exponential'
axis Literal['t1', 't2', 'both']

Which axis to apply window to ("t1", "t2", or "both").

'both'
lb float

Line broadening for exponential window (Hz). Default 1.0.

1.0
sigma float

Sigma for gaussian window (0-0.5). Default 0.3.

0.3
alpha float

Alpha/beta for kaiser/tukey windows. Default 5.0.

5.0

Returns:

Type Description
Self

Self for method chaining.

to_spectrum #

to_spectrum(
    reference_frequency_hz: float | None = None,
    window: WindowType | None = None,
    window_axis: Literal["t1", "t2", "both"] = "both",
    window_lb: float = 1.0,
    window_sigma: float = 0.3,
    window_alpha: float = 5.0,
    zero_fill_f1: int | None = None,
    zero_fill_f2: int | None = None,
    shift_f1: bool = True,
    shift_f2: bool = True,
    tppi_mode: Literal[
        "none", "correct", "correct_and_half", "half_only"
    ]
    | None = None,
    tppi_keep_upper: bool = False,
    fft_axes: Literal["both", "t2", "t1", "auto"] = "auto",
) -> Spectrum2D

Convert 2D signal to 2D frequency spectrum via FFT.

Parameters:

Name Type Description Default
reference_frequency_hz float | None

Reference frequency for PPM calculations.

None
window WindowType | None

Optional window function to apply before FFT. Options: "none", "exponential", "gaussian", "hamming", "hann", "blackman", "blackmanharris", "kaiser", "bartlett", "cosine", "tukey".

None
window_axis Literal['t1', 't2', 'both']

Which axis to apply window to ("t1", "t2", or "both").

'both'
window_lb float

Line broadening for exponential window (Hz). Default 1.0.

1.0
window_sigma float

Sigma for gaussian window (0-0.5). Default 0.3.

0.3
window_alpha float

Alpha/beta for kaiser/tukey windows. Default 5.0.

5.0
zero_fill_f1 int | None

Zero-fill factor for F1 dimension (None = auto: 4x if < 128 points, else next power of 2)

None
zero_fill_f2 int | None

Zero-fill factor for F2 dimension (None = auto: next power of 2)

None
shift_f1 bool

Apply fftshift to F1 axis (default True). Set False for TPPI acquisition.

True
shift_f2 bool

Apply fftshift to F2 axis (default True).

True
tppi_mode Literal['none', 'correct', 'correct_and_half', 'half_only'] | None

TPPI/half-spectrum processing mode: - None or "none": No TPPI processing (default) - "correct": Apply TPPI correction only (multiply odd rows by -1) - "correct_and_half": Apply TPPI correction AND extract half of F1 spectrum - "half_only": Extract half of F1 spectrum without TPPI correction (for non-phase-sensitive acquisitions like CPMG, JRES)

None
tppi_keep_upper bool

Which half of F1 to keep when extracting: - False (default): keep lower half (frequencies < carrier) - for MIRRORIMAGE=0 - True: keep upper half (frequencies >= carrier) - for MIRRORIMAGE=1

False
fft_axes Literal['both', 't2', 't1', 'auto']

Which axes to FFT: - "auto" (default): auto-detect from axis roles. If t1 has PARAMETER role, only FFT along t2 (row-wise 1D). Otherwise full 2D FFT. - "both": Full 2D FFT (standard for COSY, HSQC, etc.) - "t2": FFT only along t2 (direct dimension). Keeps t1 as-is. Use for relaxation experiments (T1, T2, CPMG, DOSY). - "t1": FFT only along t1 (indirect dimension). Keeps t2 as-is.

'auto'

Returns:

Type Description
Spectrum2D

Spectrum2D object.

_to_spectrum_1d_rows #

_to_spectrum_1d_rows(
    fft_axis: Literal["t2", "t1"],
    reference_frequency_hz: float | None = None,
    window: WindowType | None = None,
    window_lb: float = 1.0,
    window_sigma: float = 0.3,
    window_alpha: float = 5.0,
) -> Spectrum2D

FFT along one axis only, keeping the other as parameter values.

For relaxation experiments (T1/T2/CPMG/DOSY), the indirect axis is a parameter (delay, gradient strength), not a time dimension. Each row (or column) is an independent FID that gets its own 1D FFT.

Parameters:

Name Type Description Default
fft_axis Literal['t2', 't1']

Which axis to FFT ("t2" = row-wise, "t1" = column-wise).

required
reference_frequency_hz float | None

Reference frequency for PPM calculations.

None
window WindowType | None

Window function for apodization before FFT.

None
window_lb float

Line broadening parameter.

1.0
window_sigma float

Gaussian sigma parameter.

0.3
window_alpha float

Kaiser/tukey alpha parameter.

5.0

normalize #

normalize(
    method: Literal["max", "rms", "unit"] = "max",
) -> Self

Normalize signal amplitudes.

Parameters:

Name Type Description Default
method Literal['max', 'rms', 'unit']

Normalization method: - "max": Divide by maximum absolute value - "rms": Divide by RMS value - "unit": Divide by L2 norm

'max'

Returns:

Type Description
Self

Self for method chaining

__post_init__ #

__post_init__() -> None

__bytes__ #

__bytes__() -> bytes

load #

load(
    stream: SupportsRead[bytes], size: int | None = None
) -> Self

parse classmethod #

parse(data: bytes) -> Self

FromString classmethod #

FromString(s: bytes) -> Self

_pb_amplitudes #

_pb_amplitudes() -> NdComplexArray

_ensure_storage_initialized #

_ensure_storage_initialized() -> None

_mark_dirty #

_mark_dirty(field: str) -> None

_sync_proto_to_numpy #

_sync_proto_to_numpy() -> None

_pb_axis_f1 #

_pb_axis_f1() -> NdAxis

_pb_axis_f2 #

_pb_axis_f2() -> NdAxis

_ensure_axes_initialized #

_ensure_axes_initialized() -> None

to_nd #

to_nd(visualization_handler: str | None = None) -> SignalNd

__len__ #

__len__() -> int

copy #

copy() -> Self

Create a deep copy of this signal.

pad_zeros #

pad_zeros(
    factor_t1: float | None = None,
    factor_t2: float | None = None,
) -> Self

Pad signal with zeros to increase resolution after FFT.

Parameters:

Name Type Description Default
factor_t1 float | None

Zero-fill factor for t1 dimension (None = next power of 2)

None
factor_t2 float | None

Zero-fill factor for t2 dimension (None = next power of 2)

None

Returns:

Type Description
Self

Self for method chaining

correct_tppi #

correct_tppi() -> Self

Apply TPPI (Time-Proportional Phase Incrementation) correction.

In TPPI acquisition, the phase of the first pulse in the indirect dimension is incremented by 90° for each t1 increment. This effectively shifts the carrier frequency by half the spectral width in F1.

To correct for this before FFT, we multiply every other row by -1, which undoes the phase alternation and properly centers the spectrum in F1.

This method should be called before FFT for data acquired with TPPI.

Returns:

Type Description
Self

Self for method chaining

get_base_signal #

get_base_signal() -> Self

Get the main signal without reference data.

Returns a copy of this signal with reference fields set to None.

get_reference_f1 #

get_reference_f1() -> Signal1D | None

Get F1 (indirect dimension) reference signal.

get_reference_f2 #

get_reference_f2() -> Signal1D | None

Get F2 (direct dimension) reference signal.

has_references #

has_references() -> bool

Check if any reference signals are attached.

from_proto classmethod #

from_proto(proto: Signal2D | SignalNd | None) -> Self

_setup_from_arrays #

_setup_from_arrays(
    t1: FloatArray,
    t2: FloatArray,
    amplitudes: ndarray,
    t1_unit: TimeUnit,
    t2_unit: TimeUnit,
) -> None

_sync_numpy_to_proto #

_sync_numpy_to_proto() -> None

Override to sync reference signals before serialization.