Skip to content

Commit b4ab9c2

Browse files
committed
feat: frequency-specific denoising params + 200kHz BGN -115dB
- denoise_day_zarr uses DenoiseConfig(use_frequency_specific=True) - BGN per-channel from FREQUENCY_PRESETS (200kHz: -115dB, 38kHz: -125dB) - Fix attenuation_threshold default 0.8 -> 6.0 dB - Add --skip-sv flag and list_sv_zarrs discovery
1 parent 184ea44 commit b4ab9c2

2 files changed

Lines changed: 50 additions & 12 deletions

File tree

oceanstream/echodata/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
"range_window": 15,
106106
"ping_window": 40,
107107
"SNR_threshold": "3.0dB",
108-
"background_noise_max": "-110.0dB",
108+
"background_noise_max": "-115.0dB",
109109
},
110110
"transient": {
111111
"exclude_above": 100.0,

scripts/batch_processing/build_full_survey.py

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,8 @@ def denoise_day_zarr(
648648
) -> str:
649649
"""Apply 4-stage denoising to a day zarr.
650650
651-
Pattern from process_campaign.py:denoise_day().
651+
Uses frequency-specific parameters from FREQUENCY_PRESETS so that
652+
38 kHz and 200 kHz channels get appropriately tuned thresholds.
652653
653654
The transient noise mask uses ``dask.array.map_overlap`` which
654655
re-chunks the data even after ``.load()``. Using the threaded
@@ -658,7 +659,7 @@ def denoise_day_zarr(
658659
"""
659660
import dask
660661
from oceanstream.echodata.denoise import apply_denoising
661-
from oceanstream.echodata.config import DenoiseConfig
662+
from oceanstream.echodata.config import DenoiseConfig, FREQUENCY_PRESETS
662663

663664
log.info("Denoising %s/%s", day_key, category)
664665

@@ -674,7 +675,10 @@ def denoise_day_zarr(
674675
# dask.array.map_overlap (used by transient_noise_mask) can
675676
# parallelise the kernel across CPU cores.
676677
with dask.config.set(scheduler=dask_scheduler):
677-
denoise_config = DenoiseConfig(methods=DENOISE_METHODS)
678+
denoise_config = DenoiseConfig(
679+
methods=DENOISE_METHODS,
680+
use_frequency_specific=True,
681+
)
678682

679683
# Step 1: Mask-based denoising (impulse, transient, attenuation)
680684
mask_methods = [m for m in DENOISE_METHODS if m != "background"]
@@ -686,28 +690,62 @@ def denoise_day_zarr(
686690
ds_denoised = ds
687691

688692
# Step 2: echopype background noise removal per channel
693+
# Uses frequency-specific params from FREQUENCY_PRESETS.
689694
if "background" in DENOISE_METHODS:
690695
from echopype.clean import remove_background_noise as ep_remove_bgn
691696

692697
parent_attrs = dict(ds_denoised.attrs)
693698
parent_attrs.setdefault("processing_level", "Level 2A")
694699
parent_attrs["input_processing_level"] = parent_attrs["processing_level"]
695700

701+
# Pre-compute per-channel BGN params
702+
bgn_params_by_ch: dict[int, dict] = {}
703+
for ch_idx in range(ds_denoised.sizes.get("channel", 1)):
704+
freq = float(
705+
ds_denoised["frequency_nominal"].isel(channel=ch_idx).compute().item()
706+
)
707+
freq_int = int(round(freq))
708+
if freq_int in FREQUENCY_PRESETS and "background" in FREQUENCY_PRESETS[freq_int]:
709+
fp = FREQUENCY_PRESETS[freq_int]["background"]
710+
else:
711+
fp = FREQUENCY_PRESETS[38000]["background"] # fallback
712+
bgn_params_by_ch[ch_idx] = {
713+
"ping_num": fp.get("ping_window", 50),
714+
"range_sample_num": fp.get("range_window", 20),
715+
"SNR_threshold": fp.get("SNR_threshold", "3.0dB"),
716+
"background_noise_max": fp.get("background_noise_max", "-125.0dB"),
717+
}
718+
log.info(
719+
" BGN params for ch %d (%d Hz): SNR=%s, noise_max=%s",
720+
ch_idx, freq_int,
721+
bgn_params_by_ch[ch_idx]["SNR_threshold"],
722+
bgn_params_by_ch[ch_idx]["background_noise_max"],
723+
)
724+
696725
def _remove_bgn_one_channel(ch_ds):
697726
ch_ds.attrs.update(parent_attrs)
698-
result = ep_remove_bgn(
699-
ch_ds,
700-
ping_num=50,
701-
range_sample_num=20,
702-
SNR_threshold="3.0dB",
703-
background_noise_max="-125.0dB",
704-
)
727+
# Look up params by frequency
728+
freq = float(ch_ds["frequency_nominal"].compute().item())
729+
freq_int = int(round(freq))
730+
# Find matching ch_idx
731+
params = None
732+
for ci, bp in bgn_params_by_ch.items():
733+
ch_freq = int(round(float(
734+
ds_denoised["frequency_nominal"].isel(channel=ci).compute().item()
735+
)))
736+
if ch_freq == freq_int:
737+
params = bp
738+
break
739+
if params is None:
740+
params = bgn_params_by_ch[0]
741+
742+
result = ep_remove_bgn(ch_ds, **params)
705743
return result["Sv_corrected"] if "Sv_corrected" in result else result["Sv"]
706744

707745
sv_clean = ds_denoised.groupby("channel").map(_remove_bgn_one_channel)
708746
sv_clean.name = "Sv"
709747
ds_denoised["Sv"] = sv_clean
710-
log.info(" Background noise removal applied")
748+
log.info(" Background noise removal applied (frequency-specific)")
711749

712750
# Rechunk and save
713751
output_zarr = f"{day_key}/{day_key}--{category}--denoised.zarr"

0 commit comments

Comments
 (0)