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.
- vtir_wizard-1.4.1/.gitignore +36 -0
- vtir_wizard-1.4.1/LICENSE +22 -0
- vtir_wizard-1.4.1/PKG-INFO +495 -0
- vtir_wizard-1.4.1/README.md +467 -0
- vtir_wizard-1.4.1/pyproject.toml +59 -0
- vtir_wizard-1.4.1/run_vt_ir.bat +31 -0
- vtir_wizard-1.4.1/src/vtir_wizard/__init__.py +34 -0
- vtir_wizard-1.4.1/src/vtir_wizard/config.py +115 -0
- vtir_wizard-1.4.1/src/vtir_wizard/data/__init__.py +2 -0
- vtir_wizard-1.4.1/src/vtir_wizard/data/vt_ir_config.ini +231 -0
- vtir_wizard-1.4.1/src/vtir_wizard/ir_plot.py +400 -0
- vtir_wizard-1.4.1/src/vtir_wizard/orchestrator.py +1465 -0
- vtir_wizard-1.4.1/src/vtir_wizard/spectra_io.py +347 -0
- vtir_wizard-1.4.1/src/vtir_wizard/temp_plot.py +444 -0
|
@@ -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
|
+

|
|
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.
|