pypromice 1.5.2__py3-none-any.whl → 1.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pypromice might be problematic. Click here for more details.
- pypromice/__init__.py +2 -0
- pypromice/{qc → core/qc}/percentiles/compute_thresholds.py +2 -2
- pypromice/{qc → core/qc}/persistence.py +22 -29
- pypromice/{process → core/qc}/value_clipping.py +3 -3
- pypromice/core/variables/__init__.py +1 -0
- pypromice/core/variables/air_temperature.py +64 -0
- pypromice/core/variables/gps.py +221 -0
- pypromice/core/variables/humidity.py +111 -0
- pypromice/core/variables/precipitation.py +108 -0
- pypromice/core/variables/pressure_transducer_depth.py +79 -0
- pypromice/core/variables/radiation.py +422 -0
- pypromice/core/variables/station_boom_height.py +49 -0
- pypromice/core/variables/station_pose.py +375 -0
- pypromice/core/variables/wind.py +66 -0
- pypromice/io/bufr/__init__.py +0 -0
- pypromice/{postprocess → io/bufr}/bufr_to_csv.py +1 -1
- pypromice/{postprocess → io/bufr}/create_bufr_files.py +2 -2
- pypromice/{postprocess → io/bufr}/get_bufr.py +6 -6
- pypromice/{postprocess → io/bufr}/real_time_utilities.py +3 -3
- pypromice/io/ingest/__init__.py +0 -0
- pypromice/{utilities → io/ingest}/git.py +1 -3
- pypromice/io/ingest/l0.py +294 -0
- pypromice/io/ingest/l0_repository.py +103 -0
- pypromice/io/ingest/toa5.py +87 -0
- pypromice/{process → io}/write.py +1 -1
- pypromice/pipeline/L0toL1.py +291 -0
- pypromice/pipeline/L1toL2.py +233 -0
- pypromice/{process → pipeline}/L2toL3.py +102 -120
- pypromice/pipeline/__init__.py +4 -0
- pypromice/{process → pipeline}/aws.py +10 -82
- pypromice/{process → pipeline}/get_l2.py +2 -2
- pypromice/{process → pipeline}/get_l2tol3.py +19 -22
- pypromice/{process → pipeline}/join_l2.py +31 -32
- pypromice/{process → pipeline}/join_l3.py +16 -14
- pypromice/{process → pipeline}/resample.py +59 -46
- pypromice/{process → pipeline}/utilities.py +0 -22
- pypromice/resources/file_attributes.csv +4 -4
- pypromice/resources/variables.csv +27 -24
- {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/METADATA +1 -2
- pypromice-1.6.0.dist-info/RECORD +64 -0
- {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/WHEEL +1 -1
- pypromice-1.6.0.dist-info/entry_points.txt +12 -0
- pypromice/get/__init__.py +0 -1
- pypromice/get/get.py +0 -211
- pypromice/get/get_promice_data.py +0 -56
- pypromice/process/L0toL1.py +0 -536
- pypromice/process/L1toL2.py +0 -839
- pypromice/process/__init__.py +0 -4
- pypromice/process/load.py +0 -161
- pypromice-1.5.2.dist-info/RECORD +0 -53
- pypromice-1.5.2.dist-info/entry_points.txt +0 -13
- /pypromice/{postprocess → core}/__init__.py +0 -0
- /pypromice/{utilities → core}/dependency_graph.py +0 -0
- /pypromice/{qc → core/qc}/__init__.py +0 -0
- /pypromice/{qc → core/qc}/github_data_issues.py +0 -0
- /pypromice/{qc → core/qc}/percentiles/__init__.py +0 -0
- /pypromice/{qc → core/qc}/percentiles/outlier_detector.py +0 -0
- /pypromice/{qc → core/qc}/percentiles/thresholds.csv +0 -0
- /pypromice/{utilities → io}/__init__.py +0 -0
- /pypromice/{postprocess → io/bufr}/bufr_utilities.py +0 -0
- /pypromice/{postprocess → io/bufr}/positions_seed.csv +0 -0
- /pypromice/{station_configuration.py → io/bufr/station_configuration.py} +0 -0
- /pypromice/{postprocess → io}/make_metadata_csv.py +0 -0
- {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/licenses/LICENSE.txt +0 -0
- {pypromice-1.5.2.dist-info → pypromice-1.6.0.dist-info}/top_level.txt +0 -0
|
@@ -6,11 +6,14 @@ import pandas as pd
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import xarray as xr
|
|
8
8
|
from sklearn.linear_model import LinearRegression
|
|
9
|
-
from pypromice.qc.github_data_issues import adjustData
|
|
10
9
|
from scipy.interpolate import interp1d
|
|
11
10
|
from pathlib import Path
|
|
11
|
+
from pypromice.core.qc.github_data_issues import adjustData
|
|
12
12
|
import logging
|
|
13
13
|
|
|
14
|
+
from pypromice.core.qc.github_data_issues import adjustData
|
|
15
|
+
from pypromice.core.variables import humidity, station_boom_height
|
|
16
|
+
|
|
14
17
|
logger = logging.getLogger(__name__)
|
|
15
18
|
|
|
16
19
|
def toL3(L2,
|
|
@@ -24,7 +27,6 @@ def toL3(L2,
|
|
|
24
27
|
- continuous surface height, ice surface height, snow height
|
|
25
28
|
- thermistor depths
|
|
26
29
|
|
|
27
|
-
|
|
28
30
|
Parameters
|
|
29
31
|
----------
|
|
30
32
|
L2 : xarray:Dataset
|
|
@@ -41,6 +43,8 @@ def toL3(L2,
|
|
|
41
43
|
|
|
42
44
|
T_100 = T_0+100 # Get steam point temperature as K
|
|
43
45
|
|
|
46
|
+
is_bedrock = (str(ds.attrs['bedrock']).lower() == 'true')
|
|
47
|
+
|
|
44
48
|
# Turbulent heat flux calculation
|
|
45
49
|
if ('t_u' in ds.keys()) and \
|
|
46
50
|
('p_u' in ds.keys()) and \
|
|
@@ -48,30 +52,34 @@ def toL3(L2,
|
|
|
48
52
|
# Upper boom bulk calculation
|
|
49
53
|
T_h_u = ds['t_u'].copy() # Copy for processing
|
|
50
54
|
p_h_u = ds['p_u'].copy()
|
|
51
|
-
rh_h_u_wrt_ice_or_water = ds['rh_u_wrt_ice_or_water'].copy()
|
|
52
55
|
|
|
53
|
-
|
|
56
|
+
# Calculate specific humidity
|
|
57
|
+
q_h_u = humidity.calculate_specific_humidity(ds["t_u"],
|
|
58
|
+
ds["p_u"],
|
|
59
|
+
ds["rh_u_wrt_ice_or_water"])
|
|
60
|
+
|
|
54
61
|
if ('wspd_u' in ds.keys()) and \
|
|
55
62
|
('t_surf' in ds.keys()) and \
|
|
56
|
-
('
|
|
63
|
+
('z_boom_cor_u' in ds.keys()):
|
|
57
64
|
WS_h_u = ds['wspd_u'].copy()
|
|
58
65
|
Tsurf_h = ds['t_surf'].copy() # T surf from derived upper boom product. TODO is this okay to use with lower boom parameters?
|
|
59
|
-
z_WS_u = ds['z_boom_u'].copy() + 0.4 # Get height of Anemometer
|
|
60
|
-
z_T_u = ds['z_boom_u'].copy() - 0.1 # Get height of thermometer
|
|
61
66
|
|
|
62
|
-
|
|
67
|
+
z_WS_u = ds['z_boom_cor_u'].copy() + 0.4 # Get height of Anemometer
|
|
68
|
+
z_T_u = ds['z_boom_cor_u'].copy() - 0.1 # Get height of thermometer
|
|
69
|
+
|
|
70
|
+
if not is_bedrock:
|
|
63
71
|
SHF_h_u, LHF_h_u= calculate_tubulent_heat_fluxes(T_0, T_h_u, Tsurf_h, WS_h_u, # Calculate latent and sensible heat fluxes
|
|
64
72
|
z_WS_u, z_T_u, q_h_u, p_h_u)
|
|
65
73
|
|
|
66
74
|
ds['dshf_u'] = (('time'), SHF_h_u.data)
|
|
67
75
|
ds['dlhf_u'] = (('time'), LHF_h_u.data)
|
|
68
76
|
else:
|
|
69
|
-
logger.info('wspd_u, t_surf or
|
|
77
|
+
logger.info('wspd_u, t_surf or z_boom_cor_u missing, cannot calculate turbulent heat fluxes')
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
ds['qh_u'] = (
|
|
79
|
+
# Convert specific humidity from kg/kg to g/kg
|
|
80
|
+
ds['qh_u'] = humidity.convert(q_h_u)
|
|
73
81
|
else:
|
|
74
|
-
logger.info('t_u, p_u or rh_u_wrt_ice_or_water missing, cannot
|
|
82
|
+
logger.info('t_u, p_u or rh_u_wrt_ice_or_water missing, cannot calculate turbulent heat fluxes')
|
|
75
83
|
|
|
76
84
|
# Lower boom bulk calculation
|
|
77
85
|
if ds.attrs['number_of_booms']==2:
|
|
@@ -80,29 +88,35 @@ def toL3(L2,
|
|
|
80
88
|
('rh_l_wrt_ice_or_water' in ds.keys()):
|
|
81
89
|
T_h_l = ds['t_l'].copy() # Copy for processing
|
|
82
90
|
p_h_l = ds['p_l'].copy()
|
|
83
|
-
rh_h_l_wrt_ice_or_water = ds['rh_l_wrt_ice_or_water'].copy()
|
|
84
91
|
|
|
85
|
-
|
|
92
|
+
# Calculate specific humidity
|
|
93
|
+
q_h_l = humidity.calculate_specific_humidity(ds["t_l"],
|
|
94
|
+
ds["p_l"],
|
|
95
|
+
ds["rh_l_wrt_ice_or_water"])
|
|
86
96
|
|
|
87
97
|
if ('wspd_l' in ds.keys()) and \
|
|
88
98
|
('t_surf' in ds.keys()) and \
|
|
89
|
-
('
|
|
90
|
-
z_WS_l = ds['
|
|
91
|
-
z_T_l = ds['
|
|
99
|
+
('z_boom_cor_l' in ds.keys()):
|
|
100
|
+
z_WS_l = ds['z_boom_cor_l'].copy() + 0.4 # Get height of radiometer
|
|
101
|
+
z_T_l = ds['z_boom_cor_l'].copy() - 0.1 # Get height of thermometer
|
|
102
|
+
|
|
103
|
+
# Get wind speed lower boom measurements
|
|
92
104
|
WS_h_l = ds['wspd_l'].copy()
|
|
93
|
-
|
|
105
|
+
|
|
106
|
+
if not is_bedrock:
|
|
94
107
|
SHF_h_l, LHF_h_l= calculate_tubulent_heat_fluxes(T_0, T_h_l, Tsurf_h, WS_h_l, # Calculate latent and sensible heat fluxes
|
|
95
108
|
z_WS_l, z_T_l, q_h_l, p_h_l)
|
|
96
109
|
|
|
97
110
|
ds['dshf_l'] = (('time'), SHF_h_l.data)
|
|
98
111
|
ds['dlhf_l'] = (('time'), LHF_h_l.data)
|
|
99
112
|
else:
|
|
100
|
-
logger.info('wspd_l, t_surf or
|
|
113
|
+
logger.info('wspd_l, t_surf or z_boom_cor_l missing, cannot calculate turbulent heat fluxes')
|
|
114
|
+
|
|
115
|
+
# Convert specific humidity from kg/kg to g/kg
|
|
116
|
+
ds['qh_l'] = humidity.convert(q_h_l)
|
|
101
117
|
|
|
102
|
-
q_h_l = 1000 * q_h_l # Convert sp.humid from kg/kg to g/kg
|
|
103
|
-
ds['qh_l'] = (('time'), q_h_l.data)
|
|
104
118
|
else:
|
|
105
|
-
logger.info('t_l, p_l or rh_l_wrt_ice_or_water missing, cannot
|
|
119
|
+
logger.info('t_l, p_l or rh_l_wrt_ice_or_water missing, cannot calculate turbulent heat fluxes')
|
|
106
120
|
|
|
107
121
|
if len(station_config)==0:
|
|
108
122
|
logger.warning('\n***\nThe station configuration file is missing or improperly passed to pypromice. Some processing steps might fail.\n***\n')
|
|
@@ -158,12 +172,18 @@ def process_surface_height(ds, data_adjustments_dir, station_config={}):
|
|
|
158
172
|
ds['z_surf_1'] = ('time', ds['z_boom_u'].data * np.nan)
|
|
159
173
|
ds['z_surf_2'] = ('time', ds['z_boom_u'].data * np.nan)
|
|
160
174
|
|
|
175
|
+
z_boom_best_u = station_boom_height.adjust_and_include_uncorrected_values(ds["z_boom_u"], ds["t_u"])
|
|
176
|
+
|
|
161
177
|
if ds.attrs['site_type'] == 'ablation':
|
|
162
178
|
# Calculate surface heights for ablation sites
|
|
163
|
-
ds['z_surf_1'] = 2.6 -
|
|
179
|
+
ds['z_surf_1'] = 2.6 - z_boom_best_u
|
|
164
180
|
if ds.z_stake.notnull().any():
|
|
165
|
-
|
|
166
|
-
|
|
181
|
+
|
|
182
|
+
# Calculate stake boom height correction with uncorrected values where needed
|
|
183
|
+
z_stake_best = station_boom_height.adjust_and_include_uncorrected_values(ds["z_stake"], ds["t_u"])
|
|
184
|
+
|
|
185
|
+
first_valid_index = ds.time.where((z_stake_best + z_boom_best_u).notnull(), drop=True).data[0]
|
|
186
|
+
ds['z_surf_2'] = ds.z_surf_1.sel(time=first_valid_index) + z_stake_best.sel(time=first_valid_index) - z_stake_best
|
|
167
187
|
|
|
168
188
|
# Use corrected point data if available
|
|
169
189
|
if 'z_pt_cor' in ds.data_vars:
|
|
@@ -171,17 +191,24 @@ def process_surface_height(ds, data_adjustments_dir, station_config={}):
|
|
|
171
191
|
|
|
172
192
|
else:
|
|
173
193
|
# Calculate surface heights for other site types
|
|
174
|
-
first_valid_index = ds.time.where(
|
|
175
|
-
ds['z_surf_1'] =
|
|
194
|
+
first_valid_index = ds.time.where(z_boom_best_u.notnull(), drop=True).data[0]
|
|
195
|
+
ds['z_surf_1'] = z_boom_best_u.sel(time=first_valid_index) - z_boom_best_u
|
|
196
|
+
|
|
176
197
|
if 'z_stake' in ds.data_vars and ds.z_stake.notnull().any():
|
|
177
|
-
|
|
178
|
-
|
|
198
|
+
z_stake_best = station_boom_height.adjust_and_include_uncorrected_values(ds["z_stake"], ds["t_u"])
|
|
199
|
+
first_valid_index = ds.time.where(z_stake_best.notnull(), drop=True).data[0]
|
|
200
|
+
ds['z_surf_2'] = z_stake_best.sel(time=first_valid_index) - z_stake_best
|
|
201
|
+
|
|
179
202
|
if 'z_boom_l' in ds.data_vars:
|
|
180
|
-
|
|
181
|
-
#
|
|
182
|
-
|
|
203
|
+
|
|
204
|
+
# Calculate lower boom height correction with uncorrected values where needed
|
|
205
|
+
z_boom_best_l = station_boom_height.adjust_and_include_uncorrected_values(ds["z_boom_l"], ds["t_l"])
|
|
206
|
+
|
|
207
|
+
# need a combine first because KAN_U switches from having a z_stake_best
|
|
208
|
+
# to having a z_boom_best_l
|
|
209
|
+
first_valid_index = ds.time.where(z_boom_best_l.notnull(), drop=True).data[0]
|
|
183
210
|
ds['z_surf_2'] = ds['z_surf_2'].combine_first(
|
|
184
|
-
|
|
211
|
+
z_boom_best_l.sel(time=first_valid_index) - z_boom_best_l)
|
|
185
212
|
|
|
186
213
|
# Adjust data for the created surface height variables
|
|
187
214
|
ds = adjustData(ds, data_adjustments_dir, var_list=['z_surf_1', 'z_surf_2', 'z_ice_surf'])
|
|
@@ -218,8 +245,10 @@ def process_surface_height(ds, data_adjustments_dir, station_config={}):
|
|
|
218
245
|
.rolling('1D', center=True, min_periods=1)
|
|
219
246
|
.median())
|
|
220
247
|
|
|
221
|
-
z_ice_surf = z_ice_surf.
|
|
222
|
-
|
|
248
|
+
z_ice_surf = z_ice_surf.reindex(ds.time,
|
|
249
|
+
method=None).interpolate(method='time')
|
|
250
|
+
|
|
251
|
+
# here we make sure that the periods where both z_stake_best and z_pt are
|
|
223
252
|
# missing are also missing in z_ice_surf
|
|
224
253
|
msk = ds['z_ice_surf'].notnull() | ds['z_surf_2_adj'].notnull()
|
|
225
254
|
z_ice_surf = z_ice_surf.where(msk)
|
|
@@ -231,7 +260,7 @@ def process_surface_height(ds, data_adjustments_dir, station_config={}):
|
|
|
231
260
|
# sides are less than 0.01 m appart
|
|
232
261
|
|
|
233
262
|
# Forward and backward fill to identify bounds of gaps
|
|
234
|
-
df_filled = z_ice_surf.
|
|
263
|
+
df_filled = z_ice_surf.ffill().bfill()
|
|
235
264
|
|
|
236
265
|
# Identify gaps and their start and end dates
|
|
237
266
|
gaps = pd.DataFrame(index=z_ice_surf[z_ice_surf.isna()].index)
|
|
@@ -250,7 +279,7 @@ def process_surface_height(ds, data_adjustments_dir, station_config={}):
|
|
|
250
279
|
z_ice_surf.loc[gaps_to_fill] = df_filled.loc[gaps_to_fill]
|
|
251
280
|
|
|
252
281
|
# bringing the variable into the dataset
|
|
253
|
-
ds['z_ice_surf'] = z_ice_surf
|
|
282
|
+
ds['z_ice_surf'] = ('time', z_ice_surf.values)
|
|
254
283
|
|
|
255
284
|
ds['z_surf_combined'] = np.maximum(ds['z_surf_combined'], ds['z_ice_surf'])
|
|
256
285
|
ds['snow_height'] = np.maximum(0, ds['z_surf_combined'] - ds['z_ice_surf'])
|
|
@@ -268,6 +297,7 @@ def process_surface_height(ds, data_adjustments_dir, station_config={}):
|
|
|
268
297
|
ice_temp_vars = [v for v in ds.data_vars if 't_i_' in v]
|
|
269
298
|
vars_out = [v.replace('t', 'd_t') for v in ice_temp_vars]
|
|
270
299
|
vars_out.append('t_i_10m')
|
|
300
|
+
|
|
271
301
|
df_out = get_thermistor_depth(
|
|
272
302
|
ds[ice_temp_vars + ['z_surf_combined']].to_dataframe(),
|
|
273
303
|
ds.attrs['station_id'],
|
|
@@ -286,7 +316,7 @@ def combine_surface_height(df, site_type, threshold_ablation = -0.0002):
|
|
|
286
316
|
period is estimated each year (either the period when z_pt_cor decreases
|
|
287
317
|
or JJA if no better estimate) then different adjustmnents are conducted
|
|
288
318
|
to stitch the three time series together: z_ice_surface (adjusted from
|
|
289
|
-
z_pt_cor) or if
|
|
319
|
+
z_pt_cor) or if unavailable, z_surf_2 (adjusted from z_stake)
|
|
290
320
|
are used in the ablation period while an average of z_surf_1 and z_surf_2
|
|
291
321
|
are used otherwise, after they are being adjusted to z_ice_surf at the end
|
|
292
322
|
of the ablation season.
|
|
@@ -341,22 +371,24 @@ def combine_surface_height(df, site_type, threshold_ablation = -0.0002):
|
|
|
341
371
|
|
|
342
372
|
# defining ice ablation period from the decrease of a smoothed version of z_pt
|
|
343
373
|
# meaning when smoothed_z_pt.diff() < threshold_ablation
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
#
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
ind_ablation =
|
|
358
|
-
|
|
359
|
-
|
|
374
|
+
hourly_interp = (df["z_ice_surf"]
|
|
375
|
+
.resample("h")
|
|
376
|
+
.interpolate(limit=72))
|
|
377
|
+
once_smoothed = hourly_interp.rolling("14D", center=True, min_periods=1).mean()
|
|
378
|
+
smoothed_PT = once_smoothed.rolling("14D", center=True, min_periods=1).mean()
|
|
379
|
+
|
|
380
|
+
# ablation detection
|
|
381
|
+
diff_series = smoothed_PT.diff()
|
|
382
|
+
ind_ablation = np.full_like(diff_series, False, dtype=bool)
|
|
383
|
+
ind_ablation = np.logical_and(diff_series.values < threshold_ablation,
|
|
384
|
+
np.isin(diff_series.index.month, [6, 7, 8, 9]))
|
|
385
|
+
# making sure that we only qualify as ablation timestamps where we actually have ablation data
|
|
386
|
+
msk = np.isnan(smoothed_PT.values)
|
|
387
|
+
ind_ablation[msk] = False
|
|
388
|
+
|
|
389
|
+
# reindex back to df
|
|
390
|
+
smoothed_PT = smoothed_PT.reindex(df.index, method="ffill")
|
|
391
|
+
ind_ablation = pd.Series(ind_ablation, index=diff_series.index).reindex(df.index, fill_value=False).values
|
|
360
392
|
|
|
361
393
|
# finding the beginning and end of each period with True
|
|
362
394
|
idx = np.argwhere(np.diff(np.r_[False,ind_ablation, False])).reshape(-1, 2)
|
|
@@ -375,13 +407,12 @@ def combine_surface_height(df, site_type, threshold_ablation = -0.0002):
|
|
|
375
407
|
# finding the beginning and end of each period with True
|
|
376
408
|
idx = np.argwhere(np.diff(np.r_[False,ind_ablation, False])).reshape(-1, 2)
|
|
377
409
|
idx[:, 1] -= 1
|
|
378
|
-
|
|
379
410
|
# because the smooth_PT sees 7 days ahead, it starts showing a decline
|
|
380
|
-
# 7 days in advance, we therefore need to exclude the first
|
|
411
|
+
# 7 days in advance, we therefore need to exclude the first few days of
|
|
381
412
|
# each ablation period
|
|
382
413
|
for start, end in idx:
|
|
383
414
|
period_start = df.index[start]
|
|
384
|
-
period_end = period_start + pd.Timedelta(days=
|
|
415
|
+
period_end = period_start + pd.Timedelta(days=3)
|
|
385
416
|
exclusion_period = (df.index >= period_start) & (df.index < period_end)
|
|
386
417
|
ind_ablation[exclusion_period] = False
|
|
387
418
|
|
|
@@ -390,8 +421,6 @@ def combine_surface_height(df, site_type, threshold_ablation = -0.0002):
|
|
|
390
421
|
z=df["z_ice_surf_adj"].interpolate(limit=24*2).copy()
|
|
391
422
|
|
|
392
423
|
# the surface heights are adjusted so that they start at 0
|
|
393
|
-
|
|
394
|
-
|
|
395
424
|
if any(~np.isnan(hs2.iloc[:24*7])):
|
|
396
425
|
hs2 = hs2 - hs2.iloc[:24*7].mean()
|
|
397
426
|
|
|
@@ -467,9 +496,8 @@ def combine_surface_height(df, site_type, threshold_ablation = -0.0002):
|
|
|
467
496
|
# to hs1 and hs2 the year after.
|
|
468
497
|
|
|
469
498
|
for i, y in enumerate(years):
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
logger.debug(str(y))
|
|
499
|
+
logger.debug(f'{y}: Ablation from {z.index[ind_start[i]]} to {z.index[ind_end[i]]}')
|
|
500
|
+
|
|
473
501
|
# defining subsets of hs1, hs2, z
|
|
474
502
|
hs1_jja = hs1[str(y)+'-06-01':str(y)+'-09-01']
|
|
475
503
|
hs2_jja = hs2[str(y)+'-06-01':str(y)+'-09-01']
|
|
@@ -585,7 +613,7 @@ def combine_surface_height(df, site_type, threshold_ablation = -0.0002):
|
|
|
585
613
|
# import pdb; pdb.set_trace()
|
|
586
614
|
# if there's ablation and
|
|
587
615
|
# if there are PT data available at the end of the melt season
|
|
588
|
-
if z.iloc[(ind_end[i]-24*7):
|
|
616
|
+
if z.iloc[(ind_end[i]-24*7):ind_end[i]].notnull().any():
|
|
589
617
|
logger.debug('adjusting hs2 to z')
|
|
590
618
|
# then we adjust hs2 to the end-of-ablation z
|
|
591
619
|
# first trying at the end of melt season
|
|
@@ -602,7 +630,7 @@ def combine_surface_height(df, site_type, threshold_ablation = -0.0002):
|
|
|
602
630
|
np.nanmean(hs2.iloc[(ind_start[i+1]-24*7):(ind_start[i+1]+24*7)]) + \
|
|
603
631
|
np.nanmean(z.iloc[(ind_start[i+1]-24*7):(ind_start[i+1]+24*7)])
|
|
604
632
|
else:
|
|
605
|
-
logger.debug('no ablation')
|
|
633
|
+
logger.debug('no ablation data')
|
|
606
634
|
hs1_following_winter = hs1[str(y)+'-09-01':str(y+1)+'-03-01'].copy()
|
|
607
635
|
hs2_following_winter = hs2[str(y)+'-09-01':str(y+1)+'-03-01'].copy()
|
|
608
636
|
if all(np.isnan(hs2_following_winter)):
|
|
@@ -877,14 +905,18 @@ def get_thermistor_depth(df_in, site, station_config):
|
|
|
877
905
|
|
|
878
906
|
# removing negative depth
|
|
879
907
|
df_in.loc[df_in[depth_cols_name[i]]<0, depth_cols_name[i]] = np.nan
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
908
|
+
|
|
909
|
+
logger.info("interpolating 10 m firn/ice temperature (on hourly values)")
|
|
910
|
+
df_in_h = df_in[depth_cols_name+temp_cols_name].resample('h').mean()
|
|
911
|
+
df_in_h['t_i_10m'] = interpolate_temperature(
|
|
912
|
+
df_in_h.index.values,
|
|
913
|
+
df_in_h[depth_cols_name].values.astype(float),
|
|
914
|
+
df_in_h[temp_cols_name].values.astype(float),
|
|
885
915
|
kind="linear",
|
|
886
916
|
min_diff_to_depth=1.5,
|
|
887
917
|
).set_index('date').values
|
|
918
|
+
df_in['t_i_10m'] = df_in_h['t_i_10m'].reindex(df_in.index,
|
|
919
|
+
method=None)
|
|
888
920
|
|
|
889
921
|
# filtering
|
|
890
922
|
ind_pos = df_in["t_i_10m"] > 0.1
|
|
@@ -993,7 +1025,7 @@ def piecewise_smoothing_and_interpolation(data_series, breaks):
|
|
|
993
1025
|
|
|
994
1026
|
Parameters
|
|
995
1027
|
----------
|
|
996
|
-
data_series :
|
|
1028
|
+
data_series : pd.Series
|
|
997
1029
|
Series of observed latitude, longitude or elevation with datetime index.
|
|
998
1030
|
breaks: list
|
|
999
1031
|
List of timestamps of station relocation. First and last item should be
|
|
@@ -1225,56 +1257,6 @@ def calculate_viscosity(T_h, T_0, rho_atm):
|
|
|
1225
1257
|
# Kinematic viscosity of air in m^2/s
|
|
1226
1258
|
return mu / rho_atm
|
|
1227
1259
|
|
|
1228
|
-
def calculate_specific_humidity(T_0, T_100, T_h, p_h, rh_h_wrt_ice_or_water, es_0=6.1071, es_100=1013.246, eps=0.622):
|
|
1229
|
-
'''Calculate specific humidity
|
|
1230
|
-
Parameters
|
|
1231
|
-
----------
|
|
1232
|
-
T_0 : float
|
|
1233
|
-
Steam point temperature. Default is 273.15.
|
|
1234
|
-
T_100 : float
|
|
1235
|
-
Steam point temperature in Kelvin
|
|
1236
|
-
T_h : xarray.DataArray
|
|
1237
|
-
Air temperature
|
|
1238
|
-
p_h : xarray.DataArray
|
|
1239
|
-
Air pressure
|
|
1240
|
-
rh_h_wrt_ice_or_water : xarray.DataArray
|
|
1241
|
-
Relative humidity corrected
|
|
1242
|
-
es_0 : float
|
|
1243
|
-
Saturation vapour pressure at the melting point (hPa)
|
|
1244
|
-
es_100 : float
|
|
1245
|
-
Saturation vapour pressure at steam point temperature (hPa)
|
|
1246
|
-
eps : int
|
|
1247
|
-
ratio of molar masses of vapor and dry air (0.622)
|
|
1248
|
-
|
|
1249
|
-
Returns
|
|
1250
|
-
-------
|
|
1251
|
-
xarray.DataArray
|
|
1252
|
-
Specific humidity data array
|
|
1253
|
-
'''
|
|
1254
|
-
# Saturation vapour pressure above 0 C (hPa)
|
|
1255
|
-
es_wtr = 10**(-7.90298 * (T_100 / (T_h + T_0) - 1) + 5.02808 * np.log10(T_100 / (T_h + T_0))
|
|
1256
|
-
- 1.3816E-7 * (10**(11.344 * (1 - (T_h + T_0) / T_100)) - 1)
|
|
1257
|
-
+ 8.1328E-3 * (10**(-3.49149 * (T_100 / (T_h + T_0) -1)) - 1) + np.log10(es_100))
|
|
1258
|
-
|
|
1259
|
-
# Saturation vapour pressure below 0 C (hPa)
|
|
1260
|
-
es_ice = 10**(-9.09718 * (T_0 / (T_h + T_0) - 1) - 3.56654
|
|
1261
|
-
* np.log10(T_0 / (T_h + T_0)) + 0.876793
|
|
1262
|
-
* (1 - (T_h + T_0) / T_0)
|
|
1263
|
-
+ np.log10(es_0))
|
|
1264
|
-
|
|
1265
|
-
# Specific humidity at saturation (incorrect below melting point)
|
|
1266
|
-
q_sat = eps * es_wtr / (p_h - (1 - eps) * es_wtr)
|
|
1267
|
-
|
|
1268
|
-
# Replace saturation specific humidity values below melting point
|
|
1269
|
-
freezing = T_h < 0
|
|
1270
|
-
q_sat[freezing] = eps * es_ice[freezing] / (p_h[freezing] - (1 - eps) * es_ice[freezing])
|
|
1271
|
-
|
|
1272
|
-
q_nan = np.isnan(T_h) | np.isnan(p_h)
|
|
1273
|
-
q_sat[q_nan] = np.nan
|
|
1274
|
-
|
|
1275
|
-
# Convert to kg/kg
|
|
1276
|
-
return rh_h_wrt_ice_or_water * q_sat / 100
|
|
1277
|
-
|
|
1278
1260
|
if __name__ == "__main__":
|
|
1279
1261
|
# unittest.main()
|
|
1280
1262
|
pass
|
|
@@ -16,11 +16,13 @@ from importlib import metadata
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import pypromice.resources
|
|
19
|
-
from pypromice.
|
|
20
|
-
from pypromice.
|
|
21
|
-
from pypromice.
|
|
22
|
-
from pypromice.
|
|
23
|
-
from pypromice.
|
|
19
|
+
from pypromice.pipeline.L0toL1 import toL1
|
|
20
|
+
from pypromice.pipeline.L1toL2 import toL2
|
|
21
|
+
from pypromice.pipeline.L2toL3 import toL3
|
|
22
|
+
from pypromice.pipeline import utilities
|
|
23
|
+
from pypromice.io import write
|
|
24
|
+
from pypromice.io.ingest.l0 import (load_data_files, load_config)
|
|
25
|
+
from pypromice.io.ingest.git import get_commit_hash_and_check_dirty
|
|
24
26
|
|
|
25
27
|
pd.set_option("display.precision", 2)
|
|
26
28
|
xr.set_options(keep_attrs=True)
|
|
@@ -66,7 +68,6 @@ class AWS(object):
|
|
|
66
68
|
)
|
|
67
69
|
|
|
68
70
|
# Load config, variables CSF standards, and L0 files
|
|
69
|
-
self.config = self.loadConfig(config_file, inpath)
|
|
70
71
|
self.vars = pypromice.resources.load_variables(var_file)
|
|
71
72
|
self.meta = pypromice.resources.load_metadata(meta_file)
|
|
72
73
|
self.data_issues_repository = Path(data_issues_repository)
|
|
@@ -85,7 +86,9 @@ class AWS(object):
|
|
|
85
86
|
self.meta["source"] = json.dumps(source_dict)
|
|
86
87
|
|
|
87
88
|
# Load config file
|
|
88
|
-
|
|
89
|
+
config = load_config(config_file, inpath)
|
|
90
|
+
L0 = load_data_files(config)
|
|
91
|
+
|
|
89
92
|
self.L0 = []
|
|
90
93
|
for l in L0:
|
|
91
94
|
n = write.getColNames(self.vars, l)
|
|
@@ -148,78 +151,3 @@ class AWS(object):
|
|
|
148
151
|
logger.info("Level 3 processing...")
|
|
149
152
|
self.L3 = toL3(self.L2, data_adjustments_dir=self.data_issues_repository / "adjustments")
|
|
150
153
|
|
|
151
|
-
def loadConfig(self, config_file, inpath):
|
|
152
|
-
"""Load configuration from .toml file
|
|
153
|
-
|
|
154
|
-
Parameters
|
|
155
|
-
----------
|
|
156
|
-
config_file : str
|
|
157
|
-
TOML file path
|
|
158
|
-
inpath : str
|
|
159
|
-
Input folder directory where L0 files can be found
|
|
160
|
-
|
|
161
|
-
Returns
|
|
162
|
-
-------
|
|
163
|
-
conf : dict
|
|
164
|
-
Configuration parameters
|
|
165
|
-
"""
|
|
166
|
-
conf = load.getConfig(config_file, inpath)
|
|
167
|
-
return conf
|
|
168
|
-
|
|
169
|
-
def loadL0(self):
|
|
170
|
-
"""Load level 0 (L0) data from associated TOML-formatted
|
|
171
|
-
config file and L0 data file
|
|
172
|
-
|
|
173
|
-
Try readL0file() using the config with msg_lat & msg_lon appended. The
|
|
174
|
-
specific ParserError except will occur when the number of columns in
|
|
175
|
-
the tx file does not match the expected columns. In this case, remove
|
|
176
|
-
msg_lat & msg_lon from the config and call readL0file() again. These
|
|
177
|
-
station files either have no data after Nov 2022 (when msg_lat &
|
|
178
|
-
msg_lon were added to processing), or for whatever reason these fields
|
|
179
|
-
did not exist in the modem message and were not added.
|
|
180
|
-
|
|
181
|
-
Returns
|
|
182
|
-
-------
|
|
183
|
-
ds_list : list
|
|
184
|
-
List of L0 xr.Dataset objects
|
|
185
|
-
"""
|
|
186
|
-
ds_list = []
|
|
187
|
-
for k in self.config.keys():
|
|
188
|
-
target = self.config[k]
|
|
189
|
-
try:
|
|
190
|
-
ds_list.append(self.readL0file(target))
|
|
191
|
-
|
|
192
|
-
except pd.errors.ParserError as e:
|
|
193
|
-
# ParserError: Too many columns specified: expected 40 and found 38
|
|
194
|
-
# logger.info(f'-----> No msg_lat or msg_lon for {k}')
|
|
195
|
-
for item in ["msg_lat", "msg_lon"]:
|
|
196
|
-
target["columns"].remove(item) # Also removes from self.config
|
|
197
|
-
ds_list.append(self.readL0file(target))
|
|
198
|
-
logger.info(f"L0 data successfully loaded from {k}")
|
|
199
|
-
return ds_list
|
|
200
|
-
|
|
201
|
-
def readL0file(self, conf):
|
|
202
|
-
"""Read L0 .txt file to Dataset object using config dictionary and
|
|
203
|
-
populate with initial metadata
|
|
204
|
-
|
|
205
|
-
Parameters
|
|
206
|
-
----------
|
|
207
|
-
conf : dict
|
|
208
|
-
Configuration parameters
|
|
209
|
-
|
|
210
|
-
Returns
|
|
211
|
-
-------
|
|
212
|
-
ds : xr.Dataset
|
|
213
|
-
L0 data
|
|
214
|
-
"""
|
|
215
|
-
file_version = conf.get("file_version", -1)
|
|
216
|
-
ds = load.getL0(
|
|
217
|
-
conf["file"],
|
|
218
|
-
conf["nodata"],
|
|
219
|
-
conf["columns"],
|
|
220
|
-
conf["skiprows"],
|
|
221
|
-
file_version,
|
|
222
|
-
time_offset=conf.get("time_offset"),
|
|
223
|
-
)
|
|
224
|
-
ds = utilities.populateMeta(ds, conf, ["columns", "skiprows", "modem"])
|
|
225
|
-
return ds
|
|
@@ -5,8 +5,8 @@ import sys
|
|
|
5
5
|
from argparse import ArgumentParser
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
-
from pypromice.
|
|
9
|
-
from pypromice.
|
|
8
|
+
from pypromice.pipeline.aws import AWS
|
|
9
|
+
from pypromice.io.write import prepare_and_write
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def parse_arguments_l2():
|
|
@@ -5,9 +5,9 @@ from pathlib import Path
|
|
|
5
5
|
import xarray as xr
|
|
6
6
|
from argparse import ArgumentParser
|
|
7
7
|
import pypromice
|
|
8
|
-
from pypromice.
|
|
8
|
+
from pypromice.pipeline.L2toL3 import toL3
|
|
9
9
|
import pypromice.resources
|
|
10
|
-
from pypromice.
|
|
10
|
+
from pypromice.io.write import prepare_and_write
|
|
11
11
|
logger = logging.getLogger(__name__)
|
|
12
12
|
|
|
13
13
|
def parse_arguments_l2tol3(debug_args=None):
|
|
@@ -17,13 +17,13 @@ def parse_arguments_l2tol3(debug_args=None):
|
|
|
17
17
|
parser.add_argument('-c', '--config_folder', type=str, required=True,
|
|
18
18
|
default='../aws-l0/metadata/station_configurations/',
|
|
19
19
|
help='Path to folder with sites configuration (TOML) files')
|
|
20
|
-
parser.add_argument('-i', '--inpath', type=str, required=True,
|
|
20
|
+
parser.add_argument('-i', '--inpath', type=str, required=True,
|
|
21
21
|
help='Path to Level 2 .nc data file')
|
|
22
|
-
parser.add_argument('-o', '--outpath', default=None, type=str, required=False,
|
|
22
|
+
parser.add_argument('-o', '--outpath', default=None, type=str, required=False,
|
|
23
23
|
help='Path where to write output')
|
|
24
|
-
parser.add_argument('-v', '--variables', default=None, type=str,
|
|
24
|
+
parser.add_argument('-v', '--variables', default=None, type=str,
|
|
25
25
|
required=False, help='File path to variables look-up table')
|
|
26
|
-
parser.add_argument('-m', '--metadata', default=None, type=str,
|
|
26
|
+
parser.add_argument('-m', '--metadata', default=None, type=str,
|
|
27
27
|
required=False, help='File path to metadata')
|
|
28
28
|
parser.add_argument('--data_issues_path', '--issues', default=None, help="Path to data issues repository")
|
|
29
29
|
|
|
@@ -40,11 +40,11 @@ def get_l2tol3(config_folder: Path|str, inpath, outpath, variables, metadata, da
|
|
|
40
40
|
level=logging.INFO,
|
|
41
41
|
stream=sys.stdout,
|
|
42
42
|
)
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
# Define Level 2 dataset from file
|
|
45
45
|
with xr.open_dataset(inpath) as l2:
|
|
46
46
|
l2.load()
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
# Remove encoding attributes from NetCDF
|
|
49
49
|
for varname in l2.variables:
|
|
50
50
|
if l2[varname].encoding!={}:
|
|
@@ -54,7 +54,7 @@ def get_l2tol3(config_folder: Path|str, inpath, outpath, variables, metadata, da
|
|
|
54
54
|
l2.attrs['bedrock'] = l2.attrs['bedrock'] == 'True'
|
|
55
55
|
if 'number_of_booms' in l2.attrs.keys():
|
|
56
56
|
l2.attrs['number_of_booms'] = int(l2.attrs['number_of_booms'])
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
# importing station_config (dict) from config_folder (str path)
|
|
59
59
|
config_file = config_folder / (l2.attrs['station_id']+'.toml')
|
|
60
60
|
|
|
@@ -62,7 +62,7 @@ def get_l2tol3(config_folder: Path|str, inpath, outpath, variables, metadata, da
|
|
|
62
62
|
# File exists, load the configuration
|
|
63
63
|
station_config = toml.load(config_file)
|
|
64
64
|
else:
|
|
65
|
-
# File does not exist, initialize with standard info
|
|
65
|
+
# File does not exist, initialize with standard info
|
|
66
66
|
# this was prefered by RSF over exiting with error
|
|
67
67
|
logger.error("\n***\nNo station_configuration file for %s.\nPlease create one on AWS-L0/metadata/station_configurations.\n***"%l2.attrs['station_id'])
|
|
68
68
|
station_config = {"stid":l2.attrs['station_id'],
|
|
@@ -70,7 +70,7 @@ def get_l2tol3(config_folder: Path|str, inpath, outpath, variables, metadata, da
|
|
|
70
70
|
"project": "PROMICE",
|
|
71
71
|
"location_type": "ice sheet",
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
# checking that the adjustement directory is properly given
|
|
75
75
|
if data_issues_path is None:
|
|
76
76
|
data_issues_path = Path("../PROMICE-AWS-data-issues")
|
|
@@ -82,7 +82,7 @@ def get_l2tol3(config_folder: Path|str, inpath, outpath, variables, metadata, da
|
|
|
82
82
|
data_issues_path = Path(data_issues_path)
|
|
83
83
|
|
|
84
84
|
data_adjustments_dir = data_issues_path / "adjustments"
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
# Perform Level 3 processing
|
|
87
87
|
l3 = toL3(l2, data_adjustments_dir, station_config)
|
|
88
88
|
|
|
@@ -92,20 +92,17 @@ def get_l2tol3(config_folder: Path|str, inpath, outpath, variables, metadata, da
|
|
|
92
92
|
if outpath is not None:
|
|
93
93
|
prepare_and_write(l3, outpath, v, m, '60min')
|
|
94
94
|
prepare_and_write(l3, outpath, v, m, '1D')
|
|
95
|
-
prepare_and_write(l3, outpath, v, m, '
|
|
95
|
+
prepare_and_write(l3, outpath, v, m, 'ME')
|
|
96
96
|
return l3
|
|
97
97
|
|
|
98
98
|
def main():
|
|
99
99
|
args = parse_arguments_l2tol3()
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
_ = get_l2tol3(args.config_folder,
|
|
104
|
-
args.inpath,
|
|
100
|
+
_ = get_l2tol3(args.config_folder,
|
|
101
|
+
args.inpath,
|
|
105
102
|
args.outpath,
|
|
106
|
-
args.variables,
|
|
107
|
-
args.metadata,
|
|
103
|
+
args.variables,
|
|
104
|
+
args.metadata,
|
|
108
105
|
args.data_issues_path)
|
|
109
|
-
|
|
110
|
-
if __name__ == "__main__":
|
|
106
|
+
|
|
107
|
+
if __name__ == "__main__":
|
|
111
108
|
main()
|