A chemistry-informed deep learning model for automated analysis of ¹H NMR spectra.
One forward pass per spectrum window returns chemical shift δ, coupling J, proton count, and line width.
No prior structure, no reference standards, no iterative fitting.
Guajazulene, 500 MHz. A clean high-field case: three aromatic protons, resolved in one forward pass.
Reading a ¹H NMR spectrum by hand means picking peaks, grouping them into multiplets, counting protons, and measuring coupling constants. It is slow and hard to reproduce, and least reliable on the hardest cases: overlapping signals and strong (higher-order) coupling. MolDeTr produces all four in a single forward pass.
MolDeTr reads a 1D ¹H NMR spectrum and returns the spin systems in it directly: for each group of equivalent protons it gives the chemical shift (δ), the coupling (J), the proton count, and the line width. If you know NMR but not machine learning: it replaces manual peak-picking and multiplet analysis for a region. If you know ML but not NMR: it is a 1D object detector (a DETR) that predicts a labelled box per multiplet instead of asking a chemist to annotate one by hand.
It was trained on quantum-mechanical spin-dynamics simulations with realistic experimental distortions, and it works on real spectra from 80 to 600 MHz. This repository holds the model code, the training and evaluation entry points, the Hydra configuration, and the ground-truth ROI annotations. The trained weights and the spectral data live on Zenodo (see Install).
Research code accompanying the paper. MolDeTr extracts δ, proton count, and couplings from real ¹H NMR spectra, including the congested, strongly-coupled cases it was built for. It is largely field-agnostic: it works in Hz, so it was tested across 80–600 MHz (and simulated down to ~5 MHz). Predictions can deviate for inputs outside its trained regime: unusual distortions, non-standard pulse sequences or processing, mixtures/impurities, or regions wider than the 1200 Hz window.
max Jis the dominant coupling per multiplet (the full set is in the committedstructured_outputpath). Read Scope & limitations and Usage notes, and sanity-check predictions against your own chemistry.
Questions? Email nicolas.schmid.research@gmail.com or open an issue. If MolDeTr helps your work, please ⭐ the repo and cite the paper.
| You are… | Start here |
|---|---|
| Bench chemist / spectroscopist | Try it in 2 minutes · Test it on YOUR data · FAQ |
| ML researcher | How it works · Train and evaluate · Reproducing the paper |
| Method developer | Scope & limitations · Input format · Usage notes |
| New to NMR or ML | Glossary · How it works · FAQ |
| Does | Does not |
|---|---|
Detects the multiplets in one spectral window and reports δ, proton count, line width, and the largest coupling (max J) per multiplet; see Scope for how much to trust each output. |
Read raw vendor files, phase/baseline-correct, or choose regions; you supply one preprocessed window (see Test it on YOUR data). |
| Handles overlap and strong coupling that defeat rule-based peak-picking. | Identify the molecule or assign peaks to atoms; it returns spin-system parameters, not a structure. |
| Generalises across field strengths (80–600 MHz) because it works in Hz, not ppm. | Handle a multiplet whose coupling partner sits outside the analysed window (see the rule in Test it on YOUR data). |
Vanillin, 300 MHz. The classic 1,2,4-trisubstituted-benzene ABX: two ortho doublets
(J ≈ 8 Hz) and a meta doublet (J ≈ 2 Hz). The live predictions recover that pattern — one proton per
multiplet, both ortho couplings near 8 Hz and the meta one near 2 Hz. Exact values are in the figure's own
table, and in docs/figure_predictions.json, which the
test suite ties to the published checkpoint.
The same three protons, on the molecule. Each aromatic proton is its own spin system: H5 is a clean ortho doublet (J ≈ 8 Hz to H6), H2 a meta doublet (J ≈ 2 Hz to H6), and H6 the doublet-of-doublets that couples to both. The colours link every ring position to its multiplet above; that is the spin-system structure MolDeTr recovers without ever seeing the molecule.
The one command that needs nothing but the repo reproduces the paper's headline numbers from committed data:
git clone https://github.com/smidooo/MolDeTr.git && cd MolDeTr
pip install -e ".[dev]" # CPU-only PyTorch is fine
python scripts/aggregate_experimental.py # → |Δδ| 0.90 Hz · |ΔJ| 0.20 Hz · proton-count 93.5 %To run the model itself, download the checkpoint from Zenodo (see Install), then:
python scripts/predict.py --demo # synthetic smoke run
python scripts/predict.py --input examples/roi_S8_example.npz --plot # a bundled real ROI, writes a PNG
python app.py # the point-and-click GUI (pip install -e ".[app]")To skip the install entirely, run it in Colab: the ▶ interactive app (Detect + Simulate, launched with a public share link) or the ▶ quickstart notebook (predict on a bundled example).
Pipeline, in words (for screen readers): a ¹H NMR window of up to 1200 Hz is resampled to 6144 points at 5.12 points/Hz, passed through a Deformable-DETR (a convolutional FPN backbone feeding a deformable-attention transformer), which emits one box per multiplet; each box is decoded to a chemical shift δ, a coupling J, a proton count, and a line width.
A convolutional FPN backbone turns the 6144-point window into multi-scale features. A deformable-attention transformer with a fixed set of object queries then predicts, for each query, whether it found a multiplet and, if so, its position and parameters. Training matches predictions to ground truth with the Hungarian algorithm, so the model learns set prediction directly and needs no hand-tuned peak-picking. Detection at inference is a single pass; the parameters come straight from the matched queries. The op that makes the attention practical on long 1D signals has a compiled CUDA kernel and a pure-PyTorch fallback, so inference runs on CPU with no build step. Key terms are defined in the glossary.
The network in full: an FPN backbone, a deformable-attention transformer with a fixed set of object queries, and per-query heads matched to ground truth by the Hungarian algorithm at training time.
moldetr/ the installable package: model + inference stack
├── model/ Deformable-DETR + FPN backbone + the deformable-attention op (CPU + CUDA)
├── dataloader/ dataset, normalization, and the training-time distortions
├── loss/ matcher/ metrics/ learner/ Hungarian matching · combined loss · FastAI wrapper
├── simulate.py exact spin-Hamiltonian spectrum simulator (powers the Simulate tab)
├── distort.py deterministic per-effect wrapper over the training-time augmentations
├── inference.py run(), the forward pass · postprocess.py, decode_predictions()
└── visualization.py static matplotlib renderer (predict.py --plot / figure exports)
app.py Gradio GUI entry point (kept at repo root for Hugging Face Spaces)
app_ui/ GUI support imported by app.py: plotting.py (interactive Plotly) · theme.py (branding/CSS)
scripts/ CLIs: predict · evaluate_experimental · evaluate_synthetic · aggregate_experimental
· simulate_and_predict · quick_validation
conf/ Hydra YAML configs (training)
structured_output/ committed experimental ground-truth metadata (the paper's headline numbers)
examples/ example spectra for the Detect tab
notebooks/ Colab / quickstart demos
docs/ the GitHub Pages site: input contract, scope notes, brand tokens
deploy/ Hugging Face Space packaging notes · the training-environment lockfile
tests/ pytest suite (unit · e2e · browser tiers)
.github/ CI workflow, issue/PR templates, and the contributing / conduct / security docs
There are three ways in, from zero-effort to full control:
1. No install: the GUI in Colab. The ▶ interactive Colab app
launches the same point-and-click GUI. Drop a .npz/.npy window in and it shows the assignment table and
the annotated plot, validating the input for you. Or run it locally with python app.py.
2. One line: predict.py. With the checkpoint in place:
python scripts/predict.py --input your_window.npz --plot
# → per multiplet: proton count · δ (ppm/Hz) · max J (Hz) · line width · confidence, and an annotated PNG3. Prepare a window from raw data. MolDeTr expects 6144 points at 5.12 points/Hz (a 1200 Hz
window), real-valued; only the overall scale is normalised away (relative intensities still matter).
docs/INPUT_FORMAT.md gives the full contract, a preparation recipe, and a Bruker
TopSpin phasing/baseline recipe; moldetr/validation.py enforces the contract and tells you exactly what
to fix. The Colab notebook
is a ready template.
One rule worth repeating. A window need not contain the whole molecule, but every proton that couples to a proton inside the window must also be inside it. A multiplet whose coupling partner is outside the region is out of distribution, and its prediction will be wrong. Draw regions so each spin system is complete.
git clone https://github.com/smidooo/MolDeTr.git
cd MolDeTr
conda env create -f environment.yml
conda activate moldetrIf you prefer pip: pip install -e ".[app]" (add dev for the tests, eval for evaluate_synthetic.py).
For bit-exact reproduction of the training environment (CUDA 11.7, linux-64), use the explicit lockfile:
conda create --name moldetr --file deploy/conda-lock-linux64.txt.
Important
PyTorch is an extra, not a base dependency (since v1.1.0). A bare pip install -e . gives you the
spin-physics half — moldetr.simulate and moldetr.distort, pure NumPy/SciPy — without pulling several
hundred megabytes of deep-learning stack you may never call. Anything that loads the checkpoint or runs
the network needs pip install -e ".[model]", including moldetr predict, moldetr app and
scripts/evaluate_*.py. The app, dev and eval extras already include it, so the commands on this
page are unaffected.
CPU-only: remove the pytorch-cuda line from environment.yml first (or install CPU PyTorch with pip).
Inference then uses the pure-PyTorch fallback of the deformable-attention op: no CUDA, no compilation.
Supported versions: Python 3.10–3.12 (newer versions may lack compatible PyTorch wheels). The
fastai>=2.7,<2.9 ceiling is a deliberate manual gate, not a compatibility workaround: the shipped
checkpoint was trained against a specific stack, and no CI lane exercises learner.load(). It no
longer constrains PyTorch — fastai 2.8.8 requires torch<3, where the older <2.8 ceiling selected
2.7.19 and capped PyTorch at 2.6.
cd moldetr/model/ops
bash make.sh # or: python setup.py build install
cd ../../..Optional for CPU inference: the model falls back to ms_deform_attn_core_pytorch when the extension is absent.
python scripts/quick_validation.py # confirms imports + ROI metadata load; no weights or data neededContributing a change? Run python scripts/install_hooks.py once per clone. It points git at the
tracked .githooks/ (a pre-commit guard on the paper-median reproduction, plus a commit-message
trailer cleanup) — this is per-checkout git config, not something a git clone picks up on its own.
The trained weights and the spectral regions are archived on Zenodo
(DOI 10.5281/zenodo.21217102), not in git. Fetch the
checkpoint straight into moldetr/model/ (where conf/config_big.yaml expects it) with:
python scripts/download_weights.py # downloads + MD5-verifies model_spin_system_ABCDEFG_exp2.pth (~974 MB)Or fetch it from Hugging Face, a byte-identical mirror
(huggingface-cli download smidooo/moldetr model_spin_system_ABCDEFG_exp2.pth --local-dir moldetr/model),
or download it from the Zenodo record by hand and place it in moldetr/model/
(paths.model_folder_save, lognames.best_model_file).
Configuration is Hydra; conf/config_big.yaml is the production config, and any field
can be overridden on the command line:
python scripts/train.py # train (CUDA required)
python scripts/train.py optim_params.batch_size=8 # example override
python scripts/evaluate_experimental.py # the 13 experimental ROIs (S1..S13); needs checkpoint + ROI npz
pip install -e ".[eval]" # evaluate_synthetic needs pandas/seaborn/scikit-learn/cmcrameri
python scripts/evaluate_synthetic.py # synthetic test set; needs checkpoint + synthetic npz
python scripts/evaluate_synthetic.py device.device_name=cpu # force CPU on a box without CUDApython scripts/predict.py --demo # synthetic smoke run (checkpoint only)
python scripts/predict.py --input examples/roi_S8_example.npz --plot # a real ROI; ppm read from the .npzAdd --input your_window.npz for your own data. The --plot flag writes the annotated figure shown above;
when the input file carries ground_truth annotations (the ROI examples do), they are overlaid as dashed
reference lines.
After pip install -e ".[model]" these are also available as one command: moldetr predict …, moldetr app,
moldetr reproduce, moldetr download-weights (run moldetr --help for the full list). The model extra
is what supplies PyTorch — see the note under Install.
A Gradio app: load a spectrum, get the assignment table and the annotated plot.
pip install -e ".[app]"
python app.pyRuns locally, and can be deployed unchanged as a Hugging Face Space (set the checkpoint via MOLDETR_CHECKPOINT).
Ready-to-try inputs are in examples/.
The article's headline experimental medians are 0.89 Hz (|Δδ|), 0.20 Hz (|ΔJ|), and 93.5 % proton-count accuracy. The evaluation set is 13 ROIs across 12 spectra (the ethyl vanillin spectrum contributes two regions, S5 and S5_R2), spanning 10 compounds at 80–600 MHz.
| Command | What it does | Needs |
|---|---|---|
python scripts/aggregate_experimental.py |
reproduce Table 4 (medians, per-class accuracy, MAE, R²) from committed match data | nothing (CPU, in-repo) |
python scripts/evaluate_experimental.py |
regenerate predictions from the weights | checkpoint + ROI npz (Zenodo) |
python scripts/evaluate_synthetic.py |
synthetic test-set metrics | checkpoint + synthetic npz (Zenodo), .[eval] |
aggregate_experimental.py reads the article's Hungarian-matched pairs
(structured_output/experimental_matched_pairs.json) and reproduces all three headline numbers exactly:
median |Δδ| = 0.90 Hz, median |ΔJ| = 0.20 Hz, and overall proton-count accuracy = 93.5 % (matched-only
92.1 %). "Overall" is the DETR-style figure: matched-correct plus correctly-empty queries, over all
queries. Two decode paths exist, and this matters for J. The committed path (structured_output +
aggregate_experimental.py) inverts the article's exact coupling post-processing; these are the
paper's numbers. The live tools (predict.py, the GUI, evaluate_experimental.py) instead report a
single largest coupling, max(J), per multiplet (the coupling head emits a permutation-invariant
embedding [sum, min, max, std], and the demo surfaces only its max component). The live path
reproduces the paper's proton counts, shifts, and largest coupling max J; the committed path
additionally recovers the full coupling set per multiplet (the exact E⁻¹). That is the only
difference. Predictions can deviate for inputs outside the trained regime. (The live tools also inject the same calibrated input noise the
model was trained and evaluated with; see Scope.)
Synthetic numbers. The synthetic set on Zenodo is a small representative subset of the full test set used in the paper, so
evaluate_synthetic.pywill land close to, but not exactly on, the published synthetic figures. The experimental numbers above reproduce exactly.
- CPU (any OS): works out of the box. The deformable-attention op falls back to pure PyTorch, so no
CUDA build is needed for inference, the tests, or
predict.py. - NVIDIA GPU: optional; build the CUDA op (
moldetr/model/ops/) for faster training and inference. - Apple Silicon: CPU or MPS. Install without
pytorch-cuda;device.device_name=cudafalls back tompsthencpu. - Windows: supported for inference and the test suite; the Linux
file_systemsharing strategy is skipped automatically. - CI runs
ruff+quick_validation+pytest+ the headline reproduction on ubuntu, macOS, and windows (Python 3.10, 3.11 and 3.12).
New to one side of this? These are the terms that matter.
| NMR | Meaning |
|---|---|
| chemical shift δ | peak position, in ppm (field-independent) or Hz |
| coupling constant J | spacing between sub-peaks of a multiplet, in Hz |
| multiplet | a peak split into sub-peaks by coupling (singlet, doublet, triplet, …) |
| proton count | number of equivalent protons giving rise to the multiplet |
| spin system | a set of mutually coupled protons |
| ROI | region of interest: one analysed window of the spectrum |
| ML | Meaning |
|---|---|
| object detection / box | here, a 1D interval on the ppm axis marking one multiplet |
| DETR | detection transformer: predicts a set of objects, no hand-tuned peak-picking |
| deformable attention | attention that samples a few relevant points, so it scales to long signals |
| object query | a learned slot that becomes one predicted multiplet (or "nothing") |
| Hungarian matching | optimal prediction-to-ground-truth assignment, used in training |
| checkpoint | the trained weights (on Zenodo) |
"Spectrum has N points, but MolDeTr needs exactly 6144." Your window is the wrong size. Resample it to
5.12 points/Hz over a 1200 Hz region and pad/crop to 6144 points; see
docs/INPUT_FORMAT.md.
"Checkpoint not found." Download model_spin_system_ABCDEFG_exp2.pth from
Zenodo into moldetr/model/, or point to it with --checkpoint.
Do I need a GPU? No. Inference, the tests, and predict.py run on CPU via the pure-PyTorch fallback.
A GPU only speeds up training and large batches.
Why 0.89 in the paper but 0.90 here? Rounding of the same median; aggregate_experimental.py prints
0.90. Both refer to the identical matched pairs.
A multiplet came out wrong at the edge of my window. Its coupling partner is probably outside the window. Widen or re-centre the region so the whole spin system is inside it (≤ 1200 Hz).
How many multiplets can it find at once? Up to 10 equivalent-spin groups per 1200 Hz window, an engineering limit rather than a physical one. Split a busier region into several windows.
Can it do ¹³C, 2D, or mixtures? No. 1-D ¹H only, one clean compound per spectrum. Water/solvent
suppression, mixtures, and non-¹³C heteronuclear artifacts are out of scope; see
docs/SCOPE.md. The 4H and 6H proton classes exist in training but were not tested
on real spectra.
How accurate is the coupling (J)? The live predict.py/GUI reproduce the
paper's largest coupling max J closely — for vanillin's ABX all three land within 0.7 Hz of a
ground truth of 8.1 / 2.0 / 8.1 Hz (measured values in docs/figure_predictions.json). max J
is only the largest coupling per multiplet; the committed structured_output path recovers the full set
(the paper's per-coupling 0.20 Hz median). Predictions can deviate for inputs outside the trained regime;
see Scope → coupling constants.
Cite the article as the primary reference:
@article{Schmid2026MolDeTr,
author = {Schmid, Nicolas and Wanner, Marc and Fischetti, Giulia and Henrici, Andreas and
Meshkian, Mohsen and Bruderer, Simon and Füchslin, Rudolf M. and Heitmann, Bjoern and
Wegner, Jan Dirk and Sigel, Roland K. O. and Wilhelm, Dirk},
title = {{MolDeTr}: A Chemistry-Informed Deep Learning Model for Next-Generation
Automated Analysis of $^{1}$H NMR Spectra},
journal = {Analytical Chemistry},
year = {2026},
doi = {10.1021/acs.analchem.5c03465}
}For the software, use the Zenodo concept DOI 10.5281/zenodo.21214876 (it resolves to the latest
release). For the data, use the Zenodo concept DOI 10.5281/zenodo.21217101 (it resolves to the latest
dataset version). The article's Data Availability statement cites 10.5281/zenodo.21217102 instead —
that is the version DOI of v1.0.0 of this same deposit, the snapshot the paper was written against.
Both are correct: cite the concept DOI to track every version, the version DOI to pin the one the paper
used. Machine-readable metadata is in CITATION.cff; GitHub's "Cite this repository"
button uses it.
Paper. Open access under CC BY 4.0 — Analytical Chemistry, published 4 August 2026, DOI 10.1021/acs.analchem.5c03465. The Supporting Information is free to download.
Code. Apache-2.0, at https://github.com/smidooo/MolDeTr, archived at Zenodo (DOI 10.5281/zenodo.21214876). The trained weights are deposited with the data (concept DOI 10.5281/zenodo.21217101, all versions).
Data. A selection of the simulated and experimental spectral regions analysed in this work, with their ground-truth spin-system annotations and metadata, is at Zenodo (concept DOI 10.5281/zenodo.21217101, all versions). The metadata follow the format used in the Supporting Information; full curation details are in Supporting Information Section 4.4.
Apache License 2.0; see LICENSE. © 2026 Nicolas Schmid and the MolDeTr authors.
Corresponding authors: Nicolas Schmid (nicolas.schmid.research@gmail.com, ORCID 0000-0003-1930-7654); Dirk Wilhelm (wilk@zhaw.ch, ORCID 0000-0001-5109-9803). For questions or "my spectrum didn't work" reports, an issue is often fastest.
Supported by Innosuisse – Swiss Innovation Agency (Grant No. 2155007318).

