-
-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add PSP Radio Frequency Spectrometner client
* Add new generic client * Add tests * Add to API docs
- Loading branch information
Shane Maloney
committed
Oct 22, 2020
1 parent
722bd97
commit 81047ef
Showing
7 changed files
with
232 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add Parker Solar Probe (PSP) Radio Frequency Receiver (RFS) Fido client `radiospectra.net.sources.psp.RFSClient`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
.. automodapi:: radiospectra.sources | ||
.. automodapi:: radiospectra.net |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Import to register with Fido but keep out of namespace | ||
from radiospectra.net.sources.psp import RFSClient | ||
|
||
__all__ = ['RFSClient'] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import astropy.units as u | ||
from sunpy.net import attrs as a | ||
from sunpy.net.dataretriever.client import GenericClient, QueryResponse | ||
from sunpy.util.scraper import Scraper | ||
|
||
__all__ = ['RFSClient'] | ||
|
||
RECEIVER_FREQUENCIES = { | ||
'rfs_lfr': a.Wavelength(10*u.kHz, 1.7*u.MHz), | ||
'rfs_hfr': a.Wavelength(1.3*u.MHz, 19.2*u.MHz) | ||
} | ||
|
||
|
||
class RFSClient(GenericClient): | ||
""" | ||
Provides access to Parker Solar Probe FIELDS Radio Frequency Spectrometer data | ||
`archive <https://spdf.gsfc.nasa.gov/pub/data/psp/fields/>`__ at `NASA Goddard Space Physics | ||
Data Facility (SPDF) <https://spdf.gsfc.nasa.gov>`__. | ||
Examples | ||
-------- | ||
>>> import radiospectra.net | ||
>>> from sunpy.net import Fido, attrs as a | ||
>>> results = Fido.search(a.Time("2019/10/02", "2019/10/05"), | ||
... a.Instrument('rfs')) #doctest: +REMOTE_DATA | ||
>>> print(results) #doctest: +REMOTE_DATA | ||
Results from 1 Provider: | ||
<BLANKLINE> | ||
8 Results from the RFSClient: | ||
Start Time End Time ... Provider Wavelength [2] | ||
------------------- ------------------- ... -------- ----------------- | ||
2019-10-02 00:00:00 2019-10-02 23:59:59 ... SPDF 10.0 .. 1700.0 | ||
2019-10-03 00:00:00 2019-10-03 23:59:59 ... SPDF 10.0 .. 1700.0 | ||
2019-10-04 00:00:00 2019-10-04 23:59:59 ... SPDF 10.0 .. 1700.0 | ||
2019-10-05 00:00:00 2019-10-05 23:59:59 ... SPDF 10.0 .. 1700.0 | ||
2019-10-02 00:00:00 2019-10-02 23:59:59 ... SPDF 1300.0 .. 19200.0 | ||
2019-10-03 00:00:00 2019-10-03 23:59:59 ... SPDF 1300.0 .. 19200.0 | ||
2019-10-04 00:00:00 2019-10-04 23:59:59 ... SPDF 1300.0 .. 19200.0 | ||
2019-10-05 00:00:00 2019-10-05 23:59:59 ... SPDF 1300.0 .. 19200.0 | ||
<BLANKLINE> | ||
<BLANKLINE> | ||
""" | ||
|
||
baseurl = (r'https://spdf.gsfc.nasa.gov/pub/data/psp/fields/l2/{Wavelength}/' | ||
r'{year}/psp_fld_l2_(\w){{7}}_(\d){{8}}_v(\d){{2}}.cdf') | ||
pattern = r'{}/{Wavelength}/{year:4d}/psp_fld_l2_{Wavelength}_{year:4d}{month:2d}{day:2d}_v{:2d}.cdf' | ||
|
||
@classmethod | ||
def _check_wavelengths(cls, wavelength): | ||
""" | ||
Check for overlap between given wavelength and receiver frequency coverage defined in `RECEIVER_FREQUENCIES`. | ||
Parameters | ||
---------- | ||
wavelength : `sunpy.net.attrs.Wavelength` | ||
Input wavelength range to check | ||
Returns | ||
------- | ||
`list` | ||
List of receivers names or empty list if no overlap | ||
""" | ||
# Input wavelength range is completely contained in one receiver range | ||
receivers = [k for k, v in RECEIVER_FREQUENCIES.items() if wavelength in v] | ||
# If not defined need to continue | ||
if not receivers: | ||
# Overlaps but not contained in, either max in lfr or min hfr | ||
if wavelength.min in RECEIVER_FREQUENCIES['rfs_hfr'] or wavelength.max in RECEIVER_FREQUENCIES['rfs_hfr']: | ||
receivers.append('rfs_hfr') | ||
if wavelength.min in RECEIVER_FREQUENCIES['rfs_lfr'] or wavelength.max in RECEIVER_FREQUENCIES['rfs_lfr']: | ||
receivers.append('rfs_lfr') | ||
# min in lfr and max in hfr | ||
# min and max of combined lft and hfr contained in give wavelength range | ||
if a.Wavelength(RECEIVER_FREQUENCIES['rfs_lfr'].min, | ||
RECEIVER_FREQUENCIES['rfs_hfr'].max) in wavelength: | ||
receivers = ['rfs_lfr', 'rfs_hfr'] | ||
# If we get here the is no overlap so set to empty list | ||
return receivers | ||
|
||
def search(self, *args, **kwargs): | ||
""" | ||
Query this client for a list of results. | ||
Parameters | ||
---------- | ||
*args: `tuple` | ||
`sunpy.net.attrs` objects representing the query. | ||
**kwargs: `dict` | ||
Any extra keywords to refine the search. | ||
Returns | ||
------- | ||
A `QueryResponse` instance containing the query result. | ||
""" | ||
matchdict = self._get_match_dict(*args, **kwargs) | ||
req_wave = matchdict.get('Wavelength', None) | ||
receivers = RECEIVER_FREQUENCIES.keys() | ||
if req_wave is not None: | ||
receivers = self._check_wavelengths(req_wave) | ||
|
||
metalist = [] | ||
start_year = matchdict['Time'].start.datetime.year | ||
end_year = matchdict['Time'].end.datetime.year | ||
for receiver in receivers: | ||
for year in range(start_year, end_year+1): | ||
urlpattern = self.baseurl.format(Wavelength=receiver, year=year) | ||
scraper = Scraper(urlpattern, regex=True) | ||
filesmeta = scraper._extract_files_meta(matchdict['Time'], extractor=self.pattern) | ||
for i in filesmeta: | ||
rowdict = self.post_search_hook(i, matchdict) | ||
metalist.append(rowdict) | ||
|
||
return QueryResponse(metalist, client=self) | ||
|
||
def post_search_hook(self, exdict, matchdict): | ||
""" | ||
This method converts 'rfs_hfr' and 'rfs_lfr' in the url's metadata | ||
to the frequency ranges of for low and high frequency receivers. | ||
""" | ||
rowdict = super().post_search_hook(exdict, matchdict) | ||
if rowdict['Wavelength'] == 'rfs_hfr': | ||
fr = RECEIVER_FREQUENCIES['rfs_hfr'] | ||
rowdict['Wavelength'] = u.Quantity([float(fr.min.value), float(fr.max.value)], unit=fr.unit) | ||
elif rowdict['Wavelength'] == 'rfs_lfr': | ||
fr = RECEIVER_FREQUENCIES['rfs_lfr'] | ||
rowdict['Wavelength'] = u.Quantity([float(fr.min.value), float(fr.max.value)], unit=fr.unit) | ||
return rowdict | ||
|
||
@classmethod | ||
def register_values(cls): | ||
from sunpy.net import attrs | ||
adict = {attrs.Instrument: [('RFS', | ||
('Radio Frequency Spectrometer'))], | ||
attrs.Source: [('PSP', 'Parker Solar Probe')], | ||
attrs.Provider: [('SPDF', 'NASA Goddard Space Physics Data Facility')], | ||
attrs.Wavelength: [('*')]} | ||
return adict |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import numpy as np | ||
import pytest | ||
|
||
import astropy.units as u | ||
from astropy.time import Time | ||
from sunpy.net import Fido | ||
from sunpy.net import attrs as a | ||
|
||
from radiospectra.net.sources.psp import RFSClient | ||
|
||
client = RFSClient() | ||
|
||
|
||
@pytest.mark.parametrize("req_wave,receivers", [ | ||
# Completely contain the both receiver ranges | ||
(a.Wavelength(1*u.kHz, 25000*u.kHz), ['rfs_lfr', 'rfs_hfr']), | ||
# Min in lower freq and max in high freq receiver | ||
(a.Wavelength(20*u.kHz, 15*u.MHz), ['rfs_lfr', 'rfs_hfr']), | ||
# Min below and max in low freq receiver | ||
(a.Wavelength(1*u.kHz, 100*u.kHz), ['rfs_lfr']), | ||
# Min and max in low freq receiver | ||
(a.Wavelength(20*u.kHz, 100*u.kHz), ['rfs_lfr']), | ||
# Min and max in high freq receiver | ||
(a.Wavelength(1800*u.kHz, 18000*u.kHz), ['rfs_hfr']), | ||
# Min in high freq receiver and max above | ||
(a.Wavelength(1800*u.kHz, 20000*u.kHz), ['rfs_hfr']), | ||
# Min and max in the over lap | ||
(a.Wavelength(1.4*u.MHz, 1.5*u.MHz), ['rfs_lfr', 'rfs_hfr']) | ||
]) | ||
def test_check_wavelength(req_wave, receivers): | ||
res = RFSClient._check_wavelengths(req_wave) | ||
assert set(res) == set(receivers) | ||
|
||
|
||
@pytest.mark.remote_data | ||
def test_fido(): | ||
atr = a.Time('2019/10/01', '2019/10/02') | ||
res = Fido.search(atr, a.Instrument('rfs')) | ||
res0 = res.get_response(0) | ||
isinstance(res0.client, RFSClient) | ||
assert len(res0) == 4 | ||
tr = res0.time_range() | ||
assert tr.start.datetime == Time('2019-10-01T00:00').datetime | ||
assert tr.end.datetime == Time('2019-10-02T23:59:59.999').datetime | ||
|
||
|
||
@pytest.mark.remote_data | ||
def test_search_with_wavelength(): | ||
tr = a.Time('2019/10/13', '2019/10/15') | ||
wr1 = a.Wavelength(1*u.kHz, 1.1*u.MHz) | ||
res1 = client.search(tr, wr1) | ||
assert np.array_equal(res1.blocks[0]['Wavelength'], [10, 1700] * u.kHz) | ||
assert len(res1) == 3 | ||
assert res1.time_range().start == Time('2019-10-13T00:00').datetime | ||
assert res1.time_range().end == Time('2019-10-15T23:59:59.999').datetime | ||
wr2 = a.Wavelength(2*u.MHz, 20*u.MHz) | ||
res2 = client.search(tr, wr2) | ||
assert np.array_equal(res2.blocks[0]['Wavelength'], [1300, 19200] * u.kHz) | ||
assert len(res2) == 3 | ||
assert res2.time_range().start == Time('2019-10-13T00:00').datetime | ||
assert res2.time_range().end == Time('2019-10-15T23:59:59.999').datetime | ||
|
||
|
||
@pytest.mark.remote_data | ||
def test_get_url_for_time_range(): | ||
url_start = 'https://spdf.gsfc.nasa.gov/pub/data/psp/fields/l2/rfs_lfr/2019/' \ | ||
'psp_fld_l2_rfs_lfr_20191001_v02.cdf' | ||
url_end = 'https://spdf.gsfc.nasa.gov/pub/data/psp/fields/l2/rfs_hfr/2019/' \ | ||
'psp_fld_l2_rfs_hfr_20191015_v02.cdf' | ||
tr = a.Time('2019/10/01', '2019/10/15') | ||
res = client.search(tr) | ||
urls = [i['url'] for i in res] | ||
assert urls[0] == url_start | ||
assert urls[-1] == url_end | ||
|
||
|
||
def test_can_handle_query(): | ||
atr = a.Time('2019/10/01', '2019/11/01') | ||
res = client._can_handle_query(atr, a.Instrument('rfs')) | ||
assert res is True | ||
res = client._can_handle_query(atr) | ||
assert res is False | ||
|
||
|
||
@pytest.mark.remote_data | ||
def test_get(): | ||
query = client.search(a.Time('2019/10/05', '2019/10/10'), a.Instrument('rfr')) | ||
download_list = client.fetch(query) | ||
assert len(download_list) == len(query) |