NMR Pulse Programme Semantic Annotation System

Alpha Development Status

This annotation system is in early alpha development. The schema, syntax, and functionality are subject to significant changes. Use with caution in production environments.

Motivation

Modern NMR pulse programmes are cryptic text files that encode complex experimental procedures, but they lack semantic information about what they actually measure. Analysis software must guess at the meaning of parameters like VALIST or VPLIST, leading to errors and requiring manual interpretation.

This annotation system embeds structured semantic metadata directly within pulse programme files as comments, enabling:

  • Automated Analysis: Software can automatically understand experimental structure
  • Reproducible Research: Clear provenance and citation information
  • Knowledge Preservation: Experimental design captured alongside implementation
  • Cross-Platform Compatibility: Vendor-agnostic semantic layer
  • Collaborative Development: Track contributors and development status
Current Implementation

Basic annotation parsing is implemented in NMRTools.jl. The system is under active development with ongoing refinement of the schema and parsing capabilities.

Design Principles

Human and Machine Readable

Annotations use simple comment syntax that NMR spectroscopists can read naturally whilst being structured enough for automated parsing.

Embedded and Immutable

Metadata travels with the pulse programme file and cannot drift out of sync with the code.

Modular and Extensible

Experiments are described as combinations of core types and features, allowing new variations without redefining the entire taxonomy.

Version Controlled

Both individual pulse programmes and the annotation system itself are versioned for reproducibility and compatibility.

Annotation Syntax

Basic Format

Annotations are extensions of Bruker comment lines, and are marked ;@. Within these lines, annotations are written in YAML format. The initial ;@ will be stripped when parsing, permitting multi-line entries.

;@ parameter: text value
;@ parameter1: 0.123
;@ parameter2: [list, of, items]
;@ parameter3: {associative: array, key:value}
;@ parameter4: |
;@   This is
;@   a multi-line
;@   entry.

Angle brackets <> within an entry should be interpreted as a reference to auxiliary files (following TopSpin conventions).

Using Annotations in NMRTools.jl

The complete annotation schema is defined at https://waudbylab.org/pulseprograms/schema/fields/. In NMRTools.jl, parsed annotation data is accessible via the :annotations metadata field or the annotations() convenience function.

Accessing annotation data

Annotations can be accessed using the annotations() function, which provides convenient nested access to annotation data:

# Load a spectrum with annotations
spec = loadnmr("path/to/annotated/experiment")

# Access all annotations
all_annotations = annotations(spec)

# Access specific annotation fields
experiment_type = annotations(spec, "experiment_type")

# Access nested dictionary fields
spinlock_duration = annotations(spec, "spinlock", "duration")

# Access array elements by index
first_dimension = annotations(spec, "dimensions", 1)

# Alternatively, use direct metadata access
annotations_dict = spec[:annotations]
experiment_type = annotations_dict["experiment_type"]

The annotations() function accepts both string and symbol keys, and returns nothing if the requested field does not exist.

Example: 19F R1ρ on-resonance experiment

This example experiment is annotated as follows:

;@ schema_version: "0.0.1"
;@ sequence_version: "0.1.0"
;@ title: 19F on-resonance R1rho relaxation dispersion
;@ authors:
;@   - Chris Waudby <c.waudby@ucl.ac.uk>
;@   - Jan Overbeck
;@ created: 2020-01-01
;@ last_modified: 2025-08-01
;@ repository: github.com/waudbygroup/pulseprograms
;@ status: beta
;@ experiment_type: [r1rho, 1d]
;@ features: [relaxation dispersion, on-resonance, temperature compensation]
;@ nuclei_hint: [19F, 1H]
;@ citation:
;@   - Overbeck (2020)
;@ dimensions: [spinlock_duration, spinlock_power, f1]
;@ acquisition_order: [3, 1, 2]
;@ decoupling: [nothing, nothing, f2]
;@ hard_pulse:
;@ - {channel: f1, length: p1, power: pl1}
;@ - {channel: f2, length: p3, power: pl2}
;@ decoupling_pulse:
;@ - {channel: f2, length: p4, power: pl12, program: cpdprg2}
;@ spinlock: {channel: f1, power: <VALIST>, duration: <VPLIST>, offset: 0, alignment: hard_pulse}

Using the test data 19F-r1rho-onres, we can parse these annotations:

using NMRTools

# Load the annotated experiment
spec = loadnmr("test/test-data/19F-r1rho-onres")

# View all available annotations
spec[:annotations]

# Access experiment metadata using annotations() function
annotations(spec, "title")           # "19F on-resonance R1rho relaxation dispersion"
annotations(spec, "experiment_type") # ["r1rho", "1d"]
annotations(spec, "features")        # ["relaxation dispersion", "on-resonance", "temperature compensation"]

# Access nested spinlock parameters
annotations(spec, "spinlock", "power")    # list of powers, [Power(36.82 dB, 0.0002079696687103696 W), ...]
annotations(spec, "spinlock", "duration") # list of durations, [0.00001, 0.005, 0.01, ...]
annotations(spec, "spinlock", "channel")  # "19F"
annotations(spec, "spinlock", "offset")   # 0 (on-resonance)

# Access dimension information
annotations(spec, "dimensions")    # ["spinlock_duration", "spinlock_power", "f1"]
annotations(spec, "dimensions", 1) # "spinlock_duration"