diff --git a/study_lyte/adjustments.py b/study_lyte/adjustments.py index d29675a..ff626e0 100644 --- a/study_lyte/adjustments.py +++ b/study_lyte/adjustments.py @@ -175,12 +175,15 @@ def remove_ambient(active, ambient, min_ambient_range=100, direction='forward'): return clean -def apply_calibration(series, coefficients, minimum=None, maximum=None): +def apply_calibration(series, coefficients, minimum=None, maximum=None, tare=False): """ Apply any calibration using poly1d """ poly = np.poly1d(coefficients) result = poly(series) + if tare: + result = result - np.nanmedian(result[0:50]) + if maximum is not None: result[result > maximum] = maximum if minimum is not None: diff --git a/study_lyte/profile.py b/study_lyte/profile.py index 17996dd..2777b75 100644 --- a/study_lyte/profile.py +++ b/study_lyte/profile.py @@ -81,11 +81,12 @@ def __init__(self, filename, surface_detection_offset=4.5, calibration=None, self._error = None self._ground = None - def assign_event_depths(self): + def assign_event_depths(self, depth:pd.Series): """" Enable depth assignment post depth realization """ self.events - for event in [self._start, self._stop, self._surface.nir, self._surface.force]: - event.depth = self.depth.iloc[event.index] + for event in [self._start, self._stop]: + event.depth = depth.iloc[event.index] + self._surface = self.assign_surface_depths(depth) @property def serial_number(self): @@ -182,8 +183,7 @@ def force(self): force = self.raw['Sensor1'].values if self.calibration is not None: if 'Sensor1' in self.calibration.keys(): - force = apply_calibration(self.raw['Sensor1'].values, self.calibration['Sensor1'], minimum=0, maximum=15000) - force = force - np.nanmean(force[0:20]) + force = apply_calibration(self.raw['Sensor1'].values, self.calibration['Sensor1'], minimum=None, maximum=15000, tare=True) self._force = pd.DataFrame({'force': force, 'depth': self.depth.values}) self._force = self._force.iloc[self.surface.force.index:self.end].reset_index() @@ -424,14 +424,26 @@ def depth(self): self.barometer.depth.values.copy(), error=self.error.index) - if depth.min() < -230 and self.accelerometer.depth.min() > -230: - LOG.warning('Fused depth result produced a profile > 230 cm. Defaulting to accelerometer') - self._depth = self.accelerometer.depth - - elif depth.min() < -230 and self.barometer.depth.min() > -230: - LOG.warning('Fused and accelerometer depth resulted in a profile > 230 cm. Defaulting to barometer') - self._depth = self.barometer.depth - + # Failed fusion + unrealistic_depth = 230 + travel = abs(depth.max() - depth.min()) + + # Unrealistic depth + if travel > unrealistic_depth: + warn_msg = f'Fused depth result produced a profile > {unrealistic_depth} cm.' + + # Check if acceleration alone is reasonable + if self.accelerometer.distance_traveled < unrealistic_depth: + LOG.warning(warn_msg + ' Defaulting to accelerometer') + self._depth = self.accelerometer.depth + + # Check if barometer alone is reasonable + elif self.barometer.distance_traveled < unrealistic_depth: + LOG.warning(warn_msg + ' Defaulting to barometer') + self._depth = self.barometer.depth + else: + LOG.error(warn_msg + ' Alternate sensors also unrealistic, using data as is.') + self._depth = pd.Series(data=depth, index=self.raw['time']) else: self._depth = pd.Series(data=depth, index=self.raw['time']) @@ -444,7 +456,7 @@ def depth(self): self._depth = self.barometer.depth # Assign positions of each event detected - self.assign_event_depths() + self.assign_event_depths(self._depth) return self._depth @@ -481,6 +493,38 @@ def stop(self): return self._stop + def assign_surface_depths(self, depth:pd.Series): + # Event according the NIR sensors + idx = self.surface.nir.index + self._surface.nir.depth = depth.iloc[idx] + + # Event according to the force sensor + force_surface_depth = self._surface.nir.depth + self.surface_detection_offset + f_idx = abs(depth - force_surface_depth).argmin() + # Retrieve force estimated start + f_start = get_sensor_start(self.raw['Sensor1'], max_threshold=0.02, threshold=-0.02) + f_start = f_start or f_idx + + # If the force start is before the NIR start then adjust + if f_start < self.start.index: + LOG.info(f'Choosing motion start ({self.start.index}) over force start ({f_start})...') + f_idx = self.start.index + force_surface_depth = depth.iloc[f_idx] + + elif f_start < f_idx: + LOG.info(f'Choosing force start ({f_start}) over nir derived ({f_idx})...') + f_idx = f_start + force_surface_depth = depth.iloc[f_idx] + + self._surface.force.index = f_idx + self._surface.force.depth = depth.iloc[f_idx] + self._surface.force.time = self.raw['time'].iloc[f_idx] + + # Adjust surface detection to modify the start if there is conflict. + if self._surface.nir.time < self.start.time: + self._start = self._surface.nir + self._start.name = 'start' + @property def surface(self): """ @@ -517,11 +561,6 @@ def surface(self): force = Event(name='surface', index=f_idx, depth=force_surface_depth, time=self.raw['time'].iloc[f_idx]) self._surface = SimpleNamespace(name='surface', nir=nir, force=force) - # Allow surface detection to modify the start if there is conflict. - if nir.time < self.start.time: - self._start = nir - self._start.name = 'start' - return self._surface @property @@ -651,10 +690,7 @@ def fuse_depths(cls, acc_depth, baro_depth, error=None): # Scale total sensor_diff = abs(acc_bottom) - abs(baro_bottom) delta = 0.572 * abs(acc_bottom) + 0.308 * abs(baro_bottom) + 0.264 * sensor_diff + 8.916 - # delta = (acc_bottom * (5 - scale) + baro_bottom * scale) / 5 avg = (avg / avg_bottom) * -1 * delta - # from study_lyte.plotting import plot_ts - # ax = plot_ts(avg, show=True) return avg