Skip to content

Commit 50efe33

Browse files
authored
Merge pull request #173 from DanBrandt/fism
Major updates to fism.py: Errors regarding rebinning and case sensiti…
2 parents bb8cfb6 + 8044ddd commit 50efe33

File tree

1 file changed

+150
-107
lines changed

1 file changed

+150
-107
lines changed

srcPython/fism.py

Lines changed: 150 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# Authors of this code:
44
# Daniel A. Brandt, Ph.D., Michigan Tech Research Institute, daabrand@mtu.edu
5+
# Aaron L. Bukowski, Ph.D., University of Michigan, abukowski@umich.edu
56
# Aaron J. Ridley, Ph.D., University of Michigan, ridley@umich.edu
67

78
# This file contains a suite of tools that do the following:
@@ -14,83 +15,137 @@
1415
import numpy as np
1516
from datetime import datetime, timedelta
1617
import pathlib
17-
import os
18-
from urllib.request import urlretrieve
18+
import os, sys
19+
import pooch
1920
from netCDF4 import Dataset
2021
import scipy.integrate as integ
2122

2223
# Directory management:
2324
here = pathlib.Path(__file__).parent.resolve()
24-
saveLoc = here.joinpath('.')
25+
euvDir = here.parent.joinpath('share/run/UA/inputs')
2526

2627
# Physical constants:
2728
h = 6.62607015e-34 # Planck's constant in SI units of J s
2829
c = 299792458 # Speed of light in m s^-1
2930

3031
# Helper Functions:
31-
def getFism(dateStart, dateEnd, saveLoc, stanBands=False):
32+
def getFism2(dateStart, dateEnd, source, downloadDir=None):
3233
"""
33-
Download FISM2 daily data, either in the Standard Bands or not. Default is to download the data NOT in the
34-
Standard Bands. Note that this function downloads the data to a pre-determined location. The download is also
35-
'smart'. If the data already exists (over the exact same time period). It is NOT overwitten. If the requested data
36-
covers times OUTSIDE the time period of existing data, the NEW data is simply appended or prepended to a file
37-
containing the existing data.
38-
Args:
39-
dateStart: str
40-
The start date in 'YYYY-MM-DD' format.
41-
dateEnd: str
42-
The end date in 'YYYY-MM-DD' format.
43-
saveLoc: str/path
44-
The path to save data to
45-
stanBands: bool
46-
If True, downloads FISM2 data in the STAN BANDS. Default is False.
47-
Returns:
48-
fismFile: str
49-
The location of the downloaded FISM data.
34+
Given a starting date and an ending date, automatically download irradiance data from LISIRD for a specific source,
35+
including FISM2 daily or FISM2 in the Standard Bands.
36+
:param dateStart: str
37+
The starting date for the data in YYYY-MM-DD format.
38+
:param dateEnd: str
39+
The ending date for the data in YYYY-MM-DD format.
40+
:param source: str
41+
The type of data to be obtained. Valid inputs are:
42+
- FISM2 (for daily averages of FISM2 data)
43+
- FISM2S (for daily averages of FISM2 standard bands, according to Solomon and Qian 2005)
44+
:return times: ndarray
45+
Datetime values for each spectrum.
46+
:return wavelengths: ndarray
47+
Wavelength bins (bin boundaries) for the spectral data.
48+
:return irradiance: ndarray
49+
A 2D array where each row is a spectrum at a particular time, and the columns are wavelength bands.
5050
"""
51+
# Converting the input time strings to datetimes:
5152
dateStartDatetime = datetime.strptime(dateStart, "%Y-%m-%d")
5253
dateEndDatetime = datetime.strptime(dateEnd, "%Y-%m-%d")
5354

54-
# Helper function to manage the obtaining of data, given a URL:
55-
def urlObtain(url, fname):
56-
if os.path.isfile(fname) == True:
57-
print('File already exists (loading in data) '+str(fname))
58-
else:
59-
urlretrieve(url, fname)
60-
61-
URL = 'https://lasp.colorado.edu/eve/data_access/eve_data/fism/daily_bands/daily_bands.nc'
62-
fname = 'FISM2_daily.nc'
63-
urlObtain(URL, fname)
64-
65-
if stanBands:
66-
datetimes, wavelengths, irradiance = readFism(fname, stanBands=True)
55+
# Check if the user has asked for a source that can be obtained:
56+
validSources = ['FISM2', 'FISM2S']
57+
if source not in validSources:
58+
raise ValueError("Variable 'source' must be either 'FISM2' or 'FISM2S.")
59+
60+
# If the download directory is not specified, set it to the top directory that the package is in:
61+
if downloadDir is None:
62+
downloadDir = os.getcwd()
63+
64+
# Download the most recent file for the corresponding source and read it in:
65+
if source == 'FISM2':
66+
url = 'https://lasp.colorado.edu/eve/data_access/eve_data/fism/daily_hr_data/daily_data.nc'
67+
fname = 'FISM2_daily_data.nc'
68+
urlObtain(url, loc=downloadDir, fname=fname) # hash='dbee404e1c75689b47691b8a4a733236bb66abbdc0f01b8cbd8236f69fe9d469'
69+
datetimes, wavelengths, irradiance, uncertainties = obtainFism2(downloadDir + '/' + fname)
6770
else:
68-
datetimes, wavelengths, irradiance = readFism(fname, stanBands=False)
71+
url = 'https://lasp.colorado.edu/eve/data_access/eve_data/fism/daily_bands/daily_bands.nc'
72+
fname = 'FISM2_daily_bands.nc'
73+
urlObtain(url, loc=downloadDir, fname=fname) # hash='27e3183f8ad6b289de191a63d3feada64c9d3f6b2973315ceda4a42c41638465'
74+
datetimes, wavelengths, irradiance, uncertainties = obtainFism2(downloadDir + '/' + fname, bands=True)
75+
76+
# Subset the data according to user demands:
77+
validInds = np.where((datetimes >= dateStartDatetime) & (datetimes <= dateEndDatetime))[0]
78+
times = datetimes[validInds]
79+
if source == 'FISM2S':
80+
irradiance = irradiance[-1, validInds, :]
81+
else:
82+
irradiance = irradiance[validInds, :]
6983

70-
# Subset the data in time, and save the subset data to a relative path:
71-
subset_inds = np.where((datetimes >= dateStartDatetime) & (datetimes <= dateEndDatetime))[0]
72-
subset_times = datetimes[subset_inds]
73-
subset_irradiance = irradiance[subset_inds, :]
84+
# Return the resulting data:
85+
return times, wavelengths, irradiance
7486

75-
return subset_times, wavelengths, subset_irradiance
87+
def obtainFism2(myFism2File, bands=False):
88+
"""
89+
Load in spectrum data from a FISM2 file.
90+
:param myFism2File: str
91+
The location of the NETCDF4 file.
92+
:param bands: bool
93+
If True, loads in the data segmented into the Solomon and Qian 2005 standard bands.
94+
:return datetimes: ndarray
95+
An array of datetimes for each TIMED/SEE spectra.
96+
:return wavelengths: ndarray
97+
A one-dimensional array of wavelengths at which there are irradiance values.
98+
:return irradiances: ndarray
99+
A two-dimensional array of irradiance values at each time.
100+
:return uncertainties: ndarray
101+
A two-dimensional array of irradiance uncertainty values at each time.
102+
"""
103+
fism2Data = Dataset(myFism2File)
104+
wavelengths = np.asarray(fism2Data.variables["wavelength"])
105+
if bands == True: # STANDARD BANDS
106+
flux = np.asarray(fism2Data.variables["ssi"]) # photons/cm2/second
107+
# bandwidths = np.asarray(fism2Data.variables['band_width'])
108+
pFlux = flux * 1.0e4 # photons/m2/second
109+
# Convert fluxes to irradiances:
110+
irr = np.zeros_like(flux)
111+
for i in range(flux.shape[1]):
112+
irr[:, i] = spectralIrradiance(pFlux[:, i], wavelengths[i] * 10.0) # W/m^2
113+
irradiance = np.array([flux, irr])
114+
uncertainties = np.full_like(irradiance, fill_value=np.nan) # TODO: Replace with an estimation of uncertainty
115+
else: # NATIVE DATA
116+
irradiance = np.asarray(fism2Data.variables["irradiance"]) # W/m^2/nm
117+
uncertainties = np.asarray(fism2Data.variables["uncertainty"])
118+
dates = fism2Data.variables["date"]
119+
datetimes = []
120+
for i in range(len(dates)):
121+
year = dates[i][:4]
122+
day = dates[i][4:]
123+
currentDatetime = (
124+
datetime(int(year), 1, 1)
125+
+ timedelta(int(day) - 1)
126+
+ timedelta(hours=12)
127+
)
128+
datetimes.append(currentDatetime)
129+
datetimes = np.asarray(datetimes)
130+
return datetimes, wavelengths, irradiance, uncertainties
76131

77-
def rebin(fism_out, saveLoc, binning_scheme='EUVAC', zero=True):
132+
def rebin(fism_out, saveLoc=os.getcwd(), binning_scheme='EUVAC', zero=True):
78133
"""
79134
Takes the output of getFism and rebins the data into whatever format the user desires.
80135
Args:
81136
fism_out: arraylike
82-
The output of getFism. Contains 4 elements: (1) datetime values for the FISM2 spectra, (2) the wavelengths
137+
The output of getFism2. Contains 4 elements: (1) datetime values for the FISM2 spectra, (2) the wavelengths
83138
of the spectrum, (3) the actual FISM2 irradiance spectra.
84139
saveLoc: path
85-
Path to save data files to.
140+
Path to save data files to. Defaults to the current working directory.
86141
binning_scheme: str
87142
Determines the binning scheme to be used. Valid arguments include the following:
88-
'EUVAC': Uses the 37 wavelength band scheme described in Richards, et al. 1994; doi.org/10.1029/94JA00518
89-
'NEUVAC': Uses the 59 wavelength band scheme described in Brandt and Ridley, 2024; doi.org/10.1029/2024SW004043
143+
'EUVAC' or 'Euvac' or 'euvac': Uses the 37 wavelength band scheme described in Richards, et al. 1994; doi.org/10.1029/94JA00518
144+
'NEUVAC' or 'Neuvac' or 'neuvac': Uses the 59 wavelength band scheme described in Brandt and Ridley, 2024; doi.org/10.1029/2024SW004043
90145
'HFG': Uses the 23 wavelength band scheme described in Solomon and Qian, 2005; https://doi.org/10.1029/2005JA011160
91-
'SOLOMON': Same situation as for argument 'HFG'.
92-
NOTE: If 'HFG' or 'SOLOMON' is chosen, the values of fism_out must correspond to getFism being run with the
93-
argument stanBands = True. If this IS NOT the case, an error will be thrown.
146+
'SOLOMON' or 'Solomon' or 'solomon': Same situation as for argument 'HFG'.
147+
NOTE: If 'HFG' or 'SOLOMON' is chosen, the values of fism_out must correspond to getFism2 being run with the
148+
argument source='FISM2'. If this IS NOT the case, an error will be thrown.
94149
zero: bool
95150
Controls whether singular (bright) wavelength lines are set to a value of zero after they are extracted.
96151
Default is True.
@@ -107,15 +162,15 @@ def rebin(fism_out, saveLoc, binning_scheme='EUVAC', zero=True):
107162
# nativeResolution = np.concatenate((np.diff(wavelengths), np.array([np.diff(wavelengths)[-1]])), axis=0)
108163
nativeWavelengths = wavelengths.copy()
109164

110-
if binning_scheme != 'HFG' and binning_scheme != 'SOLOMON':
111-
if binning_scheme == 'EUVAC':
165+
if binning_scheme != 'HFG' and binning_scheme != 'SOLOMON' and binning_scheme != 'Solomon' and binning_scheme != 'solomon':
166+
if binning_scheme == 'EUVAC' or binning_scheme == 'Euvac' or binning_scheme == 'euvac':
112167
# Grab the euv_37.csv file:
113-
fileStr = str(saveLoc.joinpath('euv.csv'))
168+
fileStr = str(euvDir.joinpath('euv.csv'))
114169
bin_bounds = read_euv_csv_file(fileStr)
115170
tag = '_37'
116-
elif binning_scheme == 'NEUVAC':
171+
elif binning_scheme == 'NEUVAC' or binning_scheme == 'Neuvac' or binning_scheme == 'neuvac':
117172
# Grab the euv_59.csv file:
118-
fileStr = str(saveLoc.joinpath('euv_59.csv'))
173+
fileStr = str(euvDir.joinpath('euv_59.csv'))
119174
bin_bounds = read_euv_csv_file(fileStr)
120175
tag = '_59'
121176
else:
@@ -172,7 +227,7 @@ def rebin(fism_out, saveLoc, binning_scheme='EUVAC', zero=True):
172227
except:
173228
fism2_data[:, iWave] = integ.trapezoid(myData[iStart:iEnd], wavelengths[iStart:iEnd])
174229

175-
elif binning_scheme == 'HFG' or binning_scheme == 'SOLOMON':
230+
elif binning_scheme == 'HFG' or binning_scheme == 'SOLOMON' or binning_scheme == 'Solomon' or binning_scheme == 'solomon':
176231
# Determine whether the supplied data already conforms to the Solomon and Qian binning scheme.
177232
tag = '_solomon'
178233
if fism_out[2].shape[1] != 23:
@@ -186,7 +241,7 @@ def rebin(fism_out, saveLoc, binning_scheme='EUVAC', zero=True):
186241
raise ValueError("Invalid value for argument 'binning_scheme'. Must be 'EUVAC', 'NEUVAC', 'HFG', or 'SOLOMON'.")
187242

188243
# Save the rebinned data to a relative path (outside the package directory) in the form of a .txt file:
189-
fism2_file = saveLoc.joinpath('fism2_file'+tag+'.txt')
244+
fism2_file = pathlib.Path(os.getcwd()).joinpath('fism2_file'+tag+'.txt')
190245
saveFism(fism2_data, datetimes, fism2_file)
191246

192247
return fism2_file, fism2_data
@@ -246,46 +301,6 @@ def safe_open_w(path):
246301
os.system('readlink -f '+str(filename))
247302
return
248303

249-
def readFism(fism_file, stanBands=False):
250-
"""
251-
Load in spectrum data from a FISM2 file.
252-
Args:
253-
fism_file: str
254-
The location of a FISM2 NETCDF4 file.
255-
stanBands: bool
256-
If True, expects the data to be read in to be in the STAN BANDS. Default is False.
257-
Returns:
258-
datetimes: numpy.ndarray
259-
An array of datetimes for reach FISM2 spectrum.
260-
wavelengths: numpy.ndarray
261-
A one-dimensional array of wavelengths at which there are irradiance values.
262-
irradiances: numpy.ndarray
263-
A two-dimensional array of irradiance values in each wavelength bin.
264-
uncertainties: numpy.ndarray
265-
A two-dimensional array of irradiance uncertainty values at each bin. Only returned if stanBands is False.
266-
"""
267-
fism2Data = Dataset(fism_file)
268-
wavelengths = np.asarray(fism2Data.variables['wavelength'])
269-
if stanBands:
270-
flux = np.asarray(fism2Data.variables['ssi']) # photons/cm^2/second
271-
pFlux = flux * 1e4 # photons/m^2/second
272-
# Convert fluxes to irradiances:
273-
irradiance = np.zeros_like(flux)
274-
for i in range(flux.shape[1]):
275-
irradiance[:, i] = spectralIrradiance(pFlux[:, i], wavelengths[i] * 10.)# W/m^2
276-
else:
277-
irradiance = np.asarray(fism2Data.variables['ssi'])
278-
dates = fism2Data.variables['date']
279-
datetimes = []
280-
for j in range(len(dates)):
281-
year = dates[j][:4]
282-
day = dates[j][4:]
283-
currentDatetime = datetime(int(year), 1, 1) + timedelta(int(day) -1 ) + timedelta(hours=12)
284-
datetimes.append(currentDatetime)
285-
datetimes = np.asarray(datetimes)
286-
287-
return datetimes, wavelengths, irradiance
288-
289304
def spectralIrradiance(photonFlux, wavelength):
290305
"""
291306
Convert the photon flux to the corresponding spectral irradiance, given a specific wavelength.
@@ -354,6 +369,28 @@ def read_euv_csv_file(file):
354369
'f74113': f74113}
355370
return wavelengths
356371

372+
def urlObtain(URL, loc=None, fname=None, hash=None):
373+
"""
374+
Helper function that uses Pooch to download files to a location specified by the user.
375+
:param URL: str
376+
The location of a file to be downloaded.
377+
:param loc: str
378+
The place the file will be downloaded.
379+
:param fname: str
380+
The name the file will have once it is downloaded.
381+
:param hash: str
382+
A known hash (checksum) of the file. Will be used to verify the download or check if an existing file needs to
383+
be updated.
384+
:return:
385+
"""
386+
if loc is None:
387+
loc = os.getcwd()
388+
if os.path.isfile(str(loc) + '/' + fname) is False:
389+
fname_loc = pooch.retrieve(url=URL, known_hash=hash, fname=fname, path=loc)
390+
else:
391+
fname_loc = str(loc) + '/' + fname
392+
393+
return fname_loc
357394

358395
def get_args():
359396

@@ -378,16 +415,22 @@ def get_args():
378415
# Download some FISM2 data for the time period stated by the user.
379416

380417
args = get_args()
418+
dateStart = args.start
419+
dateEnd = args.end
420+
binning_scheme = args.binning
421+
422+
if binning_scheme == 'HFG' or binning_scheme == 'SOLOMON' or binning_scheme == 'Solomon' or binning_scheme == 'solomon':
423+
# SOLOMON (STAN BANDS; b23)
424+
fism2_out_23 = getFism2(dateStart, dateEnd, 'FISM2S', downloadDir=None)
425+
fism2_file_23, fism2_data_23 = rebin(fism2_out_23, binning_scheme=binning_scheme)
426+
else:
427+
fism2_out_raw = getFism2(dateStart, dateEnd, 'FISM2', downloadDir=None)
428+
if binning_scheme == 'NEUVAC' or binning_scheme == 'Neuvac' or binning_scheme == 'neuvac':
429+
# NEUVAC BINS (b59)
430+
fism2_file_59, fism2_data_59 = rebin(fism2_out_raw, binning_scheme=binning_scheme, zero=True)
431+
else:
432+
# EUVAC BINS (b37)
433+
fism2_file_37, fism2_data_37 = rebin(fism2_out_raw, binning_scheme=binning_scheme, zero=True)
381434

382-
# fismOut = getFism(dateStart, dateEnd)
383-
# rebinnedFismFile, rebinnedFismData = rebin(fismOut, binning_scheme='EUVAC')
384-
# rebinnedFismFile_N, rebinnedFismData_N = rebin(fismOut, binning_scheme='NEUVAC')
385-
386-
fismOut_S = getFism(dateStart, dateEnd, saveLoc, stanBands=True)
387-
rebinnedFismFile_S, rebinnedFismData_S = rebin(fismOut_S, saveLoc, binning_scheme='SOLOMON')
388-
389-
fismOut_N = getFism(dateStart, dateEnd, saveLoc, stanBands=False)
390-
rebinnedFismFile_N, rebinnedFismData_N = rebin(fismOut_N, saveLoc, binning_scheme='NEUVAC')
391-
392-
fismOut_E = getFism(dateStart, dateEnd, saveLoc, stanBands=True)
393-
rebinnedFismFile_E, rebinnedFismData_E = rebin(fismOut_E, saveLoc, binning_scheme='EUVAC')
435+
# Exit with a zero error code:
436+
sys.exit(0)

0 commit comments

Comments
 (0)