Tutorial

This chapter is a recipe-style guide for the tasks an instrument team typically performs when building an Instrument Package (IP) with IpTool. Where the previous chapter (Examples) walks through the bundled TSTINS example, this chapter answers “how do I add X?”.

The recipes assume you have already read Overview for the big picture and Schema for the conventions (snake_case, / as parameter-name separator, !cfg.include / !cfg.merge rules).

Bootstrap a new instrument

To stand up a new instrument from scratch:

  1. Pick a short upper-case instrument code (3-20 chars, matches [A-Z0-9]{3,20}). Examples: FORS3, MICADO, TSTINS. This is the OTTO instrument field. The lowercased form is used inside the emitted directory tree (resource/ip/fors3/ip/, ipt/fors3/).

  2. Create the directory layout the compiler expects:

    example/<instr_lower>/
        wscript                             # standalone-build shell
        README.md
        ipt/
            wscript                          # build/install logic
            resource/ipt/<instr_lower>/
                <instr_lower>.meta.ipt.yaml          # IP-wide metadata
                <instr_lower>_<purpose>.tpl.ipt.yaml # one per template
                include/                              # shared params
                    <category>.keys.ipt.yaml
    

    The split is convention, not enforced - the compiler accepts any set of .ipt.yaml inputs as long as they collectively contain exactly one ip_metadata instance and at least one template.

  3. Author the IP-wide metadata in <instr_lower>.meta.ipt.yaml:

    # @file
    # @copyright
    #   SPDX-FileCopyrightText: 2026 European Southern Observatory (ESO)
    #   SPDX-License-Identifier: LGPL-3.0-only
    
    !cfg.include schema/ifw/iptool/iptool.schema.yaml:
    
    ip_metadata: !cfg.type:IpToolIpMetadata
    
      format:
        format: 2
        instrument: FORS3
        version: '1.00'
    
      constraints: []        # add observing constraints later
      checklist:   []        # add checklist items later
      # laser:               # omit the laser block entirely if the
      #   ...                # instrument doesn't use a laser
    
  4. Author one template in <instr_lower>_first_template.tpl.ipt.yaml - see the next recipe.

  5. Compile:

    ifwIpToolCompiler \
        -f example/fors3ipt/ipt/resource/ipt/fors3/fors3.meta.ipt.yaml \
           example/fors3ipt/ipt/resource/ipt/fors3/*.tpl.ipt.yaml \
        -o build/iptool
    

    (For new instruments the project’s own waf install command wraps this — see Examples for the standalone-build pattern.)

    On success the compiler prints an Instrument/IP root/ Templates summary and the OTTO IP appears under build/iptool/resource/ip/fors3/ip/ (with ip.zip + ip.tar.gz siblings by default - see CLI).

  6. Validate:

    ifwIpToolValidator -i build/iptool/resource/ip/fors3/ip
    

    OK: ... means the IP matches the OTTO ip_schemata. Validate regularly during authoring.

Author a parameter (one recipe per type)

Every parameter is a top-level !cfg.type:IpToolParameter instance. The instance name is the OTTO parameter name with / as separator (DET/EXP/TIME, TEL/TARG/ALPHA, …) - see Schema. Conventionally parameters are grouped by FITS prefix into per-prefix *.keys.ipt.yaml files (det.keys.ipt.yaml, tel.keys.ipt.yaml, …) and included by templates with !cfg.include.

Each recipe below shows the minimum authoring needed for that type; the recipes are excerpts from the bundled TSTINS example (example/tstins/ipt/resource/ipt/tstins/include/det.keys.ipt.yaml and tel.keys.ipt.yaml).

Integer with an interval domain

DET/WIN1/NX: !cfg.type:IpToolParameter
  type: integer
  label: CCD Window X Size
  domain:
    int_intervals:
      - {min: 1, max: 2048}
  initial_value: {integer: 100}
  hide: [P1Setup, P1Observation, ETC, P2, SEQ]

Integer with a discrete domain

DET/NDIT: !cfg.type:IpToolParameter
  type: integer
  label: Number of Sub-integrations
  domain:
    int_values: [1, 2, 4, 8, 16, 32, 64]
  initial_value: {integer: 1}
  hide: [P1Setup, P1Observation, ETC]

Number with an interval domain (C3 worked example)

The reviewer’s specific question - how to define DET.EXP.TIME with a domain - is answered with a num_intervals-style domain. The two common shapes:

Continuous interval (the more common case):

DET/EXP/TIME: !cfg.type:IpToolParameter
  type: number
  label: Detector Exposure Time
  unit: s
  domain:
    num_intervals:
      - {min: 0.25, max: 100000.0}
  initial_value: {number: 1.0}
  hide: [P1Setup, P1Observation, ETC]

Discrete set of allowed values (use num_values instead):

DET/GAIN: !cfg.type:IpToolParameter
  type: number
  label: Detector Gain
  unit: e-/ADU
  domain:
    num_values: [0.5, 1.0, 2.0, 4.0]
  initial_value: {number: 1.0}
  hide: [P1Setup, P1Observation, ETC, P2, SEQ]

Common pitfalls when the validator complains about a number domain:

  • Missing ``values`` / interval root. OTTO’s NumParam.json requires exactly one of num_intervals or num_values to be present. The validator’s error in this case mentions additionalProperties or values because of the union shape.

  • Type mismatch in ``initial_value``. For type: number use initial_value: {number: ...}; for type: integer use initial_value: {integer: ...}. The compiler rejects mismatches with a C0003_INVALID_VALUE error from CII Config Service.

  • Range mismatch. initial_value must fall inside the declared interval (or be one of the declared discrete values).

String (free text)

DPR/CATG: !cfg.type:IpToolParameter
  type: string
  label: DPR Category
  initial_value: {string: SCIENCE}

Keyword (controlled vocabulary)

A keyword is a string restricted to a set of allowed values. Use domain.keyword_values for the allowed set:

DET/READ/CLKIND: !cfg.type:IpToolParameter
  type: keyword
  label: CCD Read-out
  description: 'CCD Read-out Setup: port, binning, gain'
  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]

Note that type: keyword parameters carry initial_value as {string: ...}, not {keyword: ...} - the OTTO union field for keyword initial values uses the string slot.

Boolean

DET/WIN1/ST: !cfg.type:IpToolParameter
  type: boolean
  label: CCD Windowing Flag
  initial_value: {boolean: false}

RA / Dec

Sexagesimal strings with at most 3 fractional-second digits (this is an OTTO constraint - see Schema, “Per-type value constraints in OTTO”). RA spans 00:00:00 to 23:59:59.999; Dec spans -89:59:59.999 to +89:59:59.999 (with 90:00:00 as the inclusive upper bound).

TEL/TARG/ALPHA: !cfg.type:IpToolParameter
  type: ra
  label: Right Ascension
  initial_value: {string: '10:00:00.000'}

TEL/TARG/DELTA: !cfg.type:IpToolParameter
  type: dec
  label: Declination
  initial_value: {string: '-30:00:00.000'}

Lists (intlist / numlist / keywordlist)

List parameters carry the same per-item type rules as their scalar counterparts, plus a list_items block in the domain bounding the allowed list length:

DET/WIN/STRIPES: !cfg.type:IpToolParameter
  type: intlist
  label: Detector Stripe Indices
  domain:
    int_intervals:
      - {min: 0, max: 31}
    list_items:
      min: 1
      max: 16
      unique: true
  initial_value: {integer_list: [0, 1, 2, 3]}

unique: true rejects duplicates. initial_value uses integer_list / number_list / string_list to match the list item type.

Target reference (targetref / targetreflist)

targetref is a placeholder for a single user-supplied science target; targetreflist for a list of them. Targets themselves are filled in by the astronomer in Phase 2 - the IP only declares the slot:

TEL/TARG/MAIN: !cfg.type:IpToolParameter
  type: targetref
  label: Main Science Target
  description: Single user-supplied science target object
  hide: [P1Setup, P1Observation, ETC]

TEL/TARG/MOSLIST: !cfg.type:IpToolParameter
  type: targetreflist
  label: MOS Target List
  domain:
    list_items: {min: 1, max: 50}

Targetref parameters do not carry initial_value (OTTO disallows it - the value comes from the user at OB-preparation time).

Binary / text file uploads

For parameters whose value is a file blob uploaded by the user:

DET/BAD/PIXMAP: !cfg.type:IpToolParameter
  type: binfile
  label: Bad-Pixel Map

DET/CALIB/SCRIPT: !cfg.type:IpToolParameter
  type: textfile
  label: Calibration Script

File parameters do not carry initial_value.

Author a template

A template lists the parameters it uses and (for P1 templates) any phase1 metadata.

Minimal non-P1 template — !cfg.include each per-prefix file the template’s parameters belong to:

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

fors3_img_obs: !cfg.type:IpToolTemplate
  type: science
  instrument: FORS3
  label: FORS3 Imaging Observation
  description: Standard imaging observation
  parameters:
    - DET/WIN1/NX
    - DET/WIN1/NY
    - DET/EXP/TIME
    - DPR/CATG
    - DPR/TECH
    - DPR/TYPE

The parameters: list contains names only - they refer to IpToolParameter instances declared (typically) in an included *.keys.ipt.yaml file. The order is preserved in the emitted Template Signature and in the auto-generated documentation.

P1 acquisition template (with reference targets):

fors3_acq: !cfg.type:IpToolTemplate
  type: acquisition
  instrument: FORS3
  label: FORS3 Acquisition
  description: Acquire the science target on the slit
  phase1:
    label: P1 Acquisition
    description: Acquisition step
    reference_targets:
      - {kind: NGS, label: Natural Guide Star, min: 1, max: 1}
  parameters:
    - TEL/TARG/ALPHA
    - TEL/TARG/DELTA
    - TEL/TARG/EQUINOX

Acquisition templates must carry the reference_targets block (the OTTO Fmt1TemplateSignature.json requires it). Science and calib templates must not have reference_targets.

Per-template overrides with !cfg.merge

The bias / crsplit pattern: parameters are declared once in a shared *.keys.ipt.yaml file with the library default value; an individual template overrides only the fields it cares about for its OBD.

Library defaults (include/det.keys.ipt.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'}

Per-template override (fors3_img_cal_bias.tpl.ipt.yaml):

!cfg.include schema/ifw/iptool/iptool.schema.yaml:
!cfg.include ipt/fors3/include/det.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 Bias
  description: Bias frames
  parameters: [DET/READ/CLKIND, ...]

Rules to keep in mind:

  • !cfg.merge only modifies an existing parameter. Merging into an undefined name fails with B0070_UNDEFINED_INSTANCE.

  • Only existing fields can be modified; new fields raise C0003_INVALID_VALUE.

  • The merged value must satisfy the field’s declared type (same rule as the original declaration).

  • Each template file is its own compilation unit - merges in one template do not leak into others, so different templates can carry different per-template values for the same shared parameter.

If a merge fails, the error is reported by the CII Config Service with the source file and line number; chase those rather than the IpTool compiler output.

When the validator complains

Common errors during authoring, and what they actually mean:

  • ``parameters.<NAME>.type: ‘foo’ is not one of […]`` — the parameter’s type: field doesn’t match the OTTO branch the validator picked. With the type-discriminator fix (B5b) the validator picks the branch matching the instance’s own type field whenever possible, so this error now usually means the type: value itself is misspelled.

  • ``additionalProperties`` / “is not allowed” — you added a field to a parameter or template that the OTTO schema doesn’t recognise. Common cause: typo in a field name, or carrying over a field from a different parameter type.

  • ``ra is not one of [‘binfile’, ‘textfile’]`` (legacy) — pre-B5b this was the misleading message users saw for a bad RA. With B5b it should no longer appear; if it does, your validator install is out of date.

  • ``pattern: ‘…’ does not match`` on an RA / Dec — see Schema, “Per-type value constraints in OTTO”. You almost certainly authored more than 3 fractional-second digits.

  • ``’CLR’ was expected`` on a skyTransparency constraint — OTTO updated the value codes from 1PHO/2CLR/3THN/4THK to bare PHO/CLR/THN/THK in the 2026-05 schemata. Update your meta file accordingly.

For everything else, use the location reported in the validator’s output (e.g. parameters.DET.EXP.TIME.type) to locate the offending field in the emitted Template Signature, then trace it back to your IPT YAML.