156156FREQ_200KHZ : float = 200000.0
157157MAX_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.
161169FREQUENCY_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
14731508def _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 ()
0 commit comments