Processing Data

SpinLab provides a comprehensive set of processing functions for NMR and EPR data. All functions follow the same design pattern:

  • They accept a SpinData object as their first argument.

  • They return a new SpinData object — the original is never modified.

  • They automatically stamp a record of the processing step (name + parameters) into proc_attrs.

A typical NMR processing workflow looks like this:

import spinlab as sl

data = sl.load("path/to/fid/")           # load raw FID
data = sl.apodize(data, lw=5)            # apply window function
data = sl.fourier_transform(data)        # Fourier transform
data = sl.phase(data, p0=45)             # phase correction
data = sl.remove_background(data)        # baseline correction

sl.plot(data)                            # plot the spectrum

Apodization (Window Functions)

sl.apodize() multiplies the time-domain signal by a window function before Fourier transformation to improve sensitivity or resolution.

# Exponential line broadening (most common for NMR)
data = sl.apodize(data, dim="t2", kind="exponential", lw=5)

# Gaussian window
data = sl.apodize(data, kind="gaussian", lw=5)

# Hann or Hamming window (good for EPR ESEEM)
data = sl.apodize(data, kind="hann")
data = sl.apodize(data, kind="hamming")

# Sin-squared (common for 2D EPR)
data = sl.apodize(data, kind="sin2")

# Lorentz-Gauss transformation (resolution enhancement)
data = sl.apodize(data, kind="lorentz_gauss", lw=4, gauss_lw=8)

# TRAF window
data = sl.apodize(data, kind="traf", lw=5, gauss_lw=10)

Available window functions:

kind

Description

"exponential"

Exponential decay — line broadening, improves sensitivity

"gaussian"

Gaussian window — line broadening with Gaussian shape

"hann"

Hann (von Hann) window — reduces spectral leakage

"hamming"

Hamming window — similar to Hann, slightly different shape

"sin2"

Sine-squared — commonly used for 2D EPR experiments

"lorentz_gauss"

Lorentz-to-Gauss transformation — resolution enhancement

"traf"

TRAF function — sensitivity/resolution compromise

Fourier Transform

sl.fourier_transform() performs a fast Fourier transform (FFT) along the chosen dimension. The time axis is renamed automatically (e.g. "t2""f2").

# Basic Fourier transform along t2 (default)
data = sl.fourier_transform(data)

# Zero-fill to twice the original length before transforming
data = sl.fourier_transform(data, zero_fill_factor=2)

# Fourier transform along t1 in a 2D dataset
data = sl.fourier_transform(data, dim="t1")

# Suppress fftshift (zero frequency stays at edge)
data = sl.fourier_transform(data, shift=False)

Note

For NMR data imported from TopSpin, the frequency axis is automatically converted to ppm using the NMR frequency stored in attrs. Set convert_to_ppm=False to keep the axis in Hz.

The inverse Fourier transform is available as sl.inverse_fourier_transform().

Phase Correction

sl.phase() applies zero-order (p0) and first-order (p1) phase corrections to complex spectral data.

# Zero-order phase correction only
data = sl.phase(data, p0=45.0)

# Zero- and first-order correction with a pivot point
data = sl.phase(data, p0=45.0, p1=120.0, pivot=0.0)

# Apply a different phase to each spectrum in a 2D dataset
import numpy as np
phases = np.array([10.0, 15.0, 20.0, 25.0])
data = sl.phase(data, p0=phases)

For automated phase correction use sl.autophase():

# Autophase all spectra independently (entropy minimization)
data = sl.autophase(data)

# Autophase using a specific reference slice
data = sl.autophase(data, reference_slice=("Average", 0))

Baseline / Background Correction

sl.remove_background() fits and removes a polynomial background from the data.

# Remove DC offset (0th-order polynomial, most common)
data = sl.remove_background(data)

# Remove a linear background (1st order)
data = sl.remove_background(data, deg=1)

# Fit background only in specified regions (leaving signal untouched)
data = sl.remove_background(data, deg=1, regions=[(-500, -200), (200, 500)])

# Use a custom fitting function (e.g. exponential background)
data = sl.remove_background(data, dim="tau",
                            func=sl.relaxation.general_exp,
                            p0=(1, -1, 900))

Alignment

sl.ndalign() aligns a series of spectra using FFT cross-correlation. This is useful for correcting small frequency drifts across a set of repeated measurements.

# Align all spectra to the last one (default reference)
data = sl.ndalign(data)

# Align using only a sub-region of the spectrum
data = sl.ndalign(data, center=10.0, width=5.0)

# Align to a specific reference spectrum
reference = data["Average", 0]
data = sl.ndalign(data, reference=reference)

Integration

sl.integrate() calculates the area under the curve using the trapezoidal rule.

# Integrate over the entire dimension
result = sl.integrate(data, dim="f2")

# Integrate over a specific region
result = sl.integrate(data, dim="f2", regions=[(-10, 10)])

sl.cumulative_integrate() returns the running integral (cumulative sum), useful for visualizing EPR lineshapes or calculating enhancement profiles:

data_cumint = sl.cumulative_integrate(data, dim="B0")

Conversion

SpinLab provides unit conversion utilities in the sl.processing.conversion module:

# Convert microwave power from Watts to dBm
power_dBm = sl.w2dBm(power_W)

# Convert from dBm to Watts
power_W = sl.dBm2w(power_dBm)

Helpers

Several utility functions assist with common data preparation tasks.

Create a complex dataset from separate real and imaginary arrays:

data_complex = sl.create_complex(data, real=real_data, imag=imag_data)

# Or from two slices along a dimension (e.g. "channel")
data_complex = sl.create_complex(data, real="channel",
                                 real_index=0, imag_index=1)

Signal-to-noise ratio calculation:

snr = sl.signal_to_noise(data,
                          signal_region=(-1.0, 1.0),
                          noise_region=[(-400, -200), (200, 400)])

Inspecting the Processing Log

After applying processing steps, the full audit log is available via proc_info():

data.proc_info()

Example output:

1 | window           | kind: exponential, lw: 5
2 | fourier_transform | dim: t2, zero_fill_factor: 2, shift: True
3 | phase            | p0: 45.0, p1: 0.0, pivot: None
4 | remove_background| dim: f2, deg: 0, regions: None

Full NMR Processing Example

import spinlab as sl

# 1. Load raw FID from TopSpin
data = sl.load("experiment/1/")

# 2. Apply exponential line broadening (5 Hz)
data = sl.apodize(data, dim="t2", kind="exponential", lw=5)

# 3. Fourier transform with 2x zero-filling
data = sl.fourier_transform(data, zero_fill_factor=2)

# 4. Phase correction
data = sl.phase(data, p0=12.5)

# 5. Remove linear baseline
data = sl.remove_background(data, deg=1)

# 6. Plot
sl.plt.figure()
sl.plot(data)
sl.plt.xlabel("Chemical shift (ppm)")
sl.plt.tight_layout()
sl.plt.show()

# 7. Save the processed spectrum
sl.save(data, "processed.h5")

Full EPR ESEEM Processing Example

import spinlab as sl

# 1. Load raw ESEEM time trace
data = sl.load("eseem_data.d01")

# 2. Remove background decay
data = sl.remove_background(data, dim="t2", deg=3)

# 3. Apply Hamming window
data = sl.apodize(data, dim="t2", kind="hamming")

# 4. Fourier transform with 4x zero-filling
data = sl.fourier_transform(data, zero_fill_factor=4)

# 5. Take the absolute value
import numpy as np
data.values = np.abs(data.values)

# 6. Plot the ESEEM spectrum
sl.plt.figure()
sl.plot(data)
sl.plt.xlabel("Frequency (MHz)")
sl.plt.tight_layout()
sl.plt.show()

Further Reading