Skip to content

JEOL Delta .jdf

JEOL Delta, the software of JEOL NMR spectrometers, saves each data set as a .jdf file. OpenReadout returns the stored FID or spectrum as a trace with the acquisition parameters, and can process FIDs into spectra (NMR processing). Derived from nmrglue’s JEOL reader (nmrglue/fileio/jeol.py, BSD-3-Clause), read as prior art and used as the reference reader, and from hex dumps of public .jdf files (Delta 5.3.1, 5.3.3 and 6.0; JNM-ECZ400S, ECZ500R and an unnamed 500 MHz console). Provenance: docs/provenance/jeol-jdf.md. Format id jeol-jdf, family nmr, extension .jdf.

A .jdf file is one data set: a fixed header, a parameter section, optional axis lists, the data section, a context section (the experiment text, read only for the sample id) and an annotation section (not decoded). Detection: the file starts with JEOL.NMR.

Header (JdfHeader, 1360 bytes, always big-endian)

offset bytes our name meaning
0 8 – JEOL.NMR
8 1 little_endian 1: parameters and data are little-endian (every corpus file), 0: big-endian
9, 10 1 + 2 version layout version (1.2 in the corpus)
12 1 dimensions 1…8
14 1 data_type (2 high bits), data_format (6 low bits) 0 float64, 1 float32; 1 one_d, 2 two_d, 3…8 higher, 12…14 small-submatrix layouts (format_name)
15 1 instrument console code (instrument_name: 25 ECA in the corpus; the name table is nmrglue’s)
24 8 axis_types per dimension (JdfAxisType): 1 Real, 2 Tppi, 3 Complex, 4 RealComplex, 5 Envelope, 0 None
32 8 × 2 units per dimension (JdfUnit): high nibble of the first byte the SI prefix (prefix, signed: 1 milli, 2 micro, −1 kilo, −2 mega), low nibble the power (power), second byte the base unit (base: 13 hertz, 26 ppm, 28 second, 4 °C, 31 tesla, … as tabulated by nmrglue; base_symbol, symbol, factor)
48 124 title
172 4 axis_listed one nibble per dimension (high nibble first): 0 a linear range, 1 or 3 a point-by-point list (below)
176 8 × 4 points stored points per dimension (2D: multiples of 32)
208, 240 8 × 4 each valid_start, valid_stop the valid point range per dimension (only it is returned)
272, 336 8 × 8 each axis_start, axis_stop axis value of stored point 0 and of the last stored point, in the dimension’s unit
400, 404 4 each created, revised date: first 16 bits = 7 bits years since 1990, 4 bits month, 5 bits day (our reading: nmrglue takes the day from the third byte, which disagrees with the acquisition dates of every corpus file); the other 16 bits are not decoded
408 16 node_name computer name
424, 552, 680 128 each site, author, comment spectrometer name, login, comment
808 8 × 32 axis_titles Proton, Carbon13, Silicon29
1064, 1128 8 × 8 each base_frequency, zero_point MHz; reported in info --view full
1192 8 reversed
1212, 1216 4 each param_start, param_length parameter section
1220, 1252 8 × 4 each list_start, list_length axis lists
1284 4 data_start data section
1288 8 data_length bytes of data (two 32-bit halves, high first)
1296, 1304 8, 4 context_start, context_length the context section: the experiment text the spectrometer ran (header … end header;, acquisition … end acquisition;); it follows the data section in every corpus file (nmrglue’s names; its content inferred from the corpus)
1320 8 total_size declared file size

JDF_HEADER_BYTES = 1360. A file shorter than the header is corrupt (exit 4).

Parameters (JdfParam, parse_jdf_params)

At param_start: record size (64 = JDF_PARAM_BYTES), first and last index, total size (four 32-bit integers in the file’s byte order), then records first…last (nmrglue reads one record fewer: it counts to the last index exclusive). Each record: 4 bytes of class, a 16-bit power of ten (scaler), five 2-byte units (the first is unit), 16 bytes of value, a 32-bit value type, 28 bytes of name (name, blanks trimmed). Value types (JdfValue): 0 Text (16 characters: longer strings are cut in the file, e.g. sample_id), 1 Integer, 2 Float, 3 Complex, 4 Infinity, other Unknown. number = value × 10^scaler; text = trimmed string. Parameter names are looked up case-insensitively (param); the file mixes X_FREQ and solvent.

Data (two_d submatrices, sections)

The data section holds 2^c sections of ∏points values each, where c is the number of Complex axes; a RealComplex pair gives 2 sections (nmrglue nsections). Decoded layouts:

layout axis types sections sweeps × channels
one_d Real 1 1 × real
one_d Complex 2 (real, imaginary) 1 × real, imag
two_d Real/Real 1 rows × real
two_d Complex/Real, RealComplex/RealComplex 2 rows × real, imag
two_d Complex/Complex 4 2 × rows: sweep 2k = sections 0/1 of row k, sweep 2k+1 = sections 2/3 (the indirect real and imaginary parts)

one_d sections are plain arrays. two_d sections are 32 × 32 submatrices, submatrix rows then columns, each submatrix row-major (nmrglue reorder_submatrix with edge 32). Other layouts (3D+, small submatrices) and axis types (Tppi, Envelope) are unsupported (exit 6). Only the valid range (valid_start…valid_stop) of each dimension is returned.

Sign. Values are returned as stored. nmrglue returns section 0 − i·section 1 (the complex conjugate), and for 2D complex data negates the odd rows; the oracle undoes both (exactly) before hashing.

Axis. Point i of a linear axis is at axis_start + i · (axis_stop − axis_start) / (points − 1) (the corpus FIDs: n − 1 points span x_acq_time; the processed 29Si spectra are centred on X_OFFSET). The trace’s extra.axis covers the valid range: time in s for FIDs (kind time_domain), chemical shift in ppm for processed spectra (kind processed_spectrum).

Listed axes (non-uniform sampling). When axis_listed is set for a dimension, list_length bytes at list_start hold one big-endian float64 per stored point, in the dimension’s unit (inferred from the corpus: the HSQC and HMBC lists are 0, 0.11696, 0.23392, 0.40936 … ms, multiples of the 0.05848 ms dwell — the sampled increments of a 25 % NUS schedule; the header’s linear range is then meaningless). Each list is table axis_list (columns point, value in s/ppm/Hz; valid rows only) and the indirect axis in extra is {listed: true, first, last, size, values_table}. NUS reconstruction is not performed.

Traces and extra

One trace, name fid (time domain) or spectrum; sample_rate_hz = X_SWEEP for FIDs.

our name from notes
kind, axis header see above
nucleus, domain X_DOMAIN (else the axis title) Proton → 1H, Carbon13 → 13C, element name + mass number otherwise (jeol_nucleus, inferred)
spectrometer_frequency_mhz X_FREQ Hz in the file
carrier_offset_ppm X_OFFSET
spectral_width_hz, spectral_width_ppm X_SWEEP, X_SWEEP/X_FREQ
time_domain_size X_POINTS
scans, total_scans SCANS, TOTAL_SCANS
pulse_program experiment single_pulse_dec, hsqcad_auto.jxp
solvent solvent CHLOROFORM-D
temperature_c, temperature_k temp_get when its unit is °C (or K)
field_strength_t field_strength
sample_id the context section’s sample_id => "…"; line when it begins with the sample_id parameter (or there is none), else the parameter the parameter is a 16-byte text field and cuts longer ids (20230816 Zheng R); the context line holds the whole id (20230816 Zheng Rui Qi MHSWJ-15.81)
sample_id_truncated – true when only the parameter was found and it fills its 16 bytes: the id may be cut (reported in the assurance block’s assumed)
title, comment, operator, site header operator is the header author
instrument, instrument_serial inst_model_number, inst_serial_number JNM-ECZ400S/L1
console header instrument code ECA
software, software_version version Delta (inferred: the file names no program; Delta is JEOL’s spectrometer software) when version is present; 5.3.1 [Windows]; format_version is JDF <major>.<minor>, Delta <version>
sampling sampling Non Uniform on the NUS 2D files
acquired_at ACTUAL_START_TIME seconds since 1990-01-01T00:00:00Z (inferred: agrees with the sample.last shimmed local times of three sites in three time zones after the zone offset)
created_on, revised_on header dates
data_format, axis_types, stored_points, sample_type, byte_order header
indirect_dimensions[] Y_DOMAIN, Y_POINTS, Y_SWEEP, Y_FREQ, header {dimension, nucleus, domain, points, spectral_width_hz, spectrometer_frequency_mhz, encoding, axis}

Experiment facts (Dataset::experiment)

sample.id ← sample_id (source context sample_id or parameter sample_id), sample.name ← header title when it differs from the id, acquisition.operator ← header author (origin inferred). The derived model adds vendor, model (inst_model_number), serial, nucleus, pulse program, frequency, solvent, temperature and start time from extra.

check finding codes

truncated, bad_header, unsupported_layout (errors); size_mismatch, extra_bytes, parameter_section, missing_parameters, missing_parameter (X_SWEEP) (warnings). axis_rate_mismatch (info): a FID whose header axis range does not step by 1/X_SWEEP; its extra.axis then steps by 1/X_SWEEP from the header’s first value (step_from X_SWEEP, as X_ACQ_DURATION = X_POINTS / X_SWEEP confirms) and the header’s range is kept in extra.header_axis.

Observed corpus values

id Delta layout notes
nmrxiv-s200-qhnmr-jdf, -13c-jdf 5.3.1 one_d complex, 65536 JNM-ECZ400S
nmrxiv-s200-cosy-jdf 5.3.1 two_d real_complex/real_complex, 1280 × 256
nmrxiv-s200-hsqc-jdf, -hmbc-jdf 5.3.1 two_d complex/complex, 1024 × 32, 2048 × 64 NUS, listed Y axis
nmrxiv-s1243-esinica 5.3.3 one_d complex, 35000 500 MHz
nmrxiv-s908-zgig30, -aihe0 6.0 one_d complex, 131072 stored, 104858 valid processed 29Si spectra in ppm

Vocabulary (every public identifier in crates/openreadout-nmr/src/jeol_*.rs must appear here)

identifier meaning
JeolReader, JeolDataset, JEOL_FORMAT_ID, open, param, header reader entry points: format reader, opened file (core Dataset), the id jeol-jdf; parameter lookup; the parsed header
JdfHeader, JDF_HEADER_BYTES, parse, little_endian, version, dimensions, data_type, data_format, instrument, axis_types, units, title, axis_listed, list_start, list_length, points, valid_start, valid_stop, axis_start, axis_stop, created, revised, node_name, site, author, comment, axis_titles, base_frequency, zero_point, reversed, param_start, param_length, data_start, data_length, context_start, context_length, total_size, value_bytes, dtype, format_name, instrument_name the fixed header
JdfAxisType { None, Real, Tppi, Complex, RealComplex, Envelope, Other }, name axis kind
JdfUnit, prefix, power, base, base_symbol, factor, symbol a unit
JdfParam, JDF_PARAM_BYTES, name, value, scaler, unit, number, text, parse_jdf_params one parameter record
JdfValue { Text, Integer, Float, Complex, Infinity, Unknown } its value
jeol_nucleus axis title → nucleus

How this reader was derived, file by file: provenance log.