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:
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/).
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.
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
Author one template in
<instr_lower>_first_template.tpl.ipt.yaml - see the next
recipe.
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).
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.