@@ -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+
11791259def 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