Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write L2 to file and leave all additional variable derivation for the L2toL3 step #255

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
64b6aa2
Revert "revert"
BaptisteVandecrux Jun 4, 2024
df5a9c5
correcting typo
BaptisteVandecrux Jun 4, 2024
8b4d430
aws process handling modified
PennyHow Jun 7, 2024
f8de75c
Level 2 processing capabilities added to get_l3
PennyHow Jun 7, 2024
17245f7
get_l3 renamed to l2_to_l3
PennyHow Jun 7, 2024
cc6bfb2
l2_to_l3 functionality from pypromice
PennyHow Jun 7, 2024
09f12a0
minor de-bug for process test to run
PennyHow Jun 7, 2024
17b647c
Update .gitignore
BaptisteVandecrux Jun 10, 2024
5927dbb
L2 split from L3 CLI processing
PennyHow Jun 10, 2024
b9a2e70
unit tests moved to separate module
PennyHow Jun 10, 2024
bfb1603
file writing functions moved to separate module
PennyHow Jun 10, 2024
eb58371
Loading functions moved to separate module
PennyHow Jun 10, 2024
f536e74
Handling and reformating functions moved
PennyHow Jun 10, 2024
9b2d8f4
resampling functions moved
PennyHow Jun 10, 2024
2e98bfd
aws module updated with structure changes
PennyHow Jun 10, 2024
526f0f0
Logging package typo
PennyHow Jun 10, 2024
de7e5d8
de-bugging
PennyHow Jun 10, 2024
e9bcc8c
get_l2 and l2_to_l3 process test added
PennyHow Jun 10, 2024
496ef03
data prep and write function moved out of AWS class
PennyHow Jun 10, 2024
facf0ee
actions de-bug
PennyHow Jun 10, 2024
bd8d8a5
stations for testing changed
PennyHow Jun 10, 2024
679aa4f
l2 action de-bug
PennyHow Jun 10, 2024
e5db5d6
loop de-bug in l2 test action
PennyHow Jun 10, 2024
a6911f0
out filepath changed
PennyHow Jun 10, 2024
ba1a424
Path alteration 2
PennyHow Jun 10, 2024
0ae13cd
poutput directory alteration 3
PennyHow Jun 10, 2024
6a9b66b
action stages merged
PennyHow Jun 10, 2024
eaa49fd
get_l2tol3 action test skipped
PennyHow Jun 10, 2024
e0d93b5
typo fix in join_levels.py
BaptisteVandecrux Jun 11, 2024
a6e8807
further debugging
BaptisteVandecrux Jun 11, 2024
1a406f4
creating folder before writing files, writing hourly daily monthly fi…
BaptisteVandecrux Jun 11, 2024
697fa60
typo fix
BaptisteVandecrux Jun 12, 2024
b9fb365
fixed daily output file name
BaptisteVandecrux Jun 12, 2024
9d0cb14
resampling frequency specified
PennyHow Jun 12, 2024
59cfbf0
modified file filter added
PennyHow Jun 12, 2024
fdb6b02
reverts changes on get_l2.py to write to file both 10min and hourly data
BaptisteVandecrux Jun 12, 2024
3bd0435
renamed join_levels to join_l2 because join_l3 will have different me…
BaptisteVandecrux Jun 12, 2024
2cebff4
skipping resample after join_l2, fixed setup.py for join_l2
BaptisteVandecrux Jun 12, 2024
cb1f6a3
fixing test
BaptisteVandecrux Jun 13, 2024
d1d46b7
fixed function names
BaptisteVandecrux Jun 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/process_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
run: |
mkdir $GITHUB_WORKSPACE/out/
for i in $(echo ${{ env.TEST_STATION }} | tr ' ' '\n'); do
python3 $GITHUB_WORKSPACE/main/src/pypromice/process/get_l3.py -v $GITHUB_WORKSPACE/main/src/pypromice/process/variables.csv -m $GITHUB_WORKSPACE/main/src/pypromice/process/metadata.csv -c $GITHUB_WORKSPACE/aws-l0/raw/config/$i.toml -i $GITHUB_WORKSPACE/aws-l0/raw -o $GITHUB_WORKSPACE/out/
python3 $GITHUB_WORKSPACE/main/src/pypromice/process/get_l2.py -v $GITHUB_WORKSPACE/main/src/pypromice/process/variables.csv -m $GITHUB_WORKSPACE/main/src/pypromice/process/metadata.csv -c $GITHUB_WORKSPACE/aws-l0/raw/config/$i.toml -i $GITHUB_WORKSPACE/aws-l0/raw -o $GITHUB_WORKSPACE/out/
done
- name: Upload test output
uses: actions/upload-artifact@v3
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ src/pypromice/postprocess/positions.csv

# sqlite db files
*.db
*.bak
BaptisteVandecrux marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@
'console_scripts': [
'get_promice_data = pypromice.get.get_promice_data:get_promice_data',
'get_l0tx = pypromice.tx.get_l0tx:get_l0tx',
'get_l2 = pypromice.process.get_l2:get_l2',
'join_l2 = pypromice.process.join_l2:join_l2',
'get_l3 = pypromice.process.get_l3:get_l3',
'join_l3 = pypromice.process.join_l3:join_l3',
BaptisteVandecrux marked this conversation as resolved.
Show resolved Hide resolved
'get_watsontx = pypromice.tx.get_watsontx:get_watsontx',
'get_bufr = pypromice.postprocess.get_bufr:main',
'get_msg = pypromice.tx.get_msg:get_msg'
Expand Down
67 changes: 59 additions & 8 deletions src/pypromice/process/L1toL2.py
BaptisteVandecrux marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,18 @@ def toL2(
eps_clear=9.36508e-6,
emissivity=0.97,
) -> xr.Dataset:
'''Process one Level 1 (L1) product to Level 2
'''Process one Level 1 (L1) product to Level 2.
In this step we do:
- manual flagging and adjustments
- automated QC: persistence, percentile
- custom filter: gps_alt filter, NaN t_rad removed from dlr & ulr
- smoothing of tilt and rot
- calculation of rh with regards to ice in subfreezin conditions
- calculation of cloud coverage
- correction of dsr and usr for tilt
- filtering of dsr based on a theoritical TOA irradiance and grazing light
- calculation of albedo
- calculation of directional wind speed

Parameters
----------
Expand Down Expand Up @@ -85,10 +96,20 @@ def toL2(
ds['dlr'] = ds.dlr.where(ds.t_rad.notnull())
ds['ulr'] = ds.ulr.where(ds.t_rad.notnull())

# calculating realtive humidity with regard to ice
T_100 = _getTempK(T_0)
ds['rh_u_cor'] = correctHumidity(ds['rh_u'], ds['t_u'],
T_0, T_100, ews, ei0)

if ds.attrs['number_of_booms']==2:
ds['rh_l_cor'] = correctHumidity(ds['rh_l'], ds['t_l'],
T_0, T_100, ews, ei0)

if hasattr(ds,'t_i'):
if ~ds['t_i'].isnull().all():
ds['rh_i_cor'] = correctHumidity(ds['rh_i'], ds['t_i'],
T_0, T_100, ews, ei0)

# Determiune cloud cover for on-ice stations
cc = calcCloudCoverage(ds['t_u'], T_0, eps_overcast, eps_clear, # Calculate cloud coverage
ds['dlr'], ds.attrs['station_id'])
Expand Down Expand Up @@ -176,22 +197,52 @@ def toL2(
ds['precip_u_cor'], ds['precip_u_rate'] = correctPrecip(ds['precip_u'],
ds['wspd_u'])
if ds.attrs['number_of_booms']==2:
ds['rh_l_cor'] = correctHumidity(ds['rh_l'], ds['t_l'], # Correct relative humidity
T_0, T_100, ews, ei0)

if ~ds['precip_l'].isnull().all() and precip_flag: # Correct precipitation
ds['precip_l_cor'], ds['precip_l_rate']= correctPrecip(ds['precip_l'],
ds['wspd_l'])

if hasattr(ds,'t_i'):
if ~ds['t_i'].isnull().all(): # Instantaneous msg processing
ds['rh_i_cor'] = correctHumidity(ds['rh_i'], ds['t_i'], # Correct relative humidity
T_0, T_100, ews, ei0)
# Get directional wind speed
ds['wdir_u'] = ds['wdir_u'].where(ds['wspd_u'] != 0)
ds['wspd_x_u'], ds['wspd_y_u'] = calcDirWindSpeeds(ds['wspd_u'], ds['wdir_u'])

if ds.attrs['number_of_booms']==2:
ds['wdir_l'] = ds['wdir_l'].where(ds['wspd_l'] != 0)
ds['wspd_x_l'], ds['wspd_y_l'] = calcDirWindSpeeds(ds['wspd_l'], ds['wdir_l'])

if hasattr(ds, 'wdir_i'):
if ~ds['wdir_i'].isnull().all() and ~ds['wspd_i'].isnull().all():
ds['wdir_i'] = ds['wdir_i'].where(ds['wspd_i'] != 0)
ds['wspd_x_i'], ds['wspd_y_i'] = calcDirWindSpeeds(ds['wspd_i'], ds['wdir_i'])


ds = clip_values(ds, vars_df)
return ds


def calcDirWindSpeeds(wspd, wdir, deg2rad=np.pi/180):
'''Calculate directional wind speed from wind speed and direction

Parameters
----------
wspd : xr.Dataarray
Wind speed data array
wdir : xr.Dataarray
Wind direction data array
deg2rad : float
Degree to radians coefficient. The default is np.pi/180

Returns
-------
wspd_x : xr.Dataarray
Wind speed in X direction
wspd_y : xr.Datarray
Wind speed in Y direction
'''
wspd_x = wspd * np.sin(wdir * deg2rad)
wspd_y = wspd * np.cos(wdir * deg2rad)
return wspd_x, wspd_y


def calcCloudCoverage(T, T_0, eps_overcast, eps_clear, dlr, station_id):
'''Calculate cloud cover from T and T_0

Expand Down
40 changes: 4 additions & 36 deletions src/pypromice/process/L2toL3.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

def toL3(L2, T_0=273.15, z_0=0.001, R_d=287.05, eps=0.622, es_0=6.1071,
es_100=1013.246):
'''Process one Level 2 (L2) product to Level 3 (L3)
'''Process one Level 2 (L2) product to Level 3 (L3) meaning calculating all
derived variables:
- Sensible fluxes


Parameters
----------
Expand All @@ -32,9 +35,6 @@ def toL3(L2, T_0=273.15, z_0=0.001, R_d=287.05, eps=0.622, es_0=6.1071,

T_100 = _getTempK(T_0) # Get steam point temperature as K

ds['wdir_u'] = ds['wdir_u'].where(ds['wspd_u'] != 0) # Get directional wind speed
ds['wspd_x_u'], ds['wspd_y_u'] = calcDirWindSpeeds(ds['wspd_u'], ds['wdir_u'])

# Upper boom bulk calculation
T_h_u = ds['t_u'].copy() # Copy for processing
p_h_u = ds['p_u'].copy()
Expand Down Expand Up @@ -85,41 +85,9 @@ def toL3(L2, T_0=273.15, z_0=0.001, R_d=287.05, eps=0.622, es_0=6.1071,
q_h_l = cleanSpHumid(q_h_l, T_h_l, Tsurf_h, p_h_l, RH_cor_h_l) # Clean sp.humid values
ds['qh_l'] = (('time'), q_h_l.data)

ds['wdir_l'] = ds['wdir_l'].where(ds['wspd_l'] != 0) # Get directional wind speed
ds['wspd_x_l'], ds['wspd_y_l'] = calcDirWindSpeeds(ds['wspd_l'], ds['wdir_l'])

if hasattr(ds, 'wdir_i'):
if ~ds['wdir_i'].isnull().all() and ~ds['wspd_i'].isnull().all(): # Instantaneous msg processing
ds['wdir_i'] = ds['wdir_i'].where(ds['wspd_i'] != 0) # Get directional wind speed
ds['wspd_x_i'], ds['wspd_y_i'] = calcDirWindSpeeds(ds['wspd_i'], ds['wdir_i'])

return ds


def calcDirWindSpeeds(wspd, wdir, deg2rad=np.pi/180):
'''Calculate directional wind speed from wind speed and direction

Parameters
----------
wspd : xr.Dataarray
Wind speed data array
wdir : xr.Dataarray
Wind direction data array
deg2rad : float
Degree to radians coefficient. The default is np.pi/180

Returns
-------
wspd_x : xr.Dataarray
Wind speed in X direction
wspd_y : xr.Datarray
Wind speed in Y direction
'''
wspd_x = wspd * np.sin(wdir * deg2rad)
wspd_y = wspd * np.cos(wdir * deg2rad)
return wspd_x, wspd_y


def calcHeatFlux(T_0, T_h, Tsurf_h, rho_atm, WS_h, z_WS, z_T, nu, q_h, p_h,
kappa=0.4, WS_lim=1., z_0=0.001, g=9.82, es_0=6.1071, eps=0.622,
gamma=16., L_sub=2.83e6, L_dif_max=0.01, c_pd=1005., aa=0.7,
Expand Down
109 changes: 77 additions & 32 deletions src/pypromice/process/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,44 +91,79 @@ def getL1(self):
logger.info('Level 1 processing...')
self.L0 = [addBasicMeta(item, self.vars) for item in self.L0]
self.L1 = [toL1(item, self.vars) for item in self.L0]
self.L1A = reduce(xr.Dataset.combine_first, self.L1)

BaptisteVandecrux marked this conversation as resolved.
Show resolved Hide resolved
if self.merge_flag:
self.L1A = self.hard_merge(self.L1)
else:
self.L1A = reduce(xr.Dataset.combine_first, self.L1)

def getL2(self):
'''Perform L1 to L2 data processing'''
logger.info('Level 2 processing...')
self.L2 = toL2(self.L1A, vars_df=self.vars)
self.L2 = self.resample(self.L2)
PennyHow marked this conversation as resolved.
Show resolved Hide resolved
self.L2 = reformat_time(self.L2)

# Switch gps_lon to negative (degrees_east)
# Do this here, and NOT in addMeta, otherwise we switch back to positive
# when calling getMeta in joinL2! PJW
if self.L2.attrs['station_id'] not in ['UWN', 'Roof_GEUS', 'Roof_PROMICE']:
self.L2['gps_lon'] = self.L2['gps_lon'] * -1

# Add variable attributes and metadata
self.L2 = self.addAttributes(self.L2)

# Round all values to specified decimals places
self.L2 = roundValues(self.L2, self.vars)

def getL3(self):
'''Perform L2 to L3 data processing, including resampling and metadata
and attribute population'''
logger.info('Level 3 processing...')
self.L3 = toL3(self.L2)

# Resample L3 product
def resample(self, dataset):
'''Resample dataset to specific temporal resolution (based on input
data type)'''
f = [l.attrs['format'] for l in self.L0]
if 'raw' in f or 'STM' in f:
logger.info('Resampling to 10 minute')
self.L3 = resampleL3(self.L3, '10min')
resampled = resampleL2(dataset, '10min')
else:
self.L3 = resampleL3(self.L3, '60min')
resampled = resampleL2(dataset, '60min')
logger.info('Resampling to hour')

# Re-format time
t = self.L3['time'].values
self.L3['time'] = list(t)

# Switch gps_lon to negative (degrees_east)
# Do this here, and NOT in addMeta, otherwise we switch back to positive
# when calling getMeta in joinL3! PJW
if self.L3.attrs['station_id'] not in ['UWN', 'Roof_GEUS', 'Roof_PROMICE']:
self.L3['gps_lon'] = self.L3['gps_lon'] * -1

# Add variable attributes and metadata
self.L3 = self.addAttributes(self.L3)

# Round all values to specified decimals places
self.L3 = roundValues(self.L3, self.vars)
BaptisteVandecrux marked this conversation as resolved.
Show resolved Hide resolved

return resampled

def merge_flag(self):
'''Determine if hard merging is needed, based on whether a hard
merge_type flag is defined in any of the configs'''
f = [l.attrs['merge_type'] for l in self.L0]
if 'hard' in f:
return True
else:
return False

def hard_merge(self, dataset_list):
PennyHow marked this conversation as resolved.
Show resolved Hide resolved
'''Determine positions where hard merging should occur, combine
data and append to list of combined data chunks, then hard merge all
combined data chunks. This should be called in instances where there
needs to be a clear break between input datasets, such as when a station
is moved (and we do not want the GPS position jumping)'''
# Define positions where hard merging should occur
m=[]
f = [l.attrs['merge_type'] for l in self.L0]
[m.append(i) for i, item in enumerate(f) if item=='hard']

# Perform combine between hard merge breaks and append to list of combined data
combined=[]
for i in range(len(m[:-1])):
combined.append(reduce(xr.Dataset.combine_first, dataset_list[m[i]:m[i+1]]))
combined.append(reduce(xr.Dataset.combine_first, dataset_list[m[-1]:]))

# Hard merge all combined datasets together
return reduce(xr.Dataset.update, combined)


def addAttributes(self, L3):
'''Add variable and attribute metadata

Expand Down Expand Up @@ -365,6 +400,12 @@ def getL0(infile, nodata, cols, skiprows, file_version,
ds = xr.Dataset.from_dataframe(df)
return ds

def reformat_time(dataset):
'''Re-format time'''
t = dataset['time'].values
dataset['time'] = list(t)
return dataset

def addBasicMeta(ds, vars_df):
''' Use a variable lookup table DataFrame to add the basic metadata
to the xarray dataset. This is later amended to finalise L3
Expand Down Expand Up @@ -712,8 +753,8 @@ def getMeta(m_file=None, delimiter=','):
pass
return meta

def resampleL3(ds_h, t):
'''Resample L3 AWS data, e.g. hourly to daily average. This uses pandas
def resampleL2(ds_h, t):
PennyHow marked this conversation as resolved.
Show resolved Hide resolved
'''Resample L2 AWS data, e.g. hourly to daily average. This uses pandas
DataFrame resampling at the moment as a work-around to the xarray Dataset
resampling. As stated, xarray resampling is a lengthy process that takes
~2-3 minutes per operation: ds_d = ds_h.resample({'time':"1D"}).mean()
Expand Down Expand Up @@ -881,7 +922,7 @@ def testAddAll(self):
self.assertTrue(d.attrs['station_id']=='TEST')
self.assertIsInstance(d.attrs['references'], str)

def testL0toL3(self):
def testL0toL2(self):
'''Test L0 to L3 processing'''
try:
import pypromice
Expand All @@ -890,19 +931,23 @@ def testL0toL3(self):
except:
pAWS = AWS('../test/test_config1.toml', '../test/')
pAWS.process()
self.assertIsInstance(pAWS.L3, xr.Dataset)
self.assertTrue(pAWS.L3.attrs['station_id']=='TEST1')
self.assertIsInstance(pAWS.L2, xr.Dataset)
self.assertTrue(pAWS.L2.attrs['station_id']=='TEST1')

def testCLIgetl3(self):
'''Test get_l3 CLI'''
exit_status = os.system('get_l3 -h')
def testCLIgetl2(self):
'''Test get_l2 CLI'''
exit_status = os.system('get_l2 -h')
self.assertEqual(exit_status, 0)

def testCLIjoinl3(self):
'''Test join_l3 CLI'''
exit_status = os.system('join_l3 -h')
def testCLIjoinl2(self):
'''Test join_l2 CLI'''
exit_status = os.system('join_l2 -h')
self.assertEqual(exit_status, 0)

def testCLIgetl3(self):
'''Test get_l3 CLI'''
exit_status = os.system('get_l3 -h')
self.assertEqual(exit_status, 0)
#------------------------------------------------------------------------------

if __name__ == "__main__":
Expand Down
Loading
Loading