Skip to content

Commit 50659fb

Browse files
Merge branch 'dev'
2 parents a187f2d + db3858b commit 50659fb

File tree

12 files changed

+145
-97
lines changed

12 files changed

+145
-97
lines changed

Main.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@
1616
import sys
1717
import time
1818
from PyQt5 import QtCore, QtGui, QtWidgets
19-
from PIL import Image, ImageTk
20-
21-
# import requests
22-
# from tqdm import tqdm
2319

2420
from Source import PACKAGE_DIR as CODE_HOME
2521
from Source.MainConfig import MainConfig
@@ -31,7 +27,7 @@
3127
from Source.SeaBASSHeaderWindow import SeaBASSHeaderWindow
3228
from Source.Utilities import Utilities
3329

34-
VERSION = "1.2.9"
30+
VERSION = "1.2.10"
3531

3632

3733
class Window(QtWidgets.QWidget):

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ retrieval. Data outputs are formatted to text files for submission to the [SeaBA
1616
Currently, HyperCP supports <a href='https://www.seabird.com/'>Sea-Bird Scientific</a> HyperSAS packages with and
1717
without SolarTracker or pySAS robotic platforms as well as [TriOS](https://www.trios.de/en/radiometers.html) used in manual configuration. If you are interested in integrating support for your platform, contact us at the email addresses below or in the Discussions tab of the GitHub repository.
1818

19-
## Version 1.2.9
19+
## Version 1.2.10
2020

2121
```
2222
The MIT license

Source/ConfigWindow.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ def initUI(self):
220220
l1bSublabel6 = QtWidgets.QLabel(" Fallback values when no model available:", self)
221221
# l1bSublabel5.setOpenExternalLinks(True)
222222
self.l1bGetAncCheckBox1 = QtWidgets.QCheckBox("GMAO MERRA2", self)
223-
self.l1bGetAncCheckBox2 = QtWidgets.QCheckBox("ECMWF EAC4", self)
223+
self.l1bGetAncCheckBox2 = QtWidgets.QCheckBox("ECMWF CAMS", self)
224224

225225
# If clicked trigger l1bGetAncCheckBoxUpdate
226226
self.l1bGetAncCheckBox1.clicked.connect(lambda: self.l1bGetAncCheckBoxUpdate('NASA_Earth_Data'))
@@ -1730,7 +1730,7 @@ def l1bGetAncCheckBoxUpdate(self,ancillarySource):
17301730
credentials.credentialsWindow('NASA_Earth_Data')
17311731
self.l1bGetAncUntickIfNoCredentials('NASA_Earth_Data')
17321732
elif self.l1bGetAncCheckBox2.isChecked():
1733-
print("ConfigWindow - l1bGetAncCheckBoxUpdate ECMWF EAC4")
1733+
print("ConfigWindow - l1bGetAncCheckBoxUpdate ECMWF CAMS")
17341734
ConfigFile.settings["bL1bGetAnc"] = 2
17351735
credentials.credentialsWindow('ECMWF_ADS')
17361736
self.l1bGetAncUntickIfNoCredentials('ECMWF_ADS')

Source/GetAnc_credentials.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def credentialsSpec(credentialsID):
3939
out['URL_string'] = 'https://urs.earthdata.nasa.gov'
4040
elif credentialsID == 'ECMWF_ADS':
4141
out['credentials_filename'] = '.ecmwf_ads_credentials.json'
42-
out['title'] = 'Login ECMWF ADS credentials - EAC-4 Ancillary'
42+
out['title'] = 'Login ECMWF ADS credentials - CAMS Ancillary'
4343
out['key_string'] = 'url'
4444
out['secret_string'] = 'key'
4545
out['URL_string'] = 'https://ads.atmosphere.copernicus.eu/how-to-api'

Source/GetAnc_ecmwf.py

Lines changed: 53 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,18 @@
1919

2020
class GetAnc_ecmwf:
2121

22-
def timeStamp2yrMnthDayHrMinSec(timestamp):
23-
'''
24-
Definition for timestamp managing
25-
:param timeStamp: a string, the time in UTC with format yyyy-mm-ddThh:MM:ss
26-
'''
27-
date = timestamp.split('T')[0]
28-
time = timestamp.split('T')[1]
29-
30-
year = date.split('-')[0]
31-
month = date.split('-')[1]
32-
day = date.split('-')[2]
33-
34-
hour = time.split(':')[0]
35-
minute = time.split(':')[1]
36-
second = time.split(':')[2]
37-
38-
return year,month,day,hour,minute,second
39-
4022
def ECMWF_latLonTimeTags(lat, lon, timeStamp, latRes, lonRes, timeResHours):
4123
'''
4224
:param timeStamp: a string, the time in UTC with format yyyy-mm-ddThh:MM:ss
4325
:param lat: a float, the query latitude in degrees North
4426
:param lon: a float, the query longitude in degrees East
27+
:param timeResHours: an integer, time resolution of the queried ECMWF dataset
4528
:return:
4629
latEff: a float, the effective latitude - i.e. to the closest resolution degree
4730
lonEff: a float, the effective latitude - i.e. to the closest resolution degree
4831
latLonTag: a string, a tag to indicate the effective lat/lon.
49-
dateTag: a string, a tag to indicate time stamp, to the hour
32+
dateTagEff: a string, a tag to indicate effective date (i.e. date in UTC rounded to closest hour)
33+
timeStampEff: a string, a tag to indicate effective time (i.e. time in UTC rounded to closest hour)
5034
'''
5135

5236
# Lat-Lon
@@ -63,31 +47,19 @@ def ECMWF_latLonTimeTags(lat, lon, timeStamp, latRes, lonRes, timeResHours):
6347
str(int(np.sign(latEff))).replace('-1', 'S').replace('1', 'N'),
6448
np.abs(latEff * (10 ** latSigFigures)))
6549

66-
# Time; choose 00:00 or 12:00 model
67-
year, month, day, hour, minute, second = GetAnc_ecmwf.timeStamp2yrMnthDayHrMinSec(timeStamp)
68-
dTtimeStamp = datetime.datetime(int(year), int(month), int(day), int(hour), int(minute), int(second),tzinfo=datetime.timezone.utc)
69-
dTtimeVec = [datetime.datetime(int(year), int(month), int(day), int(0), int(0), int(0),tzinfo=datetime.timezone.utc),\
70-
datetime.datetime(int(year), int(month), int(day), int(12), int(0), int(0),tzinfo=datetime.timezone.utc)]
71-
timeEff = min(dTtimeVec, key=lambda x: abs(x - dTtimeStamp))
72-
73-
# timeSec = datetime.datetime(int(year), int(month), int(day), int(hour), int(minute), int(second),tzinfo=datetime.timezone.utc).timestamp()
74-
# timeEffSec = np.round(timeSec / (3600 * timeResHours)) * (3600 * timeResHours) - 7200
75-
# timeStampEff = str(pd.Timestamp(datetime.datetime.fromtimestamp(timeEffSec))).replace(' ','T')
76-
timeStampEff = str(pd.Timestamp(timeEff)).replace(' ','T')
77-
78-
dateTag = timeStampEff.replace(':','').replace('-','').replace('T','')
79-
# print(timeStamp)
80-
# print(year, month, day, hour, minute, second)
81-
# print(timeSec)
82-
# print(timeEffSec)
83-
# print(timeStampEff)
50+
# Convert to a datetime object
51+
epoch_time = datetime.datetime.strptime(':'.join(timeStamp.split(':')[:-2]), '%Y-%m-%dT%H:%M:%S').timestamp()
52+
timeResHoursSecs = 3600 * timeResHours
53+
rounded_epoch_time = round(epoch_time / timeResHoursSecs) * timeResHoursSecs
54+
rounded_timestamp = datetime.datetime.fromtimestamp(rounded_epoch_time).strftime('%Y-%m-%dT%H:%M:%S')
55+
dateTagEff, timeStampEff = rounded_timestamp.split('T')
8456

85-
return latEff,lonEff,timeStampEff,latLonTag,dateTag
57+
return latEff,lonEff,latLonTag,dateTagEff,timeStampEff
8658

8759

88-
def EAC4_download_ensembles(lat, lon, timeStamp, EAC4_variables, pathOut):
60+
def CAMS_download_ensembles(lat, lon, dateTag, timeTag, CAMS_variables, pathOut):
8961
'''
90-
Performs CDSAPI command to download the required data from EAC4 (dataset "cams-global-atmospheric-composition-forecasts") in netCDF
62+
Performs CDSAPI command to download the required data from CAMS (dataset "cams-global-atmospheric-composition-forecasts") in netCDF
9163
format. It will retrieve the variables in a single space-time point corresponding to
9264
9365
For more information, please check: https://ads.atmosphere.copernicus.eu/cdsapp#!/dataset/cams-global-atmospheric-composition-forecasts?tab=overview
@@ -96,38 +68,42 @@ def EAC4_download_ensembles(lat, lon, timeStamp, EAC4_variables, pathOut):
9668
:param lat: a float, the query latitude in degrees North
9769
:param lon: a float, the query longitude in degrees East
9870
99-
:param EAC4_variables: a list, with the variables of interest
71+
:param CAMS_variables: a list, with the variables of interest
10072
:param pathOut: full path to output, a netCDF file with the requested variables.
10173
:return:
10274
'''
10375

10476
if os.path.exists(pathOut):
10577
pass
10678
else:
107-
print(f'Nearest model found at {timeStamp}')
10879
url,key = read_user_credentials('ECMWF_ADS')
10980

110-
year, month, day, hour, _, _ = GetAnc_ecmwf.timeStamp2yrMnthDayHrMinSec(timeStamp)
81+
year = dateTag.split('-')[0]
82+
hour = timeTag.split(':')[0]
11183

112-
if int(year) < 2003:
113-
print('EAC4 dataset not available before 2003, skipping')
84+
hourForecast = '%02d' % (int(hour) // 12)
85+
leadtime = str(int(hour) % 12)
86+
87+
if int(year) < 2015:
88+
print('CAMS dataset not available before 2015, skipping')
11489
else:
11590
try:
11691
c = cdsapi.Client(timeout=5, url=url, key=key)
11792
c.retrieve(
11893
'cams-global-atmospheric-composition-forecasts',
11994
{
120-
'format': 'netcdf',
12195
'type' : 'forecast',
122-
'variable': list(EAC4_variables.keys()),
123-
'date': '%s-%s-%s/%s-%s-%s' % (year, month, day, year, month, day),
124-
'time': '%s:00' % (hour,),
96+
'variable': list(CAMS_variables.keys()),
97+
'date': '%s/%s' % (dateTag, dateTag),
12598
'area': [lat, lon, lat, lon],
126-
'leadtime_hour': '0',
99+
'time': '%s:00' % hourForecast,
100+
'leadtime_hour': leadtime,
101+
'format': 'netcdf',
102+
'download_format': 'unarchived',
127103
},
128104
pathOut)
129105
except:
130-
print('EAC4 atmospheric data could not be retrieved. Check inputs.')
106+
print('CAMS atmospheric data could not be retrieved. Check inputs.')
131107
exit()
132108

133109

@@ -152,49 +128,49 @@ def get_ancillary_main(lat, lon, timeStamp, pathAncillary):
152128

153129
ancillary = {}
154130

155-
#################### EAC4 ####################
156-
pathEAC4 = pathAncillary
157-
if not os.path.exists(pathEAC4):
158-
os.mkdir(pathEAC4)
131+
#################### CAMS ####################
132+
pathCAMS = pathAncillary
133+
if not os.path.exists(pathCAMS):
134+
os.mkdir(pathCAMS)
159135

160-
EAC4_variables = {
136+
CAMS_variables = {
161137
'10m_u_component_of_wind' :'u10',
162138
'10m_v_component_of_wind' :'v10',
163139
'total_aerosol_optical_depth_550nm' :'aod550'
164140
}
165141

166-
EAC4nc = {}
142+
CAMSnc = {}
167143

168144

169145
# Check https://ads.atmosphere.copernicus.eu/cdsapp#!/dataset/cams-global-atmospheric-composition-forecasts?tab=overview
170146
latRes = 0.4
171147
lonRes = 0.4
172-
timeResHours = 12
148+
timeResHours = 1
173149

174-
latEff, lonEff, timeStampEff, latLonTag, dateTag = GetAnc_ecmwf.ECMWF_latLonTimeTags(lat, lon, timeStamp, latRes, lonRes, timeResHours)
150+
latEff, lonEff, latLonTag, dateTagEff, timeStampEff = GetAnc_ecmwf.ECMWF_latLonTimeTags(lat, lon, timeStamp, latRes, lonRes, timeResHours)
175151

176-
pathOut = os.path.join(pathEAC4, 'EAC4_%s_%s.nc' % (latLonTag, dateTag))
152+
pathOut = os.path.join(pathCAMS, 'CAMS_%s_%s_%s.nc' % (latLonTag, dateTagEff.replace('-',''), timeStampEff.replace(':','')))
177153

178-
GetAnc_ecmwf.EAC4_download_ensembles(latEff, lonEff, timeStampEff, EAC4_variables, pathOut)
154+
GetAnc_ecmwf.CAMS_download_ensembles(latEff, lonEff, dateTagEff, timeStampEff, CAMS_variables, pathOut)
179155

180156
try:
181-
EAC4nc['reanalysis'] = xr.open_dataset(pathOut,engine='netcdf4')
182-
EAC4_flag = True
157+
CAMSnc['reanalysis'] = xr.open_dataset(pathOut,engine='netcdf4')
158+
CAMS_flag = True
183159
except:
184-
EAC4_flag = False
185-
print('EAC4 data missing. Skipping...')
160+
CAMS_flag = False
161+
print('CAMS data missing. Skipping...')
186162

187-
if EAC4_flag:
163+
if CAMS_flag:
188164
try:
189-
for EAC4_variable, shortName in EAC4_variables.items():
190-
var = EAC4nc['reanalysis'][shortName]
191-
ancillary[EAC4_variable] = {}
192-
ancillary[EAC4_variable]['value'] = var.values[0][0][0]
193-
ancillary[EAC4_variable]['units'] = var.units
194-
ancillary[EAC4_variable]['long_name'] = var.long_name
195-
ancillary[EAC4_variable]['source'] = 'EAC4 (ECMWF). https://ads.atmosphere.copernicus.eu/cdsapp#!/dataset/cams-global-atmospheric-composition-forecasts?tab=overview'
165+
for CAMS_variable, shortName in CAMS_variables.items():
166+
var = CAMSnc['reanalysis'][shortName]
167+
ancillary[CAMS_variable] = {}
168+
ancillary[CAMS_variable]['value'] = var.values[0][0][0][0]
169+
ancillary[CAMS_variable]['units'] = var.units
170+
ancillary[CAMS_variable]['long_name'] = var.long_name
171+
ancillary[CAMS_variable]['source'] = 'CAMS (ECMWF). https://ads.atmosphere.copernicus.eu/cdsapp#!/dataset/cams-global-atmospheric-composition-forecasts?tab=overview'
196172
except:
197-
print('Problem processing EAC4 data. Skipping...')
173+
print('Problem processing CAMS data. Skipping...')
198174

199175
return ancillary
200176

@@ -223,11 +199,11 @@ def getAnc_ecmwf(inputGroup):
223199
ancillary = GetAnc_ecmwf.get_ancillary_main(lat[index], lon[index], lat_timeStamp, ancPath)
224200

225201
# position retrieval index has been confirmed manually in SeaDAS
226-
uWind = ancillary['10m_u_component_of_wind']['value'][0]
227-
vWind = ancillary['10m_v_component_of_wind']['value'][0]
202+
uWind = ancillary['10m_u_component_of_wind']['value']
203+
vWind = ancillary['10m_v_component_of_wind']['value']
228204
modWind.append(np.sqrt(uWind*uWind + vWind*vWind)) # direction not needed
229205
#ancAOD = aerGroup.getDataset("TOTEXTTAU")
230-
modAOD.append(ancillary['total_aerosol_optical_depth_550nm']['value'][0])
206+
modAOD.append(ancillary['total_aerosol_optical_depth_550nm']['value'])
231207

232208
modData = HDFRoot()
233209
modGroup = modData.addGroup('ECMWF')

Source/ProcessL1aTriOS.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ def formatting_instrument(name, cal_path, input_file, root, configPath):
360360
c3 = float(gp.attributes['c3s'])
361361
wl = []
362362
for i in range(1,256):
363+
# for i in range(1,data.shape[1]+1):
363364
wl.append(str(round((c0 + c1*(i+1) + c2*(i+1)**2 + c3*(i+1)**3), 2)))
364365

365366
#Create Data (LI,LT,ES) dataset

Source/ProcessL1b.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,10 @@ def read_unc_coefficient_frm(root, inpath, classbased_dir):
162162
Utilities.RenameUncertainties_FullChar(root)
163163

164164
# interpolate LAMP and PANEL to full wavelength range
165-
Utilities.interpUncertainties_FullChar(root)
165+
success = Utilities.interpUncertainties_FullChar(root)
166+
if not success:
167+
print('interpUncertainties_FullChar failed.')
168+
return None
166169

167170
# generate temperature coefficient
168171
Utilities.UncTempCorrection(root)

Source/ProcessL1bqc.py

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,7 @@ def QC(node):
662662
print(msg)
663663
Utilities.writeLogFile(msg)
664664
return False
665-
665+
666666
if py6sGroup is not None:
667667
Utilities.filterData(py6sGroup,badTimes)
668668

@@ -683,17 +683,80 @@ def QC(node):
683683
elif ConfigFile.settings['SensorType'].lower() == 'trios':
684684
Utilities.filterData(esGroup,badTimes,'L1AQC')
685685
Utilities.filterData(liGroup,badTimes,'L1AQC')
686-
Utilities.filterData(ltGroup,badTimes,'L1AQC')
686+
Utilities.filterData(ltGroup,badTimes,'L1AQC')
687687

688688
# Next apply the Meteorological FLAGGING prior to slicing
689-
esData = referenceGroup.getDataset("ES")
689+
# esData = referenceGroup.getDataset("ES")
690690
if enableMetQualityCheck:
691691
# msg = "Applying meteorological filtering to eliminate spectra."
692692
msg = "Applying meteorological flags. Met flags are NOT used to eliminate spectra."
693693
print(msg)
694694
Utilities.writeLogFile(msg)
695695
badTimes = ProcessL1bqc.metQualityCheck(referenceGroup, sasGroup, py6sGroup, ancGroup)
696696

697+
# NOTE: This is not finalized and needs a ConfigFile.setting #########################################
698+
ConfigFile.settings['bL2filterMetFlags'] = 0
699+
if ConfigFile.settings['bL2filterMetFlags'] == 1:
700+
# Placeholder to filter out based on Met Flags
701+
metFlags = ancGroup.datasets['MET_FLAGS']
702+
AncDatetime = metFlags.columns['Datetime']
703+
Flag3 = metFlags.columns['Flag3']
704+
705+
badTimes = []
706+
for indx, dateTime in enumerate(AncDatetime):
707+
if Flag3[indx]:
708+
badTimes.append(dateTime)
709+
710+
badTimes = np.unique(badTimes)
711+
# Duplicate each element to a list of two elements in a list
712+
badTimes = np.rot90(np.matlib.repmat(badTimes,2,1), 3)
713+
# msg = f'{len(np.unique(badTimes))/len(AncDatetime)*100:.1f}% of spectra flagged'
714+
715+
if badTimes is not None:
716+
msg = "Removing spectra from Met flags. ######################### Hard-coded override for Flag3"
717+
print(msg)
718+
Utilities.writeLogFile(msg)
719+
check = Utilities.filterData(referenceGroup, badTimes)
720+
if check > 0.99:
721+
msg = "Too few spectra remaining. Abort."
722+
print(msg)
723+
Utilities.writeLogFile(msg)
724+
return False
725+
check = Utilities.filterData(sasGroup, badTimes)
726+
if check > 0.99:
727+
msg = "Too few spectra remaining. Abort."
728+
print(msg)
729+
Utilities.writeLogFile(msg)
730+
return False
731+
check = Utilities.filterData(ancGroup, badTimes)
732+
if check > 0.99:
733+
msg = "Too few spectra remaining. Abort."
734+
print(msg)
735+
Utilities.writeLogFile(msg)
736+
return False
737+
738+
if py6sGroup is not None:
739+
Utilities.filterData(py6sGroup,badTimes)
740+
741+
# Filter L1AQC data for L1BQC criteria
742+
if ConfigFile.settings['SensorType'].lower() == 'seabird':
743+
check = []
744+
check.append(Utilities.filterData(esDarkGroup,badTimes,'L1AQC'))
745+
check.append(Utilities.filterData(esLightGroup,badTimes,'L1AQC'))
746+
check.append(Utilities.filterData(liDarkGroup,badTimes,'L1AQC'))
747+
check.append(Utilities.filterData(liLightGroup,badTimes,'L1AQC'))
748+
check.append(Utilities.filterData(ltDarkGroup,badTimes,'L1AQC'))
749+
check.append(Utilities.filterData(ltLightGroup,badTimes,'L1AQC'))
750+
if any(np.array(check) > 0.99):
751+
msg = "Too few spectra remaining. Abort."
752+
print(msg)
753+
Utilities.writeLogFile(msg)
754+
return False
755+
elif ConfigFile.settings['SensorType'].lower() == 'trios':
756+
Utilities.filterData(esGroup,badTimes,'L1AQC')
757+
Utilities.filterData(liGroup,badTimes,'L1AQC')
758+
Utilities.filterData(ltGroup,badTimes,'L1AQC')
759+
697760
return True
698761

699762
@staticmethod

Source/ProcessL2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2357,7 +2357,7 @@ def processL2(root,station=None):
23572357
newGrp = node.addGroup(grp.id)
23582358
newGrp.copy(grp)
23592359
for ds in newGrp.datasets:
2360-
newGrp.datasets[ds].datasetToColumns()
2360+
newGrp.datasets[ds].datasetToColumns()
23612361

23622362
# Process stations, ensembles to reflectances, OC prods, etc.
23632363
if not ProcessL2.stationsEnsemblesReflectance(node, root,station):

0 commit comments

Comments
 (0)