Release v0.31.0 — report parity + the inverted rescue (0.29.0 → 0.31.0) #40
+62
-25
@@ -396,18 +396,25 @@ def _render_waveform_layout(fig, rd: ReportData) -> None:
|
|||||||
ax_stats = fig.add_subplot(gs[2]); ax_stats.axis("off")
|
ax_stats = fig.add_subplot(gs[2]); ax_stats.axis("off")
|
||||||
_draw_channel_stats_waveform(ax_stats, rd)
|
_draw_channel_stats_waveform(ax_stats, rd)
|
||||||
|
|
||||||
_draw_compliance_panel(fig, gs, rd)
|
_draw_compliance_panel(fig, rd)
|
||||||
_draw_waveform_subplot(fig, gs[3], rd)
|
_draw_waveform_subplot(fig, gs[3], rd)
|
||||||
|
|
||||||
|
|
||||||
def _draw_compliance_panel(fig, gs, rd: ReportData) -> None:
|
# Compliance-chart placement, in figure fractions. Measured directly off a
|
||||||
"""Large square USBM RI8507 compliance chart in the upper-right, spanning the
|
# Blastware Event Report PDF (ref-stuff/n844lqhbzt0w_bw_pdf.pdf) so the chart
|
||||||
mic (row 1) and stats (row 2) rows — matching Blastware's Event Report."""
|
# matches BW's size and position: it spans from just under the header down
|
||||||
bottoms, tops, _lefts, rights = gs.get_grid_positions(fig)
|
# through the stats band, hard against the right page margin. The left edge
|
||||||
y0, y1 = bottoms[2], tops[1] # bottom of stats row → top of mic row
|
# leaves room for the y-axis tick labels + "Velocity (in/s)" title, which the
|
||||||
x0, x1 = 0.64, rights[0] # clear of the stats columns → page right margin
|
# compacted stats table (see _draw_channel_stats_waveform) is sized to clear.
|
||||||
fig.text((x0 + x1) / 2, y1 + 0.004, "USBM RI8507", fontsize=9, weight="bold",
|
_COMPLIANCE_BOX = (0.489, 0.502, 0.951, 0.867) # x0, y0, x1, y1
|
||||||
color="#333", ha="center", va="bottom")
|
|
||||||
|
|
||||||
|
def _draw_compliance_panel(fig, rd: ReportData) -> None:
|
||||||
|
"""Large USBM RI8507 compliance chart in the upper-right, sized and
|
||||||
|
positioned to match Blastware's Event Report (see _COMPLIANCE_BOX)."""
|
||||||
|
x0, y0, x1, y1 = _COMPLIANCE_BOX
|
||||||
|
fig.text((x0 + x1) / 2, y1 + 0.006, "USBM RI8507 And OSMRE", fontsize=9,
|
||||||
|
weight="bold", color="#333", ha="center", va="bottom")
|
||||||
if rd.channels and rd.sample_rate_sps:
|
if rd.channels and rd.sample_rate_sps:
|
||||||
from sfm.compliance import draw_compliance_chart
|
from sfm.compliance import draw_compliance_chart
|
||||||
ax = fig.add_axes([x0, y0, x1 - x0, y1 - y0])
|
ax = fig.add_axes([x0, y0, x1 - x0, y1 - y0])
|
||||||
@@ -495,11 +502,11 @@ def _split_iso_to_date_time(iso: Optional[str]) -> tuple[Optional[str], Optional
|
|||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
|
|
||||||
def _kv(ax, x, y, label, value, *, label_w=0.18):
|
def _kv(ax, x, y, label, value, *, label_w=0.18, fontsize=8):
|
||||||
"""Render a 'Label Value' row at axes-coordinates (x, y)."""
|
"""Render a 'Label Value' row at axes-coordinates (x, y)."""
|
||||||
ax.text(x, y, label, fontsize=8, color="#555", ha="left", va="top",
|
ax.text(x, y, label, fontsize=fontsize, color="#555", ha="left", va="top",
|
||||||
transform=ax.transAxes)
|
transform=ax.transAxes)
|
||||||
ax.text(x + label_w, y, _fmt(value), fontsize=8, ha="left", va="top",
|
ax.text(x + label_w, y, _fmt(value), fontsize=fontsize, ha="left", va="top",
|
||||||
transform=ax.transAxes, family="monospace")
|
transform=ax.transAxes, family="monospace")
|
||||||
|
|
||||||
|
|
||||||
@@ -592,8 +599,11 @@ def _draw_mic_and_usbm(ax, rd: ReportData) -> None:
|
|||||||
transform=ax.transAxes, va="top")
|
transform=ax.transAxes, va="top")
|
||||||
rows = _mic_rows(rd)
|
rows = _mic_rows(rd)
|
||||||
y = 0.80
|
y = 0.80
|
||||||
|
# Tighter label indent + slightly smaller font so the long "Channel Test
|
||||||
|
# Passed (Freq = … Amp = … mv)" line clears the enlarged compliance chart's
|
||||||
|
# left edge (_COMPLIANCE_BOX) instead of running behind it.
|
||||||
for label, value in rows:
|
for label, value in rows:
|
||||||
_kv(ax, 0.0, y, label, value, label_w=0.18)
|
_kv(ax, 0.0, y, label, value, label_w=0.13, fontsize=7)
|
||||||
y -= 0.15
|
y -= 0.15
|
||||||
# The USBM compliance chart is drawn as its own large square panel spanning
|
# The USBM compliance chart is drawn as its own large square panel spanning
|
||||||
# the mic + stats rows on the right — see _draw_compliance_panel().
|
# the mic + stats rows on the right — see _draw_compliance_panel().
|
||||||
@@ -647,7 +657,13 @@ def _draw_channel_stats_waveform(ax, rd: ReportData) -> None:
|
|||||||
("Peak Displacement", "peak_disp_in", "in"),
|
("Peak Displacement", "peak_disp_in", "in"),
|
||||||
("Sensor Check", "sensor_check", ""),
|
("Sensor Check", "sensor_check", ""),
|
||||||
]
|
]
|
||||||
_draw_stats_table(ax, rd, rows_spec)
|
# Compacted to the left half so the enlarged compliance chart (BW-sized,
|
||||||
|
# right against the page margin) has room — see _COMPLIANCE_BOX.
|
||||||
|
_draw_stats_table(
|
||||||
|
ax, rd, rows_spec,
|
||||||
|
bbox_width=0.42, fontsize=7.5,
|
||||||
|
col_widths=[0.185, 0.065, 0.065, 0.065, 0.040],
|
||||||
|
)
|
||||||
_draw_pvs_summary(ax, rd, n_data_rows=len(rows_spec))
|
_draw_pvs_summary(ax, rd, n_data_rows=len(rows_spec))
|
||||||
|
|
||||||
|
|
||||||
@@ -708,19 +724,39 @@ def _draw_pvs_summary(
|
|||||||
table_bottom_y = getattr(ax, "_stats_table_bottom", -0.10)
|
table_bottom_y = getattr(ax, "_stats_table_bottom", -0.10)
|
||||||
pvs_y = table_bottom_y - 0.04 # small gap below the table border
|
pvs_y = table_bottom_y - 0.04 # small gap below the table border
|
||||||
|
|
||||||
# Centered for visual balance — looks intentional rather than offset.
|
# Centered under the stats table for visual balance — looks intentional
|
||||||
# The original BW-replica had a "NA: Not Applicable" caption below
|
# rather than offset. When the table is compacted (waveform layout), it
|
||||||
# this line; dropped because we use "—" for missing values and the
|
# occupies only the left portion of the axes, so center on the table's
|
||||||
# legend was always squished against the PVS line.
|
# width rather than the full axes (which would push the line under the
|
||||||
ax.text(0.5, pvs_y, line, fontsize=9, weight="bold",
|
# compliance chart). The original BW-replica had a "NA: Not Applicable"
|
||||||
ha="center", va="top", transform=ax.transAxes)
|
# caption below this line; dropped because we use "—" for missing values.
|
||||||
|
table_w = getattr(ax, "_stats_table_width", 0.80)
|
||||||
|
if table_w < 0.79:
|
||||||
|
# Compacted (waveform) layout: left-align under the table, one point
|
||||||
|
# smaller, so the line clears the enlarged compliance chart's
|
||||||
|
# bottom-left tick labels on the right.
|
||||||
|
ax.text(0.0, pvs_y, line, fontsize=8, weight="bold",
|
||||||
|
ha="left", va="top", transform=ax.transAxes)
|
||||||
|
else:
|
||||||
|
ax.text(0.5, pvs_y, line, fontsize=9, weight="bold",
|
||||||
|
ha="center", va="top", transform=ax.transAxes)
|
||||||
|
|
||||||
|
|
||||||
def _draw_stats_table(ax, rd: ReportData, rows_spec: list[tuple[str, str, str]]) -> None:
|
def _draw_stats_table(
|
||||||
|
ax, rd: ReportData, rows_spec: list[tuple[str, str, str]],
|
||||||
|
*, bbox_width: float = 0.80, fontsize: float = 8,
|
||||||
|
col_widths: Optional[list[float]] = None,
|
||||||
|
) -> None:
|
||||||
"""Render a per-channel stats table (Tran/Vert/Long).
|
"""Render a per-channel stats table (Tran/Vert/Long).
|
||||||
|
|
||||||
rows_spec: list of (label, field_name_in_channel_stats, unit_string)
|
rows_spec: list of (label, field_name_in_channel_stats, unit_string)
|
||||||
|
|
||||||
|
``bbox_width`` / ``col_widths`` / ``fontsize`` let a caller compact the
|
||||||
|
table (the waveform layout packs it into the left half to clear the
|
||||||
|
compliance chart; the histogram layout keeps the wider defaults).
|
||||||
"""
|
"""
|
||||||
|
if col_widths is None:
|
||||||
|
col_widths = [0.28, 0.14, 0.14, 0.14, 0.10]
|
||||||
headers = ["", "Tran", "Vert", "Long", ""]
|
headers = ["", "Tran", "Vert", "Long", ""]
|
||||||
ch_lookup = {c["name"]: c for c in rd.channel_stats}
|
ch_lookup = {c["name"]: c for c in rd.channel_stats}
|
||||||
|
|
||||||
@@ -760,16 +796,17 @@ def _draw_stats_table(ax, rd: ReportData, rows_spec: list[tuple[str, str, str]])
|
|||||||
table_bottom = 1.0 - table_height
|
table_bottom = 1.0 - table_height
|
||||||
tbl = ax.table(
|
tbl = ax.table(
|
||||||
cellText=table_data,
|
cellText=table_data,
|
||||||
colWidths=[0.28, 0.14, 0.14, 0.14, 0.10],
|
colWidths=col_widths,
|
||||||
cellLoc="left", edges="open",
|
cellLoc="left", edges="open",
|
||||||
bbox=[0.0, table_bottom, 0.80, table_height],
|
bbox=[0.0, table_bottom, bbox_width, table_height],
|
||||||
)
|
)
|
||||||
tbl.auto_set_font_size(False)
|
tbl.auto_set_font_size(False)
|
||||||
tbl.set_fontsize(8)
|
tbl.set_fontsize(fontsize)
|
||||||
for j in range(5):
|
for j in range(5):
|
||||||
tbl[(0, j)].set_text_props(weight="bold", color="#555")
|
tbl[(0, j)].set_text_props(weight="bold", color="#555")
|
||||||
# Stash the bottom Y so _draw_pvs_summary can position itself below.
|
# Stash the bottom Y + width so _draw_pvs_summary can position itself.
|
||||||
ax._stats_table_bottom = table_bottom
|
ax._stats_table_bottom = table_bottom
|
||||||
|
ax._stats_table_width = bbox_width
|
||||||
|
|
||||||
|
|
||||||
def _channel_axis_color(ch: str) -> str:
|
def _channel_axis_color(ch: str) -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user