Source code for uwacan.background

 1"""Classes and methods to evaluate, model, and compensate for background noise in measurements.
 2
 3.. autosummary::
 4    :toctree: generated
 5
 6    Background
 7
 8"""
 9
10from . import _core
11import xarray as xr
12import numpy as np
13
14
[docs] 15class Background(_core.FrequencyData): 16 """A class for simple measured background noise. 17 18 Parameters 19 ---------- 20 data : `~uwacan.FrequencyData` 21 The measured background noise, as a a power spectral density. 22 snr_requirement : float 23 The required SnR for a measurement to be valid. 24 The compensation will output NaN for invalid 25 data points. 26 """ 27 28 def __init__(self, data, snr_requirement=3, **kwargs): 29 super().__init__(data, **kwargs) 30 self.snr_requirement = snr_requirement 31
[docs] 32 def __call__(self, sensor_power): 33 """Compensate a recorded power spectral density. 34 35 Parameters 36 ---------- 37 sensor_power : `~uwacan.FrequencyData` 38 The measured power spectral density to compensate. 39 The background measurement will be interpolated 40 to the required frequencies if needed. 41 42 Notes 43 ----- 44 We have requirements on the sensor information on the 45 background data and the sensor data. 46 47 1) If the background data has sensor information, 48 the recorded power also needs to have sensor data. 49 2) If the background data has no sensor information, it does 50 not matter if the recorded power has sensor information. 51 3) If both have sensor information, all the sensors in the 52 recorded power has to exist in the background data. 53 54 """ 55 background = self.data 56 sensor_power_xr = sensor_power.data if isinstance(sensor_power, _core.xrwrap) else sensor_power 57 58 # if bg has sensors, data needs to have at least the same sensors 59 if "sensor" in background.coords: 60 if "sensor" not in sensor_power_xr.coords: 61 raise ValueError("Cannot apply sensor-wise background compensation to sensor-less recording") 62 if "sensor" not in background.dims: 63 # Single sensor in background, expand it to a dim so we can select from it 64 background = background.expand_dims("sensor") 65 # Pick the correct sensors from the background 66 background = background.sel(sensor=sensor_power_xr.coords["sensor"]) 67 68 if not sensor_power_xr.frequency.equals(background.frequency): 69 background_interp = background.interp( 70 frequency=sensor_power_xr.frequency, 71 method="linear", 72 ) 73 # Extrapolating using the lowest and highest frequency in the background 74 background_interp = xr.where( 75 background_interp.frequency <= background.frequency[0], 76 background.isel(frequency=0), 77 background_interp, 78 ) 79 background_interp = xr.where( 80 background_interp.frequency >= background.frequency[-1], 81 background.isel(frequency=-1), 82 background_interp, 83 ) 84 background = background_interp 85 86 snr = _core.dB(sensor_power_xr / background, power=True) 87 compensated = xr.where( 88 snr > self.snr_requirement, 89 sensor_power_xr - background, 90 np.nan, 91 ) 92 compensated = sensor_power.from_dataset(compensated) 93 compensated.snr = snr 94 return compensated