Release v0.31.0 — report parity + the inverted rescue (0.29.0 → 0.31.0) #40

Merged
serversdown merged 31 commits from dev into main 2026-09-18 16:46:45 -04:00
2 changed files with 80 additions and 10 deletions
Showing only changes of commit 91b9b4578c - Show all commits
+19 -10
View File
@@ -777,6 +777,19 @@ def _draw_waveform_subplot(fig, gridspec_cell, rd: ReportData) -> None:
dt_s = (rd.dt_ms or (1000.0 / sr)) / 1000.0
t0_s = (rd.t0_ms if rd.t0_ms is not None else 0.0) / 1000.0
# Shared geo scale across Long/Vert/Tran (matches the event modal + BW's
# single amp/div): all three geo lanes use ONE Y scale = the max |sample|
# across them (padded, floored), so relative amplitudes stay honest instead
# of each lane auto-zooming to its own peak. Mic keeps its own (psi) scale.
GEO_FLOOR_INS = 0.05
_geo_amax = 0.0
for _gch in ("Long", "Vert", "Tran"):
for _x in (rd.channels.get(_gch) or []):
_a = abs(_x)
if _a > _geo_amax:
_geo_amax = _a
geo_shared = max(_geo_amax * 1.10, GEO_FLOOR_INS)
last_idx = len(order) - 1
for i, ch in enumerate(order):
ax = fig.add_subplot(inner[i])
@@ -786,10 +799,10 @@ def _draw_waveform_subplot(fig, gridspec_cell, rd: ReportData) -> None:
if values:
color = _channel_axis_color(ch)
ax.plot(times, values, color=color, linewidth=0.5)
# Symmetric y-axis for geo; zero-anchored for mic.
# Geo: one shared symmetric scale (honest relative amplitudes).
# Mic: symmetric on its own psi scale (different unit).
if ch != "MicL":
amax = max((abs(v) for v in values), default=0.001)
ax.set_ylim(-amax * 1.10, amax * 1.10)
ax.set_ylim(-geo_shared, geo_shared)
else:
amax = max((abs(v) for v in values), default=0.001)
ax.set_ylim(-amax * 1.10, amax * 1.10)
@@ -824,13 +837,9 @@ def _draw_waveform_subplot(fig, gridspec_cell, rd: ReportData) -> None:
# and find peak geo amplitude for the geo amp/div setting.
total_s = times[-1] - times[0] if values else 0
div_s = total_s / 10 if total_s > 0 else 0
geo_amp_div = "—"
for ch in ("Tran", "Vert", "Long"):
v = rd.channels.get(ch) or []
if v:
amax = max(abs(x) for x in v)
geo_amp_div = f"{(amax * 1.1 * 2) / 10:.3f}"
break
# Footer div value reflects the SHARED geo scale (so it's correct for all
# three lanes, not just whichever one happened to be checked first).
geo_amp_div = f"{(geo_shared * 2) / 10:.3f}" if _geo_amax > 0 else "—"
fig.text(
0.11, 0.030,
f"Time(Seconds) {div_s:.2f} sec/div Amplitude Geo: {geo_amp_div} in/s/div Mic: 0.001 psi(L)/div",
+61
View File
@@ -0,0 +1,61 @@
"""The event-report PDF must draw the three geo channels on ONE shared Y scale
(max |sample| across Long/Vert/Tran, floored), not each trace auto-zoomed to its
own peak — so relative amplitudes are honest and a small channel doesn't fill its
lane looking as big as a large one. Mirrors the event-modal waveform behaviour.
"""
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import pytest
from sfm.report_pdf import ReportData, _draw_waveform_subplot
def _draw(channels):
rd = ReportData(
channels=channels,
sample_rate_sps=1024,
dt_ms=1000.0 / 1024,
t0_ms=0.0,
)
fig = plt.figure()
cell = fig.add_gridspec(1, 1)[0, 0]
_draw_waveform_subplot(fig, cell, rd)
by_label = {ax.get_ylabel(): ax for ax in fig.axes}
try:
yield_ = {k: by_label[k].get_ylim() for k in ("Long", "Vert", "Tran", "MicL")}
finally:
plt.close(fig)
return yield_
def test_geo_traces_share_one_y_scale():
# Tran is the biggest geo channel (0.35); Long 0.10, Vert 0.02.
ylims = _draw({
"Long": [0.10, -0.10, 0.0],
"Vert": [0.02, -0.02, 0.0],
"Tran": [0.35, -0.35, 0.0],
"MicL": [0.0005, -0.0005, 0.0],
})
# Shared scale = max(0.35 * 1.10, floor 0.05) = 0.385, symmetric.
expected = pytest.approx(0.385, rel=1e-6)
for ch in ("Long", "Vert", "Tran"):
lo, hi = ylims[ch]
assert hi == expected, f"{ch} top ylim {hi} != shared 0.385"
assert lo == pytest.approx(-0.385, rel=1e-6), f"{ch} bottom ylim {lo}"
# All three geo lanes identical.
assert ylims["Long"] == ylims["Vert"] == ylims["Tran"]
# Mic keeps its own (much smaller) scale — not lumped into the geo max.
assert ylims["MicL"][1] < 0.01
def test_geo_shared_scale_has_floor():
# A tiny event (all geo well under the floor) clamps to the 0.05 floor.
ylims = _draw({
"Long": [0.008, -0.008, 0.0],
"Vert": [0.006, -0.006, 0.0],
"Tran": [0.010, -0.010, 0.0],
"MicL": [0.0001, -0.0001, 0.0],
})
for ch in ("Long", "Vert", "Tran"):
assert ylims[ch][1] == pytest.approx(0.05, rel=1e-6), f"{ch} not floored"