Skip to content

Commit c911914

Browse files
committed
Add polifit to baseline calculation and gap detection to 803_04
1 parent 56d63bf commit c911914

1 file changed

Lines changed: 56 additions & 43 deletions

File tree

scdata/device/process/alphasense.py

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from scdata.device.process import clean_ts, baseline_als
77
from scipy.stats import linregress
88
import matplotlib.pyplot as plt
9+
import numpy as np
910
from pandas import date_range, DataFrame, Series, isnull, Timedelta
1011
from scdata.device.process.error_codes import StatusCode, ProcessResult
1112

@@ -29,6 +30,11 @@ def alphasense_803_04(dataframe, **kwargs):
2930
Use alternative algorithm as shown in the AAN
3031
rolling_frequency: string
3132
Rolling average frequency for we and ae electrodes
33+
n_gaps: int
34+
Number of rows that need to be NaN to be considered a gap
35+
Default: 5
36+
gap_window_removal_h: int
37+
Number of hours to remove data after a gap is found for baseline calculation
3238
return_all_cols: bool
3339
Return all columns intermediate to the calculation
3440
no2_channel: string
@@ -77,6 +83,10 @@ def comp_t(x, comp_lut):
7783
logger.error(f"Sensor {kwargs['alphasense_id']} not in calibration data")
7884
return ProcessResult(None, StatusCode.ERROR_CALIBRATION_NOT_FOUND)
7985

86+
# Gaps cleaning
87+
n_gaps = kwargs.get('n_gaps', None)
88+
gap_window_removal_h = kwargs.get('gap_window_removal_h', 6)
89+
8090
rolling_frequency = kwargs.get('rolling_frequency', None)
8191
return_all_cols = kwargs.get('return_all_cols', False)
8292
no2_channel = kwargs.get('no2_channel', 'NO2')
@@ -137,6 +147,7 @@ def comp_t(x, comp_lut):
137147
# Compensate electronic zero
138148
df['we_t'] = df['we_clean'] - (cal_data['we_electronic_zero_mv'] / 1000) # in V
139149
df['ae_t'] = df['ae_clean'] - (cal_data['ae_electronic_zero_mv'] / 1000) # in V
150+
140151
# Get requested temperature
141152
df['t'] = df[kwargs['t']]
142153

@@ -145,6 +156,32 @@ def comp_t(x, comp_lut):
145156
df['we_t'] = df['we_t'].rolling(rolling_frequency).mean()
146157
df['ae_t'] = df['ae_t'].rolling(rolling_frequency).mean()
147158

159+
# Find gaps
160+
found_gaps = False
161+
baseline_als_kwargs = {'name': 'we_t'}
162+
if n_gaps and gap_window_removal_h is not None:
163+
logger.info(f'Searching gaps of at least {n_gaps}')
164+
165+
df['time'] = df.index
166+
df['time_lag'] = df['time'].shift(1)
167+
df['time_delta'] = df['time'] - df['time_lag']
168+
df['gap'] = df['time_delta'] > Timedelta(minutes=n_gaps)
169+
df['we_mask'] = False
170+
171+
# Extract index of the gaps and add first index by default
172+
# as it will probably have the stab issue.
173+
true_idx = df.index[df["gap"]].insert(0, df.index[0])
174+
175+
for t in true_idx:
176+
end = t + Timedelta(hours=gap_window_removal_h)
177+
df.loc[t:end, "we_mask"] = True
178+
179+
found_gaps = True
180+
181+
if found_gaps:
182+
df.loc[df['we_mask'], 'we_t'] = np.nan
183+
df.loc[df['we_mask'], 'ae_t'] = np.nan
184+
148185
# Temperature compensation - done line by line as it has special conditions
149186
df[comp_type] = df.apply(lambda x: comp_t(x, comp_lut), axis = 1) # temperature correction factor
150187

@@ -264,9 +301,6 @@ def alphasense_als(dataframe, **kwargs):
264301

265302
return_all_cols = kwargs.get('return_all_cols', False)
266303

267-
# Algorithm selection
268-
algorithm = kwargs.get('algorithm', 1)
269-
270304
# Gaps cleaning
271305
n_gaps = kwargs.get('n_gaps', None)
272306
# resample_frequency = kwargs.get('resample_frequency', None)
@@ -325,34 +359,27 @@ def alphasense_als(dataframe, **kwargs):
325359
baseline_als_kwargs = {'name': 'we_t'}
326360
if n_gaps and gap_window_removal_h is not None:
327361
logger.info(f'Searching gaps of at least {n_gaps}')
328-
# logger.info(f'Resampling at {resample_frequency}')
329-
# df = df.resample(resample_frequency).mean() # This is important, otherwise we can't find gaps
330-
# df['we_na'] = df['we_t'].isna()
331-
# for i in range(n_gaps):
332-
# df[f'we_na_shift_{i+1}'] = df['we_na'].shift(i+1)
333-
334-
# # We consider a gap only when there is at least "n_gaps" consecutive gaps
335-
# cols = [c for c in df.columns if c.startswith("we_na_shift_")]
336-
# # Get a falling edge afte a n_gaps-long gap
337-
# df['we_gap_falling_edge'] = df[cols].all(axis=1) & (~df["we_na"])
338362

339363
df['time'] = df.index
340364
df['time_lag'] = df['time'].shift(1)
341365
df['time_delta'] = df['time'] - df['time_lag']
342366
df['gap'] = df['time_delta'] > Timedelta(minutes=n_gaps)
343367
df['we_mask'] = False
344-
# true_idx = df.index[df["we_gap_falling_edge"]]
345-
true_idx = df.index[df["gap"]]
368+
369+
# Extract index of the gaps and add first index by default
370+
# as it will probably have the stab issue.
371+
true_idx = df.index[df["gap"]].insert(0, df.index[0])
346372

347373
for t in true_idx:
348374
end = t + Timedelta(hours=gap_window_removal_h)
349375
df.loc[t:end, "we_mask"] = True
350376

351-
df['we_t_filter'] = df['we_t'][~df['we_mask']]
352-
# Replace baseline kwargs
353-
baseline_als_kwargs = {'name': 'we_t_filter'}
354377
found_gaps = True
355378

379+
if found_gaps:
380+
df.loc[df['we_mask'], 'we_t'] = np.nan
381+
df.loc[df['we_mask'], 'ae_t'] = np.nan
382+
356383
# Get requested temperature
357384
df['t'] = df[kwargs['t']]
358385

@@ -366,30 +393,17 @@ def alphasense_als(dataframe, **kwargs):
366393
else:
367394
return ProcessResult(None, StatusCode.ERROR_UNDEFINED)
368395

369-
if algorithm == 1: # Division
370-
# Calculate baseline factor with the baseline mean
371-
mean_we = df['we_baseline'].mean()
372-
mask = df['ae_t'].notna() & (df['ae_t'] != 0)
373-
df.loc[mask, 'baseline_t'] = mean_we / df.loc[mask, 'ae_t']
374-
df['baseline_t_mean'] = df['baseline_t'].mean()
375-
376-
# Correct Auxiliary electrode based on mean factor
377-
df['ae_cor'] = df['baseline_t_mean'] * df['ae_t']
378-
df['we_c'] = df['we_t'] - df['ae_cor']
379-
if found_gaps:
380-
df['we_c'] = df['we_c'][~df['we_mask']]
381-
elif algorithm == 2: # +/-
382-
# Calculate baseline factor with the baseline mean
383-
mean_we = df['we_baseline'].quantile(0.05)
384-
mask = df['ae_t'].notna() & (df['ae_t'] != 0)
385-
df.loc[mask, 'baseline_t'] = mean_we - df.loc[mask, 'ae_t']
386-
df['baseline_t_mean'] = mean_we
387-
388-
# Correct Auxiliary electrode based on mean factor
389-
df['ae_cor'] = df['ae_t'] + mean_we
390-
df['we_c'] = df['we_t'] - df['ae_cor']
391-
if found_gaps:
392-
df['we_c'] = df['we_c'][~df['we_mask']]
396+
# Calculate baseline factor with the baseline mean
397+
mean_we = df['we_baseline'].mean()
398+
mask = df['ae_t'].notna() & df['we_baseline'].notna()
399+
coef = np.polyfit(df.loc[mask, 'ae_t'], df.loc[mask, 'we_baseline'], 1)
400+
a, b = coef
401+
402+
# Correct Auxiliary electrode based on mean factor
403+
df['ae_cor'] = a * df['ae_t'] + b
404+
df['we_c'] = df['we_t'] - df['ae_cor']
405+
if found_gaps:
406+
df.loc[df['we_mask'], 'we_c'] = np.nan
393407

394408
# Verify if it has NO2 cross-sensitivity (in V)
395409
if cal_data['we_cross_sensitivity_no2_mv_ppb'] != float (0) and 'NO2' not in as_type:
@@ -419,7 +433,6 @@ def alphasense_als(dataframe, **kwargs):
419433
"we_gap_falling_edge",
420434
"ae_clean",
421435
"we_t",
422-
"we_t_filter",
423436
"ae_t",
424437
"we_baseline",
425438
"baseline_t",

0 commit comments

Comments
 (0)