Skip to content

Commit 8deae44

Browse files
authored
Merge pull request #61 from MMTObservatory/periodicity-plot-contrast
Readable periodicity plot; report focus-only focus error
2 parents 49684b1 + 052279a commit 8deae44

4 files changed

Lines changed: 40 additions & 7 deletions

File tree

‎mmtwfs/period.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,18 +227,25 @@ def plot_periodicity(meas):
227227
keep = np.abs(f) <= lim
228228
fig, ax = plt.subplots()
229229
fig.set_label("Grid Periodicity")
230+
logp = np.log10(meas["power"][np.ix_(keep, keep)] + np.finfo(float).tiny)
231+
# stretch from the noise floor to the strongest peak. the full range reaches down to near-empty bins many decades
232+
# below the noise and leaves everything that matters a flat, dark grey.
230233
ax.imshow(
231-
np.log10(meas["power"][np.ix_(keep, keep)] + np.finfo(float).tiny),
234+
logp,
232235
origin="lower",
233236
cmap="Greys",
237+
vmin=np.median(logp),
238+
vmax=logp.max(),
234239
extent=(f[keep][0], f[keep][-1], f[keep][0], f[keep][-1]),
235240
)
236241
t = np.linspace(0, 2 * np.pi, 361)
237242
for rad in (fmin, fmax):
238243
ax.plot(rad * np.cos(t), rad * np.sin(t), color="blue", lw=0.8)
239244
for (fx, fy), snr, sp in zip(meas["freqs"], meas["snr"], meas["spacing"]):
240245
ax.scatter([fx, -fx], [fy, -fy], facecolors="none", edgecolors="red", s=80)
241-
ax.annotate(f"{sp:.3f} px (SNR {snr:.0f})", (fx, fy), color="red", fontsize=8)
246+
ax.annotate(
247+
f"{sp:.3f} px (SNR {snr:.0f})", (fx, fy), xytext=(8, 8), textcoords="offset points", color="red", fontsize=8
248+
)
242249
ax.set_xlabel("f$_x$ (cycles/pixel)")
243250
ax.set_ylabel("f$_y$ (cycles/pixel)")
244251
ax.set_title("Grid period (focus-only fallback)")

‎mmtwfs/tests/test_period.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ def test_plot_periodicity():
108108
m = measure_grid_period(img, CENTER, RADIUS, PITCH, inner=INNER)
109109
fig = plot_periodicity(m)
110110
assert fig.get_label() == "Grid Periodicity"
111+
# the stretch runs from the noise floor (median) to the strongest peak, not from the near-empty minimum, which
112+
# leaves the whole spectrum a dark, flat grey
113+
im = fig.axes[0].images[0]
114+
shown = np.asarray(im.get_array())
115+
assert im.get_clim() == pytest.approx((np.median(shown), shown.max()))
111116
plt.close("all")
112117

113118

‎mmtwfs/tests/test_wfs.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,10 @@ def test_frosted_donut():
228228
assert results["method"] == "periodicity"
229229
assert abs(results["grid"]["scale"] - 0.899) < 5e-3
230230
assert -40.0 < results["pending_focus"].to_value(u.um) < -24.0
231+
# the Z04 error carried through the same gains as the correction
232+
z04_err = results["zernike"].errorbars["Z04"]
233+
expected = wfs.m2_gain_periodicity * wfs.m2_gain * z04_err / wfs.secondary.focus_trans
234+
assert results["focus_err"].to_value(u.um) == pytest.approx(expected.to_value(u.um), abs=0.01)
231235
plt.close("all")
232236

233237

@@ -524,6 +528,8 @@ def test_focus_from_scale_matches_analytic():
524528
expected = k * 0.01 - zref["Z04"].value
525529
assert np.isclose(zv["Z04"].value, expected, rtol=0.02)
526530
assert np.isclose(zv.errorbars["Z04"].value, abs(k) * 1e-4, rtol=0.02)
531+
# only defocus is measured; the reference's other terms would show up as if they had been
532+
assert list(zv.coeffs) == ["Z04"]
527533

528534

529535
def test_focus_from_scale_gain():

‎mmtwfs/wfs.py‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,9 @@ def focus_from_scale(self, scale, scale_err, mode, rotator, hdr=None):
13171317

13181318
raw.rotate(angle=-(self.rotation - rotator))
13191319
zv = raw - self.reference_aberrations(mode, hdr=hdr)
1320+
# only defocus is measured. the subtraction leaves the reference's other terms (sign-flipped) and ~0 tilts,
1321+
# which would read as measured aberrations, so keep Z04 alone.
1322+
zv = ZernikeVector(Z04=zv["Z04"].value, errorbars={"Z04": zv.errorbars["Z04"].value}, units=zv.units)
13201323

13211324
focus = self.m2_gain_periodicity * self.calculate_focus(zv.copy())
13221325
fmax = self.periodicity_focus_max.to_value(u.um)
@@ -1349,8 +1352,18 @@ def periodicity_focus(self, data, mode, center, rotator, hdr=None, plot=True):
13491352
grid["center"] = tuple(center)
13501353

13511354
zv, focus = self.focus_from_scale(grid["scale"], grid["scale_err"], mode, rotator, hdr=hdr)
1355+
# the Z04 error bar through the same gains as the correction, before the (1 - frac_error) down-weighting
1356+
zd = zv.copy()
1357+
zd.denormalize()
1358+
focus_err = np.abs(self.m2_gain_periodicity * self.m2_gain * zd.errorbars["Z04"] / self.secondary.focus_trans)
13521359
fig = plot_periodicity(meas) if plot else None
1353-
return {"grid": grid, "zernike": zv, "pending_focus": focus, "figure": fig}
1360+
return {
1361+
"grid": grid,
1362+
"zernike": zv,
1363+
"pending_focus": focus,
1364+
"focus_err": focus_err.to(u.um).round(2),
1365+
"figure": fig,
1366+
}
13541367

13551368
def process_image(self, fitsfile):
13561369
"""
@@ -1505,15 +1518,17 @@ def measure_slopes(
15051518
fallback = None
15061519
if fallback is not None:
15071520
grid = fallback["grid"]
1508-
log.warning(
1509-
f"Using focus-only periodicity fallback: scale = {grid['scale']:.5f} +/- "
1510-
f"{grid['scale_err']:.5f}, SNR = {grid['snr'].min():.0f}, focus = {fallback['pending_focus']}"
1511-
)
1521+
# one value per line so the focus correction isn't lost off the right edge of a log window
1522+
log.warning("Using focus-only periodicity fallback:")
1523+
log.warning(f" grid scale = {grid['scale']:.5f} +/- {grid['scale_err']:.5f}")
1524+
log.warning(f" SNR = {grid['snr'].min():.0f}")
1525+
log.warning(f" focus = {fallback['pending_focus']} +/- {fallback['focus_err']}")
15121526
results["focus_only"] = True
15131527
results["method"] = "periodicity"
15141528
results["grid"] = grid
15151529
results["zernike"] = fallback["zernike"]
15161530
results["pending_focus"] = fallback["pending_focus"]
1531+
results["focus_err"] = fallback["focus_err"]
15171532
results["figures"]["periodicity"] = fallback["figure"]
15181533
return results
15191534
except Exception as e:

0 commit comments

Comments
 (0)