Skip to content

Commit 35c4699

Browse files
committed
echograms: freq-specific depth limits, skip near-empty, clean titles
- Add FREQ_MAX_DEPTH: 200kHz capped at 300m, 38kHz at 1200m - Skip echograms with <500 pings or <20% valid data - Single pulse mode: drop pulse_mode bar, plot data directly - Remove pings/colormap second line from all echogram titles
1 parent b4ab9c2 commit 35c4699

3 files changed

Lines changed: 51 additions & 16 deletions

File tree

scripts/batch_processing/build_full_survey.py

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,14 @@
156156
FREQ_200KHZ: float = 200000.0
157157
MAX_PLOT_DEPTH: float = 1200.0
158158

159+
# Per-frequency max echogram depth (metres) and minimum pings to plot.
160+
# 200 kHz physically can't penetrate beyond ~250 m; showing deeper is just noise.
161+
FREQ_MAX_DEPTH: dict[float, float] = {
162+
FREQ_38KHZ: 1200.0,
163+
FREQ_200KHZ: 300.0,
164+
}
165+
MIN_PINGS_FOR_ECHOGRAM: int = 500 # skip near-empty echograms
166+
159167
# Frequency configs: (freq_hz, label, zarr_stem, categories_to_include)
160168
# 200 kHz is only present in short_pulse mode.
161169
FREQUENCY_CONFIGS: list[tuple[float, str, str, list[str] | None]] = [
@@ -1068,6 +1076,7 @@ def _plot_perday_echogram(
10681076
cmap_name: str,
10691077
cmap: str | mcolors.Colormap,
10701078
output_dir: Path,
1079+
freq_hz: float = 0.0,
10711080
) -> Path | None:
10721081
"""Render a single per-day echogram for one data_type and frequency."""
10731082
da = ds["Sv"]
@@ -1083,9 +1092,10 @@ def _plot_perday_echogram(
10831092
if range_var == "echo_range":
10841093
depth_vals = depth_vals + TRANSDUCER_DEPTH
10851094

1095+
freq_max = FREQ_MAX_DEPTH.get(freq_hz, MAX_PLOT_DEPTH)
10861096
has_data = (~np.isnan(sv_raw)).any(axis=0)
10871097
last_valid = int(np.where(has_data)[0][-1]) if has_data.any() else 0
1088-
max_depth = min(MAX_PLOT_DEPTH, depth_vals[last_valid] + 10)
1098+
max_depth = min(freq_max, depth_vals[last_valid] + 10)
10891099
depth_mask = depth_vals <= max_depth
10901100
depth_plot = depth_vals[depth_mask]
10911101
sv_data = sv_raw[:, depth_mask]
@@ -1129,8 +1139,7 @@ def _plot_perday_echogram(
11291139
ax.invert_yaxis()
11301140
ax.set_ylabel("Depth (m)", fontsize=11)
11311141
ax.set_title(
1132-
f"{day_key} \u2014 {data_type} {freq_label} (combined)\n"
1133-
f"Colormap: {cmap_name} | {n_pings} pings",
1142+
f"{day_key} \u2014 {data_type} {freq_label}",
11341143
fontsize=12, fontweight="bold",
11351144
)
11361145

@@ -1225,17 +1234,43 @@ def generate_perday_echograms(
12251234
if not datasets:
12261235
continue
12271236

1228-
combined = xr.concat(datasets, dim="ping_time")
1229-
combined = combined.sortby("ping_time")
1237+
if len(datasets) == 1:
1238+
combined = datasets[0]
1239+
# Single pulse mode — drop pulse_mode so no pulse bar is drawn
1240+
if "pulse_mode" in combined:
1241+
combined = combined.drop_vars("pulse_mode")
1242+
else:
1243+
combined = xr.concat(datasets, dim="ping_time")
1244+
combined = combined.sortby("ping_time")
1245+
# If all pings are the same mode, drop pulse_mode
1246+
if "pulse_mode" in combined:
1247+
modes = np.unique(combined["pulse_mode"].values)
1248+
if len(modes) == 1:
1249+
combined = combined.drop_vars("pulse_mode")
12301250

12311251
for ds in datasets:
12321252
ds.close()
12331253
del datasets
12341254

1255+
# Skip near-empty echograms (e.g. 200 kHz on a mostly-long_pulse day).
1256+
# Check: if non-NaN data covers less than 20% of pings, skip.
1257+
n_combined = combined.sizes.get("ping_time", 0)
1258+
if n_combined > 0:
1259+
sv_vals = combined["Sv"].isel(channel=0).values
1260+
frac_valid = float((~np.isnan(sv_vals)).any(axis=1).sum()) / n_combined
1261+
else:
1262+
frac_valid = 0.0
1263+
if n_combined < MIN_PINGS_FOR_ECHOGRAM or frac_valid < 0.2:
1264+
log.info(" Skip %s/%s @ %s: %d pings, %.0f%% valid",
1265+
day_key, data_type, freq_label, n_combined, frac_valid * 100)
1266+
combined.close()
1267+
del combined
1268+
continue
1269+
12351270
for cmap_name, cmap_val in COLORMAPS[:1]: # Use first colormap for per-day
12361271
p = _plot_perday_echogram(
12371272
combined, day_key, data_type, freq_label, freq_stem,
1238-
cmap_name, cmap_val, echogram_dir,
1273+
cmap_name, cmap_val, echogram_dir, freq_hz=freq_hz,
12391274
)
12401275
if p:
12411276
all_files.append(p)
@@ -1472,6 +1507,7 @@ def _build_hourly_ticks(
14721507

14731508
def _prepare_echogram_data(
14741509
ds: xr.Dataset,
1510+
freq_hz: float = 0.0,
14751511
) -> tuple[np.ndarray, np.ndarray, np.ndarray, float, np.ndarray | None]:
14761512
da = ds["Sv"].isel(channel=0)
14771513
ping_time = da.ping_time.values
@@ -1482,9 +1518,10 @@ def _prepare_echogram_data(
14821518
if range_var == "echo_range":
14831519
depth_vals = depth_vals + TRANSDUCER_DEPTH
14841520

1521+
freq_max = FREQ_MAX_DEPTH.get(freq_hz, MAX_PLOT_DEPTH)
14851522
has_data = (~np.isnan(sv_raw)).any(axis=0)
14861523
last_valid = int(np.where(has_data)[0][-1]) if has_data.any() else 0
1487-
max_depth = min(MAX_PLOT_DEPTH, depth_vals[last_valid] + 10)
1524+
max_depth = min(freq_max, depth_vals[last_valid] + 10)
14881525
depth_mask = depth_vals <= max_depth
14891526
depth_plot = depth_vals[depth_mask]
14901527
sv_data = sv_raw[:, depth_mask]
@@ -1552,8 +1589,9 @@ def plot_combined_echogram(
15521589
freq_label: str = "38 kHz",
15531590
freq_stem: str = "38kHz",
15541591
segment_label: str | None = None,
1592+
freq_hz: float = 0.0,
15551593
) -> Path:
1556-
sv_data, depth_plot, ping_time, _max_depth, pulse_mode = _prepare_echogram_data(ds)
1594+
sv_data, depth_plot, ping_time, _max_depth, pulse_mode = _prepare_echogram_data(ds, freq_hz=freq_hz)
15571595

15581596
n_pings = len(ping_time)
15591597
log.info(" %d pings", n_pings)
@@ -1591,9 +1629,7 @@ def plot_combined_echogram(
15911629
ax.invert_yaxis()
15921630
ax.set_ylabel("Depth (m)", fontsize=14)
15931631
ax.set_title(
1594-
f"Campaign MVBS — Combined {freq_label} ({pulse_desc}){segment_title}\n"
1595-
f"Colormap: {cmap_name} | {t0} to {t1} "
1596-
f"({n_pings} pings) | transducer depth: {TRANSDUCER_DEPTH}m",
1632+
f"Campaign MVBS — Combined {freq_label} ({pulse_desc}){segment_title}",
15971633
fontsize=16, fontweight="bold",
15981634
)
15991635

@@ -2871,7 +2907,7 @@ def _collect_day_result(res: dict) -> None:
28712907
plot_combined_echogram(
28722908
seg_ds, cmap_name, cmap,
28732909
freq_label=freq_label, freq_stem=freq_stem,
2874-
segment_label=seg_label,
2910+
segment_label=seg_label, freq_hz=freq_hz,
28752911
)
28762912

28772913
log.info("Stage 10 (%s) complete (%.1fs)", freq_label, time.time() - t0)
@@ -2973,6 +3009,7 @@ def run_echogram_only(args: argparse.Namespace) -> None:
29733009
plot_combined_echogram(
29743010
campaign_ds, cmap_name, cmap,
29753011
freq_label=freq_label, freq_stem=freq_stem,
3012+
freq_hz=freq_hz,
29763013
)
29773014

29783015
campaign_ds.close()

scripts/batch_processing/campaign_echograms.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,7 @@ def plot_campaign_echogram(
122122
ax.set_ylabel("Depth (m)", fontsize=14)
123123
ax.set_xlabel("Date", fontsize=14)
124124
ax.set_title(
125-
f"Campaign MVBS — {category} | {freq_label}\n"
126-
f"Colormap: {cmap_name} | {str(ping_time[0])[:10]} to {str(ping_time[-1])[:10]}",
125+
f"Campaign MVBS — {category} | {freq_label}",
127126
fontsize=16,
128127
fontweight="bold",
129128
)

scripts/batch_processing/run_combine_daily.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -777,8 +777,7 @@ def render_echogram(
777777

778778
product_label = {"sv": "Sv", "denoised": "Denoised Sv", "mvbs": "MVBS", "nasc": "NASC"}
779779
ax.set_title(
780-
f"{day}{product_label.get(product, product)} {freq_label} (combined)\n"
781-
f"{n_valid} pings | {cmap_name}",
780+
f"{day}{product_label.get(product, product)} {freq_label}",
782781
fontsize=12, fontweight="bold",
783782
)
784783

0 commit comments

Comments
 (0)