Skip to content

Commit 8593831

Browse files
committed
fix: regrid pulse modes to common depth before concat in echograms
1 parent 4e094fa commit 8593831

1 file changed

Lines changed: 84 additions & 1 deletion

File tree

scripts/batch_processing/build_full_survey.py

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,86 @@ def _plot_perday_echogram(
11761176
return out_path
11771177

11781178

1179+
def _regrid_to_common_depth(
1180+
datasets: list[xr.Dataset],
1181+
depth_step: float = 0.5,
1182+
) -> list[xr.Dataset]:
1183+
"""Regrid datasets with different range_sample grids onto a common 1D depth axis.
1184+
1185+
Each dataset may have a 3D depth variable (channel, ping_time, range_sample).
1186+
We compute a median depth profile per dataset, build a union depth grid,
1187+
then interpolate Sv onto that grid — replacing range_sample with depth.
1188+
"""
1189+
# 1. Determine global depth range from all datasets
1190+
max_depth = 0.0
1191+
for ds in datasets:
1192+
if "depth" in ds:
1193+
d = ds["depth"].values
1194+
elif "echo_range" in ds:
1195+
d = ds["echo_range"].values + TRANSDUCER_DEPTH
1196+
else:
1197+
continue
1198+
max_depth = max(max_depth, float(np.nanmax(d)))
1199+
1200+
common_depth = np.arange(0, max_depth + depth_step, depth_step)
1201+
1202+
# 2. Regrid each dataset
1203+
result: list[xr.Dataset] = []
1204+
for ds in datasets:
1205+
# Get 1D depth profile (median across pings, channel 0)
1206+
if "depth" in ds:
1207+
d = ds["depth"].values
1208+
elif "echo_range" in ds:
1209+
d = ds["echo_range"].values + TRANSDUCER_DEPTH
1210+
else:
1211+
result.append(ds)
1212+
continue
1213+
1214+
if d.ndim == 3:
1215+
depth_1d = np.nanmedian(d[0], axis=0)
1216+
elif d.ndim == 2:
1217+
depth_1d = d[0]
1218+
else:
1219+
depth_1d = d
1220+
1221+
# Interpolate Sv from native depth to common depth grid
1222+
sv = ds["Sv"].isel(channel=0).values # (ping_time, range_sample)
1223+
n_pings = sv.shape[0]
1224+
sv_regridded = np.full((n_pings, len(common_depth)), np.nan, dtype=np.float32)
1225+
for i in range(n_pings):
1226+
valid = ~np.isnan(sv[i]) & ~np.isnan(depth_1d)
1227+
if valid.any():
1228+
sv_regridded[i] = np.interp(
1229+
common_depth, depth_1d[valid], sv[i][valid],
1230+
left=np.nan, right=np.nan,
1231+
)
1232+
1233+
# Build new dataset with depth dimension
1234+
new_ds = xr.Dataset(
1235+
{
1236+
"Sv": xr.DataArray(
1237+
sv_regridded[np.newaxis, :, :], # (1, ping_time, depth)
1238+
dims=["channel", "ping_time", "depth"],
1239+
coords={
1240+
"channel": ds.channel.values[:1],
1241+
"ping_time": ds.ping_time.values,
1242+
"depth": common_depth,
1243+
},
1244+
),
1245+
},
1246+
)
1247+
# Carry over pulse_mode and other 1D vars
1248+
if "pulse_mode" in ds:
1249+
new_ds["pulse_mode"] = ds["pulse_mode"]
1250+
if "frequency_nominal" in ds.coords:
1251+
new_ds = new_ds.assign_coords(
1252+
frequency_nominal=("channel", ds.frequency_nominal.values[:1]),
1253+
)
1254+
result.append(new_ds)
1255+
1256+
return result
1257+
1258+
11791259
def generate_perday_echograms(
11801260
day_key: str,
11811261
concat_zarrs: dict[str, str],
@@ -1245,7 +1325,10 @@ def generate_perday_echograms(
12451325
if "pulse_mode" in combined:
12461326
combined = combined.drop_vars("pulse_mode")
12471327
else:
1248-
combined = xr.concat(datasets, dim="ping_time")
1328+
# Different pulse modes may have different range_sample grids.
1329+
# Regrid all to a common 1D depth axis before concatenation.
1330+
regridded = _regrid_to_common_depth(datasets)
1331+
combined = xr.concat(regridded, dim="ping_time")
12491332
combined = combined.sortby("ping_time")
12501333
# If all pings are the same mode, drop pulse_mode
12511334
if "pulse_mode" in combined:

0 commit comments

Comments
 (0)