diff --git a/defs/BTOF.py b/defs/BTOF.py index e69de29..f5521ad 100644 --- a/defs/BTOF.py +++ b/defs/BTOF.py @@ -0,0 +1,3 @@ +r""" +An empty docstring +""" \ No newline at end of file diff --git a/defs/TOC.py b/defs/TOC.py index 47e59d1..15a751a 100644 --- a/defs/TOC.py +++ b/defs/TOC.py @@ -8,7 +8,7 @@ ### ----------------------------------------------------------------------------------------------------------------------------------- ### ### SUBMODULE DOCSTRING -"""The docstring for this submodule +"""This submodule contains functionality to calibrate TOC measurement data and analyse them. """ ### ----------------------------------------------------------------------------------------------------------------------------------- ### @@ -27,10 +27,37 @@ from defs.Helper import compute_R2 class CalibrationCurve(): + r"""An object representing a calibration curve for TOC, IC or NPOC Measurements. + + Description + ----------- + This object instanciates a class that relates the area $A$ measured in the TOC Analyser to the concentration $c$ used in the calibration solutions. The calibration curve is affine, $c=aA+b$ where $a$ and $b$ denote slope and intercept, respectively, of the affine map. This object can be used to represent linear calibration curves for TC (total carbon)-, IC (inorganic carbon)- and NPOC (non-purgeable organic carbon) measurements. + + Attributes + ---------- + exists: Bool + This value is set to `True` if a calibration curve is already available, else it is set to `False`. + slope: float + Value of the slope for the calibration of concentration (dependent variable) with measured area (independent variable). + intercept: float + Value of the intercep for the calibration of concentration (dependent variable) with measured area (independent variable). + R2_value: float + The coefficient of determination of the calibration curve. For a good calibration curve, this should be above 0.995.\ + excluded: list + A list of integers denoting the samples in the calibrant data that are excluded. + lower_bound: float + The lower bound value for which this calibration curve can be used. + upper_bound: float + The upper bound value for which this calibration curve can be used. + computed_on: pd.Timestamp + A timestamp indicating the time at which the calibration curve was computed. + """ + def __init__(self): - self.exists = False + # initialises all member variables + self.exists = False self.slope = None self.intercept = None self.R2_value = None @@ -41,16 +68,76 @@ class CalibrationCurve(): def __linear_function(self, x_values, a, b): + r"""Computes the value of the calibration curve for the passed parameters. + + Description + ----------- + Computes the affine map, $c=aA+b$, given data $A$ and parameters $a$ (slope) and $b$ (intercept). + + Parameters + ---------- + x_values: array-like + An array-like (array or list) object of length $n$ containing the data for the independent variable that are to be mapped. + a: float + The slope of the calibration curve. + b: float + The intercept of the calibration curve. + + Returns + ------- + y_values: np.ndarray + An array (of size $n$) containing the image under the affine map of `x_values`. + """ + + # check if the input data is an instance of a np.ndarray. If not, cast it to np.ndarray if not isinstance(x_values, np.ndarray): x_values = np.array(x_values) + # evaluate the image of the input under the affine map and return the results return a * x_values + b def __optimise(self, x_data, y_data, p0): + + r"""Finds the optimal parameters of the calibration curve + + Description + ----------- + Uses `scipy.optimize.curve_fit` to find optimal parameters for the calibrant data. + + Parameters + ---------- + x_data: array-like + The data representing the independent variable. + y_data: array-like + The data representing the dependent variable. + p0: list + A list containing the initial guesses of the parameters. + + Returns + ------- + p: np.ndarray + An array containing the optimal parameter values. + pcov: The covariance matrix associated with the optimal parameters. + """ + + # evaluate the optimisation problem and return optimal parameters and their covariance matrix. return curve_fit(self.__linear_function, x_data, y_data, p0) def __check_exist(self): + r"""Checks if attribute `exists` is set + + Description + ----------- + Checks of the attribute `exists` is set to `None` (its instanciation value) and returns `False` in this case, or returns `True` if attribute value is not `None` (i.e. if it has been set). + + Returns + ------- + t: Bool + `True` if attribute has a value, else returns `None`. + """ + + # this line is equivalent to "if None is True" if attribute is not set, which evaluates to 'False'. Else evaluates to 'True'. if self.exists == True: return True else: @@ -58,6 +145,13 @@ class CalibrationCurve(): def __check(self): + r"""Gives a warning message when overwriting an existing calibration curve. + + Description + ----------- + Calls the private `check_exists` method to see if a calibration curve has already been computed. If yes, this will throw a warning message, but not an error. + """ + if self.__check_exist(): warnings.warn("Calibration alreadyt exists, will be overwritten...") else: @@ -65,6 +159,20 @@ class CalibrationCurve(): def calibrate(self, x_data, y_data): + """Compute a calibration curve based on the available calibrant data. + + Description + ----------- + Bla Bla Bla + + Parameters + ---------- + x_data: np.ndarray + Bla bla bla + y_data: np.ndarray + Bla Bla Bla + """ + self.__check() parameters, _ = self.__optimise(x_data=x_data, y_data=y_data, p0=[10, 0.1]) @@ -103,6 +211,8 @@ class Calibration(): self.__read_calibration_file() + self.mask = None + def __read_calibration_file(self): conctr = [] @@ -196,12 +306,12 @@ class Calibration(): if excluded_points: self.curve.excluded = excluded_points - mask = [False if i + 1 in self.curve.excluded else True for i in range(len(self.calibrant_data))] + self.mask = [False if i + 1 in self.curve.excluded else True for i in range(len(self.calibrant_data))] else: self.curve.excluded = None - mask = [True for i in range(len(self.calibrant_data))] + self.mask = [True for i in range(len(self.calibrant_data))] - self.curve.calibrate(x_data=self.calibrant_data['area_average'].values[mask], y_data=self.calibrant_data['concentration'].values[mask]) + self.curve.calibrate(x_data=self.calibrant_data['area_average'].values[self.mask], y_data=self.calibrant_data['concentration'].values[self.mask]) with open(self.cal_file, 'r', errors = 'ignore') as file: lines = file.readlines() diff --git a/doc/defs/visualising.html b/doc/defs/BTOF.html similarity index 82% rename from doc/defs/visualising.html rename to doc/defs/BTOF.html index 3e93905..6aaf9a9 100644 --- a/doc/defs/visualising.html +++ b/doc/defs/BTOF.html @@ -4,8 +4,8 @@ -defs.visualising API documentation - +defs.BTOF API documentation + @@ -24,9 +24,10 @@ hljs.highlightAll();
-

Module defs.visualising

+

Module defs.BTOF

+

An empty docstring

@@ -35,50 +36,6 @@ hljs.highlightAll();
-

Classes

-
-
-class HelloWorld -(word='Hellow') -
-
-

The docstring for this class

-

Description: Bla bla bla

-

Attributes

-
-
word : str
-
A word
-
-

Methods

-

init: HelloWorld -The class constructor

-
- -Expand source code - -
class HelloWorld():
-
-    """The docstring for this class
-    
-    Description: Bla bla bla
-
-    Attributes
-    ----------
-    word: str
-        A word
-
-    Methods
-    -------
-    __init__: HelloWorld
-        The class constructor
-    """
-
-    def __init__(self, word = "Hellow"):
-
-        self.word = word
-
-
-
diff --git a/doc/defs/Helper.html b/doc/defs/Helper.html new file mode 100644 index 0000000..96aed75 --- /dev/null +++ b/doc/defs/Helper.html @@ -0,0 +1,168 @@ + + + + + + +defs.Helper API documentation + + + + + + + + + + + + + +
+
+
+

Module defs.Helper

+
+
+

The docstring for this submodule

+
+
+
+
+
+
+

Functions

+
+
+def compute_R2(y_true, y_est) +
+
+
+
+
+
+
+

Classes

+
+
+class DATFile +(file, data_name='value') +
+
+

The docstring for this class

+

Description: Bla bla bla

+

Attributes

+
+
word : str
+
A word
+
+

Methods

+

init: HelloWorld +The class constructor

+
+ +Expand source code + +
class DATFile():
+
+    """The docstring for this class
+        
+        Description: Bla bla bla
+    
+        Attributes
+        ----------
+        word: str
+            A word
+    
+        Methods
+        -------
+        __init__: HelloWorld
+            The class constructor
+        """
+
+    def __init__(self, file, data_name = 'value'):
+
+        self.file = file
+        self.data_name = data_name
+        self.data = None
+
+    def read(self):
+
+        dates = []
+        times = []
+        values = []
+
+        with open(self.file, 'r', errors='ignore') as f:
+            for i, line in enumerate(f.readlines()):
+
+                if i >= 3:
+                    
+                    items = line.split()
+                    dates.append(items[0])
+                    times.append(items[1])
+                    values.append(float(items[2]))
+
+        pre_datetimes = [date + ' ' + time for date, time in zip(dates, times)]
+        datetimes = pd.to_datetime(pre_datetimes, format=r'%d.%m.%Y %H:%M:%S')
+        self.data = pd.DataFrame({'timestamp': datetimes, self.data_name: values})
+
+    def exclude_outliers():
+            
+        pass
+
+

Methods

+
+
+def exclude_outliers() +
+
+
+
+
+def read(self) +
+
+
+
+
+
+
+
+
+ +
+ + + diff --git a/doc/defs/OzoneMonitor.html b/doc/defs/OzoneMonitor.html new file mode 100644 index 0000000..f1aa5bb --- /dev/null +++ b/doc/defs/OzoneMonitor.html @@ -0,0 +1,180 @@ + + + + + + +defs.OzoneMonitor API documentation + + + + + + + + + + + + + +
+
+
+

Module defs.OzoneMonitor

+
+
+

The docstring for this submodule

+
+
+
+
+
+
+
+
+

Classes

+
+
+class OzoneAnalyser +(file: DATFile, on_time_interval) +
+
+

The docstring for this class

+

Description: Bla bla bla

+

Attributes

+
+
word : str
+
A word
+
+

Methods

+

init: HelloWorld +The class constructor

+
+ +Expand source code + +
class OzoneAnalyser():
+
+    """The docstring for this class
+    
+    Description: Bla bla bla
+
+    Attributes
+    ----------
+    word: str
+        A word
+
+    Methods
+    -------
+    __init__: HelloWorld
+        The class constructor
+    """
+
+    def __init__(self, file: DATFile, on_time_interval):
+
+        self.file = file
+        self.on_time_interval = on_time_interval
+        self.averaged_data = None
+
+    def GetData(self):
+
+        self.file.read()
+
+    def AverageWithSMPS(self, smps_analyser: OFRAnalyser, cut_edges: int, subtract_baseline = ['yes', 5]):
+
+        avg_ozone = []
+        mprr_data = []
+
+        local_smps_data = smps_analyser.data[(smps_analyser.data['Starting Time (-)'] >= smps_analyser.on_time_interval[0]) &
+                                             (smps_analyser.data['Starting Time (-)'] <= smps_analyser.on_time_interval[1])].reset_index()
+
+        baseline = None
+
+        for index, data in local_smps_data.iterrows():
+
+            start = data['Starting Time (-)']
+
+            if index == 0:
+
+                if subtract_baseline[0] == 'yes':
+                    baseline_start = start - pd.Timedelta(minutes = int(subtract_baseline[1]))
+                    baseline_ozone_data = self.file.data[(self.file.data['timestamp'] >= baseline_start) &
+                                                         (self.file.data['timestamp'] <= start)]
+                    baseline = np.average(baseline_ozone_data['ozone'])
+
+            if index in list(range(cut_edges)):
+                    continue
+
+            stop = start + pd.Timedelta(seconds = data['Sampling Time (s)'])
+
+            mprr_data.append(data['Mass Production Rate (kg/s)'])
+
+            local_ozone_data = self.file.data[(self.file.data['timestamp'] >= start) &
+                                              (self.file.data['timestamp'] <= stop)]
+            avg_ozone.append(np.average(local_ozone_data['ozone']))
+
+        for sub_index in list(range(cut_edges)):
+                del avg_ozone[-(sub_index + 1)]
+                del mprr_data[-(sub_index + 1)]
+
+        out_mprr_data = np.array(mprr_data)
+
+        if subtract_baseline[0] == 'yes':
+                out_avg_ozone = np.array([baseline - value for value in avg_ozone])
+        elif subtract_baseline[0] != 'yes':
+                out_avg_ozone = np.array([value for value in avg_ozone])
+
+        self.averaged_data = pd.DataFrame({'Ozone (ppb)': out_avg_ozone, 'Mass Production Rate (kg/s)': out_mprr_data})
+
+

Methods

+
+
+def AverageWithSMPS(self, smps_analyser: OFRAnalyser, cut_edges: int, subtract_baseline=['yes', 5]) +
+
+
+
+
+def GetData(self) +
+
+
+
+
+
+
+
+
+ +
+ + + diff --git a/doc/defs/OFR.html b/doc/defs/SMPS.html similarity index 84% rename from doc/defs/OFR.html rename to doc/defs/SMPS.html index 90e26f9..d784970 100644 --- a/doc/defs/OFR.html +++ b/doc/defs/SMPS.html @@ -4,7 +4,7 @@ -defs.OFR API documentation +defs.SMPS API documentation @@ -24,7 +24,7 @@ hljs.highlightAll();
-

Module defs.OFR

+

Module defs.SMPS

The docstring for this submodule

@@ -38,7 +38,7 @@ hljs.highlightAll();

Classes

-
+
class Distribution (variable_array, function_array, form='log10')
@@ -183,7 +183,7 @@ hljs.highlightAll();

Methods

-
+
def integrate(self) ‑> numpy.float64
@@ -191,25 +191,25 @@ hljs.highlightAll();

Description

Bla Bla Bla

-
+
def mean()
-
+
def median()
-
+
def mode()
-
+
def transform(self, to='linear')
@@ -222,7 +222,7 @@ hljs.highlightAll();
Indicating into which representation the distribution should be transformed. Possible values are: linear, ln or log10
-
+
def update_units(self, new_var_units, new_fct_units)
@@ -237,7 +237,7 @@ hljs.highlightAll();
The SI factor used to transform the dependent variable self.fct
-
+
def variance()
@@ -245,9 +245,9 @@ hljs.highlightAll();
-
+
class OFRAnalyser -(data_source_file, flows_source_file, sample_number_list, data_separator=',') +(data_source_file, flow_source_file, smpl_number_list, on_time_interval, data_separator=',')

An object representing an OFR experiment

@@ -257,10 +257,12 @@ hljs.highlightAll();
data_source_file : str
A string pointing to the data source file
-
flows_source_file : str
+
flow_source_file : str
A string pointing to the flow data source file
sample_number_list : [int]
A list of integers indicating which samples to consider
+
on_time_interval : [pd.Timestamp]
+
A list with two elements containing the pd.Timestamp objects when the VOC flow was switched on and off, respectively.
max_samples : int
The highest sample available in the data
data : pandas.DataFrame
@@ -282,23 +284,27 @@ hljs.highlightAll(); ---------- data_source_file: str A string pointing to the data source file - flows_source_file: str + flow_source_file: str A string pointing to the flow data source file sample_number_list: [int] A list of integers indicating which samples to consider + on_time_interval: [pd.Timestamp] + A list with two elements containing the pd.Timestamp objects when the VOC flow was switched on and off, respectively. max_samples: int The highest sample available in the data data: pandas.DataFrame A dataframe containing all the data """ - def __init__(self, data_source_file, flows_source_file, sample_number_list, data_separator = ',') -> None: + def __init__(self, data_source_file, flow_source_file, smpl_number_list, on_time_interval, data_separator = ',') -> None: self.data_source_file = data_source_file - self.flows_source_file = flows_source_file - self.sample_number_list = sample_number_list + self.flow_source_file = flow_source_file + self.smpl_number_list = smpl_number_list + self.on_time_interval = on_time_interval self.__data_separator = data_separator + with open(self.data_source_file, 'r', errors = 'ignore') as file: for i, line in enumerate(file.readlines()): if i == 17: @@ -310,11 +316,11 @@ hljs.highlightAll(); self.max_samples = max_sample_number self.data = None - def __sample_number_list_check(self, sample_number_list) -> None: + def __smpl_number_list_check(self, smpl_number_list) -> None: # TODO: implement a similar check for the flow data file! - last_sample = sample_number_list[-1] + last_sample = smpl_number_list[-1] if last_sample > self.max_samples: raise IndexError("The last sample(s) are out of range of the number of recorded samples") @@ -331,32 +337,29 @@ hljs.highlightAll(); # TODO make this work also for a specific sample! # TODO convert everything is SI units, making live a lot easier... - self.__sample_number_list_check(self.sample_number_list) + self.__smpl_number_list_check(self.smpl_number_list) + # Quantities of interest: numbers_of_sample = [] # (-) + starting_times = [] # (timestamp) + sampling_times = [] # (s) number_concentrations = [] # (#/m^3) length_concentrations = [] # (m/m^3) surface_concentrations = [] # (m^2/m^3) volume_concentrations = [] # (m^3/m^3) aerosol_densities = [] # (kg/m^3) mass_concentrations = [] # (kg/m^3) - starting_times = [] # (s) - stopping_times = [] # (s) - sampling_times = [] # (s) - dry_air_flows = [] # (m^3/s) - species_flows = [] # (m^3/s) - ozone_flows = [] # (m^3/s) - total_flows = None # (m^3/s) - cpc_flows = [] # (m^3/s) + filter_flows = [] # (m^3/s) mass_production_rate = None # (kg/s) mass_produced = None # (kg) cumulative_mass_produced = None # (kg) - - for sample_number in self.sample_number_list: + for sample_number in self.smpl_number_list: sample = OFRSample(source_file=self.data_source_file, sample_number = sample_number, data_separator = self.__data_separator) numbers_of_sample.append(sample_number) + starting_times.append(sample.start_time) + number_concentrations.append(sample.Count.integrate()) length_concentrations.append(sample.Length.integrate()) surface_concentrations.append(sample.Surface.integrate()) @@ -364,14 +367,11 @@ hljs.highlightAll(); aerosol_densities.append(sample.SOA_density) mass_concentrations.append(sample.Mass.integrate()) - starting_times.append((sample_number - 1) * sample.sampling_time) - stopping_times.append(sample_number * sample.sampling_time) - sampling_times.append(sample.sampling_time) out_flag = False - with open(self.flows_source_file, 'r', errors='ignore') as file: + with open(self.flow_source_file, 'r', errors='ignore') as file: j = 1 @@ -391,11 +391,8 @@ hljs.highlightAll(); while i < int(items[-1]): - if j in self.sample_number_list: - dry_air_flows.append(float(items[0]) * 6E-4) - species_flows.append(float(items[1]) * 6E-4) - ozone_flows.append(float(items[2]) * 6E-4) - cpc_flows.append(float(items[3]) * 6E-4) + if j in self.smpl_number_list: + filter_flows.append(float(items[0]) * (1E-3) / 60) i += 1 j += 1 @@ -403,33 +400,27 @@ hljs.highlightAll(); else: continue - total_flows = [(flow_1 + flow_2 + flow_3) for flow_1, flow_2, flow_3 in zip(dry_air_flows, species_flows, ozone_flows)] - mass_production_rate = [(mass * flow) for mass, flow in zip(mass_concentrations, total_flows)] + mass_production_rate = [(mass * flow) for mass, flow in zip(mass_concentrations, filter_flows)] mass_produced = [(mpr * duration) for mpr, duration in zip(mass_production_rate, sampling_times)] cumulative_mass_produced = np.cumsum(np.array(mass_produced)).tolist() self.data = pd.DataFrame({'Sample (-)': numbers_of_sample, + 'Starting Time (-)': starting_times, + 'Sampling Time (s)': sampling_times, 'Number (#/m^3)': number_concentrations, 'Length (m/m^3)': length_concentrations, 'Surface (m^2/m^3)': surface_concentrations, 'Volume (m^3/m^3)': volume_concentrations, 'Density (kg/m^3)': aerosol_densities, 'Mass (kg/m^3)': mass_concentrations, - 'Start (s)': starting_times, - 'Stop (s)': stopping_times, - 'Duration (s)': sampling_times, - 'Dry Air Flow (m^3/s)': dry_air_flows, - 'Species Flow (m^3/s)': species_flows, - 'Ozone Flow (m^3/s)': ozone_flows, - 'Total Flow (m^3/s)': total_flows, - 'CPC Flow (m^3/s)': cpc_flows, + 'Filter Flow (m^3/s)': filter_flows, 'Mass Production Rate (kg/s)': mass_production_rate, 'Mass Produced (kg)': mass_produced, 'Cumulative Mass Produced (kg)': cumulative_mass_produced})

Methods

-
+
def GetData(self) ‑> None
@@ -439,7 +430,7 @@ hljs.highlightAll();
-
+
class OFRSample (source_file, sample_number, data_separator=',')
@@ -451,6 +442,8 @@ hljs.highlightAll();
number : int
The sample number
+
start_time : pd.datetime
+
The starting time of the sample recording
sourcefile : str
The path to the data file
headlines : int
@@ -485,6 +478,8 @@ hljs.highlightAll(); number: int The sample number + start_time: pd.datetime + The starting time of the sample recording sourcefile: str The path to the data file headlines: int @@ -513,8 +508,17 @@ hljs.highlightAll(); midpoints = [] counts = [] + date_line = None + time_line = None + with open(self.source_file, 'r', errors = 'ignore') as file: for i, line in enumerate(file.readlines()): + if i == 18: + date_line = line + continue + if i == 19: + time_line = line + continue if i == 20: data_flag = True continue @@ -548,6 +552,16 @@ hljs.highlightAll(); self.sampling_time = self.scan_up_time + self.scan_down_time data_lines.pop(-1) + # obtaining the starting time + raw_date_items = date_line.split(self.__data_separator) + raw_time_items = time_line.split(self.__data_separator) + del raw_date_items[0] + del raw_time_items[0] + + raw_date = raw_date_items[(self.sample_number - 1) * 2].splitlines()[0] + raw_time = raw_time_items[(self.sample_number - 1) * 2].splitlines()[0] + self.start_time = pd.to_datetime(raw_date + ' ' + raw_time, format=r'%m/%d/%y %H:%M:%S') + for line in data_lines: items = line.split(self.__data_separator) local_diameter_midpoint = float(items[0]) @@ -563,7 +577,6 @@ hljs.highlightAll(); self.lower_diameter_bound = self.lower_diameter_bound * 1E-9 self.upper_diameter_bound = self.upper_diameter_bound * 1E-9 - # Input data from file is in (nm) return [np.array(midpoints), np.array(counts)] @@ -575,6 +588,7 @@ hljs.highlightAll(); # Sample information self.source_file = source_file # where the data is stored, i.e. "C:/Users/tschan_d/..." self.sample_number = sample_number # sample number, i.e. 33 + self.start_time = None self.__data_separator = data_separator self.scan_up_time = None # Time to scan up the voltage range in DMA, in (s) @@ -621,25 +635,25 @@ hljs.highlightAll();
  • Classes

  • diff --git a/doc/defs/TOC.html b/doc/defs/TOC.html new file mode 100644 index 0000000..5d4d323 --- /dev/null +++ b/doc/defs/TOC.html @@ -0,0 +1,468 @@ + + + + + + +defs.TOC API documentation + + + + + + + + + + + + + +
    +
    +
    +

    Module defs.TOC

    +
    +
    +

    This submodule contains functionality to calibrate TOC measurement data and analyse them.

    +
    +
    +
    +
    +
    +
    +
    +
    +

    Classes

    +
    +
    +class Calibration +(cal_file: pathlib.Path) +
    +
    +
    +
    + +Expand source code + +
    class Calibration():
    +
    +    def __init__(self, cal_file: Path):
    +
    +        self.metadata = CalibrationMetadata()
    +        self.curve = CalibrationCurve()
    +        self.cal_file = cal_file
    +        self.calibrant_data = None
    +        self.__results_index = None
    +        self.excluded_points = None
    +
    +        self.__read_calibration_file()
    +
    +        self.mask = None
    +
    +    def __read_calibration_file(self):
    +
    +        conctr = []
    +        diltns = []
    +        area_1 = []
    +        area_2 = []
    +        area_3 = []
    +
    +        results_flag = False
    +
    +        with open(self.cal_file, 'r', errors = 'ignore') as file:
    +
    +            results_index = None
    +
    +            for index, line in enumerate(file.readlines()):
    +
    +                # Reading in the metadata
    +                if index == 2:
    +                    self.metadata.standard = float(line.split()[1])
    +                if index == 3:
    +                    self.metadata.unit = line.split()[1]
    +                if index == 4:
    +                    self.metadata.prepared = line.split()[1]
    +                if index == 5:
    +                    self.metadata.measured = line.split()[1]
    +
    +                # Reading in the calibration data
    +                if index >= 10 and results_flag == False:
    +
    +                    if line.startswith('CALIBRATION RESULTS'):
    +
    +                        results_flag = True
    +                        continue
    +
    +                    elif not results_flag:
    +
    +                        if line == '\n':
    +                            continue
    +
    +                        items = line.split()
    +
    +                        conctr.append(float(items[0]))
    +                        diltns.append(float(items[1]))
    +                        area_1.append(float(items[2]))
    +                        area_2.append(float(items[3]))
    +                        area_3.append(float(items[4]))
    +
    +                if results_flag == True:
    +
    +                    if line.startswith('-'):
    +
    +                        if results_index is None:
    +                            self.__results_index = index + 1
    +                            continue
    +                        else:
    +                            continue
    +
    +                    items = line.split()
    +
    +                    if index == self.__results_index:
    +                        self.curve.exists = True
    +                        self.curve.slope = float(items[1])
    +                    elif index == self.__results_index + 1:
    +                        self.curve.intercept = float(items[1])
    +                    elif index == self.__results_index + 2:
    +                        self.curve.R2_value = float(items[1])
    +                    elif index == self.__results_index + 3:
    +                        if items[1] == 'None':
    +                            continue
    +                        else:
    +                            del items[0]
    +                            self.curve.excluded = [int(items[i].replace(',', '')) for i in range(len(items))]
    +                    elif index == self.__results_index + 4:
    +                        self.curve.lower_bound = float(items[1])
    +                    elif index == self.__results_index + 5:
    +                        self.curve.upper_bound = float(items[1])
    +                    elif index == self.__results_index + 6:
    +                        self.curve.computed_on = pd.to_datetime(items[1] + ' ' + items[2])
    +
    +        self.calibrant_data = pd.DataFrame({'concentration': conctr,
    +                                            'dilution': diltns, 
    +                                            'area_1': area_1, 
    +                                            'area_2': area_2, 
    +                                            'area_3': area_3,
    +                                            'area_average': [(a1 + a2 + a3) / 3 for a1, a2, a3 in zip(area_1, area_2, area_3)]})
    +    def produce_statistics():
    +        # TODO
    +        pass
    +
    +    def calibrate(self, excluded_points = [], write_to_new_file = False):
    +
    +        if excluded_points:
    +            self.curve.excluded = excluded_points
    +            self.mask = [False if i + 1 in self.curve.excluded else True for i in range(len(self.calibrant_data))]
    +        else:
    +            self.curve.excluded = None
    +            self.mask = [True for i in range(len(self.calibrant_data))]
    +
    +        self.curve.calibrate(x_data=self.calibrant_data['area_average'].values[self.mask], y_data=self.calibrant_data['concentration'].values[self.mask])
    +
    +        with open(self.cal_file, 'r', errors = 'ignore') as file:
    +            lines = file.readlines()
    +
    +        if self.__results_index is None:
    +
    +            lines.append('\nCALIBRATION RESULTS\n')
    +            lines.append('----------------------------------------------------------------------\n')
    +            lines.append('Slope\t\t' + f"{self.curve.slope:2.4f}\n")
    +            lines.append('Intercept\t' + f"{self.curve.intercept:2.4f}\n")
    +            lines.append('R2\t\t' + f"{self.curve.R2_value:1.4f}\n")
    +            if self.curve.excluded is None:
    +                lines.append('EXCLUDED\t' + f"None\n")
    +            else:
    +                lines.append('EXCLUDED\t' + ', '.join([str(n) for n in self.curve.excluded]) + '\n')
    +            lines.append('FROM\t\t' + f"{self.curve.lower_bound:2.4f}\n")
    +            lines.append('TO\t\t' + f"{self.curve.upper_bound:2.4f}\n")
    +            lines.append('COMPUTED\t' + f"{self.curve.computed_on:%d.%m.%Y %H:%M:%S}")
    +
    +        else:
    +            lines[self.__results_index] = 'Slope\t\t' + f"{self.curve.slope:2.4f}\n"
    +            lines[self.__results_index + 1] ='Intercept\t' + f"{self.curve.intercept:2.4f}\n"
    +            lines[self.__results_index + 2] ='R2\t\t' + f"{self.curve.R2_value:1.4f}\n"
    +            if self.curve.excluded is None:
    +                lines[self.__results_index + 3] = 'EXCLUDED\t' + f"None\n"
    +            else:
    +                lines[self.__results_index + 3] = 'EXCLUDED\t' + ', '.join([str(n) for n in self.curve.excluded]) + '\n'
    +            lines[self.__results_index + 4] ='FROM\t\t' + f"{self.curve.lower_bound:2.4f}\n"
    +            lines[self.__results_index + 5] ='TO\t\t' + f"{self.curve.upper_bound:2.4f}\n"
    +            lines[self.__results_index + 6] ='COMPUTED\t' + f"{self.curve.computed_on:%d.%m.%Y %H:%M:%S}"
    +
    +        if write_to_new_file == False:
    +
    +            with open(self.cal_file, 'w', errors = 'ignore') as file:
    +                file.writelines(lines)
    +
    +        elif write_to_new_file == True:
    +
    +            directory_path = self.cal_file.parents[0]
    +            new_file = self.cal_file.stem + '_' + self.curve.computed_on.strftime('%d_%m_%Y_%H_%M_%S') + '.cal'
    +            new_path = PurePath(directory_path, new_file)
    +
    +            with open(new_path, 'w', errors = 'ignore') as file:
    +                            file.writelines(lines)
    +
    +    def compute(self, sample_areas):
    +        return self.curve.compute(sample_areas=sample_areas)
    +
    +

    Methods

    +
    +
    +def calibrate(self, excluded_points=[], write_to_new_file=False) +
    +
    +
    +
    +
    +def compute(self, sample_areas) +
    +
    +
    +
    +
    +def produce_statistics() +
    +
    +
    +
    +
    +
    +
    +class CalibrationCurve +
    +
    +

    An object representing a calibration curve for TOC, IC or NPOC Measurements.

    +

    Description

    +

    This object instanciates a class that relates the area $A$ measured in the TOC Analyser to the concentration $c$ used in the calibration solutions. The calibration curve is affine, $c=aA+b$ where $a$ and $b$ denote slope and intercept, respectively, of the affine map. This object can be used to represent linear calibration curves for TC (total carbon)-, IC (inorganic carbon)- and NPOC (non-purgeable organic carbon) measurements.

    +

    Attributes

    +
    +
    exists : Bool
    +
    This value is set to True if a calibration curve is already available, else it is set to False.
    +
    slope : float
    +
    Value of the slope for the calibration of concentration (dependent variable) with measured area (independent variable).
    +
    intercep : float
    +
    Value of the intercep for the calibration of concentration (dependent variable) with measured area (independent variable).
    +
    R2_value : float
    +
    The coefficient of determination of the calibration curve. For a good calibration curve, this should be above 0.995.\
    +
    excluded : list
    +
    A list of integers denoting the samples in the calibrant data that are excluded.
    +
    lower_bound : float
    +
    The lower bound value for which this calibration curve can be used.
    +
    upper_bound : float
    +
    The upper bound value for which this calibration curve can be used.
    +
    computed_on : pd.Timestamp
    +
    A timestamp indicating the time at which the calibration curve was computed.
    +
    +
    + +Expand source code + +
    class CalibrationCurve():
    +
    +    r"""An object representing a calibration curve for TOC, IC or NPOC Measurements.
    +
    +    Description
    +    -----------
    +    This object instanciates a class that relates the area $A$ measured in the TOC Analyser to the concentration $c$ used in the calibration solutions. The calibration curve is affine, $c=aA+b$ where $a$ and $b$ denote slope and intercept, respectively, of the affine map. This object can be used to represent linear calibration curves for TC (total carbon)-, IC (inorganic carbon)- and NPOC (non-purgeable organic carbon) measurements. 
    +
    +    Attributes
    +    ----------
    +    exists: Bool
    +        This value is set to `True` if a calibration curve is already available, else it is set to `False`.
    +    slope: float
    +        Value of the slope for the calibration of concentration (dependent variable) with measured area (independent variable).
    +    intercep: float
    +        Value of the intercep for the calibration of concentration (dependent variable) with measured area (independent variable).
    +    R2_value: float
    +        The coefficient of determination of the calibration curve. For a good calibration curve, this should be above 0.995.\
    +    excluded: list
    +        A list of integers denoting the samples in the calibrant data that are excluded.
    +    lower_bound: float
    +        The lower bound value for which this calibration curve can be used.
    +    upper_bound: float
    +        The upper bound value for which this calibration curve can be used.
    +    computed_on: pd.Timestamp
    +        A timestamp indicating the time at which the calibration curve was computed.
    +    """
    +
    +    def __init__(self):
    +
    +        # initialises all member variables
    +
    +        self.exists = False
    +        self.slope = None
    +        self.intercept = None
    +        self.R2_value = None
    +        self.excluded = None
    +        self.lower_bound = None
    +        self.upper_bound = None
    +        self.computed_on = None
    +
    +    def __linear_function(self, x_values, a, b):
    +
    +        r"""Computes the value of the calibration curve for the passed parameters.
    +
    +        Description
    +        -----------
    +        Computes the affine map, $c=aA+b$, given data $A$ and parameters $a$ (slope) and $b$ (intercept).
    +
    +        Parameters
    +        ----------
    +        x_values: array-like
    +            An array-like (array or list) object of length $n$ containing the data for the independent variable that are to be mapped.
    +        a: float
    +            The slope of the calibration curve.
    +        b: float
    +            The intercept of the calibration curve.
    +
    +        Returns
    +        -------
    +        y_values: np.ndarray
    +            An array (of size $n$) containing the image under the affine map of `x_values`.
    +        """
    +
    +        if not isinstance(x_values, np.ndarray):
    +            x_values = np.array(x_values)
    +
    +        return a * x_values + b
    +
    +    def __optimise(self, x_data, y_data, p0):
    +        return curve_fit(self.__linear_function, x_data, y_data, p0)
    +
    +    def __check_exist(self):
    +
    +        if self.exists == True:
    +            return True
    +        else:
    +            return False
    +
    +    def __check(self):
    +
    +        if self.__check_exist():
    +            warnings.warn("Calibration alreadyt exists, will be overwritten...")
    +        else:
    +            return
    +
    +    def calibrate(self, x_data, y_data):
    +
    +            """The docstring for this method
    +            
    +            Description
    +            -----------
    +            Bla Bla Bla
    +
    +            Parameters
    +            ----------
    +            x_data: np.ndarray
    +                Bla bla bla
    +            y_data: np.ndarray
    +                Bla Bla Bla
    +            """
    +
    +            self.__check()
    +
    +            parameters, _ = self.__optimise(x_data=x_data, y_data=y_data, p0=[10, 0.1])
    +
    +            self.exists = True
    +            self.slope = parameters[0]
    +            self.intercept = parameters[1]
    +            self.R2_value = compute_R2(y_true = y_data, y_est = self.__linear_function(x_values=x_data, a = parameters[0], b=parameters[1]))
    +            self.lower_bound = np.min(y_data)
    +            self.upper_bound = np.max(y_data)
    +            self.computed_on = pd.to_datetime(datetime.datetime.now())
    +
    +    def compute(self, sample_areas):
    +        return self.__linear_function(x_values=sample_areas, a = self.slope, b = self.intercept)
    +
    +

    Methods

    +
    +
    +def calibrate(self, x_data, y_data) +
    +
    +

    The docstring for this method

    +

    Description

    +

    Bla Bla Bla

    +

    Parameters

    +
    +
    x_data : np.ndarray
    +
    Bla bla bla
    +
    y_data : np.ndarray
    +
    Bla Bla Bla
    +
    +
    +
    +def compute(self, sample_areas) +
    +
    +
    +
    +
    +
    +
    +class CalibrationMetadata +
    +
    +
    +
    + +Expand source code + +
    class CalibrationMetadata():
    +
    +    def __init__(self):
    +
    +        self.standard = None
    +        self.unit = None
    +        self.prepared = None
    +        self.measured = None
    +
    +
    +
    +
    +
    + +
    + + + diff --git a/doc/defs/index.html b/doc/defs/index.html index 8b4d041..e9d8a3b 100644 --- a/doc/defs/index.html +++ b/doc/defs/index.html @@ -33,13 +33,25 @@ hljs.highlightAll();

    Sub-modules

    -
    defs.OFR
    +
    defs.BTOF
    +
    +

    An empty docstring

    +
    +
    defs.Helper

    The docstring for this submodule

    -
    defs.visualising
    +
    defs.OzoneMonitor
    -
    +

    The docstring for this submodule

    +
    +
    defs.SMPS
    +
    +

    The docstring for this submodule

    +
    +
    defs.TOC
    +
    +

    This submodule contains functionality to calibrate TOC measurement data and analyse them.

    @@ -57,8 +69,11 @@ hljs.highlightAll();