Two-dimensional NMR processing

End-to-end 2D NMR processing from raw SER data to a 2D frequency-domain spectrum.

This example complements sphx_glr_plugins_spectrochempy-nmr_examples_processing_nmr_plot_processing_nmr.py:

  • plot_processing_nmr.py starts from a TopSpin 2rr dataset that is already processed.

  • This example starts from the raw TopSpin ser file and performs the main processing steps inside SpectroChemPy.

The pipeline is intentionally split in two stages:

  1. F2 (direct dimension): apodization -> FFT -> auto-phase

  2. F1 (indirect dimension): zero-filling -> apodization -> FFT

It illustrates:

  • reading raw 2D SER data;

  • automatic handling of Bruker encodings such as STATES-TPPI;

  • quaternion-aware 2D FFT processing;

  • how to obtain a spectrum comparable to the processed 2rr reference.

Requires the official spectrochempy-nmr plugin. Install with: pip install spectrochempy[nmr].

Import API

import spectrochempy as scp

Read raw 2D SER data

The raw SER file contains time-domain data together with the acquisition metadata needed to decode the hypercomplex encoding in F1.

nmrdir = scp.preferences.datadir / "nmrdata" / "bruker" / "tests" / "nmr"
ser = scp.nmr.read(nmrdir / "topspin_2d", expno=1, remove_digital_filter=True)
/home/runner/work/spectrochempy/spectrochempy/plugins/spectrochempy-nmr/src/spectrochempy_nmr/extern/nmrglue/_bruker.py:441: UserWarning: (196608,)cannot be shaped into(147, 1024)
  _, data = read_binary(f, shape=shape, cplex=cplex, big=big, isfloat=isfloat)

Print dataset summary — both dimensions are in time domain

NDDataset [topspin_2d expno:1 procno:1 (SER)] — quaternion, shape: (y:96, x:948), count
name
:
topspin_2d expno:1 procno:1 (SER)
author
:
runner@runnervm3jd5f
created
:
2026-07-18 21:38:45+00:00
history
:
2026-07-18 21:38:45+00:00> Imported from TopSpin dataset
Data
title
:
intensity
values
:
RR[[ -0.2238 -0.1985 ... -0.1662 0.03262]
[-0.006566 0.0282 ... -0.02949 -0.06717]
...
[ 0 0 ... 0 0]
[ 0 0 ... 0 0]] count
RI[[ 0.06219 0.1467 ... 0.04565 0.03068]
[-0.05969 -0.08752 ... -0.05134 -0.05994]
...
[ -0 -0 ... -0 -0]
[ -0 -0 ... -0 -0]] count
IR[[ -0.1623 -0.0563 ... 0.02654 -0.01094]
[ 0.1344 -0.006515 ... -0.08239 0.00516]
...
[ 0 0 ... 0 0]
[ 0 0 ... 0 0]] count
II[[-0.003312 -0.001535 ... 0.02067 -0.08058]
[-0.05685 0.1174 ... 0.05831 -0.003414]
...
[ -0 -0 ... -0 -0]
[ -0 -0 ... -0 -0]] count
shape
:
(y:96(complex), x:948(complex))
Dimension `x`
size
:
948
title
:
F2 acquisition time
coordinates
:
[ 0 48 ... 4.541e+04 4.546e+04] µs
Dimension `y`
size
:
96
title
:
F1 acquisition time
coordinates
:
[ 0 74 ... 6956 7030] µs


Plot the raw time-domain data (quaternion representation)

_ = ser.plot_map()
plot processing nmr 2d

For comparison, load the TopSpin-processed reference spectrum. This is similar to the dataset used in plot_processing_nmr.py.

reference = scp.nmr.read(nmrdir / "topspin_2d" / "1" / "pdata" / "1" / "2rr")

Plot the processed TopSpin reference

_ = reference.plot_map()
plot processing nmr 2d

Stage 1: process F2 (direct dimension)

Experiment.process() applies the usual direct-dimension workflow: exponential multiplication, FFT, then automatic phasing along F2.

Here we also set the F2 zero-filled size to match the processed TopSpin spectrum more closely.

from spectrochempy_nmr import Experiment

exp = Experiment(ser)
f2_processed = exp.process(apodization="em", lb=2.0, size=2048)

# After F2 processing: F2 is in frequency domain, F1 is still time domain
f2_processed
/home/runner/work/spectrochempy/spectrochempy/src/spectrochempy/utils/numutils.py:29: RuntimeWarning: divide by zero encountered in log10
  n_decimals = sigdigits - int(np.floor(np.log10(abs(n)))) - 1
/home/runner/work/spectrochempy/spectrochempy/src/spectrochempy/core/dataset/coord.py:902: RuntimeWarning: divide by zero encountered in scalar divide
  (np.max(spacing) - np.min(spacing))
NDDataset [topspin_2d expno:1 procno:1 (SER)] — quaternion, shape: (y:96, x:2048), count
name
:
topspin_2d expno:1 procno:1 (SER)
author
:
runner@runnervm3jd5f
created
:
2026-07-18 21:38:45+00:00
history
:
2026-07-18 21:38:45+00:00> Imported from TopSpin dataset
2026-07-18 21:38:48+00:00> `em` apodization performed on dimension `x` with parameters: {'lb': }
2026-07-18 21:38:48+00:00> `zf_size` shift performed on dimension `x` with parameters: {'size': 2048}
2026-07-18 21:38:48+00:00> `zf_size` shift performed on dimension `x` with parameters: {'size': 2048}
2026-07-18 21:38:48+00:00> Fft applied on dimension x
2026-07-18 21:38:48+00:00> `pk` applied to dimension `x` with parameters: {'phc0': -43.22656, 'phc1': 23235.2, 'pivot': 0.0, 'exptc': 0.0}
Data
title
:
intensity
values
:
RR[[ 2.756 1.854 ... 0.03561 -0.9503]
[ 2.076 -1.155 ... 0.3568 1.068]
...
[ 0 0 ... -0 -0]
[ 0 0 ... -0 -0]] count
RI[[ 2.875 -0.3508 ... -4.254 -3.024]
[ -0.2439 -0.4442 ... 0.7714 -2.242]
...
[ 0 0 ... 0 0]
[ 0 0 ... 0 0]] count
IR[[-0.008057 1.359 ... -1.558 -0.4359]
[ -0.2737 -2.446 ... -2.225 -3.934]
...
[ 0 0 ... -0 -0]
[ 0 0 ... -0 -0]] count
II[[ 0.3187 -0.884 ... 0.2557 1.38]
[ -3.224 1.061 ... -1.517 0.575]
...
[ 0 0 ... 0 0]
[ 0 0 ... 0 0]] count
shape
:
(y:96(complex), x:2048(complex))
Dimension `x`
size
:
2048
title
:
$\delta\ ^{27}Al$
coordinates
:
[ 96.79 96.7 ... -102.8 -102.9] ppm
Dimension `y`
size
:
96
title
:
F1 acquisition time
coordinates
:
[ 0 74 ... 6956 7030] µs


Plot after F2 processing — only the horizontal axis is transformed

_ = f2_processed.plot_map()
plot processing nmr 2d

Stage 2: process F1 (indirect dimension)

F1 still needs the usual indirect-dimension steps. We keep them explicit here because they are often tuned case by case in practice.

The values below are chosen so that the resulting spectrum is close to the processed TopSpin 2rr reference shipped with the test data.

f1 = f2_processed.zf_size(size=1024, dim="y")
f1 = f1.em(lb=5.0, dim="y")
f1 = f1.fft(dim="y")

f1
NDDataset [topspin_2d expno:1 procno:1 (SER)] — quaternion, shape: (y:1024, x:2048), count
name
:
topspin_2d expno:1 procno:1 (SER)
author
:
runner@runnervm3jd5f
created
:
2026-07-18 21:38:45+00:00
history
:
2026-07-18 21:38:45+00:00> Imported from TopSpin dataset
2026-07-18 21:38:48+00:00> `em` apodization performed on dimension `x` with parameters: {'lb': }
2026-07-18 21:38:48+00:00> `zf_size` shift performed on dimension `x` with parameters: {'size': 2048}
2026-07-18 21:38:48+00:00> `zf_size` shift performed on dimension `x` with parameters: {'size': 2048}
2026-07-18 21:38:48+00:00> Fft applied on dimension x
2026-07-18 21:38:48+00:00> `pk` applied to dimension `x` with parameters: {'phc0': -43.22656, 'phc1': 23235.2, 'pivot': 0.0, 'exptc': 0.0}
2026-07-18 21:38:49+00:00> Data swapped between dims -2 and -1
2026-07-18 21:38:49+00:00> `zf_size` shift performed on dimension `y` with parameters: {'size': 1024, 'dim': 'y'}
2026-07-18 21:38:49+00:00> Data swapped between dims -2 and -1
2026-07-18 21:38:49+00:00> Data swapped between dims -2 and -1
2026-07-18 21:38:49+00:00> `em` apodization performed on dimension `y` with parameters: {'lb': }
2026-07-18 21:38:49+00:00> Data swapped between dims -2 and -1
2026-07-18 21:38:49+00:00> Data swapped between dims -2 and -1
2026-07-18 21:38:49+00:00> `zf_size` shift performed on dimension `y` with parameters: {'size': 1024}
2026-07-18 21:38:49+00:00> Fft applied on dimension y
2026-07-18 21:38:49+00:00> `pk` applied to dimension `y` with parameters: {'phc0': 33.6, 'phc1': -0.0, 'pivot': 0.0, 'exptc': 0.0}
2026-07-18 21:38:49+00:00> Data swapped between dims -2 and -1
Data
title
:
intensity
values
:
RR[[ 16.41 8.054 ... -2.03 0.2295]
[ 18.47 7.006 ... -4.365 -1.674]
...
[ 10.31 9.28 ... 1.601 2.976]
[ 13.61 8.833 ... 0.07038 1.847]] count
RI[[ 8.073 -6.503 ... -9.794 -8.518]
[ 4.937 -7.555 ... -9.712 -9.415]
...
[ 12.08 -3.861 ... -7.44 -5.102]
[ 10.52 -5.24 ... -8.974 -7.049]] count
IR[[ 0 0 ... 0 0]
[ 0 0 ... 0 0]
...
[ 0 0 ... 0 0]
[ 0 0 ... 0 0]] count
II[[ 0 0 ... 0 0]
[ 0 0 ... 0 0]
...
[ 0 0 ... 0 0]
[ 0 0 ... 0 0]] count
shape
:
(y:1024(complex), x:2048(complex))
Dimension `x`
size
:
2048
title
:
$\delta\ ^{27}Al$
coordinates
:
[ 96.79 96.7 ... -102.8 -102.9] ppm
Dimension `y`
size
:
1024
title
:
$\delta\ ^{31}P$
coordinates
:
[ 38.79 38.7 ... -44.52 -44.6] ppm


Plot the fully processed 2D spectrum

_ = f1.plot_map()
plot processing nmr 2d

Extract and inspect a 1D slice

Once the 2D spectrum is processed we can reuse the same style of operations shown in plot_processing_nmr.py.

slice_f2 = f1[-27.6, :]
_ = slice_f2.plot()
plot processing nmr 2d

Compare the same slice against the TopSpin 2rr reference. We normalize both traces before overlaying them because the absolute intensity depends on the exact processing chain (window functions, phasing, scaling conventions, etc.).

reference_slice_f2 = reference[-27.6, :]
slice_f2_norm = slice_f2.normalize(method="max", dim="x")
reference_slice_f2_norm = reference_slice_f2.normalize(method="max", dim="x")
_ = slice_f2_norm.plot(color="black")
_ = reference_slice_f2_norm.plot(clear=False, color="red", linestyle="--")
plot processing nmr 2d

Total running time of the script: (0 minutes 8.478 seconds)