vtir-wizard 1.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,36 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ .pytest_cache/
6
+ .mypy_cache/
7
+ .venv/
8
+ venv/
9
+ env/
10
+
11
+ # Build / packaging artifacts
12
+ build/
13
+ dist/
14
+ *.egg-info/
15
+ .eggs/
16
+
17
+ # IDE / editor
18
+ .vscode/
19
+ .idea/
20
+ *.swp
21
+ *.swo
22
+
23
+ # OS clutter
24
+ desktop.ini
25
+ Thumbs.db
26
+ .DS_Store
27
+
28
+ # Local test runs / generated output that should not be committed
29
+ .dryrun-out/
30
+ *.log
31
+
32
+ # Personal user config -- the committed vt_ir_config.ini ships with <you>
33
+ # placeholders; this rule prevents accidentally committing a copy that has
34
+ # been edited to point at lab-PC absolute paths. Comment it out if you want
35
+ # to share machine-specific config across multiple checkouts.
36
+ # vt_ir_config.ini
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Christian Nelle, Arbeitsgruppe Prof. Sebastian Henke,
4
+ Fakultät für Chemie und Chemische Biologie, Technische Universität Dortmund
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,495 @@
1
+ Metadata-Version: 2.4
2
+ Name: vtir-wizard
3
+ Version: 1.4.1
4
+ Summary: Variable-temperature IR orchestrator for a Thermo Nicolet iS5 + Specac heated Golden Gate ATR (drives the heater + OMNIC from one process).
5
+ Project-URL: Homepage, https://github.com/ACH-Repo/ACH-VT-IR-Wizard
6
+ Project-URL: Repository, https://github.com/ACH-Repo/ACH-VT-IR-Wizard
7
+ Project-URL: Issues, https://github.com/ACH-Repo/ACH-VT-IR-Wizard/issues
8
+ Author: Christian Nelle
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: ATR,DDE,FTIR,OMNIC,Specac,VT-IR,infrared,spectroscopy,variable-temperature
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
22
+ Classifier: Topic :: Scientific/Engineering :: Physics
23
+ Requires-Python: >=3.9
24
+ Requires-Dist: matplotlib>=3.5
25
+ Requires-Dist: numpy>=1.21
26
+ Requires-Dist: pywin32>=305; sys_platform == 'win32'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # VT-IR Wizard
30
+
31
+ One Python script that drives a **Thermo Nicolet iS5** FTIR and a **Specac
32
+ heated Golden Gate ATR** from a single process — turning a heater + spectrometer
33
+ from two different manufacturers into one variable-temperature IR experiment
34
+ with chronologically-indexed output files and a live overlay plot.
35
+
36
+ <!-- TODO: replace with a screenshot of the live plots running alongside a
37
+ real VT-IR experiment. Best frame: the temperature/setpoint trace already
38
+ covers a few steps so the shaded scan bands (BG = blue, up = red,
39
+ down = orange, return = green) are clearly visible. -->
40
+ ![Live overlay plot of a VT-IR run](docs/images/live-overlay.png)
41
+
42
+ ## Quick start
43
+
44
+ ```
45
+ pip install vtir-wizard
46
+ ```
47
+
48
+ 1. Create your editable config and fill it in:
49
+ ```
50
+ vtir-wizard --init-config
51
+ ```
52
+ This drops a commented `vt_ir_config.ini` in `%APPDATA%\vtir-wizard\` and
53
+ prints the path. Open it, replace every `<you>` with your Windows user name,
54
+ and adjust the paths if your data lives elsewhere.
55
+ 2. Open OMNIC (the DDE conversation needs it running).
56
+ 3. Run it from any terminal:
57
+ ```
58
+ vtir-wizard
59
+ ```
60
+ (or double-click `run_vt_ir.bat`).
61
+
62
+ The wizard asks for the sample name, mode (background or sample), temperatures,
63
+ and a couple of optional measurement modifiers. After you confirm, it
64
+ **(a)** drives the Specac controller temperature-by-temperature via its CLI,
65
+ **(b)** waits for the cell to actually reach each setpoint,
66
+ **(c)** collects the BG / sample spectrum over OMNIC's DDE interface, and
67
+ **(d)** exports it as both `.SPA` and a plotting-friendly format
68
+ (`.csv` by default).
69
+
70
+ Two console windows open automatically alongside the run: a **temperature
71
+ overlay** (temperature/setpoint trace with each completed scan shaded onto the
72
+ timeline) and a **live IR stack** (a temperature-colored waterfall of the
73
+ spectra themselves, growing as each scan lands).
74
+
75
+ > **One-time OMNIC setup (avoids a mid-run hang).** In the OMNIC experiment
76
+ > (`.exp`) you load, open *Experiment Setup → Collect* and set **Background
77
+ > handling** to **"Collect background after N minutes"** with **N = 999999**,
78
+ > then **Save** it back into that `.exp`. Otherwise — if it is left on "Collect
79
+ > background before every measurement" — OMNIC stops to ask "the background is
80
+ > old, collect a new one?" before each sample, and that modal blocks the DDE
81
+ > call and hangs the run. The wizard also forces this over DDE every run as a
82
+ > safety net, but saving it in the `.exp` is the durable fix. See
83
+ > [How it works](#how-it-works).
84
+
85
+ Once the run is finished, plot the spectra themselves with the companion
86
+ **[ACH-VT-IR-Plotter](https://github.com/ACH-Repo/ACH-VT-IR-Plotter)** — point
87
+ it at the session folder for an overlay, a fixed-offset waterfall, or split
88
+ heating/cooling panels. It reads OMNIC `.SPA` natively, so no CSV export is
89
+ needed.
90
+
91
+ ## What you'll see
92
+
93
+ ```
94
+ ==================================================================
95
+ VT-IR Orchestrator -- iS5 + Specac heated Golden Gate
96
+ ==================================================================
97
+ Sample / experiment name: MOF42_run3
98
+ Mode -- 0 = Background (empty ATR) / 1 = Sample (0/1) [0]: 1
99
+ Temperatures -- list "50 100 150" or range "(50,200,20)" [(50,200,20)]:
100
+ Add a cool-down (down-scan) after heating, to check reversibility? (y/n) [n]: n
101
+ Add ONE final measurement at the starting temperature after cooling? (y/n) [n]: y
102
+ OMNIC experiment file (.exp) [C:\my documents\omnic\Param\VT_128_2.exp]:
103
+ Extra equilibration seconds at each set point (after Specac says ready) [120]:
104
+ 2026-06-01 12:00:00 INFO Sample: MOF42_run3
105
+ 2026-06-01 12:00:00 INFO Mode: Sample
106
+ 2026-06-01 12:00:00 INFO Temperatures (C): 50, 70, 90, 110, 130, 150, 170, 190, 200
107
+ 2026-06-01 12:00:00 INFO Extra steps: yes (10 total: up then one final point at starting T)
108
+ 2026-06-01 12:00:00 INFO Schedule: 50^ 70^ 90^ 110^ 130^ 150^ 170^ 190^ 200^ 50*
109
+ 2026-06-01 12:00:00 INFO OMNIC experiment file: C:\my documents\omnic\Param\VT_128_2.exp
110
+ 2026-06-01 12:00:00 INFO Extra export formats: csv
111
+ ...
112
+ Ready? (y = start, n = abort) (y/n) [y]: y
113
+ ```
114
+
115
+ <!-- TODO: optional screenshot of the orchestrator console + OMNIC + the live
116
+ plot side-by-side during a real run. Demonstrates the "single launch,
117
+ two windows" workflow nicely. -->
118
+
119
+ After the confirmation the wizard runs unattended; every Specac CLI call,
120
+ every DDE command, and the actual `temp?` reading at each set point land in a
121
+ timestamped log file under your configured `log_dir`.
122
+
123
+ ## Temperature input syntax
124
+
125
+ | Form | Example | Expands to |
126
+ |------|---------|-----------|
127
+ | Range tuple | `(50,200,20)` | `50, 70, 90, 110, 130, 150, 170, 190, 200` |
128
+ | Whitespace-separated list | `50 100 150 200` | `50, 100, 150, 200` |
129
+ | Comma-separated list | `50, 100, 150` | `50, 100, 150` |
130
+
131
+ All temperatures are in °C and must fall within `[tmin, tmax]` from the config.
132
+
133
+ ## Features
134
+
135
+ - **Two-pass workflow** — one heating run with the ATR empty produces the
136
+ per-temperature backgrounds; a second run with the sample loaded re-uses
137
+ those BGs to ratio the sample spectra automatically. No "needs a background
138
+ first" error and no manual sample swap at temperature.
139
+ - **Optional cool-down (down-scan)** — ascending then descending, files tagged
140
+ `_up` / `_down`, reuses the up-scan BGs.
141
+ - **Optional single return-to-start** — for users who want a lightweight
142
+ reversibility check without a full down-scan pass; produces one extra
143
+ `…_return.SPA` at the lowest temperature after the heating ramp.
144
+ - **Flexible background matching** — backgrounds do *not* have to match the
145
+ sample temperature exactly (the cell sits in a controlled glovebox). Per
146
+ run you pick `exact`, `closest` (nearest available BG temperature per step),
147
+ or `fixed` (one chosen BG for every step). Temperature mismatches and
148
+ backgrounds older than `[backgrounds] max_age_warn_days` are **warned about
149
+ but never block the run** — and every such decision is written to the run
150
+ log so there's a clear record of exactly which background each measurement
151
+ used.
152
+ - **Chronological filename index** — every spectrum gets a zero-padded
153
+ prefix (`00_`, `01_`, …) so Explorer's name sort matches the order they
154
+ were collected. Padding adapts to the schedule length.
155
+ - **Configurable extra export formats** — every collection is written as
156
+ OMNIC's native `.SPA` plus any extension listed in `[export]
157
+ additional_formats` (default `csv`; `jdx` and others work too).
158
+ - **Robust against the long-collection DDE timeout** — uses OMNIC's `Polling`
159
+ keyword + `MenuStatus` polling, the pattern the DDE manual recommends on
160
+ page 124.
161
+ - **Robust against stuck-Wait state on the Specac controller** — sends an
162
+ explicit `off` → `on` at start, queries `temp?` after every `sp T w`, and
163
+ aborts before OMNIC collects at the wrong temperature.
164
+ - **No mid-run hang on stale backgrounds** — the wizard forces OMNIC's
165
+ background handling to "reuse the current background" (`BackgroundHandling =
166
+ AfterTime` with a very large `MaxBackgroundAge`) over DDE at the start of
167
+ every run, so a day-old background never triggers the "collect a new one?"
168
+ modal that would otherwise block the DDE call and stall the run. The age (in
169
+ minutes) is the `[backgrounds] max_bg_age_min` config knob. Pair it with the
170
+ matching one-time `.exp` setting (see Quick start) for the durable fix.
171
+ - **Live temperature overlay** — auto-launches alongside the orchestrator and
172
+ re-reads the Specac log + the session folder every few seconds. It is
173
+ scoped to the current run (the background pass and the sample pass don't
174
+ pile into one cramped image), preserves your zoom across refreshes (press
175
+ `f` to resume auto-follow), and auto-saves an SVG snapshot — once the run
176
+ finishes plus `save_delay_s` (default 10 min, to capture the cool-down
177
+ tail), and again on manual window close. Snapshots land in
178
+ `<plot_dir>/<sample>/<sample>_<BG|SAMPLE>_<timestamp>.svg`.
179
+ - **Live IR stack** — a second auto-launched window that re-reads the session's
180
+ `.SPA` files as they land and re-draws a temperature-colored waterfall of
181
+ every scan so far (or an `overlay`, per `[ir_plot] mode`). After each new scan
182
+ it overwrites a single SVG at `<plot_dir>/<sample>/<sample>_IR_stack.svg`, so
183
+ only the final stacked spectrum persists. Reads OMNIC `.SPA` natively (readers
184
+ shared with the companion plotter). Configure layout/unit in the `[ir_plot]`
185
+ config section; suppress with `--no-ir-plot` (or both windows with
186
+ `--no-live-plot`).
187
+ - **Companion spectrum plotter** — where the live overlay tracks *temperature
188
+ vs. time* during a run, the separate
189
+ [**ACH-VT-IR-Plotter**](https://github.com/ACH-Repo/ACH-VT-IR-Plotter) turns
190
+ the collected spectra into a publication-ready figure afterwards: overlay /
191
+ fixed-offset waterfall / split heating–cooling panels, with the scan
192
+ direction and temperature read straight from the wizard's filename
193
+ convention and Absorbance-vs-Transmittance detected automatically. Reads
194
+ OMNIC `.SPA` directly, so the `.csv` export is optional.
195
+ - **Tracebacks land in the run log** — if something blows up at 3 AM, the
196
+ full traceback is in the same `.log` file as the call history that
197
+ preceded it.
198
+
199
+ ## Installation
200
+
201
+ ```
202
+ pip install vtir-wizard
203
+ ```
204
+
205
+ then `vtir-wizard --init-config` to write an editable `vt_ir_config.ini` to
206
+ `%APPDATA%\vtir-wizard\`. To update later: `pip install --upgrade vtir-wizard`.
207
+
208
+ `pip` pulls in `matplotlib`, `numpy`, and (on Windows) `pywin32` automatically.
209
+
210
+ Requirements:
211
+
212
+ - Python 3.9+ on Windows (the DDE/`pywin32` path is Windows-only).
213
+ - Specac USB Temperature Controller software **v1.0.23.0 or later** (the CLI
214
+ was added in this version). Confirm with `specac.cmd temp?` in a terminal.
215
+ - Thermo OMNIC. Any version that supports DDE (i.e. ≥ 6.0).
216
+
217
+ Tested combination: OMNIC + Nicolet iS5 + Specac heated Golden Gate ATR +
218
+ Specac controller software v1.0.23.0+.
219
+
220
+ **From a source checkout** (development): `pip install -e .` from the repo root,
221
+ or run without installing via `run_vt_ir.bat` (it puts `src/` on `PYTHONPATH`).
222
+
223
+ ### Config file resolution
224
+
225
+ `vtir-wizard` looks for `vt_ir_config.ini` in this order: an explicit
226
+ `--config <path>`, then `./vt_ir_config.ini` in the current folder, then the
227
+ per-user `%APPDATA%\vtir-wizard\vt_ir_config.ini`. So you can keep a global
228
+ default and override it per-experiment by dropping a config in the working
229
+ folder.
230
+
231
+ ## Project layout
232
+
233
+ ```
234
+ ACH-VT-IR-Wizard/
235
+ ├── pyproject.toml packaging metadata (entry point: vtir-wizard)
236
+ ├── src/vtir_wizard/
237
+ │ ├── __init__.py version + shared SCHEDULE_KINDS table
238
+ │ ├── orchestrator.py the run wizard (the `vtir-wizard` command)
239
+ │ ├── temp_plot.py live temperature/setpoint overlay window
240
+ │ ├── ir_plot.py live stacked-IR-spectrum window
241
+ │ ├── spectra_io.py native .SPA/.csv/.jdx readers + styling
242
+ │ ├── config.py config discovery + --init-config
243
+ │ └── data/vt_ir_config.ini bundled config template
244
+ ├── run_vt_ir.bat double-click launcher for the wizard
245
+ ├── run_analyse_live.bat double-click launcher for a plot (standalone)
246
+ ├── README.md you are here
247
+ ├── LICENSE MIT
248
+ └── .gitignore
249
+ ```
250
+
251
+ ## How it works
252
+
253
+ <details>
254
+ <summary>Architecture and DDE call shapes (click to expand)</summary>
255
+
256
+ ### Why the obvious approach doesn't work
257
+
258
+ OMNIC and the Specac controller come from different manufacturers and don't
259
+ talk to each other. The naive "run a Specac heating program in parallel with
260
+ an OMNIC macro" workflow has two failure modes that turn up immediately on
261
+ real hardware:
262
+
263
+ 1. **Timing drift.** A `Wait <N> s` slot in the Specac `.prog` that's sized to
264
+ match an estimated OMNIC scan duration breaks the moment scans run longer
265
+ than expected. Every subsequent measurement lands inside the next ramp.
266
+ 2. **"OMNIC needs a background first."** OMNIC refuses to collect a sample
267
+ without a current background, and a parallel macro has no way to attach a
268
+ different BG to each temperature.
269
+
270
+ ### The Specac side
271
+
272
+ Specac's USB controller v1.0.23.0+ exposes a CLI:
273
+
274
+ ```
275
+ specac.cmd on | off | c | f | k
276
+ specac.cmd tol <degrees>
277
+ specac.cmd ramp <C/min>
278
+ specac.cmd sp <T> # setpoint only
279
+ specac.cmd sp <T> w # setpoint AND block until reached within tolerance
280
+ specac.cmd temp? | sp? | ramp?
281
+ ```
282
+
283
+ The `sp <T> w` call is the key — it returns when the cell is actually within
284
+ tolerance of the setpoint, so the Python orchestrator never has to guess
285
+ timings.
286
+
287
+ The controller has been observed returning from `sp T w` while the cell was
288
+ still far from `T` (a stuck "Wait" state left over from a previous `.prog`
289
+ session). To defend against that, the wizard sends an explicit `off` before
290
+ `on` in its preamble and queries `temp?` after every wait — if the reading
291
+ is more than `tolerance + 5 °C` from the setpoint, the run aborts before
292
+ OMNIC starts a doomed collection.
293
+
294
+ ### The OMNIC side
295
+
296
+ OMNIC's DDE interface (manual: `OMNIC DDE.pdf`, app name `OMNIC`, topic
297
+ `Spectra`) handles the spectrometer. A few details from the trenches:
298
+
299
+ - **The 60-second DDE Exec timeout.** pywin32's `Conversation.Exec(...)` has
300
+ a hard 60 s internal timeout, but a 128-scan @ 2 cm⁻¹ collection is ~3.5
301
+ min. The fix is the `Polling` keyword (DDE manual p. 124): the command
302
+ returns immediately, and Python waits by polling
303
+ `MenuStatus CollectBackground` / `…CollectSample` until OMNIC reports the
304
+ menu enabled again.
305
+ - **`SetAsBackground` instead of `Collect/BackgroundFileName`.** The
306
+ documented way to bind a saved `.SPA` as the next ratio reference is to
307
+ poke the `Collect` group's `BackgroundHandling = ThisBkg` and
308
+ `BackgroundFileName = <path>` parameters. On the tested iS5 build, OMNIC
309
+ rejects writes to `BackgroundFileName` over both `DDEPoke` and
310
+ `[Set …]` Exec. The GUI-equivalent workaround works fine:
311
+ ```
312
+ [Import "<bg.spa>"] -> [Display] -> [SetAsBackground]
313
+ -> [DeleteSelectedSpectra] -> [CollectSample …]
314
+ ```
315
+ - **Defeating the "background is old — collect a new one?" prompt.** If the
316
+ experiment's *Background handling* is left on "Collect background before every
317
+ measurement" (`BackgroundHandling = BeforeCol`), OMNIC pops a modal asking to
318
+ confirm reuse of the bound (old) background before each sample. The modal
319
+ blocks the DDE `Exec` (the same 60 s timeout), so the wizard never gets its
320
+ ack and the run hangs in "Still waiting for CollectSample". The fix is
321
+ `BackgroundHandling = AfterTime` with a very large `MaxBackgroundAge` (minutes)
322
+ — "the current background is young enough, just use it" — which the wizard
323
+ sets over DDE right after `LoadParameters` (best-effort; logged and read back
324
+ for the audit trail). Because the iS5 build can reject parameter writes, the
325
+ **durable** fix is to also select "Collect background after N minutes" with
326
+ N = `max_bg_age_min` in the `.exp` and save it; the DDE write is the
327
+ belt-and-suspenders. (DDE manual, Collect group: `BackgroundHandling` ∈
328
+ `{BeforeCol, AfterCol, AfterTime, ThisBkg}`; `MaxBackgroundAge` = integer
329
+ minutes, consulted only in `AfterTime` mode.)
330
+ - **Without `Invoke`, collected spectra land in OMNIC's invisible DDE
331
+ window.** A `[Display]` before `[Export]` makes the new spectrum the
332
+ active/selected one so Export saves what we just collected.
333
+
334
+ ### The orchestrator side
335
+
336
+ The per-step body in the wizard follows the same shape regardless of mode:
337
+
338
+ ```
339
+ specac.cmd sp <T> w # heat to T, block until reached
340
+ specac.cmd temp? # sanity check the cell is at T
341
+ sleep <equilibration_s> # let the sample equilibrate
342
+ collect spectrum # see DDE call shapes above
343
+ export .SPA + extras # one [Export] per requested format
344
+ clear workspace
345
+ ```
346
+
347
+ Sample-mode adds the BG bind (Import + SetAsBackground) before the collect.
348
+
349
+ ### Filename convention
350
+
351
+ ```
352
+ <output_root>/<sample>/NN_BG_<sample>_<T>C.SPA # backgrounds
353
+ <output_root>/<sample>/NN_<sample>_<T>C.SPA # samples, plain
354
+ <output_root>/<sample>/NN_<sample>_<T>C_up.SPA # samples, down-scan enabled
355
+ <output_root>/<sample>/NN_<sample>_<T>C_down.SPA # samples, down-scan enabled
356
+ <output_root>/<sample>/NN_<sample>_<T>C_return.SPA # samples, return-to-start enabled
357
+ ```
358
+
359
+ `NN` is the zero-padded chronological index for that session. Each extra
360
+ format from `[export] additional_formats` produces a parallel file with the
361
+ same stem (`NN_<sample>_<T>C.csv`, …).
362
+
363
+ The sample-mode BG lookup uses a glob that matches both indexed
364
+ (`NN_BG_…`) and unindexed (`BG_…`) names, so BG sets collected before the
365
+ index feature was introduced still work without renaming.
366
+
367
+ </details>
368
+
369
+ <details>
370
+ <summary>Troubleshooting (click to expand)</summary>
371
+
372
+ **`pywin32 is required for OMNIC DDE access`** — `pip install pywin32`.
373
+
374
+ **`Could not open DDE conversation with OMNIC`** — OMNIC isn't running, or
375
+ its DDE server isn't ready. Open OMNIC first, leave at least one spectral
376
+ window visible, then re-run.
377
+
378
+ **`Could not find specac.cmd at: …`** — update the Specac controller
379
+ software to v1.0.23.0+ or correct the `specac_exe` path in
380
+ `vt_ir_config.ini`. Confirm with `where specac.cmd.exe` in a terminal.
381
+
382
+ **`Specac CLI refused the command. … Not Running In Manual Mode`** — the
383
+ Specac controller GUI is in Program Mode (the tab you use to run a
384
+ `.prog` file), and the CLI only works in Manual Mode. Switch the GUI to
385
+ Manual Mode (the home tab with the setpoint readout and arrow buttons)
386
+ and re-run. The orchestrator aborts on this within one second — before
387
+ OMNIC opens — so no spectra are collected at the wrong temperature.
388
+
389
+ **`Specac CLI refused the command. … Received Invalid Value`** — Specac
390
+ rejected a numeric argument. Most common cause: a leading decimal point
391
+ like `tolerance_c = .5` in `vt_ir_config.ini`. Use `0.5` instead. (Recent
392
+ versions of the wizard normalize this automatically, but the underlying
393
+ CLI is picky about it.)
394
+
395
+ **`No background files found for sample …`** — the sample-mode preflight
396
+ found no `BG_<sample>_<T>C.SPA` at all for this sample. The message lists
397
+ which other sample folders DO have BG files, in case you typed the name
398
+ slightly wrong. (Run BG mode first if there genuinely are none.)
399
+
400
+ **`Missing exact-temperature backgrounds for sample …`** — you chose the
401
+ `exact` matching mode but at least one sample temperature has no BG at that
402
+ exact temperature. Either collect the missing BGs, or re-run and pick the
403
+ `closest` or `fixed` matching mode to proceed with the backgrounds you
404
+ already have (mismatches are warned about and logged, not blocked).
405
+
406
+ **`Specac reported 'setpoint reached' but cell is at X C`** — the
407
+ controller's CLI returned from `sp T w` while the cell was still far from
408
+ `T`. The orchestrator already sends `off` before `on` to flush this state;
409
+ if it triggers anyway, close and reopen the Specac controller GUI to fully
410
+ clear it, then re-run.
411
+
412
+ **`OMNIC did not accept 'sample …' / 'LoadParameters' after N attempt(s)` /
413
+ repeated collections fail to start** — OMNIC accepted the file/display
414
+ commands (Import, SetAsBackground) but could not *start* the actual
415
+ collection — or a fresh run failed immediately on `LoadParameters`. The DDE
416
+ channel is fine; the **iS5 bench is wedged** — a dialog is open in OMNIC, a
417
+ scan is already running, or (most common) the spectrometer has dropped
418
+ offline. The orchestrator reconnects and retries `collect_retries` times
419
+ first (this now also covers the `LoadParameters` preamble, so a run started
420
+ right after a wedged one gets a chance to recover); if that fails it aborts
421
+ with this message. **Fix: restart OMNIC.** The bench can stay wedged across
422
+ runs until OMNIC is restarted — which is why re-running without restarting
423
+ keeps failing on the very first command. If it recurs specifically during a
424
+ long passive cool-down, the lengthening gap between scans may be letting the
425
+ bench idle out; restarting OMNIC before the sample pass and keeping an eye on
426
+ the first down-scan collection is the practical workaround.
427
+
428
+ **`dde.error: Exec failed` mid-collection** — the underlying pywin32 error
429
+ behind the message above (pywin32's DDE Exec has a hard 60 s transaction
430
+ timeout, which trips when OMNIC can't start the command). Handled by the
431
+ retry/reconnect logic; see the entry above.
432
+
433
+ **Heater not switching off after a crash** — the `finally` block calls
434
+ `specac.cmd off` on every exit path, including Ctrl-C. If you killed the
435
+ process hard, run `specac.cmd off` manually in a terminal.
436
+
437
+ **Run hangs at "Still waiting for CollectSample" with an OMNIC dialog open** —
438
+ OMNIC is asking whether to collect a fresh background (its *Background handling*
439
+ is on "before every measurement"). The wizard already forces
440
+ `BackgroundHandling = AfterTime` + a huge `MaxBackgroundAge` over DDE, but if
441
+ that write is rejected by your build the dialog can still appear. Fix it at the
442
+ source: in the experiment's *Collect → Background handling*, choose "Collect
443
+ background after N minutes" with N = 999999 and **Save** the `.exp`. Dismiss the
444
+ open dialog with **No** to let the current run continue.
445
+
446
+ **`No vt_ir_config.ini found`** — run `vtir-wizard --init-config` to create the
447
+ per-user config (the path is printed), edit it, then re-run. Or pass an explicit
448
+ `--config <path>`.
449
+
450
+ </details>
451
+
452
+ ## Building and publishing (maintainers)
453
+
454
+ The package builds with [hatchling](https://hatch.pypa.io/). From the repo root:
455
+
456
+ ```
457
+ pip install build twine
458
+ python -m build # writes dist/vtir_wizard-<ver>-py3-none-any.whl + .tar.gz
459
+ pip install dist/*.whl # smoke-test the wheel in a fresh venv
460
+ twine upload dist/* # upload to PyPI (needs your PyPI API token)
461
+ ```
462
+
463
+ To validate the listing first, upload to TestPyPI:
464
+ `twine upload --repository testpypi dist/*`, then
465
+ `pip install -i https://test.pypi.org/simple/ vtir-wizard`.
466
+
467
+ Bump `__version__` in `src/vtir_wizard/__init__.py` before each release (the
468
+ build reads the version from there). Confirm the `vtir-wizard` name is available
469
+ on PyPI before the first upload.
470
+
471
+ ## Authorship and history
472
+
473
+ This project was written by **[@p3rAsperaAdAstra](https://github.com/p3rAsperaAdAstra)**
474
+ in collaboration with **Claude (Anthropic's AI assistant)** in May–June 2026.
475
+ The earlier parallel-process design (a Specac `.prog` generator plus an OMNIC
476
+ Macros\Basic script timed against each other) is preserved for reference in
477
+ the author's notes; this repository is the rewrite that replaced it with a
478
+ single Python orchestrator talking to both instruments over their respective
479
+ control interfaces.
480
+
481
+ Specific user-visible features added during the rewrite:
482
+
483
+ - Single-process orchestration via DDE + Specac CLI (no parallel macros).
484
+ - Per-temperature background binding via `Import` + `SetAsBackground`.
485
+ - Polling-based long-collection support.
486
+ - Live temperature overlay (`temp_plot`) + live stacked-IR-spectrum window
487
+ (`ir_plot`) auto-launched alongside the run.
488
+ - Packaged for PyPI as `vtir-wizard` (single console command, per-user config).
489
+ - OMNIC background-aging forced over DDE so stale backgrounds never hang a run.
490
+ - Optional cool-down and optional single return-to-start measurement.
491
+ - Chronological filename indices, configurable extra export formats.
492
+ - Sanity-checked Specac waits, traceback-routed log files.
493
+
494
+ This note is included for transparency about what was written by hand vs.
495
+ with AI assistance.