ChromIQ ships a handful of built-in presets in the Create Chart → Manual Presets dropdown that the user can't delete. This guide explains how the mechanism works end-to-end and how to add, rename, or re-file one.
History. Earlier builds had three kinds of built-in (a ti1-based preset that ran
printtargon a bundled.ti1, targen-based parameter presets, and prebuilt-files). Those were all replaced by the four prebuilt-files charts below; the ti1-based / targen-based machinery was removed (see the "remove dead TC9.18/ColorMunki preset code" commit). If you need to revive a run-printtarg-at-selection preset, recover the old_apply_tc918_preset/_generate_from_ti1wiring from git history —_generate_from_ti1itself is still present (it backs the user preset "attach a .ti1" feature).
Built-ins now come in three kinds:
- prebuilt-files (the ten "by Pharmacist" charts) — a complete, ready-made target is bundled and just copied into the run; no targen/printtarg.
- ti1 → printtarg (the 17 "TC9.18+Spyderprint Grays" charts, see the
dedicated section below) — one shared
.ti1is bundled and printtarg is run at selection with a fixed per-preset layout. targen is greyed (the patch set is fixed) but printtarg stays editable so you can re-layout it; only one small.ti1ships instead of 17 sets of page TIFFs. An "Edit patch recipe" override checkbox can unlock targen (→ a fresh, different patch set). - ti1 → layout engine (the two "Scanner" charts by Knut, #100) — the same
_Ti1Presetmachinery as kind 2, but the row setslayout_recipe(a fullLayoutRecipe.to_dict()): selecting it turns the ChromIQ layout engine on (_on_preset_selectedflips the engine checkbox to match the preset kind before dispatching),_seed_knut_presetseeds the Manual layout panel from the recipe instead of the printtarg widgets, and the bundled.ti1builds through the engine (_generate_from_ti1routes there while the engine setting is on).group="Scanner"files the row under its own dropdown/overlay header instead of an instrument group (display_group). targen is greyed like kind 2; the engine layout panel stays editable. Assets live underassets/charts/knut/rgb/scanner/<paper>/chart.ti1, each with arecipe.jsonsidecar (the full New-chart design, #107) so the presets appear in "Load setup from preset" and seed the editor's New Patch Set window. Tests:tests/test_scanner_builtin_presets.py.
All kinds grey one or both parameter panels while active, with override checkboxes to unlock them — see Panel locks + override checkboxes below.
The prebuilt-files kind is described first.
A complete,
pre-generated target (.ti1 + .ti2 + page .tifs) is bundled in assets/.
Selecting one prompts for a name, copies the bundled files into a fresh
~/ChromIQ/<name>/runs/<current>/ folder under the canonical chart stem, and
loads the TIFFs — no targen or printtarg runs, so the parameter panels
are greyed out while the preset is active.
Scanner geometry. Because nothing runs at selection, these bundles used to
carry no channels.json layout block, so the copied runs couldn't be turned
into scanner targets (Knut). Each bundle now ships a <stem>.channels.json
whose exact patch grid was derived from the bundled render itself,
colour-verified patch by patch against the bundle's own .ti2
(workflow.layout_from_render, engine: "derived" — same schema as an engine
chart's exact geometry). Regenerate it with
python scripts/derive_prebuilt_geometry.py after adding or replacing a bundle;
_create_prebuilt_target already copies the sidecar into the run.
A bundle whose render disagrees with its own .ti2 (e.g. the i1Pro/A4 tc924
set, whose patch V16 renders white where the .ti2 says grey) fails
derivation and ships without geometry — correct-or-absent, never guessed —
and the underlying bundle should be regenerated.
The ten shipped presets (all RGB). Labels follow the same
Instrument · Paper-NNNNp-Mpages Name by Pharmacist convention as the
ti1→printtarg presets below (patch width / orientation omitted — not stored for
these pre-rendered charts):
| Label (in the dropdown) | Instrument | Asset leaf |
|---|---|---|
| ★ i1Pro · A4-924p-2pages TC9.24 by Pharmacist | i1Pro | i1pro/a4/tc924 |
| ★ i1Pro · A4-1110p-2pages ABW-optimized by Pharmacist | i1Pro | i1pro/a4/abw1110 |
| ★ i1Pro · A4-1160p-2pages TC9.18 extended greys by Pharmacist | i1Pro | i1pro/a4/tc918eg |
| ★ i1Pro · Letter-1160p-2pages TC9.18 extended greys by Pharmacist | i1Pro | i1pro/letter/tc918eg |
| ★ ColorMunki · A4-300p-1page TC3.00 by Pharmacist | ColorMunki | colormunki/a4/tc300 |
| ★ ColorMunki · A4-702p-2pages ABW-optimized by Pharmacist | ColorMunki | colormunki/a4/abw702 |
| ★ ColorMunki · A3-924p-1page TC9.24 by Pharmacist | ColorMunki | colormunki/a3/tc924 |
| ★ ColorMunki · A3+-1160p-1page TC9.18 extended greys by Pharmacist | ColorMunki | colormunki/a3plus/tc918eg |
| ★ i1Pro · A4-1944p-3pages extended target by Pharmacist | i1Pro | i1pro/a4/extended1944 |
| ★ i1Pro · Letter-1944p-3pages extended target by Pharmacist | i1Pro | i1pro/letter/extended1944 |
The tc918eg pair is the same patch set in two page sizes; the page size lives
in the label (and is read back from the asset path by _prebuilt_paper for the
tooltip), so the two entries are distinguishable in both the dropdown and the
overlay.
Constants (module level)
TC924_PRESET_KEY/TC924_PRESET_LABEL,ABW1110_*,TC300_*,ABW702_*— one sentineluserDatakey + one display label per preset. The combo entry is matched by its key, never its text.PREBUILT_PRESETS: dict[key -> (asset_stem, default_name)]— the registry.asset_stemis the path without extension underassets/; it locates<stem>.ti1,<stem>.ti2and the<stem>_NN.tifpages in that leaf folder.DISABLED_BUILTIN_PRESET_KEYS— keys shown greyed-out and non-selectable (park a preset here pending a fix instead of deleting it). Currently empty.BUILTIN_PRESET_KEYS = frozenset(PREBUILT_PRESETS)andBUILTIN_PRESET_LABELS— protect built-ins from the delete button and stop a user.jsonfrom shadowing one.BUILTIN_PRESET_GROUPS— the single source of truth for which built-ins exist and how they group by instrument, as[(instrument, [(combo_label, overlay_label, key), …]), …]. Both the dropdown and the overlay read it, so they can't drift apart.
Combo population
_populate_preset_combo— adds "none", then the user presets, then the built-ins grouped by instrument with separators (built fromBUILTIN_PRESET_GROUPS, sorted by instrument name, curated order preserved within a group via stable sort). Guided mode shows only the recommended starter (i1Pro TC9.24)._add_builtin_preset_item— appends a bold, tooltipped, pinned entry;disabled=Truegreys it out and blocks selection._prebuilt_tooltip(paper)— the tooltip body for a prebuilt preset._builtin_default_name(key)— the name suggested in the prompt (PREBUILT_PRESETS[key][1], else"chart").
Built-in presets overlay (ui/builtin_preset_popup.py)
- A star button (
BuiltinPresetButton) sits at the right edge of the GUIDED / MANUAL switch row. Clicking it opensBuiltinPresetPopup— a speech-bubble (same look as the masthead Tools popup) listing the built-ins under instrument headers, built fromBUILTIN_PRESET_GROUPS. _open_builtin_preset_overlayshows it;_activate_builtin_preset(key)wires a pick back through the dropdown: switch to Manual, then select the matching combo entry (or re-call_on_preset_selectedif it's already current). So the overlay and the dropdown share the exact same name-prompt + generate flow.
Selection → creation
_on_preset_selected— for anyBUILTIN_PRESET_KEYSentry: guard against a running process, prompt for a target name (Cancel reverts the dropdown), then route straight to_apply_prebuilt_preset(key, name)._apply_prebuilt_preset— sets_prebuilt_active, seeds the instrument and paper the bundle was made for (_prebuilt_instrument/_prebuilt_paper_code, both parsed from the asset path), snapshots the targen + printtarg signatures as the change-detection baseline, greys both panels (locked), then calls_create_prebuilt_target._create_prebuilt_target— copies<stem>.ti1/.ti2and the<stem>_NN.tifpages into the run aschart.ti1/chart.ti2/chart_NN.tif, clears_last_params, and hands the TIFF list to_on_generate_finished(same path a generated chart takes).resource_path()resolves the asset in both dev and frozen builds._leave_prebuilt— clears the prebuilt state, unticks the override boxes, and re-enables the panels (run when the user picks Default / a user preset / loads a different patch set).
Panel locks + override checkboxes (shared by both kinds)
Selecting a preset that supplies a fixed patch set or layout greys the matching
panel; an override checkbox above each panel (built once per tool in
_make_manual_panel, hidden by default) lets the user unlock it.
_update_preset_locks— the single authority. A ti1 preset (_ti1_preset_active(): TC9.18, Knut, or a user preset with an attached.ti1) greys only targen; a prebuilt preset greys both. Each panel's inner content (_manual_targen_content/_manual_printtarg_content= its basic + expert sub-groups) is disabled, never the outer group — so the override row, which sits outside that content, stays clickable. Called from every apply / leave path and after every Default/user-preset selection._reset_override_checks— unticks both boxes (signals blocked, no pop-up) on every selection so a freshly picked preset starts locked._on_override_clicked— fires only on a real user click (not programmaticsetChecked); shows the warningInfoDialogwhen the box is ticked on.- Generate-time routing keys off the snapshots: for a prebuilt preset,
_on_generatecompares the live targen / printtarg signatures against the baselines — targen changed → freshtargen→printtarg; else printtarg changed → re-lay-out the bundled.ti1via_generate_from_ti1(same patches, new layout); else copy the bundled files. User-.ti1presets gate the same way on_preset_ti1_targen_sig.
Charts are filed by creator / colorspace / instrument / paper / target:
assets/charts/<creator>/<colorspace>/<instrument>/<paper>/<target>/
<stem>.ti1
<stem>.ti2
<stem>_01.tif
<stem>_02.tif # only multi-page charts
e.g. assets/charts/pharmacist/rgb/i1pro/a4/tc924/tc924.{ti1,ti2} +
tc924_01.tif / tc924_02.tif. The stem inside the leaf folder is the
asset_stem's last path component; PREBUILT_PRESETS stores the stem path
without extension, so _create_prebuilt_target can find every file by globbing
<stem>_*.tif next to <stem>.ti1.
- Bundle the files. Drop
<stem>.ti1,<stem>.ti2and the page TIFFs (<stem>_01.tif, …) intoassets/charts/<creator>/<colorspace>/<instrument>/<paper>/<target>/. Name the TIFFs<stem>_NN.tif(zero-padded, 1-based) — a single-page chart still uses<stem>_01.tif. - Add the constants. Define
FOO_PRESET_KEY = "__chromiq_foo_builtin__"(unique sentinel) andFOO_PRESET_LABEL = "★ <name> by <author> · built-in". - Register it. Add
FOO_PRESET_KEY: ("assets/charts/.../<stem>", "<default-name>")toPREBUILT_PRESETS.BUILTIN_PRESET_KEYSderives from the dict automatically; add the label toBUILTIN_PRESET_LABELS. - Show it. Add
(FOO_PRESET_LABEL, "<short overlay label>", FOO_PRESET_KEY)to the right instrument group inBUILTIN_PRESET_GROUPS(add a new(instrument, [...])group if the instrument is new). This single registry feeds both the Manual presets dropdown (_populate_preset_comboderives itsbuiltinslist from it) and the Built-in presets overlay (BuiltinPresetPopup) — no other UI edit is needed. The overlay groups by instrument, so the short label should omit the instrument (e.g. just"TC9.24 by Pharmacist"). - Verify (see snippet below): the asset files resolve, the key is in the registry, and the suite passes.
Note (#89): the 17 "TC9.18+Spyderprint Grays" presets that shared one bundled
.ti1were removed; only the Full layout setup family (each with its own.ti1+recipe.json) and the "by Pharmacist" built-ins remain. The description below documents the now-retired shared-.ti1mechanism for reference — the dataclass defaults (KNUT_TI1_ASSET,KNUT_SUFFIX, …) are kept but no preset uses them.
These presets (_Ti1Preset dataclass + KNUT_PRESETS registry in
ui/tabs/tab_chart.py) all shared one bundled .ti1
(1168 patches) and differed only in their printtarg layout (instrument, page
size, -a, margin, -A spacer scale, -R seed). Selecting one:
_apply_knut_preset(key, name)→_seed_knut_preset(key)seeds every printtarg control the recipe touches (and resets the optional-A/-Rrows when the preset doesn't use them, so values can't leak between presets), sets_knut_active=True, snapshots the targen controls into_knut_targen_sig(via the shared_targen_signature()), then calls_generate_from_ti1.- ti1 reuse, signature-gated (same as the TC9.18 built-in): on later
Generate clicks,
_on_generatere-lays-out the bundled.ti1with printtarg only as long as the targen settings are untouched (_knut_activeand_targen_signature() == _knut_targen_sig). Change a targen setting and it falls through to a fresh targen run (the OFPS patch set can't be recreated by re-running targen, so this is opt-in). The Manual info box says which mode it's in. Changing only printtarg settings keeps the.ti1and just re-lays it out. - targen is greyed (printtarg stays editable). To change the patch set the
user ticks the "Edit patch recipe" override box — which, once a targen value is
changed, drops
_knut_activeand falls through to a fresh targen run as above. Leaving a preset reverts its forced printtarg flags (_reset_knut_overrides), unticks the override box, and clears the flags.
16 _Ti1Preset rows (slugs fls_*, label suffix
KNUT_FLS_SUFFIX = " · Full layout setup") are Knut's exported Create-Chart
charts. The name highlights what sets them apart: each ships the complete
Create-Chart setup (colour-set recipe + layout), so they're meant as a basis for
new charts. (This family replaced the earlier "Wide-gamut" set — the label was a
misnomer.) They use the same ti1→printtarg machinery but each carries its
own bundled .ti1 (varied patch sets: 480 … 2016) under
assets/charts/knut/rgb/fulllayout/<slug>/chart.ti1. The _Ti1Preset dataclass
grew optional fields whose defaults reproduce the shared TC9.18 presets
byte-for-byte, so only this family sets them: ti1_asset (per-preset .ti1),
patches/white/black (descriptive targen display), no_strip_limit (-P),
suppress_left_clip (-L), tiff_16bit (8- vs 16-bit -T/-t),
triple_density (the i1Pro-layout + ColorMunki-tag trick — set on the manual TD
checkbox in _seed_knut_preset before -a/-m so the recipe's own
scale/margin win over the TD -a1.3/-m5 preset), and suffix (so
default_target_name strips the right tail). _seed_knut_preset reads those
fields instead of the old hard-coded constants, and _apply_knut_preset resolves
p.ti1_asset. To add another, append a row — no other wiring changes.
test_knut_spyderprint_presets.py is field-driven, so it pins both families.
The rows + bundled assets are generated from Knut's JSON+.ti1 exports: the
build step normalises each preset's stored recipe (Set B) to the chart actually
built (Set A) — layout copied from data.printtarg_*, fill_to set to the
.ti1 patch count — then writes chart.ti1 + recipe.json under each slug.
"Load setup from preset" (New chart): every chart carries its recipe in a
per-folder recipe.json sidecar beside its chart.ti1 (the general
built-in convention — builtin_preset_recipe reads the sidecar first; the old
shared widegamut/recipes.json store is gone). The recipe is keyed for display
as "<instrument> <name>", e.g. "i1Pro A4-924p-2pages-Portrait", since i1Pro
and ColorMunki variants can share a chart name.
_NewChartDialog._available_preset_recipes lists those with a ★.
_Ti1Preset.key is __chromiq_knut_<slug>__; the slug, not the display
name, is the stable identity — renaming a name must not change a slug.
KNUT_PRESET_KEYS folds into BUILTIN_PRESET_KEYS, the combo labels into
BUILTIN_PRESET_LABELS, and the entries merge into BUILTIN_PRESET_GROUPS
(by instrument), so the dropdown + overlay pick them up with no extra wiring.
_builtin_tooltip(key) routes these to _knut_tooltip (which shows the
printtarg line) instead of _prebuilt_tooltip.
3-decimal
-a. Several recipes are tuned to 3 places (0.929, 1.125, …) to land on an exact page count. The printtarg-aparam carriesdecimals: 3inparameters.yaml(read byParameterWidget), and_build_printtarg_argskeeps a 3rd decimal only when the value needs it (-a1.30/-a0.95are unchanged). To add another such preset, append a_Ti1Preset(...)row — no other code change is needed.tests/test_knut_spyderprint_presets.pypins the seeded command against each recipe.
ChromIQ vs. Knut's literal flags. Knut's commands use a lone
-M8; ChromIQ emits-m8 -M8together, which is functionally identical (printtarg's-mand-Mwrite the same margin;-Monly also includes it in the TIFF). The i1 charts keep the left clip border (no-L, seeded off) to match Knut.
Knut re-made the whole ColorMunki line-up from scratch and measured it on paper.
It replaced his ten fls_colormunki_* Full-layout-setup charts; the four
"by Pharmacist" ColorMunki prebuilts and the Red River ColorMunki variants were
deliberately left alone (Basti). Three of the retired .ti1 files moved to
tests/fixtures/charts/ — five margin-inspector tests are pinned against those
exact sheets.
What the family is for: margins and a clip border chosen so a ruler can be laid across the sheet, so the first and last strip are both readable, and so the knobs under the instrument can't catch on the page edge at the start of a read. Every chart is engine-built with the helper markers on — at ~10 mm patch width the ruler goes four markers below the strip being read, which is the whole point of the 10 mm sizing. Most sizes come in a Fast and a Slow Reading Speed cut (the ColorMunki's reading speed follows the patch count per strip), plus three big-patch Hand Held charts.
Structurally it is the kind-3 (ti1 → layout engine) mechanism, with one
difference worth copying: the recipe is shared. _CM_BASE in
ui/tabs/tab_chart.py holds every setting the 45 charts agree on, and
_cm_preset() builds a row from the five that actually differ — paper,
area_cols, area_rows, margin_left and clip_text. So a row reads:
_cm_preset("cm_a4_204p_1page_portrait_w10_0mm_fast_reading_speed",
"A4-204p-1page-Portrait-w10.0mm-Fast Reading Speed",
"A4", 17, 12, 204, 1, 1, 1),Assets live at assets/charts/knut/rgb/colormunki/<slug>/chart.ti1 with the
usual recipe.json sidecar. Adding or replacing charts is scripted:
python scripts/import_colormunki_presets.py <folder-of-exports> --writeIt reads Knut's <name>.ti1 + <name>.json export pairs, stages the assets and
prints the rows. Two things it does that matter:
- It rejects an export that diverges. Every recipe must equal
_CM_BASEoutside the five per-chart fields. Silently folding a stray setting into the base would change all 45 charts at once — so a divergence is reported and the import fails instead.tests/test_colormunki_builtin_presets.pyre-checks the same invariant against the shipped rows. - It re-points the colour-set recipe (Set B) at the chart it built (Set A). Knut designs a colour set once and lays it out on several sheets, so his export keeps whatever instrument and paper were on screen when the colours were designed — that was wrong for 33 of the 45. Left alone, "Load setup from preset" for a ColorMunki A3 chart would seed an i1Pro on A4. Only the chart's identity is corrected (instrument, paper, page size, density/dpi/bit depth); the printtarg-style scale and margin knobs are carried forward untouched, because an engine chart's four per-side margins have no honest single-margin equivalent. Same normalisation the Full-layout-setup family already went through.
tests/test_colormunki_builtin_presets.py also builds a five-chart sample in
the everyday tier and all 45 under --runslow, checking each lands on the
page count its name promises. A count that doesn't fill the last strip is padded
out with white by the engine (e.g. 1623 → 1632 across 8 sheets) — ordinary
behaviour, so the test asserts a range, not equality.
- Rename (label only): change
*_PRESET_LABELand updateBUILTIN_PRESET_LABELS. The*_PRESET_KEYis the stable identity — leave it alone so saved selections still resolve. - Re-file (move assets): move the leaf folder and update the
asset_steminPREBUILT_PRESETS. Nothing else references the path. - Park temporarily: add the key to
DISABLED_BUILTIN_PRESET_KEYS— it stays visible but greyed-out and unselectable; remove it to re-enable.
- The key, not the label, is the identity. Renaming a label must never change the key, or existing projects/selections lose their preset.
- TIFFs must be
<stem>_NN.tif._create_prebuilt_targetglobs<stem>_*.tif; a bare<stem>.tifis not picked up. - The preset's instrument is pinned to i1Pro layout routing (
-i i1) for the downstream hand-off; the actual instrument the chart was laid out for is read from the bundled.ti2(TARGET_INSTRUMENT) during measurement. - The bundled
.ti2should be randomised (carryRANDOM_START). A fixed-order chart (CHART_ID) can make chartread misrecognise strips — see theanalyze_randomisationgate. - Don't let a user
.jsonpreset share a built-in's key or label;_populate_preset_comboalready filters those out, but keep new keys unique.
# QT_QPA_PLATFORM=offscreen python - <<'PY'
from ui.tabs.tab_chart import (
PREBUILT_PRESETS, KNUT_PRESET_KEYS, BUILTIN_PRESET_KEYS, BUILTIN_PRESET_LABELS,
)
from core.resource_path import resource_path
# Built-ins = prebuilt-files + ti1→printtarg presets.
assert set(PREBUILT_PRESETS) | KNUT_PRESET_KEYS == BUILTIN_PRESET_KEYS
for key, (stem, default) in PREBUILT_PRESETS.items():
ti1 = resource_path(f"{stem}.ti1")
ti2 = resource_path(f"{stem}.ti2")
tiffs = sorted(ti1.parent.glob(f"{ti1.stem}_*.tif"))
assert ti1.is_file() and ti2.is_file() and tiffs, f"{key}: missing assets"
print(f"OK {key} ({len(tiffs)} page[s], default name {default!r})")
print("labels:", len(BUILTIN_PRESET_LABELS))
# PYRun the full suite (QT_QPA_PLATFORM=offscreen pytest) after any change here —
tests/test_pharmacist_builtin_chart.py, tests/test_tc924_prebuilt.py and
tests/test_chart_tab.py cover the registry, the asset files, and the
copy-into-run flow for both instruments.