66from scdata .device .process import clean_ts , baseline_als
77from scipy .stats import linregress
88import matplotlib .pyplot as plt
9+ import numpy as np
910from pandas import date_range , DataFrame , Series , isnull , Timedelta
1011from 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