Skip to content

Spectrum2D #

Spectrum2D(
    f1: FloatArray | None = None,
    f2: FloatArray | None = None,
    amplitudes: ndarray | None = None,
    f1_unit: FrequencyUnit = HERTZ,
    f2_unit: FrequencyUnit = HERTZ,
    metadata: dict[str, Any] | None = None,
    reference_spectrum_f1: Spectrum1D | None = None,
    reference_spectrum_f2: Spectrum1D | None = None,
    f1_reference_frequency_hz: float | None = None,
    f2_reference_frequency_hz: float | None = None,
)

2D frequency-domain spectrum for NMR experiments (e.g., COSY, HSQC).

Attributes:

Name Type Description
f1, f2

FloatArray (np.ndarray[float64]) - frequency axes

amplitudes ndarray

np.ndarray[complex] with shape (len(f1), len(f2))

f1_unit, f2_unit

FrequencyUnit

f1_reference_frequency_hz, f2_reference_frequency_hz

Reference frequencies for PPM conversion

processing_history class-attribute instance-attribute #

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

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

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.

_frequency_axis_1 class-attribute instance-attribute #

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

Exactly two frequency axes (enforced by validation)

e.g., f1

_frequency_axis_2 class-attribute instance-attribute #

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

e.g., f2

_amplitudes class-attribute instance-attribute #

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

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

metadata instance-attribute #

metadata: dict[str, str]

amplitudes property writable #

amplitudes: ndarray

is_jres property #

is_jres: bool

Whether this plane is a J-resolved experiment (detected, never configured).

is_jres_tilted property #

is_jres_tilted: bool

Whether :meth:tilt_jres has already sheared this plane.

_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 class-attribute instance-attribute #

_visualization_handler: str = ''

visualization_handler property writable #

visualization_handler: str

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

x property writable #

x: FloatArray

x_units property writable #

x_units: FrequencyUnit

y property writable #

y: FloatArray

y_units property writable #

y_units: FrequencyUnit

nucleus_f1 property writable #

nucleus_f1: Nucleus

Nucleus type for the F1 (indirect) axis.

nucleus_f2 property writable #

nucleus_f2: Nucleus

Nucleus type for the F2 (direct) axis.

role_f1 property writable #

role_f1: AxisRole

Physical role of the F1 (indirect) axis.

role_f2 property writable #

role_f2: AxisRole

Physical role of the F2 (direct) axis.

z property writable #

z: FloatArray

__module__ class-attribute instance-attribute #

__module__ = GrpcSpectrum2D.__module__

f1_unit property writable #

f1_unit: FrequencyUnit

Frequency unit for first dimension (F1/indirect).

f2_unit property writable #

f2_unit: FrequencyUnit

Frequency unit for second dimension (F2/direct).

f1_reference_frequency_hz property writable #

f1_reference_frequency_hz: float | None

Reference frequency for F1 dimension (for PPM conversion).

f2_reference_frequency_hz property writable #

f2_reference_frequency_hz: float | None

Reference frequency for F2 dimension (for PPM conversion).

reference_spectrum_f1 instance-attribute #

reference_spectrum_f1 = reference_spectrum_f1

reference_spectrum_f2 instance-attribute #

reference_spectrum_f2 = reference_spectrum_f2

f1 property writable #

f1: FloatArray

Frequency coordinates for first dimension.

f2 property writable #

f2: FloatArray

Frequency coordinates for second dimension.

shape property #

shape: tuple[int, int]

is_complex property #

is_complex: bool

Whether the spectrum has complex amplitudes.

f2_imaginary property writable #

f2_imaginary: ndarray | None

The F2-dispersion quadrant RI + i*II, or None for non-hypercomplex data.

A hypercomplex transform takes the real part along F2 before transforming F1, so the F2 dispersion channel has to live somewhere if an F2 phase correction is to remain possible afterwards; it lives here. It is a processing intermediate: it is deliberately not a proto field and does not survive serialization, so it is available for as long as the spectrum stays in the process that transformed it.

f1_acquisition_mode property #

f1_acquisition_mode: F1AcquisitionMode

How the indirect dimension was quadrature-detected.

Read from the metadata the transform stamps; "undefined" when the spectrum did not come from a 2D transform that knew the mode.

is_hypercomplex property #

is_hypercomplex: bool

Whether the F2-dispersion quadrant is available for an F2 phase correction.

True only for a spectrum produced by the hypercomplex transform in this process: the second quadrant is a processing intermediate and is not carried over the wire, so a spectrum received from gRPC reports False.

is_f1_phase_sensitive property #

is_f1_phase_sensitive: bool

Whether an F1 phase correction can do anything to this spectrum.

A complex amplitudes array is not evidence: a magnitude-mode (QF) or undefined acquisition still comes out of a complex transform with a non-zero imaginary part, and phasing it only fits a meaningless ramp to the mirror-image artefacts. Only a phase-sensitive F1 acquisition scheme -- States, States-TPPI, Echo-Antiecho or TPPI -- gives F1 a phase worth correcting.

is_f2_phase_sensitive property #

is_f2_phase_sensitive: bool

Whether an F2 phase correction can do anything to this spectrum.

The direct dimension is quadrature detected in every 2D experiment, so this is true whenever there is an imaginary channel to rotate into: either the ordinary complex amplitudes of a non-hypercomplex transform, or the second quadrant.

_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

detect_jres #

detect_jres() -> JresDetection

Measure whether this plane's multiplets lie on a shearable line.

tilt_jres #

tilt_jres(force: bool = False) -> Self

Shear the plane so multiplets become vertical.

The slope is measured from the plane's own peaks, so an unconventional slope is followed as readily as +/-1. A plane whose peaks do not lie on a shearable line, and a plane already tilted, are returned untouched with the reason logged.

Parameters:

Name Type Description Default
force bool

Shear on the measured slope even when the alignment is too weak to pass detection. A plane with no measurable slope at all is still left alone.

False

Returns:

Type Description
Self

Self, for chaining.

symmetrize_f1 #

symmetrize_f1() -> Self

Symmetrise the plane about F1 = 0, suppressing asymmetric artefacts.

Each point is scaled down to the smaller of its own magnitude and its mirror's; the phase of genuine, symmetric peaks is untouched. A no-op on an F1 axis with fewer than two points or a zero increment.

Returns:

Type Description
Self

Self, for chaining.

skyline_projection_f2 #

skyline_projection_f2() -> Spectrum1D

Maximum-magnitude projection onto F2 — a decoupled 1D spectrum once tilted.

Returns:

Type Description
Spectrum1D

A real-valued :class:Spectrum1D on this spectrum's F2 axis.

process_jres #

process_jres(symmetrize: bool = True) -> Self

Run the conventional JRES chain if, and only if, a shear collapses this plane.

Parameters:

Name Type Description Default
symmetrize bool

Also symmetrise about F1 = 0 after tilting (standard practice).

True

Returns:

Type Description
Self

Self, unchanged when the plane's peaks do not lie on a shearable line.

_f1_hz #

_f1_hz() -> ndarray

_f2_hz #

_f2_hz() -> ndarray

find_peaks #

find_peaks(
    min_distance_points: int | None = None,
    min_height_ratio: float = 0.05,
    use_magnitude: bool = True,
    adaptive: bool = False,
    snr_threshold: float = 5.0,
) -> list[PeakInfo2D]

Find peaks in 2D spectrum.

There is one detector, not a choice between named ones. Local maxima above the height gate are found first, then the maxima that the structure of the plane shows to be artefacts rather than resonances are rejected — see :func:_reject_plane_artefacts. Nothing here reads a pulse-program name, a title or a filename, and no experiment class is special-cased: t1 ridges and edge features are properties of the numbers, so they are measured.

Parameters:

Name Type Description Default
min_distance_points int | None

Minimum peak separation in points (in each dimension). If None, auto-calculates as ~2% of each axis.

None
min_height_ratio float

Minimum peak height as a ratio of the maximum height (0-1). Applies in BOTH modes. Unlike the 1D find_peaks, adaptive=True does not discard it: on a 2D plane it acts as a floor under the noise gate, because t1 noise ridges and truncation wiggles sit far above the thermal noise floor and a bare noise gate would report thousands of them.

0.05
use_magnitude bool

If True, work on |z| else on Re(z). When True the noise gate uses the noise floor of the magnitude plane itself, pedestal included.

True
adaptive bool

If True, raise the height threshold to snr_threshold noise sigmas whenever that exceeds min_height_ratio * max. The sigma comes from estimate_noise_plane, which measures each row along the direct dimension, so it is valid for a pseudo-2D plane whose indirect axis is a delay list; the gate is referenced to the measured baseline level. Defaults to False, matching the 1D find_peaks; the callers inside this class that want it pass adaptive=True explicitly.

False
snr_threshold float

When adaptive=True, minimum SNR for peak detection.

5.0

Returns:

Type Description
list[PeakInfo2D]

List of PeakInfo2D objects containing peak positions and heights,

list[PeakInfo2D]

with the plane's measurable artefacts removed.

calculate_peak_volumes #

calculate_peak_volumes(
    peaks: list[PeakInfo2D],
    radius_factor: float = 3.0,
    use_magnitude: bool = True,
) -> list[PeakInfo2D]

Calculate volumes for 2D peaks using elliptical integration regions.

For each peak, estimates the peak width in both dimensions by finding the half-height contour, then integrates within an ellipse of radius_factor * half-width.

Parameters:

Name Type Description Default
peaks list[PeakInfo2D]

List of PeakInfo2D objects from find_peaks().

required
radius_factor float

Multiplier for the half-width to define the integration ellipse (default 3.0 = ~99% of Gaussian).

3.0
use_magnitude bool

If True, integrate |z|, else integrate Re(z).

True

Returns:

Type Description
list[PeakInfo2D]

The same peaks with volume field populated.

estimate_snr #

estimate_snr(
    signal_region: tuple[
        tuple[float, float], tuple[float, float]
    ]
    | None = None,
) -> float

Estimate signal-to-noise ratio of the 2D spectrum.

The noise sigma comes from estimate_noise_plane, which measures the quiet regions of each row along the direct dimension.

Parameters:

Name Type Description Default
signal_region tuple[tuple[float, float], tuple[float, float]] | None

Optional ((f1_min, f1_max), (f2_min, f2_max)) to restrict signal calculation. If None, uses entire spectrum.

None

Returns:

Type Description
float

Signal-to-noise ratio (linear scale)

infer_multiplets #

infer_multiplets(
    peaks: list[PeakInfo2D] | None = None,
    group_axis: Literal["f1", "f2"] = "f1",
    snr_threshold: float = 30.0,
    margin_factor: float = 2.0,
) -> list[MultipletGroup2D]

Infer multiplet patterns from 2D spectrum using slice-and-project.

The groups are the ones group_peaks() labels the peaks with — there is one clustering, not two — so every returned MultipletGroup2D.group_id equals the group_id carried by its member peaks and peak_indices index into the peaks list exactly as supplied. Callers that have already grouped their peaks keep those labels; ungrouped peaks are grouped here.

For each group, cuts a rectangle from the 2D spectrum, projects (sums) along the grouping axis to produce a 1D micro-spectrum along the spread axis, then runs 1D multiplet scoring (spacing regularity + amplitude symmetry) on the projection peaks.

For HSQC (group_axis="f1"): Projects onto the 1H axis → detects the 1H multiplet. For COSY (group_axis="f2"): Projects onto the 13C axis → detects coupling partners.

Frequencies and couplings are reported in Hz, converted from the axis' own unit through that axis' reference frequency; a PPM axis with no usable reference leaves the Hz values at 0.0 and the couplings unset rather than reporting a PPM number under a Hz name.

Parameters:

Name Type Description Default
peaks list[PeakInfo2D] | None

Pre-detected peaks. If None, runs find_peaks(adaptive=True).

None
group_axis Literal['f1', 'f2']

Which axis is the grouping (projected-over) axis. "f1" — spread along F2 gives multiplicity. "f2" — spread along F1 gives multiplicity.

'f1'
snr_threshold float

SNR threshold for peak detection (if peaks not provided).

30.0
margin_factor float

How much to expand the bounding box around each cluster (as a multiple of the cluster extent in each axis).

2.0

Returns:

Type Description
list[MultipletGroup2D]

List of MultipletGroup2D with pattern, coupling constant, and score.

measure_peak_footprints #

measure_peak_footprints(
    peaks: list[PeakInfo2D], use_magnitude: bool = True
) -> list[PeakInfo2D]

Populate each peak's width_f1 / width_f2, in that axis' own units.

An NMR lineshape is separable in the two dimensions, so a peak covers an axis-aligned ellipse whose semi-axes are half its measured line width on each axis. The widths are the same half-height measurements the grouping tolerances are read off, so a group's drawn extent and the rule that put its members together come from one measurement of the data.

Peaks whose flank runs off the plane before reaching half height take the median width of the peaks that could be measured, which is the same fallback the tolerances use; if nothing on an axis could be measured at all, that axis falls back to _MIN_WINDOW_POINTS of digital resolution so a footprint is never zero-sized.

Parameters:

Name Type Description Default
peaks list[PeakInfo2D]

Peaks to measure, from find_peaks().

required
use_magnitude bool

If True, measure widths on |z|, else on Re(z).

True

Returns:

Type Description
list[PeakInfo2D]

The same peaks with width_f1 and width_f2 populated.

group_extents #

group_extents(
    peaks: list[PeakInfo2D], use_magnitude: bool = True
) -> dict[int, tuple[float, float, float, float]]

Return each group's extent on the plane, in the axes' own units.

The extent is the axis-aligned bounding box of the group's members' elliptical footprints — the union of every member's centre ± half its line width on each axis — so it depends only on the data and not on the viewport, the zoom level, or which members happen to be on screen. It is the 2D counterpart of the 1D MultipletGroup's start/end frequencies.

Parameters:

Name Type Description Default
peaks list[PeakInfo2D]

Grouped peaks; ungrouped peaks are grouped here first.

required
use_magnitude bool

If True, measure widths on |z|, else on Re(z).

True

Returns:

Type Description
dict[int, tuple[float, float, float, float]]

group_id → (f1_min, f1_max, f2_min, f2_max).

grouping_tolerances #

grouping_tolerances(
    peaks: list[PeakInfo2D],
    width_factor: float = DEFAULT_WIDTH_FACTOR,
    use_magnitude: bool = True,
) -> tuple[float, float]

Return the (F1, F2) grouping tolerances in each axis' own units.

Each tolerance is width_factor times the median half-height width of the supplied peaks along that axis, measured on this spectrum's own plane and expressed in that axis' own coordinates. Nothing is carried across from the other axis, so the two tolerances differ by however much the axes differ in nucleus, span and digitization; and nothing is assumed about which nucleus or experiment produced the plane. Each tolerance is widened to _MIN_WINDOW_POINTS points of its own digital resolution.

Parameters:

Name Type Description Default
peaks list[PeakInfo2D]

Peaks whose line widths set the scale, from find_peaks().

required
width_factor float

Multiple of the measured width to allow within a group.

DEFAULT_WIDTH_FACTOR
use_magnitude bool

If True, measure widths on |z|, else on Re(z).

True

Returns:

Type Description
tuple[float, float]

(tolerance_f1, tolerance_f2), each in the units of its own axis.

group_peaks #

group_peaks(
    peaks: list[PeakInfo2D],
    width_factor: float = DEFAULT_WIDTH_FACTOR,
    tolerance_f1: float | None = None,
    tolerance_f2: float | None = None,
) -> list[PeakInfo2D]

Group 2D peaks by proximity, along whichever directions the plane shows.

Which rule applies is measured, never inferred from a pulse-program name, a title or a filename: measure_alignment reads the direction the plane's own peaks step in when they are close together (see alignment_geometry). Three outcomes, all of them the same measurement:

The peaks line up on a diagonal — the components of one multiplet differ in both coordinates but share shared_f1 * F1 + shared_f2 * F2. They are grouped on that shared coordinate and chained along the direction they are spread in. A J-resolved plane is this case with a slope near -1, but so is any other plane whose multiplets happen to lie on a line, at any slope.

The peaks line up along one axis — the other coordinate is shared, because that dimension carries no structure (it is decoupled). Same rule, with the shared coordinate being one of the axes: cross peaks keep their whole multiplet without ever reaching across the decoupled axis.

The peaks do not line up — structure on both axes, or nothing but noise — and the per-axis rule below applies untouched.

Each axis is divided by its own tolerance, so one unit of scaled distance means "one tolerance" on either axis regardless of how the two axes differ in span, digitization or nucleus. Peaks are grouped so that every member of a group is within tolerance of every other member on both axes -- a chain of moderately spaced peaks does not merge endpoints that are far apart.

Parameters:

Name Type Description Default
peaks list[PeakInfo2D]

List of PeakInfo2D objects from find_peaks().

required
width_factor float

Multiple of the measured line width to allow within a group. Ignored on an axis given an explicit tolerance.

DEFAULT_WIDTH_FACTOR
tolerance_f1 float | None

Explicit F1 tolerance in F1 axis units.

None
tolerance_f2 float | None

Explicit F2 tolerance in F2 axis units.

None

Returns:

Type Description
list[PeakInfo2D]

The same peaks with group_id assigned.

alignment_geometry #

alignment_geometry(
    peaks: list[PeakInfo2D],
) -> AlignmentGeometry | None

Measure the direction this plane's peaks line up in, or None if unmeasurable.

The measurement is made on the peaks themselves, in Hz, at the line width this plane actually has on each axis — see :func:~tqt_nmr.core.spectrum.mixins.jres.measure_alignment. It answers the only question grouping needs: which combination of the two coordinates do the components of one feature share, and along which direction are they spread. No name, title, filename or nucleus takes part.

Parameters:

Name Type Description Default
peaks list[PeakInfo2D]

The peaks to measure, from find_peaks().

required

Returns:

Type Description
AlignmentGeometry | None

The geometry, or None when there are fewer than two peaks or an axis is

AlignmentGeometry | None

in ppm with no reference frequency, so the two axes cannot be put on one

AlignmentGeometry | None

Hz scale. Grouping then falls back to the per-axis rule, which needs no

AlignmentGeometry | None

such conversion.

_axes_convert_to_hz #

_axes_convert_to_hz() -> bool

Whether both axes can be expressed in Hz, which the experiment rules need.

A PPM axis with no reference frequency cannot be placed on a Hz scale, so neither the J-resolved shear nor a coupling-sized tolerance means anything on it; grouping then falls back to the per-axis rule, which needs no such conversion because it works entirely in each axis' own units.

_peak_positions_hz #

_peak_positions_hz(
    peaks: list[PeakInfo2D],
) -> tuple[ndarray, ndarray]

The peaks' (F1, F2) positions in Hz, whatever unit the axes are stored in.

_find_peaks_local_max #

_find_peaks_local_max(
    z: ndarray,
    min_distance_points: int | None,
    min_height_ratio: float,
    adaptive: bool,
    snr_threshold: float,
    use_magnitude: bool,
) -> list[PeakInfo2D]

Classical local maximum + threshold peak detection.

correct_phase_auto #

correct_phase_auto(
    axis: Literal["f1", "f2", "both"] = "both",
    method: Literal["acme", "peak_minima"] = "acme",
    trace: Literal["max", "sum"] = "max",
    per_trace: bool = False,
    multi_start: bool = False,
    return_phases: Literal[False] = False,
) -> Self
correct_phase_auto(
    axis: Literal["f1", "f2", "both"] = "both",
    method: Literal["acme", "peak_minima"] = "acme",
    trace: Literal["max", "sum"] = "max",
    per_trace: bool = False,
    multi_start: bool = False,
    *,
    return_phases: Literal[True],
) -> tuple[Self, dict[str, tuple[float, float]]]
correct_phase_auto(
    axis: Literal["f1", "f2", "both"] = "both",
    method: Literal["acme", "peak_minima"] = "acme",
    trace: Literal["max", "sum"] = "max",
    per_trace: bool = False,
    multi_start: bool = False,
    return_phases: bool = False,
) -> Self | tuple[Self, dict[str, tuple[float, float]]]

Apply automatic phase correction along the specified axis.

Phase is a property of the acquisition, not of an individual row or column, so a single (p0, p1) pair is determined ONCE per axis from a representative trace and then applied to every row/column. Phasing each trace independently (per_trace=True) introduces discontinuities along the indirect dimension and breaks the lineshape.

Parameters:

Name Type Description Default
axis Literal['f1', 'f2', 'both']

Which axis to correct ("f1", "f2", or "both")

'both'
method Literal['acme', 'peak_minima']

Objective used when a trace resolves too few multiplets for the peak regression.

'acme'
trace Literal['max', 'sum']

How to pick the representative trace - "max" uses the trace with the largest total magnitude, "sum" uses the projection (sum) over the other axis.

'max'
per_trace bool

Optimise and apply a separate phase for every row/column. Slow and physically wrong for most data; off by default.

False
multi_start bool

Accepted for signature compatibility with the settings API; the objective fallback on this path is single-start, so it selects nothing.

False
return_phases bool

If True, also return the phases applied per axis.

False

Returns:

Type Description
Self | tuple[Self, dict[str, tuple[float, float]]]

Self, or (Self, {"f1": (p0, p1), "f2": (p0, p1)}) if return_phases=True.

correct_phase_manual #

correct_phase_manual(
    p0: float,
    p1: float = 0.0,
    axis: Literal["f1", "f2", "both"] = "both",
    p2: float = 0.0,
) -> Self

Apply manual phase correction along specified axis.

Parameters:

Name Type Description Default
p0 float

Zero-order phase correction (degrees)

required
p1 float

First-order phase correction (degrees)

0.0
axis Literal['f1', 'f2', 'both']

Which axis to correct ("f1", "f2", or "both")

'both'
p2 float

Second-order phase correction (degrees)

0.0

Returns:

Type Description
Self

Self

_phase_auto_hypercomplex #

_phase_auto_hypercomplex(
    axis: Literal["f1", "f2", "both"],
    method: Literal["acme", "peak_minima"],
    trace: Literal["max", "sum"],
    return_phases: bool,
) -> Self | tuple[Self, dict[str, tuple[float, float]]]

Phase a hypercomplex plane through its quadrants.

F2 is measured on the F2-complex plane -- the absorption and dispersion channels of the plane that will actually be displayed -- and applied as a rotation between the quadrants. F1 is the complex axis of both planes, so it is an ordinary rotation applied to each of them.

_phase_along_last_axis staticmethod #

_phase_along_last_axis(
    amps: ndarray,
    method: Literal["acme", "peak_minima"],
    trace: Literal["max", "sum"],
    per_trace: bool,
) -> tuple[ndarray, tuple[float, float]]

Phase every row of amps (last axis), returning (phased, (p0, p1)).

correct_baseline #

correct_baseline(
    method: BaselineMethod2D = "arpls",
    axis: Literal["f1", "f2", "both"] = "both",
    edge_fraction: float = 0.1,
    poly_order: int = 1,
    smoothness_decades: float = 10.0,
    asymmetry: float = 0.001,
    iterations: int = 50,
    tol: float = 0.001,
    mask_sigma: float = 3.0,
    als_lambda: float = 100000.0,
    als_p: float = 0.01,
    als_iterations: int = 10,
    valley_prominence: float = 0.02,
    imaginary_method: ImaginaryBaselineMethod = "auto",
    allow_unsafe_poly_order: bool = False,
    **kwargs,
) -> Self

Apply baseline correction to a 2D spectrum.

Every method other than "plane" is applied trace by trace along the requested axis and behaves exactly as the 1D method of the same name.

Parameters:

Name Type Description Default
method BaselineMethod2D

Baseline correction method. "plane" fits a single 2D plane to the four corner regions; all other names are the 1D methods documented on BaselineCorrectionMixin1D.correct_baseline.

'arpls'
axis Literal['f1', 'f2', 'both']

Which axis to correct ("f1", "f2", or "both"); ignored for "plane".

'both'
edge_fraction float

Fraction of each edge used by the edge-anchored methods and by "plane" to size its corner regions.

0.1
poly_order int

Polynomial order for "polynomial" and "masked_poly".

1
smoothness_decades float

Whittaker smoothness as log10(lambda) quoted at 16384 points, length-normalised per trace.

10.0
asymmetry float

Asymmetry parameter p for "asls".

0.001
iterations int

Maximum reweighting iterations for the Whittaker methods.

50
tol float

Relative convergence tolerance for the Whittaker methods.

0.001
mask_sigma float

Signal-mask threshold in robust noise sigma.

3.0
als_lambda float

Smoothness parameter for "als", quoted at 16384 points and scaled by (N / 16384) ** 4.

100000.0
als_p float

Asymmetry parameter for "als".

0.01
als_iterations int

Iteration count for "als"; must be >= 1.

10
valley_prominence float

Minimum valley prominence as a fraction of max|y|.

0.02
imaginary_method ImaginaryBaselineMethod

How to treat the dispersion channel; see BaselineCorrectionMixin1D.correct_baseline.

'auto'
allow_unsafe_poly_order bool

Permit poly_order above MAX_SAFE_EDGE_POLY_ORDER for the edge-only "polynomial" method.

False
**kwargs

Additional parameters (ignored for compatibility)

{}

Returns:

Type Description
Self

Self

Raises:

Type Description
ProcessingParameterError

If method is not supported.

_baseline_plane #

_baseline_plane(edge_fraction: float) -> None

Fit and subtract a 2D plane estimated from the four corner regions.

Each corner contributes the median of a whole region rather than a single pixel, so an isolated noise spike cannot tilt the plane.

Parameters:

Name Type Description Default
edge_fraction float

Fraction of each axis used to size the corner regions.

required

_baseline_per_axis #

_baseline_per_axis(
    axis: Literal["f1", "f2", "both"],
    params: BaselineParams,
    imaginary_method: ImaginaryBaselineMethod,
) -> None

Apply the 1D baseline estimator to every row and/or column.

Parameters:

Name Type Description Default
axis Literal['f1', 'f2', 'both']

Axis/axes to correct.

required
params BaselineParams

Estimator selection and parameters.

required
imaginary_method ImaginaryBaselineMethod

How to treat the dispersion channel.

required

__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,
) -> SpectrumNd

__len__ #

__len__() -> int

__getitem__ #

__getitem__(key: tuple[int, int]) -> Self

Slice-only 2D indexing. Example: spec[:, 10:50] (Indexing with ints that reduce dimensionality is not supported here.)

copy #

copy() -> Self

Create a deep copy of this spectrum.

phase_f2_hypercomplex #

phase_f2_hypercomplex(
    p0: float, p1: float = 0.0, p2: float = 0.0
) -> Self

Rotate the F2 lineshape of a hypercomplex spectrum.

An F2 rotation of hypercomplex data is a rotation between the two quadrant planes, so an ordinary ps2 along F2 must not be used instead.

Parameters:

Name Type Description Default
p0 float

Zero-order phase in degrees.

required
p1 float

First-order phase in degrees, ramped across F2.

0.0
p2 float

Second-order phase in degrees.

0.0

Returns:

Type Description
Self

Self.

Raises:

Type Description
ProcessingParameterError

If the second quadrant is not available.

phase_f1_hypercomplex #

phase_f1_hypercomplex(
    p0: float, p1: float = 0.0, p2: float = 0.0
) -> Self

Rotate the F1 lineshape, keeping the second quadrant in step.

F1 is the complex axis of both quadrant planes, so this is an ordinary phase rotation along F1 -- applied to both planes so a later F2 rotation still sees a consistent pair.

reverse_f1 #

reverse_f1() -> Self

Traverse the F1 amplitudes in the opposite sense, quadrant included.

Whether the transformed F1 axis runs with or against the frequency axis is a property of the acquisition, and the import handlers flip it for the modes that need it. Doing it through this method keeps the second hypercomplex quadrant in step with amplitudes; flipping amplitudes alone would silently pair the two planes the wrong way round for a later F2 phase correction.

f2_complex_plane #

f2_complex_plane() -> ndarray

The F2-complex spectrum of the F1 absorption channel, RR + i*RI.

This is the array an F2 phase has to be measured on: it holds the F2 absorption and dispersion channels of the plane that will be displayed. Falls back to amplitudes when the spectrum is not hypercomplex.

normalize #

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

Normalize spectrum amplitudes.

Parameters:

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

Normalization method ("max", "rms", "unit")

'max'

Returns:

Type Description
Self

Self for method chaining

to_ppm #

to_ppm() -> Self

Convert spectrum frequency axes to PPM scale.

Returns:

Type Description
Self

New spectrum with PPM units (or copy if already in PPM)

Raises:

Type Description
ValueError

If reference frequency is not set for either axis and not already in PPM

smooth #

smooth(sigma: float = 2.0) -> Self

Apply 2D Gaussian smoothing to spectrum.

Parameters:

Name Type Description Default
sigma float

Standard deviation for Gaussian kernel

2.0

Returns:

Type Description
Self

Self for method chaining

symmetrize #

symmetrize() -> Self

Symmetrize a 2D homonuclear spectrum by averaging with its transpose.

This is valid only for homonuclear experiments (COSY, NOESY, TOCSY) where F1 and F2 correspond to the same nucleus, and the amplitude matrix is square.

Returns:

Type Description
Self

Self for method chaining

Raises:

Type Description
ProcessingParameterError

If the spectrum is not square or not homonuclear

trim_frequency_range #

trim_frequency_range(
    f1_min: float = -inf,
    f1_max: float = inf,
    f2_min: float = -inf,
    f2_max: float = inf,
) -> Self

Trim spectrum to specified frequency ranges.

Parameters:

Name Type Description Default
f1_min float

Range for f1 axis (from)

-inf
f1_max float

Range for f1 axis (to)

inf
f2_min float

Range for f2 axis (from)

-inf
f2_max float

Range for f2 axis (to)

inf

Returns:

Type Description
Self

A new, trimmed spectrum (the original is left unchanged).

convert_frequency_unit #

convert_frequency_unit(
    target_unit: FrequencyUnit,
    axis: Literal["f1", "f2", "both"] = "both",
) -> Self

Convert frequency axes to different units.

Parameters:

Name Type Description Default
target_unit FrequencyUnit

Target frequency unit

required
axis Literal['f1', 'f2', 'both']

Which axis to convert

'both'

Returns:

Type Description
Self

Self for method chaining

get_base_spectrum #

get_base_spectrum() -> Self

Get the main spectrum without reference data.

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

get_reference_f1 #

get_reference_f1() -> Spectrum1D | None

Get F1 (indirect dimension) reference spectrum.

get_reference_f2 #

get_reference_f2() -> Spectrum1D | None

Get F2 (direct dimension) reference spectrum.

has_references #

has_references() -> bool

Check if any reference spectra are attached.

apply_reference_correction #

apply_reference_correction() -> Self

Remove the reference acquisitions' phase from the plane.

Only the reference's phase is removed; every |amplitude| is left bit-for-bit unchanged, so there is no cost in signal-to-noise. Points where the reference is exactly zero define no phase and are left alone.

Returns:

Type Description
Self

Self for method chaining

to_signal #

to_signal(
    t1_unit: TimeUnit = MICROSECOND,
    t2_unit: TimeUnit = MICROSECOND,
    carrier_offset_hz_1: float | None = None,
    carrier_offset_hz_2: float | None = None,
    first_point_scale: bool | None = None,
) -> Signal2D

Convert spectrum back to time-domain signal via inverse 2D FFT.

Parameters:

Name Type Description Default
t1_unit TimeUnit

Time unit for t1 axis (default: MICROSECOND)

MICROSECOND
t2_unit TimeUnit

Time unit for t2 axis (default: MICROSECOND)

MICROSECOND
carrier_offset_hz_1 float | None

Carrier offset for F1 in Hz. If provided, subtracted from F1 frequencies before inverse FFT and stored in output signal.

None
carrier_offset_hz_2 float | None

Carrier offset for F2 in Hz. If provided, subtracted from F2 frequencies before inverse FFT and stored in output signal.

None
first_point_scale bool | None

Whether to undo the forward transform's first-point halving (doubling fid[0, 0]) on both axes so the roundtrip is lossless. If None, read from the first_point_scale metadata flag recorded by to_spectrum.

None

Returns:

Name Type Description
Signal2D Signal2D

Time-domain 2D signal

__repr__ #

__repr__() -> str

__str__ #

__str__() -> str

from_proto classmethod #

from_proto(proto: Spectrum2D | SpectrumNd | None) -> Self

_setup_from_arrays #

_setup_from_arrays(
    f1: FloatArray,
    f2: FloatArray,
    amplitudes: ndarray,
    f1_unit: FrequencyUnit,
    f2_unit: FrequencyUnit,
) -> None

_sync_numpy_to_proto #

_sync_numpy_to_proto() -> None

Override to sync reference spectra before serialization.