jwst-vmpt 1.2.2__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.
- jwst_vmpt-1.2.2/CHANGELOG.md +638 -0
- jwst_vmpt-1.2.2/CONTEXT.md +888 -0
- jwst_vmpt-1.2.2/LICENSE +21 -0
- jwst_vmpt-1.2.2/MANIFEST.in +14 -0
- jwst_vmpt-1.2.2/PKG-INFO +728 -0
- jwst_vmpt-1.2.2/README.md +690 -0
- jwst_vmpt-1.2.2/jwst_vmpt.egg-info/PKG-INFO +728 -0
- jwst_vmpt-1.2.2/jwst_vmpt.egg-info/SOURCES.txt +30 -0
- jwst_vmpt-1.2.2/jwst_vmpt.egg-info/dependency_links.txt +1 -0
- jwst_vmpt-1.2.2/jwst_vmpt.egg-info/entry_points.txt +2 -0
- jwst_vmpt-1.2.2/jwst_vmpt.egg-info/requires.txt +12 -0
- jwst_vmpt-1.2.2/jwst_vmpt.egg-info/top_level.txt +1 -0
- jwst_vmpt-1.2.2/pyproject.toml +77 -0
- jwst_vmpt-1.2.2/requirements.txt +14 -0
- jwst_vmpt-1.2.2/setup.cfg +4 -0
- jwst_vmpt-1.2.2/vmpt/__init__.py +1 -0
- jwst_vmpt-1.2.2/vmpt/catalog.py +335 -0
- jwst_vmpt-1.2.2/vmpt/catalog_ops.py +81 -0
- jwst_vmpt-1.2.2/vmpt/cli.py +159 -0
- jwst_vmpt-1.2.2/vmpt/coords.py +59 -0
- jwst_vmpt-1.2.2/vmpt/data/dispersion_cutoffs.npz +0 -0
- jwst_vmpt-1.2.2/vmpt/data/nirspec_msa_v2v3.npz +0 -0
- jwst_vmpt-1.2.2/vmpt/empt_io.py +368 -0
- jwst_vmpt-1.2.2/vmpt/image_io.py +160 -0
- jwst_vmpt-1.2.2/vmpt/main.py +6711 -0
- jwst_vmpt-1.2.2/vmpt/mpt_io.py +293 -0
- jwst_vmpt-1.2.2/vmpt/msa.py +84 -0
- jwst_vmpt-1.2.2/vmpt/optimizer.py +939 -0
- jwst_vmpt-1.2.2/vmpt/session_io.py +639 -0
- jwst_vmpt-1.2.2/vmpt/static/favicon.svg +27 -0
- jwst_vmpt-1.2.2/vmpt/templates/index.html +54 -0
- jwst_vmpt-1.2.2/vmpt/wavelengths.py +283 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to vMPT are recorded here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this
|
|
5
|
+
project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [1.2.2] — 2026-06-03
|
|
8
|
+
|
|
9
|
+
Packaging release: vMPT is now **pip-installable** as `jwst-vmpt`
|
|
10
|
+
on TestPyPI / PyPI.
|
|
11
|
+
|
|
12
|
+
### `pip install`
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install jwst-vmpt # PyPI (when promoted)
|
|
16
|
+
pip install -i https://test.pypi.org/simple/ jwst-vmpt # TestPyPI
|
|
17
|
+
vmpt # opens at http://localhost:5006/app
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The console script `vmpt` accepts the same flags as `run.sh`:
|
|
21
|
+
`--port`, `--fits`, `--jpg`, `--wcs`, `--catalog` (repeatable). A
|
|
22
|
+
new `vmpt examples download [DIR]` subcommand pulls the two example
|
|
23
|
+
datasets (`example_a370`, `example_r0600` — together ~64 MB) from a
|
|
24
|
+
GitHub release asset on demand, so the pip wheel itself stays at
|
|
25
|
+
~20 MB (only the required MSA grid + per-shutter dispersion table
|
|
26
|
+
are bundled).
|
|
27
|
+
|
|
28
|
+
### Repo restructuring
|
|
29
|
+
|
|
30
|
+
- The Bokeh app directory renamed from `app/` to `vmpt/` so it
|
|
31
|
+
doubles as the Python import package. All `from app.X` imports
|
|
32
|
+
rewritten to `from vmpt.X` (17 files, ~56 references).
|
|
33
|
+
- `data/*.npz` moved to `vmpt/data/*.npz` so the wheel ships them
|
|
34
|
+
alongside the modules. Path lookups in `vmpt/msa.py` and
|
|
35
|
+
`vmpt/wavelengths.py` adjusted (one fewer `parent`).
|
|
36
|
+
- `run.sh` updated to `bokeh serve vmpt/` for source-tree users; no
|
|
37
|
+
behavioural change.
|
|
38
|
+
- New top-level files: `pyproject.toml` (PEP 517/518 metadata + the
|
|
39
|
+
`vmpt` console script), `MANIFEST.in` (sdist completeness),
|
|
40
|
+
`vmpt/cli.py` (entry point).
|
|
41
|
+
- `.gitignore` gains `build/`, `dist/`, `.eggs/` for the pip build
|
|
42
|
+
flow.
|
|
43
|
+
|
|
44
|
+
### No behavioural changes
|
|
45
|
+
|
|
46
|
+
Tests still pass at **139 / 4 skipped**. The Bokeh app behaves
|
|
47
|
+
identically to v1.2.1; only the install path / directory name
|
|
48
|
+
changed.
|
|
49
|
+
|
|
50
|
+
## [1.2.1] — 2026-06-03
|
|
51
|
+
|
|
52
|
+
Patch release. Two real bug fixes on top of v1.2.0's collision-
|
|
53
|
+
protection feature, plus a substantial UI cleanup that came out of
|
|
54
|
+
a hands-on review.
|
|
55
|
+
|
|
56
|
+
### Collision-protection fixes
|
|
57
|
+
|
|
58
|
+
- **Row tolerance is now slitlet-aware.** v1.2.0 hard-coded
|
|
59
|
+
``|Δs| ≤ 1`` between source centres, but a 3-shutter slitlet at
|
|
60
|
+
row ``s_p`` already occupies rows ``{s_p−1, s_p, s_p+1}`` and the
|
|
61
|
+
user-requested rule is "no other shutter at s_p±2 either." The
|
|
62
|
+
evaluator now computes two tolerances at construction time:
|
|
63
|
+
- Protected slitlet ↔ stuck-open (single shutter):
|
|
64
|
+
``|Δs| ≤ half + 1``
|
|
65
|
+
- Protected ↔ another slitlet (same slit_length):
|
|
66
|
+
``|Δs| ≤ 2·half + 1``
|
|
67
|
+
where ``half = slit_length // 2``. So the default ``slit_length=3``
|
|
68
|
+
now correctly forbids stuck-open or other slitlets at rows
|
|
69
|
+
``s_p±2``. For ``slit_length=5`` the buffer scales up to
|
|
70
|
+
``s_p±3`` (stuck-open) or ``s_p±5`` (other slitlets).
|
|
71
|
+
``SHVAL_S_TOLERANCE = 1`` is preserved as the per-individual-
|
|
72
|
+
shutter constant for the live-canvas orange overlap glyph (each
|
|
73
|
+
opened shutter contributes its own ±1 zone, so the visualization
|
|
74
|
+
already paints the correct envelope around a multi-shutter slitlet).
|
|
75
|
+
- **Advanced settings modal sits above the new config modal.**
|
|
76
|
+
When the optimizer config dialog was added in this release the
|
|
77
|
+
Advanced settings card stayed at the same z-index, so opening
|
|
78
|
+
Advanced from inside Configure showed nothing — the config card
|
|
79
|
+
drew on top. Bumped Advanced backdrop / card to ``z-index`` 1001 /
|
|
80
|
+
1002.
|
|
81
|
+
|
|
82
|
+
### Pointing-tab UI moved into a dialog
|
|
83
|
+
|
|
84
|
+
The Pointing tab used to stack 10+ optimizer-config widgets, and the
|
|
85
|
+
``Run optimization`` button slid below the fold on any window under
|
|
86
|
+
~1200 px tall. The whole block now lives in a centered modal:
|
|
87
|
+
|
|
88
|
+
- The Pointing tab shows a single primary ``Open optimizer…`` button.
|
|
89
|
+
- The modal (``opt_config_modal_card``) contains every optimizer-
|
|
90
|
+
config widget plus ``Run`` / ``Cancel`` and the live status line.
|
|
91
|
+
- The existing progress + results modal flow (``opt_modal_card``) is
|
|
92
|
+
unchanged after ``Run`` is clicked — the config card just dismisses
|
|
93
|
+
itself first.
|
|
94
|
+
- Both the optimizer config and the catalog editor modals gained a
|
|
95
|
+
top-right ``×`` dismiss button.
|
|
96
|
+
|
|
97
|
+
### Help / status text — context-aware
|
|
98
|
+
|
|
99
|
+
- The help panel on the right side of the canvas is now **collapsed
|
|
100
|
+
by default**. The toggle button stays in place; one click on
|
|
101
|
+
``Show help`` restores width to its v1.2.0 size with the Quick
|
|
102
|
+
guide + rotating tip. The figure uses fixed
|
|
103
|
+
``frame_width``/``frame_height`` so the canvas pixel aspect doesn't
|
|
104
|
+
change when the panel collapses / expands.
|
|
105
|
+
- The Method dropdown's three-line Democracy / Meritocracy /
|
|
106
|
+
Hierarchy blurb is hidden by default; an ``ⓘ What do these mean?``
|
|
107
|
+
toggle reveals it on demand. The dropdown's own option labels
|
|
108
|
+
already carry the one-line summary.
|
|
109
|
+
- The status line under ``Run optimization`` was always reading
|
|
110
|
+
*"Load a catalog with priorities, then click Run."* even when a
|
|
111
|
+
catalog with priorities was loaded. ``_refresh_opt_status_div()``
|
|
112
|
+
now updates it based on (catalog presence, method, priority /
|
|
113
|
+
weight column availability):
|
|
114
|
+
- no catalog → ``Load a catalog (Input tab) before running.``
|
|
115
|
+
- catalog + Democracy → ``Ready · N sources.``
|
|
116
|
+
- catalog + Meritocracy without ``weight`` →
|
|
117
|
+
``⚠ Meritocracy needs a weight column.``
|
|
118
|
+
- catalog + Hierarchy without ``priority`` →
|
|
119
|
+
``⚠ Hierarchy needs a priority column.``
|
|
120
|
+
|
|
121
|
+
### Input / MPT tabs
|
|
122
|
+
|
|
123
|
+
- All path inputs across the Input + MPT tabs now use a unified
|
|
124
|
+
``_wrap_path_picker`` helper: the path ``TextInput`` is hidden
|
|
125
|
+
behind an ``Edit path`` toggle when empty, and Browse buttons are
|
|
126
|
+
promoted to primary blue. The path **auto-reveals** as soon as
|
|
127
|
+
it's populated (by Browse, by autoload, or by typing), so users
|
|
128
|
+
always see what's loaded — only the empty default is hidden.
|
|
129
|
+
- The MPT tab is grouped into four sections (Import / Save / Load /
|
|
130
|
+
Export) separated by dashed and solid hr dividers, so the 10+
|
|
131
|
+
widgets feel like coherent blocks instead of one long column.
|
|
132
|
+
- Renamed the ``Setting`` tab title to ``Settings`` (singular →
|
|
133
|
+
plural).
|
|
134
|
+
|
|
135
|
+
### Tests
|
|
136
|
+
|
|
137
|
+
- ``tests/test_optimizer_protection.py`` gains 4 new tests:
|
|
138
|
+
parametrize over ``N ∈ {1, 3, 5}`` to pin the cached tolerances,
|
|
139
|
+
and a regression that an N=3 slitlet drops at least as many
|
|
140
|
+
unprotected sources as N=1 under an H grating. **139 passed, 4
|
|
141
|
+
skipped** in total (up from 135 / 4 in v1.2.0).
|
|
142
|
+
|
|
143
|
+
## [1.2.0] — 2026-06-03
|
|
144
|
+
|
|
145
|
+
Feature release: **shutter collision protection in the optimizer**.
|
|
146
|
+
|
|
147
|
+
Same-row sources on the same NIRSpec detector half (Q1/Q3 → NRS1,
|
|
148
|
+
Q2/Q4 → NRS2) disperse onto overlapping detector pixels when their
|
|
149
|
+
V2 separation is smaller than the spectrum's V2 half-extent
|
|
150
|
+
(`app.wavelengths.v2_overlap_distance` — 35″ for PRISM, ~500″ for
|
|
151
|
+
the H gratings). Until now the optimizer counted both members of
|
|
152
|
+
every such pair as observable; the live canvas already painted the
|
|
153
|
+
loser orange, but the score didn't reflect that downstream
|
|
154
|
+
penalty. v1.2.0 wires the same collision check into the optimizer's
|
|
155
|
+
per-pointing scoring so the user can mark high-priority targets as
|
|
156
|
+
**protected** and have the optimizer steer them into rows free of
|
|
157
|
+
collisions.
|
|
158
|
+
|
|
159
|
+
### Optimizer core (`app/optimizer.py`)
|
|
160
|
+
|
|
161
|
+
- `PointingEvaluator` accepts new keyword args: `protect_mask`,
|
|
162
|
+
`priorities`, `weights`, `disperser`, `filt`, `reason`. When
|
|
163
|
+
`protect_mask` is None (the default), behaviour is identical to
|
|
164
|
+
v1.1.1 — the existing 16 optimizer tests are unchanged.
|
|
165
|
+
- A new method `evaluate_with_stats(...)` returns the existing
|
|
166
|
+
3-tuple plus the count of sources dropped by the collision rules
|
|
167
|
+
at this pointing. `evaluate(...)` still returns the 3-tuple but
|
|
168
|
+
its `detected` mask is now the **kept** mask (post-drop) when
|
|
169
|
+
protection is configured, so callers that score via `det.sum()`
|
|
170
|
+
pick up collision filtering for free.
|
|
171
|
+
- Three rules, applied in order at every pointing:
|
|
172
|
+
1. **Protected ↔ stuck-open** — a protected source landing on a
|
|
173
|
+
row colliding with any shutter flagged as stuck-open (REASON
|
|
174
|
+
== 2 in the CRDS `msaoper` file) is dropped. Stuck-opens
|
|
175
|
+
always disperse light onto the detector regardless of which
|
|
176
|
+
slitlets the user opens, so the protected target's spectrum is
|
|
177
|
+
unavoidably contaminated.
|
|
178
|
+
2. **Protected ↔ protected** — within each colliding cluster the
|
|
179
|
+
lowest-priority-number source wins. Ties on priority break on
|
|
180
|
+
higher weight; ties on weight break on lower index (stable).
|
|
181
|
+
Losers are dropped; winners continue to provide collision
|
|
182
|
+
pressure on the next rule.
|
|
183
|
+
3. **Protected ↔ unprotected** — every unprotected source whose
|
|
184
|
+
row collides with any still-kept protected source is dropped.
|
|
185
|
+
- Dropped protected sources do **not** propagate collision pressure
|
|
186
|
+
to rules 2/3 — if a high-priority spectrum is already
|
|
187
|
+
contaminated we won't compound the loss by also blocking
|
|
188
|
+
unprotected sources in the same row.
|
|
189
|
+
|
|
190
|
+
### Pointing-tab UI (`app/main.py`)
|
|
191
|
+
|
|
192
|
+
- New **"Protect spectra from collision"** group in the optimizer
|
|
193
|
+
sidebar (just below the existing Priority cutoff input):
|
|
194
|
+
- Checkbox: **Enable collision protection**.
|
|
195
|
+
- Radio: **By priority ≤** | **By weight ≥** (mutually exclusive).
|
|
196
|
+
- **Threshold** text input.
|
|
197
|
+
- Live status line: e.g. *"12 protected · 240 other (G140H /
|
|
198
|
+
F100LP · V2 overlap ≈ 500″)"* — updates as you toggle the
|
|
199
|
+
checkbox, switch the radio, type a threshold, or change the
|
|
200
|
+
current Disperser/Filter.
|
|
201
|
+
- `_rebuild_merged_catalog` now propagates `weight` when multiple
|
|
202
|
+
catalogs are stacked — previously single-catalog mode kept weight
|
|
203
|
+
(it pointed at the original `Catalog` object) but merged mode
|
|
204
|
+
dropped it, so the multi-catalog "By weight ≥" rule would have
|
|
205
|
+
silently selected zero sources.
|
|
206
|
+
|
|
207
|
+
### Results modal
|
|
208
|
+
|
|
209
|
+
- When protection is enabled the Score cell gains a **`−K`**
|
|
210
|
+
suffix where K = number of collision-dropped sources at this
|
|
211
|
+
pointing. The Score column is widened by ~36 px so the suffix
|
|
212
|
+
doesn't ellipsis-truncate.
|
|
213
|
+
- The hover top-10 prefixes protected sources with **🛡** so the
|
|
214
|
+
user can verify which sources are providing collision pressure.
|
|
215
|
+
A trailing line in the tooltip explains the −K count.
|
|
216
|
+
- Header summary line picks up a **"🛡 collision protection ON"**
|
|
217
|
+
badge with a one-line explainer.
|
|
218
|
+
|
|
219
|
+
### Tests
|
|
220
|
+
|
|
221
|
+
- New `tests/test_optimizer_protection.py` — 11 tests covering
|
|
222
|
+
backwards compatibility, input validation, all three drop rules,
|
|
223
|
+
cross-detector-half non-collision, stuck-open handling, and the
|
|
224
|
+
invariant that protection can only reduce a pointing's score
|
|
225
|
+
(never increase it). 3 tests skip gracefully when synthetic
|
|
226
|
+
sources don't happen to land in the geometry the test exercises.
|
|
227
|
+
- Existing test suite unchanged: **135 passed, 4 skipped** total
|
|
228
|
+
(up from 124/1).
|
|
229
|
+
|
|
230
|
+
### Notes / known limitations
|
|
231
|
+
|
|
232
|
+
- For the H gratings the V2 overlap distance is ~500″ — comparable
|
|
233
|
+
to the full MSA — so even one protected target rules out a large
|
|
234
|
+
fraction of co-observable sources. The result is physically
|
|
235
|
+
truthful, not a bug; the modal shows the lower kept count so
|
|
236
|
+
expectations match reality.
|
|
237
|
+
- Unprotected sources whose rows collide with a stuck-open shutter
|
|
238
|
+
are NOT dropped from scoring (only protected sources have the
|
|
239
|
+
contamination penalty applied to them). This matches the
|
|
240
|
+
user-requested semantic: protection is a high-priority-only
|
|
241
|
+
feature, not a universal contamination filter.
|
|
242
|
+
|
|
243
|
+
## [1.1.1] — 2026-06-03
|
|
244
|
+
|
|
245
|
+
Patch release. Polish + several real bugs in the v1.1.0 optimizer
|
|
246
|
+
and catalog editor. The big change is that **Hierarchy mode actually
|
|
247
|
+
optimises lower tiers now**, plus a much richer results table.
|
|
248
|
+
|
|
249
|
+
### Optimizer
|
|
250
|
+
|
|
251
|
+
- **Slitlet centre is now right under the target.** The optimizer's
|
|
252
|
+
`axy_to_shutter` returns 0-based fractional indices, but
|
|
253
|
+
`_add_slitlet` expects 1-based — the missing `+1` was opening every
|
|
254
|
+
slitlet one row up and one column to the left of the target. Now
|
|
255
|
+
centred correctly.
|
|
256
|
+
- **Confirm dialog before Apply.** Clicking **Apply #N** opens a
|
|
257
|
+
browser confirm: "This will CLEAR all previously open shutters and
|
|
258
|
+
replace them with the optimizer's slitlets." OK → clears + applies
|
|
259
|
+
(single Undo step); Cancel → no-op. Wired via `Button.js_on_click`
|
|
260
|
+
→ hidden trigger `TextInput` → Python handler. The trigger pattern
|
|
261
|
+
is needed because `CustomJS.args` only accepts Bokeh Model
|
|
262
|
+
instances, not floats — that's why the Apply button silently did
|
|
263
|
+
nothing in v1.1.0; embed per-button scalars via Python f-string
|
|
264
|
+
interpolation into the JS body.
|
|
265
|
+
- **Hierarchy mode now genuinely optimises every priority tier.**
|
|
266
|
+
Previously DE refinement used `weights = 1 at top tier, 0
|
|
267
|
+
elsewhere`, so DE happily slid to any pointing that kept the
|
|
268
|
+
top-tier count even if it lost lower-tier sources in the process.
|
|
269
|
+
DE now uses **auto-derived lex weights** (smallest int weights
|
|
270
|
+
such that any higher tier strictly outweighs the sum of all lower
|
|
271
|
+
tiers); their sum is a lex-equivalent scalar that DE maximises
|
|
272
|
+
without violating priority ordering. The grid + multi-stage filter
|
|
273
|
+
phase is unchanged.
|
|
274
|
+
- **Results table shows tier breakdown.** For Hierarchy, the Score
|
|
275
|
+
column reads e.g. `P0:4 · P1:12 · P2:30 (46)` — per-tier source
|
|
276
|
+
count + total in parens. For Meritocracy, `Σw 287.0 (46)`. For
|
|
277
|
+
Democracy, just the count `46`.
|
|
278
|
+
- **Hover any Score cell** to see the top 10 placed sources at that
|
|
279
|
+
pointing, sorted by priority ascending then weight descending —
|
|
280
|
+
IDs + P + W per line.
|
|
281
|
+
- **Modal widened** to 740 px to fit the new columns; Score column
|
|
282
|
+
width is method-specific; cells now `overflow: hidden +
|
|
283
|
+
white-space: nowrap + text-overflow: ellipsis` so a label that
|
|
284
|
+
overruns its column truncates instead of wrapping under the row.
|
|
285
|
+
|
|
286
|
+
### Catalog editor
|
|
287
|
+
|
|
288
|
+
- **Numeric sort on Priority + Weight.** Both columns are now stored
|
|
289
|
+
as floats with NaN for missing (was strings → `"10"` < `"2"`
|
|
290
|
+
lexicographically). An HTMLTemplateFormatter renders the cell as
|
|
291
|
+
a rounded integer or blank; cell edits via StringEditor are
|
|
292
|
+
coerced back to float in `_on_cat_edit_data_change` so the column
|
|
293
|
+
stays a sortable numeric.
|
|
294
|
+
- **After a header click, the table scrolls to row 1.** Document-
|
|
295
|
+
level click delegate on `.slick-header-column` resets the table's
|
|
296
|
+
`.slick-viewport.scrollTop` to 0 (with an 80 ms delay so the
|
|
297
|
+
re-render finishes first).
|
|
298
|
+
- **CSV save** uses `_fmt_int_or_blank` for Priority + Weight so
|
|
299
|
+
the output is `5` not `5.0` and blanks stay blank.
|
|
300
|
+
- **`Compute w from p` / `Compute p from w`** write floats to the
|
|
301
|
+
source (was strings) so the new column stays numerically sortable.
|
|
302
|
+
|
|
303
|
+
### Misc
|
|
304
|
+
|
|
305
|
+
- The `compute_weights_from_priorities` helper now correctly
|
|
306
|
+
satisfies BOTH `w(p) > w(p+1)` AND `N(p)·w(p) > N(p+1)·w(p+1)`
|
|
307
|
+
using `max(w_prev + 1, n_prev * w_prev // n_q + 1)` as the
|
|
308
|
+
smallest integer that dominates the prior class (regression-tested
|
|
309
|
+
in `tests/test_catalog_ops.py`).
|
|
310
|
+
- Loader: empty cells in numeric columns now properly become NaN
|
|
311
|
+
even when the source column was masked-int (previously came
|
|
312
|
+
through as 0).
|
|
313
|
+
- A couple of additional patterns added to `.gitignore` so stray
|
|
314
|
+
personal files in the repo root can't accidentally be staged.
|
|
315
|
+
|
|
316
|
+
### Tests
|
|
317
|
+
|
|
318
|
+
- 124 passing, 1 skipped (same as v1.1.0; no test regressions).
|
|
319
|
+
|
|
320
|
+
[1.1.1]: https://github.com/fengwusun/vMPT/releases/tag/v1.1.1
|
|
321
|
+
|
|
322
|
+
## [1.1.0] — 2026-06-02
|
|
323
|
+
|
|
324
|
+
Headline feature: a complete **MSA pointing optimizer** with three
|
|
325
|
+
methods (Democracy / Meritocracy / Hierarchy), plus an editable,
|
|
326
|
+
sortable catalog editor. Several quality-of-life improvements
|
|
327
|
+
elsewhere.
|
|
328
|
+
|
|
329
|
+
### MSA pointing optimizer
|
|
330
|
+
|
|
331
|
+
- New panel at the bottom of the **Pointing** tab. Searches over
|
|
332
|
+
(ΔRA, ΔDec, ΔPA) for an (RA, Dec, V3 PA) that maximises a
|
|
333
|
+
user-selectable objective. Re-implemented in vMPT style from
|
|
334
|
+
[hMPT](https://github.com/zihaowu-astro/hMPT) (Eisenstein, McCarty,
|
|
335
|
+
Wu; CfA / Harvard); see `app/optimizer.py` for attribution +
|
|
336
|
+
algorithm notes.
|
|
337
|
+
- **Three methods**:
|
|
338
|
+
- **Democracy** — raw source count; ignores priority and weight.
|
|
339
|
+
- **Meritocracy** — sum of `weight` of placed sources (MPT-style).
|
|
340
|
+
Requires a populated weight column.
|
|
341
|
+
- **Hierarchy** — strict priority-tier lex ordering (eMPT-style).
|
|
342
|
+
Multi-stage filter: a higher-priority source is never traded for
|
|
343
|
+
any number of lower-priority sources.
|
|
344
|
+
- **Pop-up modal** with an animated striped progress bar, a spinning
|
|
345
|
+
ring, and a status line showing the current phase
|
|
346
|
+
(`Grid: 5,200 / 20,000 · 4.2s elapsed · ~12s left`,
|
|
347
|
+
`Hierarchy filter: tier 2 / 4 (p=1) — survivors: 18`,
|
|
348
|
+
`Refining top 10: 3 / 10 · 7.4s elapsed`).
|
|
349
|
+
- **Results table** with the top-10 distinct solutions (near-
|
|
350
|
+
duplicates collapsed). Each row pairs score + (ΔRA, ΔDec, ΔPA)
|
|
351
|
+
with an **Apply #N** button.
|
|
352
|
+
- **Apply #N** sets the pointing AND opens an N-shutter slitlet
|
|
353
|
+
(N from the Setting tab) at every observable target's shutter,
|
|
354
|
+
auto-tagged with the catalog source ID. One Undo step reverts
|
|
355
|
+
the whole apply.
|
|
356
|
+
- **ΔX = 0 freezes the axis** — set ΔPA = 0 to search RA/Dec only
|
|
357
|
+
at the current roll, etc. Both the grid sweep and the DE
|
|
358
|
+
refinement honour the freeze.
|
|
359
|
+
- **Advanced settings…** modal exposes grid resolution (n_RA, n_Dec,
|
|
360
|
+
n_PA), DE max iterations, objective (count/flux), source σ, and
|
|
361
|
+
the APT DVA θ.
|
|
362
|
+
|
|
363
|
+
### Catalog editor
|
|
364
|
+
|
|
365
|
+
- New **Edit catalog…** button in the Input tab opens a sortable,
|
|
366
|
+
in-cell-editable spreadsheet pop-up.
|
|
367
|
+
- Single-click any cell to edit. Tab / Enter commits; Esc cancels.
|
|
368
|
+
- Drag inside a cell to highlight text; Cmd/Ctrl-C / Cmd/Ctrl-V
|
|
369
|
+
copy / paste — a custom capture-phase keydown handler bypasses
|
|
370
|
+
SlickGrid's column-copy default so only the selected text is
|
|
371
|
+
copied.
|
|
372
|
+
- 🗑️ icon at the end of each row deletes that row.
|
|
373
|
+
- ↶ Undo / ↷ Redo for every edit, delete, derivation, and
|
|
374
|
+
column add (100-step history).
|
|
375
|
+
- **Column picker** — toggle which columns are visible. Extras
|
|
376
|
+
columns from the source CSV/FITS (the loader now preserves every
|
|
377
|
+
column it didn't claim) live alongside the standard set and can
|
|
378
|
+
be turned on or off.
|
|
379
|
+
- **Add a custom column** via a text input + button. Empty by
|
|
380
|
+
default; useful for `weight`, `reference`, etc. Round-trips
|
|
381
|
+
through Apply changes and Save as CSV.
|
|
382
|
+
- **Compute w from p** and **Compute p from w** buttons derive
|
|
383
|
+
one column from the other:
|
|
384
|
+
- `w(lowest p) = 1`; for each higher-priority class, the smallest
|
|
385
|
+
integer `w(p)` satisfying `w(p) > w(p+1)` AND
|
|
386
|
+
`N(p) * w(p) > N(p+1) * w(p+1)`. Guarantees strict-dominance:
|
|
387
|
+
one source at any tier outweighs every source at all lower
|
|
388
|
+
tiers combined.
|
|
389
|
+
- `p` from `w` groups unique weights descending and assigns
|
|
390
|
+
priorities 1, 2, 3, …
|
|
391
|
+
- **Save as CSV** with a Browse… file picker.
|
|
392
|
+
- **Apply changes & close** commits the working copy to the
|
|
393
|
+
in-memory catalog so the eMPT bundle export reflects edits.
|
|
394
|
+
|
|
395
|
+
### Catalog model
|
|
396
|
+
|
|
397
|
+
- `Catalog.weight` is now a first-class field (sibling of
|
|
398
|
+
`priority`). Loader detects `weight` / `w` / `wt` / `weights`
|
|
399
|
+
aliases. Empty cells in numeric columns properly become NaN
|
|
400
|
+
(previously masked integer columns silently became 0).
|
|
401
|
+
- Extras columns the loader didn't claim are preserved on the
|
|
402
|
+
`Catalog.extras` dict (object arrays, original column name as
|
|
403
|
+
key) and surfaced through the editor's column picker.
|
|
404
|
+
|
|
405
|
+
### UI polish
|
|
406
|
+
|
|
407
|
+
- **`run.sh`** gained `--port N`, `--fits PATH`, `--jpg PATH`,
|
|
408
|
+
`--wcs PATH`, `--catalog PATH` (repeatable) flags. Mutual-
|
|
409
|
+
exclusion rules: `--jpg` and `--wcs` come as a pair; `--fits` is
|
|
410
|
+
exclusive with them.
|
|
411
|
+
- **Tabs renamed**: Image → Input, Aim → Pointing, Pick → Setting.
|
|
412
|
+
- **Pointing tab** now also hosts Disperser/Filter (was on the
|
|
413
|
+
former Pick tab). RA/Dec inputs share a row; V3 PA/APA share a
|
|
414
|
+
row; Visibility date + button share a row.
|
|
415
|
+
- **Canvas pixel aspect locked** — `frame_width` / `frame_height`
|
|
416
|
+
match the loaded image's pixel W:H exactly. Window resizes
|
|
417
|
+
letterbox around the canvas; the image is never stretched.
|
|
418
|
+
- **Sequenced autoload**: `run.sh --jpg ... --wcs ... --catalog ...`
|
|
419
|
+
loads the image first and the catalogs strictly after, via an
|
|
420
|
+
`on_complete` callback chain so the catalog overlay never races
|
|
421
|
+
the image's `_set_image_and_recenter`.
|
|
422
|
+
- **Status bar** moved out of the scrollable sidebar column and
|
|
423
|
+
pinned to the bottom-left of the viewport (position:fixed) so
|
|
424
|
+
it can't render on top of tab content.
|
|
425
|
+
- **Optimizer Advanced settings** moved into a pop-up modal.
|
|
426
|
+
- **6 new tips** in the help-panel carousel — `run.sh` args,
|
|
427
|
+
optimizer, catalog editor, multi-catalog, pixel aspect, big-ID
|
|
428
|
+
mod.
|
|
429
|
+
|
|
430
|
+
### Tests
|
|
431
|
+
|
|
432
|
+
- 124 passing (was 96 at v1.0.1). New coverage: catalog `weight`
|
|
433
|
+
column, mod-1e7 + empty-cell NaN handling, optimizer correctness
|
|
434
|
+
(radec→Axy, quadrant inverse, centration monotonicity,
|
|
435
|
+
Hierarchy-vs-Democracy divergence, dΔ=0 freezes, dedup), the two
|
|
436
|
+
weight↔priority compute helpers in `app/catalog_ops.py`.
|
|
437
|
+
|
|
438
|
+
[1.1.0]: https://github.com/fengwusun/vMPT/releases/tag/v1.1.0
|
|
439
|
+
|
|
440
|
+
## [1.0.1] — 2026-05-21
|
|
441
|
+
|
|
442
|
+
Patch release. Two large quality-of-life corrections — accurate
|
|
443
|
+
per-shutter wavelength values, and a much friendlier catalog
|
|
444
|
+
loader — plus polish on the catalog UI and overlay defaults.
|
|
445
|
+
|
|
446
|
+
### Wavelength accuracy
|
|
447
|
+
|
|
448
|
+
- **Per-shutter dispersion table for every (disperser, filter)
|
|
449
|
+
combo**, derived from numerical integration of the pipeline
|
|
450
|
+
reference files via [`spacetelescope/msaviz`](https://github.com/spacetelescope/msaviz).
|
|
451
|
+
Lives at `data/dispersion_cutoffs.npz` (19 MB compressed) and is
|
|
452
|
+
regenerated by `scripts/precompute_dispersion_cutoffs.py`. Replaces
|
|
453
|
+
the old linear V2-shift approximation that was wrong in two ways
|
|
454
|
+
for PRISM:
|
|
455
|
+
- Previous PRISM gap was held at 2.7–3.2 μm everywhere. Real
|
|
456
|
+
gap location varies dramatically across the MSA (5–95 %
|
|
457
|
+
spread: gap_lo 0.65–3.59 μm, gap_hi 3.03–5.02 μm) because
|
|
458
|
+
PRISM dispersion is highly non-linear. The new lookup gives
|
|
459
|
+
msaviz-accurate values per shutter.
|
|
460
|
+
- PRISM endpoints used to drift with V2; in reality they're
|
|
461
|
+
essentially constant (msaviz spread is ~0.01 μm).
|
|
462
|
+
- **Q3 / Q4 PRISM shutters** correctly report "no gap on this
|
|
463
|
+
spectrum" — their spectra fall entirely on one detector.
|
|
464
|
+
- **Grating endpoints** updated to match the pipeline-reference
|
|
465
|
+
`sci_range` instead of the slightly narrower JDox "useful range":
|
|
466
|
+
- G140M/F100LP: 0.97–**1.89** (was 0.97–1.84)
|
|
467
|
+
- G140H/F100LP: 0.97–**1.89** (was 0.97–1.84)
|
|
468
|
+
- G235M/F170LP: 1.66–**3.17** (was 1.66–3.07)
|
|
469
|
+
- G235H/F170LP: 1.66–**3.17** (was 1.66–3.07)
|
|
470
|
+
- G395M/F290LP: 2.87–**5.27** (was 2.87–5.14)
|
|
471
|
+
- G395H/F290LP: 2.87–**5.27** (was 2.87–5.14)
|
|
472
|
+
- G140H/F070LP: **0.70**–1.27 (was 0.81–1.27)
|
|
473
|
+
- **vMPT does NOT depend on msaviz at runtime** — only the
|
|
474
|
+
precompute script does. The shipped npz is everything the app
|
|
475
|
+
needs.
|
|
476
|
+
|
|
477
|
+
### Catalog loader: looser column matching
|
|
478
|
+
|
|
479
|
+
- **`_norm()`** lowercases, strips bracketed/parenthesised unit
|
|
480
|
+
annotations (`[deg]`, `(deg)`), collapses non-alphanumerics, and
|
|
481
|
+
peels trailing unit/epoch tokens (`deg`, `degrees`, `rad`,
|
|
482
|
+
`arcsec`, `J2000`, `ICRS`, `FK5`). All of these now match RA:
|
|
483
|
+
`RA`, `ra`, `RA[deg]`, `RA(deg)`, `RA_deg`, `RAJ2000`,
|
|
484
|
+
`Right Ascension`, `ALPHA_J2000`, `R.A.[deg]`. Same for Dec
|
|
485
|
+
(including Vizier's `DEJ2000`).
|
|
486
|
+
- **ID resolution** accepts the usual aliases (`id`, `no`,
|
|
487
|
+
`source_id`, `objid`, `srcid`, …) plus permissive fallbacks
|
|
488
|
+
(`name`, `label`, `tag`, `target`, `#`) — fallbacks honoured only
|
|
489
|
+
when values coerce to integer.
|
|
490
|
+
- **Missing ID column → synthesised** sequential IDs 1..N so the
|
|
491
|
+
catalog still loads.
|
|
492
|
+
- **Numeric IDs ≥ 10⁷ are taken mod 10⁷** (`ID_MOD = 10_000_000`)
|
|
493
|
+
so JADES-style 8–9-digit IDs collapse to APT's compact space.
|
|
494
|
+
- **Priority class strings** (`P0`, `P1`, …) and masked numeric
|
|
495
|
+
cells now flow through cleanly — the old loader threw
|
|
496
|
+
`ValueError` on a `P0` priority cell and produced `0.0` for
|
|
497
|
+
masked `mag` / `z` instead of `NaN`.
|
|
498
|
+
|
|
499
|
+
### Multi-catalog
|
|
500
|
+
|
|
501
|
+
- **Load multiple catalogs at once**. Each gets a colour chip in
|
|
502
|
+
the sidebar list. Toggle visibility per-catalog with a checkbox;
|
|
503
|
+
× to remove; ▲ / ▼ to reorder the visual stack.
|
|
504
|
+
- **Per-catalog marker colours** cycle through an 8-entry palette
|
|
505
|
+
(yellow / magenta / pale green / coral / lavender / sky-blue /
|
|
506
|
+
white / salmon), picked to read clearly on dark fields and avoid
|
|
507
|
+
the other overlay colours.
|
|
508
|
+
- **Z-order by list order** (earlier-loaded catalogs draw on top)
|
|
509
|
+
with **alpha decay** by depth (1.0 → 0.35 floor). Matched-shutter
|
|
510
|
+
targets always render fully opaque so a "picked" marker is never
|
|
511
|
+
visually demoted.
|
|
512
|
+
- Sessions serialise the list (`catalog_paths`) — workspace JSON
|
|
513
|
+
remembers each path and its enabled flag.
|
|
514
|
+
|
|
515
|
+
### MPT-importable catalog (export)
|
|
516
|
+
|
|
517
|
+
- **Output is now a superset of the input catalog** — every input
|
|
518
|
+
source is included, plus any synthesised entries for slitlets
|
|
519
|
+
without a real match. The `Label` column carries `real` or
|
|
520
|
+
`vMPT_synth` so downstream tools can tell which is which.
|
|
521
|
+
- **Integer IDs only**, extracted as the largest digit run from
|
|
522
|
+
the original token (so `RJ0600-10274-P0` → 10274). The original
|
|
523
|
+
string token is preserved in the `Label` column for traceability.
|
|
524
|
+
|
|
525
|
+
### Overlay defaults
|
|
526
|
+
|
|
527
|
+
- Operable-shutter stroke: 0.75 px → **1.0 px**.
|
|
528
|
+
- Spectral-overlap fill alpha: 0.10 → **0.20**.
|
|
529
|
+
- Spectral-overlap edge colour now explicitly **orange (#d97a00)**
|
|
530
|
+
— when you reveal the edge via the stroke slider it now matches
|
|
531
|
+
the orange fill instead of Bokeh's default blue-grey.
|
|
532
|
+
|
|
533
|
+
### Tests
|
|
534
|
+
|
|
535
|
+
- 96 passing (was 63 at v1.0.0). Coverage growth concentrated on
|
|
536
|
+
the catalog loader (column aliases, ID synth, mod-10⁷, string
|
|
537
|
+
IDs, name-as-numeric-ID) and the wavelength model (every
|
|
538
|
+
disperser × filter combo verified against the msaviz table).
|
|
539
|
+
|
|
540
|
+
[1.0.1]: https://github.com/fengwusun/vMPT/releases/tag/v1.0.1
|
|
541
|
+
|
|
542
|
+
## [1.0.0] — 2026-05-20
|
|
543
|
+
|
|
544
|
+
First public release. The tool is feature-complete for hand-picking
|
|
545
|
+
JWST/NIRSpec MSA shutter configurations on a target field and
|
|
546
|
+
exporting a bundle that loads into APT MPT and the eMPT pipeline.
|
|
547
|
+
|
|
548
|
+
### Highlights
|
|
549
|
+
|
|
550
|
+
- **Interactive shutter picker** with N-shutter slitlets (N ∈ {1, 2, 3, 5}),
|
|
551
|
+
snap-to-nearest-operable, undo / clear, double-click highlights,
|
|
552
|
+
shift-click to move the pointing, wheel-zoom and pan.
|
|
553
|
+
- **Live overlays** — MSA outline, operable shutters (silver edge),
|
|
554
|
+
stuck-open (dark-red outline), user picks (red fill), spectral
|
|
555
|
+
conflicts (orange fill, stackable), 5 fixed slits (gold), catalog
|
|
556
|
+
targets (yellow / green when matched), lime pointing cross.
|
|
557
|
+
- **APT-ready bundle export** — 6 files per export, with role-prefixed
|
|
558
|
+
filenames (`MPT_*`, `vMPT_*`, `eMPT_*`). The MPT plan JSON matches
|
|
559
|
+
APT's reference schema field-for-field; the `<catalog>.cat` uses
|
|
560
|
+
JDox-recognized column names (ID, RA, DEC, Weight, Primary, Label).
|
|
561
|
+
Labels distinguish `real` catalog rows from `vMPT_synth` synthesised
|
|
562
|
+
entries.
|
|
563
|
+
- **APT plan importer** — load any `MPT_plan.json`, shutter mask CSV,
|
|
564
|
+
local `.aptx` archive, or fetch by JWST program ID directly from
|
|
565
|
+
STScI. Reads multi-plan archives (e.g. program 1208 with 40+ plans).
|
|
566
|
+
- **Bundle round-trip** — Save session → load session restores
|
|
567
|
+
pointing, V3 PA, disperser/filter, every open shutter with its
|
|
568
|
+
`target_id` + `role`, the highlighted set, and the image + sidecar
|
|
569
|
+
paths. Point at either `MPT_plan.json` OR `vMPT_workspace.json` —
|
|
570
|
+
the sibling auto-loads.
|
|
571
|
+
- **Responsive layout** — canvas stretches to fill the browser
|
|
572
|
+
window; sidebar / help panel scroll on overflow; left-sidebar
|
|
573
|
+
fixed at 340 px, right help panel at 340 px.
|
|
574
|
+
- **Rotating tip card** in the help panel (13 hand-written tips,
|
|
575
|
+
15-second rotation with CSS fade-in).
|
|
576
|
+
- **GitHub version-check on startup** — non-blocking background
|
|
577
|
+
thread compares the local HEAD to `origin/main`; shows a
|
|
578
|
+
dismissible amber notification if the local copy is behind.
|
|
579
|
+
- **Custom favicon** (4 MSA quadrants + lime pointing cross).
|
|
580
|
+
- **One-page summary slide** generator (`build_vmpt_slide.js`,
|
|
581
|
+
pptxgenjs-based).
|
|
582
|
+
|
|
583
|
+
### Science correctness
|
|
584
|
+
|
|
585
|
+
- **MSA geometry** sourced from `pysiaf` (`NRS_FULL_MSA`); 138.575°
|
|
586
|
+
intra-MSA rotation, V2/V3 reference at (378.563, −428.403).
|
|
587
|
+
- **APA = V3 PA + V3IdlYAngle (mod 360)**; both quantities are
|
|
588
|
+
surfaced in the status bar and editable from the Aim tab.
|
|
589
|
+
- **Operability** read from CRDS `jwst_nirspec_msaoper_*.json` —
|
|
590
|
+
failed-open shutters always disperse and contribute to the
|
|
591
|
+
spec-overlap calculation.
|
|
592
|
+
- **Spectral overlap** — `|Δs| ≤ 1` cross-quadrant via NRS1 (Q1↔Q3)
|
|
593
|
+
and NRS2 (Q2↔Q4) detector pairing; per-grating V2 half-extent
|
|
594
|
+
(PRISM 35″, M-gratings 200″, H-gratings 500″).
|
|
595
|
+
- **Wavelength endpoints** per disperser+filter, clamped to the
|
|
596
|
+
grating's intrinsic range (no spurious PRISM > 5.3 µm tooltips).
|
|
597
|
+
- **Source matching** uses APT's *Unconstrained* Source Centering
|
|
598
|
+
rule (full shutter pitch including bars).
|
|
599
|
+
- **WCS Jacobian** uses `astropy.SkyCoord.spherical_offsets_to` —
|
|
600
|
+
cos(Dec) factor handled correctly at non-equatorial fields.
|
|
601
|
+
|
|
602
|
+
### Example data shipped
|
|
603
|
+
|
|
604
|
+
- `example_a370/` (43 MB) — JWST NIRCam F182M+F200W+F210M FITS of
|
|
605
|
+
Abell 370, target catalog, GTO-1208 APT MPT plan, shutter-mask CSV.
|
|
606
|
+
- `example_r0600/` (21 MB) — JWST NIRCam F090W+F200W+F444W JPG of
|
|
607
|
+
RXCJ0600 + WCS sidecar + 28k-source target catalog. JPG re-encoded
|
|
608
|
+
at quality 85 (was 251 MB) without changing WCS.
|
|
609
|
+
|
|
610
|
+
### Tests
|
|
611
|
+
|
|
612
|
+
- 63 tests, ~5 s. Run with `pytest tests/`.
|
|
613
|
+
- Coverage: session bundle round-trip, MPT plan parser (incl. .aptx
|
|
614
|
+
archives), eMPT format byte-compatibility, MPT catalog writer
|
|
615
|
+
format guard, wavelength model, image loaders, end-to-end export.
|
|
616
|
+
|
|
617
|
+
### Known limitations
|
|
618
|
+
|
|
619
|
+
- `plannerSpecification` block in `MPT_plan.json` carries sensible
|
|
620
|
+
defaults (matching APT's reference schema) but its dither /
|
|
621
|
+
search-grid parameters don't reflect any vMPT internal state —
|
|
622
|
+
APT uses them only as starting values for re-planning.
|
|
623
|
+
- Bokeh single-session state: opening the same server in two
|
|
624
|
+
browser tabs lets picks bleed across them. Use one tab per user.
|
|
625
|
+
- Older `pysiaf` PRD (PRDOPSSOC-068) lags the online version by
|
|
626
|
+
~0.05″ for some apertures; safe to ignore unless you need
|
|
627
|
+
milli-arcsec geometry.
|
|
628
|
+
|
|
629
|
+
### Acknowledgements
|
|
630
|
+
|
|
631
|
+
Export-bundle format calibrated against [eMPT](https://github.com/esdc-esac-esa-int/eMPT_v1)
|
|
632
|
+
(Bonaventura et al. 2023, A&A 672, A40). Coordinate plumbing builds
|
|
633
|
+
on `pysiaf` (NIRSpec apertures) and `astropy.wcs`. Visibility
|
|
634
|
+
windows queried via [`jwst_gtvt`](https://github.com/spacetelescope/jwst_gtvt).
|
|
635
|
+
MPT catalog and plan JSON schemas follow the
|
|
636
|
+
[JDox MPT documentation](https://jwst-docs.stsci.edu/jwst-near-infrared-spectrograph/nirspec-apt-templates/nirspec-multi-object-spectroscopy-apt-template).
|
|
637
|
+
|
|
638
|
+
[1.0.0]: https://github.com/fengwusun/vMPT/releases/tag/v1.0.0
|