Terminology and conventions#

Documentation is the generic guide, and states what belongs in each form of documentation and how it should be written. This page holds the conventions specific to pulserver, and governs every docstring, documentation page, code comment and user-facing diagnostic string in this repository, including the C and C++ doc comments under src/. It is binding on human contributors and on automated agents alike; AGENTS.md requires compliance with both.

The target register is reference documentation for MRI researchers and sequence developers who run acquisitions on clinical scanners. The reader is assumed to know MR physics, Pulseq and the MRD raw-data format. The documentation’s job is to state what an object is, in the field’s own vocabulary, together with the semantics a signature cannot carry: units, frames, the layer a statement belongs to, state and invariants.

The failure mode this guide exists to prevent is the replacement of an established MRI, Pulseq or MRD term by a paraphrase of what the thing does. A reader then has to reconstruct the standard concept from a description of it. Repeating the correct technical term is always preferable to varying the wording.

1. Terminology is the substance#

Use the established term. Do not explain around it.

Do not write

Write

what the operator asks for

the requested protocol, the prescription

what the sequence will actually play

the resolved protocol

the numbers the scanner keeps

the scanner parameters

a design written earlier

a stored design, by its identifier

the folder both sides look at

the design store

the scanner program

the interpreter

the host process for one sequence

the interpreter host process

the compiled form of a sequence

the IR, the IR cache

a distinct block, amplitudes aside

a base block

the part that repeats, the repeating unit

the repetition, the TR

pieces of a TR, a segment definition

a virtual segment

one occurrence of such a piece

a segment instance

the order the pieces are played in

the execution stream

filling in the raw data

MRD enrichment

what the reconstruction runs on

the reconstruction worker

the reconstruction’s code

the reconstruction plugin

the sequence’s code

the scanner-sequence plugin, the sequence function

Distinctions that must not be blurred#

Protocol. A prescription or requested protocol is what the operator enters. The resolved protocol is what the design achieves and what the interpreter plays. A listing carries entries with their UI schema; a value block carries values only. A preset is a negative time value standing for a design choice, not a time.

Design. A sequence function is a function of the scanner limits and the protocol arguments that returns the designed sequences, a chain with the prescans first and the main sequence last. A scanner sequence is the pulserver SequencePlugin that binds a sequence function, its app attribute, to protocol entries. “App” names that attribute only; in prose the bound function is the sequence function. A plugin is the file a scanner sequence or a reconstruction is loaded from; say which kind. The evaluation of a prescription is what the scanner sequence’s evaluate returns: the resolved protocol, the scan time, a note and, optionally, the RF layout. An RF layout is the RF definitions of a sequence, the RF instances of one TR in play order with the TR as its period, and for each instance the control its amplitude follows, the flip angle or a float user entry. The list and validate replies carry it when asked, as an estimate; the design the interpreter plays is the stored one.

Storage. A design is one generated or imported chain with its IR cache, immutable once written. Its identity is the hash of what it depends on; its identifier is the first 18 hexadecimal digits of the identity. The design store is the directory holding every design. A design call is one list, validate, generate or import of an interpreter host process; no call keeps state beyond the designs it stores. A design is pushed to the design intake, the proxy’s HTTP endpoint writing its store, as a bundle of the files of its directory.

IR. The IR follows the model of PulSeg, whose terms are used for its concepts. A subsequence is one file of a NextSequence chain. An event definition is a distinct RF, gradient or ADC event after deduplication; an event instance is its occurrence in a block, with its own amplitude. A base block is a block with its waveform amplitudes normalised; blocks that differ only in amplitudes, offsets, rotation or, for a pure delay, duration share one. A virtual segment is an ordered list of base blocks, a reusable structural unit that need not be periodic; a segment instance is one occurrence of it, with the amplitudes, offsets, rotations and durations it plays; the execution stream is the order of segment instances over the scan, which accounts for every block. The repetition of a subsequence is the period pypulseqpp.Sequence.repetition() reports, the whole subsequence where it does not repeat; the conversion finds virtual segments within it. Do not write “repeating unit”, which implies that every sequence is periodic, nor “segment definition” for a virtual segment. A TRID label marks a safety group here, not a segment boundary. The IR cache is the file; the collection is what a reader loads from it.

Virtual scanner. The stand-ins for the scanner in tests: the virtual interpreter, which plays a cache with the C library’s cursor, the phantom acquired along the trajectory it plays, and the virtual reconstruction client, which sends the samples as the scanner’s reconstruction client does. The design calls, the IR, the proxy and the plugins are not stand-ins.

Raw data. A series is one MRD stream from the reconstruction client. An acquisition is one MRD readout record; a readout is the ADC event of the sequence it corresponds to. Encoding counters are the MRD idx fields; flags are the MRD acquisition flags; an encoding space is an entry of the header’s encoding list. Enrichment replaces these with what the sequence states.

Layers. The Pulseq representation is the content of a .seq file. The engines are pypulseqpp (design) and the reconstruction a plugin imports. Orchestration is pulserver. Scanner execution is what the interpreter does on the hardware. Do not attribute a property of one layer to another: a design is not a .seq file, and the IR cache is not the design of record.

Fixed vocabulary#

  • design calls, warm server, reconstruction proxy, reconstruction server; pulserver.host and pulserver.proxy when the module is meant.

  • interpreter host process, not “host interpreter” or “interpreter process”.

  • interpreter for the scanner-side program; reconstruction client for the scanner-side sender of raw data.

  • IR and IR cache; .pseg is a file extension, not a name for the representation.

  • MRD for the format; ISMRMRD for the library and the HDF5 file.

2. Register#

Write dry, declarative technical prose.

No personification. A server does not decide, a design does not know, a proxy does not wait for anything it is not blocked on, a plugin does not ask. Objects have properties and functions have behaviour.

No literary compression. Do not describe an object by a relative clause where a noun exists: not “the directory the design calls write into” but “the design store”; not “what the design managed” but “the resolved protocol”.

No taglines. An API summary classifies its object.

No conversational or tutorial voice. No “simply”, “just”, “note that”, “under the hood”, “powerful”, “seamless”. No rhetorical questions.

No history. Describe the code as it is. No “used to”, “previously”, “this replaces”, no named fixed bugs, no comparison with removed designs.

Variation is not a virtue. Use the same term for the same concept every time it appears.

3. Summary lines#

The first line is one sentence, on one line, ending in a period.

  • Functions and methods take an imperative or third-person declarative verb that classifies the operation: “Return…”, “Read…”, “Format…”, “Segment…”.

  • Classes take a noun phrase naming what the class represents.

  • Modules and packages take one line naming the responsibility.

Do not restate the signature, the annotations or the parameter names in prose.

4. Units and frames#

Every documented quantity carries its unit.

Quantity

Unit

Time entry of a protocol, on the wire and in a scanner parameter

integer µs

Time argument of a sequence function

s

Float protocol entry

the entry’s unit; the argument is the value times scale

Scan time in a VALIDATE reply

s; ? where the evaluation states none

Times, frequencies and flip angles of an RF block

s, Hz and degrees; the amplitude of an instance in an [RfLayout] block is unitless, the peak RF amplitude over the peak_hz the listing states for its definition

Rasters passed to the IR conversion

s in pypulseqpp.Opts, µs in the cache

Gyromagnetic ratio, field strength

Hz/T, T

Field-of-view offset in a protocol or an import block

mm

Field-of-view offset passed to ir.convert and ir.prescribe

m

Prescription rotation (fov_rotation_ij)

unitless, element (i, j) of an orthonormal matrix

PNS limit (pns_limit)

fraction of the nerve model’s threshold

Forbidden band

Hz; amplitude allowed in it in mT/m

SAR ratio (vop_sar_ratio, vop_global_sar_ratio)

unitless: energy per pulse over that of the reference pulse, the local term scaled by the head limit over the local limit

SAR limits (vop_head_limit, vop_local_limit)

W/kg

Default channel weights (vop_default_shim)

unitless magnitude, phase in rad

VOP safety factor (safety_factor of a VOP file)

unitless, at least 1, on local SAR

Transmit configuration (vop_coil, transmit of a VOP file)

an opaque string the scanner reports

Dwell time in an enriched acquisition (sample_time_us)

µs

k-space trajectory

1/m, as pypulseqpp reports it

Grid trajectory (ReconBuffer.grid_trajectory)

k times the reconstructed field of view; an N-point matrix spans [-N/2, N/2)

Design identifier

18 hexadecimal digits: three 24-bit integers, each exact in a float32

State the coordinate frame wherever a position or a k-space quantity appears. The field-of-view offset is expressed along the logical readout, phase and slice axes. The prescription rotation maps the logical axes to the physical x, y and z gradient axes, physical = R logical. The trajectory is expressed along the sequence’s x, y and z gradient axes, with the sequence’s block rotations applied and no prescription rotation. The host’s checks are made in the physical frame, with the prescription rotation composed after the block rotations.

State the precision a value is exchanged at when it matters: time entries are rounded to the nearest microsecond, ties to even, and float entries to six significant digits.

5. Safety language#

Pulserver runs the timing, gradient, PNS, mechanical-resonance and sound pressure checks, which pypulseqpp implements, under the limits the interpreter passes, before it writes an IR cache, and refuses a design that fails one. It writes each subsequence’s sound pressure levels into the cache, and from pypulseqpp’s SAR check each subsequence’s SAR relative to a reference pulse. The interpreter computes the SAR and the gradient and RF heating with the scanner’s own routines. The checks compute estimates; passing them does not establish scanner or patient safety.

  • Never write “safe”, “validated”, “compliant” or “approved” of a sequence, a protocol or a design.

  • A valid VALIDATE reply means the plugin’s evaluation accepted the prescription under the scanner limits it was given. State that, not more.

6. Source of truth#

Existing documentation is not evidence. Before writing or changing a substantive statement, verify it against, in order of authority:

  1. the implementation being documented, including src/cpp/ and src/c/;

  2. the tests that protect it;

  3. pypulseqpp, for sequence design and the Pulseq format;

  4. the ISMRMRD specification and library, for MRD;

  5. the Pulseq specification and the primary literature, cited by DOI.

Do not infer semantics from a name, a type annotation or another docstring. If a contract cannot be established from these sources, say what is known and leave the rest undocumented rather than guessing.

Do not print measured constants that are not guaranteed across releases or hardware.

7. What to document, and what not to#

The Docstrings section of AGENTS.md governs docstrings: document what the name, signature and annotations do not already state, and write no docstring for a private helper whose behaviour is evident. Use local comments for implementation detail and for a choice a reader would otherwise undo.

8. Format#

  • NumPy-style docstrings throughout, rendered by sphinx.ext.napoleon.

  • Documentation pages are Markdown with MyST roles and directives.

  • C and C++ documentation comments are Doxygen-style and follow this guide’s terminology and register rules in full.

9. Do not churn#

Rewriting correct technical prose for stylistic preference wastes review effort and risks introducing errors. Leave alone documentation that already uses the conventional term, states its units and frame, and classifies its object. When modifying code, clean up nearby documentation that clearly violates this guide; do not broaden a focused change into a repository-wide rewrite unless that is the task.