Process step by step

Under development

Loading and processing raw time-domain data is new and its interface may change.

This page covers the processing functions one by one: what each does, its options, and how to apply it to one dimension of multidimensional data. For an introduction, see the tutorial Processing a 1D spectrum.

using NMRTools, CairoMakie
fid = exampledata("1D_1H"; fid=true)
fid2d = exampledata("2D_HN"; fid=true, firstpoint=Dict(T2Dim => 1.0))

The ¹⁵N dimension of 2D_HN starts half a dwell time after t = 0, so its first point is not a t = 0 point and is not halved (firstpoint, see loadfid).

The functions

FunctionActs onEffect
apodizetime domainmultiply by a window function
zerofilltime domainappend zeros (default: double the size)
linearpredicttime domainextend a dimension, or repair its first points
solventfiltertime domainremove a solvent signal
fteitherFourier transform (inverse=true for the inverse)
phaseeitherzero- and first-order phase correction
autophasefrequency domainestimate and apply a phase correction
extracteitherselect a region in a pipeline, e.g. extract(F1Dim, 6.0 .. 10.0)
real, realesteitherthe real part, giving Float64 data (real for 1D, realest for nD)

Each function takes an NMRData and returns a new one. Called without data, each returns a function, so steps chain with |>:

spec = fid |> apodize(ExponentialWindow(5.0)) |> zerofill |> ft |> phase(F1Dim, 16.8, 0.0) |> real

Each also accepts a Dict of spectra and processes every entry.

Choosing dimensions

Without a dimension argument, apodize, zerofill and ft act on every Fourier dimension with the same settings, and autophase on every frequency dimension. linearpredict extends the indirect dimensions, and solventfilter acts on the direct dimension. Pass a dimension (by type or number) to act on one only, for example to use different windows in each dimension of a 2D:

spec2d = fid2d |>
         apodize(T1Dim, Cos2Window()) |> apodize(T2Dim, CosWindow()) |>
         zerofill |> ft |>
         phase(F1Dim, -247.9, 218.4) |> phase(F2Dim, 90.0, -180.0) |>
         realest
fig = nmrplot(spec2d[6 .. 10.5, :])

The phases here are those used to process this dataset in TopSpin; see Phasing below.

Window functions

apodize(spec, window) multiplies the FID by a window function. Without a window, the one recorded in the dimension's :window metadata is used. Common choices:

WindowUse
ExponentialWindow(lb)line broadening of lb Hz; improves sensitivity
Cos2Window()cos², falls to zero at the end of the FID; a good default
CosWindow()cosine (sine bell shifted by 90°)
SineWindow(...)general sine bell
GaussWindow(...), LorentzToGaussWindow(...)resolution enhancement
fig, ax, _ = nmrplot(real(fid |> apodize(ExponentialWindow(5.0)) |> zerofill |> ft |> phase(F1Dim, 16.8, 0.0))[0.5 .. 1.0];
                     title="exponential (5 Hz) and cos² windows")
nmrplot!(ax, real(fid |> apodize(Cos2Window()) |> zerofill |> ft |> phase(F1Dim, 16.8, 0.0))[0.5 .. 1.0])

See Window functions for every window and its parameters.

Zero-filling

zerofill(spec) doubles the size of each Fourier dimension. Pass factor for another multiple, and a dimension to zero-fill one dimension only:

size(zerofill(fid; factor=4)), size(zerofill(fid2d, T2Dim; factor=4))
((65536,), (1024, 256))

Solvent filtering

solventfilter removes a solvent signal from the FID before apodization. By default it acts on the carrier frequency; pass shift (ppm) to target another position. method=:lowpass (default) subtracts a low-pass filtered copy of the FID, the convolution difference method of nmrPipe's SOL. method=:hsvd fits and subtracts only the lines within a band around the target, which is slower but more selective.

filtered = solventfilter(fid)
fig, ax, _ = nmrplot(real(fid |> apodize(Cos2Window()) |> zerofill |> ft |> phase(F1Dim, 16.8, 0.0))[3.5 .. 6];
                     title="before and after solvent filtering")
nmrplot!(ax, real(filtered |> apodize(Cos2Window()) |> zerofill |> ft |> phase(F1Dim, 16.8, 0.0))[3.5 .. 6])

fid |> solventfilter(method=:hsvd) |> apodize |> zerofill |> ft
fid |> solventfilter(shift=4.75) |> apodize |> zerofill |> ft

The filter takes the digital-filter group delay into account, filtering the FID from t = 0.

autoprocess applies a solvent filter to ¹H spectra in H₂O (turn it off with solvent=false). The water signal's dispersion tails help automatic phasing, so autoprocess estimates the phase of a 1D spectrum before filtering, leaving out ±0.2 ppm around the water, and applies it to the filtered spectrum. To do the same by hand:

est = estimatephase(fid |> apodize(Cos2Window()) |> zerofill |> ft; exclude=[4.5 .. 4.9])
spec = filtered |> apodize(Cos2Window()) |> zerofill |> ft |> phase(F1Dim, est.p0, est.p1)
est.p0, est.p1
(16.618847918164622, 0.4276711401518191)

Phasing

phase(spec, dim, p0, p1) applies the phase θ = p0 + p1·n/N to point n (counting from zero at the left-hand, highest-ppm edge), in degrees. The sign and pivot match TopSpin's PHC0 and PHC1, so values can be copied between the two. Pass pivot (in ppm) to move the point at which only p0 acts.

Each call adds to the dimension's :phase metadata, the total correction applied so far:

spec2d[F1Dim, :phase]
(p0 = 112.10000000000001, p1 = 218.4)

Automatic phasing

autophase estimates the correction and applies it, to every frequency dimension in turn unless a dimension is given:

auto = fid |> apodize(Cos2Window()) |> zerofill |> ft |> autophase
auto[F1Dim, :phase]
(p0 = 14.09351960805514, p1 = 2.1841559631632643)

To see the estimate without applying it, use estimatephase. It returns the phases with a residual (the weighted rms phase error of the lines used, in degrees), the number of line groups used, and the groups left out of the fit:

unphased = fid |> apodize(Cos2Window()) |> zerofill |> ft
est = estimatephase(unphased)
est.p0, est.p1, est.residual, est.nlines
(14.09351960805514, 2.1841559631632643, 7.979950270021072, 30)

A large residual, few groups, or a strong rejected group indicates an unreliable estimate.

Useful options:

  • order=0 fits the zero-order phase only, with the first-order phase fixed at p1 (default 0). Once the digital-filter group delay has been removed, the first-order phase of the direct dimension should be close to zero, so this is often the more robust choice.
  • p1 and maxdelay set the expected first-order phase and how far it may range. A first-order phase is a shift of the time origin, so the range is given as a timing uncertainty (default 20 µs) and converted with the spectral width.
  • exclude=[4.6 .. 4.9] ignores lines in the given chemical-shift ranges, for example water or buffer.
  • λ sets how strongly the first-order phase is pulled towards p1.

Multidimensional spectra

For multidimensional data, autophase(spec, dim) estimates the correction from 1D traces along dim through the strongest cross peaks, pooled into one fit. Each trace combines the components of the other dimensions so that it does not depend on how they are phased, and ridges such as t₁ noise from residual water are left out. For an indirect dimension, first-order phases of 0° and -180° (a first point half a dwell time after t = 0) are both tried.

auto2d = fid2d |> apodize(T1Dim, Cos2Window()) |> apodize(T2Dim, CosWindow()) |> zerofill |> ft |>
         autophase
auto2d[F1Dim, :phase], auto2d[F2Dim, :phase]
((p0 = 115.73125752100853, p1 = 218.47093032148567), (p0 = 100.92949744277664, p1 = -203.6553618092495))

These are within about 4° (¹H) and 13° (¹⁵N) of TopSpin's phases for this dataset, (−247.9°, 218.4°) and (90°, −180°). The ¹H first-order phase is large, and the cross peaks span only 6–10 ppm, which constrains it less well than lines across the whole spectrum would. Check the result, and set phases known from the pulse sequence or from TopSpin with phase.

The estimate is logged at debug level; set ENV["JULIA_DEBUG"] = "NMRTools" to see it. The method is described in the autophase docstring.

Extracting a region

extract keeps a region of one or more dimensions in a pipeline, for example to discard empty parts of the spectrum:

amides = spec2d |> extract(F1Dim, 6.0 .. 10.5)
size(spec2d), size(amides)
((2048, 128), (511, 128))

extract(F1Dim => 6.0 .. 10.5, F2Dim => 105 .. 135) selects in several dimensions. Outside a pipeline, index the spectrum with a selector (spec2d[6.0 .. 10.5, :]); extract also gives the result its own metadata and refreshes its noise estimate.

Pseudo-2D series

In a pseudo-2D experiment, such as a relaxation series, only the direct dimension is Fourier transformed. loadfid loads the series axis as an unknown dimension (X2Dim), which the processing functions leave alone:

series = exampledata("pseudo2D_T2"; fid=true)
dims(series)
(↓ T1Dim Sampled{Float64} -0.0063840000000000025:8.400000000000004e-5:1.3697880000000007 ForwardOrdered Regular Points,
→ X2Dim Sampled{Int64} 1:16 ForwardOrdered Regular Points)

Process it as a 1D, then set the series axis with setrelaxtimes (or setgradientlist, setkinetictimes, setoffsets):

relax = series |> apodize(ExponentialWindow(2.0)) |> zerofill |> ft |> autophase |> real
relax = setrelaxtimes(relax, acqus(relax, :vclist) * 4e-3, "s")
fig = nmrplot(relax[0.7 .. 1.0, :]; style=:flat)
┌ Warning: autophase: strong lines left out of the fit, with their remaining phase error: 4.69 ppm (-85°), 3.76 ppm (-37°). They may have a genuinely different phase, or the first-order phase may be far from the expected value; pass `p1` (and `maxdelay`) to set it.
└ @ NMRTools.NMRBase ~/work/NMRTools.jl/NMRTools.jl/src/NMRBase/autophase.jl:532

autophase estimates the phase from the first spectrum of the series and applies it to all of them.

Real and imaginary parts

For 1D data, real(spec) gives the real spectrum. For multidimensional data, realest(spec) gives the part real in every dimension. real and imag take the coefficients of the highest-order imaginary unit: for 2D data real.(spec) and imag.(spec) are the parts real and imaginary in the indirect dimension, each still complex in the direct dimension. Individual components are available with component. See Multicomplex numbers and quadrature.