Bruker TopSpin NMR
Bruker NMR spectrometers (TopSpin and the older XWIN-NMR) store each experiment as a directory. OpenReadout returns the time-domain data (FID or ser), the processed spectra (1D, 2D and 3D, real and imaginary parts), the acquisition and processing parameters, and the non-uniform sampling list as a table.
Derived from nmrglue’s documentation and source (BSD-3-Clause, read as prior art and used as a reference reader) and public corpus experiment directories (XWIN-NMR 3.1/3.5, TOPSPIN 1.3, TopSpin 3.5 to 4.3.0). Provenance: docs/provenance/bruker-nmr.md. Format id bruker-nmr, family nmr.
A data set is a directory, not a file. OpenReadout opens the experiment directory <name>/<expno>/; a path to a file inside it (fid, ser, acqus, acqu2s, pulseprogram, nuslist) or to a processing directory (pdata, pdata/<procno>, or its procs/1r/2rr) resolves to that experiment (resolve_experiment_dir). The <name>/ directory above several experiments is detected (find_experiments) but not opened: the error lists the experiment numbers (exit 2).
<name>/<expno>/ acqus acqu2s acqu3s … acquisition parameters (status copies; `acqu`, `acqu2`: set-up copies) fid | ser time-domain samples (1D | nD) pulseprogram nuslist … other files (listed by `info --view structure`; nuslist is also a table) pdata/<procno>/ procs proc2s … processing parameters 1r 1i | 2rr 2ri 2ir 2ii | 3rrr … 3iii processed spectrum (real/imaginary parts)Parameter files (ParamFile, parse_param_file)
JCAMP-DX-like text (nmrglue read_jcamp):
##TITLE= Parameter file, TopSpin 4.3.0— the writing software and version (software; alsoXWIN-NMR\t\tVersion 3.1,TOPSPIN\t\tVersion 1.3). It is the file’sformat_version.##$NAME= value— one parameter. Names are case-sensitive (SW≠SW_h) and kept exactly as written.(0..n)starts an array of n+1 values continued on the following lines; items are numbers, bare words or<strings>.<…>is a string; it may continue over several lines until>(a probe head string in the synthetic tests).yes/noand other bare words are kept as text.
##LABEL=without$(##JCAMPDX=,##ORIGIN=,##OWNER=) →header;$$lines →comments(the second is a timestamp with time zone, the third the file’s original path).##END=ends the file (ended). Problems (short arrays, unterminated strings, stray lines) are collected inissues, never fatal;checkreports them asparameter_syntax/parameter_unterminated.
Values are exposed verbatim in info --view full’s vendor tree (acquisition.acqus.parameters, pdata.<procno>.procs.parameters).
Time-domain data (RawLayout)
| parameter | use |
|---|---|
DTYPA |
SampleType: 0 (or absent) Int32, 2 Float64; anything else → unsupported (exit 6) |
BYTORDA |
ByteOrder: 1 Big, otherwise Little |
AQ_mod |
1 or 3: real/imaginary interleaved (complex, channels real,imag); 0 or 2: real only |
TD (acqus) |
values per row (real and imaginary counted separately) |
TD (acqu2s, acqu3s, …) |
rows of ser, multiplied (rows) |
NC |
normalization exponent: values are scaled by 2^NC (channel scale; see below) |
fid: one row ofTDvalues; bytes after them are ignored (TopSpin 3.7 writes exactlyTD×4 bytes even whenTDis not a multiple of 256;checkreports extra bytes asfid_padding).ser: each row starts on a 1024-byte boundary (row_stride=TD×width rounded up to 1024; nmrglue quoting the acquisition reference onNBL). A file that is exactlyTD×width×rows long is read unpadded (unpadded_rows).rows_in_filecounts rows whose values are present (the last row’s padding may be missing). Fewer rows than declared →truncated(error, exit 4; a stopped acquisition looks the same).- No
acqu2s: rows from the file size. More whole rows than declared, and a multiple of them (nmrglue’s 3D test set has noacqu3s): rows from the file size, with a note. - Non-uniform sampling (
FnTYPE= 2):serholds the acquired rows only (acqu2sTD= 2 ×nuslistentries); they are returned as sweeps, not reconstructed onto theNusTDgrid (NUS reconstruction is out of scope). Thenuslistitself is table 0 (nuslist,extra.kindsampling_schedule): one row per acquired increment in file order, oneuint32column per indirect dimension (index_1,index_2, …), parsed byparse_nuslist(whitespace-separated unsigned integers, the same count on every line, at most 7). A file that is not such a list (or is over 16 MiB) is no table;checkreports it (nuslist_unreadable, fromnuslist_issue).
Scaling. nmrglue returns fid/ser unscaled. By analogy with its documented rule for processed data (value = stored × 2^NC_proc) OpenReadout multiplies time-domain values by 2^NC (NC is −1…−7 for int32 data and 0 for float64 data in the corpus). This is inferred; the factor is the channel scale, so the stored integers are value / scale exactly.
Digital filter. group_delay gives the group delay in points: GRPDLY when positive, else the DSP_GROUP_DELAY table (firmware DSPFVS 10–13, keyed by DECIM, as tabulated by nmrglue), else none. It is reported (group_delay_points, group_delay_source), never removed from the data.
Processed data (ProcLayout)
| parameter (procs / proc2s) | use |
|---|---|
SI |
points per dimension; the direct dimension is the trace’s sample_count, the product of the indirect ones its sweep_count |
XDIM |
submatrix edge of 2D and 3D files (ignored for 1D; 0 or ≥ SI means untiled); SI must be a multiple of it |
DTYPP, BYTORDP |
sample type and byte order, as for acquisition data |
NC_proc |
values are stored × 2^−NC_proc; read values = stored × 2^NC_proc (nmrglue scale_pdata) |
OFFSET, SW_p, SF |
ppm axis: point i is at OFFSET − i·SW_p/(SF·SI) (nmrglue unit_conversion) |
AXNUC |
nucleus of the axis |
1D: channels real (1r) and imag (1i, when present). 2D: channels real (2rr), ri, ir, ii (2ri, 2ir, 2ii, each when present; the first letter is F2, the second F1, as in the file names), one sweep per F1 row. 3D: channels real (3rrr), rri, rir, rii, irr, iri, iir, iii; sweep j + SI(F2) × k is F2 row j of F1 plane k (F2 fastest, as nmrglue’s reshape). Files are stored as XDIM-sized submatrices: submatrix index runs over the dimensions slowest-first (F1, F2, direct), each submatrix row-major (row_ranges; nmrglue reorder_submatrix). 4D+ processed data are not decoded (listed by info --view structure).
Traces
| trace | name |
channels | sample_rate_hz |
sweep_count |
|---|---|---|---|---|
time domain (extra.kind = time_domain) |
fid / ser |
real, imag (complex) or real |
SW_h (complex) or 2×SW_h (real) |
rows |
each pdata/<procno> with 1r, 2rr or 3rrr (processed_spectrum) |
pdata/<procno> |
real [, imag] (1D); real, ri, ir, ii (2D); real, rri … iii (3D) — those present |
0 (frequency domain) | 1, SI(F1) or SI(F2) × SI(F1) |
extra of the time-domain trace (our name ← Bruker parameter)
| our name | from | notes |
|---|---|---|
kind |
– | time_domain |
file |
– | fid or ser |
axis |
SW_h |
{quantity: time, unit: s, first: 0, step: 1/rate, size} |
nucleus |
NUC1 |
|
spectrometer_frequency_mhz |
SFO1 |
|
base_frequency_mhz |
BF1 |
|
carrier_offset_hz |
O1 |
|
spectral_width_hz |
SW_h |
|
spectral_width_ppm |
SW |
|
time_domain_size |
TD |
|
scans |
NS |
|
dummy_scans |
DS |
|
receiver_gain |
RG |
|
pulse_program |
PULPROG |
|
experiment |
EXP |
parameter-set name |
solvent |
SOLVENT |
|
temperature_k |
TE |
|
acquired_at |
DATE |
Unix seconds → ISO-8601 UTC (inferred; matches the $$ timestamp line) |
instrument |
INSTRUM |
|
probe |
PROBHD |
|
software, software_version |
##TITLE= |
|
quadrature |
AQ_mod |
complex or real |
acquisition_mode_code |
AQ_mod |
the number as written |
normalization_exponent |
NC |
|
sample_type, byte_order |
DTYPA, BYTORDA |
|
row_stride_bytes |
– | ser only |
group_delay_points, group_delay_source |
GRPDLY, DSPFVS, DECIM |
see Digital filter |
decimation |
DECIM |
|
dsp_firmware |
DSPFVS |
|
indirect_dimensions[] |
acqu2s … |
{dimension, parameter_file, nucleus (NUC1), time_domain_size (TD), spectral_width_hz (SW_h), spectral_width_ppm (SW), spectrometer_frequency_mhz (SFO1), encoding (FnMODE: undefined, QF, QSEQ, TPPI, States, States-TPPI, Echo-Antiecho as named by nmrglue)} |
non_uniform_sampling |
FnTYPE = 2 |
{nuslist_entries, amount_percent (NusAMOUNT), full_time_domain_size (acqu2s NusTD)} |
extra of a processed trace
kind (processed_spectrum), procno, axis ({quantity: chemical_shift, unit: ppm, first, step, last, size, spectral_width_hz, spectrometer_frequency_mhz}), nucleus (AXNUC), spectral_width_hz (SW_p), spectrometer_frequency_mhz (SF), transform_size (FTSIZE), phase0_deg (PHC0), phase1_deg (PHC1), line_broadening_hz (LB), window_function_code (WDW), normalization_exponent (NC_proc), sample_type, byte_order, files, and for 2D/3D submatrix (XDIM slowest dimension first), shape (SI slowest first) and indirect_axes (the ppm axis of each indirect dimension from proc2s, proc3s); for 2D also sweep_axis (the F1 axis from proc2s).
Experiment facts (Dataset::experiment)
Facts the normalized model cannot carry, laid over the derived experiment (experiment-model.md); origin inferred:
| experiment field | from |
|---|---|
sample.id |
the first of USERA1…USERA5 in acqus that is not the <user>/<> placeholder (source_field acqus ##$USERAn), else the first non-empty line of pdata/<procno>/title when it reads like a label (at most 4 words, 64 characters, no =) |
sample.name |
that title line otherwise (Bruker standard tube for water suppression) |
acquisition.operator |
the ##OWNER= header of acqus (the login that acquired) |
The title file is read only when it is at most 64 KiB; the first pdata directory with one wins.
check finding codes
truncated, missing_data, unsupported_sample_type, bad_parameters, missing_parameter (TD), missing_parameters (pdata without procs), bad_processed_data (errors); parameter_syntax, parameter_unterminated, acqus_missing, missing_dimension, extra_rows, nuslist_missing, nuslist_mismatch, nuslist_unreadable, size_mismatch, missing_parameter (SW_h) (warnings); layout, fid_padding, group_delay, non_utf8 (info).
Observed corpus values
| id | software (##TITLE=) |
data | DTYPA/BYTORDA |
NC |
notes |
|---|---|---|---|---|---|
nmrglue-bruker-1d |
XWIN-NMR 3.1 | fid TD 4096 | 0 / 1 (big) | −2 | DSPFVS 12, DECIM 16 |
nmrglue-bruker-2d |
XWIN-NMR 3.5 | ser 1300 × 600 | 0 / 1 (big) | −2 | 2D HSQC, no pdata |
nmrglue-bruker-3d |
XWIN-NMR 3.5 | ser 1300 × 14848 | 0 / 1 (big) | −2 | acqu2s TD = total rows, no acqu3s |
nmrxiv-s596-1 |
TOPSPIN 1.3 | fid TD 65536, pdata/1 | 0 / 1 (big) | −1 | AQ_mod 1, DSPFVS 10, GRPDLY −1 |
nmrxiv-s846-50 |
TopSpin 3.5 pl 7 | fid TD 131072, pdata/1 (XDIM 8192) | 0 / 0 | −6 | |
nmrxiv-s837-18 |
TopSpin 3.7.0 | fid TD 21424 (unpadded) | 0 / 0 | −5 | |
nmrxiv-s837-21 |
TopSpin 3.7.0 | ser 2048 × 32, NUS | 0 / 0 | −6 | nuslist 16 entries, NusTD 128 |
nmrxiv-s837-24 |
TopSpin 3.7.0 | ser 2048 × 128 | 0 / 0 | −7 | |
nmrxiv-s501-6 |
TopSpin 3.5 pl 7 | ser + pdata/1 2rr, 2ri, 2ir, 2ii |
0 / 0 | first public 2D processed set (nmrXiv S501, CC-BY-SA-4.0) | |
nmrxiv-s275-1, -13 |
TopSpin 4.3.0 | fid TD 65536 float64, pdata/1 | 2 / 0 | 0 | NC_proc 13 and −2 |
Vocabulary (every public identifier in crates/openreadout-nmr/src/bruker_*.rs must appear here)
| identifier | meaning |
|---|---|
BrukerReader, BrukerDataset, BRUKER_FORMAT_ID, open, experiment |
reader entry points: format reader, opened experiment (core Dataset), the id bruker-nmr; the parsed experiment |
JCAMP_FORMAT_ID, JcampReader |
the sibling JCAMP-DX reader in the same crate (docs/formats/jcamp-dx.md) |
decode_text |
UTF-8, else Latin-1, text decoding of parameter files |
ParamFile, name, title, header, comments, params, issues, ended, latin1, size |
a parsed parameter file and its parts |
get, float, int, text, software, to_json, parse_param_file |
parameter lookup, ##TITLE= software/version, JSON form, parser |
ParamValue { Int, Float, Text, List }, as_f64, as_i64, as_str |
one parameter value |
Experiment, dir, acqus, acqu_n, raw_file, nuslist_len, nuslist, nuslist_issue, parse_nuslist, processing, files, total_size, files_truncated, load |
an experiment directory: parameter files, fid/ser, nuslist line count, pdata, file listing |
Processing, procno, procs, data_files |
one pdata/<procno> directory |
SampleType { Int32, Float64 }, from_code, width, dtype |
DTYPA/DTYPP |
ByteOrder { Little, Big } |
BYTORDA/BYTORDP |
decode_samples |
stored bytes → f64 |
RawLayout, file_name, file_len, sample_type, byte_order, complex, td, row_stride, rows, rows_in_file, unpadded_rows, notes, samples_per_row, row_bytes, from_experiment |
fid/ser layout |
ProcLayout, dims, si, xdim, components, from_processing, is_data_name, values, row_ranges |
processed-file layout, 2D/3D submatrix addressing |
DSP_GROUP_DELAY, group_delay |
digital-filter group delay table and lookup |
resolve_experiment_dir, find_experiments |
path → experiment directory; experiments inside a data-set directory |
How this reader was derived, file by file: provenance log.