From 2af3d548a9a055bbd5055387b6a2315180082ded Mon Sep 17 00:00:00 2001 From: Patrick Wright Date: Tue, 21 Mar 2023 16:18:05 +0100 Subject: [PATCH 01/35] initial implementation --- .gitignore | 3 + src/pypromice/qc/compute_percentiles.py | 139 ++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 src/pypromice/qc/compute_percentiles.py diff --git a/.gitignore b/.gitignore index 5a93fa05..594b8ea9 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ src/pypromice/postprocess/latest_timestamps.pkl # Output positions from transmitted GPS src/pypromice/postprocess/positions.csv + +# sqlite db files +*.db diff --git a/src/pypromice/qc/compute_percentiles.py b/src/pypromice/qc/compute_percentiles.py new file mode 100644 index 00000000..5aeec8a3 --- /dev/null +++ b/src/pypromice/qc/compute_percentiles.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +""" +Compute percentile distributions and write to sqlite db +""" +import sqlite3 +import os +import pandas as pd +from datetime import timedelta +import argparse +from IPython import embed + +def parse_arguments(): + parser = argparse.ArgumentParser() + + parser.add_argument('--l3-filepath', + default='../../../../aws-l3/level_3/', # relative to qc dir + # default='/data/pypromice_aws/aws-l3/level_3/' # full + type=str, + required=False, + help='Path to read level 3 csv files.') + + args = parser.parse_args() + return args + +def make_db_connection(): + print('Creating sqlite3 connection...') + con = sqlite3.connect( # will create db if does not exist + 'percentiles.db', # write to on-disk file at current directory location + isolation_level=None # autocommit mode + ) + + print('Creating sqlite3 cursor...') + cur = con.cursor() + return con, cur + +def create_tables(cur, var_list): + for v in var_list: + print(f'Creating {v} table...') + if v not in ('t_u',): + # No seasonality, create "flat" percentile distribution, single PRIMARY key + try: + cur.execute( + f'create table {v}(' + 'stid text PRIMARY KEY, ' + 'p0 float, p0p5 float, p1 float, p5 float, p10 float, ' + # 'p25 float, p33 float, p50 float, p66 float, p75 float, ' + 'p90 float, p95 float, p99 float, p99p5 float, p100 float, ' + 'years smallint)' + ) + # cur.execute("create unique index percid on percentiles (stid,season)") + except sqlite3.OperationalError: + print(f"----> {v} table already exists!") + elif v=='t_u': + # Here we add the 'season' column, and have a compound PRIMARY key + # TODO: difference between using index or compound PRIMARY? + try: + cur.execute( + f'create table {v}(' + 'stid text, ' + 'season smallint, ' # only used for airtemp + 'p0 float, p0p5 float, p1 float, p5 float, p10 float, ' + # 'p25 float, p33 float, p50 float, p66 float, p75 float, ' + 'p90 float, p95 float, p99 float, p99p5 float, p100 float, ' + 'years smallint, ' + 'PRIMARY KEY (stid, season))' + ) + # cur.execute("create unique index percid on percentiles (stid,season)") + except sqlite3.OperationalError: + print(f"----> {v} table already exists!") + +def clear_tables(cur, var_list): + ''' + Clear all rows from all tables. We run this by default, assuming that anytime + we are running this script we intend to overwrite all rows for all tables. + ''' + for v in var_list: + cur.execute(f'delete from {v}') + print(f'Deleted {cur.rowcount} records from the {v} table.') + +def write_percentiles(cur, var_list): + for x in os.walk(args.l3_filepath): + if (len(x[2]) > 0): # files are present + stid = x[0].split('/')[-1] + csv_file = [s for s in x[2] if '_hour.csv' in s] + if (len(csv_file) > 0) and (stid not in disclude_stations): # csv file is present + print(f'{stid} writing to tables...') + csv_filepath = x[0] + '/' + csv_file[0] + df = pd.read_csv(csv_filepath) + timestamp = pd.to_datetime(df.time) + years = round((timestamp.max()-timestamp.min()) / timedelta(days=365.25)) + quantiles = [0,0.005,0.01,0.05,0.10,0.90,0.95,0.99,0.995,1] + for v in var_list: + if v not in ('t_u',): + exe_list = [stid] # initialize list + for i in quantiles: + exe_list.append(df[f'{v}'].quantile(q=i)) + exe_list.append(years) + # exe_list.insert(0,stid) + cur.execute( + f'insert into {v} ' + '(stid,p0,p0p5,p1,p5,p10,p90,p95,p99,p99p5,p100,years) ' + 'values (?,?,?,?,?,?,?,?,?,?,?,?)', + exe_list + ) + elif v=='t_u': + df.set_index(timestamp, inplace=True) + # data.drop(['time'], axis=1, inplace=True) # drop original time column + winter = df.t_u[(df.index.month >= 1) & (df.index.month <= 3)] + spring = df.t_u[(df.index.month >= 4) & (df.index.month <= 6)] + summer = df.t_u[(df.index.month >= 7) & (df.index.month <= 9)] + fall = df.t_u[(df.index.month >= 10) & (df.index.month <= 12)] + + season_list = [winter,spring,summer,fall] + season_integers = [1,2,3,4] + + for s, s_i in zip(season_list,season_integers): + exe_list = [stid,s_i] # initialize list + for i in quantiles: + exe_list.append(s.quantile(q=i)) + exe_list.append(years) + cur.execute( + f'insert into {v} ' + '(stid,season,p0,p0p5,p1,p5,p10,p90,p95,p99,p99p5,p100,years) ' + 'values (?,?,?,?,?,?,?,?,?,?,?,?,?)', + exe_list + ) + +if __name__ == '__main__': + """Executed from the command line""" + args = parse_arguments() + + var_list = ['t_u','rh_u','p_u','wspd_u'] # one table per var + disclude_stations = ('XXX',) + + con, cur = make_db_connection() + create_tables(cur,var_list) + clear_tables(cur,var_list) + write_percentiles(cur,var_list) + embed() \ No newline at end of file From 8586a912eb17ce24e5094b88e70879408307f17a Mon Sep 17 00:00:00 2001 From: Patrick Wright Date: Wed, 22 Mar 2023 15:02:51 +0100 Subject: [PATCH 02/35] minor tweaks to compute_percentiles.py --- src/pypromice/qc/compute_percentiles.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pypromice/qc/compute_percentiles.py b/src/pypromice/qc/compute_percentiles.py index 5aeec8a3..e3433d11 100644 --- a/src/pypromice/qc/compute_percentiles.py +++ b/src/pypromice/qc/compute_percentiles.py @@ -1,13 +1,13 @@ #!/usr/bin/env python """ -Compute percentile distributions and write to sqlite db +Compute percentile distributions and write to local sqlite db """ import sqlite3 import os import pandas as pd from datetime import timedelta import argparse -from IPython import embed +# from IPython import embed def parse_arguments(): parser = argparse.ArgumentParser() @@ -78,12 +78,13 @@ def clear_tables(cur, var_list): print(f'Deleted {cur.rowcount} records from the {v} table.') def write_percentiles(cur, var_list): + print(f'writing to tables...') for x in os.walk(args.l3_filepath): if (len(x[2]) > 0): # files are present stid = x[0].split('/')[-1] csv_file = [s for s in x[2] if '_hour.csv' in s] if (len(csv_file) > 0) and (stid not in disclude_stations): # csv file is present - print(f'{stid} writing to tables...') + print(stid) csv_filepath = x[0] + '/' + csv_file[0] df = pd.read_csv(csv_filepath) timestamp = pd.to_datetime(df.time) @@ -136,4 +137,4 @@ def write_percentiles(cur, var_list): create_tables(cur,var_list) clear_tables(cur,var_list) write_percentiles(cur,var_list) - embed() \ No newline at end of file + # embed() \ No newline at end of file From 9f2a0ec00bee1eeb5b9313b8d5f54bc94948ebe8 Mon Sep 17 00:00:00 2001 From: Patrick Wright Date: Wed, 22 Mar 2023 15:03:19 +0100 Subject: [PATCH 03/35] typo in aws.py --- src/pypromice/process/aws.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pypromice/process/aws.py b/src/pypromice/process/aws.py index 92379ec5..b3aa6aa5 100644 --- a/src/pypromice/process/aws.py +++ b/src/pypromice/process/aws.py @@ -463,7 +463,7 @@ def mergeVars(ds_list, variables, cols=['lo','hi','OOL']): # ---------- ds_list : list List of xarray.Dataset objects - varaibles : pandas.DataFrame + variables : pandas.DataFrame Variable look up table cols : str, optional Variable column names to merge by. The default is ['lo','hi','OOL']. From df91382f4eb39020a4e8687952cc284551362ce0 Mon Sep 17 00:00:00 2001 From: Patrick Wright Date: Wed, 22 Mar 2023 15:04:46 +0100 Subject: [PATCH 04/35] stub in prelim location for running percentile QC check --- src/pypromice/process/L1toL2.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 625f1408..c9ab0e76 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -45,7 +45,9 @@ def toL2(L1, T_0=273.15, ews=1013.246, ei0=6.1071, eps_overcast=1., except Exception as e: print('Flagging and fixing failed:') print(e) - + + ds = percentileQC(ds) + T_100 = _getTempK(T_0) ds['rh_u_cor'] = correctHumidity(ds['rh_u'], ds['t_u'], T_0, T_100, ews, ei0) From 7985e7df4628046ebcaa78bf9d1f46a0dc6a4451 Mon Sep 17 00:00:00 2001 From: patrickjwright Date: Fri, 24 Mar 2023 15:30:49 +0100 Subject: [PATCH 05/35] stub out percentile QC check in L1toL2 --- src/pypromice/process/L1toL2.py | 119 ++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index c9ab0e76..6ce8190f 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -8,6 +8,9 @@ import pandas as pd import os import xarray as xr +import sqlite3 + +from IPython import embed def toL2(L1, T_0=273.15, ews=1013.246, ei0=6.1071, eps_overcast=1., eps_clear=9.36508e-6, emissivity=0.97): @@ -143,6 +146,81 @@ def toL2(L1, T_0=273.15, ews=1013.246, ei0=6.1071, eps_overcast=1., T_0, T_100, ews, ei0) return ds +def percentileQC(ds): + ''' + Parameters + ---------- + ds : xr.Dataset + Level 1 dataset + ''' + stid=ds.station_id + # Switch to pandas + df = ds.to_dataframe() + + # Define threshold dict to hold limit values, and 'hi' and 'lo' percentile. + # Limit values indicate how far we will go beyond the hi and lo percentiles to flag outliers. + var_threshold = { + 't_u': {'limit': 9}, # 'hi' and 'lo' held in separate 'seasons' dict + 'p_u': {'limit': 15}, + 'rh_u': {'limit': 12}, + 'wspd_u': {'limit': 10} + } + + con = sqlite3.connect('../qc/percentiles.db') + cur = con.cursor() + + # Query from the sqlite db for specified percentiles + # Different pattern for t_u, which considers seasons + for k in var_threshold.keys(): + if k == 't_u': + seasons = {1: {}, 2: {}, 3: {}, 4: {}} + sql = f"SELECT p0p5,p99p5,season FROM {k} WHERE season in (1,2,3,4) and stid = ?" + cur.execute(sql, [stid]) + result = cur.fetchall() + for row in result: + seasons[row[2]]['lo'] = row[0] # 0.005 + seasons[row[2]]['hi'] = row[1] # 0.995 + var_threshold[k]['seasons'] = seasons + else: + sql = f"SELECT p0p5,p99p5 FROM {k} WHERE stid = ?" + cur.execute(sql, [stid]) + result = cur.fetchone() # we only expect one row back per station + var_threshold[k]['lo'] = result[0] # 0.005 + var_threshold[k]['hi'] = result[1] # 0.995 + + con.close() # close the database connection (and cursor) + + # TO DO: Set outliers to NaN and write back to df + for k in var_threshold.keys(): + if k == 't_u': + winter = df.t_u[df.index.month.isin([12,1,2])] + spring = df.t_u[df.index.month.isin([3,4,5])] + summer = df.t_u[df.index.month.isin([6,7,8])] + fall = df.t_u[df.index.month.isin([9,10,11])] + season_dfs = [winter,spring,summer,fall] + + for x1,x2 in zip([1,2,3,4], season_dfs): + lower_thresh = var_threshold[k]['seasons'][x1]['lo'] - var_threshold[k]['limit'] + upper_thresh = var_threshold[k]['seasons'][x1]['hi'] + var_threshold[k]['limit'] + + _plot_percentiles_t(k,df,season_dfs,var_threshold,stid) + else: + upper_thresh = var_threshold[k]['hi'] + var_threshold[k]['limit'] + lower_thresh = var_threshold[k]['lo'] - var_threshold[k]['limit'] + + _plot_percentiles(k,df,var_threshold,upper_thresh,lower_thresh,stid) + + # Back to xarray, and re-assign the original attrs + ds_out = df_out.to_xarray() + ds_out = ds_out.assign_attrs(ds.attrs) # Dataset attrs + for x in ds_out.data_vars: # variable-specific attrs + ds_out[x].attrs = ds[x].attrs + + # equivalent to above: + # vals = [xr.DataArray(data=df_out[c], dims=['time'], coords={'time':df_out.index}, attrs=ds[c].attrs) for c in df_out.columns] + # ds_out = xr.Dataset(dict(zip(df_out.columns, vals)), attrs=ds.attrs) + return ds_out + def flagNAN(ds_in, flag_url='https://raw.githubusercontent.com/GEUS-Glaciology-and-Climate/PROMICE-AWS-data-issues/master/flags/', flag_dir='local/flags/'): @@ -1014,6 +1092,47 @@ def _getRotation(): # rad2deg = 1 / deg2rad return deg2rad, rad2deg +def _plot_percentiles_t(k, df, season_dfs, var_threshold, stid): + '''Plot data and percentile thresholds for air temp (seasonal)''' + import matplotlib.pyplot as plt + plt.figure(figsize=(20,12)) + inst_var = k.split('_')[0] + '_i' + i_plot = df[inst_var] + plt.scatter(df.index,i_plot, color='orange', s=3, label='instantaneuous') + plt.scatter(df.index,df[k], color='b', s=3, label='hourly ave') + for x1,x2 in zip([1,2,3,4], season_dfs): + y1 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['lo'] - var_threshold[k]['limit'])) + y2 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['hi'] + var_threshold[k]['limit'])) + y11 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['lo'] )) + y22 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['hi'] )) + plt.scatter(x2.index, y1, color='r',s=5) + plt.scatter(x2.index, y2, color='r', s=5) + plt.scatter(x2.index, y11, color='r', s=0.5) + plt.scatter(x2.index, y22, color='r', s=0.5) + plt.title('{} {}'.format(stid, k)) + plt.legend(loc="lower left") + plt.show() + +def _plot_percentiles(k, df, var_threshold, upper_thresh, lower_thresh, stid): + '''Plot data and percentile thresholds''' + import matplotlib.pyplot as plt + plt.figure(figsize=(20,12)) + inst_var = k.split('_')[0] + '_i' + if k == 'p_u': + i_plot = (df[inst_var]+1000.) + else: + i_plot = df[inst_var] + plt.scatter(df.index,i_plot, color='orange', s=3, label='instantaneuous') + plt.scatter(df.index,df[k], color='b', s=3, label='hourly ave') + plt.axhline(y=upper_thresh, color='r', linestyle='-') + plt.axhline(y=lower_thresh, color='r', linestyle='-') + plt.axhline(y=var_threshold[k]['hi'], color='r', linestyle='--') + plt.axhline(y=var_threshold[k]['lo'], color='r', linestyle='--') + plt.title('{} {}'.format(stid, k)) + plt.legend(loc="lower left") + plt.show() + + if __name__ == "__main__": # unittest.main() pass From 0d9becea5f68dce2e0cf1a07314483abb4d37cb5 Mon Sep 17 00:00:00 2001 From: patrickjwright Date: Fri, 24 Mar 2023 15:32:05 +0100 Subject: [PATCH 06/35] fix season definitions in compute_percentiles.py --- src/pypromice/qc/compute_percentiles.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/pypromice/qc/compute_percentiles.py b/src/pypromice/qc/compute_percentiles.py index e3433d11..877afcc0 100644 --- a/src/pypromice/qc/compute_percentiles.py +++ b/src/pypromice/qc/compute_percentiles.py @@ -106,10 +106,16 @@ def write_percentiles(cur, var_list): elif v=='t_u': df.set_index(timestamp, inplace=True) # data.drop(['time'], axis=1, inplace=True) # drop original time column - winter = df.t_u[(df.index.month >= 1) & (df.index.month <= 3)] - spring = df.t_u[(df.index.month >= 4) & (df.index.month <= 6)] - summer = df.t_u[(df.index.month >= 7) & (df.index.month <= 9)] - fall = df.t_u[(df.index.month >= 10) & (df.index.month <= 12)] + + winter = df.t_u[df.index.month.isin([12,1,2])] + spring = df.t_u[df.index.month.isin([3,4,5])] + summer = df.t_u[df.index.month.isin([6,7,8])] + fall = df.t_u[df.index.month.isin([9,10,11])] + + # winter = df.t_u[df.index.month.isin([12,1,2])] + # spring = df.t_u[(df.index.month >= 3) & (df.index.month <= 5)] + # summer = df.t_u[(df.index.month >= 6) & (df.index.month <= 8)] + # fall = df.t_u[(df.index.month >= 9) & (df.index.month <= 11)] season_list = [winter,spring,summer,fall] season_integers = [1,2,3,4] From c766e9c67b6daaf8ae52ae5b3f364d0ff3151e15 Mon Sep 17 00:00:00 2001 From: patrickjwright Date: Tue, 28 Mar 2023 14:21:15 +0200 Subject: [PATCH 07/35] set percentile outliers to nan --- src/pypromice/process/L1toL2.py | 103 ++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 33 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 6ce8190f..086c42d5 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -10,7 +10,7 @@ import xarray as xr import sqlite3 -from IPython import embed +# from IPython import embed def toL2(L1, T_0=273.15, ews=1013.246, ei0=6.1071, eps_overcast=1., eps_clear=9.36508e-6, emissivity=0.97): @@ -152,6 +152,11 @@ def percentileQC(ds): ---------- ds : xr.Dataset Level 1 dataset + + Returns + ------- + ds_out : xr.Dataset + Level 1 dataset with percentile outliers set to NaN ''' stid=ds.station_id # Switch to pandas @@ -159,6 +164,7 @@ def percentileQC(ds): # Define threshold dict to hold limit values, and 'hi' and 'lo' percentile. # Limit values indicate how far we will go beyond the hi and lo percentiles to flag outliers. + # *_u are used to calculate and define all limits, which are then applied to *_u, *_l and *_i var_threshold = { 't_u': {'limit': 9}, # 'hi' and 'lo' held in separate 'seasons' dict 'p_u': {'limit': 15}, @@ -166,13 +172,12 @@ def percentileQC(ds): 'wspd_u': {'limit': 10} } + # Query from the sqlite db for specified percentiles con = sqlite3.connect('../qc/percentiles.db') cur = con.cursor() - - # Query from the sqlite db for specified percentiles - # Different pattern for t_u, which considers seasons for k in var_threshold.keys(): if k == 't_u': + # Different pattern for t_u, which considers seasons seasons = {1: {}, 2: {}, 3: {}, 4: {}} sql = f"SELECT p0p5,p99p5,season FROM {k} WHERE season in (1,2,3,4) and stid = ?" cur.execute(sql, [stid]) @@ -190,28 +195,58 @@ def percentileQC(ds): con.close() # close the database connection (and cursor) - # TO DO: Set outliers to NaN and write back to df for k in var_threshold.keys(): if k == 't_u': - winter = df.t_u[df.index.month.isin([12,1,2])] - spring = df.t_u[df.index.month.isin([3,4,5])] - summer = df.t_u[df.index.month.isin([6,7,8])] - fall = df.t_u[df.index.month.isin([9,10,11])] - season_dfs = [winter,spring,summer,fall] + # use t_u thresholds to flag t_u, t_l, t_i + base_var = k.split('_')[0] + vars_all = [k, base_var+'_l', base_var+'_i'] + for t in vars_all: + if t in df: + winter = df[t][df.index.month.isin([12,1,2])] + spring = df[t][df.index.month.isin([3,4,5])] + summer = df[t][df.index.month.isin([6,7,8])] + fall = df[t][df.index.month.isin([9,10,11])] + season_dfs = [winter,spring,summer,fall] - for x1,x2 in zip([1,2,3,4], season_dfs): - lower_thresh = var_threshold[k]['seasons'][x1]['lo'] - var_threshold[k]['limit'] - upper_thresh = var_threshold[k]['seasons'][x1]['hi'] + var_threshold[k]['limit'] + # _plot_percentiles_t(k,t,df,season_dfs,var_threshold,stid) # BEFORE OUTLIER REMOVAL + for x1,x2 in zip([1,2,3,4], season_dfs): + print(f'percentile flagging {t} {x1}') + lower_thresh = var_threshold[k]['seasons'][x1]['lo'] - var_threshold[k]['limit'] + upper_thresh = var_threshold[k]['seasons'][x1]['hi'] + var_threshold[k]['limit'] + outliers_upper = x2[x2.values > upper_thresh] + outliers_lower = x2[x2.values < lower_thresh] + outliers = pd.concat([outliers_upper,outliers_lower]) + df.loc[outliers.index,t] = np.nan + df.loc[outliers.index,t] = np.nan - _plot_percentiles_t(k,df,season_dfs,var_threshold,stid) + # _plot_percentiles_t(k,t,df,season_dfs,var_threshold,stid) # AFTER OUTLIER REMOVAL else: - upper_thresh = var_threshold[k]['hi'] + var_threshold[k]['limit'] - lower_thresh = var_threshold[k]['lo'] - var_threshold[k]['limit'] + # use *_u thresholds to flag *_u, *_l, *_i + base_var = k.split('_')[0] + vars_all = [k, base_var+'_l', base_var+'_i'] + for t in vars_all: + if t in df: + print(f'percentile flagging {t}') + upper_thresh = var_threshold[k]['hi'] + var_threshold[k]['limit'] + lower_thresh = var_threshold[k]['lo'] - var_threshold[k]['limit'] + # _plot_percentiles(k,t,df,var_threshold,upper_thresh,lower_thresh,stid) # BEFORE OUTLIER REMOVAL + if t == 'p_i': + # shift p_i so we can use the p_u thresholds + shift_p = df[t]+1000. + outliers_upper = shift_p[shift_p.values > upper_thresh] + outliers_lower = shift_p[shift_p.values < lower_thresh] + else: + outliers_upper = df[t][df[t].values > upper_thresh] + outliers_lower = df[t][df[t].values < lower_thresh] + outliers = pd.concat([outliers_upper,outliers_lower]) + df.loc[outliers.index,t] = np.nan + df.loc[outliers.index,t] = np.nan + + # _plot_percentiles(k,t,df,var_threshold,upper_thresh,lower_thresh,stid) # AFTER OUTLIER REMOVAL - _plot_percentiles(k,df,var_threshold,upper_thresh,lower_thresh,stid) # Back to xarray, and re-assign the original attrs - ds_out = df_out.to_xarray() + ds_out = df.to_xarray() ds_out = ds_out.assign_attrs(ds.attrs) # Dataset attrs for x in ds_out.data_vars: # variable-specific attrs ds_out[x].attrs = ds[x].attrs @@ -1092,43 +1127,45 @@ def _getRotation(): # rad2deg = 1 / deg2rad return deg2rad, rad2deg -def _plot_percentiles_t(k, df, season_dfs, var_threshold, stid): +def _plot_percentiles_t(k, t, df, season_dfs, var_threshold, stid): '''Plot data and percentile thresholds for air temp (seasonal)''' import matplotlib.pyplot as plt plt.figure(figsize=(20,12)) - inst_var = k.split('_')[0] + '_i' + inst_var = t.split('_')[0] + '_i' i_plot = df[inst_var] - plt.scatter(df.index,i_plot, color='orange', s=3, label='instantaneuous') - plt.scatter(df.index,df[k], color='b', s=3, label='hourly ave') + plt.scatter(df.index,i_plot, color='orange', s=3, label='t_i instantaneuous') + if t in ('t_u','t_l'): + plt.scatter(df.index,df[t], color='b', s=3, label=f'{t} hourly ave') for x1,x2 in zip([1,2,3,4], season_dfs): y1 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['lo'] - var_threshold[k]['limit'])) y2 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['hi'] + var_threshold[k]['limit'])) y11 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['lo'] )) y22 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['hi'] )) - plt.scatter(x2.index, y1, color='r',s=5) - plt.scatter(x2.index, y2, color='r', s=5) - plt.scatter(x2.index, y11, color='r', s=0.5) - plt.scatter(x2.index, y22, color='r', s=0.5) - plt.title('{} {}'.format(stid, k)) + plt.scatter(x2.index, y1, color='r',s=1) + plt.scatter(x2.index, y2, color='r', s=1) + plt.scatter(x2.index, y11, color='k', s=1) + plt.scatter(x2.index, y22, color='k', s=1) + plt.title('{} {}'.format(stid, t)) plt.legend(loc="lower left") plt.show() -def _plot_percentiles(k, df, var_threshold, upper_thresh, lower_thresh, stid): +def _plot_percentiles(k, t, df, var_threshold, upper_thresh, lower_thresh, stid): '''Plot data and percentile thresholds''' import matplotlib.pyplot as plt plt.figure(figsize=(20,12)) - inst_var = k.split('_')[0] + '_i' + inst_var = t.split('_')[0] + '_i' if k == 'p_u': i_plot = (df[inst_var]+1000.) else: i_plot = df[inst_var] plt.scatter(df.index,i_plot, color='orange', s=3, label='instantaneuous') - plt.scatter(df.index,df[k], color='b', s=3, label='hourly ave') + if t != inst_var: + plt.scatter(df.index,df[t], color='b', s=3, label=f' {t} hourly ave') plt.axhline(y=upper_thresh, color='r', linestyle='-') plt.axhline(y=lower_thresh, color='r', linestyle='-') - plt.axhline(y=var_threshold[k]['hi'], color='r', linestyle='--') - plt.axhline(y=var_threshold[k]['lo'], color='r', linestyle='--') - plt.title('{} {}'.format(stid, k)) + plt.axhline(y=var_threshold[k]['hi'], color='k', linestyle='--') + plt.axhline(y=var_threshold[k]['lo'], color='k', linestyle='--') + plt.title('{} {}'.format(stid, t)) plt.legend(loc="lower left") plt.show() From 5bdbfbc50d46e4dd199237b299b22426e85e72ad Mon Sep 17 00:00:00 2001 From: patrickjwright Date: Wed, 29 Mar 2023 09:53:05 +0200 Subject: [PATCH 08/35] clean up and comment L1toL2.py --- src/pypromice/process/L1toL2.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 086c42d5..68b57c83 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -158,31 +158,32 @@ def percentileQC(ds): ds_out : xr.Dataset Level 1 dataset with percentile outliers set to NaN ''' - stid=ds.station_id - # Switch to pandas - df = ds.to_dataframe() + stid = ds.station_id + df = ds.to_dataframe() # Switch to pandas # Define threshold dict to hold limit values, and 'hi' and 'lo' percentile. # Limit values indicate how far we will go beyond the hi and lo percentiles to flag outliers. # *_u are used to calculate and define all limits, which are then applied to *_u, *_l and *_i var_threshold = { - 't_u': {'limit': 9}, # 'hi' and 'lo' held in separate 'seasons' dict + 't_u': {'limit': 9}, # 'hi' and 'lo' will be held in 'seasons' dict 'p_u': {'limit': 15}, 'rh_u': {'limit': 12}, 'wspd_u': {'limit': 10} } - # Query from the sqlite db for specified percentiles + # Query from the on-disk sqlite db for specified percentiles con = sqlite3.connect('../qc/percentiles.db') cur = con.cursor() for k in var_threshold.keys(): if k == 't_u': # Different pattern for t_u, which considers seasons + # 1: winter (DecJanFeb), 2: spring (MarAprMay), 3: summer (JunJulAug), 4: fall (SepOctNov) seasons = {1: {}, 2: {}, 3: {}, 4: {}} sql = f"SELECT p0p5,p99p5,season FROM {k} WHERE season in (1,2,3,4) and stid = ?" cur.execute(sql, [stid]) result = cur.fetchall() for row in result: + # row[0] is p0p5, row[1] is p99p5, row[2] is the season integer seasons[row[2]]['lo'] = row[0] # 0.005 seasons[row[2]]['hi'] = row[1] # 0.995 var_threshold[k]['seasons'] = seasons @@ -195,6 +196,7 @@ def percentileQC(ds): con.close() # close the database connection (and cursor) + # Set flagged data to NaN for k in var_threshold.keys(): if k == 't_u': # use t_u thresholds to flag t_u, t_l, t_i @@ -244,13 +246,11 @@ def percentileQC(ds): # _plot_percentiles(k,t,df,var_threshold,upper_thresh,lower_thresh,stid) # AFTER OUTLIER REMOVAL - # Back to xarray, and re-assign the original attrs ds_out = df.to_xarray() ds_out = ds_out.assign_attrs(ds.attrs) # Dataset attrs for x in ds_out.data_vars: # variable-specific attrs ds_out[x].attrs = ds[x].attrs - # equivalent to above: # vals = [xr.DataArray(data=df_out[c], dims=['time'], coords={'time':df_out.index}, attrs=ds[c].attrs) for c in df_out.columns] # ds_out = xr.Dataset(dict(zip(df_out.columns, vals)), attrs=ds.attrs) From fa1c0e1ace95df5332ae562bd5dbff3df60e1c0e Mon Sep 17 00:00:00 2001 From: patrickjwright Date: Wed, 29 Mar 2023 10:08:55 +0200 Subject: [PATCH 09/35] tweak comments --- src/pypromice/qc/compute_percentiles.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/pypromice/qc/compute_percentiles.py b/src/pypromice/qc/compute_percentiles.py index 877afcc0..257285d8 100644 --- a/src/pypromice/qc/compute_percentiles.py +++ b/src/pypromice/qc/compute_percentiles.py @@ -13,8 +13,8 @@ def parse_arguments(): parser = argparse.ArgumentParser() parser.add_argument('--l3-filepath', - default='../../../../aws-l3/level_3/', # relative to qc dir - # default='/data/pypromice_aws/aws-l3/level_3/' # full + default='../../../../aws-l3/level_3/', # relative path to qc dir + # default='/data/pypromice_aws/aws-l3/level_3/' # full path type=str, required=False, help='Path to read level 3 csv files.') @@ -25,8 +25,8 @@ def parse_arguments(): def make_db_connection(): print('Creating sqlite3 connection...') con = sqlite3.connect( # will create db if does not exist - 'percentiles.db', # write to on-disk file at current directory location - isolation_level=None # autocommit mode + 'percentiles.db', # define path and filename + isolation_level = None # autocommit mode ) print('Creating sqlite3 cursor...') @@ -59,7 +59,7 @@ def create_tables(cur, var_list): 'stid text, ' 'season smallint, ' # only used for airtemp 'p0 float, p0p5 float, p1 float, p5 float, p10 float, ' - # 'p25 float, p33 float, p50 float, p66 float, p75 float, ' + # 'p25 float, p33 float, p50 float, p66 float, p75 float, ' # optional add'l percentiles 'p90 float, p95 float, p99 float, p99p5 float, p100 float, ' 'years smallint, ' 'PRIMARY KEY (stid, season))' @@ -92,11 +92,10 @@ def write_percentiles(cur, var_list): quantiles = [0,0.005,0.01,0.05,0.10,0.90,0.95,0.99,0.995,1] for v in var_list: if v not in ('t_u',): - exe_list = [stid] # initialize list + exe_list = [stid] # initialize list with stid for i in quantiles: - exe_list.append(df[f'{v}'].quantile(q=i)) + exe_list.append(df[f'{v}'].quantile(q=i)) # percentiles calculated here! exe_list.append(years) - # exe_list.insert(0,stid) cur.execute( f'insert into {v} ' '(stid,p0,p0p5,p1,p5,p10,p90,p95,p99,p99p5,p100,years) ' @@ -105,13 +104,14 @@ def write_percentiles(cur, var_list): ) elif v=='t_u': df.set_index(timestamp, inplace=True) - # data.drop(['time'], axis=1, inplace=True) # drop original time column + # data.drop(['time'], axis=1, inplace=True) # optionally drop original time column winter = df.t_u[df.index.month.isin([12,1,2])] spring = df.t_u[df.index.month.isin([3,4,5])] summer = df.t_u[df.index.month.isin([6,7,8])] fall = df.t_u[df.index.month.isin([9,10,11])] + # Equivalent to above # winter = df.t_u[df.index.month.isin([12,1,2])] # spring = df.t_u[(df.index.month >= 3) & (df.index.month <= 5)] # summer = df.t_u[(df.index.month >= 6) & (df.index.month <= 8)] From 560c808162127a60ffe3368d97775562fa4210c5 Mon Sep 17 00:00:00 2001 From: patrickjwright Date: Wed, 29 Mar 2023 11:30:07 +0200 Subject: [PATCH 10/35] clean up and comment L1toL2.py --- src/pypromice/process/L1toL2.py | 37 +++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 68b57c83..99a2b296 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -49,14 +49,14 @@ def toL2(L1, T_0=273.15, ews=1013.246, ei0=6.1071, eps_overcast=1., print('Flagging and fixing failed:') print(e) - ds = percentileQC(ds) + ds = percentileQC(ds) # Flag and remove percentile outliers T_100 = _getTempK(T_0) ds['rh_u_cor'] = correctHumidity(ds['rh_u'], ds['t_u'], T_0, T_100, ews, ei0) # Determiune cloud cover - cc = calcCloudCoverage(ds['t_u'], T_0, eps_overcast, eps_clear, # Calculate cloud coverage + cc = calcCloudCoverage(ds['t_u'], T_0, eps_overcast, eps_clear, # Calculate cloud coverage ds['dlr'], ds.attrs['station_id']) ds['cc'] = (('time'), cc.data) @@ -158,6 +158,11 @@ def percentileQC(ds): ds_out : xr.Dataset Level 1 dataset with percentile outliers set to NaN ''' + # Optionally examine flagged data by setting make_plots to True + # This is best done by running aws.py directly and setting 'test_station' + # Plots will be shown before and after flag removal for each var + make_plots = True + stid = ds.station_id df = ds.to_dataframe() # Switch to pandas @@ -210,7 +215,8 @@ def percentileQC(ds): fall = df[t][df.index.month.isin([9,10,11])] season_dfs = [winter,spring,summer,fall] - # _plot_percentiles_t(k,t,df,season_dfs,var_threshold,stid) # BEFORE OUTLIER REMOVAL + if make_plots: + _plot_percentiles_t(k,t,df,season_dfs,var_threshold,stid) # BEFORE OUTLIER REMOVAL for x1,x2 in zip([1,2,3,4], season_dfs): print(f'percentile flagging {t} {x1}') lower_thresh = var_threshold[k]['seasons'][x1]['lo'] - var_threshold[k]['limit'] @@ -221,7 +227,8 @@ def percentileQC(ds): df.loc[outliers.index,t] = np.nan df.loc[outliers.index,t] = np.nan - # _plot_percentiles_t(k,t,df,season_dfs,var_threshold,stid) # AFTER OUTLIER REMOVAL + if make_plots: + _plot_percentiles_t(k,t,df,season_dfs,var_threshold,stid) # AFTER OUTLIER REMOVAL else: # use *_u thresholds to flag *_u, *_l, *_i base_var = k.split('_')[0] @@ -231,7 +238,8 @@ def percentileQC(ds): print(f'percentile flagging {t}') upper_thresh = var_threshold[k]['hi'] + var_threshold[k]['limit'] lower_thresh = var_threshold[k]['lo'] - var_threshold[k]['limit'] - # _plot_percentiles(k,t,df,var_threshold,upper_thresh,lower_thresh,stid) # BEFORE OUTLIER REMOVAL + if make_plots: + _plot_percentiles(k,t,df,var_threshold,upper_thresh,lower_thresh,stid) # BEFORE OUTLIER REMOVAL if t == 'p_i': # shift p_i so we can use the p_u thresholds shift_p = df[t]+1000. @@ -244,7 +252,8 @@ def percentileQC(ds): df.loc[outliers.index,t] = np.nan df.loc[outliers.index,t] = np.nan - # _plot_percentiles(k,t,df,var_threshold,upper_thresh,lower_thresh,stid) # AFTER OUTLIER REMOVAL + if make_plots: + _plot_percentiles(k,t,df,var_threshold,upper_thresh,lower_thresh,stid) # AFTER OUTLIER REMOVAL # Back to xarray, and re-assign the original attrs ds_out = df.to_xarray() @@ -1132,8 +1141,9 @@ def _plot_percentiles_t(k, t, df, season_dfs, var_threshold, stid): import matplotlib.pyplot as plt plt.figure(figsize=(20,12)) inst_var = t.split('_')[0] + '_i' - i_plot = df[inst_var] - plt.scatter(df.index,i_plot, color='orange', s=3, label='t_i instantaneuous') + if inst_var in df: + i_plot = df[inst_var] + plt.scatter(df.index,i_plot, color='orange', s=3, label='t_i instantaneuous') if t in ('t_u','t_l'): plt.scatter(df.index,df[t], color='b', s=3, label=f'{t} hourly ave') for x1,x2 in zip([1,2,3,4], season_dfs): @@ -1154,11 +1164,12 @@ def _plot_percentiles(k, t, df, var_threshold, upper_thresh, lower_thresh, stid) import matplotlib.pyplot as plt plt.figure(figsize=(20,12)) inst_var = t.split('_')[0] + '_i' - if k == 'p_u': - i_plot = (df[inst_var]+1000.) - else: - i_plot = df[inst_var] - plt.scatter(df.index,i_plot, color='orange', s=3, label='instantaneuous') + if inst_var in df: + if k == 'p_u': + i_plot = (df[inst_var]+1000.) + else: + i_plot = df[inst_var] + plt.scatter(df.index,i_plot, color='orange', s=3, label='instantaneuous') if t != inst_var: plt.scatter(df.index,df[t], color='b', s=3, label=f' {t} hourly ave') plt.axhline(y=upper_thresh, color='r', linestyle='-') From 5a17f96968e326bc8a36028c759090ed9e21f7e0 Mon Sep 17 00:00:00 2001 From: patrickjwright Date: Wed, 29 Mar 2023 11:30:35 +0200 Subject: [PATCH 11/35] add plotting capability to compute_percentiles.py --- src/pypromice/qc/compute_percentiles.py | 211 +++++++++++++++++++++++- 1 file changed, 210 insertions(+), 1 deletion(-) diff --git a/src/pypromice/qc/compute_percentiles.py b/src/pypromice/qc/compute_percentiles.py index 257285d8..b1f504b1 100644 --- a/src/pypromice/qc/compute_percentiles.py +++ b/src/pypromice/qc/compute_percentiles.py @@ -5,6 +5,7 @@ import sqlite3 import os import pandas as pd +import numpy as np from datetime import timedelta import argparse # from IPython import embed @@ -23,6 +24,17 @@ def parse_arguments(): return args def make_db_connection(): + ''' + Make connection to on-disk sqlite database + + Parameters + ---------- + None + + Returns + ------- + None + ''' print('Creating sqlite3 connection...') con = sqlite3.connect( # will create db if does not exist 'percentiles.db', # define path and filename @@ -34,6 +46,20 @@ def make_db_connection(): return con, cur def create_tables(cur, var_list): + ''' + Create variable-specific tables in the sqlite database + + Parameters + ---------- + cur : sqlite3.Cursor + cursor on the sqlite database connection + var_list : list + list of variable strings (e.g. 't_u') used to make percentiles + + Returns + ------- + None + ''' for v in var_list: print(f'Creating {v} table...') if v not in ('t_u',): @@ -72,12 +98,37 @@ def clear_tables(cur, var_list): ''' Clear all rows from all tables. We run this by default, assuming that anytime we are running this script we intend to overwrite all rows for all tables. + + Parameters + ---------- + cur : sqlite3.Cursor + cursor on the sqlite database connection + var_list : list + list of variable strings (e.g. 't_u') used to make percentiles + + Returns + ------- + None ''' for v in var_list: cur.execute(f'delete from {v}') print(f'Deleted {cur.rowcount} records from the {v} table.') def write_percentiles(cur, var_list): + ''' + Write percentile data to tables + + Parameters + ---------- + cur : sqlite3.Cursor + cursor on the sqlite database connection + var_list : list + list of variable strings (e.g. 't_u') used to make percentiles + + Returns + ------- + None + ''' print(f'writing to tables...') for x in os.walk(args.l3_filepath): if (len(x[2]) > 0): # files are present @@ -132,6 +183,157 @@ def write_percentiles(cur, var_list): exe_list ) +def _analyze_percentiles(): + ''' + This is run ONLY to examine percentile thresholds with context to full datasets + ''' + for x in os.walk(args.l3_filepath): + if (len(x[2]) > 0): # files are present + stid = x[0].split('/')[-1] + csv_file = [s for s in x[2] if '_hour.csv' in s] + if (len(csv_file) > 0) and (stid not in disclude_stations): # csv file is present + print(stid) + csv_filepath = x[0] + '/' + csv_file[0] + df = pd.read_csv(csv_filepath) + timestamp = pd.to_datetime(df.time) + df.set_index(timestamp, inplace=True) + _percentileQC(df, stid) + +def _percentileQC(df, stid): + ''' + This is the same function that is found in L1toL2.py + Once thresholds are determined, they can be transferred to L1toL2.py + ''' + # Define threshold dict to hold limit values, and 'hi' and 'lo' percentile. + # Limit values indicate how far we will go beyond the hi and lo percentiles to flag outliers. + # *_u are used to calculate and define all limits, which are then applied to *_u, *_l and *_i + var_threshold = { + 't_u': {'limit': 9}, # 'hi' and 'lo' will be held in 'seasons' dict + 'p_u': {'limit': 15}, + 'rh_u': {'limit': 12}, + 'wspd_u': {'limit': 10} + } + + # Query from the on-disk sqlite db for specified percentiles + con = sqlite3.connect('percentiles.db') + cur = con.cursor() + for k in var_threshold.keys(): + if k == 't_u': + # Different pattern for t_u, which considers seasons + # 1: winter (DecJanFeb), 2: spring (MarAprMay), 3: summer (JunJulAug), 4: fall (SepOctNov) + seasons = {1: {}, 2: {}, 3: {}, 4: {}} + sql = f"SELECT p0p5,p99p5,season FROM {k} WHERE season in (1,2,3,4) and stid = ?" + cur.execute(sql, [stid]) + result = cur.fetchall() + for row in result: + # row[0] is p0p5, row[1] is p99p5, row[2] is the season integer + seasons[row[2]]['lo'] = row[0] # 0.005 + seasons[row[2]]['hi'] = row[1] # 0.995 + var_threshold[k]['seasons'] = seasons + else: + sql = f"SELECT p0p5,p99p5 FROM {k} WHERE stid = ?" + cur.execute(sql, [stid]) + result = cur.fetchone() # we only expect one row back per station + var_threshold[k]['lo'] = result[0] # 0.005 + var_threshold[k]['hi'] = result[1] # 0.995 + + con.close() # close the database connection (and cursor) + + # Set flagged data to NaN + for k in var_threshold.keys(): + if k == 't_u': + # use t_u thresholds to flag t_u, t_l, t_i + base_var = k.split('_')[0] + vars_all = [k, base_var+'_l', base_var+'_i'] + for t in vars_all: + if t in df: + winter = df[t][df.index.month.isin([12,1,2])] + spring = df[t][df.index.month.isin([3,4,5])] + summer = df[t][df.index.month.isin([6,7,8])] + fall = df[t][df.index.month.isin([9,10,11])] + season_dfs = [winter,spring,summer,fall] + + _plot_percentiles_t(k,t,df,season_dfs,var_threshold,stid) # BEFORE OUTLIER REMOVAL + for x1,x2 in zip([1,2,3,4], season_dfs): + print(f'percentile flagging {t} {x1}') + lower_thresh = var_threshold[k]['seasons'][x1]['lo'] - var_threshold[k]['limit'] + upper_thresh = var_threshold[k]['seasons'][x1]['hi'] + var_threshold[k]['limit'] + outliers_upper = x2[x2.values > upper_thresh] + outliers_lower = x2[x2.values < lower_thresh] + outliers = pd.concat([outliers_upper,outliers_lower]) + df.loc[outliers.index,t] = np.nan + df.loc[outliers.index,t] = np.nan + + # _plot_percentiles_t(k,t,df,season_dfs,var_threshold,stid) # AFTER OUTLIER REMOVAL + else: + # use *_u thresholds to flag *_u, *_l, *_i + base_var = k.split('_')[0] + vars_all = [k, base_var+'_l', base_var+'_i'] + for t in vars_all: + if t in df: + print(f'percentile flagging {t}') + upper_thresh = var_threshold[k]['hi'] + var_threshold[k]['limit'] + lower_thresh = var_threshold[k]['lo'] - var_threshold[k]['limit'] + _plot_percentiles(k,t,df,var_threshold,upper_thresh,lower_thresh,stid) # BEFORE OUTLIER REMOVAL + if t == 'p_i': + # shift p_i so we can use the p_u thresholds + shift_p = df[t]+1000. + outliers_upper = shift_p[shift_p.values > upper_thresh] + outliers_lower = shift_p[shift_p.values < lower_thresh] + else: + outliers_upper = df[t][df[t].values > upper_thresh] + outliers_lower = df[t][df[t].values < lower_thresh] + outliers = pd.concat([outliers_upper,outliers_lower]) + df.loc[outliers.index,t] = np.nan + df.loc[outliers.index,t] = np.nan + + # _plot_percentiles(k,t,df,var_threshold,upper_thresh,lower_thresh,stid) # AFTER OUTLIER REMOVAL + return None + +def _plot_percentiles_t(k, t, df, season_dfs, var_threshold, stid): + '''Plot data and percentile thresholds for air temp (seasonal)''' + import matplotlib.pyplot as plt + plt.figure(figsize=(20,12)) + inst_var = t.split('_')[0] + '_i' + if inst_var in df: + i_plot = df[inst_var] + plt.scatter(df.index,i_plot, color='orange', s=3, label='t_i instantaneuous') + if t in ('t_u','t_l'): + plt.scatter(df.index,df[t], color='b', s=3, label=f'{t} hourly ave') + for x1,x2 in zip([1,2,3,4], season_dfs): + y1 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['lo'] - var_threshold[k]['limit'])) + y2 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['hi'] + var_threshold[k]['limit'])) + y11 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['lo'] )) + y22 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['hi'] )) + plt.scatter(x2.index, y1, color='r',s=1) + plt.scatter(x2.index, y2, color='r', s=1) + plt.scatter(x2.index, y11, color='k', s=1) + plt.scatter(x2.index, y22, color='k', s=1) + plt.title('{} {}'.format(stid, t)) + plt.legend(loc="lower left") + plt.show() + +def _plot_percentiles(k, t, df, var_threshold, upper_thresh, lower_thresh, stid): + '''Plot data and percentile thresholds''' + import matplotlib.pyplot as plt + plt.figure(figsize=(20,12)) + inst_var = t.split('_')[0] + '_i' + if inst_var in df: + if k == 'p_u': + i_plot = (df[inst_var]+1000.) + else: + i_plot = df[inst_var] + plt.scatter(df.index,i_plot, color='orange', s=3, label='instantaneuous') + if t != inst_var: + plt.scatter(df.index,df[t], color='b', s=3, label=f' {t} hourly ave') + plt.axhline(y=upper_thresh, color='r', linestyle='-') + plt.axhline(y=lower_thresh, color='r', linestyle='-') + plt.axhline(y=var_threshold[k]['hi'], color='k', linestyle='--') + plt.axhline(y=var_threshold[k]['lo'], color='k', linestyle='--') + plt.title('{} {}'.format(stid, t)) + plt.legend(loc="lower left") + plt.show() + if __name__ == '__main__': """Executed from the command line""" args = parse_arguments() @@ -139,8 +341,15 @@ def write_percentiles(cur, var_list): var_list = ['t_u','rh_u','p_u','wspd_u'] # one table per var disclude_stations = ('XXX',) + # THE FOLLOWING WILL WRITE A NEW SQLITE DB + # Intended to be run on an (e.g.) monthly cron + # Turn this off if you want to only run _analyze_percentiles() + # ======================================== con, cur = make_db_connection() create_tables(cur,var_list) clear_tables(cur,var_list) write_percentiles(cur,var_list) - # embed() \ No newline at end of file + # ======================================== + + # Turn this on to make full station plots + # _analyze_percentiles() \ No newline at end of file From d678550aba8d04e217e7cd67415db448eda4ded2 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Fri, 4 Aug 2023 11:23:43 +0200 Subject: [PATCH 12/35] Changing the Percentiles limits. Adding Difference QC. And checking if percentiles.db existst --- src/pypromice/process/L1toL2.py | 93 +++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 99a2b296..340386f0 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -6,6 +6,7 @@ import urllib.request from urllib.error import HTTPError, URLError import pandas as pd +import subprocess import os import xarray as xr import sqlite3 @@ -48,7 +49,9 @@ def toL2(L1, T_0=273.15, ews=1013.246, ei0=6.1071, eps_overcast=1., except Exception as e: print('Flagging and fixing failed:') print(e) - + + + ds = differenceQC(ds) # Flag and Remove difference outliers ds = percentileQC(ds) # Flag and remove percentile outliers T_100 = _getTempK(T_0) @@ -146,6 +149,78 @@ def toL2(L1, T_0=273.15, ews=1013.246, ei0=6.1071, eps_overcast=1., T_0, T_100, ews, ei0) return ds +def differenceQC(ds): + ''' + + Parameters + ---------- + ds : xr.Dataset + Level 1 datset + + Returns + ------- + ds_out : xr.Dataset + Level 1 dataset with difference outliers set to NaN + ''' + + # the differenceQC is not done on the Windspeed + # Optionally examine flagged data by setting make_plots to True + # This is best done by running aws.py directly and setting 'test_station' + # Plots will be shown before and after flag removal for each var + + stid = ds.station_id + df = ds.to_dataframe() # Switch to pandas + + # Define threshold dict to hold limit values, and the difference values. + # Limit values indicate how much a variable has to change to the previous value + # diff_period is how many hours a value can stay the same without being set to NaN + # * are used to calculate and define all limits, which are then applied to *_u, *_l and *_i + + var_threshold = { + 't': {'static_limit': 0.001, 'diff_period' : 1}, + 'p': {'static_limit': 0.0001, 'diff_period' : 24}, + 'rh': {'static_limit': 0.0001, 'diff_period' : 24} + } + + + for k in var_threshold.keys(): + + var_all = [k + '_u',k + '_l',k + '_i'] # appåly to upper, lower boom, and instant + static_lim = var_threshold[k]['static_limit'] # loading static limit + diff_h = var_threshold[k]['diff_period'] # loading diff period + + for v in var_all: + if v in df: + + data = df[k] + diff = data.diff() + diff.fillna(method='ffill', inplace=True) # forward filling all NaNs! + diff = np.array(diff) + + diff_period = np.ones_like(diff) * False + + for i,d in enumerate(diff): # algorithm that ensures values can stay the same within the diff_period + if i > (diff_h-1): + if sum(abs(diff[i-diff_h:i])) < static_lim: + diff_period[i-diff_h:i] = True + + diff_period = np.array(diff_period).astype('bool') + + outliers = df[v][diff_period] # finding outliers in dataframe + + df.loc[outliers.index,v] = np.nan # setting outliers to NaN + + # Back to xarray, and re-assign the original attrs + ds_out = df.to_xarray() + ds_out = ds_out.assign_attrs(ds.attrs) # Dataset attrs + for x in ds_out.data_vars: # variable-specific attrs + ds_out[x].attrs = ds[x].attrs + # equivalent to above: + # vals = [xr.DataArray(data=df_out[c], dims=['time'], coords={'time':df_out.index}, attrs=ds[c].attrs) for c in df_out.columns] + # ds_out = xr.Dataset(dict(zip(df_out.columns, vals)), attrs=ds.attrs) + return ds_out + + def percentileQC(ds): ''' Parameters @@ -158,10 +233,19 @@ def percentileQC(ds): ds_out : xr.Dataset Level 1 dataset with percentile outliers set to NaN ''' + # Check if on-disk sqlite db exists + # If it not exists, then run compute_percentiles.py + + file_path = '../qc/percentiles.db' + script_path = '../qc/compute_percentiles.py' + + if not os.path.isfile(file_path): + subprocess.run(script_path) + # Optionally examine flagged data by setting make_plots to True # This is best done by running aws.py directly and setting 'test_station' # Plots will be shown before and after flag removal for each var - make_plots = True + make_plots = False stid = ds.station_id df = ds.to_dataframe() # Switch to pandas @@ -171,11 +255,12 @@ def percentileQC(ds): # *_u are used to calculate and define all limits, which are then applied to *_u, *_l and *_i var_threshold = { 't_u': {'limit': 9}, # 'hi' and 'lo' will be held in 'seasons' dict - 'p_u': {'limit': 15}, + 'p_u': {'limit': 12}, 'rh_u': {'limit': 12}, - 'wspd_u': {'limit': 10} + 'wspd_u': {'limit': 12}, } + # Query from the on-disk sqlite db for specified percentiles con = sqlite3.connect('../qc/percentiles.db') cur = con.cursor() From 156161f58c58664b7c9fc0bdf8c3e46773726f23 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 08:57:55 +0200 Subject: [PATCH 13/35] Wrong index --- src/pypromice/process/L1toL2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 340386f0..15af8472 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -185,14 +185,14 @@ def differenceQC(ds): for k in var_threshold.keys(): - var_all = [k + '_u',k + '_l',k + '_i'] # appåly to upper, lower boom, and instant + var_all = [k + '_u',k + '_l',k + '_i'] # apply to upper, lower boom, and instant static_lim = var_threshold[k]['static_limit'] # loading static limit diff_h = var_threshold[k]['diff_period'] # loading diff period for v in var_all: if v in df: - data = df[k] + data = df[v] diff = data.diff() diff.fillna(method='ffill', inplace=True) # forward filling all NaNs! diff = np.array(diff) From 2ad99fe7dbbcb6d14083187438833e31eba40546 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 10:00:50 +0200 Subject: [PATCH 14/35] fixing subprocess run compute_percentiles.py --- src/pypromice/process/L1toL2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 15af8472..a5502884 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -240,7 +240,7 @@ def percentileQC(ds): script_path = '../qc/compute_percentiles.py' if not os.path.isfile(file_path): - subprocess.run(script_path) + subprocess.run([script_path],shell=True) # Optionally examine flagged data by setting make_plots to True # This is best done by running aws.py directly and setting 'test_station' From 280797780d0f5143a079b30e4ea8b322d8260e1b Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 10:10:18 +0200 Subject: [PATCH 15/35] Trying to fix subprocess path --- src/pypromice/process/L1toL2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index a5502884..c3bc3603 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -237,10 +237,10 @@ def percentileQC(ds): # If it not exists, then run compute_percentiles.py file_path = '../qc/percentiles.db' - script_path = '../qc/compute_percentiles.py' + script_path = os.path.abspath('../qc/compute_percentiles.py') if not os.path.isfile(file_path): - subprocess.run([script_path],shell=True) + subprocess.run([script_path]) # Optionally examine flagged data by setting make_plots to True # This is best done by running aws.py directly and setting 'test_station' From a66e392ea2c652cae9fc1308b471ec83c9781367 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 10:31:32 +0200 Subject: [PATCH 16/35] script_check --- src/pypromice/process/L1toL2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index c3bc3603..9c84964a 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -238,6 +238,8 @@ def percentileQC(ds): file_path = '../qc/percentiles.db' script_path = os.path.abspath('../qc/compute_percentiles.py') + script_check = os.path.isfile(script_path) + print('Does compute_percentiles exist: {script_check}') if not os.path.isfile(file_path): subprocess.run([script_path]) From b9d29d51cf94905f108b8ffb457a34b5412a3158 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 10:53:34 +0200 Subject: [PATCH 17/35] Update L1toL2.py --- src/pypromice/process/L1toL2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 9c84964a..19214647 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -239,7 +239,7 @@ def percentileQC(ds): file_path = '../qc/percentiles.db' script_path = os.path.abspath('../qc/compute_percentiles.py') script_check = os.path.isfile(script_path) - print('Does compute_percentiles exist: {script_check}') + print(f'Does compute_percentiles exist: {script_check}') if not os.path.isfile(file_path): subprocess.run([script_path]) From 1c5fd1ad38cf8e5d2cd12f5cb3bdadf53080744e Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 11:29:10 +0200 Subject: [PATCH 18/35] Printing Current Path --- src/pypromice/process/L1toL2.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 19214647..baf4de05 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -238,8 +238,9 @@ def percentileQC(ds): file_path = '../qc/percentiles.db' script_path = os.path.abspath('../qc/compute_percentiles.py') - script_check = os.path.isfile(script_path) - print(f'Does compute_percentiles exist: {script_check}') + + current_path = os.getcwd() + print(f'This is my current Path {current_path}') if not os.path.isfile(file_path): subprocess.run([script_path]) From f3d113e0cf349fe951ef9e8b0d73732f2524d014 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 11:32:44 +0200 Subject: [PATCH 19/35] Update L1toL2.py --- src/pypromice/process/L1toL2.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index baf4de05..d7023c4a 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -242,6 +242,8 @@ def percentileQC(ds): current_path = os.getcwd() print(f'This is my current Path {current_path}') + script_path = os.path.abspath('..') + '/qc/compute_percentiles.py' + if not os.path.isfile(file_path): subprocess.run([script_path]) From c185aaa27bf5ece49875f27e28057c97e89796e6 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 11:42:24 +0200 Subject: [PATCH 20/35] Fixing paths to percentiles and compute_percentiles --- src/pypromice/process/L1toL2.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index d7023c4a..4a901dd2 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -236,15 +236,15 @@ def percentileQC(ds): # Check if on-disk sqlite db exists # If it not exists, then run compute_percentiles.py - file_path = '../qc/percentiles.db' - script_path = os.path.abspath('../qc/compute_percentiles.py') + file_path = '/qc/percentiles.db' + script_path = '/qc/compute_percentiles.py' - current_path = os.getcwd() - print(f'This is my current Path {current_path}') + #current_path = os.getcwd() + #print(f'This is my current Path {current_path}') - script_path = os.path.abspath('..') + '/qc/compute_percentiles.py' if not os.path.isfile(file_path): + print(f'percentiles.db does not exist running {script_path}') subprocess.run([script_path]) # Optionally examine flagged data by setting make_plots to True From 564705fb00ff9b33501d58920c1949544a504f32 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 11:48:14 +0200 Subject: [PATCH 21/35] Update L1toL2.py --- src/pypromice/process/L1toL2.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 4a901dd2..bfca5322 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -236,8 +236,10 @@ def percentileQC(ds): # Check if on-disk sqlite db exists # If it not exists, then run compute_percentiles.py - file_path = '/qc/percentiles.db' - script_path = '/qc/compute_percentiles.py' + base_path = os.getcwd() + + file_path = base_path + '/qc/percentiles.db' + script_path = base_path + '/qc/compute_percentiles.py' #current_path = os.getcwd() #print(f'This is my current Path {current_path}') From 6a55ec7145d5c66fa0c308c0463ce15c5379c763 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 11:56:45 +0200 Subject: [PATCH 22/35] Fixing paths --- src/pypromice/process/L1toL2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index bfca5322..2049ca41 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -238,8 +238,8 @@ def percentileQC(ds): base_path = os.getcwd() - file_path = base_path + '/qc/percentiles.db' - script_path = base_path + '/qc/compute_percentiles.py' + file_path = base_path + '/main/src/pypromice/qc/percentiles.db' + script_path = base_path + '/main/src/pypromice/qc/compute_percentiles.py' #current_path = os.getcwd() #print(f'This is my current Path {current_path}') From 068004d352c6b2407cc7dcf71dbaf6df522bdaae Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 12:54:03 +0200 Subject: [PATCH 23/35] Update L1toL2.py --- src/pypromice/process/L1toL2.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 2049ca41..25a3655c 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -241,8 +241,10 @@ def percentileQC(ds): file_path = base_path + '/main/src/pypromice/qc/percentiles.db' script_path = base_path + '/main/src/pypromice/qc/compute_percentiles.py' + script_exist = os.path.isfile(script_path) + #current_path = os.getcwd() - #print(f'This is my current Path {current_path}') + print(f'Does compute_percintiles.py exist {script_exist}') if not os.path.isfile(file_path): From 53e2d5ca79ea1ddea7135876a7157df587b44de8 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 14:07:53 +0200 Subject: [PATCH 24/35] updating subprocess --- src/pypromice/process/L1toL2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 25a3655c..66421d82 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -241,15 +241,15 @@ def percentileQC(ds): file_path = base_path + '/main/src/pypromice/qc/percentiles.db' script_path = base_path + '/main/src/pypromice/qc/compute_percentiles.py' - script_exist = os.path.isfile(script_path) + #script_exist = os.path.isfile(script_path) #current_path = os.getcwd() - print(f'Does compute_percintiles.py exist {script_exist}') + #print(f'Does compute_percintiles.py exist {script_exist}') if not os.path.isfile(file_path): print(f'percentiles.db does not exist running {script_path}') - subprocess.run([script_path]) + subprocess.run(['python',script_path]) # Optionally examine flagged data by setting make_plots to True # This is best done by running aws.py directly and setting 'test_station' From 64c0e482ff2bd91990155fe45fd781bce163fd32 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 14:16:44 +0200 Subject: [PATCH 25/35] Update L1toL2.py --- src/pypromice/process/L1toL2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 66421d82..46dc451b 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -249,7 +249,7 @@ def percentileQC(ds): if not os.path.isfile(file_path): print(f'percentiles.db does not exist running {script_path}') - subprocess.run(['python',script_path]) + subprocess.call(['python',script_path]) # Optionally examine flagged data by setting make_plots to True # This is best done by running aws.py directly and setting 'test_station' From 879d43d2f25e27c780e4bebc2e147a9a7bdf038d Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 14:22:08 +0200 Subject: [PATCH 26/35] Fixing path to .db file --- src/pypromice/process/L1toL2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 46dc451b..3830dc3f 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -271,7 +271,7 @@ def percentileQC(ds): # Query from the on-disk sqlite db for specified percentiles - con = sqlite3.connect('../qc/percentiles.db') + con = sqlite3.connect(file_path) cur = con.cursor() for k in var_threshold.keys(): if k == 't_u': From 44f43609a37dc4a8a4b4792208eff4a485086d31 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 8 Aug 2023 14:39:24 +0200 Subject: [PATCH 27/35] changing path to l3 data --- src/pypromice/qc/compute_percentiles.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pypromice/qc/compute_percentiles.py b/src/pypromice/qc/compute_percentiles.py index b1f504b1..cb66dcc5 100644 --- a/src/pypromice/qc/compute_percentiles.py +++ b/src/pypromice/qc/compute_percentiles.py @@ -14,8 +14,8 @@ def parse_arguments(): parser = argparse.ArgumentParser() parser.add_argument('--l3-filepath', - default='../../../../aws-l3/level_3/', # relative path to qc dir - # default='/data/pypromice_aws/aws-l3/level_3/' # full path + #default='../../../../aws-l3/level_3/', # relative path to qc dir + default ='/data/geusgk/awsl3-fileshare/aws-l3/level_3', # full path type=str, required=False, help='Path to read level 3 csv files.') From ddf2fd3d9c82995c3bdb6741a5e60203999415ce Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 15 Aug 2023 14:49:10 +0200 Subject: [PATCH 28/35] crash bug if there are no season data for temp. --- src/pypromice/process/L1toL2.py | 21 ++++++------ src/pypromice/qc/compute_percentiles.py | 43 +++++++++++++++---------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 3830dc3f..b9595a74 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -312,15 +312,18 @@ def percentileQC(ds): if make_plots: _plot_percentiles_t(k,t,df,season_dfs,var_threshold,stid) # BEFORE OUTLIER REMOVAL for x1,x2 in zip([1,2,3,4], season_dfs): - print(f'percentile flagging {t} {x1}') - lower_thresh = var_threshold[k]['seasons'][x1]['lo'] - var_threshold[k]['limit'] - upper_thresh = var_threshold[k]['seasons'][x1]['hi'] + var_threshold[k]['limit'] - outliers_upper = x2[x2.values > upper_thresh] - outliers_lower = x2[x2.values < lower_thresh] - outliers = pd.concat([outliers_upper,outliers_lower]) - df.loc[outliers.index,t] = np.nan - df.loc[outliers.index,t] = np.nan - + try: + print(f'percentile flagging {t} {x1}') + lower_thresh = var_threshold[k]['seasons'][x1]['lo'] - var_threshold[k]['limit'] + upper_thresh = var_threshold[k]['seasons'][x1]['hi'] + var_threshold[k]['limit'] + outliers_upper = x2[x2.values > upper_thresh] + outliers_lower = x2[x2.values < lower_thresh] + outliers = pd.concat([outliers_upper,outliers_lower]) + df.loc[outliers.index,t] = np.nan + df.loc[outliers.index,t] = np.nan + except Exception as e: + print(f'{t} Season {x1} is not computed due to lack of data') + print(e) if make_plots: _plot_percentiles_t(k,t,df,season_dfs,var_threshold,stid) # AFTER OUTLIER REMOVAL else: diff --git a/src/pypromice/qc/compute_percentiles.py b/src/pypromice/qc/compute_percentiles.py index cb66dcc5..bb84c240 100644 --- a/src/pypromice/qc/compute_percentiles.py +++ b/src/pypromice/qc/compute_percentiles.py @@ -15,6 +15,7 @@ def parse_arguments(): parser.add_argument('--l3-filepath', #default='../../../../aws-l3/level_3/', # relative path to qc dir + #default = r'C:\Users\rabni\Desktop\AWS_L3\level_3', default ='/data/geusgk/awsl3-fileshare/aws-l3/level_3', # full path type=str, required=False, @@ -255,14 +256,18 @@ def _percentileQC(df, stid): _plot_percentiles_t(k,t,df,season_dfs,var_threshold,stid) # BEFORE OUTLIER REMOVAL for x1,x2 in zip([1,2,3,4], season_dfs): - print(f'percentile flagging {t} {x1}') - lower_thresh = var_threshold[k]['seasons'][x1]['lo'] - var_threshold[k]['limit'] - upper_thresh = var_threshold[k]['seasons'][x1]['hi'] + var_threshold[k]['limit'] - outliers_upper = x2[x2.values > upper_thresh] - outliers_lower = x2[x2.values < lower_thresh] - outliers = pd.concat([outliers_upper,outliers_lower]) - df.loc[outliers.index,t] = np.nan - df.loc[outliers.index,t] = np.nan + try: + print(f'percentile flagging {t} {x1}') + lower_thresh = var_threshold[k]['seasons'][x1]['lo'] - var_threshold[k]['limit'] + upper_thresh = var_threshold[k]['seasons'][x1]['hi'] + var_threshold[k]['limit'] + outliers_upper = x2[x2.values > upper_thresh] + outliers_lower = x2[x2.values < lower_thresh] + outliers = pd.concat([outliers_upper,outliers_lower]) + df.loc[outliers.index,t] = np.nan + df.loc[outliers.index,t] = np.nan + except Exception as e: + print(f'{t} Season {x1} is not computed due to lack of data') + print(e) # _plot_percentiles_t(k,t,df,season_dfs,var_threshold,stid) # AFTER OUTLIER REMOVAL else: @@ -301,14 +306,18 @@ def _plot_percentiles_t(k, t, df, season_dfs, var_threshold, stid): if t in ('t_u','t_l'): plt.scatter(df.index,df[t], color='b', s=3, label=f'{t} hourly ave') for x1,x2 in zip([1,2,3,4], season_dfs): - y1 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['lo'] - var_threshold[k]['limit'])) - y2 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['hi'] + var_threshold[k]['limit'])) - y11 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['lo'] )) - y22 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['hi'] )) - plt.scatter(x2.index, y1, color='r',s=1) - plt.scatter(x2.index, y2, color='r', s=1) - plt.scatter(x2.index, y11, color='k', s=1) - plt.scatter(x2.index, y22, color='k', s=1) + try: + y1 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['lo'] - var_threshold[k]['limit'])) + y2 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['hi'] + var_threshold[k]['limit'])) + y11 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['lo'] )) + y22 = np.full(len(x2.index), (var_threshold[k]['seasons'][x1]['hi'] )) + plt.scatter(x2.index, y1, color='r',s=1) + plt.scatter(x2.index, y2, color='r', s=1) + plt.scatter(x2.index, y11, color='k', s=1) + plt.scatter(x2.index, y22, color='k', s=1) + except Exception as e: + print(f'Season {x1} is not computed due to lack of data') + print(e) plt.title('{} {}'.format(stid, t)) plt.legend(loc="lower left") plt.show() @@ -352,4 +361,4 @@ def _plot_percentiles(k, t, df, var_threshold, upper_thresh, lower_thresh, stid) # ======================================== # Turn this on to make full station plots - # _analyze_percentiles() \ No newline at end of file + _analyze_percentiles() \ No newline at end of file From 1ab0ba008107f49fd0359456563acc0fdfacc981 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 15 Aug 2023 14:49:46 +0200 Subject: [PATCH 29/35] Add files via upload --- src/pypromice/qc/percentiles.db | Bin 0 -> 163840 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/pypromice/qc/percentiles.db diff --git a/src/pypromice/qc/percentiles.db b/src/pypromice/qc/percentiles.db new file mode 100644 index 0000000000000000000000000000000000000000..456877ee0e5347aedf7d189da401d5c0d67725c4 GIT binary patch literal 163840 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 literal 0 HcmV?d00001 From 4a8d581f64ec2d4d187895874ab18356f4c17245 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 15 Aug 2023 15:12:27 +0200 Subject: [PATCH 30/35] Update L1toL2.py --- src/pypromice/process/L1toL2.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index b9595a74..cb598cc2 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -257,6 +257,10 @@ def percentileQC(ds): make_plots = False stid = ds.station_id + stid_type = type(stid) + + print(f'station id {stid}') + print(f'station id type {stid_type}') df = ds.to_dataframe() # Switch to pandas # Define threshold dict to hold limit values, and 'hi' and 'lo' percentile. From 09b4f444e532728f05a2a98d73fa6e154d288152 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 15 Aug 2023 15:29:56 +0200 Subject: [PATCH 31/35] Bug fix - if station do not have var (p, wspd,rh) --- src/pypromice/process/L1toL2.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index cb598cc2..0ce812ff 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -294,9 +294,12 @@ def percentileQC(ds): sql = f"SELECT p0p5,p99p5 FROM {k} WHERE stid = ?" cur.execute(sql, [stid]) result = cur.fetchone() # we only expect one row back per station - var_threshold[k]['lo'] = result[0] # 0.005 - var_threshold[k]['hi'] = result[1] # 0.995 - + if result: + var_threshold[k]['lo'] = result[0] # 0.005 + var_threshold[k]['hi'] = result[1] # 0.995 + else: + print(f'{stid} has no {k} data') + con.close() # close the database connection (and cursor) # Set flagged data to NaN From 305fd69045c0cc117d6000ac3c64d0357da80666 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Tue, 15 Aug 2023 15:44:30 +0200 Subject: [PATCH 32/35] windows and Linux Separator bug --- src/pypromice/qc/compute_percentiles.py | 22 ++++++++++++++-------- src/pypromice/qc/percentiles.db | Bin 163840 -> 90112 bytes 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/pypromice/qc/compute_percentiles.py b/src/pypromice/qc/compute_percentiles.py index bb84c240..23c3f51f 100644 --- a/src/pypromice/qc/compute_percentiles.py +++ b/src/pypromice/qc/compute_percentiles.py @@ -133,11 +133,11 @@ def write_percentiles(cur, var_list): print(f'writing to tables...') for x in os.walk(args.l3_filepath): if (len(x[2]) > 0): # files are present - stid = x[0].split('/')[-1] + stid = x[0].split(os.sep)[-1] csv_file = [s for s in x[2] if '_hour.csv' in s] if (len(csv_file) > 0) and (stid not in disclude_stations): # csv file is present print(stid) - csv_filepath = x[0] + '/' + csv_file[0] + csv_filepath = x[0] + os.sep + csv_file[0] df = pd.read_csv(csv_filepath) timestamp = pd.to_datetime(df.time) years = round((timestamp.max()-timestamp.min()) / timedelta(days=365.25)) @@ -190,7 +190,9 @@ def _analyze_percentiles(): ''' for x in os.walk(args.l3_filepath): if (len(x[2]) > 0): # files are present - stid = x[0].split('/')[-1] + stid = x[0].split(os.sep)[-1] + + csv_file = [s for s in x[2] if '_hour.csv' in s] if (len(csv_file) > 0) and (stid not in disclude_stations): # csv file is present print(stid) @@ -214,11 +216,12 @@ def _percentileQC(df, stid): 'rh_u': {'limit': 12}, 'wspd_u': {'limit': 10} } - + # Query from the on-disk sqlite db for specified percentiles con = sqlite3.connect('percentiles.db') cur = con.cursor() for k in var_threshold.keys(): + if k == 't_u': # Different pattern for t_u, which considers seasons # 1: winter (DecJanFeb), 2: spring (MarAprMay), 3: summer (JunJulAug), 4: fall (SepOctNov) @@ -235,9 +238,12 @@ def _percentileQC(df, stid): sql = f"SELECT p0p5,p99p5 FROM {k} WHERE stid = ?" cur.execute(sql, [stid]) result = cur.fetchone() # we only expect one row back per station - var_threshold[k]['lo'] = result[0] # 0.005 - var_threshold[k]['hi'] = result[1] # 0.995 - + if result: + var_threshold[k]['lo'] = result[0] # 0.005 + var_threshold[k]['hi'] = result[1] # 0.995 + else: + print(f'{stid} has no {k} data') + con.close() # close the database connection (and cursor) # Set flagged data to NaN @@ -361,4 +367,4 @@ def _plot_percentiles(k, t, df, var_threshold, upper_thresh, lower_thresh, stid) # ======================================== # Turn this on to make full station plots - _analyze_percentiles() \ No newline at end of file + #_analyze_percentiles() \ No newline at end of file diff --git a/src/pypromice/qc/percentiles.db b/src/pypromice/qc/percentiles.db index 456877ee0e5347aedf7d189da401d5c0d67725c4..f05e2110a198f575b2a4122a08e2b99726d5b277 100644 GIT binary patch delta 19919 zcmeI4d3Y36w!rUF-Cfn)RkuSDLJ}Y#5Y~W%eJ3P=2%V6SbV5i%AnAlnHrW?xdem{= zh>TF9HR{N?ATErK%Ah`%Q5Y0)V^rK19DVAz%^MXJmC<+Z?XIq_%YX0t-pu#CkMP-j z?yt^0_tx#Id+xb)%I(|z+aLE_mDh!IMF{c#>_27Kkm8;|hjxCfv(rcojzPvL_JR>K zp3w*D9|Xq)&m35N0rug6TZ}ewr z_h@1DBlTx$57qD6=bPpm>V47sC+|GZH=f;|BC{+s&pNURj|@cz8abLT^@putn{j=p zV}Ox^Q_zR4M>gYzP)C1>&fLOjp+sNfG}=$H+qo6&M*F&SHR~%u`$%;A#h}}AUOL5^ zrEkNtL+!m?b~Ve~h9jZ&UPh*?zXdT)3naQ<(4KB;z8s2#+Ikq7>BC?y%%}^sb$4lM z=4Tw{OK?M%y1rfV0NOt#LRv+nlFZ8mlTN2Jb7E$=Z0z@X8%{h^f+slaBEbb+;k+}X^Zu+Wv>kG3YUYLolq z&$2`8ezu?8%zndmvP;<(wwf(sEv$~sWK-E_R>b>_ zngca~^1!%2QJ`0#OF;2|nPtF}aoXeC;a)?3TceCqe=-_^gWC)A_rqw0O?ZR&5; z-RkA)R&}+yNNtX(HR^PAyjrC8QZrSb?|a|pzIS~md`Eo``R?-Ff9~DlUFmJ}M!Xf?iQZw}^Ss&K zfERhb_I%=b$Mb^ci01*%e$O7yF3+W&jhJR9ScLuFFWwQ1UUES2C+LvVMeuPNf$q`YOFh(3iTBui47zi=dK|tg4`Y zI0cIS+bIzA_kfa%PCOwzFmm5mJsw_s!j>|t2ok7KBE|h z1Je}rmQ$eUO{YN68>uSO(CdCB6O%|oRZKyz*)m11+9E-(XiBEWHPFk#NTj;FHKL#s zPJyDAoB}~F3iCn@y^uNpC-E1jK+$ohK+y96B?i9@uMglJn-Q_zt_A;yhR^k?S~L5Ibe z2t9NtWiAX&J)Tyg=&`gCL4Qg$6)r}PrtHB0JmM56de|ut^iUwiFHYW682xABNJ%Xp z=LekvMGrUyg6>cBj;o=AsRPi=ADse42b=;y_ob?Y(Y>hya82G5g!W-1!B`1Je{c>F zba$#w3A#%hsBI{1ovonXI|YjNI|YL76jg*8xv0TcqeVTO??o_%&-|4W;PT z1TCvn&|X`l=oVWf=;nlOStYtD3E^~av_*=3XNv^wNvej?4T-Lm&gR4UTc<$L^-h7r z&~=Gkb3Jrz>IgLO8>c{#nP)MA zc6U)?{WMo)v@4_(B<;;8kJKvYDmS3$S8hPiPNozjt?(-7N->Sq@GFGRay!zC6kU;C zg!BKUnB|h%=s(1HijsnU;S?yk+$j+Bb1?!!4PBNx06*ZRPJyCJoB}~VOI4{v@l+pT zVj|uSeZZKT_VqNmpL+7 zn$oXYlEPtSu_Mz(j!ZgJ>fw$Qo~1+`nYKGJX|vU1t+N)wErW4YZMm|*mT0Rjk@+h4 z0e-Y4fyyhDW?Q07wnXM7)XFOx6R0Gj%(W%jU`wPvp;i)!BvGtfsY{CGwAPl$oTOfP zjX;$b)G5`rL}%L)sS;{%v{IZ~xUs6WP6<0Qo#n`6rZ{Jzb3qEn!b(M|5T-L6nM_aV zhs(upKdZV_+)-ueC0d$ZBGbfmk<=^^)>d178Bge>O(Tq@aEIcs9rzry*nf7;NQkc@8)=%J>C3Ct+0$C1f-LS5+e z7S{@hN-tZY1-3+bsxf|b!{Hu+LD;EuwQz?O(#sKHUq zuViCfT3%JGs40>9QX=tcO135xJT{CCR}@DX4dkcbw;h=VzWC%7e-x zh0ud^Da|2=NHhKx--Fw57BXATA7fqi9UjZ357u)MJ0=+ZH0#Un@HiG8q-Q3#Q8=|0 ze2=HF@IW^_!D0CT8*)W#Rr~-p+xy!#xe;vL{{yaP?S*bQ?F_)WetJKrA zea$NV2~T6~1#Zndf8zb@>4N6d=hw699_h8Q85v&6+Pk~;o*xc%^WAJ-F|a&0`w&=j zZW_bbylBL17Vc(`B<~?MzZo%wEyzhRI77``HnNt@>zc+u@6^ajwjeu=f!>BuwQONl zI>Wn1*Rl4@v}R!k;+LbLdtKbD>lhfUjP%ph%$vr*ScTkrxnt`g)=%ld&#alZj;&_x zhFfp&IA}w6vj;c}x>@OXU;#Hf&Y9oMnkLk<>6*>>NNMJW6UMP})jnO$uvs^8ENk@H z3`P(14owWRS>6-_HPxIpX);^rNinWy-ao01%~Dbf^fYtI>Xz5j|U6 zef$I3?LVc8wc#`d*Z=-M;A+0Xk}-sDqD<2FA$FSmH~Wa4WUsR0>?nJT9fXaPo0!F} zWS6iFY&l!V8dxPOXOq|{*huNe@>vG+8UHlCFg`NgHeN7}8jl+H8n?hU$}fy<#(HCg z(P=arbBqdOve-!}FtT6|mguMTf9N0TZ|N_p`cwKN`hEI7*pBU?$A-~%TspovK=W~O z7hsY`a}x4In}U;@VAJq;TPhjHB@qLUwWVoed^8h7*gBdM5si+rrSM383EvNh7BR{b%oBQ*enn$gEkBYY-yUGOTve1ylIeB{tZsp$G^eLdigiF zQ4jy-PLzLxAEYYH7G93{XtwOavcZKNgzIw%T#E2_Jd2eN=hFlb;v)p$)34*P;|83s=Rzs@CoaKoG5rsEHNB4BNN=GJ z(nsiFdW0S$4a1VNjIe;M%iq8=S^H3X>Jj(5<$Duc#*`uf!DgbN-EZPzHf2bn^#sM% zcN`Xix3$|Q(D?;#;anCG-d6OS&mq>`Z{Z5I(DAkc4tX2bv54($p&_n$@NLj+5Z)Hr z7Jt|(eh2#5;&@x3dEYy@nvDQ&o9H9&gJz96i3?+cz}u#EN!%d(cA(GG6ZCcZ9{oT; z3RQxNt{4Ns2|Kztd9%<8+D1F*a=M0^)S@@hTj`y&0bFyUM|}7bs#7mr^?KLK2a~B+ zs6YAh-#a+r#v7NRw^H@zjYj7MkH)`h2fiva)I$4uX z-q+5lW}~>jPMs+>d)5wMv%p^`28n-Sb4%@HR+IAATp62xuPtLO>HZqn;<{?q0RB4B z1pmb5=XEpLLdRbVwmedImcRZs0)9B%UqjvP^`IyDYkxy6i-5mQod&eKyP<;h2Z!y9 z0XEs(Jk}o^b~>96Y$!NvIDzD!X5KZoH!BB+4GgY+{)tW6IF=0shb@$Zu4b-k?0uHQ zKH3P5J>6kLUux%pW0xHE>+|YajpMMvEHqPN=(Y4NdN;j~9-s$gPk*$fj?HsCy|7j@zmk>Oo?e*5=1cQOv$BMz z7bD6~c5>?&+0&c*TWe!kBmR9}9-O@AoR>f6<^N4yJ_KHVjSz(3x!?Vs4GN-QEVU5ls;j?1I2lGfhTjIos zFwtsOSw3kdoqf$6X9}#p<&zn#BNZBQhl8z-?qn`&0{>$Nh8%v?om8C_8gA$T_M=W{ zDD2aoq~Wa4@T#7mDTRhs_X2<&PHZSzzr6rjmxDvH8fvGUjp(Mile2VZ1Cw;#MZnU7 zLty)W$!Ag0vBH&rf+~QLIAt5Z30tZBz?r}`fnolq{0kxe+pP^!kE!jxGrp@K_v@&))$^_ASDt~& zVWo+lqC07S@;GV4U*oIrVDuz9uxnR^wI_tL`()I(a*VX9lJfRycbblsv6evc&TNU^ zAZV3D2d)F%wn}#*k9BYzsS9<4(=)ulw@h76AfIfOM4uOQrbL@JfNt~xS2h^7KG{GZ zd#ys!-LR3{ogq=rCQheI)Z{c;F4^t98SHkJ8To0MN1Ao_W->R_St`*aV#C`rA4&y$`Ti+bc$DLY))R zDXar-kMaJFm*}5YVd(ETslRhp^Nhc-QXk$Gbc{q-T*P%pOY{t<(NR)=f4PQd9gdWA zbJlVTBP4pOpv4j$v5wmvF4;Y_j@vz7(ydz0?GBUZTY?Ui=+X_`Zjog7^agHsh@^Yj zMs9boL^C(xhBAKJ4noTiyPl0QjvGr1Mc<>34Ze6TYg4Qm_y@MmWo@QSFW5YnwfQf~ z+KkNQx90$K31YuxV~rEW2BV99mtLWd3ce6r8_WqD2t@p+{ieT2ds1svPeYF4VBZm6 z3nUs|}giMR(2}=>0`8>d`>PUJ@Es$8hwl|*|lqMD7s0SJk~tNCy$NpY2upa z`P{L=4XvGmu9s-{<9tq9Cz-wTIGA0u)-{h~YwrTw7+SPOq9Ye_dXYqraN4=rHRofi zu8n(wRZ<_`Z{z)4DbdyKoUV}QPn<@VOZ~kv%7@`XNjI;9TUaL1+XY=J(b1jU?h?uF zkxp)Rv7}qGh}&Hx(UXF9N_6>RZns0S`@>>xH!A7=VhOj~F43+_Ic<~ZUQVM6CA-DT zz;62jY2EbLGRR#LR=YYo^MkzU100@g}5rz)*`L3>XzfWP*XGLItUnwo?Kn`>743ZQNCY`bDk)~t_KU`RS|loo{LmF;s~itu1=B~Q0+km&WR_%g9x zqWxC`ZHgp1o9qF$4y-27fjXBiY^+}d%fz-?i5?bojzlZhkh(g49~YvniPiBlMpQqe z|5_gmdCkp%Q-K|Ue*Q=O5$$vB3az*LfLiH$-?!1HdvErR@*MRvDxX4DZ+H4f8YUl* zi%B-V3s<1`(AHhM24u9y<#!2=^(PU)(#r%^{Q$siap`pe>-_?b@TlHkI&u2}~rs`YB-> zyKP@{3`}>%k?SNiYv(bTzdK_R?eR3H+a&sXPCG7kc>v9t@C={Lwo08iCg>K4HayF9 zH%s&bpo+k}A1{wb2;jN{m@YM2p z&j{rirJeo&PcP0VN67+w8Y0&L=yBA5I^)xg+|;kf*O3olG8e|U+{jJ4WZ+>Y5tn|M z)VJ3YxKzT6snDJ#;cq#P#!K8oZ~a_>8>KToRq9EW2YS*PpTcpfi{O+!qz1GnOWH+V z(eFu8zaRCAo=lW_67~r^LBek)@OTL?RYkwYN&Ws#6_bY?(Os?y+j2R}&Oij_cm#I2xA&yOp}IEk}0$)P)f_wA*0_EXQ{FOxTu# zyE_@!Zize59slJt>#km8HefrvOZ=RgHL^G0Xnc@syfy3L-e9CX?tZ;avo1JKxQziW z=Y_3T61cyF+xrOZLJ9waCqz7o1FcysxCK^%F+=NcfrrK2O4Z3We?7lI?p6 zg>CsgyNTchrzY-e*plLl=a6*ms+>WdF= z3U`a4-=UWUPX;#yvjTSq!v2r^Kl2x84{HtTSL#l6fbVIaRkDiox%WoH$D)(cG6i8y z+Fl9I*qSG%QbeEyZ0S{GHj7ShYwoV%Pvgd?Y4Q!1+3?`4ZCpB3t^Kn}gtd)z!_Pt- zj!B1_wIKuG!qI7YL|~J2A@B@rRJta%c68y7{YJWBw@iQw($kROjP_=dS{Aj_kP^3# zRg}d$FVc_%5hM9f7F3Ty8j_9p@J`R>ns&xfvieKeymcw#sI@DHiHxJ91#EpPpvX9~ ztzv6K4rsPP#*vMxB#S_k&u;2KWw=PZC zou>iT!GxU{I^GhtzBr9%EZ_^!A!sv#yzimL(?*AZ^y~GJ!DoX}cqDptpveD(zePI@ zk3@&4PpS)i-}!d?2K%zT2fbC^G2Y&uhdj41yWJ7`IenR4NGbV*Ttf2k0Xz%6 zk2dcr$%AOky<4hTzg{AuHTQO@X7ybvVhwq}^gsg38>Z8i32k}D^aYNiae2#h!G)xL zP}lf{5VgZ8C;v2_cn(R*N@skT6wVhdhc0%;mrD3izyq`6OCZd*jllo3B59^0E^nP) zu|o7w-aE}(DX_eGdI!hRxV(G%PBp2|%Zl^uQ>RUpw^^cO#oMi3H{k40+`VI}S|8Ss z`pKDbzGW)c%8D;=+e*B29*wuUA~Dr^tyY9{^Ak7`w)s@6vyRN}pBZm)n-)f*@n)9| z%^Da18_{@^gda%Yc@i$K7qMTXE7sPmm+D0zCU2fDXb@Q5J^eg^w(h+FXIX4Mse-_EmMbRJtgGk4$+g92O1O6`;DzxET%jt& z>8+#|F8T`h=E~LAiCbZYn_7clt0>Ipj}hnzz7DHr8R_};=hEvTaraz${kimdm}oTf zTzdWgkMw$L|6{nZn_oZ1(@`o|w(+^~gmHs0)=>4Y^kWBxKYo|a2Ea~(^B#a^y>SBP z_sxWbtK)|gZ@JN-uyD0C`HMsyFJpKcLfUC~Z8#v#`6c1q4Mzq)1080wP%EP!^C5f`AQsMNz>L`x>PPw%CciMWfN! z5^K12WADZmd&hzWyZ-OJvmm~F%MN+p-+TY}*;Qs{XKy+8+;iKhZIWX94k(o#=7JZdR>i7_>i_cn2o4V5?lCxsYZ8!%wNfB^#r3>YwAz`*|r3}{2u zQY&j~@%YT#E?u*F49gkVJu{2HnuR56LQ*vLDIuY;8hieoTTX7D?)JGohUeNRCby0Y zNzSm3(PT8TALwJ>Gb_7GE_oUlQ1$Fv^;GTMm!v_}9={|A3aZK#6jZ%1pQ_~F^yo4; z$3CZjm#nNl19IIx&5W%$Yq6-$fbKnp=M2ii`>4z=Lvpj}_e>u0Oke(Pt_@O3tt>3W z<1=WB2WDpB|EjOrieLQiY^bx!*eb~4pEVSNo#~4&YHbTssa5UT;_>Zi69@Os%p!k^ zuNq0-{O@d~tHRi-dF_AJN&3(!UJgA(YgI$iBh^|LzMis@MHL zj0bm5*}omYLv#aph+G?F^z{H9GB9(9@mFodFaCEn)Ybi;T#F`Yd^LRliqPsLR!5>bvUe>Pzag^4IcyvKz9t(j!tZ-eT--w7|$* z@`ogj-NP2JM$8cg#9PE3qJ@$}l5XrLb`cxIoMHMi#^OcdW}?%gex%T9pB^46E3vfd zBaR7)&x~bIXxp;V4eiqqH>>dy$9ES{%Zm@(8{XN9f;w&h~)njOntW3WS(ILqUsx^;%N>Ht??VfbAn}liw zyC3{!!5Rcq{Y)f+4lEmnK@q=H3%aqxG`jKG3V$7p4? zs!Vwm_)@Xnu%IxHmIIevFhQX^Z(8^8JcFA&XL7=|7APcj&(scX6$p|WtJ+GeQG+#% zqV@JpsP>3X0o-UKBwb%`(QiY~BesLvt36Z0OP}WM>^tV|WeiIFuh1gb50t3XI0=e+ zOWIR{B73pxe|=zvBKk~xmfc5#!kU?NjXV7ug*nOS-e zznrBt<~F!I7=BJ9;$93t`|-su5n7o|4MAa%|EWh48*EKRO)}S6j<^wpe6xQWdT4Vn zY7`QQU))5b(HK3YZ;sMW=}pp61Lkmdo*7Txy8RqiMqeM}UR`V6y$EeE+IpvILwYiPD5xTo7Mdac$t1Z8j-A#Xk}Jk^??3aS!4+Wp_5T9*Jie(OIste z8GE}}KmuZM*J_p=DSi5sHVPb!8f;B^G!YyzMPRZ37t&OTHMssXz4zds9l(>2%E0AX zS)Ce!!XR%QF?ek}oc2TB9@IFER)stWHO|r^_n8ELmPoo^qRkfdEB&Zx_SFCU>e9;% zf=B2K|wum0Nz*20e$lCNfciVkTF7bolIr zqPx`^+i5~MYQ1&EeB<`rQN+(93zh9UAPrmhLt>N_^1U+My2XBstt*Axob7!s;<)Pr)Gv2*)ors!t zqg{pkcF!$8^EesRsTel!n>&k;xmmv0Y)SLd*Q!GaQwuMZUI{j~Uo+SRB;nt$xYgzj zhNV^wVPQ}L8vL{UzWFG2r{ktVuLmgRk43OFe=KUP%$plfy9h-*z4)=|>s!csPAB__ zgWZvbc%@R)^(Jy?*6PrAYx*La+aGLS_^6P{k@vN?#v*V}TDCS5-YKP=|D0=*^7ER6lQH$Z<9j*8+5CyIA{J8n3GYZJx z+-T4FHON2YmNl28xQlHx*xgCC9~I9A72o_Af_MBP%Be2a$@gTCLnvMxUhPG; ze3i&Z!j_c!<&>V<$ku=OnyFkBv{L0)w4#3pp@@(C5*V@P+=(BLEkY6V=mLZ^4Spc= zA__Z*SMlcEk?(bW7HzzWELiTy*=3K*xCPOu;hiNog9&or-)rTHuaKhW%o3L9D?u82 zig*-W=l87r@86&>TTFa??<3IcY5q;}Y1DF6eJ{VK<;a|_*3*!vFfiu856`$~l}qHq z%n66jX+ew^*r0SXMk_b1AuKEkb|B014dm-RZT612hmcRLg5v{&KOpzJlqirhSrhE{ zaaVM8Kie=c;iX?W_s)JFo=Q@+eR}@@WMrZaDa5dt)DRX1g=LW5FF~P8_^m{! z_i;iU2$qt?VkF}Dg$p_ARujAD<*V`j2Gv=38Ux)5vB~6nSm|Sa)xbL|`B&7Ue;=W! zD|EktqI$ z?pQqo+0Ei`!faIk3E3PrXS60VRhKV&5`FiZVVZb6wL}~$T&t7=;=0eH21(J(2V49I_-;4I=UU&yS?hzn8uv^s~yL^w?>FpZ*vaX-$D)QPT+# zIobxTe=@a6mr>@(LP~aK7bIE} zwx+KoO@k4J*dRqwtq2Mhwq#M9$N;o+v$#f*gv;iggmzY4dniiO+OoY5%C# zp1gOZ@5t2KHD02Xi)%f`=`{&C~B0#XX?X!DB{iqyzV_hnoXw< zVxn@<_Q6*NZ4|yX)HcFbTdf(sTIJ%4TN=L94aOIX1`NKk>I{4pIjIk;r>iqmS5<3O zBUQ1g`pPopPUU1}M`dHhN5yf)Ld5_@D}~zhn(2Dev8IWp_9o9vkjZqDE+)PvV);q= zLV1=vST2`ckgbsA$UHeVD#8%hf$$Xnvs*_nPjJ= zK$0eLW?!(o*(q#0)`fY_>}IAi9T`vYJMkg$Y;kvSfY?ZMPBBq2N-5a^nvN0rl(C0m~O|gEi?6&WQz5ix#L==unEG_Gx1&$d|K4+ zgoewH^Obbd7o6CP_{U=_&>T!tfxQwNAfyV+%}tf~3(dj$3C+PY z5z-6-m9M}m;v_yobFkh*b1+^4n-SMoU=^{FMnZG2on^a0P>Gw+9IUI* z9E^*=W`sHmr~=C)G7}_DLUXVUh2~%!1vG;~>6%V=bU~!t1XqEIn%_EIm_40QeZ5+5(Cv4020h=~)Y5>6uyr zz{l{I3n-p2Y|VtFXVt>eGb#b#V|bJTiYH7&g|PIjkYf}spD_^tK29(9k#x~_BTkuG zGJzRbslW`3vA3j)zWZ+ilF`4Y03WFB{3JcZjDF|Dl&I9q)FzDR0M1w2A0JWl%$F3> zh8|Hw#W0;SPBL8R975FNuMdbg=aZ074SYcOK(ZD zjISG~7#%hWk^CkJz~}WA%+HLIc&^x9G*hxw(o4dyTiCv=6+XXr5Wg1xEbc0{6de@} zAO(Nm7)Srcwilou}A1>%mQkKUizeh;BKMHrDRyql*lrhuI7oGQ|a6l^tPa@&NQrfW4K7C#8 zL_+xYXKcRj`4*z45<$5wL@)d1-JBbK(DpbMeWAIKcEhNRi`8yOf08hDQfvcA|MaMS zSfdq?A^T~~qSWurv~rso!V(Vw-x4udKM2X4wq|pD5`-NKGrn^y9$Gu~fTP17LQKJw zrc-4R(Du=i*s_qTkaml3;xi%r=etu{J)Z#Sr)zE4)b9+ml{{S;vYy1H4@O0+$Y9r+-;Dx@~{ZtRYgcGHB~#GOFFa!H~AIXwL97cSyhY zY0*#XPeJuRtFA&waC4$sSm#>O5?W(NHUex5B}i`(pi zP>-1}cnH!XXnIVXjMmtys#09H zo+7n)O&lcj<%eV94I+jqf~0x}&5pQdLeeWn3!NSj5)k&teo z>ktntW&8dINGFiZE73|Vs{^9vRE7DI(8!kv?Dj*eYpsi$DxJ-6H}P7~7^3p7 zeA_T!DzyIgLD0ji84w$Sr7EK)Lfnx$^0w##q&AN_v#xh1Xj?!eAWBG|9k=aZ(;`UU zkr}eC;w_}#!n$KlSJhfpU4;XwR4f1on5za6_o;v#TE}j=a)7ghXg{)W?Sa??tO>AZ zD#SOV+6s`Cp~J-)9WE+yQbvgeV?v0`4jn>!Sx}YCK z#r2n4o&N=*O$On`JPcBnbe@%^%!bqpW2Swc(-6{nPyN~S$ZbeFRXAo|H%myru=7m2 zi3Mt{Tv-!X2ww}hvrVwFR6ay!5!Hu$h}p)sJE0|B=3;#yu@Gw`MLmGFy7y^2sKmZ< z@`ChtWe=7f=mQy+RL2JoTty9GiQ`UDq5pjD_NTmWTXbf*@{G*KE4#Qs+=xzBjobqu zv2jqC)!O}#Ca3y_kcL!!KODD#^o!G8h#&628!jSUoB&0x{r#|%n&1!y9jckv%V5Uxx z8urb}l|vn&?aUh;Rvu;{{YQH8cl2?L4_M>_ZAj;|i=OO%d zGxiM|aiwE-h<7Gp%ifUq3)N?Vq$vSJ1p(6Y=-h#)j_&QlV>n-geal*APfAqu-^2BvpXm#_LH~ zm<<8p4M|@whv=)k0(IOBI@d$eZ~Xoxd7+N18tE$DMghc)AIw&-QqQ(y)&WGFYn%1b z&H>`*91B>L{tYC$VIpg|43cymoIJTWrRmmpkRHV2Lboy#BGZZ)Wf$Fs8RJ{uO7UqQ zV#YTNLXVz6;*!V^sc#%4nV5e}pLhn6WBG<9KX;g&ysHJIUDVZTuOqKz1R)sn`-ocY zN(_TcTnz?&I~{Wze7WmHxhUl@#HH{@#&~~xpsdpX66;_c4OJ;5t{@0$1#SD`V{m%} zX_jQV4TrSHL|df`q`MG}J`9(AOMJ0uV1lo$Bz;Bd)9PjFA?irAwd#>-mujjiQ{}Dv ztURGyq|8>f#5{q^iZzOnia5*}C^y|}I?J?&X@IGb$vKl{COIZyCg$=R@(uE_@))_L z?6z#PEKim$^N_xi9+s9!`$~hQa^s7}tBgk&M;q5ODl^(?RBY7A$V>7`a#S)OS8n(i zFkryI|8WfXvc2@2Y}CTcQM1B9iI_17Pol+<`XAGU{{nB+O>P4owcX7CkO zL2MHrVHL!B3rJ6R`d-4)$2Jz0KBSTU>D#1*WhQv)-(NVrhk(@KaqdD=$GQoCJS-tI z)m2#fSeK^kAU$aSRa=bpPmP%|M*62FiX{4{#>@`Z zi|wN4xJkzxnY{(pPK8SPlf-IOv?M$cx`g$^bRq;`Q&B>QY)b8`z6Ia`0f zarhJ{%Dyq(a_S{0nxBb{X}qC$=3&gnDTCrQi-H#;eMiFNNgpFN_Im@9mj#b&-4lPm zT6@xm$U89kbvY64!W7Q*`k$u@jpOZ=y_l+xo}EdMfM*zXHTyHWiQS1cG;XqY*m7)J z@p`mf4UrlP#e;}1geEYVTYY_bJr|gqKGn$nlrKzPyVeZz?_u&KUJf+nVx5!IM`yv5 zmqW_N9c<3tfvG{K)>XVIXJ4CXS)$INL#f^@PSTzbm=s2g!Cl#8n6$F}Pr;qzVN&@x zY;8FgifsDb8vP&`iY@(4j+*cb6uWJqjIZK2Rhn6W5{gqAqdeNHm;nPZT*A zOjPuvXU|qDaum*|l*`7l2{7rvk5~evVmq=U*)i+{HV>Pw48qD9xNQ6(S0RP7erzmE z8hY)PhjDk9w9ULi{LLtsbe+&gCKMgAeLtgc3pA@*NcAJnQReWrRmcgKuv;uZQBo4O6kTJv)<~ z3&qDK<#%lRf?a{XSJ%K0QgbX6?QKpNwou&dyi4}^WGEi{VXKv&BNU(9wdhRt7$|=G zYDVVXEin0JcKCGnLYVx%ZpNYCo4^#eqJv(p?O{sD*rRm^-DA(0jmD}MJV}wOfab@r zU7+Y(WWVNHTSC#(T{~Czv4P@dv9)98W*Tt!5Xa;@}>+J%7nvD(GN^E|USFLaI1;3+d8ul>pL?63)tH}l&3u1kJ_ zJo59=W00?t>*in4u8A4E9}4R7?+cuCcYk{S8@d>IGQOm|fx3GhFCEES!%I!`4)XW> zZ*}+C{O|dz`1JYfb?=YRyH;9V7entZ!MYfEzw!m;>1NQpYr29})a3)@yXwB*)7{_k zfE1WfW9BcY38$mVJCX?f0l-ijT>qX^`!B0f)?A#aZkHHUOfchRB3TL)2UTS?s+ zLm)q$cyQSP`5CmFkys;xCsHyq=t-o+A`Op(356cN6hDRHmCFw&gqy--X|HoVPlUqc zmew}=)4RizuudOF1dWF&otFJn_jD#qnc-9Kn@_*Plph~Hc%s?IURT9oC5);?MBmEk zPMb}@ntc7)Ty{7+mHm#L&CX%JXX3CPMU9G!Wk<6m>_T=qyNdk{FG~m5L+nu|1?xgo zPR0zQ=NhNmr?bPLXg8Jku_M^A?6(-g5_T!Ol3mpgYdYXk=`_sfOMd;Le(6U%A+Sqf@?z@oMV*3G87M~Rnj)YHWSL%v zm+8%T1GNjLOeC~2mp#W`#Y^_>zoDA|#lJMf3K4Z-a_fMDlluPzlY3o!{pPS1CU2=w z&3qCBlP`4tX8O*yFlAQ5?`}NJg(({brkxtz7^XaX`SXp<+hMAzJDavF6{eO@_b+%m zR9!bgUm7BQol+rx2ps~D|GCTe(;hB@f;vw>n0Kszf_9JhpKq221zI9&?hS=mckzN| z0fpc4(!#~Gv1-|EM1N-;Stls6BD1~^6hEaSSK=Ox^#;B)L4Umg z>_)ASolK?|_9r|eY{VP)O*K~tm>6`c&FkYAVd4Z`Qx;1D#`IFb#9K#i>>uA2^5p!8 z%)8G2p7&a}c-eH?HLdPF$aj|>9c{B2@;l?bkHu=KR`f+9K!0QsjeyD;@&k1>gz4^u zx;bH;ZUJA!zc0{j016)J-t+EdSjRq<3IY_ndeR0_5XjeQ!{zVZJ?4|*2IQYv;XBt& z3k6n*nqe1Hp`Zo7p>N2#u#NBu+n4Ffs|C=tL+_DI4&0Eh+pZL}Xmb3hHXaJ%Xe(JI z6pZEDSJ;X_GPvWq8nfPiL;jC<7%9Z<8swj?zisuUc94HNL$Yb_Jt%PBe7F5W1r)S4 zb+RA04GIIyH(c}_2Zc%B5T653m}}{HbkkBO9EnPt*xE3)lqdnzcQnZVDIpjx1`HT5 z@E^s1{>*ZN{QuuMI5o)s|GD#5gZ%&BQ4ck{Gsyq{f#cvdSYN;(|JQS$t&=zyh7O|DCD*f1F~UqB*wycQGk3 zQOU>4Dr9}7SEWhD`;40ztu=C#%#*0uJmv#45KH~jMMov8CE@Hf_8Zoc`ISi&-xrS- z*A;F0j~*ntx$2w`m#_Ztm-`=`+iJ_suH{}Ho<04C z4yVD%>H)Fo2lsx&FSIQ*=^)>G+o|Ifuh?aX!RP$J1|vC z{)Wwb6)Z=nZE_)Zng(m&xLXsN$tKEob7h;UPfM`Lcswe5mmAm@uU%hp#s}Ocb~~cY z@qtEHw%=d4y$=A-0>pT!ZZog1h=weqF;%cEjI4EAmi#NX-T$z{tx-02gK(u~C0tpb zZ@DXXU&DURgfGWu+4_E zU&Wo-llSH)=ib~4<+@f`vF*5z1R1WazzKWFuRCKXxQ>{#e>e7H>z#ExGnc&yzGlSU zPy~U?sexh5g}B@@KFk1jX=B~mi$?}!ofz1p- zrZWh1)&b^9nbnryrL!LM88xh=U*c+LIshBWMXsy45SROd7y*xY$36Pt(((_>J-PBd zw_1I+PU1esbUcckg8)Oks7K_2Be8_)I}V(55V-ay4u>7UV;Ttn5(S=wjr%w3>Z_=P znCTP8*S`}NBsVs2cfK7N-7si3_k^Gvn=RMh<+L#ToiQ{VOUCasXmqV{Cu#k`;Nxza zFyC$h1Xh%sZPa4X-?6K&f@K6QX<^xZZZ>wIj$@5*XKJv!(Lc+_kg)TuSr% za?dHn)Z#u;0d6idBnw`Ht>79;rx5V2v!u)I*F(X#Cmv=w#Yc%g>#OsfnH@6;41z%$yaY9inG+#-l zKP*kHF4)$06{3ReDp*eYxT>3tcjIofUVr1#NCo$17ZI`!1!mpgOSQb$f$MDSCi`43 zXtaUobZrB_;hh>?5!*t*DiX$_2tu4EVdvEC{*HLN3YI@E8X>XjL1JX}1cDmoM^dvbWOD z8kx$st6({w{rk>gmVVqd54$PB=m7WZeydRIFNAx8-K-PqSbH@;>ov@LS5Hy?4S3!p z%Xka$3CA0#opI1=9Nj(s9f@}pEI$xe<>z{Fn@Cuj_!ZpIQ{DW>|2CaFPr@t(aA3KC zIBCd*diTks<_=Cbao~p5$>7HEGjRYWE=*wx92THTg;M_+hKjs7I@fh?$K9AZ$YD)P zE_dJf22qIMKIlAENq$cnzj}rO+@6wPPlv#B7ajfJvw{S;Sq{Fph%x&O2s%Yw<7lN- zCixf=eY*&Q!tpbGE$;gGKe5A>y4<~F61>Bjdwa@X@-TV|uy!>361Y8nbWuEI2zV|e z1KSRK#_jGgZS;5GJA>%%4Tpfy6hOQ}R|?w+Ao|WOSlG@@(@nY0YJX3hJ#ZE3q_riV zY(c_rGC}r@yx+FsvEZni=v~Z-x1dSjYeo9=0{C6R)8T-5&@!L&PL$U8i`_m3M1T1^ zN25k0m2t;NOrhRaxYH!i%A5+Wass|bsASX=P*IH^XE0ySd(f&ouJ`4lwIiYap9Dp< zz?EOgYb?93T7<5)7&~lzcVq6ZZEHdm+y^V7zw#1T8rFgVd))-*sN1u+_%FqKS_!xX zMHH1T9U++z~)$csu61J<^=p&kWH5zG?!8$!9w@yhX48w9CDlxMa6yh)+>4fbNF+AKY1uP9v@9yB!Kgs%}$x^&O>9? z4(B)z-aV~$?^=OpSjQlIsZ-w||EH^s;bOpm0R#V03>f79|0-mu!T$fBv-%D8|NlDN zEs^IN?En8!AYdvRFxda=cZ?_Ue1rY}zaDBt5Mi+Y|3^bDKEz=E|Ie2&D$O_8|Nje3 z8Okg(*#G~RNcR6P?f>gx`~QI|3*|axYsGDaR$*(3Ogos0Ov+3OOgv5M$~VgsWiMn? zv0uNV(q7UO<7dV*jGG#rHp(?pOMaAuuotk+Aj_;|Lc~|Z!^9S%b^raxiEbV$i|Qd! z+l0q4D2nHap-4FidD=mnWV*VmEUE|e#Fe0^0Lmaok#&|43p*6CmqwySnoK@AdDtk*P)2@tsel1B z;ia6&i#XCBF%o%vB+UMf$c-?D{QQ`1ZvV9*u{Z+f!__lPuU|vqYh1H`{tm~7zDKx? z^-)L;&4jpiMCwzBEX=9DZp7>%>EChJ?WI@4Ez-IBX6%!t@mZ zFVJp5&3xZSnY25G0*(+lU?=3$qT{dFC>nY4JgU;yJnNHNO;}d*O1S4wNbv1k?(N|1 zb?z;51BRHiC?7N3T>erC?vmwH>WjnBCPvkHW{e^tO+!X4t%bsf<%H$)9A2*R`tW107sxBejFK!bai=wvZx@5`2 zrHfamqewR+(?3S0q41ga*!90YKw(z$SwFs>hMMpVeu_N1xh!})VhwURYlpe}UdU+} zjeX1Xc2e0@BPuy{>O{OVNlG6&(a_r5&mlCPGI!^}lCl-kYs8?d_i6Q=Z9$X+mNgz1{0lyOv5d`vh?d71dt2SD`U=LaH;|bNGGCE77 zrm~dCO3ErdONY#8sLxXv*bm_=Y)IrBN#)>7CDc*ivWZejH~X&%4ZA*)pvW|=0rS&Z z6kceL`rLbuLf;avr}Yr5&1`qEZ(r2vWU~&vuH8aTbja*M4X8RqZDgj4>T4W{!w+=& z2^^_Rssq!_?k|s4(s8;}(1wGjbf-L2?{VlF`_2f(Av}lG&~Th0myki8K*~ z@)9l7@BkT33sCK~L=WQE(z4AY%4S@+mEAx2gV$1Z}Sy;NZ~j@y*ZVBp{5UL zU~?4kf)WL4@^NEhEONm1$UNFNAV8_jgB zSG{m4DV#x}x~U{In)2^aC@--_@C!eibNnzzJ~}jd2T)2y?z;DG8Kmb+kPCVJYOlF9 z?g2W>(hI4hTivg!6~~99V&Z+vXNycD3L(KL;yE8kJ)9rwDC9cLj~Y^qmeHtgIT6&& zE1t^?0R~U>r!Ta2o!)8=cgMBE$sGf30z;?1XH2&`e`x}PG?OUpqwwM1w7fXB8wxAu zmnPsR5!A|u>b@X%>QBK4xzGu9KWgxi#1UwZY%i~I(6%!{HpHq?vy|yt`@axx2o9uv zi>MAQM^W9#y8a$TdC)}xMfef5w|o?y!;^xqnqMv(HA-fOQJ%9d8Wcvf&Q~cNiu|z%T z=NCwP=}9*%NLuYyG7T1~s&% z-G>@dd7LTI$)b?sIG%_c4pK1~GNZHdcSu!_4@a-C#(^9r{EAF35?w)8WJ-#eS7L7d zUuZZ))86cBykSW$8T2QxJjr|kifl}lhr=i=k;uHZp^%T%_d0SoPE_D>kcFmyhj(kQ zmL8-VCSz_hS$;OSa?kl?sH^(xHsQiqybZI<_B6O72WIO-67a`1L7m&LSK9?jy{il)BI9fqh#n)}Z;#ItVG+4zV29fhWs_bQ^;FMpE57~Yn!RK0!Q0C@-um3Hi~Oq*PwGICi9FsTUeFFj5l89lfHZx1nhn#<2)9rQ#;u^}(oe zM{oR>N*ENz^7CV8U$O?yMWLotRF7a4!Qw!~)umEssJ!7M`jDU@1J7uj7-C?!2HmJga@guuVu~)p@dP`l3rHB#Qur zdsa8nY{f*&>K>kL?+giUb*l7D_|t-I!&a8+PAN=xmM@+!oiCn6O?PZ7#$LdXf8l~` zbMG_A?^miAjl6UDrO<Kmp0R1=cqMlRd)g5n({hjapj}LN=C8Fd zDA3XF`Ryr_P@r9;YGZUi)b!&k*G><&2Uq4IMWk>{m}!yV}m0q6!7;Ko4G{wM$4`jDi;x%y}@| z7X@dzFDPif3z<>b*1`HT5V8DO@0|pEjFkrxd0RsjM82Ariz*J%-7893BArk*D&#d2< zXQqjX#7fSm@s&$3XCG_aNqj1wSntXw#!Du_+%o)YWBLk9MkS3ZpIFbzC&q&&!@s)g zUd2k>DxX-_$|uG}mn_y zsijM%F{iHxtJJLWiB(rVF)Er2|Ei>~2!l{j`NW!5J~1XV8U9sHUlIP5tn!JKRz5Mt zG#UQY$V5^bM>Zs^6-m`2%T_%yj7(BnE++|;nCg_K(?vM?U#eQI+N?@eRw%z$#wp4a zD->y_ukcTXj{yS)3>YwAz<>b*1`HT5V8DO@0|pHIr!de}S>3ypj2_&e$pETD2YwH* zUHlj~Xxc`5;quD@XkJLP0Y^h<2~|jdq)Wtwz-DMuK*56)Dp)Rol)F?z0#ZBE_nj3= z3!Q`%-{kl_2l2cd7ZUdpAM#d^SdJ^m{|HGd=?{=xkFTMvE?t^2|Gp~Lc~f5FG5rjc zrN)|8)qsP^V|~py@Z?3fppk4L8f_5u(?S|$D9N1a1wzvM13x@nunE$NsmK@7b7)B< zX1m6OFAWQh`O;#cnJ!gq7SWnBhJ^P!vA#|LB**OVE^B)}7SeS9rhh|Z>m{U^Ou5po zs@dW%nu_3mmv2lfF%Jsl@!un;rbA0;J%nl&vGbd81dxAc4WSeep7R;f(F z5Y0Oqh+D{qB}>{wn*+&wvmlM{3P|Jg;^6SJ&fb47g7jC;zUI&Ni6$}`(kdL{b`vc! zNR;w`B-v4=BuE-g^=l!y6|Z-ie3__DmO)w&1qjl%@%Qu$-F+=>K=*dW)r%&IVi2vM zKS5$TRiA~#SH#bm29kKC8c6QO!{1hitE!@N_-;!l*^eIU&U7}aRyJ0!3(-LIbxL-S zIEhzUO>&^N{Lp43)jfuk0X+Cs#ALzOl}@0ZR|4tVba%e)3`w;b#6*n;sM68aWJvsz zXjDChBq>i0RPzBNB)PJNAikuu#XLkhB7^h;w8y)Lvz0{?LLW%6j)l1bS_$R~1fB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0Rsm9n=v3~t$0NYrIfWGnidLj$NV!>{vKb*1`HT5V8DO@0|pEjFkrxd0R#VO47gye zf@%@!sa652VI%OrNH&UX%ck-FQh3b(90eVJ7^)dCp#v{s%>RKG%I0sP2Jy^CC}60> z6AISyDGGM;vdaSMkmMK?Ql~ndCDC2JXanG4sW!lbL_W`iW&N=qh@WA?4lMlN*cc`n z@nuZp1GGb40$*!h7DW-{mGE`uE#~v*@pUupv5Ei>HG!Nsh+=+%i521-Q{HcYyjsO+ z9i+V>uW#g*^50(KLio)2qxs76rwuurwYUZ3|C+RHL-aJr-|?xzAV~t`U!mz353D%w z1$$o<2iOzrIrb8Jlf5euNmz+OqLippu?_$M0e=`N)d7GB8GJQ)zK<5Ae%})E+UnZ! zBMl1=d3-gHe@F)wFLGxkP!K?KvY|}7>dF8NRR*X+Q7(0}19_8ls9CQ=*B{&?Me zrmlEuEMEfo>#6GvHmyC@2B?NRtPOx2#pjX4M~^|?d0lQ@XXo4K$l#{#{Y$za z>Hy?XS34MG&2=?v_;T`#Nii6r4Z3od>l(kEufO22?)@uzXC1>TH3KL)k&`&&XX(n= zN}W7F{*hO8=9IsK0!#jVK@gv}A?w0=vW;0^Cc07~fV7w46$0|_4eRl7(q$-!D12Db zvN05-Qb#;ckj+C}kmoLH_0WNJV?Cg-^RjFGw*l+L`Y;ChKkYHY#ee|=1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfPw#~82BHN|C@XgVQ+systc+is(Q*_m8pv7ib)Dr(|x90 z@lS@20RsjM7%*VKfB^#r3>YwAz<>b*|3@(JkBsfP;kpRu3mPA>>~a4)Zqc@8-zSU4 zaQA6=iJ!RV)TzEBIN+G{MPakSEtofrcYk%_$72g8fX9OU=bPoFgU&1cUybdnYPfm6 z;pAm|%(*gA8}i7#FDKD!Y=FV>%jX`v1@=^S=Le1(TpkQR`yO0zJPR!41GjTMPlPR^{O!88>zu{bM9dMIqH!Mjk$;7Wdpo6?BibDEjin$#bR)wegSHOr#*4f zzZZO8lPDLjpvg1hzkLt{T%pF!Oc$L6K9y@z@jBNLJ2fABB#egdNbc2CVm}@XPGxbcvFH)pzajCW+`%`H1dNykO@0`K<?J*zx*HV!!)495^p2nErHqf9( zW!$|*hh8)szJ_~30_|)Y#=ZaU0**qJ1Pn`r_(@=wOQM#%2Zs+NhL;^Qeml17FYCsD z?;{chVjcLupfu5?x&c3KL`W((4M(6VU;Tl*Ga*hK{M~ZyS^w{EAItp?L?lSqpJ%|B zdgNaYPF}n(2^U=#H+J{%No~>rybq`SkhjMjnr^4$%w$yA@e_{*#Ezf4bn*@f(ZfB| z6rVPE=gmDXT02OwaW?lpyjL^q;~vDk|9o%^|9-`-Hg8S9b(+O$51+Q+v5o`}>j|EG z2$s%O5_>{VL}Fjn1!WIOa2FHq<+RUpoN`k@LPKio2RV%mbO0K5BKGnPaC|J{PGUJU z38gV0z(0-zds9M7J_Jwa>N0#9xPlv~!_W1?Mko5+8pGXtwl}ukqyyZ05|bq|0wi$( ztJ1$|1|zpe`GuCJ<&efzgwNfjx%)7zE|y}I@(cmHx$VCT|Kc+@4WE&0>{9Iu^M z&3qCB&K3DMj8+tQx{zQvy};{4X7*{j3J7{aslXur|39*l8Jc0hfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjF!28`4E#^X|HbMY5&rLlF-87d@t-sPbJ2Zw!1so_cMb5}4*%KX zKU@4q(mLWl@}A_e!hde~kG%VD_-N$~RGF0yH7T1VxI=1AkHI+|26yQ?pihVJ9y$GT zvj=tvNo$iC>(?Qx$Iu>GnSLFjLy{R(iH5}s8mSoFtiv}HzM!KY)iGFw&VSMeD2g$0Fq@F7z}ciyz_;dusy zwk38iEl@}*A1o7+NepXi)L;$uhly&FxWJ>0kd$&R&m)!^QiYd3&7~$H7?k>7p+z31 z7bwIJ1d4h~+Eaoedyxq9W+ zEBc&B#Jw1P76)aG(8_FT2nr7Gdy0?g-6WIpyO3`-ab6pY8if!R+)YGT&u$f^ZwOcQ z=+V-f#PDnYj!aElX>LEqmC@J7xL1@hzqVOD49ORw(;q2}R%DI4_6wcqpA`p2-AbdE zqo_qBByn35d7X+6Pz2vssL59TyVqu7#AJm7q;AQ1xTz0v2^liQVRjsH;sd5OtNRu3 zGW~iqB3UER%B;TX0sXVG$P)Od>9t%5|F$(Uo3Xcx1tcJr#M@nRr1U9`v)mRO_<-o( zNSLn6E`SRSSZxiiKM~_S2k_*1%UW5T8iK+gZyhmsZKQl?)Hsb+g*=FmWtJAX&!k3R zNV;F5%@*}5{itd7)c^eI(#s8kNB@{GkvsJ|NpddfB3FKyFrh;OYvUgMLJayNMJu=b zstscOdO{PKC_yokD?U1W_CnF!YK`qQp&Yf|x?;X@`|c>>=aGfVb{&w0t@|M{$_n{j zneJXNbt3XgZm=vP;3IO3tJ{Bzx*4+cUOmRbvuWuQ%H!TD)ygeu2n&lsRvo-K9=4(u zt#u$0AsoKNhuC=FJVLU4$Z@ zUi{ef^)2K*r;~le!S2XIyi%#@dJ{P`Yjxe^{WeT1 zQ`Zm_95I~ujeax{`GwD_>oLz4`Dz;v^PRdMxy>cB*I4AlOCq`J{E*_FQ%HMqpKA{- zJY07Ui22_^O(8DP%2YK31;-bsYh_Cm)`&>J8lupn(@(`WKZf8Pzld@wJ_x)+C=Q|h zYA>?ot3*Z;wxrZAr}WfDw*JG{Oy#Pel`6lY75zI1MSP@!8WgeT9G2`ZLJ{-m0)#XT zsI4*zJBU~D=G~F+b$%9Yyo!YNc1O-GdtAmXh(-52Pl24ORPXrZt9GZN zC^44h*9%0NIs9l2_uzxqdri&nQ`YDu>b1$!1yn!8N43{nf0UVN>W0EUlhEXYQJ68`M8;1qO#-pAeCwy>JEjk9)tZb=PR*H+w$g zzM>3ykD58Kd)Wizp&Q8}o>)s?G)Ta5_C2LM9nI%Xf_i&_%g|bH3bGoX^+aw zh<;g)0yRGksr&OO6zD+qVQo-=;;6IKJ`ee^SpF=}LXHhDu1;}4$o9!y-#3e=A)8-f zdU{{mgQK-CiVNHwfNUJeR(>z0UL#c(;l~2fO(+=gqdIu&tDdb*0#NXVheyA&?2Cd2 zj4N;C`U48OdaXz5elO%xgqOXCn~?YPk-3)3&!B%0%$@Wi?j=bL5re0g2+ca&(j`dK zHPyEFff*?L`Iu40H|C?Tfq|Qs`prk7!DJR(h}C5?7LL8;XG>81 zlc`O*j50?SQnE9#M+ z$IyzRy*`9iw3XDIF$BU(=^+YI`I!+a<2C5wU_km}N^_7-Pfy24{J#dGg(7vX>Yl2d z@|ZG2u^DUrmzb(ezLme0_mka_wUr)`g7Fq(ccTSH=8``oaqJ$pfHh){Fd*I{_Q09{ zV;{r$AIEbsod4;%U^xH(mC{{dUraQ^>C%yQxt*l_;;uObAD z6~p=eFM<`!i!hx3|G~_H@nSgt|0BHMQ?B9s|Bq%}!}W7nsP3OqC!?rQ3ZJPD_o0Y87ck%Z5z=freQ@iv^Qh_Ly?@@*VupTJ z=DM9wj2ib}WMXr3Giqd!-u}j_Cdf^Tjd12)mk|Gd5zJJP`jmPxjsVa~tx{c8tyPUw z#j5Hn%al8nla(EnjTIji#}x||0~D=rM1X6i>rKa+CYsutJTpNi(@naV_?n32C*=#} zS@K}HTy{aWLY5;7m6=KZl&+PIl(v>y8Q(G9Y&_mL(YS%pW1}5Lg+^&cPLgMmost4c zn#7rX!R}_Ku&Y&SYn{R-cuO+H z`ln~&y(Cy#tRFiTmmlXV>85Y-u^I81DUw*BIoKGXIhbf4NjH5PL7{3bu!>kol+YY( zq|h8pgurIRYWyU<^zDt9#4y5hlY|S;!-fgZ!-O`G^wPIabg+d8z!=RU0iiiqPG}CM zm4IHMP_-0@tytVEErjP`n+wmw1PkmH3|o*ujKxZt3C+O<3eCYZ71%4W0Ya(>iO)=x z_zTU!`U%a!G!fDa0+p}8D&izQLUXX*LUS-)0-F)nSYQ>gl14&vu%1G5FdhP%5$i6n zicpE0&>XC*&>W15z-ELx3#bC?BW5N@oP_3J8w$2_~ z6hFx*-N;4qQnFVvU6Ltj%zj`Gvvb*=YyitLrYF?jU6nu8fCU^%By4H0m@ ziBF{(C6Zj>dDtA`d6>Zh&QUtp1_{6z%_12nGzXh4GzT+4K(A1!`U|WgUXmp=2is3* z4yLcbX2kaqSVgR)x6mAHFQGY@o&uW@+e2U#F_P{=bFkfn=3u%CY(`8M0n0OXawO?2 zEIpekEIrdn!19mb=_sUlO`N2Iu=H$uVd2nX zse+2vNK%BQXWIx%&m;>1Uz6mInYel`@MK-YOk74ZsViQzXiXAPfzT|h zC|_t6MwEwV7(tB@O%zy3oJ2H1XckuVt3nMBK zKvA?7(L8~b#7abSg=S$zbA)DLM6(4pB~~;mNHR#@$uT)QyJu!%azb3|FpWesljamK zbz|wKBvDv;gS7SES+Cbr+A45P#A_leDkA#-^+iWT>LIF!s?N#_$~eV7MRU^)rY?iODrS0Q_RCnoAF_xbbnNl!Pf0mt8nozyyT z>Gm31y-ft4dk(}>1^ky|N2_j=pw(;a@4!@z^-b#45SG{~SdQ?5V(AOf^tF}qcygw z4vC)pyLe3;w~Ti)z+Ku{xAx+Z0o>h;=i3+9b>`mde2X&0wPWXIO2M8tfCNXK`H<)K z_hY7go(DdI+NZTzgT20vr`EJuX`SkT=sSe4`&sJ!Kw{0^A82l3F*b-RZ@OIce$}_Y zgi&|NAR}hy*mZ!j4lvhSZ7|hZf>%Roj}AVghGEyktD)%t-Z81xxOR0&^hF8wQu_yW zxbTj9^uwj)AC`M^<$G?m`fQ!VeT?aNv|D)yFvN;FA{QK~ZSy#A(m~+bpLb^9L7moO zm#M_GbY6*8YFQl+JyC*J#wvQn#<18QZVLCiV&kj_H#Ts0z8x9eFlaaTgrI!SRH(np zX<_&~V`w;*jNfU{=vw1W()xqJ$K5tzzTE@}tRT*s7pbs2fEtV)x|~$OGJ;r*mF?$d z6SuIfw{vH(p9gHD%-zNo=fsnPH8qIs5B3za4&X{{pzXk&H=G5Z`P3^1HhZj9Ls(+D z@j9o}zq)mOHFrTdx6j}DmdNrgcbWE&19yjxnjv7ZYIn--6Q4t!PaXb0_TD-^s-*cF zoy?hdGD%26h>(PM0wL}srzTEFAXo&K;IOzXEHX%h81C*ai!U;bJBzyn*TpU9t?r&6 zzrF8$C&PR1ANRAfpNDy7Cg*gW>gww1l5f$1I&iw%b?5DmJD|n}rc1zwLuX*wqlFd) zb?O8fv5i{e{~vl+8k%dt7)H#uNxI4E&U=dw4ePgT?)FIhOsA+yPY@_X%ZnA@G4ks3lDOnJEoTwV{P3lYE<`)Bc`j;Um4on|0UTygbB2e9sap2Y_@tMmIG+aPFAt6Eoeszb;!3gb`+3iq*CoZ1XmwTYIrd`Hc`cuAK8wapN3 zanx+_y>4R-6oJ7vO%N8~M_M%8j97w&NTHsha+--Z zOMBN?S+Ae?K=0ye>JlrJ8uZ5e4tC70XWr%O0hafZ)U{hrRxJd`YLI zKh&H<7q%1lkJ#N|+|UUSF!4Adqz{6Sp-erY(W3t&B^ok?g1wJZtK8?-Go$qn)f3;E zQPA@0AXDSV8~kqAZClZBa8WCI4+{h#=sp$#Z0LMmfS}712x>Y+=h4|oR_f{fNQmZG zKO#eoX!t}tPBDc#UlmVNpp~g_#R>&{pGwIePeINwp4`D=EuG5hP;Er#D}}3ufWvjl zqAK9YcXFk^?vI3MXewYP!naN6TAkqo*XHPn_o~;WQXzh_aqHK(L$ z`!vk|jg$%#q2Mb8*N+h&hq1$;`06cf3o#d5J}}e~@DQkh5)3uNxtgur9ifD-&x24~ zW>F$3I@*!)oh!q~8cMHAPK!6&G)k}iWT^P|?E~++i#vk!$k!f0hOyvsVrWM9*G}Mm zccWYDX6M0IaKbCjgI`A*Y`VY(WM!Em2{Fff+po_S*qf7RlNG`V*TbH4B&l#%g+OENoBcx9%kgU%5%Zr-ZSqt5+j!aDIT2ju}vPL`lF?j>)prTC!m zjNn2OL1Y0tANZ2+EwwKE-MT&rcC$)r`FU6#CwR}lZ9jO?e7kj7e)m4*#9@!?$!@TUG51EU)Eh}D0TurZqXq% zziCYk3XV0~_lWp)P^afBTu6+0i$O~+A0^@8HwM=qyp;sEpWbs>?L6_dL-@Ls&H5m_ zP{+jS&?~TN+}v1|ejln{IML|(5zmUlyt&e-hrGR(f2MsDo7J>5zzCtTha&mO`z7X+(aWcHya_x~Y5jm3#yqAMFmKMJ@#!+^TWsK-P5E zmNoTdB(gC(tdp=4wvXG7J4{0FDgs=#Y$rjTVpfI@HYfhC`gXtJ(M`OaAGI((I$QjR z?v&8MCSaPF;df2YiguK zWa`G7MCqCy?0jPgbf+50%X! z;Z_IkUD}mKLLL05--T4KA(z&O?M14NrTvcFRiE2MzOuJ&sCqu!aPo?G}3#WRHjUZT)Vr-|>TI@ivZr4lcy60v`g#kiT}2Yl?nCY^Gs zBRGA%d$_vQO>jx6iYQn^z=v;nD8!w#rbZx^^ojbcQ+<5Auhe|`Ox4qe5s$8An~3+q z4$pY#_DcNFwO~NV{Wz#T`1-P`?OTKM{MHF&8_U4$_SqM${wM;EVTsK$b2@`xy-?gy zrXn>RwWdZ&Vs7#6Dbn$}OY0@sB>e9t_13SABtcni8~OAUiN8DZcYuY*SY%HNZLgV!2QGLU6I9$h8viLL0od$p+Lm9kdmjR020Sa$J;w&vgG?auVsXZQR;> zCM0wlv(-rO;Y(+*-4&6bFVW{8Q}OJRhYJpL5y1TJ{P=u4I$Hyehae(f%s)6w~Ag+PQ3cC69#e5U{HjhKV`|hD%ta)N-g@u zL8zX(vr7CDXK?<37pHLB0I0239d~jKJi8Hbd8kh9~%l@jhBt=y6isqJFuVuQqxXrYNRAu zpH{xsB4H=z=}cG}M#2m)8NPT?mjqs*edr>I-)}8%wpemRd{zG3KKHkH&|l5`h+xyF zhwNUpLty{p&ZCoiPlJ2lkUzid?_sDkYOAf-)FX8sEUk_`^hiYD%9W4Kz9r!`a^o9` zok>V0?pb4lNYJ?Znb;VQRFOq4o%t6+ICZBz{@leUG>mle21p%iG!DJK6&!d&K0~Ep z8*M-kX<`>i?99Rb;+6zQ^?H^;VvpN)X*qrsfmh6MB4XP))cr*~*#SR-xa_Cv%bJ*` z&(;fBREy|x`%3kWdf?Z+lqz*vSI}ZW4@?>!+K?8k^&iM$P9!*Zmu|tT9VGA!u2r*g z;>&%t#M>N0S1WFd56uF@#;!Z`9TYORpMlwQQ!wPTNR`r76*Mr5PjeciB(Wjw=$1ed zeXwid=hr0B-L;^P%$p>vI(o`2r5#DAoe~~6p#OCO6PTNjSWfIYEZSilF5&+Y=3$dz_XJDJDPMP2A$o|g^{Lu!g>wPP#vCIbNQ*ac_88b2c1~c3m-6_35yk&|Aa7xM-n6dC4ii7)`Noz;V+`LLb~U(a zP+xzyevsZ`JsaJzvQM(#gfgMAbiXuAXSt5Or0Dze|N8$^nneC|{MC@7GFJ*h_I51`&q;+6vM&YM#fwlSkuvbM* zz{Y|C*zQ*vNBng9tsFT-?8~K148+Z*#YYmeiw3E28{5yNtjkM#6xQ+<2OQ6Qt!?bw;QWL5*HF)^#Lp!A2Ac}(+5ufTt6wdnCBh#5gf8G~( zBy$;wT1CAz3rMwzC;u3|=^8P8j&D~Fs7{wMD0=bXB&trV!Qm#u3B{;eh&FZwn<)~v z=2cqPs*WT!*uN&c?M4)TE;4NG)|iMiK;iY4#PmOSyr9sJ1h~vT=)U&`@txQdyNv!r zYMeiq8})QIvENza+Pv(Gs!z;3sfDZw)`~T(nwum}W5-AmS0__=dFCLAjqbkc)aX4# zEZtZ?eg9(;HEwGAR{gw*b(g+VPL3Z+3^opm_5buyyj$f-w-ckUiSN##Uun@7Fy{Vo zxzadDYibY_W<1{`1MY7}=gE2EUoy7$^)9Q4Cw0d6HX$x_yMMJ=e9^M__mfKoh~+&l zpLl8(0@BfW+h!-!z|*ehpT~OTfqNYFgyB#H3${c}YkN%(V8L1~-jB<^g=X1y&dNUSyOLHm}}+*OWFwfn@g&75ghuS5_> zBFPQgX+!L-`dvD(u7T>RUFfaH4spmDe{ETVJw{@W)BeSnE#0uG?;tFTbz)vUzLy`hc#ggT==;{4ryq`1Osj)jsDs=p~)^ceyqgs?&hj z#t0N*Kdq?|NZ?<5tMqnglYjoq7UI#ph2>{cJz}?<_Jc7dRaEFPR~!UM)^EFq{q+I_ z+GOC-Ixu{`_}RmNd@yk=Z}Qmk9$1cMU5=5O+FDbC#_2p3S0QmnyEVDBE{DY4k=On0 z`V}Is_~enX@;Zsy=au&9wlxvZZGWk27uA`-!XCL#2uMCXP01DN)qH9trk+h&YD@l!?o&&1;5c z^dJsi^rYoOOzsEXNN?#%^g^~;>g044-|{{o$eLQ(vIc!7b!exV@e&d~DL*-U=vxxj z-rsTMjz3AzPVSuyusgaG#}&T@Pu-$OX$VG_P>Sx|1M7|HtH{n>U`t+%JJt6YhG7ok zhTj^gsi`$J0&&~Oob<#p{nDi7-NT9L_FJ{_fT?>vQoYV~!WK*Wrkp4M&0lc=V&)%a-=VG`A#Z6{Y!#eoC7 zOID%@uk(la(yDy-`4S{?JiZUGZ3WQ~`$tcjJ8=*1kkJoLkoRCMu?ko6cj zHNHs#_9OE5A7hAbm$S5~1y~$F6#6S~z-AD2v5Wxcu{=pc-5UTr*gkG$qBQo@ni_#r zTib~!fbdpp(r;}qt94gD6ZMLg2&C0{=a*b zf%kBzce&hy+7DUVcBIBbYidwzES}PyDMV}#^)c&IBeeT6b}SBSNy66G zr1`5P=xtf5?gVqFa=C7ug4f1i+kr-e=739f-3Q86Uf}xTuRGm84aOu5cD^uC8o6sL zHnAjj5^G>W#Js}ZTUSU(%y`+`8~aa?$Q8SNF52B7;XQUN?_xKAm=@Cz^9|KwYib2< z2jLkN*%n|rd1%@Bo{r$Gi^ygD-ytz>Ke0wb1D&O1$Ve1DAl*zR;g@N+csdE4j*TOv zCL~Zso7cZ5wM$sk4$~vB`$))QkkK53!gw%I2f|HNEe9#>UBR56q?AUkS`#A>Ut4y< zCT{OIyjzV;%n42Gd8Cq9^V1dR@yH@BZ)S*t>8y71Hyu0*@*Rh+`|Pa&7Ce-u6kL8J zMyE2Ik&?&_{NxfTVTp?*Vk_^N6V{O#SR}-P+hNAo9<8xQRU+X>chxqUe(Pl+KA~sN zLB}yiu*OfqQW+{p+#fua8M}!(t1L+@4f0@P9!1}GG?+`knX!a8s|{o8Pt+)nRei<| zVHg`xy&Pkxn$_N@qGOHH&{>-^H_8fzfY|x}CJFz8;rEk}=`3T11RQ5=ut;sjt{}0T zu?bryCDTJv9GE)kZ0&d_8szFOS{6LafqZe&k`{qfZ;I)>dhbXzE_I};PGFxn%OS*s z87&)#K;>xq4A6_mv&+&vFmef+CEryBX4h!$t23A{P;J|F#ssSHIb-L4AITbt*-Wz# z`QP$ha!b=S=mWT8qQrWAV%)-5g0=ejMm3G98E!O8H+W@GYT%=POuv(U6TRnp6ZJxM zPwV#6HIuE7DTE6`53KxOB30;I)fuQ`i8uW}{%Y?dx2*6!QO9KC_%ta=<~cDW$q;iN zo3tT`qaw)Te?E}d#Vm|~UOt+DyM(x_#oFa6mn?Oo6=GYZ+xr1?juOl3Eb=35=PkGV zF?NZAkmL}4{7R}yMZOwI+)Me5aEq;Zce1!YSV9>IQoCaUx&NKicA)OPA;gE8-)~wH zFD@$5_Fn(v`L96`y%+ep2@m*~Y}D*H2|3Dp4*J(=xusjwR#MZJ z36Sa?PkpNJ{fRHxM$%6#@AFsH`wP85x&p%`rvFIWd;DtQt zR}uJEV~$h^XhmI0P7t)2s%J|Gc0zO6`yhmHap>&&zZgjX*G%S%1P|6bX%=|WXD}-V zZ{CRyYVlAjgc;I#83y4Kn3{tqx``#NfjVyVFx(BIKTzd6q_fNaVk9Xs;&Y(-z(RsG zRJU0+U#|*}AP8J~z>h0d2-r&hj|YU9VvKhFAqY84NhA=)`P!+5-06qa@|}CcimxY9 zg;8il%-DvTR3TZavuw!>1qUi{sIr{Ejrz}P2AX$*+U#zIfOMaS9kT%W3Iu~ z;L?KeSK8j`UxNrF*^K>)t%JDJEab?!W|0PwB%sH#Rd|#nwV53mt-8q{M7+t|jrL-B zlhj>~8=b_@v@wqTW$=ilZjimw7LGp-qN9UPg6G0(+)a?^zW(GPnK?<=Y<0nN`T`RB znSZZXIQMg#hYg5_x@nSYT|7xU*b!SMipMdAF?;w$&mLydcGbVF73v|UcPGhewUq45 z&B3I_G(UJJNgP%$Z|C3W!w9>5>X^A12|T!J-?RL!#D8Z`49Tt|-YeG>JYKSnc*ctu zmbOMVvV0X>pVs|nTBd!PNa+*!7SJ66% zEvhHjSWn-1ma2?rSBm@Fu+wOB`=8iDZ;tT;wapRwkTo_VA+MNFkigG;5B8^L{Hevn z{3t!&JSFnjpu??{bf7~Z#>L~2Y=ZhG0%i#UB^v29X) zDcTe(IuL;cM_&=0%e0%YA<=1~!u{nv(TSrm25X}3fY{FRCoa*OqX&vBX%eVUo$MIh zufo#IiT7ZZUQFDX3|Av2EIzlB2&-tkJX&?NtB(8he{P7oN9r7JJ!788#jj(#D*qZo z`Ynd%%JvasqRvGnE+e=Q)i}tHJLXI)PatwT-j2@177xjzw-Dn?v{ulQ7;`bOu=pP` ztVn2X{xjbwo*u>)|Hgb$i`gW}mu?S7NL)JZfR5XU;xi^F(PqjRmMNPc6VLz?3)2jD$ z;89gnx0xUWi4WDH{Je-}ML39Gm=HFpBF8ZpH`qUhIEJ*DDL&^9nhd|gXF9_5K=M2u zOiN;0<}s3}mbAdX`0ur;U@p9XJ$*TS9u2AVpC)c2*Z}QJetsrQuSTOTnQo`vBz6$~ z>AqP+F`h2!A`+F$lnDu|N7tjf>KRQVGaDsdry=IlHp$j>Pq+`ooUIKIc9}g$d=-aUwF^=|pM(V*;r@g8#1Tip%$DqUsbjjXZc~ zk@${JR$IfLRy-YpB&Bn{C%$5TLlTd%{4kQxmCJ0LS|bF|O!V%B#Fx#_apLYxPXJwr z>yy`L;`Ju#Lb3J+KRt#H1;Zl=9-QCtUh3ot-&H28BwkVr>{CWlN3on+4C3blDxE+-2hgps==J_J2*$4jt{-E&azA&hG4oImSVpLZzHj1>qa7NuOqkq!LnwoB9Us+ zBUdV&8l=x7k#qB>KAaLjBD;If#tc*vajcbbeT%Eae+2bThY`P}TTksap3=l z9Iy(O+x*}gGNA3&6*8-!|C?`xoIw0meZs^y{h)^HBTRxwNl(W||KyLGc7nzUJ&i^L| zfMJuSSfZF5#@$d!v#2M@nk03jL8sqILMHP&k~kJK8cIBEXwhI>!d-+$M3;K(j!9H6 zSdwRu_=<=8+WGx!49vaErY?#ZBs33mW1U`+kd-t@^MnMv;_ebZ?usKiEF(8r^_b#X z>EwKKF}>ROwrZciMo@)6T)W!;8Uv$-JDB5}BxTSXL|>BlkjL2*)INrI?ye#M>N*x* zcZwl(mAFxGk45Nf7)MqMm-Z2Lrbb)epFK;+D-ySXlN+~=(Lv%?asNWxMD_Q5`MaVm z3swft`@WDl-09lmOH#;+v+v!% z#09c;S@juu^g3jn380mCkoCDb#SRb#RR4ycq=PTbdJw1!-~q<7LL^Aq;-Wu(-!@3z z*ti7KyP@7JCg_khnK{cKvj?Uz$L@oyT5RTXa&m-Uz%qoI+*t;3v0b`)+)aY`nP=>0 zb}WbFQdR~8DGPu9F!g2-WSsbY`)V=`GH+r|gUxQpdfK2A3vwar3ogG}iy_+pK`olj z{1q%i2pNTVDmA--qTjfc8|ycOxZ`npcaPVHx^5ld*q}#HFTW(T)F2Tu9%HS&;u>V$ zrbYj)A#2^el9-pHA?tLN^&7gKfo38`L}FI{3YH-Rxv-pBfNJ5ksXA={UaxwEcZays zEDaG7xK4u%f1^K|?dt=XJ1C5xH)P%aGI#abQ;_v;vgP|Lbs@VNE#dmpTq*q#%dj%S zQBS=N zn=2Q{j_k0s)O|q*!#>|J3@ag^jT1oO!PF`=c<}nd>Qk*D{mP88-2<|qVHsP_(5N4) z0fEekoPw-G_V*~9+QSt>gRV@ULi$bSPKSneEL9g8zF~R|8Y!u(x)Eg1g>dXRWb#2F zvy6pzD^>_(tzy3`B|pLyYCHpqrcjsfnb6=dTP%>ii)U^$Jjgb2Xq3PS%20D7a~o6Y zkTs9G?ma5zh4Mm6oI*%;;98(T`PbUX50*lDPv*CWMxQ8HNH=IA=OksOvS)?N$t;Wx zS(fSv@u7K5OCgI=_6sv-Qkb>~*_Ldr{|Zham{2H?vtPh-8G>KUZwtOO5*o7- z6qb}Zv5#V)AsI5@fFMZY4+ITZ$nzzn+px?tNZ-o`YuJSo zk-33?&SJE5#O3-6h=dT|ikV`NoO^YYYe*^79mS9XAXUL~t0C=ZRl^K&0h&f7pIOtn z6=dZ9tvb-e6tbqIZ95oR2wB@(E7rU%hpgKS5ywm^{D??c86IGTA0$4Yi_aVCredn6 zXay-jbmQ6ssSPmg0)aH^htouf;gFfF=EWH`FDiKQgb59DM0+U0I}|3s^dE?O|7US- zhcZa0wbo(l>~4^p*2B=|{ANfoqBTiz&}2T1rS^oT7id+PD`a*qU1xms4rHDx7&g7V z6=YqY#q;^U0!0YoS)SyX(p{LL3d)0e+xT-gh^D0>{*Yc!169jI$WVWuxg9f|ryZ(L zUXb;Hff+!y6-#;kB`CsEREO-FCEh`xp*n2@@kMs)f0nfMg0#V{uIYM*K)P>4oXx8J zkZDLu_7WhI$h&?zVGmiC#=X*cv>gquzgHZeGaA~yppbB_eg%pU;5oCnAz^p_fGhE> zA#wAi8HZCgkZ$)HDiN0Q4(|QAxtaQ&LApVG_rZg@yL5*1%}Z%M1vD&S z39yir%X9~-j+}1azy|4P8k)a28lc^@=mhD&DmNj$FNI9JhJ&#j6li!?y_w(RgJyQ;n@3g~Zt7$$p?MMI zIlaH*2;#$Pjw1+aKr#?VuoQ4m$Pl&fRT{)+aLy*ue38L7A(JJ7=0XfylJ7OR{yoQ2>*%}bFFC?#<|Xy0&T8>mNEw2h-$&C}mLLt< z$WOT!9|K>5p6^t_cKNm?Up8w4Z<#XirrsGW$O#U_*X%cL_%-GLpp;2|i@rF?bCkzG| zce`yFGY8^ZkTMVEKt}cBuXD*p28iSNjh@8$*;eLx_mqNOZBB1>Kl6UZwjM( zvE(o)H27*{e`^yIy4%kDb5IEs_PjaXs`N4x&T8GE>3%;bnsfxG@d*^IoQwHs)rFZ* z^vKrWuJUgI0h5IqY_89AE#a>4LU<*-8|tJbQ&XX+_i`Wqrh!l_E-zbT=K;l8rMk|i0-$)+Ds$Yw zq4+Yl12HiV>%Y50$?N`zUJxbRh0=&qYu>(pA-pqJ3e=sWW+_eiiEPodZ-@U~7Yy#s z85>7Ix;%wOFm_4v)sb!L!`K%iDAE}e+IPD>^kF0vSp{MK;$|rF+QbCEC{3Peo^J|8 zO&pulZaNf-`VC`s+RdpWM~XtlUK$cn;m9eN#pYH>6&k?U1GMr%F0>Se2*ZTYLatCE z^v1>$cyD~se=BoyAxKDtu>)@0_R-6MvD+*tRy2$)qw>)j3J+EPII&=p@Q0v;BD?I( zJtu}jQ8z5Du5!VA;J5w~^r0#-B(iW9HxCP~C!M_d=>QbgY-DO_`T+|2KJQx7Mi*C# zv#rXNhZW5IlITZ@`VkzCSEGTM!-lOOFMo={T z(^i`x7brTpbMBd*!=R}A&BWGwH$m~Op7GP zxd-`s-YE6^6#jeOGXD2@Yt`Q$W$#+Ij)2@P{6@KI4VrsHePcOultZ4U`tyDD`v*=) zzBw;~{KlGiwn~4&C6lY2Jve|uxVCM)fq*OC#D5TgqLco=Vl5Y1k~{+a{vv;H+7M&_L*l zwS4`AK|-l8L6{;;6=q7)u-}7Lw@Vd<3NwT`!XjarunV`P0|*~{Oxgr}Ar;D4D%Eh0 zQ~N@Mfl#=cS^UCaVYn~~DV!lJ5S9qbx?%4Hyem_~Qcbl2yQ|O=3cD06$~KP{dPCvi z!>U>B7C_;X;`@(UkHPy-hoaX()VClkfZ}{Ho7BS+sl$`OW^>e?B}H@|7%S9tyhOqgWwOFp7IS3zjxU{M*A& z@Fxw2))e4Zyb@60P4UxTLBT!ao^y|yK*7hpw;DV?1!JQpF-(B5ISgwS3hVLZRM^gs zmhVGh{)ih{qzDRUULE#g;8GY@dn|Qan+^Ql9|5Oc!xc>#f}8xi4j2!VL*Z8p1xc-; z$hL*A=ZHa2)b>WGN3HEpl>eIcL4o3~&t6Ox(BvA)&XB?u;V)s|FS#I!dPHN=bQu(T zls$;;hvE^R>`*)CITVk-`tt9|=b(5?1Lm|V8Moro35P5wS=K2b{8Hz>5BTHhavp0SlH^G-p(!S_ed+;1S{3k5 zm6Gos0nM37JpvUYL`T&~+jB6bmdOMP|Q5kpOPCd}L2%ff#}3 zNm|f!8+_=hLc_h*6bd7f0%58Ab2wEb*lQ{SnK2fG!d#)~f=(LzaMkT9S+9Z=`!fVvF;gp6TWq!8MU#=pBk*dRu!(o|_G z@AgCGNo8k^STnH?B2{3yb=chH#H>fBf%+=RnepKJy0K{QRm3_PyVuucmk$mAzrNDUPM^9y0;UlPWHxg@wdyB`T+>ZK#` zXK{jFq!umP-BT)l;!V*&j|E~2z&xNoHZ4j8i-9cr2khcmoCNzcD2;!CAc0h|;AOI= zCZjqw?6IC0Aedf2)Gd{|{!7KrenDWuP#NoiIsbdG+TEZqt0V%dCXJ6#CR77w&S*-I z(Jv5$klZ}%O=1!$M$&5%ZNZic3HuG*)=6tfNT4rGz9Q-l5vp74&t{4*S-AG8_y${G z8AnvDfb$iUx@gB@>xge_zj9MXVm7cRyF(%gQ!s?@BtayEuf7UGh%Pg2hN^C1Ujn@? z_f^}j_GuCQ#6T>ki*))7>=~gn_z?<6(?bE19fTxXhxMf|R*;a7M#lC#-jLuke_TD% z>of`Q-!^bepP9sW`XcMstxUw%yowccrb(9WUzG?ZeEaa0$aTBEPg zVHF}>DfP7`M<71Fm{b=zkysg#1GUrai21M^js{+PRbT2Ne0%J4)uR~-1a991rtbfo zY&5YRSR_+qL?f_k6?@ae*#VqGJFlOi=mtSt;3+wJT9boNru8vxY3~sd(U^4;z#a+c zz1lmE1ol`#Po>1KD@Bzu60fM9ewRKLPI+eWj(5$)7rx%Y%OAuxg8=Q%!D^0<`ZXV2(QCe{BA&+0AT;f6ei} zf2I9Kyw|m#-6|!VxXUQSsz2y6^hz@@rSOqK>%fX4aLro;Rge9R9?tvVGKqOX^pG4u zYjOl)=gr`&h{0G|RQ{9*Ec|Grx*wfAVJeQTsZ1Gj?`Ml`iBzl zqUzqbl*Mo_lOn1dN#S9(el zrQ|1ltl<`_cH+y32TPNRCN5bRpfx;4oE>QlZ33|~!yx&nJH&Fr1qxmR)f&>w8(XN( z;Mo$s^)hwabsDiGqCs6Zt27z@3*-nD<+Wnj23mGY#M^w|5kF8Mk9A`~rbf7OQ14UK zw^0Np1vpbSz1a&jx?)~(>I`t_kEJvj_6y`li3blZ6L%x*1QxcDxE|w>KhAbE&N_tH z(8Q?4x50v8i6aQ2+Rm`I*!6nWn~vbn@#}8u8+|L9oGOinY6JAFo-7i|4LlN@&8yLa zhSL<>zQlhCGc!o-zLZ>w;MW zXOLQ_C@keO;xl6yW-*;2uDuWgqkjo;?1r{zL+onQkiiasbxjO8<)47+5j3j>`!~n& zlp6OFES8DwEQV-JjX=!Q7U?C{LqlIYI{56L{hGfK-y=R+yRn{*&Cs% z8-?QW1KSkZmemMcV;Dp$vSyIBtWh8s;#78;Az@SOuv0aVkoz2|H()3tGhSatYSp9N zYc`0_DFB1VL-FfSng`YljAj~G9@!cJ^1P3$HhL98wH(%e5Y4WET2q64N7U=vw#SnN zO})QhbF4o1mUih#^vmgv-B$JS&hdT|ezyXvNAF6XzWE2LzJ0lPQJ0J0XnlEg8>f#@ z!=}Yr-{C93cODaZq-KED)JRF4p0p*;9TK(H_l@q!k0kQI!|l(n4j`d^0k@Z4UQ7aM zf3WhF;>QB)$aiWJ7`@v;n>a$XD?Q>)MsxzFlr7U+-E07!hAbOgP#X2uR%{d`T5S*4 zX-H9{KVeg>aXB5=-y;!`Gic0^gxyHYA6(plIF9-ax3U0Yo7Y_FU$ri={6yQ1J|Px4 zOI%{AwIl)qYZ)al{D0YEiP>7SmS$4{-tgNima4o3FOh{^-Ds&dQVT9>-ob-tpW15cBJ~K7! z$Gd5G7^8QK6;2A5F)({uxGR&&^kt?pGnr*(C=d70WFL(~1x+*SW4vk{(z6h=so04? zPzigWbPzqWj1XQ5uZ4jjluQ%=47X^U)Y3FPp>j&Y0;z z@f8I{^M(=+tM{Ao??6e)&f6b%4uMja|8yD% zMfJlJi2o!kg5vPutnR0{`}%)u_CJH-+~?9;fhEE%-EIMNHfSh{rWRXS!OKbvmKuoB z%gMOB<_Qaht(bDSN7yGEmJan(ixQ*1GWWyuu<;aZ9g6y{+5T74Q&2QEYhUOm43(d6 zwB?HdCZv7-_^AUN7yg3chYcs>*atyLO|w~xcTN!w!+2j^D%_?@!`fPiaub$9TDa&A z`V%%mky*ye2c#~ha9YJTTW|*#j#cQ^@;{+yQ&&82yoKV^-RIS*XM|K3BfRKvD6xxP zvM9b9l!RRM9W{_G_3nkh(Gp=<5K_S$`q@oNVTNsF0lOu39&Ku%GS2@ZE`=5qH zDT$a~4Q&G!p-ykqAgyj(0-6ZtpyV|3Enx=NJ(-S77Yzlu%q-o_ zv>UxCe^utz6n_em6fg-Wk+!P$5}phv_#xFllBYCd|x7 zTX=0&38CNs9YqZH|v{7Tp4Wk-{ zPYt&l<{LIObV2e~u?-df!Ts#*3pGmT}ECwNJYhL@nxDU;m6@q;wHfZ2yq$>l&< z2SW`n;WmO~f1s?rPGv5UN%mFt5<#-JvX@9Ddje$bEA=dr-9fSrm3frZK$(P8`Wiu^ zs`NEd$*xMyOlqKHXC---DwFJ}>?ML^du1UU-D|?9`*;3g{ zq>{~*c@#>RWK$&v!CBc@*-He;hRR+dmHb)BM@f~eujC*KnPgpMFA*ebD|?AlvZj)c zqL8ev#30NJlu1@q_7Xv|va***B`YfND2y=4a(`KCO|=+aA(Jet+y#PUY2_}EN|yM^ zT5GC`ktE6D$_#)oMUq98yFidEtlR}s$%4u}LVTKJex(M$MLw@`7YLHM5{Zzd;l|5M z2to3=67t6`kxCv_LjKq#l7}-T61_o{APm*QgGv&n5G402dx=zXuaeKAkld9>bXRNm zC}?vDlG~M-MZ7>Nxm5|d!V4rfEAiOOg(#DhRrV4=a-*`BNF~=R!CXd|as`*kBMqmJS;3M@Od!@ysE5yt+P>AC1m*EPiw;CJZyHI<%|rs(X_ z3CGL-KYb~6Fn)({i|Ut#ej~cmHWhg%2ov!LO-P&8TBN}j>;fu2WF2IBf&<0gLI@#s zLoo26{oe*`g<8F6O4%(?H}(^h0tNyrDA3mwE`#m%2xJs>iltYBLhUV79OXC`ab;@Y zniu@{CS{DVkij4}=0TP-|D1K1df1L2kdgLr!{GK`TA;ffqEzRuf_^4McYAYSx19wj zViD2l{2PcpeV~i(P#b7`N51REB7mkr#u&gD0Gazo{5>Z;2eO9VNvd;hAY>b%f34vt zENa2P;Wv)xYZ&UI98o|Z^H2ssP(DSbX%8XKS=u#3)ncWV5Sun06Xt6`!_)M+_d(+k zGy&5anjE9Q>qC?Cy#BUnHuJt}8+06uLC1I20RGJ=|{tKTqas>dDR8S3{oIdo<9fVPSJ`*!0PF$i>tLC?xlLW7U4i1JKx#6p{(+3Dta&VC)|rD{v|Iduf5m!vK{luH zm%^Ztb6E`u#CN%rX8QLrNcer{fw@I4kX(X!SC8gEN{ji0=i3c}%#H{trzn7|{WL0< z3R&em5i{F_)uuqUD{GA@SL*#32G!S4AZQr0{sX=^(&uv}l75nAc=m#Z!)T(a6ExCc zIuA0`p@~fO3}u$F8G)=6mgH9{<&MPF4Rl28F^;(z;>ubc=~y^35{Abfo#)FeIIS;jF^H}=g79B8&@6u!5CiUW&o8; zP3M7ySEG}$-R!cF5F4F-`S!j2kaUZOtWs`J97E*3Et~rG6{_Led#gLR*^OL**sm<; zO2?&>Qi;J85X37wAR!Ign7{oDNoxIEZ!ndGP)Iw%;78h^Zdl;BFgvtj5qy8QGsINW z(+1r!B1=cd@ipHwn8yq1rf?wE)G3Y)2DH8eY0iA{r=Mbdx}YI1L4&Lf6=Imvhed3^ z4X2qD;%E>ug5SI1z3x4}1vG5hgrbE*qfq`i%W@3r#7{qrDfIasr`3F^(KxM@I_1BX zdT$K<$Qoor>LOOD1r1NJ^lQje%lUWAJ88FykSyb>hBJ$YRPeiYX*iZrLM&rh5Ts_5 z54$?-6*St8?dvnnLu2-s9U<$1{kqm`RJgPe3e1qjU>R5GJZi{Q7K06a%5wx_d66E( zMR0wUaA;Hh?at33=?{*v+|-g01Q|;>pR+Ak71?)j;=+y?1K;DZm7@ipUH!~4)wyns z$zzo>5c{b&0&hQpgdEmR6Ph02G^lNnZ|h3E1%n#T)od_EWz|g4T(o~YwL$oGzTL%{ zwAOn&2~n`;u~HhSr-l-4a2Vkwq$U+h9g%&Rl;Wfe#IUV)W79JgFkl`u@nYmWF%QOt zZA5T3NKEA_weD<&V-MdTg&9XEQfp6)?(%`Duxls_>gXU$Bt9!6M7@unt?&ZQK~WnLgJG*c8AyAhWgDXU4lVVDqsX5>non0 zhJ8RghG&*!yOZc^>uW%p7p&Vh3K@r3t6;EZ0#6?@P29uwN)c+`VCdNpaD*$d6!m-B zXiS6*K(yHnwAj)Bvi`wRwM!2W&lAxA%h%&OFV|9IY3lVyptTA*G`6|bWj`Qh^VJrA zk`_U&hlGhK3|B5WneXg%pK*gQ%Y#e&H%lK+ZQlXeaQI5FJr#nYSSv$_?LBMxvqnJ> zH@lX6$le2xWOO$)-O&T;Is^{rcd9*Ny3@Vo3uI00dLMy<(7rl{_^NX&))(%jw`@Dq zJZsMK|0V4tW_{$hDc3zsbK@U0M7)TE5*LXn zPpHHS>M}R5?uBe+<>d!p;PF zhabxSHIz?2UnEJ+4!$J*Re#^*xdTYt5c4~YPn!|gMq$isNzB!RqdRI&Cbi}`zN=$W zgLsVFc(eMB<-~T?{N%qzJCG_TKMzY7zFzgAW&3;t%%SAOYg4?#$U*5bVcL#F$+0S~ zTRMs0XJQgM0L`9@Ye+CxzQkn8bG_oN+lkbH8R+7Dtck@8FY)bY<`4o)s@2M`7$Z4x zT9YFXFScV_ks2e|x*_TUeqtTV>`x-c{48wJ0A@6$8#S4wlBZeOPbe{&J=+f)x6+e~ z0k~7IRni(tPOR4CpzQ3Wr};%Bwh^{}mvsC4k z^gV^OTOMLnFsDbV)K!X9c9ucL#AweLtcaue(r@;XuyiiPq4Biu zP#X3(XHG8S`<4RpSrAJsRd!!QS%IU=s6|Sw1BC2(N*yi6GbV3<>dQMC*f>5nEP7-I!FlNdW^Q zi2*GouXm7=Q%BoHg(bK#3s{>G52652@1TnR;Ft2nYr&OkQUpFxMrf;YQc3(23v|ELBZ~Kqzjr$F zh`>|xG8ZdP64Qb50`Wg?&!XOM;ysF1Rd*wc`w&-!UPfQ3I|a~x;~NJv$5$2Zevzpxy! zI^t|bZNsl%{+{j|Mc~|@>aVx@C^{ipi4L7C5`KoEHIvXy9v9bNYe_;@ow&P_+#!KY zd7Cf)xW9tq&UPbuwV$tP+(A%1yEgK3|62y)KeMl*tgRQ{Z#?*_Zg>ZVneK3imgQ1-zr;51H29gZQxqj9P-}7!ZsZ0-QX;`4-UTgN zSWW_8WFAlKnNEBd21`@o-f`dlsr5U6K_`@B$Bs~C80%>R&KtPv#5IlX@x8&1?|fML zuME&ubW%y!P{w2u^jBGnV+HF;VE>TA_c{+JzK{4u<-_-H)lF$=lzY$T;)69G@l4Sj zblVqAZ8UW#7@lbUs4;`ZFDP^TwPg+#|5w;K!BzJ>I~#r);jsohiuIaK{QP!$ zVvBm>!&R;LazBNBxdu8L*6a7{tOV1PfvAr9gZXd@>yQO5+=w+ma{RO=MuMgb22zF0Wv-{mkgz7aZXs-i! zGb1ROl2co2auDQ*d$ve~gd=E#URpo{qan**8)d=f-peQ@zqvz6bNoakyYYJgnZ(qj^S}6oS#=nNOiO^ z{#ZkTQmBb)L@et}Uo)YBGm$fdwGh>pfDTw3vqtrz`L$1T%LS3i@t#rxdj7928Ai|l z_vFn?kDDq?cAEGZ&oh=AjWm2~*xlfkL6-h;eTCj8J#XFFy5_Rc82kTCdP|z2b4*7e z*^HO|!b{sgt`UnwIJOX(1!gByUGIoNROfj`k8hi`fQdg3Oak~Gjz3+~S`g;0ac9-5n0Y#+u zA|2`80Yr&n@7*N!-uo}IM$~95iLu7sHEJxe#j;}y8Y}h^3-*Eq3u1rf-uo5fdCz%Y zc0KQNp649)4*~J(&dzOfXFl`!h{7#vz;9El-m*RX(sghmJ1UNb!z`cFwJg=w*j;CS zDcuQGcJie4M)2CjkCuH9aEi~l90-;sLzGNK>CBBS>Mm3axg?ax+}Vxp)1H!EA1jn% zQ|!etDXiiwRQT3_OJfq;T)7WC{Btf1-n0umdvS6Z4+!A*g1u9|q^QffasbA6uh}Ir zKJM(gJh0dcp5{3GhP~g#$!rIMf0$Ih2(BuBjtCX3ED$Mg^pGMmWnWU%Eg7IATmKoZ z->zq;_)6dI%Wi&kyxIMOU$Y0qu4c1w22^nzM0d~2;BC5vr}m-hT+dCX*8xP<`POI0 zUw5JUE`EFrHomA^V#)QSmjM;J1G_(bQpa;o9H3#%0$g84p>pcCX(UsT)uxP(sp_#n2|LTC2R z{2}!o6t!b7rtulN7gRjU))K+xNp7a`G!aAc`orO$DCttxhm1jmrzwy9x@^FYzD!O zvZ2}nelu+j@tvez7rQG`$W$CMzVQ3eF{iJjEH2*lkX_rVcc0ur$DYY!h`kxH@%@2b zZ6R3C<%6a|geg&7HrfR-8>P5kwRYVL%zY0)brk#imlS?WCd@nmraHuqce?cU!O#ip zp>{@>&4tLG$4itzfE#5ox<7!3>{DioH}ru7CE1;HE^FK@`(}i#;7Uga%ypKsu~rOG z=@ZJl@>^odNB`_1I^ceF=Cj-Kn#>B7J=Lq(vlroedqYUCa&?=!wWG0VdpfsQIY{c; znhG1+KoKP-anz56V1p(M*^*jfOGkmz8No&iV#08C*`-0Wd*XNOi6Gsa27#|B*XK9_ zA}R{wAnwP-gSS3RhXfgoLTa%Hnk{q|B?Vz_OexPIQ4nS;m2LM%?=@phyLzzu)U0kk zzZIDLD7e+YLP(*RK(MLUmmxG;%nOLD!fV(b5VemZw{jq9fQXlL=AWv?WJ)PF7&3*8 zlq9h%f6dK~v(1jOJ98}trsck6Z^jc|rcNPf7kA~l+7NJ${tTWC!F;U*7D2>~aZh~i zJs@^l&Sbb0;(rq7weh{aCBw7+yOBhm^fTLke%8i&N4B$jLU8LZR;19y2K*v%R-rv) zq!4wSUpD$eOo;qBZkGu6)jIQ%$}%6G()y&vr%AiiVbqu!^Ykq5bi=n<&Mn#Fj2};0 zy$)xuhzl^&xe)x9a7dD#(4xL*=%t`LEilmOrv+C2or0UH2DzU-^ zE`(iqA-nFdccG8x4E9poD{F#UaPKt%x1&Pt96Y(6A0gs=1~j{9(seudIXC(FL!Ffn z?In()FR410jL^aUJfpXq*)yWv=#FC|t$lxdqz{0E%J2~ZVOQf%@likZ1JwE=jC z&YKt0;vhuI+K0W){!`hx^b6?UWoI_Jw@Bf$zmzaDk7o(SA7|I4m}m*m)LP7mzEG}< zNHT-7Jy%RuLB$PpM!p*l-tr*V*?cNS8)y+PMwfVKpHj~__Vw}i>j$o6`5aK`d4S#J zM2U@y!CC@P!CnknFK~+G$@KY9aXC@o{qP*T_*8z7qq8n4Q5!IYl@fJ{F{kN*G&g=d zyIwn8Y8dvog_-@NS%KBM?1oCE>qPUdc#dIBOu z`XmgZ{0cTz@zO!^@4|>Ls%L*ZgRcCONn{-x*sZSPR-UapiWSf0d*FRA8NF(n^|h!`hX7^IEg?JaCgO64mwPUepnfW7lSfLq|0@M;Hbyyx{vM#^a-!_?ko|3T?A^}p z@S=IE$LtveR9t$_Yo&-zz)#us!j)|=B9~{JRzc!?K2totpvukK{zsolSTA<|%|!o% z`fJ&>=D%&rwp+)Z$h#RiNFu|04&@G8zS#x>WkLla7HwQS%C!?jRhSp@qG3}=xWYR9%_D@rl&2)A;Jbc!G4{O*iKF1lkZh;=|N*&I*l&0BqPJCVWpg@ zB2u9O3%|-7If}{YB80_+TC(gE(@4Ziln)l+61z>sN!U704TAlH5IGjg)zC)ryOS9q ziW#ybXNVaZjK~!`HwJd%TjN;tyC#^N(Mv{rhSgwYf3BTp z%>&@T**LByQ9>xV#2b7gGsFzaPsvY&?jlhuHxEg8hZPlzA?lr7q~R^pqjiU`Dvu6|qJmfpW(5b^IeHW15oF zOYI~yO3-&Rf7j0F%9ZBU-AC(7afjyIy@47Ei8C?2t+reV>GCDSSPsn zCxWS0zXhXyTnN_k_mtGo1sG`zO_t?i?qQ&Jr}(b#Dx>fEtGl|lOhng3b>?mv^CLQr z=S=!%K>c{q%cXbLfLUa=)yP)6!Nyyp@4@aJ&zBE_@(v~Hu%uh>7E8i0Wk0W7|ASr!xr0t(l?5EXVu2Op zsGyx_cId3X$xS(Olsm@PD92DwG}bVaqm*`DU5@r0cbeEWmg!>oeT%ayBsR3D%?Tjyh>F&b3lI51|WXd%I^$jW&YaYMy_H)sXs=QfyyH1k_7ad@Zj85)#t>4Zk6hSkPlTH>ZL5&Pz|@@r z7lWz&q@qRaY3`zWB$OZmPK>tZFu($g;^>?8g&5_+$88RE3+cR!nld%&q9Oy2HCz2c zbK^W_+z?nD+H8c-Fs5D?w*idGr7n=^I*jZo!YK@m;u#MW27N1JMljG`d?xM~Pvaaz zb+?*B2uHMb4W>Vw+o#&&Y)l`zmiV*gVk+YT5rJ5J-vyJ&gN9*+Vf|}m#1rp+1rF61 z#x9GEup+x_n|M3tQVTET}vmNoZ9J%uotU*P6A z8*PpnK}=;DobtDWru*tK@yDYVl53vFxL&e68kXP4#=JkvGO@Gs=}8V zrGXF`YC=^}O@9YrDh{+-1ra>JdORN@CriG?1R3-|;za`ZNBToDHF292O@riFyu0J{ z$DUU*s{EH4rA8}xfkU&eDxTfa>Q~hKowhD7Azhy@!oElBgb+%s3LblTY4RO-cjFp6 zsUKBW!2cWAnjO=Ja!4X#)0NXX*!BBYm~6!Gp`EzvHbd)<1bVnYAt*=lWGvDbDY5!V z0UC4xcQK{3;3aHI17DGE><50o^ONp3@TZ`gu3A>Kn?#vaQQ04{pJm_GKF;3E?u^|M zyS{cQc2>6MZI{{hw`I2WHdk!c+KjZRZBx^d#wpis@HL>!sEVSHh zInlC(rN6~93nY%fmKMI|kInxyA8+2&+}rH2*$%U@W)100%3ft2DDyy>2g*F~zrq7C z+AgJ~y%FRpB(ZRcisZ~YM+AgIfyiv?ky)b|kNj#%fJva0bjp~`9kEm5o z4d7l9hN%h-^cm?-XjM-Pe@LTxZ1_WJ)guE(hs;wwG|+Fvj8$OhBmZ55eg@k0KbgchG}s30VB!(g1q0>@F%M}~=M8&6qdI5U18UV- z1851!B-I%M{3LW*w5rpFKB7^bGV~F(>ZAdqAq-RH8Tcl&`JOQJ5sm7&p^vCl#|-=^ zS*oK33_B4fX;nuIdqAT)Y}f;8)!zn8EHX)T$UwUDrbMfL(BNk@s$7o$H?^&;vL9r3 z&#sN_Ir_HjRpx;*50rVJ%mZZ}DDyy>2g*E9=7BN~lzE`c1OHbK=&VSkn8SDFtCY1} zB{R*&EA3Z%IdpB=4R&PHtwD*^kqO*U|Ml#S=y~r4l^Qkau)5r-pgx)-#-FA z2|>IwZhsL}Rf_UzqDcQ}3h`K$F2y8_VW%V%gFO&3_Gaw40J#4FPMlq*;r9@{m6zlf zK#N#jLN0<9i}-t9hZaRz;>y}Ztfd0t*O7$o|ILwJT39nTg0jJEiPETeK=vN9zc=bH zW$kUqE^&-e{|)S>z+#>TmpYu;^8r+G=Pbub;5Bt55yKq-ALkv!PL%_GvOnw#sM6US zfi<|tj_=AmT)SWrJN4_I|5#>kWH*TTu)?V-tiWG=$f8O(xCkvo8}M-N{h+VE1_Fl1 zo*l6mAb8}@&2x%(K$NBUEQDk;nkv1d0pO4Cj^Cq}PKG{Pr&sl<&+c=qdv*+a`i}RI z-UY9-d^GeBP%Hof5Oj&t6F!7s0fu}4RV5;u-~YW-%jNyQt!p5DA&L-HmENiobzKRf^y%Z4l74_2ld4VBbus#3T2Z>rpP{1+ zd22OBFPHG<=;5M;5~F*ICJ>AewEFunY?J5#KnGLa(J%pR^LT@{E1C&hZFkflhZqk= zrLiGF*NFzjI6uVeIufwd;AeS(Azt^bk&kNPb>A5Is5(yfHGxl!F*s4z!I%*jY!zA> z@%F}yxOke#D#hs#g#Myga{>AYpyvpz;vnJL6~}Pqwk)utHmTclPJ+XRDFmwQ3C@GI zRWRu|8Y*>at!aJ65!@t(oH}OONoy1kS}EtBu!iQ}6^^C!z^H4S#n28T1r%w)Qw&Px zJ?t{?{3+iJv;sXh7eXZ_6~b$- zAPo3s4Ri%Cu>?6ZH*c|WIq$S1sqBEaMm8Hh&PEwr-$90iDeus zq+Q{xN>JgjeE>0HL**pSIqwTzlMb9E1_1CeUD;w-%B7F(p9DT{`Prs#$Vg%QIDWuo zVtf(@-z~+su6%rgsGCCmebjQ{J8H0zdJGlAB*4(UJ?}hB0FNBbedY>Yx5d9BV_F!4 zgy)4T^pRLQ{VU5Jys<|AKu#8ROAJ#V_C7^`^T1cURcG**9b_MQ(;(PcMpmJ+LzDNo zG&j;14QM&%%$qw<;n-5q>~$&R_a=d@CotErSxxGWSR?3SBl(wl;Zr^htJ z#QC%D)!uLh<0{C8l9<_o+66U^4Ick9x2`^4v?^)Y`{?QWy)~^sJ3S(EUchds$OViJ z{$WSvG&b5CYG7+3#vA=6&v&I|i(YSHEs8R8(J{6Oao=PqIMl^p>yY_w!Kr>w?qd1y zwLR(95OVydB_S2S`2fkGc;$n~H{uB5<}@6~>OpLFe)w|bR)C{Zpi}?=?^ar_gL{Ki6`y^fX`??L-Uz_8ram;~=NuqJqj<&vr zpKUgV3>MZ16sjed)-$+=?Q};>y~TI33kYV8OHXb@OhFMipilp1ANDx?gH`TvK=E9( z&CHqdvgJV3>b`q#bL~&C<2jkpJE-#OjKITre&Dfx;mvYe zLcphd2co|u=yFuUq}E0AA@G7=bKvIGGuj+AruOAyGYZ+wTN9$5e}&cO6B$uYCro%m zL5b}{j7zpSUoai@@@7>FOu~=k9~j*fDt`No$L|OK1lPHHI!*o0YVi6+w7T)BQ`gw3 zlZ8;gDK^cJx&K~swZ%xRK5qQV^^;CwQrNRaSp{jB;QZ@|!n?PzvUb|R1+Wk;MN8KS z_M|@bLB2-NdTbxDHDUmG*N-{7=<8c{v;$^+wjG!9)Z~S5%uEnhcdYfRAVa`fjXCXU zPt0g3dJHk$Oq@^{AP9^y(8rt~reC4yy(3#sS6Z(x+&?ZfocHq{G1$uP&{RqAgsF9S8m9nLlVxC< z8cr;~t=up*jP!-34Z_q=5%Tf-zr97e%D$i7b-TuPPPREVFKo8h1X|Cwwz3*wSzy`5 z;uQV5>{aH0G7pq_pv(hh9w_rbnFs#u0aB9~YS+c^u2fNNqOJst5e1}tj9NkLk9W>s zR7K7e@eCu{iIfah<$TV*SJ*4gC~oTyP9i;>4PH$+Ct-8&_7N$bkNw^xvoVD1`D9Qj zqbtQ8&1-a+RF1^0)}uXf1LQ6CK()-v=%2}i)<;GSa94izQ_^hyE+Q0#K$$Wj#|-`g z$EYz)8TA~}Pv zO#jinX^>qP!~0Ja$6(@bGNeoxJC$E|F`iRVr@zOzP#$8o#cJjG+qs}c)6Uf2Y^Q%i zbR_k!)@0Y*s-FqC6~OLCEu<#!_a(OjYu!L&u5rQ~Ehfqji`6bD%p*NH#mEK>lUX$k zl1^ePh106D4}xU$Iyg-|dT>zv5OA?}AcYBc@Q|r(o~{{Sv^jLim2(7blBWHz+0z2e z(u4|#{+6Ju>(BCk;ghu|aZ?rjTtUgoZ z5HaD@30@Ax_)FjMLBkk7k#oezC4!%Y6_b9%AB(>M8$kiF6uiXc$`O3L`QL{@;K#(8 zm9C#Lms~Me2$?eZTD>OsQWs2FMzj^%Uts(zp-G2ve+e#0^it{ot~rfKcxX2|ZJ+Ua z^xaWdLE|0dv8f2{h3~vH$-2I@us&@<2CQ)22Sp3BkFg7rp4Xtm70EPXmEk{q`umF{ zj6uEV_lD`8<`C;-?=byBzD%3E2QP7LOaULcK*688flB59t+;^aIS^N~z#_7z_ZQfs zMx~m7VRABIRo>gZcM1ll@o)BF;BD^r&S=8#egk*vFDUkyD5*@Xk_0a&{#oZFT`$@v zjCC)~+@8kHTuNRgY$A4nX;(0Q%Dm*AnS~f1;y5d1m^sEy9MJQse@~3|)mu(q{ZjuR z^`KYJujAQ!mm4a_;tAkz^W56JcJINtgA{#sJI`s?o^q;|9!8r(1Q+#R5phof+Fz>Y zaOv+EXgf`CuIle(i{dCd!p|hv{zP+1f-#!);Pr&KTD(=@w~|N#-J&7TXT`;(Z&yQT z56%O6jGNQlXmdz<$??X{Q?15gV)(kt$3~vOggJYiwl7nc2@2dZlfU@Y}O$OC@H5y! zDNi~YADv}`&ptybXII^?nrJbyfncn}(1KZ^8|^n@NJi~Z+2yaHZxdcGHbc(`A`%3% zcRf0-T4@e;wTOn*S`AL8&WKkc`23=Qjy6+HFw`z7J z+JC;jc>4v*!ou1tPjKS+u)^tI|KvF3B2@0mVLNld?bNcQ$#uqox6HkQZ@}&PP4aF* z$R0YGH|!@{>KSQ^8WWxijR}ms@ZoWSMSF}sc(~{Ak@GRMw!^qP*c<(a(xyB83@fyG z^R^Ql1(Q0v2|QOHDm2kFUqbPK^N*?TS33BDcO$`2N6GrSpXGr|KSmfY>7+2m_T8GU ztEypi{H^MnW*1@jp~4;xf1SioWks0$Io^Nulkf~20fKZZWU|6XoN0xMvq#w@HpC?<}r9( zRErB_4meo}p>?P(aJ zOe8Kw3Spu&2F#>{B!d8sD8(dqxltz679lIT3TmSetk7*O>8?D{KaE)B6t`fy{#F8~ zHB8oD5!AJ$Uv8agtZOLNi@YJh8Ns%k-E$jx&*^N^zQ7y`G$onv|M}HlYHYgZ3cXnU0ZOY~4l7_6Ip1Q^A@)K* zNK9FgfRW{}V#3%^@S?L$H70GO3>Ky<&PZPR70Ld|}m57fqHBX)$k+Wnh8!8t6 zUG(qILsdjS5$g6sZ~0Yc4aTf0VGS|-R+1`iX;9C5@!mL0UHx6*kQFLqL+Li*z712n zm%d#Z{182J3U@!B`y4B6;QWP6&_X|9O5(3x`l8Ec>MlLn9voy~NqmLOXlp2JwddfR zLl`)k#Mn>mL;po1&hPF&6FoZ+zoy-3bZIQKut3GNd$I?CwqcRUu#x+~I%`YribK1= zmM^AS>%c=Ecw|j=qpcxY=!<-Hj=&oC&Ib&*-T>2|9*rD4=O;`ZEHvd2yiai9Ui4B1 z)a7lBkrAiN(Ne=vTW9s}mj{f@IJ^ZcI1}IWN<^NRV$6;sMha_%i2NRAH1T+}@x)@R z@yE`FO~!V|v|IAxmwJm!oIJ+p4vxi}$LC|jCDNGkbVI*ZJQVALUU}_CPIBphmMO{A z9AZrEb*9PxUQ;He45Ya+JY|H{dka@%@?yE;B>gNg)Ct9M;>0WTp9(}(e^^LaGyvP> zeXs8cz6|BQSXp5&-l From 9ea80a9dbdbc7529b11e1dddf19ee53ac76b3e88 Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:57:26 +0200 Subject: [PATCH 33/35] check paths --- src/pypromice/process/L1toL2.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 0ce812ff..b2b405ca 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -50,7 +50,10 @@ def toL2(L1, T_0=273.15, ews=1013.246, ei0=6.1071, eps_overcast=1., print('Flagging and fixing failed:') print(e) - + #stid = ds.station_id + + + ds = differenceQC(ds) # Flag and Remove difference outliers ds = percentileQC(ds) # Flag and remove percentile outliers @@ -241,10 +244,10 @@ def percentileQC(ds): file_path = base_path + '/main/src/pypromice/qc/percentiles.db' script_path = base_path + '/main/src/pypromice/qc/compute_percentiles.py' - #script_exist = os.path.isfile(script_path) + db_exist = os.path.isfile(file_path) - #current_path = os.getcwd() - #print(f'Does compute_percintiles.py exist {script_exist}') + + print(f'Does percintiles.db exist {db_exist} in this path {file_path}') if not os.path.isfile(file_path): From 370572d1dc3f0389cba61764f11e6c9d307ee7ea Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Fri, 18 Aug 2023 11:11:13 +0200 Subject: [PATCH 34/35] update cc --- src/pypromice/process/L1toL2.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index b2b405ca..681db24d 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -61,10 +61,14 @@ def toL2(L1, T_0=273.15, ews=1013.246, ei0=6.1071, eps_overcast=1., ds['rh_u_cor'] = correctHumidity(ds['rh_u'], ds['t_u'], T_0, T_100, ews, ei0) - # Determiune cloud cover - cc = calcCloudCoverage(ds['t_u'], T_0, eps_overcast, eps_clear, # Calculate cloud coverage - ds['dlr'], ds.attrs['station_id']) - ds['cc'] = (('time'), cc.data) + # Determiune cloud cover for on-ice stations + if not ds.attrs['bedrock']: + cc = calcCloudCoverage(ds['t_u'], T_0, eps_overcast, eps_clear, # Calculate cloud coverage + ds['dlr'], ds.attrs['station_id']) + ds['cc'] = (('time'), cc.data) + else: + # Default cloud cover for bedrock station for which tilt should be 0 anyway. + cc = 0.8 # Determine surface temperature ds['t_surf'] = calcSurfaceTemperature(T_0, ds['ulr'], ds['dlr'], # Calculate surface temperature From 3c3d54f247d5477ac865a5dc93f6d39f8c00f0dc Mon Sep 17 00:00:00 2001 From: Rasmus Bahbah Nielsen <114926145+RasmusBahbah@users.noreply.github.com> Date: Fri, 18 Aug 2023 11:32:32 +0200 Subject: [PATCH 35/35] correcting path to .db file --- src/pypromice/process/L1toL2.py | 84 +++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/src/pypromice/process/L1toL2.py b/src/pypromice/process/L1toL2.py index 681db24d..3216bbfc 100644 --- a/src/pypromice/process/L1toL2.py +++ b/src/pypromice/process/L1toL2.py @@ -245,19 +245,31 @@ def percentileQC(ds): base_path = os.getcwd() - file_path = base_path + '/main/src/pypromice/qc/percentiles.db' - script_path = base_path + '/main/src/pypromice/qc/compute_percentiles.py' + file_path1 = base_path + '/main/src/pypromice/qc/percentiles.db' + file_path2 = base_path + '/qc/percentiles.db' + + script_path1 = base_path + '/main/src/pypromice/qc/compute_percentiles.py' + script_path2 = base_path + '/qc/compute_percentiles.py' - db_exist = os.path.isfile(file_path) - + script_exist1 = os.path.isfile(script_path1) - print(f'Does percintiles.db exist {db_exist} in this path {file_path}') + db_exist1 = os.path.isfile(file_path1) + db_exist2 = os.path.isfile(file_path2) + + print(f'Does percentiles.db exist {db_exist2} in this path {file_path2}') - if not os.path.isfile(file_path): - print(f'percentiles.db does not exist running {script_path}') - subprocess.call(['python',script_path]) + if not db_exist1 and not db_exist2: + if script_exist1: + print(f'percentiles.db does not exist running {script_path1}') + subprocess.call(['python',script_path1]) + file_path = file_path1 + else: + print(f'percentiles.db does not exist running {script_path2}') + subprocess.call(['python',script_path2]) + file_path = file_path2 + # Optionally examine flagged data by setting make_plots to True # This is best done by running aws.py directly and setting 'test_station' # Plots will be shown before and after flag removal for each var @@ -279,9 +291,13 @@ def percentileQC(ds): 'rh_u': {'limit': 12}, 'wspd_u': {'limit': 12}, } - # Query from the on-disk sqlite db for specified percentiles + if db_exist1: + file_path = file_path1 + else: + file_path = file_path2 + con = sqlite3.connect(file_path) cur = con.cursor() for k in var_threshold.keys(): @@ -292,11 +308,14 @@ def percentileQC(ds): sql = f"SELECT p0p5,p99p5,season FROM {k} WHERE season in (1,2,3,4) and stid = ?" cur.execute(sql, [stid]) result = cur.fetchall() - for row in result: - # row[0] is p0p5, row[1] is p99p5, row[2] is the season integer - seasons[row[2]]['lo'] = row[0] # 0.005 - seasons[row[2]]['hi'] = row[1] # 0.995 - var_threshold[k]['seasons'] = seasons + if result: + for row in result: + # row[0] is p0p5, row[1] is p99p5, row[2] is the season integer + seasons[row[2]]['lo'] = row[0] # 0.005 + seasons[row[2]]['hi'] = row[1] # 0.995 + var_threshold[k]['seasons'] = seasons + else: + print(f'{stid} has no {k} data') else: sql = f"SELECT p0p5,p99p5 FROM {k} WHERE stid = ?" cur.execute(sql, [stid]) @@ -346,23 +365,26 @@ def percentileQC(ds): vars_all = [k, base_var+'_l', base_var+'_i'] for t in vars_all: if t in df: - print(f'percentile flagging {t}') - upper_thresh = var_threshold[k]['hi'] + var_threshold[k]['limit'] - lower_thresh = var_threshold[k]['lo'] - var_threshold[k]['limit'] - if make_plots: - _plot_percentiles(k,t,df,var_threshold,upper_thresh,lower_thresh,stid) # BEFORE OUTLIER REMOVAL - if t == 'p_i': - # shift p_i so we can use the p_u thresholds - shift_p = df[t]+1000. - outliers_upper = shift_p[shift_p.values > upper_thresh] - outliers_lower = shift_p[shift_p.values < lower_thresh] - else: - outliers_upper = df[t][df[t].values > upper_thresh] - outliers_lower = df[t][df[t].values < lower_thresh] - outliers = pd.concat([outliers_upper,outliers_lower]) - df.loc[outliers.index,t] = np.nan - df.loc[outliers.index,t] = np.nan - + try: + print(f'percentile flagging {t}') + upper_thresh = var_threshold[k]['hi'] + var_threshold[k]['limit'] + lower_thresh = var_threshold[k]['lo'] - var_threshold[k]['limit'] + if make_plots: + _plot_percentiles(k,t,df,var_threshold,upper_thresh,lower_thresh,stid) # BEFORE OUTLIER REMOVAL + if t == 'p_i': + # shift p_i so we can use the p_u thresholds + shift_p = df[t]+1000. + outliers_upper = shift_p[shift_p.values > upper_thresh] + outliers_lower = shift_p[shift_p.values < lower_thresh] + else: + outliers_upper = df[t][df[t].values > upper_thresh] + outliers_lower = df[t][df[t].values < lower_thresh] + outliers = pd.concat([outliers_upper,outliers_lower]) + df.loc[outliers.index,t] = np.nan + df.loc[outliers.index,t] = np.nan + except Exception as e: + print(f'{t} is not flagged due to lack of data') + print(e) if make_plots: _plot_percentiles(k,t,df,var_threshold,upper_thresh,lower_thresh,stid) # AFTER OUTLIER REMOVAL