def deconvolution(dataframe, **kwargs):
    """
    Calculates pollutant concentration for convolved metrics, such as NO2+O3.
    Needs convolved metric, and target pollutant sensitivities
    Parameters
    ----------
        source: string
            Name of convolved metric containing both pollutants (such as NO2+O3)
        base: string
            Name of one of the already deconvolved pollutants (for instance NO2)
        id: int 
            Sensor ID
        pollutant: string
            Pollutant name. Must be included in the corresponding LUTs for unit convertion and additional parameters:
            MOLECULAR_WEIGHTS, config._background_conc, CHANNEL_LUT
    Returns
    -------
        calculation of pollutant based on: 6.36 * sensitivity(working - zero_working)/(auxiliary - zero_auxiliary)
    """

    result = Series()
    baseline = Series()

    flag_error = False
    if 'source' not in kwargs: flag_error = True
    if 'base' not in kwargs: flag_error = True
    if 'id' not in kwargs: flag_error = True
    if 'pollutant' not in kwargs: flag_error = True

    if flag_error:
        std_out('Problem with input data', 'ERROR')
        return None

    sensitivity_1 = config.calibrations.loc[kwargs['id'], 'sensitivity_1']
    sensitivity_2 = config.calibrations.loc[kwargs['id'], 'sensitivity_2']
    target_1 = config.calibrations.loc[kwargs['id'], 'target_1']
    target_2 = config.calibrations.loc[kwargs['id'], 'target_2']
    nWA = config.calibrations.loc[kwargs['id'],
                                  'w_zero_current'] / config.calibrations.loc[
                                      kwargs['id'], 'aux_zero_current']

    if target_1 != kwargs['pollutant']:
        std_out(
            f"Sensor {kwargs['id']} doesn't coincide with calibration data",
            'ERROR')
        return None

    factor_unit_1 = get_units_convf(kwargs['pollutant'], from_units='ppm')
    factor_unit_2 = get_units_convf(kwargs['base'], from_units='ppm')

    result = factor_unit_1 * (
        config._alphadelta_pcb * dataframe[kwargs['source']] -
        dataframe[kwargs['base']] / factor_unit_2 *
        abs(sensitivity_2)) / abs(sensitivity_1)

    # Add Background concentration
    result += config._background_conc[kwargs['pollutant']]

    return result
示例#2
0
def basic_4electrode_alg(dataframe, **kwargs):
    """
    Calculates pollutant concentration based on 4 electrode sensor readings (mV)
    and calibration ID. It adds a configurable background concentration.
    Parameters
    ----------
        working: string
            Name of working electrode found in dataframe
        auxiliary: string
            Name of auxiliary electrode found in dataframe
        id: int 
            Sensor ID
        pollutant: string
            Pollutant name. Must be included in the corresponding LUTs for unit convertion and additional parameters:
            MOLECULAR_WEIGHTS, config.background_conc, CHANNEL_LUT
    Returns
    -------
        calculation of pollutant based on: 6.36*sensitivity(working - zero_working)/(auxiliary - zero_auxiliary)
    """
    flag_error = False
    if 'working' not in kwargs: flag_error = True
    if 'auxiliary' not in kwargs: flag_error = True
    if 'id' not in kwargs: flag_error = True
    if 'pollutant' not in kwargs: flag_error = True

    if flag_error: 
        std_out('Problem with input data', 'ERROR')
        return None

    # Get Sensor data
    if kwargs['id'] not in config.calibrations.index: 
        std_out(f"Sensor {kwargs['id']} not in calibration data", 'ERROR')
        return None

    sensitivity_1 = config.calibrations.loc[kwargs['id'],'sensitivity_1']
    sensitivity_2 = config.calibrations.loc[kwargs['id'],'sensitivity_2']
    target_1 = config.calibrations.loc[kwargs['id'],'target_1']
    target_2 = config.calibrations.loc[kwargs['id'],'target_2']
    nWA = config.calibrations.loc[kwargs['id'],'w_zero_current']/config.calibrations.loc[kwargs['id'],'aux_zero_current']

    if target_1 != kwargs['pollutant']: 
        std_out(f"Sensor {kwargs['id']} doesn't coincide with calibration data", 'ERROR')
        return None

    # This is always in ppm since the calibration data is in signal/ppm
    result = config.alphadelta_pcb*(dataframe[kwargs['working']] - nWA*dataframe[kwargs['auxiliary']])/abs(sensitivity_1)

    # Convert units
    result *= get_units_convf(kwargs['pollutant'], from_units = 'ppm')
    # Add Background concentration
    result += config.background_conc[kwargs['pollutant']]

    return result
示例#3
0
    def __convert_units__(self):
        '''
            Convert the units based on the UNIT_LUT and blueprint
            NB: what is read/written from/to the cache is not converted.
            The files are with original units, and then converted in the device only
            for the readings but never chached like so.
        '''
        std_out('Checking if units need to be converted')
        for sensor in self.sensors:

            factor = get_units_convf(sensor,
                                     from_units=self.sensors[sensor]['units'])

            if factor != 1:
                self.readings.rename(columns={sensor: sensor + '_RAW'},
                                     inplace=True)
                self.readings.loc[:,
                                  sensor] = self.readings.loc[:, sensor +
                                                              '_RAW'] * factor
def baseline_4electrode_alg(dataframe, **kwargs):
    """
    Calculates pollutant concentration based on 4 electrode sensor readings (mV), but using
    one of the metrics (baseline) as a baseline of the others. It uses the baseline correction algorithm
    explained here: 
    https://docs.smartcitizen.me/Components/sensors/Electrochemical%20Sensors/#baseline-correction-based-on-temperature
    and the calibration ID. It adds a configurable background concentration.
    Parameters
    ----------
        target: string
            Name of working electrode found in dataframe
        baseline: string
            Name of auxiliary electrode found in dataframe
        id: int 
            Sensor ID
        pollutant: string
            Pollutant name. Must be included in the corresponding LUTs for unit convertion and additional parameters:
            MOLECULAR_WEIGHTS, config._background_conc, CHANNEL_LUT
        regression_type: 'string'
            'best'
            Use a 'linear' or 'exponential' regression for the calculation of the baseline
        period: pd.offset_alias or 'full'
            1D
            The period at which the baseline is calculated. If full, the whole index will be used.
            https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases
        store_baseline: boolean
            True
            Whether or not to store the baseline in the dataframe
        resample: str
            '1Min'
            Resample frequency for the target dataframe         
        pcb_factor: int
            config._alphadelta_pcb (6.36)
            Factor converting mV to nA due to the board configuration

    Returns
    -------
        calculation of pollutant based on: pcb_factor*sensitivity(working - zero_working)/(auxiliary - zero_auxiliary)
    """

    result = Series()
    baseline = Series()

    flag_error = False
    if 'target' not in kwargs: flag_error = True
    if 'baseline' not in kwargs: flag_error = True
    if 'id' not in kwargs: flag_error = True
    if 'pollutant' not in kwargs: flag_error = True

    if 'regression_type' in kwargs:
        if kwargs['regression_type'] not in ['best', 'exponential', 'linear']:
            flag_error = True
        else:
            reg_type = kwargs['regression_type']
    else:
        reg_type = 'best'

    if 'period' in kwargs:
        if kwargs['period'] not in ['best', 'exponential', 'linear']:
            flag_error = True
        else:
            period = kwargs['period']
    else:
        period = '1D'

    if 'store_baseline' in kwargs: store_baseline = kwargs['store_baseline']
    else: store_baseline = True

    if 'resample' in kwargs: resample = kwargs['resample']
    else: resample = '1Min'

    if 'pcb_factor' in kwargs: pcb_factor = kwargs['pcb_factor']
    else: pcb_factor = config._alphadelta_pcb

    if 'baseline_type' in kwargs: baseline_type = kwargs['baseline_type']
    else: baseline_type = 'deltas'

    if 'deltas' in kwargs: deltas = kwargs['deltas']
    else: deltas = config._baseline_deltas

    if flag_error:
        std_out('Problem with input data', 'ERROR')
        return None

    min_date, max_date, _ = find_dates(dataframe)
    pdates = date_range(start=min_date, end=max_date, freq=period)

    for pos in range(0, len(pdates) - 1):
        chunk = dataframe.loc[pdates[pos]:pdates[pos + 1],
                              [kwargs['target'], kwargs['baseline']]]
        bchunk = baseline_calc(chunk,
                               reg_type=reg_type,
                               resample=resample,
                               baseline_type=baseline_type,
                               deltas=deltas)
        if bchunk is None: continue
        baseline = baseline.combine_first(bchunk)

    if kwargs['pollutant'] not in config.convolved_metrics:

        sensitivity_1 = config.calibrations.loc[kwargs['id'], 'sensitivity_1']
        sensitivity_2 = config.calibrations.loc[kwargs['id'], 'sensitivity_2']
        target_1 = config.calibrations.loc[kwargs['id'], 'target_1']
        target_2 = config.calibrations.loc[kwargs['id'], 'target_2']
        nWA = config.calibrations.loc[
            kwargs['id'],
            'w_zero_current'] / config.calibrations.loc[kwargs['id'],
                                                        'aux_zero_current']

        if target_1 != kwargs['pollutant']:
            std_out(
                f"Sensor {kwargs['id']} doesn't coincide with calibration data",
                'ERROR')
            return None

        result = pcb_factor * (dataframe[kwargs['target']] -
                               baseline) / abs(sensitivity_1)

        # Convert units
        result *= get_units_convf(kwargs['pollutant'], from_units='ppm')
        # Add Background concentration
        result += config._background_conc[kwargs['pollutant']]

    else:
        # Calculate non convolved part
        result = dataframe[kwargs['target']] - baseline

    # Make use of DataFrame inmutable properties to store in it the baseline
    if store_baseline:
        dataframe[kwargs['target'] + '_BASELINE'] = baseline

    return result
def basic_4electrode_alg(dataframe, **kwargs):
    """
    Calculates pollutant concentration based on 4 electrode sensor readings (mV)
    and calibration ID. It adds a configurable background concentration.
    Parameters
    ----------
        working: string
            Name of working electrode found in dataframe
        auxiliary: string
            Name of auxiliary electrode found in dataframe
        id: int 
            Sensor ID
        pollutant: string
            Pollutant name. Must be included in the corresponding LUTs for unit convertion and additional parameters:
            MOLECULAR_WEIGHTS, config._background_conc, CHANNEL_LUT
        hardware: alphadelta or isb
    Returns
    -------
        calculation of pollutant based on: 6.36 * sensitivity(working - zero_working)/(auxiliary - zero_auxiliary)
    """

    flag_error = False
    if 'working' not in kwargs: flag_error = True
    if 'auxiliary' not in kwargs: flag_error = True
    if 'id' not in kwargs: flag_error = True
    if 'pollutant' not in kwargs: flag_error = True

    if flag_error:
        std_out('Problem with input data', 'ERROR')
        return None

    # Get Sensor data
    if kwargs['id'] not in config.calibrations:
        std_out(f"Sensor {kwargs['id']} not in calibration data", 'ERROR')
        return None

    we_sensitivity_na_ppb = config.calibrations[
        kwargs['id']]['we_sensitivity_na_ppb']
    we_cross_sensitivity_no2_na_ppb = config.calibrations[
        kwargs['id']]['we_cross_sensitivity_no2_na_ppb']
    sensor_type = config.calibrations[kwargs['id']]['sensor_type']
    nWA = config.calibrations[
        kwargs['id']]['we_sensor_zero_mv'] / config.calibrations[
            kwargs['id']]['ae_sensor_zero_mv']

    if sensor_type != kwargs['pollutant']:
        std_out(
            f"Sensor {kwargs['id']} doesn't coincide with calibration data",
            'ERROR')
        return None

    # This is always in ppm since the calibration data is in signal/ppm
    if kwargs['hardware'] == 'alphadelta':
        current_factor = config._alphadelta_pcb
    elif kwargs['hardware'] == 'isb':
        current_factor = 1  #TODO make it so we talk in mV
    else:
        std_out(f"Measurement hardware {kwargs['hardware']} not supported",
                'ERROR')
        return None

    result = current_factor * (
        dataframe[kwargs['working']] -
        nWA * dataframe[kwargs['auxiliary']]) / abs(we_sensitivity_na_ppb)

    # Convert units
    result *= get_units_convf(kwargs['pollutant'], from_units='ppm')
    # Add Background concentration
    result += config._background_conc[kwargs['pollutant']]

    return result