Examples

The repository ships one worked example — TSTINS — as a standalone waf project under ifw-iptool/example/tstins/. TSTINS is a fictitious “test instrument” whose purpose is to exercise every shape defined by the OTTO IP schemata, so that the example doubles as a coverage suite for the compiler. The IpTool YAML sources live under example/tstins/ipt/resource/ipt/tstins/.

The example contains four templates:

File

OTTO type

What it covers

tstins_img_cal_bias.tpl.ipt.yaml

calib

non-P1 template, !cfg.merge overrides

tstins_img_acq.tpl.ipt.yaml

acquisition

P1 acquisition + Phase1VLT + reference targets

tstins_img_obs_crsplit.tpl.ipt.yaml

science

non-P1 science, numlist parameters

tstins_spec_obs_mos.tpl.ipt.yaml

science

P1 science + Phase1 + every remaining parameter type (intlist, keywordlist, targetref, targetreflist, binfile, textfile, and values-form domains)

In addition, the example ships one IP-wide metadata file, tstins.meta.ipt.yaml, which carries all 9 OTTO constraints, all 5 checklist types, and a full laser block.

Parameter library (keys)

Excerpt from include/det.keys.ipt.yaml — each parameter is a top-level typed instance:

!cfg.include schema/ifw/iptool/iptool.schema.yaml:

DET/READ/CLKIND: !cfg.type:IpToolParameter
  type: keyword
  label: CCD Read-out
  domain:
    keyword_values:
      - 100kHz,1x1,high
      - 100kHz,2x2,high
      - 200kHz,1x1,low
      - 200kHz,2x2,low
  initial_value: {string: '200kHz,2x2,low'}
  hide: [P1Setup, P1Observation, ETC, P2, SEQ]

(The / separator is the IpTool authoring convention; OTTO sees this parameter as DET.READ.CLKIND after the compiler runs. See Schema for why.)

Templates explicitly !cfg.include each per-prefix file they need — there is no umbrella “include everything” aggregator. The choice is deliberate: each template declares its own parameter universe locally, so a reader knows immediately which prefixes contribute, and the compiler does not pull in keys files the template does not use.

Non-P1 calib template

tstins_img_cal_bias.tpl.ipt.yaml:

!cfg.include schema/ifw/iptool/iptool.schema.yaml:
!cfg.include ipt/tstins/include/det.keys.ipt.yaml:
!cfg.include ipt/tstins/include/seq.keys.ipt.yaml:
!cfg.include ipt/tstins/include/dpr.keys.ipt.yaml:

!cfg.merge DET/READ/CLKIND:
  initial_value: {string: '100kHz,2x2,high'}
  hide: [P1Setup, P1Observation, ETC]

!cfg.merge DPR/TYPE:
  initial_value: {string: BIAS}

tstins_img_cal_bias: !cfg.type:IpToolTemplate
  type: calib
  instrument: TSTINS
  label: TSTINS imaging bias
  description: Bias frames for TSTINS imaging
  parameters:
    - DET/WIN1/ST
    - DET/WIN1/STRX
    - DET/WIN1/STRY
    - DET/WIN1/NX
    - DET/WIN1/NY
    - DET/READ/CLKIND
    - SEQ/NEXPO
    - DPR/CATG
    - DPR/TECH
    - DPR/TYPE

P1 acquisition template

tstins_img_acq.tpl.ipt.yaml — exercises Phase1VLT.json and Phase1ReferenceTargets.json (the reference_targets block, which the compiler pivots from a list to the OTTO patternProperties map at emit time):

tstins_img_acq: !cfg.type:IpToolTemplate
  type: acquisition
  instrument: TSTINS
  label: TSTINS imaging acquisition
  description: Target acquisition for TSTINS imaging observations

  phase1:
    label: TSTINS imaging acquisition
    description: Target acquisition for TSTINS imaging in Phase 1
    reference_targets:
      - kind: NGS
        label: Natural Guide Star
        min: 1
        max: 1
      - kind: TTS
        label: Tip-Tilt Star
        min: 0
        max: 2

  parameters: [SEQ/PRESET, TEL/TARG/ALPHA, TEL/TARG/DELTA, ...]

P1 science template (full type coverage)

tstins_spec_obs_mos.tpl.ipt.yaml rounds out the parameter-type coverage that the calib/acq/crsplit templates do not exercise. It is a science template with a non-VLT phase1: block (just label + description; no reference_targets, since those are reserved for acquisition templates):

tstins_spec_obs_mos: !cfg.type:IpToolTemplate
  type: science
  instrument: TSTINS
  label: TSTINS MOS spectroscopy
  description: Multi-object spectroscopy observation for TSTINS

  phase1:
    label: TSTINS MOS spectroscopy
    description: MOS science observation in Phase 1

  parameters:
    - DET/WIN1/UIT1
    - DET/NDIT             # int values-domain
    - DET/GAIN             # num values-domain
    - DET/WIN/STRIPES      # intlist
    - DET/BAD/PIXMAP       # binfile
    - DET/CALIB/SCRIPT     # textfile
    - SEQ/NEXPO
    - SEQ/CATG
    - INS/FILTSET/NAMES    # keywordlist
    - TEL/TARG/MAIN        # targetref
    - TEL/TARG/MOSLIST     # targetreflist
    - DPR/CATG
    - DPR/TECH
    - DPR/TYPE

IP-wide metadata

tstins.meta.ipt.yaml — the conventional ip_metadata instance. This file exercises the full OTTO surface: all 9 constraint names (7 interval-style + skyTransparency + turbulence), all 5 checklist types (text/boolean/2-state/3-state/ list), and a non-empty laser block:

ip_metadata: !cfg.type:IpToolIpMetadata

  format:
    format: 2
    instrument: TSTINS
    version: '1.00'

  constraints:
    # interval-style
    - {name: seeing,       type: number,  label: Seeing,
       initial_value: {number: 1.0},  hide: [ETC],
       interval: {min: 0.2, max: 2.0}}
    - {name: airmass,      ...}
    - {name: moonDistance, ...}
    - {name: pwv,          ...}
    - {name: fli,          ...}
    - {name: strehlRatio,  ...}
    - {name: twilight,     ...}
    # keyword-style
    - name: skyTransparency
      type: keyword
      label: Sky Transparency
      initial_value: {string: CLR}
      hide: []
      values:
        - {value: PHO, label: Photometric}
        - {value: CLR, label: Clear}
        ...
    - {name: turbulence, ...}

  checklist:
    - {name: notes,         type: text,    compulsory: false, max_length: 500, ...}
    - {name: autoguiding,   type: boolean, compulsory: true,  ...}
    - {name: weather_ok,    type: 2-state, compulsory: true,  choices: [No, Yes]}
    - {name: dome_status,   type: 3-state, compulsory: true,  choices: [Closed, Open, Moving]}
    - {name: detector_mode, type: list,    compulsory: true,  choices: [Science, Engineering, Standby]}

  laser:
    fov: 7.5
    laser_using_templates:        [tstins_spec_obs_mos]
    always_sensitive_templates:   [tstins_img_acq]
    maybe_sensitive_templates:    [tstins_img_obs_crsplit]
    checked_parameters:           [INS.FILT1.NAME]
    checked_values:
      - {value: H_Alpha+83, sensitive: true}
      - {value: GG435+81,   sensitive: false}
      ...

Compile it

The example is a standalone waf project — the recommended flow is:

cd ifw-iptool/example/tstins
waf configure
waf build
waf install
waf validate-ip

That places the IP under $PREFIX/resource/ip/tstins/ and the Sequencer stubs under $PREFIX/lib/python3.12/site-packages/tstins/stubs/.

Alternatively, run the compiler directly against the YAML sources from the repository root:

ifwIpToolCompiler \\
  -f example/tstins/ipt/resource/ipt/tstins/tstins.meta.ipt.yaml \\
     example/tstins/ipt/resource/ipt/tstins/*.tpl.ipt.yaml \\
  -o tmp

Then validate:

ifwIpToolValidator -i tmp/resource/ip/tstins/ip

The same compile also writes a Template Reference Manual (LaTeX + PDF) under tmp/resource/doc/tstins/ip/; pass --omit-pdf to skip the PDF build. See CLI for details.

A second, minimal example: TSTNLS (no laser)

The repository also ships a tiny companion example, ifw-iptool/example/tstnls/. TSTNLS is a fictitious “test instrument, no laser” whose sole purpose is to exercise the no-laser path: an ip_metadata block with no laser: field must compile cleanly, and the compiler must NOT emit laser.json. Together with TSTINS (full laser block), the two examples cover both branches of OTTO Laser.json optionality.

TSTNLS is standalone — it ships its own minimal 5-parameter library (DET/WIN1/NX, DET/WIN1/NY, SEQ/NEXPO, DPR/CATG, DPR/TYPE) under example/tstnls/ipt/resource/ipt/tstnls/include/. It contains just one minimal calib template.

To build the standalone waf project:

cd ifw-iptool/example/tstnls
waf configure
waf build
waf install
waf validate-ip

Or run the compiler directly:

ifwIpToolCompiler \\
  -f example/tstnls/ipt/resource/ipt/tstnls/tstnls.meta.ipt.yaml \\
     example/tstnls/ipt/resource/ipt/tstnls/*.tpl.ipt.yaml \\
  -o tmp

After compilation, tmp/resource/ip/tstnls/ip/laser.json should not exist while format.json, templates.json, constraints.json and checklist.json should.

Real-world example: FORS3 IPT

For a fully-fledged real-instrument IP that mirrors what an instrument team actually delivers, see:

ifw-iptool/example/fors3ipt/

fors3ipt is a complete standalone project (not part of ifw-iptool’s build) at real-instrument scale: 77 templates, 5 prefix-based parameter library files, a portable ipt/ module, own waf build, role-based install layout. It serves as the reference for what a real-instrument IP looks like end-to-end.

Build it independently from inside its own directory:

cd ifw-iptool/example/fors3ipt
waf configure
waf build
waf install
waf validate-ip

What follows is the conceptual tour — patterns an author will reuse at any real-instrument scale. The project’s own README.md covers the project’s own heritage (it is a port of a pre-existing JSON prototype prepared by the FORS3 consortium) and any adaptations that were applied during the port.

What it covers

77 templates spanning every FORS3 observing mode — imaging (img), imaging polarimetry (ipol), long-slit (lss), multi-object (mos), mask-exchange-unit (mxu), polarimetric MOS (pmos), spectrophotometry (specphot) — plus the generic technical templates (gen_tec, hit_tec, spec_tec) and per-mode calibrations. Five categories of OBD shape are exercised:

  • Acquisition templates with a Phase1VLT block carrying reference_targets (NGS / TTS).

  • Phase-1 science templates with a Phase1 block but no reference targets.

  • Non-Phase-1 science templates.

  • Calibration templates with per-template lamp / filter / grism !cfg.merge overrides.

  • Technical templates (_tec_) with niche listItems-bound list parameters and one-off keywords.

This is 5× more of IpTool’s surface area than the bundled TSTINS example exercises.

Library organisation: one file per FITS prefix

The parameter library lives at ipt/resource/ipt/fors3/include/ and is split into five files, one per FITS prefix:

include/
    dpr.keys.ipt.yaml      # DPR.CATG, DPR.TECH, DPR.TYPE
    det.keys.ipt.yaml      # DET.WIN1.*, DET.READ.CLKIND, DET.FIELD.UIT1, DET.FOCF.UIT1
    seq.keys.ipt.yaml      # SEQ.PRESET, SEQ.NEXPO, SEQ.NOFF, SEQ.CATG, SEQ.CCD,
                           # SEQ.POL.*, SEQ.RRM.*, SEQ.ACQMODE, SEQ.SPEC.*, ...
    tel.keys.ipt.yaml      # TEL.TARG.*, TEL.ROT.OFFANGLE, TEL.AG.*, TEL.GS1.*, TEL.ADC.TYPE
    ins.keys.ipt.yaml      # INS.COLL.NAID, INS.FILT1/2.NAME, INS.GRIS.NAME,
                           # INS.SLIT.NAID, INS.WOLL.NAID, INS.LAMP1..6.{NAID,TIME},
                           # INS.MASK.NAID, INS.MOS10.WID, ...
                           # plus INS1.*/INS2.* families for the daycalib templates

The convention is simple: a parameter’s keys file is determined by its FITS prefix. There is no “aux” or “common” file — every parameter belongs to exactly one prefix file, regardless of how many templates use it. This makes the library predictable to navigate (you know where to look) and easy to extend (new parameters slot in next to their prefix-mates).

Templates use !cfg.include to pull in the keys files they need. Most templates include all five; minimal test templates may include only one or two.

Per-template overrides: !cfg.merge discipline

Every divergence from a library default is expressed as a !cfg.merge. There are no inline parameter declarations in template files (the compiler enforces this — see the file-role rule in Overview).

A typical bias template:

!cfg.include ipt/fors3/include/dpr.keys.ipt.yaml:
!cfg.include ipt/fors3/include/det.keys.ipt.yaml:
!cfg.include ipt/fors3/include/seq.keys.ipt.yaml:

!cfg.merge DET/READ/CLKIND:
  initial_value: {string: '100kHz,2x2,high'}
  hide: [P1Setup, P1Observation, ETC]

fors3_img_cal_bias: !cfg.type:IpToolTemplate
  type: calib
  instrument: FORS3
  label: FORS3 imaging bias
  description: Bias frames for FORS3 imaging
  parameters: [DET/WIN1/ST, ..., DPR/TYPE]

This is the same shape TSTINS uses, just operated at scale: 77 templates × an average of ~3 merges per template = ~230 targeted overrides expressed across the project. A given parameter (e.g. DET/READ/CLKIND) ends up declared once and overridden distinctly per template — exactly the value of a parameter library.

Type-changing !cfg.merge (advanced)

!cfg.merge is not limited to changing values within the same type — the same OTTO parameter name can carry a different declared type in two templates by merging type + domain + initial_value together. In fors3ipt, SEQ/POL/ANGLES is declared as keywordlist in the library and !cfg.merge-ed to numlist in the fors3_ipol_obs_off_fast template. Both templates emit a Template Signature whose SEQ.POL.ANGLES parameter is the type the template needs. Use sparingly — most parameters have a single stable type across an instrument.

Portable ipt/ layout

The project is split into a portable module plus a dev wrapper:

example/fors3ipt/
├── wscript                       # standalone-build shell (dev wrapper)
├── README.md                     # human entry point (dev wrapper)
├── .gitignore                    # standard waf .gitignore (dev wrapper)
└── ipt/                          # portable IPT module — the deliverable
    ├── wscript                   # all build/install logic
    └── resource/ipt/fors3/       # the IpTool sources

The ipt/ directory is the complete portable module that can be dropped into another source tree (typically the instrument’s control-software repository). It carries all the build/install logic and produces the correct role-based output layout independently. The top-level wscript, README.md, and .gitignore are convenience wrappers that let the example build and validate cleanly on its own inside ifw-iptool; they are not part of the portable deliverable.

This is the recommended layout for any IP project: keep the deliverable inside a single ipt/ subdirectory so the move into the instrument’s own repository is a single cp -r operation.

Compile performance

With the parallelisation enabled by default (see CLI, --jobs), the fors3ipt full waf install validate-ip cycle completes in ~5 seconds for the 77-template compile itself (plus LaTeX + zip/tar/install overhead). The serial fallback (--jobs 1) takes ~28 seconds. The 5× factor matters in practice: instrument-team authoring is iterative, and a 5-second feedback loop keeps the inner loop fluid.

How to start your own instrument project

Recommended seed: TSTINS, not fors3ipt. TSTINS is small (4 templates), canonical (designed to exercise every OTTO shape), and carries no consortium-specific values that a new instrument team would have to discard. The fors3ipt example is the at-scale reference — useful to look at when your project starts growing — but not the right starting point for a fresh instrument.

The minimum-effort adoption path from TSTINS:

  1. Copy ifw-iptool/example/tstins/ to your project tree.

  2. Rename the directory (tstins<yourinstr>).

  3. Rename the INSTRUMENT = "tstins" constant in ipt/wscript to your instrument code (lowercased).

  4. Rename ipt/resource/ipt/tstins/ to ipt/resource/ipt/<yourinstr>/.

  5. Update the instrument: field in <yourinstr>.meta.ipt.yaml and every <yourinstr>_<purpose>.tpl.ipt.yaml to your uppercase instrument code.

  6. Replace the TSTINS templates with your own. Keep the per-FITS-prefix keys-file convention; add new keys files only when your instrument introduces a genuinely new FITS prefix.

These are mechanical edits today; a future IpTool Organiser “New instrument project” action will automate steps 2-5 in one dialog. Until that ships, the manual approach above is the path.

Once your project starts growing past ~10 templates, the fors3ipt example becomes useful to look at: the 5-prefix library organisation, the !cfg.include / !cfg.merge discipline at scale, the type-changing merge trick, the standalone ipt/ handoff layout — all techniques that pay off at real-instrument size.