├── radiospectra ├── data │ ├── __init__.py │ └── README.rst ├── net │ ├── sources │ │ ├── __init__.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── data │ │ │ │ ├── psp_resp1.html.gz │ │ │ │ ├── psp_resp2.html.gz │ │ │ │ ├── ecallisto_resp1.html.gz │ │ │ │ ├── ecallisto_resp2.html.gz │ │ │ │ ├── rstn_holloman.html │ │ │ │ ├── rstn_learmonth.html │ │ │ │ ├── rstn_san-vito.html │ │ │ │ ├── eovsa_resp1.html │ │ │ │ ├── eovsa_resp2.html │ │ │ │ └── ecallisto_resp_alt_format.html │ │ │ ├── test_eovsa_client.py │ │ │ ├── test_rstn_client.py │ │ │ ├── test_ecallisto_client.py │ │ │ ├── test_ilofar.py │ │ │ └── test_psp_client.py │ │ ├── eovsa.py │ │ ├── rstn.py │ │ ├── ecallisto.py │ │ ├── ilofar.py │ │ └── psp.py │ ├── attrs.py │ └── __init__.py ├── spectrogram │ ├── tests │ │ ├── __init__.py │ │ └── test_spectrogrambase.py │ ├── sources │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_ilofar357.py │ │ │ ├── test_rstn.py │ │ │ ├── test_waves.py │ │ │ ├── test_swaves.py │ │ │ ├── test_psp_rfs.py │ │ │ ├── test_callisto.py │ │ │ └── test_eovsa.py │ │ ├── ilofar357.py │ │ ├── __init__.py │ │ ├── rstn.py │ │ ├── rpw.py │ │ ├── eovsa.py │ │ ├── swaves.py │ │ ├── psp_rfs.py │ │ ├── waves.py │ │ └── callisto.py │ ├── __init__.py │ └── spectrogrambase.py ├── tests │ ├── __init__.py │ ├── data │ │ └── BIR_20110607_062400_10.fit │ └── test_spectrum.py ├── __init__.py ├── _dev │ ├── __init__.py │ └── scm_version.py ├── exceptions.py ├── version.py ├── utils.py ├── mixins.py └── spectrum.py ├── setup.py ├── docs ├── code_ref │ ├── radiospectra.rst │ ├── spectrum.rst │ ├── spectrogram.rst │ ├── net.rst │ └── index.rst ├── rtd_requirements.txt ├── whatsnew │ ├── index.rst │ └── changelog.rst ├── robots.txt ├── overview.rst ├── Makefile ├── make.bat ├── index.rst └── conf.py ├── .gitattributes ├── .rtd-environment.yml ├── .github ├── dependabot.yml └── workflows │ ├── label_sync.yml │ ├── sub_package_update.yml │ └── ci.yml ├── .codecov.yaml ├── changelog ├── 127.breaking.rst └── README.rst ├── .editorconfig ├── .codespellrc ├── .readthedocs.yml ├── MANIFEST.in ├── licenses ├── README.rst ├── LICENSE.rst └── TEMPLATE_LICENSE.rst ├── .isort.cfg ├── .readthedocs.yaml ├── .flake8 ├── .coveragerc ├── .pre-commit-config.yaml ├── LICENSE.rst ├── .cruft.json ├── pytest.ini ├── .ruff.toml ├── tox.ini ├── CHANGELOG.rst ├── pyproject.toml ├── README.rst └── .gitignore /radiospectra/data/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /radiospectra/net/sources/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/tests/test_spectrogrambase.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /radiospectra/tests/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains package tests. 3 | """ 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup 3 | 4 | setup() 5 | -------------------------------------------------------------------------------- /docs/code_ref/radiospectra.rst: -------------------------------------------------------------------------------- 1 | `radiospectra` 2 | ============== 3 | 4 | .. automodapi:: radiospectra 5 | -------------------------------------------------------------------------------- /radiospectra/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .version import version as __version__ 3 | 4 | __all__ = ["__version__"] 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *fits binary 2 | *fit binary 3 | *fts binary 4 | *fit.gz binary 5 | *fits.gz binary 6 | *fts.gz binary 7 | -------------------------------------------------------------------------------- /docs/code_ref/spectrum.rst: -------------------------------------------------------------------------------- 1 | `radiospectra.spectrum` 2 | ========================== 3 | 4 | .. automodapi:: radiospectra.spectrum 5 | -------------------------------------------------------------------------------- /docs/rtd_requirements.txt: -------------------------------------------------------------------------------- 1 | # This file is used on ReadTheDocs to build the documentation and is separate of tox.ini and setup.cfg 2 | -------------------------------------------------------------------------------- /docs/code_ref/spectrogram.rst: -------------------------------------------------------------------------------- 1 | `radiospectra.spectrogram` 2 | ========================== 3 | 4 | .. automodapi:: radiospectra.spectrogram 5 | -------------------------------------------------------------------------------- /docs/code_ref/net.rst: -------------------------------------------------------------------------------- 1 | `radiospectra.net` 2 | ================== 3 | 4 | .. automodapi:: radiospectra.net 5 | 6 | .. automodapi:: radiospectra.net.attrs 7 | -------------------------------------------------------------------------------- /radiospectra/tests/data/BIR_20110607_062400_10.fit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunpy/radiospectra/HEAD/radiospectra/tests/data/BIR_20110607_062400_10.fit -------------------------------------------------------------------------------- /.rtd-environment.yml: -------------------------------------------------------------------------------- 1 | name: radiospectra 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - python=3.12 6 | - pip 7 | - graphviz!=2.42.*,!=2.43.* 8 | -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/data/psp_resp1.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunpy/radiospectra/HEAD/radiospectra/net/sources/tests/data/psp_resp1.html.gz -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/data/psp_resp2.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunpy/radiospectra/HEAD/radiospectra/net/sources/tests/data/psp_resp2.html.gz -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/data/ecallisto_resp1.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunpy/radiospectra/HEAD/radiospectra/net/sources/tests/data/ecallisto_resp1.html.gz -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/data/ecallisto_resp2.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunpy/radiospectra/HEAD/radiospectra/net/sources/tests/data/ecallisto_resp2.html.gz -------------------------------------------------------------------------------- /docs/whatsnew/index.rst: -------------------------------------------------------------------------------- 1 | .. _whatsnew: 2 | 3 | *************** 4 | Release History 5 | *************** 6 | 7 | .. toctree:: 8 | :maxdepth: 1 9 | 10 | changelog 11 | -------------------------------------------------------------------------------- /docs/code_ref/index.rst: -------------------------------------------------------------------------------- 1 | .. _reference: 2 | 3 | === 4 | API 5 | === 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | radiospectra 11 | net 12 | spectrogram 13 | spectrum 14 | -------------------------------------------------------------------------------- /.codecov.yaml: -------------------------------------------------------------------------------- 1 | comment: off 2 | coverage: 3 | status: 4 | project: 5 | default: 6 | threshold: 0.2% 7 | 8 | codecov: 9 | require_ci_to_pass: false 10 | notify: 11 | wait_for_ci: true 12 | -------------------------------------------------------------------------------- /radiospectra/_dev/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This package contains utilities that are only used when developing in a 3 | copy of the source repository. 4 | These files are not installed, and should not be assumed to exist at 5 | runtime. 6 | """ 7 | -------------------------------------------------------------------------------- /docs/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: /*/latest/ 3 | Allow: /en/latest/ # Fallback for bots that don't understand wildcards 4 | Allow: /*/stable/ 5 | Allow: /en/stable/ # Fallback for bots that don't understand wildcards 6 | Disallow: / 7 | -------------------------------------------------------------------------------- /docs/whatsnew/changelog.rst: -------------------------------------------------------------------------------- 1 | .. _changelog: 2 | 3 | ************** 4 | Full Changelog 5 | ************** 6 | 7 | .. _changelog:: 8 | :towncrier: ../../ 9 | :towncrier-skip-if-empty: 10 | :changelog_file: ../../CHANGELOG.rst 11 | -------------------------------------------------------------------------------- /radiospectra/exceptions.py: -------------------------------------------------------------------------------- 1 | __all__ = ["NoSpectrogramInFileError", "SpectraMetaValidationError"] 2 | 3 | 4 | class NoSpectrogramInFileError(Exception): 5 | pass 6 | 7 | 8 | class SpectraMetaValidationError(AttributeError): 9 | pass 10 | -------------------------------------------------------------------------------- /changelog/127.breaking.rst: -------------------------------------------------------------------------------- 1 | Increased minimum version of Python to 3.12. 2 | Increased minimum version of NumPy to 1.26.0. 3 | Increased minimum version of Matplotlib to 3.8.0. 4 | Increased minimum version of SciPy to 1.12.0. 5 | Increased minimum version of sunpy to 7.0.0. 6 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | ``radiospectra`` Spectrum 3 | """ 4 | 5 | from radiospectra.spectrogram.sources import * # NOQA 6 | from radiospectra.spectrogram.spectrogram_factory import * # NOQA 7 | from radiospectra.spectrogram.spectrogrambase import * # NOQA 8 | -------------------------------------------------------------------------------- /radiospectra/data/README.rst: -------------------------------------------------------------------------------- 1 | Data directory 2 | ============== 3 | 4 | This directory contains data files included with the package source 5 | code distribution. Note that this is intended only for relatively small files 6 | - large files should be externally hosted and downloaded as needed. 7 | -------------------------------------------------------------------------------- /docs/overview.rst: -------------------------------------------------------------------------------- 1 | ******** 2 | Overview 3 | ******** 4 | 5 | ``radiospectra`` currently supports reading spectra from: 6 | 7 | - e-CALISTO 8 | - Extend Owen Valley Array 9 | - Parker Solar Probe FIELDS/Radio Frequency Spectrometer 10 | - Radio Solar Telescope Network 11 | - STEREO Waves 12 | - Wind Waves 13 | -------------------------------------------------------------------------------- /radiospectra/tests/test_spectrum.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from radiospectra.spectrum import Spectrum 4 | 5 | 6 | def test_spectrum(): 7 | spec = Spectrum(np.arange(10), np.arange(10)) 8 | np.testing.assert_equal(spec.data, np.arange(10)) 9 | np.testing.assert_equal(spec.freq_axis, np.arange(10)) 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | 3 | root = true 4 | 5 | # utf, UNIX-style new line 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.{py,rst,md}] 13 | indent_style = space 14 | indent_size = 4 15 | 16 | [*.yml] 17 | indent_style = space 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | skip = *.asdf,*.fits,*.fts,*.header,*.json,*.xsh,*cache*,*egg*,*extern*,.git,.idea,.tox,_build,*truncated,*.svg,.asv_env,.history,radiospectra/net/sources/tests/*.html 3 | ignore-words-list = 4 | alog, 5 | nd, 6 | nin, 7 | observ, 8 | ot, 9 | te, 10 | upto, 11 | afile, 12 | precessed, 13 | precess, 14 | technik 15 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | build: 3 | os: ubuntu-20.04 4 | tools: 5 | python: "3.12" 6 | apt_packages: 7 | - graphviz 8 | 9 | sphinx: 10 | builder: html 11 | configuration: docs/conf.py 12 | fail_on_warning: false 13 | 14 | python: 15 | install: 16 | - method: pip 17 | extra_requirements: 18 | - all 19 | - docs 20 | path: . 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Exclude specific files 2 | # All files which are tracked by git and not explicitly excluded here are included by setuptools_scm 3 | # Prune folders 4 | prune build 5 | prune docs/_build 6 | prune docs/api 7 | global-exclude *.pyc *.o 8 | 9 | # This subpackage is only used in development checkouts 10 | # and should not be included in built tarballs 11 | prune radiospectra/_dev 12 | -------------------------------------------------------------------------------- /licenses/README.rst: -------------------------------------------------------------------------------- 1 | Licenses 2 | ======== 3 | 4 | This directory holds license and credit information for the package, 5 | works the package is derived from, and/or datasets. 6 | 7 | Ensure that you pick a package licence which is in this folder and it matches 8 | the one mentioned in the top level README.rst file. If you are using the 9 | pre-rendered version of this template check for the word 'Other' in the README. 10 | -------------------------------------------------------------------------------- /radiospectra/net/attrs.py: -------------------------------------------------------------------------------- 1 | from sunpy.net.attr import SimpleAttr 2 | 3 | __all__ = ["Spacecraft", "Observatory", "PolType"] 4 | 5 | 6 | class Spacecraft(SimpleAttr): 7 | """ 8 | The STEREO Spacecraft A (Ahead) or B (Behind). 9 | """ 10 | 11 | 12 | class Observatory(SimpleAttr): 13 | """ 14 | Observatory. 15 | """ 16 | 17 | 18 | class PolType(SimpleAttr): 19 | """ 20 | Polarisation Type. 21 | """ 22 | -------------------------------------------------------------------------------- /radiospectra/_dev/scm_version.py: -------------------------------------------------------------------------------- 1 | # Try to use setuptools_scm to get the current version; this is only used 2 | # in development installations from the git repository. 3 | from pathlib import Path 4 | 5 | try: 6 | from setuptools_scm import get_version 7 | 8 | version = get_version(root=Path('../..'), relative_to=__file__) 9 | except ImportError: 10 | raise 11 | except Exception as e: 12 | raise ValueError('setuptools_scm can not determine version.') from e 13 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | balanced_wrapping = true 3 | skip = 4 | docs/conf.py 5 | radiospectra/__init__.py 6 | default_section = THIRDPARTY 7 | include_trailing_comma = true 8 | known_astropy = astropy, asdf 9 | known_sunpy = sunpy 10 | known_first_party = radiospectra 11 | length_sort = false 12 | length_sort_sections = stdlib 13 | line_length = 110 14 | multi_line_output = 3 15 | no_lines_before = LOCALFOLDER 16 | sections = STDLIB, THIRDPARTY, ASTROPY, SUNPY, FIRSTPARTY, LOCALFOLDER 17 | -------------------------------------------------------------------------------- /radiospectra/net/__init__.py: -------------------------------------------------------------------------------- 1 | from radiospectra.net.attrs import * 2 | from radiospectra.net.sources.ecallisto import eCALLISTOClient 3 | from radiospectra.net.sources.eovsa import EOVSAClient 4 | from radiospectra.net.sources.ilofar import ILOFARMode357Client 5 | from radiospectra.net.sources.psp import RFSClient 6 | from radiospectra.net.sources.rstn import RSTNClient 7 | 8 | __all__ = [ 9 | "eCALLISTOClient", 10 | "EOVSAClient", 11 | "RFSClient", 12 | "RSTNClient", 13 | "ILOFARMode357Client", 14 | ] 15 | -------------------------------------------------------------------------------- /radiospectra/version.py: -------------------------------------------------------------------------------- 1 | # NOTE: First try _dev.scm_version if it exists and setuptools_scm is installed 2 | # This file is not included in wheels/tarballs, so otherwise it will 3 | # fall back on the generated _version module. 4 | try: 5 | try: 6 | from ._dev.scm_version import version 7 | except ImportError: 8 | from ._version import version 9 | except Exception: 10 | import warnings 11 | 12 | warnings.warn( 13 | f'could not determine {__name__.split(".")[0]} package version; this indicates a broken installation' 14 | ) 15 | del warnings 16 | 17 | version = '0.0.0' 18 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-lts-latest 5 | tools: 6 | python: "mambaforge-latest" 7 | jobs: 8 | post_checkout: 9 | - git fetch --unshallow || true 10 | pre_install: 11 | - git update-index --assume-unchanged .rtd-environment.yml docs/conf.py 12 | 13 | conda: 14 | environment: .rtd-environment.yml 15 | 16 | sphinx: 17 | builder: html 18 | configuration: docs/conf.py 19 | fail_on_warning: false 20 | 21 | formats: 22 | - htmlzip 23 | 24 | python: 25 | install: 26 | - method: pip 27 | extra_requirements: 28 | - docs 29 | path: . 30 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/ilofar357.py: -------------------------------------------------------------------------------- 1 | from radiospectra.spectrogram.spectrogrambase import GenericSpectrogram 2 | 3 | __all__ = [ 4 | "ILOFARMode357Spectrogram", 5 | ] 6 | 7 | 8 | class ILOFARMode357Spectrogram(GenericSpectrogram): 9 | """ 10 | Irish LOFAR Station mode 357 Spectrogram 11 | """ 12 | 13 | @property 14 | def mode(self): 15 | return self.meta.get("mode") 16 | 17 | @property 18 | def polarisation(self): 19 | return self.meta.get("polarisation") 20 | 21 | @classmethod 22 | def is_datasource_for(cls, data, meta, **kwargs): 23 | return meta["instrument"] == "ILOFAR" 24 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = 3 | # missing-whitespace-around-operator 4 | E225 5 | # missing-whitespace-around-arithmetic-operator 6 | E226 7 | # line-too-long 8 | E501 9 | # unused-import 10 | F401 11 | # undefined-local-with-import-star 12 | F403 13 | # redefined-while-unused 14 | F811 15 | # Line break occurred before a binary operator 16 | W503, 17 | # Line break occurred after a binary operator 18 | W504 19 | max-line-length = 110 20 | exclude = 21 | .git 22 | __pycache__ 23 | docs/conf.py 24 | build 25 | radiospectra/__init__.py 26 | rst-directives = 27 | plot 28 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Datasource-specific classes. 3 | 4 | This is where datasource specific logic is implemented. 5 | Each mission should have its own file with one or more classes defined. 6 | """ 7 | 8 | from ..spectrogram_factory import Spectrogram # NOQA 9 | from .callisto import * # NOQA 10 | from .eovsa import * # NOQA 11 | from .ilofar357 import * # NOQA 12 | from .psp_rfs import * # NOQA 13 | from .rpw import * # NOQA 14 | from .rstn import * # NOQA 15 | from .swaves import * # NOQA 16 | from .waves import * # NOQA 17 | 18 | __all__ = [ 19 | "SWAVESSpectrogram", 20 | "RFSSpectrogram", 21 | "CALISTOSpectrogram", 22 | "EOVSASpectrogram", 23 | "RSTNSpectrogram", 24 | "RPWSpectrogram", 25 | "ILOFARMode357Spectrogram", 26 | ] 27 | -------------------------------------------------------------------------------- /.github/workflows/label_sync.yml: -------------------------------------------------------------------------------- 1 | name: Label Sync 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | # ┌───────── minute (0 - 59) 6 | # │ ┌───────── hour (0 - 23) 7 | # │ │ ┌───────── day of the month (1 - 31) 8 | # │ │ │ ┌───────── month (1 - 12 or JAN-DEC) 9 | # │ │ │ │ ┌───────── day of the week (0 - 6 or SUN-SAT) 10 | - cron: '0 0 * * *' # run every day at midnight UTC 11 | 12 | # Give permissions to write issue labels 13 | permissions: 14 | issues: write 15 | 16 | jobs: 17 | label_sync: 18 | runs-on: ubuntu-latest 19 | name: Label Sync 20 | steps: 21 | - uses: srealmoreno/label-sync-action@850ba5cef2b25e56c6c420c4feed0319294682fd 22 | with: 23 | config-file: https://raw.githubusercontent.com/sunpy/.github/main/labels.yml 24 | -------------------------------------------------------------------------------- /radiospectra/utils.py: -------------------------------------------------------------------------------- 1 | import astropy.units as u 2 | 3 | __all__ = ["subband_to_freq"] 4 | 5 | 6 | def subband_to_freq(subband, obs_mode): 7 | """ 8 | Converts LOFAR single station subbands to frequency 9 | 10 | Parameters 11 | ---------- 12 | subband : `int` 13 | Subband number. 14 | obs_mode : `int` 15 | Observation mode 3, 5, 7. 16 | 17 | Return 18 | ------ 19 | `astropy.units.Quantity` 20 | Frequency in MHz 21 | """ 22 | nyq_zone_dict = {3: 1, 5: 2, 7: 3} 23 | if obs_mode not in nyq_zone_dict: 24 | raise ValueError(f"Observation mode {obs_mode} not supported, only 3, 5, 7 are supported.") 25 | nyq_zone = nyq_zone_dict[obs_mode] 26 | clock_dict = {3: 200, 4: 160, 5: 200, 6: 160, 7: 200} # MHz 27 | clock = clock_dict[obs_mode] 28 | freq = (nyq_zone - 1 + subband / 512) * (clock / 2) 29 | return freq * u.MHz # MHz 30 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | radiospectra/conftest.py 4 | radiospectra/*setup_package* 5 | radiospectra/extern/* 6 | radiospectra/version* 7 | */radiospectra/conftest.py 8 | */radiospectra/*setup_package* 9 | */radiospectra/extern/* 10 | */radiospectra/version* 11 | 12 | [report] 13 | exclude_lines = 14 | # Have to re-enable the standard pragma 15 | pragma: no cover 16 | # Don't complain about packages we have installed 17 | except ImportError 18 | # Don't complain if tests don't hit assertions 19 | raise AssertionError 20 | raise NotImplementedError 21 | # Don't complain about script hooks 22 | def main(.*): 23 | # Ignore branches that don't pertain to this version of Python 24 | pragma: py{ignore_python_version} 25 | # Don't complain about IPython completion helper 26 | def _ipython_key_completions_ 27 | # typing.TYPE_CHECKING is False at runtime 28 | if TYPE_CHECKING: 29 | # Ignore typing overloads 30 | @overload 31 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/rstn.py: -------------------------------------------------------------------------------- 1 | from radiospectra.spectrogram.spectrogrambase import GenericSpectrogram 2 | 3 | __all__ = ["RSTNSpectrogram"] 4 | 5 | 6 | class RSTNSpectrogram(GenericSpectrogram): 7 | """ 8 | Radio Solar Telescope Network. 9 | 10 | Examples 11 | -------- 12 | >>> import radiospectra.net 13 | >>> from sunpy.net import Fido, attrs as a 14 | >>> from radiospectra.spectrogram import Spectrogram 15 | >>> query = Fido.search(a.Time('2017/09/07 00:00', '2017/09/07 23:00'), a.Instrument.rstn) #doctest: +REMOTE_DATA 16 | >>> downloaded = Fido.fetch(query[0][0]) #doctest: +REMOTE_DATA 17 | >>> spec = Spectrogram(downloaded[0]) #doctest: +REMOTE_DATA 18 | >>> spec #doctest: +REMOTE_DATA 19 | 20 | >>> spec.plot() #doctest: +REMOTE_DATA 21 | 22 | """ 23 | 24 | @classmethod 25 | def is_datasource_for(cls, data, meta, **kwargs): 26 | return meta["instrument"] == "RSTN" 27 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/tests/test_ilofar357.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from unittest import mock 3 | 4 | import numpy as np 5 | 6 | from radiospectra.spectrogram import Spectrogram 7 | 8 | 9 | @mock.patch("numpy.fromfile") 10 | @mock.patch("sunpy.util.io.is_file") 11 | def test_ilofar(mock_is_file, mock_fromfile): 12 | mock_fromfile.return_value = np.ones(10117216) 13 | mock_is_file.return_value = True 14 | 15 | spec = Spectrogram(Path("20180602_063247_bst_00X.dat")) 16 | assert len(spec) == 3 17 | assert spec[0].polarisation == "X" 18 | assert spec[0].start_time.iso == "2018-06-02 06:32:47.000" 19 | assert spec[0].end_time.iso == "2018-06-02 12:18:18.000" 20 | assert spec[0].mode == 3 21 | assert spec[0].frequencies[0].to_value("MHz") == 10.546875 22 | assert spec[0].frequencies[-1].to_value("MHz") == 88.28125 23 | assert spec[1].mode == 5 24 | assert spec[1].frequencies[0].to_value("MHz") == 110.546875 25 | assert spec[1].frequencies[-1].to_value("MHz") == 188.28125 26 | assert spec[2].mode == 7 27 | assert spec[2].frequencies[0].to_value("MHz") == 210.546875 28 | assert spec[2].frequencies[-1].to_value("MHz") == 244.53125 29 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | # This should be before any formatting hooks like isort 3 | - repo: https://github.com/astral-sh/ruff-pre-commit 4 | rev: "v0.14.3" 5 | hooks: 6 | - id: ruff 7 | args: ["--fix"] 8 | - repo: https://github.com/PyCQA/isort 9 | rev: 7.0.0 10 | hooks: 11 | - id: isort 12 | exclude: ".*(.fits|.fts|.fit|.header|.txt|tca.*|extern.*|radiospectra/extern)$" 13 | - repo: https://github.com/pre-commit/pre-commit-hooks 14 | rev: v6.0.0 15 | hooks: 16 | - id: check-ast 17 | - id: check-case-conflict 18 | - id: trailing-whitespace 19 | exclude: ".*(.fits|.fts|.fit|.header|.txt)$" 20 | - id: check-yaml 21 | - id: debug-statements 22 | - id: check-added-large-files 23 | args: ["--enforce-all", "--maxkb=1054"] 24 | - id: end-of-file-fixer 25 | exclude: ".*(.fits|.fts|.fit|.header|.txt|tca.*|.json)$|^CITATION.rst$" 26 | - id: mixed-line-ending 27 | exclude: ".*(.fits|.fts|.fit|.header|.txt|tca.*)$" 28 | - repo: https://github.com/codespell-project/codespell 29 | rev: v2.4.1 30 | hooks: 31 | - id: codespell 32 | args: [ "--write-changes" ] 33 | ci: 34 | autofix_prs: false 35 | autoupdate_schedule: "quarterly" 36 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2024, The SunPy Developers 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 20 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /licenses/LICENSE.rst: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024, The SunPy Community 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 20 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/rpw.py: -------------------------------------------------------------------------------- 1 | from radiospectra.spectrogram.spectrogrambase import GenericSpectrogram 2 | 3 | __all__ = ["RPWSpectrogram"] 4 | 5 | 6 | class RPWSpectrogram(GenericSpectrogram): 7 | """ 8 | Solar Orbiter Radio and Plasma Waves (RPW) RPW-HFR-SURV spectrogram. 9 | 10 | For more information on the instrument see ``__. 11 | 12 | Examples 13 | -------- 14 | >>> import sunpy_soar 15 | >>> from sunpy.net import Fido, attrs as a 16 | >>> from radiospectra.spectrogram import Spectrogram 17 | >>> query = Fido.search(a.Time('2020-07-11', '2020-07-11 23:59'), a.Instrument('RPW'), 18 | ... a.Level(2), a.soar.Product('RPW-HFR-SURV')) #doctest: +REMOTE_DATA 19 | >>> downloaded = Fido.fetch(query[0]) #doctest: +SKIP 20 | >>> spec = Spectrogram(downloaded[0]) #doctest: +SKIP 21 | >>> spec #doctest: +SKIP 22 | [, ] 23 | >>> spec[0] .plot() #doctest: +SKIP 24 | 25 | """ 26 | 27 | @classmethod 28 | def is_datasource_for(cls, data, meta, **kwargs): 29 | return meta["instrument"] == "RPW" 30 | -------------------------------------------------------------------------------- /.cruft.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "https://github.com/sunpy/package-template", 3 | "commit": "c359c134fbf9e3f11302c2019fb58ac11cf14cdf", 4 | "checkout": null, 5 | "context": { 6 | "cookiecutter": { 7 | "package_name": "radiospectra", 8 | "module_name": "radiospectra", 9 | "short_description": "Provide support for some type of radio spectra in solar physics.", 10 | "author_name": "The SunPy Community", 11 | "author_email": "sunpy@googlegroups.com", 12 | "project_url": "https://sunpy.org", 13 | "github_repo": "", 14 | "sourcecode_url": "", 15 | "download_url": "https://pypi.org/project/radiospectra", 16 | "documentation_url": "", 17 | "changelog_url": "", 18 | "issue_tracker_url": "", 19 | "license": "BSD 2-Clause", 20 | "minimum_python_version": "3.12", 21 | "use_compiled_extensions": "n", 22 | "enable_dynamic_dev_versions": "y", 23 | "include_example_code": "n", 24 | "include_cruft_update_github_workflow": "y", 25 | "use_extended_ruff_linting": "n", 26 | "_sphinx_theme": "sunpy", 27 | "_parent_project": "", 28 | "_install_requires": "", 29 | "_copy_without_render": [ 30 | "docs/_templates", 31 | "docs/_static", 32 | ".github/workflows/sub_package_update.yml" 33 | ], 34 | "_template": "https://github.com/sunpy/package-template", 35 | "_commit": "c359c134fbf9e3f11302c2019fb58ac11cf14cdf" 36 | } 37 | }, 38 | "directory": null 39 | } 40 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | minversion = 7.0 3 | testpaths = 4 | radiospectra 5 | docs 6 | norecursedirs = 7 | .tox 8 | build 9 | docs/_build 10 | docs/generated 11 | *.egg-info 12 | examples 13 | radiospectra/_dev 14 | .history 15 | radiospectra/extern 16 | doctest_plus = enabled 17 | doctest_optionflags = 18 | NORMALIZE_WHITESPACE 19 | FLOAT_CMP 20 | ELLIPSIS 21 | text_file_format = rst 22 | addopts = 23 | --doctest-rst 24 | -p no:unraisableexception 25 | -p no:threadexception 26 | filterwarnings = 27 | # A list of warnings to ignore follows. If you add to this list, you MUST 28 | # add a comment or ideally a link to an issue that explains why the warning 29 | # is being ignored 30 | error 31 | # 32 | # This is due to dependencies building with a numpy version different from 33 | # the local installed numpy version, but should be fine 34 | # See https://github.com/numpy/numpy/issues/15748#issuecomment-598584838 35 | ignore:numpy.ufunc size changed:RuntimeWarning 36 | ignore:numpy.ndarray size changed:RuntimeWarning 37 | # Zeep 38 | ignore:'cgi' is deprecated:DeprecationWarning 39 | # Issue with pytest-cov injecting --rsync arg https://github.com/pytest-dev/pytest-xdist/issues/825 40 | # https://github.com/pytest-dev/pytest-cov/issues/557 41 | ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated.:DeprecationWarning 42 | ignore:File may have been truncated.* 43 | ignore:pattern has been replaced with the format keyword 44 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/eovsa.py: -------------------------------------------------------------------------------- 1 | from radiospectra.spectrogram.spectrogrambase import GenericSpectrogram 2 | 3 | __all__ = ["EOVSASpectrogram"] 4 | 5 | 6 | class EOVSASpectrogram(GenericSpectrogram): 7 | """ 8 | Extend Owen Valley Array (EOVSA) Spectrogram. 9 | 10 | Examples 11 | -------- 12 | >>> import radiospectra.net 13 | >>> from sunpy.net import Fido, attrs as a 14 | >>> from radiospectra.spectrogram import Spectrogram 15 | >>> query = Fido.search(a.Time('2021/05/07 00:00', '2021/05/07 23:00'), a.Instrument.eovsa) #doctest: +REMOTE_DATA 16 | >>> downloaded = Fido.fetch(query[0][0]) #doctest: +REMOTE_DATA 17 | >>> spec = Spectrogram(downloaded[0]) #doctest: +REMOTE_DATA 18 | >>> spec #doctest: +REMOTE_DATA 19 | 20 | >>> spec.plot() #doctest: +REMOTE_DATA 21 | 22 | """ 23 | 24 | def __init__(self, data, meta, **kwargs): 25 | super().__init__(meta=meta, data=data, **kwargs) 26 | 27 | @property 28 | def polarisation(self): 29 | return self.meta["fits_meta"]["POLARIZA"] 30 | 31 | @classmethod 32 | def is_datasource_for(cls, data, meta, **kwargs): 33 | return meta["instrument"] == "EOVSA" or meta["detector"] == "EOVSA" 34 | 35 | # TODO fix time gaps for plots need to render them as gaps 36 | # can prob do when generateing proper pcolormesh grid but then prob doesn't belong here 37 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/swaves.py: -------------------------------------------------------------------------------- 1 | from radiospectra.spectrogram.spectrogrambase import GenericSpectrogram 2 | 3 | __all__ = ["SWAVESSpectrogram"] 4 | 5 | 6 | class SWAVESSpectrogram(GenericSpectrogram): 7 | """ 8 | STEREO Waves or S/WAVES, SWAVES Spectrogram. 9 | 10 | Examples 11 | -------- 12 | >>> import radiospectra.net #doctest: +SKIP 13 | >>> from sunpy.net import Fido, attrs as a #doctest: +SKIP 14 | >>> from radiospectra.spectrogram import Spectrogram #doctest: +SKIP 15 | >>> from radiospectra.net import attrs as ra #doctest: +SKIP 16 | >>> query = Fido.search(a.Time('2019/10/05 23:00', '2019/10/06 00:59'), #doctest: +REMOTE_DATA +SKIP 17 | ... a.Instrument('SWAVES')) #doctest: +REMOTE_DATA +SKIP 18 | >>> downloaded = Fido.fetch(query[1][0]) #doctest: +REMOTE_DATA +SKIP 19 | >>> spec = Spectrogram(downloaded[0]) #doctest: +REMOTE_DATA +SKIP 20 | >>> spec #doctest: +REMOTE_DATA +SKIP 21 | 22 | >>> spec.plot() #doctest: +REMOTE_DATA +SKIP 23 | 24 | """ 25 | 26 | def __init__(self, data, meta, **kwargs): 27 | super().__init__(meta=meta, data=data, **kwargs) 28 | 29 | @property 30 | def receiver(self): 31 | """ 32 | The name of the receiver. 33 | """ 34 | return self.meta["receiver"] 35 | 36 | @classmethod 37 | def is_datasource_for(cls, data, meta, **kwargs): 38 | return meta["instrument"] == "swaves" 39 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/tests/test_rstn.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from datetime import datetime 3 | from unittest import mock 4 | 5 | import numpy as np 6 | 7 | import astropy.units as u 8 | from astropy.time import Time 9 | 10 | from sunpy.net import attrs as a 11 | 12 | from radiospectra.spectrogram import Spectrogram 13 | from radiospectra.spectrogram.sources import RSTNSpectrogram 14 | 15 | 16 | @mock.patch("radiospectra.spectrogram.spectrogram_factory.parse_path") 17 | def test_rstn(parse_path_moc): 18 | start_time = Time("2020-01-01T06:17:38.000") 19 | end_time = Time("2020-01-01T15:27:43.000") 20 | meta = { 21 | "instrument": "RSTN", 22 | "observatory": "San Vito", 23 | "start_time": start_time, 24 | "end_time": end_time, 25 | "detector": "RSTN", 26 | "wavelength": a.Wavelength(25000.0 * u.kHz, 180000.0 * u.kHz), 27 | "freqs": np.concatenate([np.linspace(25, 75, 401), np.linspace(75, 180, 401)]) * u.MHz, 28 | "times": start_time + np.linspace(0, (end_time - start_time).to_value("s"), 11003) * u.s, 29 | } 30 | array = np.zeros((802, 11003)) 31 | parse_path_moc.return_value = [(array, meta)] 32 | file = Path("fakes.srs") 33 | spec = Spectrogram(file) 34 | assert isinstance(spec, RSTNSpectrogram) 35 | assert spec.observatory == "SAN VITO" 36 | assert spec.instrument == "RSTN" 37 | assert spec.detector == "RSTN" 38 | assert spec.start_time.datetime == datetime(2020, 1, 1, 6, 17, 38) 39 | assert spec.end_time.datetime == datetime(2020, 1, 1, 15, 27, 43) 40 | assert spec.wavelength.min == 25000 * u.kHz 41 | assert spec.wavelength.max == 180000 * u.kHz 42 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/psp_rfs.py: -------------------------------------------------------------------------------- 1 | from radiospectra.spectrogram.spectrogrambase import GenericSpectrogram 2 | 3 | __all__ = ["RFSSpectrogram"] 4 | 5 | 6 | class RFSSpectrogram(GenericSpectrogram): 7 | """ 8 | Parker Solar Probe FIELDS/Radio Frequency Spectrometer (RFS) Spectrogram. 9 | 10 | >>> import radiospectra.net 11 | >>> from sunpy.net import Fido, attrs as a 12 | >>> from radiospectra.spectrogram import Spectrogram 13 | >>> from radiospectra.net import attrs as ra 14 | >>> query = Fido.search(a.Time('2019/10/05 23:00', '2019/10/06 00:59'), #doctest: +REMOTE_DATA 15 | ... a.Instrument.rfs) #doctest: +REMOTE_DATA 16 | >>> downloaded = Fido.fetch(query[0][0]) #doctest: +REMOTE_DATA 17 | >>> spec = Spectrogram(downloaded[0]) #doctest: +REMOTE_DATA 18 | >>> spec #doctest: +REMOTE_DATA 19 | 20 | >>> spec.plot() #doctest: +REMOTE_DATA 21 | 22 | """ 23 | 24 | def __init__(self, data, meta, **kwargs): 25 | super().__init__(meta=meta, data=data, **kwargs) 26 | 27 | @property 28 | def level(self): 29 | return self.meta["cdf_meta"]["Data_type"].split(">")[0] 30 | 31 | @property 32 | def version(self): 33 | return int(self.meta["cdf_meta"]["Data_version"]) 34 | 35 | @classmethod 36 | def is_datasource_for(cls, data, meta, **kwargs): 37 | return ( 38 | meta["observatory"] == "PSP" and meta["instrument"] == "FIELDS/RFS" and meta["detector"] in ("lfr", "hfr") 39 | ) 40 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/waves.py: -------------------------------------------------------------------------------- 1 | from radiospectra.spectrogram.spectrogrambase import GenericSpectrogram 2 | 3 | __all__ = ["WAVESSpectrogram"] 4 | 5 | 6 | class WAVESSpectrogram(GenericSpectrogram): 7 | """ 8 | Wind Waves Spectrogram. 9 | 10 | Examples 11 | -------- 12 | >>> import radiospectra.net #doctest: +SKIP 13 | >>> from sunpy.net import Fido, attrs as a #doctest: +SKIP 14 | >>> from radiospectra.spectrogram import Spectrogram #doctest: +SKIP 15 | >>> from radiospectra.net import attrs as ra #doctest: +SKIP 16 | >>> query = Fido.search(a.Time('2019/10/05 23:00', '2019/10/06 00:59'), #doctest: +REMOTE_DATA +SKIP 17 | ... a.Instrument('WAVES')) #doctest: +REMOTE_DATA +SKIP 18 | >>> downloaded = Fido.fetch(query[0][0]) #doctest: +REMOTE_DATA +SKIP 19 | >>> spec = Spectrogram(downloaded[0]) #doctest: +REMOTE_DATA +SKIP 20 | >>> spec #doctest: +REMOTE_DATA +SKIP 21 | 22 | >>> spec.plot() #doctest: +REMOTE_DATA +SKIP 23 | 24 | """ 25 | 26 | def __init__(self, data, meta, **kwargs): 27 | super().__init__(meta=meta, data=data, **kwargs) 28 | 29 | @property 30 | def receiver(self): 31 | """ 32 | The name of the receiver. 33 | """ 34 | return self.meta["receiver"] 35 | 36 | @property 37 | def background(self): 38 | """ 39 | The background subtracted from the data. 40 | """ 41 | return self.meta.bg 42 | 43 | @classmethod 44 | def is_datasource_for(cls, data, meta, **kwargs): 45 | return meta.get("instrument", None) == "WAVES" 46 | -------------------------------------------------------------------------------- /licenses/TEMPLATE_LICENSE.rst: -------------------------------------------------------------------------------- 1 | This project is based upon the OpenAstronomy package template 2 | (https://github.com/OpenAstronomy/package-template/) which is licensed under the terms 3 | of the following licence. 4 | 5 | --- 6 | 7 | Copyright (c) 2018, OpenAstronomy Developers 8 | All rights reserved. 9 | 10 | Redistribution and use in source and binary forms, with or without modification, 11 | are permitted provided that the following conditions are met: 12 | 13 | * Redistributions of source code must retain the above copyright notice, this 14 | list of conditions and the following disclaimer. 15 | * Redistributions in binary form must reproduce the above copyright notice, this 16 | list of conditions and the following disclaimer in the documentation and/or 17 | other materials provided with the distribution. 18 | * Neither the name of the Astropy Team nor the names of its contributors may be 19 | used to endorse or promote products derived from this software without 20 | specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 29 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /.ruff.toml: -------------------------------------------------------------------------------- 1 | target-version = "py310" 2 | line-length = 120 3 | exclude = [ 4 | ".git,", 5 | "__pycache__", 6 | "build", 7 | "radiospectra/version.py", 8 | ] 9 | 10 | [lint] 11 | select = [ 12 | "E", 13 | "F", 14 | "W", 15 | "UP", 16 | "PT", 17 | ] 18 | extend-ignore = [ 19 | # pycodestyle (E, W) 20 | "E501", # ignore line length will use a formatter instead 21 | # pytest (PT) 22 | "PT001", # Always use pytest.fixture() 23 | "PT023", # Always use () on pytest decorators 24 | # flake8-pie (PIE) 25 | "PIE808", # Disallow passing 0 as the first argument to range 26 | # flake8-use-pathlib (PTH) 27 | "PTH123", # open() should be replaced by Path.open() 28 | # Ruff (RUF) 29 | "RUF003", # Ignore ambiguous quote marks, doesn't allow ' in comments 30 | "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` 31 | "RUF013", # PEP 484 prohibits implicit `Optional` 32 | "RUF015", # Prefer `next(iter(...))` over single element slice 33 | ] 34 | 35 | [lint.per-file-ignores] 36 | "setup.py" = [ 37 | "INP001", # File is part of an implicit namespace package. 38 | ] 39 | "conftest.py" = [ 40 | "INP001", # File is part of an implicit namespace package. 41 | ] 42 | "docs/conf.py" = [ 43 | "E402" # Module imports not at top of file 44 | ] 45 | "docs/*.py" = [ 46 | "INP001", # File is part of an implicit namespace package. 47 | ] 48 | "examples/**.py" = [ 49 | "T201", # allow use of print in examples 50 | "INP001", # File is part of an implicit namespace package. 51 | ] 52 | "__init__.py" = [ 53 | "E402", # Module level import not at top of cell 54 | "F401", # Unused import 55 | "F403", # from {name} import * used; unable to detect undefined names 56 | "F405", # {name} may be undefined, or defined from star imports 57 | ] 58 | "test_*.py" = [ 59 | "E402", # Module level import not at top of cell 60 | ] 61 | 62 | [lint.pydocstyle] 63 | convention = "numpy" 64 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/callisto.py: -------------------------------------------------------------------------------- 1 | import astropy.units as u 2 | from astropy.coordinates.earth import EarthLocation 3 | 4 | from radiospectra.spectrogram.spectrogrambase import GenericSpectrogram 5 | 6 | __all__ = ["CALISTOSpectrogram"] 7 | 8 | 9 | class CALISTOSpectrogram(GenericSpectrogram): 10 | """ 11 | CALISTO Spectrogram from the e-CALISTO network. 12 | 13 | Examples 14 | -------- 15 | >>> import radiospectra.net 16 | >>> from sunpy.net import Fido, attrs as a 17 | >>> from radiospectra.spectrogram import Spectrogram 18 | >>> from radiospectra.net import attrs as ra 19 | >>> query = Fido.search(a.Time('2019/10/05 23:00', '2019/10/06 00:59'), #doctest: +REMOTE_DATA 20 | ... a.Instrument('eCALLISTO'), ra.Observatory('ALASKA')) #doctest: +REMOTE_DATA 21 | >>> downloaded = Fido.fetch(query[0][0]) #doctest: +REMOTE_DATA 22 | >>> spec = Spectrogram(downloaded[0]) #doctest: +REMOTE_DATA 23 | >>> spec #doctest: +REMOTE_DATA 24 | 25 | >>> spec.plot() #doctest: +REMOTE_DATA 26 | 27 | """ 28 | 29 | def __init__(self, data, meta, **kwargs): 30 | super().__init__(meta=meta, data=data, **kwargs) 31 | 32 | @property 33 | def observatory_location(self): 34 | lat = self.meta["fits_meta"]["OBS_LAT"] * u.deg * 1.0 if self.meta["fits_meta"]["OBS_LAC"] == "N" else -1.0 35 | lon = self.meta["fits_meta"]["OBS_LON"] * u.deg * 1.0 if self.meta["fits_meta"]["OBS_LOC"] == "E" else -1.0 36 | height = self.meta["fits_meta"]["OBS_ALT"] * u.m 37 | return EarthLocation(lat=lat, lon=lon, height=height) 38 | 39 | @classmethod 40 | def is_datasource_for(cls, data, meta, **kwargs): 41 | return meta["instrument"] == "e-CALLISTO" or meta["detector"] == "e-CALLISTO" 42 | -------------------------------------------------------------------------------- /changelog/README.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Changelog 3 | ========= 4 | 5 | .. note:: 6 | 7 | This README was adapted from the pytest changelog readme under the terms of the MIT licence. 8 | 9 | This directory contains "news fragments" which are short files that contain a small **ReST**-formatted text that will be added to the next ``CHANGELOG``. 10 | 11 | The ``CHANGELOG`` will be read by users, so this description should be aimed at SunPy users instead of describing internal changes which are only relevant to the developers. 12 | 13 | Make sure to use full sentences with correct case and punctuation, for example:: 14 | 15 | Add support for Helioprojective coordinates in `sunpy.coordinates.frames`. 16 | 17 | Please try to use Sphinx intersphinx using backticks. 18 | 19 | Each file should be named like ``.[.].rst``, where ```` is a pull request number, ``COUNTER`` is an optional number if a PR needs multiple entries with the same type and ```` is one of: 20 | 21 | * ``breaking``: A change which requires users to change code and is not backwards compatible. (Not to be used for removal of deprecated features.) 22 | * ``feature``: New user facing features and any new behavior. 23 | * ``bugfix``: Fixes a reported bug. 24 | * ``doc``: Documentation addition or improvement, like rewording an entire session or adding missing docs. 25 | * ``docfix``: Correction to existing documentation, such as fixing a typo or adding a missing input parameter. 26 | * ``removal``: Feature deprecation and/or feature removal. 27 | * ``trivial``: A change which has no user facing effect or is tiny change. 28 | 29 | So for example: ``123.feature.rst``, ``456.bugfix.rst``. 30 | 31 | If you are unsure what pull request type to use, don't hesitate to ask in your PR. 32 | 33 | Note that the ``towncrier`` tool will automatically reflow your text, so it will work best if you stick to a single paragraph, but multiple sentences and links are OK and encouraged. 34 | You can install ``towncrier`` and then run ``towncrier --draft`` if you want to get a preview of how your change will look in the final release notes. 35 | -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/test_eovsa_client.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from unittest import mock 3 | 4 | import numpy as np 5 | import pytest 6 | 7 | from sunpy.net import attrs as a 8 | from sunpy.net.fido_factory import Fido 9 | 10 | from radiospectra.net.attrs import PolType 11 | from radiospectra.net.sources.eovsa import EOVSAClient 12 | 13 | MOCK_PATH = "sunpy.net.scraper.urlopen" 14 | 15 | 16 | @pytest.fixture 17 | def client(): 18 | return EOVSAClient() 19 | 20 | 21 | @pytest.fixture 22 | def http_responses(): 23 | paths = [Path(__file__).parent / "data" / n for n in ["eovsa_resp1.html", "eovsa_resp2.html"]] 24 | response_htmls = [] 25 | for p in paths: 26 | with p.open("r") as f: 27 | response_htmls.append(f.read()) 28 | 29 | return response_htmls 30 | 31 | 32 | @mock.patch(MOCK_PATH) 33 | def test_client(urlopen, client, http_responses): 34 | urlopen.return_value.read = mock.MagicMock() 35 | urlopen.return_value.read.side_effect = http_responses 36 | urlopen.close = mock.MagicMock(return_value=None) 37 | query = client.search(a.Time("2020/10/05 00:00", "2020/10/06 23:00"), a.Instrument("EOVSA")) 38 | assert urlopen.call_count == 2 39 | # last call should be for 2020/10/06 40 | assert urlopen.call_args[0][0] == "https://ovsa.njit.edu/fits/synoptic/2020/10/06/" 41 | assert len(query) == 4 42 | 43 | 44 | @mock.patch(MOCK_PATH) 45 | def test_client_observatory(urlopen, client, http_responses): 46 | urlopen.return_value.read = mock.MagicMock() 47 | urlopen.return_value.read.side_effect = http_responses 48 | urlopen.close = mock.MagicMock(return_value=None) 49 | query = client.search(a.Time("2020/10/05 00:00", "2020/10/06 00:00"), a.Instrument("EOVSA"), PolType.cross) 50 | assert urlopen.call_count == 2 51 | # last call should be for 2020/10/06 52 | assert urlopen.call_args[0][0] == "https://ovsa.njit.edu/fits/synoptic/2020/10/06/" 53 | assert len(query) == 2 54 | assert np.all(query["PolType"] == "Cross") 55 | 56 | 57 | @pytest.mark.remote_data 58 | def test_fido(): 59 | query = Fido.search(a.Time("2020/10/05 00:00", "2020/10/06 00:00"), a.Instrument("EOVSA"), PolType.cross) 60 | assert len(query[0]) == 2 61 | assert np.all(query[0]["PolType"] == "Cross") 62 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ************************** 2 | radiospectra Documentation 3 | ************************** 4 | 5 | ``radiospectra`` is a Python software package that provides support for some type of radio spectra in solar physics. 6 | 7 | .. warning:: 8 | 9 | ``radiospectra`` is currently undergoing a transition. 10 | We have replaced the old spectragram class with a new one which is lacking in some features. 11 | 12 | We also have to decide on the fututre of the ``radiospectra`` package. 13 | This package does not see heavy development and is not used by many people. 14 | It is also not clear what the future of radio spectra within the SunPy Project is going to be. 15 | In addition, ``xarray`` or similar packages could offer a better data handling solution than the current ``radiospectra`` package. 16 | We need and want users (of radio data in general) to chime in and to help us decide the future of this package. 17 | 18 | Installation 19 | ============ 20 | The recommended way to install ``radiospectra`` is with `miniforge `__. 21 | To install ``radiospectra`` once miniforge is installed run the following command: 22 | 23 | .. code:: bash 24 | 25 | $ conda install radiospectra 26 | 27 | For detailed installation instructions, see the `installation guide `__ in the ``sunpy`` docs. 28 | 29 | Getting Help 30 | ============ 31 | For more information or to ask questions about ``radiospectra`` or any other SunPy library, check out: 32 | 33 | - `radiospectra documentation `__ 34 | - `SunPy Chat`_ 35 | - `SunPy mailing list `__ 36 | 37 | Contributing 38 | ============ 39 | If you would like to get involved, start by joining the `SunPy Chat`_ and check out our `Newcomers' guide `__. 40 | This will walk you through getting set up for contributing. 41 | 42 | Code of Conduct 43 | =============== 44 | 45 | When you are interacting with the SunPy community you are asked to follow our `Code of Conduct `__. 46 | 47 | .. _SunPy Chat: https://openastronomy.element.io/#/room/#sunpy:openastronomy.org 48 | 49 | .. toctree:: 50 | :maxdepth: 2 51 | :caption: Contents: 52 | 53 | overview 54 | code_ref/index 55 | whatsnew/index 56 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/tests/test_waves.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from datetime import datetime 3 | from unittest import mock 4 | 5 | import numpy as np 6 | 7 | import astropy.units as u 8 | from astropy.time import Time 9 | 10 | from sunpy.net import attrs as a 11 | 12 | from radiospectra.spectrogram import Spectrogram 13 | from radiospectra.spectrogram.sources import WAVESSpectrogram 14 | 15 | 16 | @mock.patch("radiospectra.spectrogram.spectrogram_factory.parse_path") 17 | def test_waves_rad1(parse_path_moc): 18 | meta = { 19 | "instrument": "WAVES", 20 | "observatory": "wind", 21 | "start_time": Time("2020-11-28 00:00:00"), 22 | "end_time": Time("2020-11-28 23:59:00"), 23 | "wavelength": a.Wavelength(20 * u.kHz, 1040 * u.kHz), 24 | "detector": "rad1", 25 | "freqs": np.linspace(20, 1040, 256) * u.kHz, 26 | "times": np.arange(1440) * u.min, 27 | } 28 | array = np.zeros((256, 1440)) 29 | parse_path_moc.return_value = [(array, meta)] 30 | file = Path("fake.r1") 31 | spec = Spectrogram(file) 32 | assert isinstance(spec, WAVESSpectrogram) 33 | assert spec.observatory == "WIND" 34 | assert spec.instrument == "WAVES" 35 | assert spec.detector == "RAD1" 36 | assert spec.start_time.datetime == datetime(2020, 11, 28, 0, 0) 37 | assert spec.end_time.datetime == datetime(2020, 11, 28, 23, 59) 38 | assert spec.wavelength.min == 20.0 * u.kHz 39 | assert spec.wavelength.max == 1040.0 * u.kHz 40 | 41 | 42 | @mock.patch("radiospectra.spectrogram.spectrogram_factory.parse_path") 43 | def test_waves_rad2(parse_path_moc): 44 | meta = { 45 | "instrument": "WAVES", 46 | "observatory": "WIND", 47 | "start_time": Time("2020-11-28 00:00:00"), 48 | "end_time": Time("2020-11-28 23:59:00"), 49 | "wavelength": a.Wavelength(1.075 * u.MHz, 13.825 * u.MHz), 50 | "detector": "RAD2", 51 | "freqs": np.linspace(1.075, 13.825, 256) * u.MHz, 52 | "times": np.arange(1440) * u.min, 53 | } 54 | array = np.zeros((319, 1440)) 55 | parse_path_moc.return_value = [(array, meta)] 56 | file = Path("fake.dat") 57 | spec = Spectrogram(file) 58 | assert isinstance(spec, WAVESSpectrogram) 59 | assert spec.observatory == "WIND" 60 | assert spec.instrument == "WAVES" 61 | assert spec.detector == "RAD2" 62 | assert spec.start_time.datetime == datetime(2020, 11, 28, 0, 0) 63 | assert spec.end_time.datetime == datetime(2020, 11, 28, 23, 59) 64 | assert spec.wavelength.min == 1.075 * u.MHz 65 | assert spec.wavelength.max == 13.825 * u.MHz 66 | -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/test_rstn_client.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from unittest import mock 3 | 4 | import pytest 5 | 6 | from sunpy.net import attrs as a 7 | from sunpy.net.fido_factory import Fido 8 | 9 | from radiospectra.net.attrs import Observatory 10 | from radiospectra.net.sources.rstn import RSTNClient 11 | 12 | MOCK_PATH = "sunpy.net.scraper.urlopen" 13 | 14 | 15 | @pytest.fixture 16 | def client(): 17 | return RSTNClient() 18 | 19 | 20 | @pytest.fixture 21 | def http_responses(): 22 | paths = [ 23 | Path(__file__).parent / "data" / n for n in ["rstn_holloman.html", "rstn_learmonth.html", "rstn_san-vito.html"] 24 | ] 25 | response_htmls = [] 26 | for p in paths: 27 | with p.open("r") as f: 28 | response_htmls.append(f.read()) 29 | 30 | # For the chosen test dates there was no data form Palehua or Sagamore so insert two empty 31 | # responses in there place 32 | response_htmls = response_htmls[:2] + ["", ""] + [response_htmls[-1]] 33 | return response_htmls 34 | 35 | 36 | @mock.patch(MOCK_PATH) 37 | def test_client(urlopen, client, http_responses): 38 | urlopen.return_value.read = mock.MagicMock() 39 | urlopen.return_value.read.side_effect = http_responses 40 | urlopen.close = mock.MagicMock(return_value=None) 41 | query = client.search(a.Time("2003/03/15 00:00", "2003/03/15 23:59"), a.Instrument("RSTN")) 42 | assert urlopen.call_count == 5 43 | # last call arg should be san-vito url 44 | assert ( 45 | urlopen.call_args[0][0] == "https://www.ngdc.noaa.gov/stp/space-weather/solar-data/" 46 | "solar-features/solar-radio/rstn-spectral/san-vito/2003/03/" 47 | ) 48 | assert len(query) == 3 49 | 50 | 51 | @mock.patch(MOCK_PATH) 52 | def test_client_observatory(urlopen, client, http_responses): 53 | urlopen.return_value.read = mock.MagicMock() 54 | urlopen.return_value.read.side_effect = http_responses[-1:] 55 | urlopen.close = mock.MagicMock(return_value=None) 56 | query = client.search(a.Time("2003/03/15 00:00", "2003/03/15 23:59"), a.Instrument("RSTN"), Observatory("San Vito")) 57 | urlopen.assert_called_once_with( 58 | "https://www.ngdc.noaa.gov/stp/space-weather/solar-data/" 59 | "solar-features/solar-radio/rstn-spectral/san-vito/2003/03/" 60 | ) 61 | assert len(query) == 1 62 | assert query["Observatory"] == "San Vito" 63 | 64 | 65 | @pytest.mark.remote_data 66 | def test_fido(): 67 | query = Fido.search(a.Time("2003/03/15 00:00", "2003/03/15 23:59"), a.Instrument("RSTN"), Observatory("San Vito")) 68 | assert len(query[0]) == 1 69 | assert all(query[0]["Observatory"] == "San Vito") 70 | -------------------------------------------------------------------------------- /radiospectra/net/sources/eovsa.py: -------------------------------------------------------------------------------- 1 | from sunpy.net import attrs as a 2 | from sunpy.net.dataretriever.client import GenericClient 3 | 4 | from radiospectra.net.attrs import PolType 5 | 6 | 7 | class EOVSAClient(GenericClient): 8 | """ 9 | Provides access to `Extended Owens Valley Solar Array `__ (EOVSA) data. 10 | 11 | Examples 12 | -------- 13 | >>> from radiospectra import net 14 | >>> from sunpy.net import Fido, attrs as a 15 | >>> query = Fido.search(a.Time('2020/10/05 00:00', '2020/10/06 00:00'), 16 | ... a.Instrument('EOVSA'), net.PolType.cross) #doctest: +REMOTE_DATA 17 | >>> query #doctest: +REMOTE_DATA 18 | 19 | Results from 1 Provider: 20 | 21 | 2 Results from the EOVSAClient: 22 | Start Time End Time Provider Instrument PolType 23 | ----------------------- ----------------------- -------- ---------- ------- 24 | 2020-10-05 00:00:00.000 2020-10-05 23:59:59.999 EOVSA EOVSA Cross 25 | 2020-10-06 00:00:00.000 2020-10-06 23:59:59.999 EOVSA EOVSA Cross 26 | 27 | 28 | """ 29 | from sunpy import __version__ 30 | if __version__ >= "6.1.0": 31 | pattern = ("https://ovsa.njit.edu/fits/synoptic/{{year:4d}}/{{month:2d}}/{{day:2d}}/" 32 | "EOVSA_{{PolType:5l}}_{{year:4d}}{{month:2d}}{{day:2d}}.fts") 33 | else: 34 | baseurl = "https://ovsa.njit.edu/fits/synoptic/%Y/%m/%d/EOVSA_.*_%Y%m%d.fts" 35 | pattern = "{}/synoptic/{year:4d}/{month:2d}/{day:2d}/EOVSA_{PolType:5l}_{year:4d}{month:2d}{day:2d}.fts" 36 | pol_map = {"Total": "TPall", "Cross": "XPall", "TPall": "Total", "XPall": "Cross"} 37 | 38 | @classmethod 39 | def pre_search_hook(cls, *args, **kwargs): 40 | baseurl, pattern, matchdict = super().pre_search_hook(*args, **kwargs) 41 | pol_values = [cls.pol_map[p.capitalize()] for p in matchdict["PolType"]] 42 | matchdict["PolType"] = pol_values 43 | return baseurl, pattern, matchdict 44 | 45 | def post_search_hook(self, exdict, matchdict): 46 | original = super().post_search_hook(exdict, matchdict) 47 | original["PolType"] = self.pol_map[original["PolType"]] 48 | return original 49 | 50 | @classmethod 51 | def register_values(cls): 52 | adict = { 53 | a.Provider: [("EOVSA", "EOVSA")], 54 | a.Instrument: [("EOVSA", "ExtendedOwens Valley Solar Array.")], 55 | PolType: [("Total", "Total polarisation"), ("Cross", "Cross polarisation")], 56 | } 57 | return adict 58 | -------------------------------------------------------------------------------- /radiospectra/mixins.py: -------------------------------------------------------------------------------- 1 | class PcolormeshPlotMixin: 2 | """ 3 | Class provides plotting functions using `~pcolormesh`. 4 | """ 5 | 6 | def plot(self, axes=None, **kwargs): 7 | """ 8 | Plot the spectrogram. 9 | 10 | Parameters 11 | ---------- 12 | axes : `matplotlib.axis.Axes`, optional 13 | The axes where the plot will be added. 14 | kwargs : 15 | Arguments pass to the plot call `pcolormesh`. 16 | 17 | Returns 18 | ------- 19 | `matplotlib.collections.QuadMesh` 20 | """ 21 | import matplotlib.dates as mdates 22 | from matplotlib import pyplot as plt 23 | 24 | if axes is None: 25 | fig, axes = plt.subplots() 26 | else: 27 | fig = axes.get_figure() 28 | 29 | if hasattr(self.data, "value"): 30 | data = self.data.value 31 | else: 32 | data = self.data 33 | 34 | title = f"{self.observatory}, {self.instrument}" 35 | if self.instrument != self.detector: 36 | title = f"{title}, {self.detector}" 37 | 38 | axes.set_title(title) 39 | axes.plot(self.times.datetime[[0, -1]], self.frequencies[[0, -1]], linestyle="None", marker="None") 40 | if self.times.shape[0] == self.data.shape[0] and self.frequencies.shape[0] == self.data.shape[1]: 41 | ret = axes.pcolormesh(self.times.datetime, self.frequencies.value, data, shading="auto", **kwargs) 42 | else: 43 | ret = axes.pcolormesh(self.times.datetime, self.frequencies.value, data[:-1, :-1], shading="auto", **kwargs) 44 | axes.set_xlim(self.times.datetime[0], self.times.datetime[-1]) 45 | locator = mdates.AutoDateLocator(minticks=4, maxticks=8) 46 | formatter = mdates.ConciseDateFormatter(locator) 47 | axes.xaxis.set_major_locator(locator) 48 | axes.xaxis.set_major_formatter(formatter) 49 | fig.autofmt_xdate() 50 | # Set current axes/image if pyplot is being used (makes colorbar work) 51 | for i in plt.get_fignums(): 52 | if axes in plt.figure(i).axes: 53 | plt.sca(axes) 54 | plt.sci(ret) 55 | return ret 56 | 57 | 58 | class NonUniformImagePlotMixin: 59 | """ 60 | Class provides plotting functions using `NonUniformImage`. 61 | """ 62 | 63 | def plotim(self, fig=None, axes=None, **kwargs): 64 | import matplotlib.dates as mdates 65 | from matplotlib import pyplot as plt 66 | from matplotlib.image import NonUniformImage 67 | 68 | if axes is None: 69 | fig, axes = plt.subplots() 70 | 71 | im = NonUniformImage(axes, interpolation="none", **kwargs) 72 | im.set_data(mdates.date2num(self.times.datetime), self.frequencies.value, self.data) 73 | axes.images.append(im) 74 | -------------------------------------------------------------------------------- /radiospectra/spectrum.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | __all__ = ["Spectrum"] 4 | 5 | 6 | class Spectrum(np.ndarray): 7 | """ 8 | Class representing a 1 dimensional spectrum. 9 | 10 | Attributes 11 | ---------- 12 | freq_axis : `~numpy.ndarray` 13 | One-dimensional array with the frequency values. 14 | data : `~numpy.ndarray` 15 | One-dimensional array which the intensity at a particular frequency at every data-point. 16 | 17 | Examples 18 | -------- 19 | >>> from radiospectra.spectrum import Spectrum 20 | >>> import numpy as np 21 | >>> data = np.linspace(1, 100, 100) 22 | >>> freq_axis = np.linspace(0, 10, 100) 23 | >>> spec = Spectrum(data, freq_axis) 24 | >>> spec.peek() # doctest: +SKIP 25 | """ 26 | 27 | def __new__(cls, data, *args, **kwargs): 28 | return np.asarray(data).view(cls) 29 | 30 | def __init__(self, data, freq_axis): 31 | if np.shape(data)[0] != np.shape(freq_axis)[0]: 32 | raise ValueError("Dimensions of data and frequency axis do not match") 33 | self.freq_axis = freq_axis 34 | 35 | def plot(self, axes=None, **matplot_args): 36 | """ 37 | Plot spectrum onto current axes. 38 | 39 | Parameters 40 | ---------- 41 | axes : `~matplotlib.axes.Axes` or `None` 42 | If provided the spectrum will be plotted on the given axes. 43 | Else the current `matplotlib` axes will be used. 44 | **matplot_args : dict 45 | Any additional plot arguments that should be used 46 | when plotting. 47 | 48 | Returns 49 | ------- 50 | `~matplotlib.axes.Axes` 51 | The plot axes. 52 | """ 53 | from matplotlib import pyplot as plt 54 | 55 | # Get current axes 56 | if not axes: 57 | axes = plt.gca() 58 | params = {} 59 | params.update(matplot_args) 60 | lines = axes.plot(self.freq_axis, self, **params) 61 | return lines 62 | 63 | def peek(self, **matplot_args): 64 | """ 65 | Plot spectrum onto a new figure. 66 | Parameters 67 | ---------- 68 | **matplot_args : dict 69 | Any additional plot arguments that should be used when plotting. 70 | 71 | Returns 72 | ------- 73 | `~matplotlib.Figure` 74 | A plot figure. 75 | 76 | Examples 77 | -------- 78 | >>> from radiospectra.spectrum import Spectrum 79 | >>> import numpy as np 80 | >>> spec = Spectrum(np.linspace(1, 100, 100), np.linspace(0, 10, 100)) 81 | >>> spec.peek() # doctest: +SKIP 82 | """ 83 | from matplotlib import pyplot as plt 84 | 85 | figure = plt.figure() 86 | self.plot(**matplot_args) 87 | figure.show() 88 | return figure 89 | -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/test_ecallisto_client.py: -------------------------------------------------------------------------------- 1 | import gzip 2 | from pathlib import Path 3 | from unittest import mock 4 | 5 | import pytest 6 | 7 | from sunpy.net import attrs as a 8 | from sunpy.net.fido_factory import Fido 9 | 10 | from radiospectra.net.sources.ecallisto import Observatory, eCALLISTOClient 11 | 12 | MOCK_PATH = "sunpy.net.scraper.urlopen" 13 | 14 | 15 | @pytest.fixture 16 | def client(): 17 | return eCALLISTOClient() 18 | 19 | 20 | @pytest.fixture 21 | def http_responses(): 22 | paths = [Path(__file__).parent / "data" / n for n in ["ecallisto_resp1.html.gz", "ecallisto_resp2.html.gz"]] 23 | response_htmls = [] 24 | for p in paths: 25 | with gzip.open(p) as f: 26 | response_htmls.append(f.read()) 27 | return response_htmls 28 | 29 | 30 | @pytest.fixture 31 | def http_response_alt(): 32 | path = Path(__file__).parent / "data" / "ecallisto_resp_alt_format.html" 33 | with path.open("r") as file: 34 | response_html = file.read() 35 | return response_html 36 | 37 | 38 | @mock.patch(MOCK_PATH) 39 | def test_client(urlopen, client, http_responses): 40 | urlopen.return_value.read = mock.MagicMock() 41 | urlopen.return_value.read.side_effect = http_responses 42 | urlopen.close = mock.MagicMock(return_value=None) 43 | query = client.search(a.Time("2019/10/05 23:00", "2019/10/06 00:59"), a.Instrument("eCALLISTO")) 44 | assert urlopen.call_count == 2 45 | # 2nd call 46 | urlopen.assert_called_with("http://soleil80.cs.technik.fhnw.ch/solarradio/data/2002-20yy_Callisto/2019/10/06/") 47 | assert len(query) == 156 48 | 49 | 50 | @mock.patch(MOCK_PATH) 51 | def test_client_alt_format(urlopen, client, http_response_alt): 52 | urlopen.return_value.read = mock.MagicMock() 53 | urlopen.return_value.read.return_value = http_response_alt 54 | urlopen.close = mock.MagicMock(return_value=None) 55 | query = client.search( 56 | a.Time("2010/03/27 00:00", "2010/03/27 23:59"), a.Instrument("eCALLISTO"), Observatory("PHOENIX3-B1") 57 | ) 58 | assert len(query) == 24 59 | 60 | 61 | @mock.patch(MOCK_PATH) 62 | def test_client_with_observatory(urlopen, client, http_responses): 63 | urlopen.return_value.read = mock.MagicMock() 64 | urlopen.return_value.read.side_effect = http_responses 65 | urlopen.close = mock.MagicMock(return_value=None) 66 | query = client.search( 67 | a.Time("2019/10/05 23:00", "2019/10/06 00:59"), a.Instrument("eCALLISTO"), Observatory("ALASKA") 68 | ) 69 | assert urlopen.call_count == 2 70 | # 2nd call 71 | urlopen.assert_called_with("http://soleil80.cs.technik.fhnw.ch/solarradio/data/2002-20yy_Callisto/2019/10/06/") 72 | assert len(query) == 8 73 | 74 | 75 | @pytest.mark.remote_data 76 | def test_fido(): 77 | query = Fido.search( 78 | a.Time("2019/10/05 23:00", "2019/10/06 00:59"), a.Instrument("eCALLISTO"), Observatory("ALASKA") 79 | ) 80 | assert len(query[0]) == 8 81 | assert all(query[0]["Observatory"] == "ALASKA") 82 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | min_version = 4.0 3 | requires = 4 | tox-pypi-filter>=0.14 5 | envlist = 6 | py{312,313,314} 7 | py314-devdeps 8 | py312-oldestdeps 9 | codestyle 10 | build_docs 11 | 12 | [testenv] 13 | pypi_filter = https://raw.githubusercontent.com/sunpy/sunpy/main/.test_package_pins.txt 14 | # Run the tests in a temporary directory to make sure that we don't import 15 | # the package from the source tree 16 | change_dir = .tmp/{envname} 17 | description = 18 | run tests 19 | oldestdeps: with the oldest supported version of key dependencies 20 | devdeps: with the latest developer version of key dependencies 21 | pass_env = 22 | # A variable to tell tests we are on a CI system 23 | CI 24 | # Custom compiler locations (such as ccache) 25 | CC 26 | # Location of locales (needed by sphinx on some systems) 27 | LOCALE_ARCHIVE 28 | # If the user has set a LC override we should follow it 29 | LC_ALL 30 | set_env = 31 | MPLBACKEND = agg 32 | COLUMNS = 180 33 | PARFIVE_HIDE_PROGRESS = True 34 | devdeps,build_docs,online: HOME = {envtmpdir} 35 | SUNPY_SAMPLEDIR = {env:SUNPY_SAMPLEDIR:{toxinidir}/.tox/{envname}/sample_data/} 36 | devdeps: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/astropy/simple https://pypi.anaconda.org/scientific-python-nightly-wheels/simple 37 | deps = 38 | # For packages which publish nightly wheels this will pull the latest nightly 39 | devdeps: sunpy>=0.0.dev0 40 | devdeps: matplotlib>=0.0.dev0 41 | devdeps: scipy>=0.0.dev0 42 | # Packages without nightly wheels will be built from source like this 43 | # devdeps: git+https://github.com/ndcube/ndcube 44 | oldestdeps: minimum_dependencies 45 | online: pytest-rerunfailures 46 | online: pytest-timeout 47 | # The following indicates which extras_require will be installed 48 | extras = 49 | all 50 | tests 51 | commands_pre = 52 | oldestdeps: minimum_dependencies radiospectra --filename requirements-min.txt 53 | oldestdeps: pip install -r requirements-min.txt 54 | pip freeze --all --no-input 55 | commands = 56 | # To amend the pytest command for different factors you can add a line 57 | # which starts with a factor like `online: --remote-data=any \` 58 | # If you have no factors which require different commands this is all you need: 59 | !online-!hypothesis-!figure: {env:PYTEST_COMMAND} {posargs} 60 | pytest \ 61 | -vvv \ 62 | -r fEs \ 63 | --pyargs radiospectra \ 64 | --cov-report=xml \ 65 | --cov=radiospectra \ 66 | --cov-config={toxinidir}/.coveragerc \ 67 | {toxinidir}/docs \ 68 | {posargs} 69 | 70 | [testenv:codestyle] 71 | pypi_filter = 72 | skip_install = true 73 | description = Run all style and file checks with pre-commit 74 | deps = 75 | pre-commit 76 | commands = 77 | pre-commit install-hooks 78 | pre-commit run --color always --all-files --show-diff-on-failure 79 | 80 | [testenv:build_docs] 81 | description = invoke sphinx-build to build the HTML docs 82 | change_dir = 83 | docs 84 | extras = 85 | docs 86 | commands = 87 | sphinx-build -j auto --color -W --keep-going -b html -d _build/.doctrees . _build/html {posargs} 88 | -------------------------------------------------------------------------------- /radiospectra/net/sources/rstn.py: -------------------------------------------------------------------------------- 1 | from sunpy.net import attrs as a 2 | from sunpy.net.dataretriever.client import GenericClient, QueryResponse 3 | from sunpy.net.scraper import Scraper 4 | from sunpy.time.timerange import TimeRange 5 | 6 | from radiospectra.net.attrs import Observatory 7 | 8 | __all__ = ["RSTNClient"] 9 | 10 | 11 | class RSTNClient(GenericClient): 12 | """ 13 | Radio Spectrometer Telescope Network (RSTN) hosted at NOAA 14 | `National Geophysical Data `__ (NGDC) archive. 15 | 16 | Examples 17 | -------- 18 | >>> from radiospectra import net 19 | >>> from sunpy.net import Fido, attrs as a 20 | >>> query = Fido.search(a.Time('2003/03/15 00:00', '2003/03/15 23:59'), 21 | ... a.Instrument('RSTN'), net.Observatory('San Vito')) #doctest: +REMOTE_DATA 22 | >>> query #doctest: +REMOTE_DATA 23 | 26 | 1 Results from the RSTNClient: 27 | Start Time End Time Provider Instrument Observatory 28 | ----------------------- ----------------------- -------- ---------- ----------- 29 | 2003-03-15 00:00:00.000 2003-03-15 23:59:59.999 RSTN RSTN San Vito 30 | 31 | 32 | """ 33 | 34 | baseurl = ( 35 | r"https://www.ngdc.noaa.gov/stp/space-weather/solar-data/" 36 | r"solar-features/solar-radio/rstn-spectral/{obs}/%Y/%m/.*.gz" 37 | ) 38 | pattern = r"{}/rstn-spectral/{obs}/{year:4d}/{month:2d}/" r"{obs_short:2l}{year2:2d}{month2:2d}{day:2d}.SRS.gz" 39 | 40 | observatory_map = { 41 | "Holloman": "holloman", 42 | "Learmonth": "learmonth", 43 | "Palehua": "palehua", 44 | "Sagamore Hill": "sagamore", 45 | "San Vito": "san-vito", 46 | } 47 | observatory_map = {**observatory_map, **dict(map(reversed, observatory_map.items()))} 48 | 49 | def search(self, *args, **kwargs): 50 | baseurl, pattern, matchdict = self.pre_search_hook(*args, **kwargs) 51 | metalist = [] 52 | for obs in matchdict["Observatory"]: 53 | scraper = Scraper(baseurl.format(obs=self.observatory_map[obs.title()]), regex=True) 54 | tr = TimeRange(matchdict["Start Time"], matchdict["End Time"]) 55 | filesmeta = scraper._extract_files_meta(tr, extractor=pattern, matcher=matchdict) 56 | 57 | for i in filesmeta: 58 | rowdict = self.post_search_hook(i, matchdict) 59 | metalist.append(rowdict) 60 | 61 | return QueryResponse(metalist, client=self) 62 | 63 | def post_search_hook(self, exdict, matchdict): 64 | original = super().post_search_hook(exdict, matchdict) 65 | obs, *_ = (original.pop(name) for name in ["obs", "year2", "month2", "obs_short"]) 66 | original["Observatory"] = self.observatory_map[obs] 67 | return original 68 | 69 | @classmethod 70 | def register_values(cls): 71 | adict = { 72 | a.Provider: [("RSTN", "Radio Solar Telescope Network.")], 73 | a.Instrument: [("RSTN", "Radio Solar Telescope Network.")], 74 | Observatory: [ 75 | ("Holloman", "Holloman"), 76 | ("Learmonth", "Learmonth"), 77 | ("Palehua", "Palehua"), 78 | ("Sagamore Hill", "Sagamore Hill"), 79 | ("San Vito", "San Vito"), 80 | ], 81 | } 82 | return adict 83 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/spectrogrambase.py: -------------------------------------------------------------------------------- 1 | from radiospectra.exceptions import SpectraMetaValidationError 2 | from radiospectra.mixins import NonUniformImagePlotMixin, PcolormeshPlotMixin 3 | 4 | __all__ = ["GenericSpectrogram"] 5 | 6 | 7 | class GenericSpectrogram(PcolormeshPlotMixin, NonUniformImagePlotMixin): 8 | """ 9 | Base spectrogram class all spectrograms inherit. 10 | 11 | Attributes 12 | ---------- 13 | meta : `dict-like` 14 | Meta data for the spectrogram. 15 | data : `numpy.ndarray` 16 | The spectrogram data itself a 2D array. 17 | """ 18 | 19 | _registry = {} 20 | 21 | def __init_subclass__(cls, **kwargs): 22 | super().__init_subclass__(**kwargs) 23 | if hasattr(cls, "is_datasource_for"): 24 | cls._registry[cls] = cls.is_datasource_for 25 | 26 | def __init__(self, data, meta, **kwargs): 27 | self.data = data 28 | self.meta = meta 29 | self._validate_meta() 30 | 31 | @property 32 | def observatory(self): 33 | """ 34 | The name of the observatory which recorded the spectrogram. 35 | """ 36 | return self.meta["observatory"].upper() 37 | 38 | @property 39 | def instrument(self): 40 | """ 41 | The name of the instrument which recorded the spectrogram. 42 | """ 43 | return self.meta["instrument"].upper() 44 | 45 | @property 46 | def detector(self): 47 | """ 48 | The detector which recorded the spectrogram. 49 | """ 50 | return self.meta["detector"].upper() 51 | 52 | @property 53 | def start_time(self): 54 | """ 55 | The start time of the spectrogram. 56 | """ 57 | return self.meta["start_time"] 58 | 59 | @property 60 | def end_time(self): 61 | """ 62 | The end time of the spectrogram. 63 | """ 64 | return self.meta["end_time"] 65 | 66 | @property 67 | def wavelength(self): 68 | """ 69 | The wavelength range of the spectrogram. 70 | """ 71 | return self.meta["wavelength"] 72 | 73 | @property 74 | def times(self): 75 | """ 76 | The times of the spectrogram. 77 | """ 78 | return self.meta["times"] 79 | 80 | @property 81 | def frequencies(self): 82 | """ 83 | The frequencies of the spectrogram. 84 | """ 85 | return self.meta["freqs"] 86 | 87 | def _validate_meta(self): 88 | """ 89 | Validates the meta-information associated with a Spectrogram. 90 | 91 | This method includes very basic validation checks which apply to 92 | all of the kinds of files that radiospectra can read. 93 | Datasource-specific validation should be handled in the relevant 94 | file in the radiospectra.spectrogram.sources. 95 | """ 96 | msg = "Spectrogram coordinate units for {} axis not present in metadata." 97 | err_message = [] 98 | for i, ax in enumerate(["times", "freqs"]): 99 | if self.meta.get(ax) is None: 100 | err_message.append(msg.format(ax)) 101 | if err_message: 102 | raise SpectraMetaValidationError("\n".join(err_message)) 103 | 104 | def __repr__(self): 105 | return ( 106 | f"<{self.__class__.__name__} {self.observatory}, {self.instrument}, {self.detector}" 107 | f" {self.wavelength.min} - {self.wavelength.max}," 108 | f" {self.start_time.isot} to {self.end_time.isot}>" 109 | ) 110 | -------------------------------------------------------------------------------- /.github/workflows/sub_package_update.yml: -------------------------------------------------------------------------------- 1 | # This template is taken from the cruft example code, for further information please see: 2 | # https://cruft.github.io/cruft/#automating-updates-with-github-actions 3 | name: Automatic Update from package template 4 | permissions: 5 | contents: write 6 | pull-requests: write 7 | 8 | on: 9 | # Allow manual runs through the web UI 10 | workflow_dispatch: 11 | schedule: 12 | # ┌───────── minute (0 - 59) 13 | # │ ┌───────── hour (0 - 23) 14 | # │ │ ┌───────── day of the month (1 - 31) 15 | # │ │ │ ┌───────── month (1 - 12 or JAN-DEC) 16 | # │ │ │ │ ┌───────── day of the week (0 - 6 or SUN-SAT) 17 | - cron: '0 7 * * 1' # Every Monday at 7am UTC 18 | 19 | jobs: 20 | update: 21 | runs-on: ubuntu-latest 22 | strategy: 23 | fail-fast: true 24 | steps: 25 | - uses: actions/checkout@v5 26 | 27 | - uses: actions/setup-python@v6 28 | with: 29 | python-version: "3.11" 30 | 31 | - name: Install Cruft 32 | run: python -m pip install git+https://github.com/Cadair/cruft@patch-p1 33 | 34 | - name: Check if update is available 35 | continue-on-error: false 36 | id: check 37 | run: | 38 | CHANGES=0 39 | if [ -f .cruft.json ]; then 40 | if ! cruft check; then 41 | CHANGES=1 42 | fi 43 | else 44 | echo "No .cruft.json file" 45 | fi 46 | 47 | echo "has_changes=$CHANGES" >> "$GITHUB_OUTPUT" 48 | 49 | - name: Run update if available 50 | id: cruft_update 51 | if: steps.check.outputs.has_changes == '1' 52 | run: | 53 | git config --global user.email "${{ github.actor }}@users.noreply.github.com" 54 | git config --global user.name "${{ github.actor }}" 55 | 56 | cruft_output=$(cruft update --skip-apply-ask --refresh-private-variables) 57 | echo $cruft_output 58 | git restore --staged . 59 | 60 | if [[ "$cruft_output" == *"Failed to cleanly apply the update, there may be merge conflicts."* ]]; then 61 | echo merge_conflicts=1 >> $GITHUB_OUTPUT 62 | else 63 | echo merge_conflicts=0 >> $GITHUB_OUTPUT 64 | fi 65 | 66 | - name: Check if only .cruft.json is modified 67 | id: cruft_json 68 | if: steps.check.outputs.has_changes == '1' 69 | run: | 70 | git status --porcelain=1 71 | if [[ "$(git status --porcelain=1)" == " M .cruft.json" ]]; then 72 | echo "Only .cruft.json is modified. Exiting workflow early." 73 | echo "has_changes=0" >> "$GITHUB_OUTPUT" 74 | else 75 | echo "has_changes=1" >> "$GITHUB_OUTPUT" 76 | fi 77 | 78 | - name: Create pull request 79 | if: steps.cruft_json.outputs.has_changes == '1' 80 | uses: peter-evans/create-pull-request@v7 81 | with: 82 | token: ${{ secrets.GITHUB_TOKEN }} 83 | add-paths: "." 84 | commit-message: "Automatic package template update" 85 | branch: "cruft/update" 86 | delete-branch: true 87 | draft: ${{ steps.cruft_update.outputs.merge_conflicts == '1' }} 88 | title: "Updates from the package template" 89 | labels: | 90 | No Changelog Entry Needed 91 | body: | 92 | This is an autogenerated PR, which will applies the latest changes from the [SunPy Package Template](https://github.com/sunpy/package-template). 93 | If this pull request has been opened as a draft there are conflicts which need fixing. 94 | 95 | **To run the CI on this pull request you will need to close it and reopen it.** 96 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | 0.6.0 (2024-07-23) 2 | ================== 3 | 4 | Backwards Incompatible Changes 5 | ------------------------------ 6 | 7 | - Dropped support for Python 3.9 (`#111 `__) 8 | - Increased the minimum required version of ``sunpy`` to v6.0.0. (`#112 `__) 9 | 10 | 11 | 0.5.0 (2024-03-01) 12 | ================== 13 | 14 | Breaking Changes 15 | ---------------- 16 | 17 | - The old ``Spectrogram`` class has been removed. (`#76 `__) 18 | - The new ``Spectrogram2`` class has been renamed to ``Spectrogram``. (`#76 `__) 19 | - Adding colorbar functionality to ``plot`` (`#80 `__) 20 | - Renamed ``CALLISTOClient`` to ``eCallistoClient`` (`#61 `__) 21 | - ``eCallistoClient`` now does not return endtimes. (`#61 `__) 22 | - Removed the ``SWAVESClient`` and ``WAVESClient`` as the old URLS have gone offline. (`#105 `__) 23 | 24 | Features 25 | -------- 26 | - Added support to second ``eCallisto`` file format. (`#61 `__) 27 | - Add support for SOLO RPW data. (`#62 `__) 28 | 29 | - Add `sunpy.net.Fido` client `~radiospectra.net.sources.ilofar.ILOFARMode357` and spectrogram class `~radiospectra.spectrogram2.sources.ILOFARMode357` for ILOFAR mode 357 observations. (`#57 `__) 30 | 31 | Bug Fixes 32 | --------- 33 | 34 | - Fix a bug where incorrectly formatted dates were not handled by the `radiospectra.spectrogram.Spectrogram`. (`#84 `__) 35 | 36 | Trivial/Internal Changes 37 | ------------------------ 38 | 39 | - Moved to Github Actions. (`#105 `__) 40 | 41 | 0.4.0 (2022-05-24) 42 | ================== 43 | 44 | Breaking Changes 45 | ---------------- 46 | 47 | - Minimum supported version of Python is now 3.8 48 | - Minimum supported version of ``sunpy`` is now 4.0.0 (LTS) 49 | 50 | Features 51 | -------- 52 | 53 | - Add a new spectrogram class `radiospectra.spectrogram.spectrogram.BaseSpectrogram` and factory `radiospectra.spectrogram.spectrogram.SpectrogramFactory` with sources for `~radiospectra.spectrogram.sources.SWAVESSpectrogram`, `~radiospectra.spectrogram.sources.RFSSpectrogram`, `~radiospectra.spectrogram.sources.CALISTOSpectrogram`, `~radiospectra.spectrogram.sources.EOVSASpectrogram` and `~radiospectra.spectrogram.sources.RSTNSpectrogram`. (`#44 `__) 54 | - Add `sunpy.net.Fido` clients for `~radiospectra.net.sources.callisto.CALLISTOClient`, `~radiospectra.net.sources.eovsa.EOVSAClient` and `~radiospectra.net.sources.rstn.RSTNClient`. (`#44 `__) 55 | - Improve `~radiospectra.spectrogram.spectrogram.SpectrogramFactory` input handling more inputs formats data header pairs, files, urls. (`#54 `__) 56 | - Add `sunpy.net.Fido` client `~radiospectra.net.sources.wind.Waves` and spectrogram class `~radiospectra.spectrogram.sources.WAVESSpectrogram` for WIND/WAVES. (`#54 `__) 57 | 58 | 0.3.0 (2021-04-01) 59 | ================== 60 | 61 | Features 62 | -------- 63 | 64 | - Add Parker Solar Probe (PSP) Radio Frequency Receiver (RFS) Fido client `radiospectra.net.sources.psp.RFSClient`. (`#34 `__) 65 | - Add STEREO WAVES (SWAVES) Fido client ``radiospectra.net.SWAVESClient``. (`#35 `__) 66 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/tests/test_swaves.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from datetime import datetime 3 | from unittest import mock 4 | 5 | import numpy as np 6 | 7 | import astropy.units as u 8 | from astropy.time import Time 9 | 10 | from sunpy.net import attrs as a 11 | 12 | from radiospectra.spectrogram import Spectrogram 13 | from radiospectra.spectrogram.sources import SWAVESSpectrogram 14 | 15 | 16 | @mock.patch("radiospectra.spectrogram.spectrogram_factory.parse_path") 17 | def test_swaves_lfr(parse_path_moc): 18 | meta = { 19 | "instrument": "swaves", 20 | "observatory": "STEREO A", 21 | "product": "average", 22 | "start_time": Time("2020-11-28 00:00:00"), 23 | "end_time": Time("2020-11-28 23:59:00"), 24 | "wavelength": a.Wavelength(2.6 * u.kHz, 153.4 * u.kHz), 25 | "detector": "lfr", 26 | "freqs": [ 27 | 2.6, 28 | 2.8, 29 | 3.1, 30 | 3.4, 31 | 3.7, 32 | 4.0, 33 | 4.4, 34 | 4.8, 35 | 5.2, 36 | 5.7, 37 | 6.2, 38 | 6.8, 39 | 7.4, 40 | 8.1, 41 | 8.8, 42 | 9.6, 43 | 10.4, 44 | 11.4, 45 | 12.4, 46 | 13.6, 47 | 14.8, 48 | 16.1, 49 | 17.6, 50 | 19.2, 51 | 20.9, 52 | 22.8, 53 | 24.9, 54 | 27.1, 55 | 29.6, 56 | 32.2, 57 | 35.2, 58 | 38.3, 59 | 41.8, 60 | 45.6, 61 | 49.7, 62 | 54.2, 63 | 59.1, 64 | 64.5, 65 | 70.3, 66 | 76.7, 67 | 83.6, 68 | 91.2, 69 | 99.4, 70 | 108.4, 71 | 118.3, 72 | 129.0, 73 | 140.6, 74 | 153.4, 75 | ] 76 | * u.kHz, 77 | "times": np.arange(1440) * u.min, 78 | } 79 | array = np.zeros((48, 1440)) 80 | parse_path_moc.return_value = [(array, meta)] 81 | file = Path("fake.dat") 82 | spec = Spectrogram(file) 83 | assert isinstance(spec, SWAVESSpectrogram) 84 | assert spec.observatory == "STEREO A" 85 | assert spec.instrument == "SWAVES" 86 | assert spec.detector == "LFR" 87 | assert spec.start_time.datetime == datetime(2020, 11, 28, 0, 0) 88 | assert spec.end_time.datetime == datetime(2020, 11, 28, 23, 59) 89 | assert spec.wavelength.min == 2.6 * u.kHz 90 | assert spec.wavelength.max == 153.4 * u.kHz 91 | 92 | 93 | @mock.patch("radiospectra.spectrogram.spectrogram_factory.parse_path") 94 | def test_swaves_hfr(parse_path_moc): 95 | meta = { 96 | "instrument": "swaves", 97 | "observatory": "STEREO A", 98 | "product": "average", 99 | "start_time": Time("2020-11-28 00:00:00"), 100 | "end_time": Time("2020-11-28 23:59:00"), 101 | "wavelength": a.Wavelength(125.0 * u.kHz, 16025.0 * u.kHz), 102 | "detector": "hfr", 103 | "freqs": np.linspace(125, 16025, 319) * u.kHz, 104 | "times": np.arange(1440) * u.min, 105 | } 106 | array = np.zeros((319, 1440)) 107 | parse_path_moc.return_value = [(array, meta)] 108 | file = Path("fake.dat") 109 | spec = Spectrogram(file) 110 | assert isinstance(spec, SWAVESSpectrogram) 111 | assert spec.observatory == "STEREO A" 112 | assert spec.instrument == "SWAVES" 113 | assert spec.detector == "HFR" 114 | assert spec.start_time.datetime == datetime(2020, 11, 28, 0, 0) 115 | assert spec.end_time.datetime == datetime(2020, 11, 28, 23, 59) 116 | assert spec.wavelength.min == 125 * u.kHz 117 | assert spec.wavelength.max == 16025 * u.kHz 118 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Main CI Workflow 2 | name: CI 3 | 4 | on: 5 | push: 6 | branches: 7 | - 'main' 8 | - '*.*' 9 | - '!*backport*' 10 | tags: 11 | - 'v*' 12 | - '!*dev*' 13 | - '!*pre*' 14 | - '!*post*' 15 | pull_request: 16 | # Allow manual runs through the web UI 17 | workflow_dispatch: 18 | schedule: 19 | # ┌───────── minute (0 - 59) 20 | # │ ┌───────── hour (0 - 23) 21 | # │ │ ┌───────── day of the month (1 - 31) 22 | # │ │ │ ┌───────── month (1 - 12 or JAN-DEC) 23 | # │ │ │ │ ┌───────── day of the week (0 - 6 or SUN-SAT) 24 | - cron: '0 7 * * 3' # Every Wed at 07:00 UTC 25 | 26 | concurrency: 27 | group: ${{ github.workflow }}-${{ github.ref }} 28 | cancel-in-progress: true 29 | 30 | jobs: 31 | core: 32 | uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v2 33 | with: 34 | submodules: false 35 | coverage: codecov 36 | toxdeps: tox-pypi-filter 37 | envs: | 38 | - linux: py313 39 | secrets: 40 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 41 | 42 | sdist_verify: 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v5 46 | - uses: actions/setup-python@v6 47 | with: 48 | python-version: '3.13' 49 | - run: python -m pip install -U --user build 50 | - run: python -m build . --sdist 51 | - run: python -m pip install -U --user twine 52 | - run: python -m twine check dist/* 53 | 54 | test: 55 | needs: [core, sdist_verify] 56 | uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v2 57 | with: 58 | submodules: false 59 | coverage: codecov 60 | toxdeps: tox-pypi-filter 61 | posargs: -n auto 62 | envs: | 63 | - linux: py314 64 | - windows: py312 65 | - macos: py312 66 | - linux: py312-oldestdeps 67 | - linux: py314-devdeps 68 | secrets: 69 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 70 | 71 | docs: 72 | needs: [core] 73 | uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v2 74 | with: 75 | default_python: '3.13' 76 | submodules: false 77 | pytest: false 78 | toxdeps: tox-pypi-filter 79 | cache-key: docs-${{ github.run_id }} 80 | libraries: | 81 | apt: 82 | - graphviz 83 | envs: | 84 | - linux: build_docs 85 | 86 | online: 87 | if: "!startsWith(github.event.ref, 'refs/tags/v')" 88 | needs: [test] 89 | uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@main 90 | with: 91 | submodules: false 92 | coverage: codecov 93 | toxdeps: tox-pypi-filter 94 | envs: | 95 | - linux: py313-online 96 | 97 | publish: 98 | # Build wheels on PRs only when labelled. Releases will only be published if tagged ^v.* 99 | # see https://github-actions-workflows.openastronomy.org/en/latest/publish.html#upload-to-pypi 100 | if: | 101 | github.event_name != 'pull_request' || 102 | ( 103 | github.event_name != 'pull_request' && ( 104 | github.ref_name != 'main' || 105 | github.event_name == 'workflow_dispatch' 106 | ) 107 | ) || ( 108 | github.event_name == 'pull_request' && 109 | contains(github.event.pull_request.labels.*.name, 'Run publish') 110 | ) 111 | needs: [test, docs] 112 | uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish_pure_python.yml@v2 113 | with: 114 | python-version: '3.13' 115 | test_extras: 'tests' 116 | test_command: 'pytest -p no:warnings --doctest-rst --pyargs radiospectra' 117 | submodules: false 118 | secrets: 119 | pypi_token: ${{ secrets.pypi_token }} 120 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=62.1", 4 | "setuptools_scm[toml]>=8.0.0", 5 | "wheel", 6 | ] 7 | build-backend = "setuptools.build_meta" 8 | 9 | [project] 10 | name = "radiospectra" 11 | description = "Provide support for some type of radio spectra in solar physics." 12 | requires-python = ">=3.12" 13 | readme = { file = "README.rst", content-type = "text/x-rst" } 14 | license-files = ["licenses/LICENSE.rst"] 15 | authors = [ 16 | { name = "The SunPy Community", email = "sunpy@googlegroups.com" }, 17 | ] 18 | dependencies = [ 19 | "sunpy[net]>=7.0.0", 20 | "numpy>=1.26.0", 21 | "matplotlib>=3.8.0", 22 | "scipy>=1.12.0", 23 | "cdflib>=0.3.20" 24 | ] 25 | dynamic = ["version"] 26 | 27 | [project.optional-dependencies] 28 | tests = [ 29 | "pytest", 30 | "pytest-astropy", 31 | "pytest-doctestplus", 32 | "pytest-cov", 33 | "pytest-xdist", 34 | "sunpy-soar", 35 | ] 36 | docs = [ 37 | "sphinx", 38 | "sphinx-automodapi", 39 | "sphinx-changelog", 40 | "sphinx-gallery", 41 | "sunpy-sphinx-theme", 42 | "packaging", 43 | 44 | ] 45 | dev = ["radiospectra[tests,docs]"] 46 | 47 | [project.urls] 48 | Homepage = "https://sunpy.org" 49 | Download = "https://pypi.org/project/radiospectra" 50 | 51 | [tool.setuptools] 52 | zip-safe = false 53 | include-package-data = true 54 | 55 | [tool.setuptools.packages.find] 56 | include = ["radiospectra*"] 57 | exclude = ["radiospectra._dev*"] 58 | 59 | [tool.setuptools_scm] 60 | version_file = "radiospectra/_version.py" 61 | 62 | [tool.black] 63 | line-length = 120 64 | include = '\.pyi?$' 65 | exclude = ''' 66 | ( 67 | /( 68 | \.eggs 69 | | \.git 70 | | \.mypy_cache 71 | | \.tox 72 | | \.venv 73 | | _build 74 | | buck-out 75 | | build 76 | | dist 77 | | docs 78 | | .history 79 | )/ 80 | ) 81 | ''' 82 | 83 | [tool.gilesbot] 84 | [tool.gilesbot.pull_requests] 85 | enabled = true 86 | 87 | [tool.gilesbot.towncrier_changelog] 88 | enabled = true 89 | verify_pr_number = true 90 | changelog_skip_label = "No Changelog Entry Needed" 91 | help_url = "https://github.com//blob/main/changelog/README.rst" 92 | changelog_missing_long = "There isn't a changelog file in this pull request. Please add a changelog file to the `changelog/` directory following the instructions in the changelog [README](https://github.com//blob/main/changelog/README.rst)." 93 | type_incorrect_long = "The changelog file you added is not one of the allowed types. Please use one of the types described in the changelog [README](https://github.com//blob/main/changelog/README.rst)" 94 | number_incorrect_long = "The number in the changelog file you added does not match the number of this pull request. Please rename the file." 95 | 96 | # TODO: This should be in towncrier.toml but Giles currently only works looks in 97 | # pyproject.toml we should move this back when it's fixed. 98 | [tool.towncrier] 99 | package = "radiospectra" 100 | filename = "CHANGELOG.rst" 101 | directory = "changelog/" 102 | issue_format = "`#{issue} `__" 103 | title_format = "{version} ({project_date})" 104 | 105 | [[tool.towncrier.type]] 106 | directory = "breaking" 107 | name = "Breaking Changes" 108 | showcontent = true 109 | 110 | [[tool.towncrier.type]] 111 | directory = "deprecation" 112 | name = "Deprecations" 113 | showcontent = true 114 | 115 | [[tool.towncrier.type]] 116 | directory = "removal" 117 | name = "Removals" 118 | showcontent = true 119 | 120 | [[tool.towncrier.type]] 121 | directory = "feature" 122 | name = "New Features" 123 | showcontent = true 124 | 125 | [[tool.towncrier.type]] 126 | directory = "bugfix" 127 | name = "Bug Fixes" 128 | showcontent = true 129 | 130 | [[tool.towncrier.type]] 131 | directory = "doc" 132 | name = "Documentation" 133 | showcontent = true 134 | 135 | [[tool.towncrier.type]] 136 | directory = "trivial" 137 | name = "Internal Changes" 138 | showcontent = true 139 | -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/test_ilofar.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from unittest import mock 3 | 4 | import pytest 5 | 6 | import astropy.units as u 7 | 8 | from sunpy.net import Fido 9 | from sunpy.net import attrs as a 10 | 11 | from radiospectra.net.attrs import PolType 12 | from radiospectra.net.sources.ilofar import ILOFARMode357Client 13 | 14 | 15 | @pytest.fixture 16 | def client(): 17 | return ILOFARMode357Client() 18 | 19 | 20 | @pytest.fixture 21 | def html_responses(): 22 | paths = [Path(__file__).parent / "data" / n for n in ["ilofar_resp1.html", "ilofar_resp2.html"]] 23 | response_htmls = [] 24 | for p in paths: 25 | with p.open("r") as f: 26 | response_htmls.append(f.read()) 27 | 28 | return response_htmls 29 | 30 | 31 | @mock.patch("sunpy.net.scraper.urlopen") 32 | def test_ilofar_client(mock_urlopen, client, html_responses): 33 | mock_urlopen.return_value.read = mock.MagicMock() 34 | mock_urlopen.return_value.read.side_effect = html_responses * 2 35 | mock_urlopen.close = mock.MagicMock(return_value=None) 36 | atr = a.Time("2018/06/01", "2018/06/02") 37 | query = client.search(atr) 38 | 39 | called_urls = [ 40 | "https://data.lofar.ie/2018/06/01/bst/kbt/rcu357_1beam/", 41 | "https://data.lofar.ie/2018/06/02/bst/kbt/rcu357_1beam/", 42 | "https://data.lofar.ie/2018/06/01/bst/kbt/rcu357_1beam_datastream/", 43 | "https://data.lofar.ie/2018/06/02/bst/kbt/rcu357_1beam_datastream/", 44 | ] 45 | assert called_urls == [call[0][0] for call in mock_urlopen.call_args_list] 46 | assert len(query) == 8 47 | assert query[0]["Source"] == "ILOFAR" 48 | assert query[0]["Provider"] == "ILOFAR" 49 | assert query[0]["Start Time"].iso == "2018-06-01 10:00:41.000" 50 | assert query[0]["Polarisation"] == "X" 51 | 52 | 53 | @mock.patch("sunpy.net.scraper.urlopen") 54 | def test_ilofar_client_polarisation(mock_urlopen, client, html_responses): 55 | mock_urlopen.return_value.read = mock.MagicMock() 56 | mock_urlopen.return_value.read.side_effect = html_responses * 2 57 | mock_urlopen.close = mock.MagicMock(return_value=None) 58 | atr = a.Time("2018/06/01", "2018/06/02") 59 | query_x = client.search(atr, PolType("X")) 60 | assert len(query_x) == 4 61 | assert query_x[0]["Source"] == "ILOFAR" 62 | assert query_x[0]["Provider"] == "ILOFAR" 63 | assert query_x[0]["Start Time"].iso == "2018-06-01 10:00:41.000" 64 | assert query_x[0]["Polarisation"] =='X' 65 | 66 | 67 | @mock.patch("sunpy.net.scraper.urlopen") 68 | def test_ilofar_client_wavelength(mock_urlopen, client, html_responses): 69 | mock_urlopen.return_value.read = mock.MagicMock() 70 | mock_urlopen.return_value.read.side_effect = html_responses * 6 71 | mock_urlopen.close = mock.MagicMock(return_value=None) 72 | atr = a.Time("2018/06/01", "2018/06/02") 73 | query_both_low = client.search(atr, a.Wavelength(1 * u.MHz, 5 * u.MHz)) 74 | query_both_high = client.search(atr, a.Wavelength(1 * u.GHz, 2 * u.GHz)) 75 | 76 | assert len(query_both_low) == 0 77 | assert len(query_both_high) == 0 78 | 79 | query_low_in = client.search(atr, a.Wavelength(90 * u.MHz, 1 * u.GHz)) 80 | query_both_in = client.search(atr, a.Wavelength(15 * u.MHz, 230 * u.MHz)) 81 | query_high_in = client.search(atr, a.Wavelength(5 * u.MHz, 90 * u.MHz)) 82 | 83 | for query in [query_low_in, query_both_in, query_high_in]: 84 | assert len(query) == 8 85 | 86 | 87 | @pytest.mark.remote_data 88 | def test_fido(): 89 | atr = a.Time("2018/06/01", "2018/06/02") 90 | query = Fido.search(atr, a.Instrument("ILOFAR")) 91 | 92 | assert isinstance(query[0].client, ILOFARMode357Client) 93 | query = query[0] 94 | assert len(query) == 8 95 | assert query[0]["Source"] == "ILOFAR" 96 | assert query[0]["Provider"] == "ILOFAR" 97 | assert query[0]["Start Time"].iso == "2018-06-01 10:00:41.000" 98 | assert query[0]["Polarisation"] == "X" 99 | 100 | 101 | @pytest.mark.remote_data 102 | def test_fido_other_dataset(): 103 | atr = a.Time("2021/08/01", "2021/10/01") 104 | query = Fido.search(atr, a.Instrument("ILOFAR")) 105 | 106 | assert isinstance(query[0].client, ILOFARMode357Client) 107 | query = query[0] 108 | assert len(query) == 38 109 | -------------------------------------------------------------------------------- /radiospectra/net/sources/ecallisto.py: -------------------------------------------------------------------------------- 1 | from sunpy.net import attrs as a 2 | from sunpy.net.attr import SimpleAttr 3 | from sunpy.net.dataretriever.client import GenericClient 4 | 5 | from radiospectra.net.attrs import Observatory 6 | 7 | 8 | class eCALLISTOClient(GenericClient): 9 | """ 10 | Provides access to `eCallisto radio spectrometer `__ 11 | `data archive `__. 12 | 13 | `Further information `__. 14 | 15 | Notes 16 | ----- 17 | `Specific information on the meaning of the filename. `__ 18 | 19 | From the filename alone there's no way to tell about either the frequency or duration. 20 | Therefore we only return a start time. 21 | 22 | Examples 23 | -------- 24 | >>> from radiospectra import net 25 | >>> from sunpy.net import Fido, attrs as a 26 | >>> query = Fido.search(a.Time('2019/10/05 23:00', '2019/10/05 23:30'), 27 | ... a.Instrument('eCALLISTO'), net.Observatory('ALASKA')) #doctest: +REMOTE_DATA 28 | >>> query #doctest: +REMOTE_DATA 29 | 30 | Results from 1 Provider: 31 | 32 | 3 Results from the eCALLISTOClient: 33 | Start Time Provider Instrument Observatory ID 34 | ----------------------- --------- ---------- ----------- --- 35 | 2019-10-05 23:00:00.000 ECALLISTO ECALLISTO ALASKA 59 36 | 2019-10-05 23:15:00.000 ECALLISTO ECALLISTO ALASKA 59 37 | 2019-10-05 23:30:00.000 ECALLISTO ECALLISTO ALASKA 59 38 | 39 | 40 | """ 41 | 42 | baseurl = ( 43 | r"http://soleil80.cs.technik.fhnw.ch/solarradio/data/2002-20yy_Callisto/" 44 | r"%Y/%m/%d/{obs}_%Y%m%d_%H%M%S.*.fit.gz" 45 | ) 46 | pattern = ( 47 | r"{}/2002-20yy_Callisto/{year:4d}/{month:2d}/{day:2d}/" 48 | r"{Observatory}_{year:4d}{month:2d}{day:2d}" 49 | r"_{hour:2d}{minute:2d}{second:2d}{suffix}.fit.gz" 50 | ) 51 | 52 | @classmethod 53 | def pre_search_hook(cls, *args, **kwargs): 54 | baseurl, pattern, matchdict = super().pre_search_hook(*args, **kwargs) 55 | obs = matchdict.pop("Observatory") 56 | if obs[0] == "*": 57 | baseurl = baseurl.format(obs=r".*") 58 | else: 59 | # Need case sensitive so have to override 60 | obs_attr = [a for a in args if isinstance(a, Observatory)][0] 61 | baseurl = baseurl.format(obs=obs_attr.value) 62 | return baseurl, pattern, matchdict 63 | 64 | def post_search_hook(self, exdict, matchdict): 65 | original = super().post_search_hook(exdict, matchdict) 66 | original["ID"] = original["suffix"].replace("_", "") 67 | del original["suffix"] 68 | # We don't know the end time for all files 69 | # https://github.com/sunpy/radiospectra/issues/60 70 | del original["End Time"] 71 | return original 72 | 73 | @classmethod 74 | def register_values(cls): 75 | adict = { 76 | a.Provider: [("eCALLISTO", "International Network of Solar Radio Spectrometers.")], 77 | a.Instrument: [("eCALLISTO", "e-Callisto - International Network of Solar Radio Spectrometers.")], 78 | Observatory: [("*", "Observatory Location")], 79 | } 80 | return adict 81 | 82 | @classmethod 83 | def _can_handle_query(cls, *query): 84 | """ 85 | Method the `sunpy.net.fido_factory.UnifiedDownloaderFactory` class uses 86 | to dispatch queries to this Client. 87 | """ 88 | regattrs_dict = cls.register_values() 89 | optional = {k for k in regattrs_dict.keys()} - cls.required 90 | if not cls.check_attr_types_in_query(query, cls.required, optional): 91 | return False 92 | for key in regattrs_dict: 93 | all_vals = [i[0].lower() for i in regattrs_dict[key]] 94 | for x in query: 95 | if ( 96 | isinstance(x, key) 97 | and issubclass(key, SimpleAttr) 98 | and x.type_name != "observatory" 99 | and str(x.value).lower() not in all_vals 100 | ): 101 | return False 102 | return True 103 | -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/test_psp_client.py: -------------------------------------------------------------------------------- 1 | import gzip 2 | from pathlib import Path 3 | from unittest import mock 4 | 5 | import numpy as np 6 | import pytest 7 | 8 | import astropy.units as u 9 | from astropy.time import Time 10 | 11 | from sunpy.net import Fido 12 | from sunpy.net import attrs as a 13 | 14 | from radiospectra.net.sources.psp import RFSClient 15 | 16 | MOCK_PATH = "sunpy.net.scraper.urlopen" 17 | 18 | 19 | @pytest.fixture 20 | def client(): 21 | return RFSClient() 22 | 23 | 24 | @pytest.mark.parametrize( 25 | ("req_wave", "receivers"), 26 | [ 27 | # Completely contain the both receiver ranges 28 | (a.Wavelength(1 * u.kHz, 25000 * u.kHz), ["rfs_lfr", "rfs_hfr"]), 29 | # Min in lower freq and max in high freq receiver 30 | (a.Wavelength(20 * u.kHz, 15 * u.MHz), ["rfs_lfr", "rfs_hfr"]), 31 | # Min below and max in low freq receiver 32 | (a.Wavelength(1 * u.kHz, 100 * u.kHz), ["rfs_lfr"]), 33 | # Min and max in low freq receiver 34 | (a.Wavelength(20 * u.kHz, 100 * u.kHz), ["rfs_lfr"]), 35 | # Min and max in high freq receiver 36 | (a.Wavelength(1800 * u.kHz, 18000 * u.kHz), ["rfs_hfr"]), 37 | # Min in high freq receiver and max above 38 | (a.Wavelength(1800 * u.kHz, 20000 * u.kHz), ["rfs_hfr"]), 39 | # Min and max in the over lap 40 | (a.Wavelength(1.4 * u.MHz, 1.5 * u.MHz), ["rfs_lfr", "rfs_hfr"]), 41 | ], 42 | ) 43 | def test_check_wavelength(req_wave, receivers, client): 44 | res = client._check_wavelengths(req_wave) 45 | assert set(res) == set(receivers) 46 | 47 | 48 | @pytest.mark.remote_data 49 | def test_fido(): 50 | atr = a.Time("2019/10/01", "2019/10/02") 51 | res = Fido.search(atr, a.Instrument("rfs")) 52 | res0 = res[0] 53 | isinstance(res0.client, RFSClient) 54 | assert len(res0) == 4 55 | assert res["rfs"]["Start Time"].min() == Time("2019-10-01T00:00").datetime 56 | assert res["rfs"]["End Time"].max() == Time("2019-10-02T23:59:59.999").datetime 57 | 58 | 59 | @pytest.fixture 60 | def http_responces(): 61 | paths = [Path(__file__).parent / "data" / n for n in ["psp_resp1.html.gz", "psp_resp2.html.gz"]] 62 | response_htmls = [] 63 | for p in paths: 64 | with gzip.open(p) as f: 65 | response_htmls.append(f.read()) 66 | return response_htmls 67 | 68 | 69 | @mock.patch(MOCK_PATH) 70 | def test_search_with_wavelength(mock_urlopen, client, http_responces): 71 | mock_urlopen.return_value.read = mock.MagicMock() 72 | mock_urlopen.return_value.read.side_effect = http_responces 73 | mock_urlopen.close = mock.MagicMock(return_value=None) 74 | tr = a.Time("2019/10/13", "2019/10/15") 75 | wr1 = a.Wavelength(1 * u.kHz, 1.1 * u.MHz) 76 | res1 = client.search(tr, wr1) 77 | 78 | mock_urlopen.assert_called_with("https://spdf.gsfc.nasa.gov/pub/data/psp/fields/l2/rfs_lfr/2019/") 79 | assert np.array_equal(res1[0]["Wavelength"], [10, 1700] * u.kHz) 80 | assert len(res1) == 3 81 | assert res1["Start Time"].min().datetime == Time("2019-10-13T00:00").datetime 82 | assert res1["End Time"].max().datetime == Time("2019-10-15T23:59:59.999").datetime 83 | wr2 = a.Wavelength(2 * u.MHz, 20 * u.MHz) 84 | res2 = client.search(tr, wr2) 85 | mock_urlopen.assert_called_with("https://spdf.gsfc.nasa.gov/pub/data/psp/fields/l2/rfs_hfr/2019/") 86 | assert np.array_equal(res2[0]["Wavelength"], [1300, 19200] * u.kHz) 87 | assert len(res2) == 3 88 | assert res2.time_range().start == Time("2019-10-13T00:00").datetime 89 | assert res2.time_range().end == Time("2019-10-15T23:59:59.999").datetime 90 | 91 | 92 | @pytest.mark.remote_data 93 | def test_get_url_for_time_range(client): 94 | url_start = "https://spdf.gsfc.nasa.gov/pub/data/psp/fields/l2/rfs_lfr/2019/" "psp_fld_l2_rfs_lfr_20191001_v03.cdf" 95 | url_end = "https://spdf.gsfc.nasa.gov/pub/data/psp/fields/l2/rfs_hfr/2019/" "psp_fld_l2_rfs_hfr_20191015_v03.cdf" 96 | tr = a.Time("2019/10/01", "2019/10/15") 97 | res = client.search(tr) 98 | urls = [i["url"] for i in res] 99 | assert urls[0] == url_start 100 | assert urls[-1] == url_end 101 | 102 | 103 | def test_can_handle_query(client): 104 | atr = a.Time("2019/10/01", "2019/11/01") 105 | res = client._can_handle_query(atr, a.Instrument("rfs")) 106 | assert res is True 107 | res = client._can_handle_query(atr) 108 | assert res is False 109 | 110 | 111 | @pytest.mark.remote_data 112 | def test_download(client): 113 | query = client.search(a.Time("2019/10/05", "2019/10/06"), a.Instrument("rfs")) 114 | download_list = client.fetch(query) 115 | assert len(download_list) == len(query) 116 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file does only contain a selection of the most common options. For a 4 | # full list see the documentation: 5 | # http://www.sphinx-doc.org/en/master/config 6 | 7 | # flake8: NOQA: E402 8 | 9 | # -- stdlib imports ------------------------------------------------------------ 10 | import os 11 | import datetime 12 | from packaging.version import Version 13 | 14 | # -- Read the Docs Specific Configuration -------------------------------------- 15 | 16 | # This needs to be done before radiospectra is imported 17 | on_rtd = os.environ.get("READTHEDOCS", None) == "True" 18 | if on_rtd: 19 | os.environ["SUNPY_CONFIGDIR"] = "/home/docs/" 20 | os.environ["HOME"] = "/home/docs/" 21 | os.environ["LANG"] = "C" 22 | os.environ["LC_ALL"] = "C" 23 | os.environ["HIDE_PARFIVE_PROGESS"] = "True" 24 | 25 | 26 | # -- Project information ----------------------------------------------------- 27 | # The full version, including alpha/beta/rc tags 28 | from radiospectra import __version__ # NOQA 29 | 30 | _version = Version(__version__) 31 | version = release = str(_version) 32 | # Avoid "post" appearing in version string in rendered docs 33 | if _version.is_postrelease: 34 | version = release = _version.base_version 35 | # Avoid long githashes in rendered Sphinx docs 36 | elif _version.is_devrelease: 37 | version = release = f"{_version.base_version}.dev{_version.dev}" 38 | is_development = _version.is_devrelease 39 | is_release = not(_version.is_prerelease or _version.is_devrelease) 40 | 41 | project = "radiospectra" 42 | author = "The SunPy Community" 43 | copyright = f"{datetime.datetime.now().year}, {author}" # noqa: A001 44 | 45 | # -- General configuration --------------------------------------------------- 46 | 47 | # Wrap large function/method signatures 48 | maximum_signature_line_length = 80 49 | 50 | # Add any Sphinx extension module names here, as strings. They can be 51 | # extensions coming with Sphinx (named "sphinx.ext.*") or your custom 52 | # ones. 53 | extensions = [ 54 | "matplotlib.sphinxext.plot_directive", 55 | "sphinx.ext.autodoc", 56 | "sphinx.ext.coverage", 57 | "sphinx.ext.doctest", 58 | "sphinx.ext.inheritance_diagram", 59 | "sphinx.ext.intersphinx", 60 | "sphinx.ext.mathjax", 61 | "sphinx.ext.napoleon", 62 | "sphinx.ext.todo", 63 | "sphinx.ext.viewcode", 64 | "sphinx_automodapi.automodapi", 65 | "sphinx_automodapi.smart_resolver", 66 | "sphinx_changelog", 67 | ] 68 | 69 | # Add any paths that contain templates here, relative to this directory. 70 | # templates_path = ["_templates"] 71 | 72 | # List of patterns, relative to source directory, that match files and 73 | # directories to ignore when looking for source files. 74 | # This pattern also affects html_static_path and html_extra_path. 75 | html_extra_path = ["robots.txt"] 76 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 77 | 78 | # The suffix(es) of source filenames. 79 | source_suffix = {".rst": "restructuredtext"} 80 | 81 | # The master toctree document. 82 | master_doc = "index" 83 | 84 | # Treat everything in single ` as a Python reference. 85 | default_role = "py:obj" 86 | 87 | # -- Options for intersphinx extension --------------------------------------- 88 | 89 | # Example configuration for intersphinx: refer to the Python standard library. 90 | intersphinx_mapping = {"python": ("https://docs.python.org/", None)} 91 | 92 | # -- Options for HTML output ------------------------------------------------- 93 | 94 | # The theme to use for HTML and HTML Help pages. See the documentation for 95 | # a list of builtin themes. 96 | html_theme = "sunpy" 97 | 98 | # Add any paths that contain custom static files (such as style sheets) here, 99 | # relative to this directory. They are copied after the builtin static files, 100 | # so a file named "default.css" will overwrite the builtin "default.css". 101 | # html_static_path = ["_static"] 102 | 103 | # Render inheritance diagrams in SVG 104 | graphviz_output_format = "svg" 105 | 106 | graphviz_dot_args = [ 107 | "-Nfontsize=10", 108 | "-Nfontname=Helvetica Neue, Helvetica, Arial, sans-serif", 109 | "-Efontsize=10", 110 | "-Efontname=Helvetica Neue, Helvetica, Arial, sans-serif", 111 | "-Gfontsize=10", 112 | "-Gfontname=Helvetica Neue, Helvetica, Arial, sans-serif", 113 | ] 114 | 115 | # By default, when rendering docstrings for classes, sphinx.ext.autodoc will 116 | # make docs with the class-level docstring and the class-method docstrings, 117 | # but not the __init__ docstring, which often contains the parameters to 118 | # class constructors across the scientific Python ecosystem. The option below 119 | # will append the __init__ docstring to the class-level docstring when rendering 120 | # the docs. For more options, see: 121 | # https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#confval-autoclass_content 122 | autoclass_content = "both" 123 | 124 | # -- Other options ---------------------------------------------------------- 125 | -------------------------------------------------------------------------------- /radiospectra/net/sources/ilofar.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import astropy.units as u 4 | 5 | from sunpy.net import attrs as a 6 | from sunpy.net.dataretriever.client import GenericClient, QueryResponse 7 | from sunpy.net.scraper import Scraper 8 | from sunpy.time import TimeRange 9 | 10 | from radiospectra.net.attrs import PolType 11 | 12 | __all__ = ["ILOFARMode357Client"] 13 | 14 | RECEIVER_FREQUENCIES = a.Wavelength(10.546875 * u.MHz, 244.53125 * u.MHz) 15 | DATASET_NAMES = ["rcu357_1beam", "rcu357_1beam_datastream"] 16 | 17 | 18 | class ILOFARMode357Client(GenericClient): 19 | """ 20 | Provides access to I-LOFAR mode 357 observations from the 21 | data `archive `__ 22 | 23 | Examples 24 | -------- 25 | >>> import radiospectra.net 26 | >>> from sunpy.net import Fido, attrs as a 27 | >>> results = Fido.search(a.Time("2021/09/01", "2021/09/21"), 28 | ... a.Instrument('ILOFAR')) # doctest: +REMOTE_DATA 29 | >>> results #doctest: +REMOTE_DATA 30 | 31 | Results from 1 Provider: 32 | 33 | 10 Results from the ILOFARMode357Client: 34 | 35 | Start Time End Time ... Provider Polarisation 36 | ----------------------- ----------------------- ... -------- ------------ 37 | 2021-09-14 07:39:13.000 2021-09-14 07:39:13.999 ... ILOFAR X 38 | 2021-09-14 07:39:13.000 2021-09-14 07:39:13.999 ... ILOFAR Y 39 | 2021-09-01 08:07:29.000 2021-09-01 08:07:29.999 ... ILOFAR X 40 | 2021-09-01 08:07:29.000 2021-09-01 08:07:29.999 ... ILOFAR Y 41 | 2021-09-07 08:07:52.000 2021-09-07 08:07:52.999 ... ILOFAR X 42 | 2021-09-07 08:07:52.000 2021-09-07 08:07:52.999 ... ILOFAR Y 43 | 2021-09-08 08:04:07.000 2021-09-08 08:04:07.999 ... ILOFAR X 44 | 2021-09-08 08:04:07.000 2021-09-08 08:04:07.999 ... ILOFAR Y 45 | 2021-09-08 10:34:31.000 2021-09-08 10:34:31.999 ... ILOFAR X 46 | 2021-09-08 10:34:31.000 2021-09-08 10:34:31.999 ... ILOFAR Y 47 | 48 | 49 | """ 50 | 51 | baseurl = r"https://data.lofar.ie/%Y/%m/%d/bst/kbt/{dataset}/" r"%Y%m%d_\d{{6}}_bst_00\S{{1}}.dat" 52 | 53 | pattern = r"{}/{year:4d}{month:2d}{day:2d}_{hour:2d}{minute:2d}{second:2d}" r"_bst_00{Polarisation}.dat" 54 | 55 | @classmethod 56 | def _check_wavelengths(cls, wavelength): 57 | """ 58 | Check for overlap between given wavelength and receiver frequency coverage defined in 59 | `RECEIVER_FREQUENCIES`. 60 | 61 | Parameters 62 | ---------- 63 | wavelength : `sunpy.net.attrs.Wavelength` 64 | Input wavelength range to check 65 | 66 | Returns 67 | ------- 68 | `bool` 69 | """ 70 | return wavelength.min in RECEIVER_FREQUENCIES or wavelength.max in RECEIVER_FREQUENCIES 71 | 72 | def search(self, *args, **kwargs): 73 | """ 74 | Query this client for a list of results. 75 | 76 | Parameters 77 | ---------- 78 | *args: `tuple` 79 | `sunpy.net.attrs` objects representing the query. 80 | **kwargs: `dict` 81 | Any extra keywords to refine the search. 82 | 83 | Returns 84 | ------- 85 | A `QueryResponse` instance containing the query result. 86 | """ 87 | matchdict = self._get_match_dict(*args, **kwargs) 88 | metalist = [] 89 | 90 | wavelentgh = matchdict.get("Wavelength", False) 91 | if wavelentgh and not self._check_wavelengths(wavelentgh): 92 | return QueryResponse(metalist, client=self) 93 | 94 | tr = TimeRange(matchdict["Start Time"], matchdict["End Time"]) 95 | 96 | for dataset in DATASET_NAMES: 97 | url = self.baseurl.format(dataset=dataset) 98 | scraper = Scraper(url, regex=True) 99 | filesmeta = scraper._extract_files_meta(tr, extractor=self.pattern) 100 | for i in filesmeta: 101 | rowdict = self.post_search_hook(i, matchdict) 102 | metalist.append(rowdict) 103 | 104 | query_response = QueryResponse(metalist, client=self) 105 | mask = np.full(len(query_response), True) 106 | pol = matchdict.get("PolType") 107 | if len(pol) == 1: 108 | pol = pol[0].upper() 109 | mask = mask & (query_response["Polarisation"] == pol) 110 | 111 | if query_response: 112 | query_response.remove_column("PolType") 113 | 114 | return query_response[mask] 115 | 116 | @classmethod 117 | def register_values(cls): 118 | adict = { 119 | a.Instrument: [("ILOFAR", "Irish LOFAR STATION (IE63)")], 120 | a.Source: [("ILOFAR", "Irish LOFAR Data Archive")], 121 | a.Provider: [("ILOFAR", "Irish LOFAR Data Archive")], 122 | a.Wavelength: [("*")], 123 | PolType: [("X", "X"), ("X Linear Polarisation", "Y Linear Polarisation")], 124 | } 125 | return adict 126 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | **************** 2 | ``radiospectra`` 3 | **************** 4 | 5 | |Latest Version| |matrix| |codecov| |Powered by NumFOCUS| |Powered by SunPy| 6 | 7 | .. |Latest Version| image:: https://img.shields.io/pypi/v/radiospectra.svg 8 | :target: https://pypi.python.org/pypi/radiospectra/ 9 | .. |matrix| image:: https://img.shields.io/matrix/sunpy:openastronomy.org.svg?colorB=%23FE7900&label=Chat&logo=matrix&server_fqdn=openastronomy.modular.im 10 | :target: https://openastronomy.element.io/#/room/#sunpy:openastronomy.org 11 | .. |codecov| image:: https://codecov.io/gh/sunpy/radiospectra/branch/main/graph/badge.svg 12 | :target: https://codecov.io/gh/sunpy/radiospectra 13 | .. |Binder| image:: https://mybinder.org/badge_logo.svg 14 | :target: https://mybinder.org/v2/gh/sunpy/sunpy/main?filepath=examples 15 | .. |Powered by NumFOCUS| image:: https://img.shields.io/badge/powered%20by-NumFOCUS-orange.svg?style=flat&colorA=E1523D&colorB=007D8A 16 | :target: https://numfocus.org 17 | .. |Powered by SunPy| image:: http://img.shields.io/badge/powered%20by-SunPy-orange.svg?style=flat 18 | :target: http://www.sunpy.org 19 | :alt: Powered by SunPy Badge 20 | 21 | ``radiospectra`` is a Python software package that provides support for some type of radio spectra in solar physics. 22 | 23 | To see the latest changes in ``radiospectra`` see our `changelog `__. 24 | 25 | Installation 26 | ============ 27 | 28 | The recommended way to install ``radiospectra`` is with `miniforge `__. 29 | To install ``radiospectra`` once miniforge is installed run the following command: 30 | 31 | .. code:: bash 32 | 33 | $ conda install radiospectra 34 | 35 | For detailed installation instructions, see the `installation guide `__ in the ``sunpy`` docs. 36 | 37 | Getting Help 38 | ============ 39 | 40 | For more information or to ask questions about ``radiospectra`` or any other SunPy library, check out: 41 | 42 | - `radiospectra documentation `__ 43 | - `SunPy Chat`_ 44 | - `SunPy mailing list `__ 45 | 46 | 47 | Usage of Generative AI 48 | ---------------------- 49 | 50 | We expect authentic engagement in our community. 51 | Be wary of posting output from Large Language Models or similar generative AI as comments on GitHub or any other platform, as such comments tend to be formulaic and low quality content. 52 | If you use generative AI tools as an aid in developing code or documentation changes, ensure that you fully understand the proposed changes and can explain why they are the correct approach and an improvement to the current state. 53 | 54 | License 55 | ======= 56 | 57 | This project is Copyright (c) The SunPy Community and licensed under 58 | the terms of the BSD 2-Clause license. This package is based upon 59 | the `Openastronomy packaging guide `_ 60 | which is licensed under the BSD 3-clause licence. See the licenses folder for 61 | more information. 62 | 63 | 64 | 65 | Contributing 66 | ============ 67 | 68 | We love contributions! radiospectra is open source, 69 | built on open source, and we'd love to have you hang out in our community. 70 | 71 | **Imposter syndrome disclaimer**: We want your help. No, really. 72 | 73 | There may be a little voice inside your head that is telling you that you're not 74 | ready to be an open source contributor; that your skills aren't nearly good 75 | enough to contribute. What could you possibly offer a project like this one? 76 | 77 | We assure you - the little voice in your head is wrong. If you can write code at 78 | all, you can contribute code to open source. Contributing to open source 79 | projects is a fantastic way to advance one's coding skills. Writing perfect code 80 | isn't the measure of a good developer (that would disqualify all of us!); it's 81 | trying to create something, making mistakes, and learning from those 82 | mistakes. That's how we all improve, and we are happy to help others learn. 83 | 84 | Being an open source contributor doesn't just mean writing code, either. You can 85 | help out by writing documentation, tests, or even giving feedback about the 86 | project (and yes - that includes giving feedback about the contribution 87 | process). Some of these contributions may be the most valuable to the project as 88 | a whole, because you're coming to the project with fresh eyes, so you can see 89 | the errors and assumptions that seasoned contributors have glossed over. 90 | 91 | Note: This disclaimer was originally written by 92 | `Adrienne Lowe `_ for a 93 | `PyCon talk `_, and was adapted by 94 | radiospectra based on its use in the README file for the 95 | `MetPy project `_. 96 | 97 | Code of Conduct 98 | =============== 99 | 100 | When you are interacting with the SunPy community you are asked to follow our `Code of Conduct `__. 101 | 102 | .. _SunPy Chat: https://openastronomy.element.io/#/room/#sunpy:openastronomy.org 103 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python: https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | tmp/ 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | pip-wheel-metadata/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | radiospectra/_version.py 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | # automodapi 78 | docs/api 79 | docs/sg_execution_times.rst 80 | 81 | # PyBuilder 82 | .pybuilder/ 83 | target/ 84 | 85 | # Jupyter Notebook 86 | .ipynb_checkpoints 87 | 88 | # IPython 89 | profile_default/ 90 | ipython_config.py 91 | 92 | # pyenv 93 | # For a library or package, you might want to ignore these files since the code is 94 | # intended to run in multiple environments; otherwise, check them in: 95 | # .python-version 96 | 97 | # pipenv 98 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 99 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 100 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 101 | # install all needed dependencies. 102 | #Pipfile.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Rope project settings 132 | .ropeproject 133 | 134 | # mkdocs documentation 135 | /site 136 | 137 | # mypy 138 | .mypy_cache/ 139 | 140 | # Pyre type checker 141 | .pyre/ 142 | 143 | # IDE 144 | # PyCharm 145 | .idea 146 | 147 | # Spyder project settings 148 | .spyderproject 149 | .spyproject 150 | 151 | ### VScode: https://raw.githubusercontent.com/github/gitignore/master/Global/VisualStudioCode.gitignore 152 | .vscode/* 153 | .vs/* 154 | 155 | ### https://raw.github.com/github/gitignore/master/Global/OSX.gitignore 156 | .DS_Store 157 | .AppleDouble 158 | .LSOverride 159 | 160 | # Icon must ends with two \r. 161 | Icon 162 | 163 | # Thumbnails 164 | ._* 165 | 166 | # Files that might appear on external disk 167 | .Spotlight-V100 168 | .Trashes 169 | 170 | ### Linux: https://raw.githubusercontent.com/github/gitignore/master/Global/Linux.gitignore 171 | *~ 172 | 173 | # temporary files which can be created if a process still has a handle open of a deleted file 174 | .fuse_hidden* 175 | 176 | # KDE directory preferences 177 | .directory 178 | 179 | # Linux trash folder which might appear on any partition or disk 180 | .Trash-* 181 | 182 | # .nfs files are created when an open file is removed but is still being accessed 183 | .nfs* 184 | 185 | # pytype static type analyzer 186 | .pytype/ 187 | 188 | # General 189 | .DS_Store 190 | .AppleDouble 191 | .LSOverride 192 | 193 | # Icon must end with two \r 194 | Icon 195 | 196 | 197 | # Thumbnails 198 | ._* 199 | 200 | # Files that might appear in the root of a volume 201 | .DocumentRevisions-V100 202 | .fseventsd 203 | .Spotlight-V100 204 | .TemporaryItems 205 | .Trashes 206 | .VolumeIcon.icns 207 | .com.apple.timemachine.donotpresent 208 | 209 | # Directories potentially created on remote AFP share 210 | .AppleDB 211 | .AppleDesktop 212 | Network Trash Folder 213 | Temporary Items 214 | .apdisk 215 | 216 | ### Windows: https://raw.githubusercontent.com/github/gitignore/master/Global/Windows.gitignore 217 | 218 | # Windows thumbnail cache files 219 | Thumbs.db 220 | ehthumbs.db 221 | ehthumbs_vista.db 222 | 223 | # Dump file 224 | *.stackdump 225 | 226 | # Folder config file 227 | [Dd]esktop.ini 228 | 229 | # Recycle Bin used on file shares 230 | $RECYCLE.BIN/ 231 | 232 | # Windows Installer files 233 | *.cab 234 | *.msi 235 | *.msix 236 | *.msm 237 | *.msp 238 | 239 | # Windows shortcuts 240 | *.lnk 241 | 242 | ### Extra Python Items and SunPy Specific 243 | docs/whatsnew/latest_changelog.txt 244 | examples/**/*.csv 245 | figure_test_images* 246 | tags 247 | baseline 248 | 249 | # Release script 250 | .github_cache 251 | 252 | # Misc Stuff 253 | .history 254 | *.orig 255 | .tmp 256 | node_modules/ 257 | package-lock.json 258 | package.json 259 | .prettierrc 260 | 261 | # Log files generated by 'vagrant up' 262 | *.log 263 | -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/data/rstn_holloman.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Index of /stp/space-weather/solar-data/solar-features/solar-radio/rstn-spectral/holloman/2003/03 5 | 6 | 7 | 8 |
9 | Logo 10 |
11 |
12 | U.S. Department of Commerce logo 13 |
14 |
15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
NameLast modifiedSize

Parent Directory  -
HO030301.SRS.gz2003-08-22 20:09 6.3M
HO030302.SRS.gz2003-06-10 17:25 6.3M
HO030303.SRS.gz2003-06-10 17:26 6.2M
HO030304.SRS.gz2003-06-10 17:26 6.4M
HO030305.SRS.gz2003-06-10 17:27 6.6M
HO030306.SRS.gz2003-06-10 17:27 5.9M
HO030307.SRS.gz2003-06-10 17:27 6.5M
HO030308.SRS.gz2003-06-10 17:28 6.6M
HO030309.SRS.gz2003-06-10 17:28 6.6M
HO030310.SRS.gz2003-06-10 17:29 6.0M
HO030311.SRS.gz2003-06-10 17:29 6.5M
HO030312.SRS.gz2003-06-10 17:30 6.6M
HO030313.SRS.gz2003-06-10 17:30 6.6M
HO030314.SRS.gz2003-06-10 17:31 6.6M
HO030315.SRS.gz2003-06-10 17:31 6.4M
HO030316.SRS.gz2003-06-10 17:32 6.5M
HO030317.SRS.gz2003-06-10 17:32 6.5M
HO030318.SRS.gz2003-06-10 17:33 6.6M
HO030319.SRS.gz2003-06-10 17:33 6.6M
HO030320.SRS.gz2003-06-10 17:34 6.6M
HO030321.SRS.gz2003-06-10 17:34 6.5M
HO030322.SRS.gz2003-06-10 17:35 6.6M
HO030323.SRS.gz2003-06-10 17:35 6.5M
HO030324.SRS.gz2003-06-10 17:36 6.5M
HO030325.SRS.gz2003-06-10 17:36 6.6M
HO030326.SRS.gz2003-06-10 17:37 6.6M
HO030327.SRS.gz2003-06-10 17:37 6.5M
HO030328.SRS.gz2003-06-10 17:38 6.6M
HO030329.SRS.gz2003-06-10 17:38 6.6M
HO030330.SRS.gz2003-06-10 17:39 6.9M
HO030331.SRS.gz2003-06-10 17:39 6.9M

54 | 55 | Home | privacy policy | questions 56 |
57 |
58 | Website of the US Department of Commerce / NOAA / NESDIS / Home
59 | 60 | 61 | -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/data/rstn_learmonth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Index of /stp/space-weather/solar-data/solar-features/solar-radio/rstn-spectral/learmonth/2003/03 5 | 6 | 7 | 8 |
9 | Logo 10 |
11 |
12 | U.S. Department of Commerce logo 13 |
14 |
15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
NameLast modifiedSize

Parent Directory  -
LM030301.SRS.gz2003-03-20 17:25 6.0M
LM030302.SRS.gz2003-04-17 20:46 6.0M
LM030303.SRS.gz2003-04-17 20:32 5.9M
LM030304.SRS.gz2003-04-17 20:32 5.9M
LM030305.SRS.gz2003-04-17 20:33 5.9M
LM030306.SRS.gz2003-04-17 20:33 6.1M
LM030307.SRS.gz2003-04-17 20:34 6.1M
LM030308.SRS.gz2003-04-17 20:34 6.1M
LM030309.SRS.gz2003-04-17 20:35 6.1M
LM030310.SRS.gz2003-04-17 20:35 6.1M
LM030311.SRS.gz2003-04-17 20:36 6.0M
LM030312.SRS.gz2003-04-17 20:36 6.1M
LM030313.SRS.gz2003-04-17 20:37 6.1M
LM030314.SRS.gz2003-04-17 20:37 6.2M
LM030315.SRS.gz2003-04-17 20:38 6.2M
LM030316.SRS.gz2003-04-17 20:38 6.2M
LM030317.SRS.gz2003-04-17 20:39 6.1M
LM030318.SRS.gz2003-04-17 20:39 6.1M
LM030319.SRS.gz2003-04-17 20:40 6.1M
LM030320.SRS.gz2003-04-17 20:40 6.1M
LM030321.SRS.gz2003-04-17 20:41 5.7M
LM030322.SRS.gz2003-04-17 20:41 6.2M
LM030323.SRS.gz2003-04-17 20:42 5.8M
LM030324.SRS.gz2003-04-17 20:42 6.0M
LM030325.SRS.gz2003-04-17 20:43 6.1M
LM030326.SRS.gz2003-04-17 20:43 6.0M
LM030327.SRS.gz2003-04-17 20:44 5.8M
LM030328.SRS.gz2003-04-17 20:44 5.8M
LM030329.SRS.gz2003-04-17 20:45 5.8M
LM030330.SRS.gz2003-04-17 20:45 5.7M
LM030331.SRS.gz2003-04-17 20:46 5.9M

54 | 55 | Home | privacy policy | questions 56 |
57 |
58 | Website of the US Department of Commerce / NOAA / NESDIS / Home
59 | 60 | 61 | -------------------------------------------------------------------------------- /radiospectra/net/sources/psp.py: -------------------------------------------------------------------------------- 1 | import astropy.units as u 2 | 3 | from sunpy.net import attrs as a 4 | from sunpy.net.dataretriever.client import GenericClient, QueryResponse 5 | from sunpy.net.scraper import Scraper 6 | from sunpy.time.timerange import TimeRange 7 | 8 | __all__ = ["RFSClient"] 9 | 10 | RECEIVER_FREQUENCIES = { 11 | "rfs_lfr": a.Wavelength(10 * u.kHz, 1.7 * u.MHz), 12 | "rfs_hfr": a.Wavelength(1.3 * u.MHz, 19.2 * u.MHz), 13 | } 14 | 15 | 16 | class RFSClient(GenericClient): 17 | """ 18 | Provides access to Parker Solar Probe FIELDS Radio Frequency Spectrometer data 19 | `archive `__ at 20 | `NASA Goddard Space Physics Data Facility (SPDF) `__. 21 | 22 | Examples 23 | -------- 24 | >>> import radiospectra.net 25 | >>> from sunpy.net import Fido, attrs as a 26 | >>> results = Fido.search(a.Time("2019/10/02", "2019/10/05"), 27 | ... a.Instrument('rfs')) #doctest: +REMOTE_DATA 28 | >>> results #doctest: +REMOTE_DATA 29 | 30 | Results from 1 Provider: 31 | 32 | 8 Results from the RFSClient: 33 | 34 | Start Time End Time Instrument Source Provider Wavelength 35 | kHz 36 | ----------------------- ----------------------- ---------- ------ -------- ----------------- 37 | 2019-10-02 00:00:00.000 2019-10-02 23:59:59.999 RFS PSP SPDF 10.0 .. 1700.0 38 | 2019-10-03 00:00:00.000 2019-10-03 23:59:59.999 RFS PSP SPDF 10.0 .. 1700.0 39 | 2019-10-04 00:00:00.000 2019-10-04 23:59:59.999 RFS PSP SPDF 10.0 .. 1700.0 40 | 2019-10-05 00:00:00.000 2019-10-05 23:59:59.999 RFS PSP SPDF 10.0 .. 1700.0 41 | 2019-10-02 00:00:00.000 2019-10-02 23:59:59.999 RFS PSP SPDF 1300.0 .. 19200.0 42 | 2019-10-03 00:00:00.000 2019-10-03 23:59:59.999 RFS PSP SPDF 1300.0 .. 19200.0 43 | 2019-10-04 00:00:00.000 2019-10-04 23:59:59.999 RFS PSP SPDF 1300.0 .. 19200.0 44 | 2019-10-05 00:00:00.000 2019-10-05 23:59:59.999 RFS PSP SPDF 1300.0 .. 19200.0 45 | 46 | 47 | """ 48 | 49 | baseurl = ( 50 | r"https://spdf.gsfc.nasa.gov/pub/data/psp/fields/l2/{Wavelength}/" 51 | r"{year}/psp_fld_l2_(\w){{7}}_(\d){{8}}_v(\d){{2}}.cdf" 52 | ) 53 | pattern = r"{}/{Wavelength}/{year:4d}/" r"psp_fld_l2_{Wavelength}_{year:4d}{month:2d}{day:2d}_v{:2d}.cdf" 54 | 55 | @classmethod 56 | def _check_wavelengths(cls, wavelength): 57 | """ 58 | Check for overlap between given wavelength and receiver frequency coverage 59 | defined in ``RECEIVER_FREQUENCIES``. 60 | 61 | Parameters 62 | ---------- 63 | wavelength : `sunpy.net.attrs.Wavelength` 64 | Input wavelength range to check 65 | 66 | Returns 67 | ------- 68 | `list` 69 | List of receivers names or empty list if no overlap 70 | """ 71 | # Input wavelength range is completely contained in one receiver range 72 | receivers = [k for k, v in RECEIVER_FREQUENCIES.items() if wavelength in v] 73 | # If not defined need to continue 74 | if not receivers: 75 | # Overlaps but not contained in, either max in lfr or min hfr 76 | if wavelength.min in RECEIVER_FREQUENCIES["rfs_hfr"] or wavelength.max in RECEIVER_FREQUENCIES["rfs_hfr"]: 77 | receivers.append("rfs_hfr") 78 | if wavelength.min in RECEIVER_FREQUENCIES["rfs_lfr"] or wavelength.max in RECEIVER_FREQUENCIES["rfs_lfr"]: 79 | receivers.append("rfs_lfr") 80 | # min in lfr and max in hfr 81 | # min and max of combined lft and hfr contained in give wavelength range 82 | if a.Wavelength(RECEIVER_FREQUENCIES["rfs_lfr"].min, RECEIVER_FREQUENCIES["rfs_hfr"].max) in wavelength: 83 | receivers = ["rfs_lfr", "rfs_hfr"] 84 | # If we get here the is no overlap so set to empty list 85 | return receivers 86 | 87 | def search(self, *args, **kwargs): 88 | """ 89 | Query this client for a list of results. 90 | 91 | Parameters 92 | ---------- 93 | *args: `tuple` 94 | `sunpy.net.attrs` objects representing the query. 95 | **kwargs: `dict` 96 | Any extra keywords to refine the search. 97 | 98 | Returns 99 | ------- 100 | A `QueryResponse` instance containing the query result. 101 | """ 102 | matchdict = self._get_match_dict(*args, **kwargs) 103 | req_wave = matchdict.get("Wavelength", None) 104 | receivers = RECEIVER_FREQUENCIES.keys() 105 | if req_wave is not None: 106 | receivers = self._check_wavelengths(req_wave) 107 | 108 | metalist = [] 109 | start_year = matchdict["Start Time"].datetime.year 110 | end_year = matchdict["End Time"].datetime.year 111 | tr = TimeRange(matchdict["Start Time"], matchdict["End Time"]) 112 | for receiver in receivers: 113 | for year in range(start_year, end_year + 1): 114 | urlpattern = self.baseurl.format(Wavelength=receiver, year=year) 115 | scraper = Scraper(urlpattern, regex=True) 116 | filesmeta = scraper._extract_files_meta(tr, extractor=self.pattern) 117 | for i in filesmeta: 118 | rowdict = self.post_search_hook(i, matchdict) 119 | metalist.append(rowdict) 120 | 121 | return QueryResponse(metalist, client=self) 122 | 123 | def post_search_hook(self, exdict, matchdict): 124 | """ 125 | This method converts 'rfs_hfr' and 'rfs_lfr' in the url's metadata to the 126 | frequency ranges for low and high frequency receivers. 127 | """ 128 | rowdict = super().post_search_hook(exdict, matchdict) 129 | if rowdict["Wavelength"] == "rfs_hfr": 130 | fr = RECEIVER_FREQUENCIES["rfs_hfr"] 131 | rowdict["Wavelength"] = u.Quantity([float(fr.min.value), float(fr.max.value)], unit=fr.unit) 132 | elif rowdict["Wavelength"] == "rfs_lfr": 133 | fr = RECEIVER_FREQUENCIES["rfs_lfr"] 134 | rowdict["Wavelength"] = u.Quantity([float(fr.min.value), float(fr.max.value)], unit=fr.unit) 135 | return rowdict 136 | 137 | @classmethod 138 | def register_values(cls): 139 | adict = { 140 | a.Instrument: [("RFS", ("Radio Frequency Spectrometer"))], 141 | a.Source: [("PSP", "Parker Solar Probe")], 142 | a.Provider: [("SPDF", "NASA Goddard Space Physics Data Facility")], 143 | a.Wavelength: [("*")], 144 | } 145 | return adict 146 | -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/data/rstn_san-vito.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Index of /stp/space-weather/solar-data/solar-features/solar-radio/rstn-spectral/san-vito/2003/03 5 | 6 | 7 | 8 |
9 | Logo 10 |
11 |
12 | U.S. Department of Commerce logo 13 |
14 |
15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
NameLast modifiedSize

Parent Directory  -
SV030207.SRS.gz2003-05-22 22:04 5.0M
SV030208.SRS.gz2003-05-22 21:48 5.0M
SV030209.SRS.gz2003-05-22 21:48 5.0M
SV030210.SRS.gz2003-05-22 21:48 5.0M
SV030211.SRS.gz2003-05-22 21:49 5.1M
SV030212.SRS.gz2003-05-22 21:49 5.1M
SV030213.SRS.gz2003-05-22 21:50 5.2M
SV030219.SRS.gz2003-05-22 21:50 5.2M
SV030220.SRS.gz2003-05-22 21:51 5.1M
SV030221.SRS.gz2003-05-22 21:51 5.2M
SV030227.SRS.gz2003-05-22 21:52 6.5M
SV030306.SRS.gz2003-05-22 21:52 5.9M
SV030309.SRS.gz2003-05-22 21:53 19M
SV030310.SRS.gz2003-05-22 21:54 6.5M
SV030311.SRS.gz2003-05-22 21:54 6.4M
SV030312.SRS.gz2003-05-22 21:55 6.4M
SV030313.SRS.gz2003-05-22 21:55 6.5M
SV030314.SRS.gz2003-05-22 21:56 6.1M
SV030315.SRS.gz2003-05-22 21:56 5.7M
SV030316.SRS.gz2003-05-22 21:57 5.8M
SV030317.SRS.gz2003-05-22 21:57 5.5M
SV030318.SRS.gz2003-05-22 21:58 5.9M
SV030319.SRS.gz2003-05-22 21:58 5.8M
SV030320.SRS.gz2003-05-22 21:59 6.2M
SV030321.SRS.gz2003-05-22 21:59 5.6M
SV030322.SRS.gz2003-05-22 22:00 5.6M
SV030323.SRS.gz2003-05-22 22:00 5.6M
SV030324.SRS.gz2003-05-22 22:01 5.9M
SV030325.SRS.gz2003-05-22 22:01 6.1M
SV030326.SRS.gz2003-05-22 22:01 5.9M
SV030327.SRS.gz2003-05-22 22:02 6.0M
SV030328.SRS.gz2003-05-22 22:02 6.2M
SV030329.SRS.gz2003-05-22 22:03 6.0M
SV030330.SRS.gz2003-05-22 22:03 6.1M
SV030331.SRS.gz2003-05-22 21:47 6.0M

58 | 59 | Home | privacy policy | questions 60 |
61 |
62 | Website of the US Department of Commerce / NOAA / NESDIS / Home
63 | 64 | 65 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/tests/test_psp_rfs.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from datetime import datetime 3 | from unittest import mock 4 | 5 | import numpy as np 6 | 7 | import astropy.units as u 8 | from astropy.time import Time 9 | 10 | from sunpy.net import attrs as a 11 | 12 | from radiospectra.spectrogram import Spectrogram 13 | from radiospectra.spectrogram.sources import RFSSpectrogram 14 | 15 | 16 | @mock.patch("radiospectra.spectrogram.spectrogram_factory.parse_path") 17 | def test_psp_rfs_lfr(parse_path_moc): 18 | start_time = Time("2019-04-09 00:01:16.197889") 19 | end_time = Time("2019-04-10 00:01:04.997573") 20 | meta = { 21 | "cdf_meta": { 22 | "TITLE": "PSP FIELDS RFS LFR data", 23 | "Project": "PSP", 24 | "Source_name": "PSP_FLD>Parker Solar Probe FIELDS", 25 | "Descriptor": "RFS_LFR>Radio Frequency Spectrometer LFR", 26 | "Data_type": "L2>Level 2 Data", 27 | "Data_version": "01", 28 | "MODS": "Revision 1", 29 | "Logical_file_id": "psp_fld_l2_rfs_lfr_20190409_v01", 30 | "Mission_group": "PSP", 31 | }, 32 | "detector": "lfr", 33 | "instrument": "FIELDS/RFS", 34 | "observatory": "PSP", 35 | "start_time": start_time, 36 | "end_time": end_time, 37 | "wavelength": a.Wavelength(10.546879882812501 * u.kHz, 1687.5 * u.kHz), 38 | "times": start_time + np.linspace(0, (end_time - start_time).to_value("s"), 12359) * u.s, 39 | "freqs": [ 40 | 10546.88, 41 | 18750.0, 42 | 28125.0, 43 | 37500.0, 44 | 46875.0, 45 | 56250.0, 46 | 65625.0, 47 | 75000.0, 48 | 84375.0, 49 | 89062.5, 50 | 94921.88, 51 | 100781.25, 52 | 106640.62, 53 | 112500.0, 54 | 118359.38, 55 | 125390.62, 56 | 132421.88, 57 | 140625.0, 58 | 146484.38, 59 | 157031.25, 60 | 166406.25, 61 | 175781.25, 62 | 186328.12, 63 | 196875.0, 64 | 208593.75, 65 | 220312.5, 66 | 233203.12, 67 | 247265.62, 68 | 261328.12, 69 | 276562.5, 70 | 292968.75, 71 | 309375.0, 72 | 328125.0, 73 | 346875.0, 74 | 366796.88, 75 | 387890.62, 76 | 411328.12, 77 | 434765.62, 78 | 459375.0, 79 | 486328.12, 80 | 514453.12, 81 | 544921.9, 82 | 576562.5, 83 | 609375.0, 84 | 645703.1, 85 | 682031.25, 86 | 721875.0, 87 | 764062.5, 88 | 808593.75, 89 | 855468.75, 90 | 904687.5, 91 | 957421.9, 92 | 1013671.9, 93 | 1072265.6, 94 | 1134375.0, 95 | 1196484.4, 96 | 1265625.0, 97 | 1312500.0, 98 | 1368750.0, 99 | 1425000.0, 100 | 1481250.0, 101 | 1565625.0, 102 | 1621875.0, 103 | 1687500.0, 104 | ] 105 | * u.Hz, 106 | } 107 | array = np.zeros((64, 12359)) 108 | parse_path_moc.return_value = [(array, meta)] 109 | file = Path("fake.cdf") 110 | spec = Spectrogram(file) 111 | assert isinstance(spec, RFSSpectrogram) 112 | assert spec.observatory == "PSP" 113 | assert spec.instrument == "FIELDS/RFS" 114 | assert spec.detector == "LFR" 115 | # TODO check why not exact prob base on spacecrast ET so won't match utc exactly 116 | assert spec.start_time.datetime == datetime(2019, 4, 9, 0, 1, 16, 197889) 117 | assert spec.end_time.datetime == datetime(2019, 4, 10, 0, 1, 4, 997573) 118 | assert spec.wavelength.min.round(1) == 10.5 * u.kHz 119 | assert spec.wavelength.max == 1687.5 * u.kHz 120 | assert spec.level == "L2" 121 | assert spec.version == 1 122 | 123 | 124 | @mock.patch("radiospectra.spectrogram.spectrogram_factory.parse_path") 125 | def test_psp_rfs_hfr(parse_path_moc): 126 | start_time = Time("2019-04-09 00:01:13.904188") 127 | end_time = Time("2019-04-10 00:01:02.758315") 128 | meta = { 129 | "cdf_meta": { 130 | "TITLE": "PSP FIELDS RFS HFR data", 131 | "Project": "PSP", 132 | "Source_name": "PSP_FLD>Parker Solar Probe FIELDS", 133 | "Descriptor": "RFS_HFR>Radio Frequency Spectrometer HFR", 134 | "Data_type": "L2>Level 2 Data", 135 | "Data_version": "01", 136 | "MODS": "Revision 1", 137 | "Logical_file_id": "psp_fld_l2_rfs_lfr_20190409_v01", 138 | "Mission_group": "PSP", 139 | }, 140 | "detector": "hfr", 141 | "instrument": "FIELDS/RFS", 142 | "observatory": "PSP", 143 | "start_time": start_time, 144 | "end_time": end_time, 145 | "wavelength": a.Wavelength(1275.0 * u.kHz, 19171.876 * u.kHz), 146 | "times": start_time + np.linspace(0, (end_time - start_time).to_value("s"), 12359) * u.s, 147 | "freqs": [ 148 | 1275000.0, 149 | 1321875.0, 150 | 1378125.0, 151 | 1425000.0, 152 | 1471875.0, 153 | 1575000.0, 154 | 1621875.0, 155 | 1678125.0, 156 | 1771875.0, 157 | 1828125.0, 158 | 1921875.0, 159 | 2025000.0, 160 | 2128125.0, 161 | 2221875.0, 162 | 2278125.0, 163 | 2371875.0, 164 | 2521875.0, 165 | 2625000.0, 166 | 2728125.0, 167 | 2878125.0, 168 | 2971875.0, 169 | 3121875.0, 170 | 3271875.0, 171 | 3375000.0, 172 | 3525000.0, 173 | 3721875.0, 174 | 3871875.0, 175 | 4021875.0, 176 | 4228125.0, 177 | 4425000.0, 178 | 4575000.0, 179 | 4771875.0, 180 | 5025000.0, 181 | 5221875.0, 182 | 5475000.0, 183 | 5728125.0, 184 | 5971875.0, 185 | 6225000.0, 186 | 6478125.0, 187 | 6778125.0, 188 | 7078125.0, 189 | 7425000.0, 190 | 7725000.0, 191 | 8071875.0, 192 | 8428125.0, 193 | 8821875.0, 194 | 9178125.0, 195 | 9571875.0, 196 | 10021875.0, 197 | 10471875.0, 198 | 10921875.0, 199 | 11428125.0, 200 | 11925000.0, 201 | 12421875.0, 202 | 13021875.0, 203 | 13575000.0, 204 | 14175000.0, 205 | 14821875.0, 206 | 15478125.0, 207 | 16125000.0, 208 | 16875000.0, 209 | 17625000.0, 210 | 18375000.0, 211 | 19171876.0, 212 | ] 213 | * u.Hz, 214 | } 215 | array = np.zeros((64, 12359)) 216 | parse_path_moc.return_value = [(array, meta)] 217 | file = Path("fake.cdf") 218 | spec = Spectrogram(file) 219 | assert isinstance(spec, RFSSpectrogram) 220 | assert spec.observatory == "PSP" 221 | assert spec.instrument == "FIELDS/RFS" 222 | assert spec.detector == "HFR" 223 | # TODO check why not exact prob base on spacecrast ET so won't match utc exactly 224 | assert spec.start_time.datetime == datetime(2019, 4, 9, 0, 1, 13, 904188) 225 | assert spec.end_time.datetime == datetime(2019, 4, 10, 0, 1, 2, 758315) 226 | assert spec.wavelength.min == 1275.0 * u.kHz 227 | assert spec.wavelength.max == 19171.876 * u.kHz 228 | assert spec.level == "L2" 229 | assert spec.version == 1 230 | -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/data/eovsa_resp1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Index of /fits/synoptic/2020/10/05 5 | 6 | 7 |

Index of /fits/synoptic/2020/10/05

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
[ICO]NameLast modifiedSizeDescription

[PARENTDIR]Parent Directory  -  
[   ]EOVSA_TPall_20201005.fts2020-10-06 10:29 50M 
[   ]EOVSA_XPall_20201005.fts2020-10-06 10:29 50M 
[   ]eovsa_20201005.spw00-01.tb.disk.fits2020-10-07 12:54 878K 
[IMG]eovsa_20201005.spw00-01.tb.disk.jp22020-10-07 12:54 105K 
[   ]eovsa_20201005.spw00-01.tb.fits2020-10-07 12:54 875K 
[IMG]eovsa_20201005.spw00-01.tb.jp22020-10-07 12:54 105K 
[   ]eovsa_20201005.spw02-05.tb.disk.fits2020-10-07 12:54 759K 
[IMG]eovsa_20201005.spw02-05.tb.disk.jp22020-10-07 12:54 104K 
[   ]eovsa_20201005.spw02-05.tb.fits2020-10-07 12:54 759K 
[IMG]eovsa_20201005.spw02-05.tb.jp22020-10-07 12:54 104K 
[   ]eovsa_20201005.spw06-10.tb.disk.fits2020-10-07 12:54 512K 
[IMG]eovsa_20201005.spw06-10.tb.disk.jp22020-10-07 12:54 105K 
[   ]eovsa_20201005.spw06-10.tb.fits2020-10-07 12:54 512K 
[IMG]eovsa_20201005.spw06-10.tb.jp22020-10-07 12:54 105K 
[   ]eovsa_20201005.spw11-20.tb.disk.fits2020-10-07 12:54 518K 
[IMG]eovsa_20201005.spw11-20.tb.disk.jp22020-10-07 12:54 104K 
[   ]eovsa_20201005.spw11-20.tb.fits2020-10-07 12:54 518K 
[IMG]eovsa_20201005.spw11-20.tb.jp22020-10-07 12:54 105K 
[   ]eovsa_20201005.spw21-30.tb.disk.fits2020-10-07 12:54 529K 
[IMG]eovsa_20201005.spw21-30.tb.disk.jp22020-10-07 12:54 104K 
[   ]eovsa_20201005.spw21-30.tb.fits2020-10-07 12:54 529K 
[IMG]eovsa_20201005.spw21-30.tb.jp22020-10-07 12:54 104K 
[   ]eovsa_20201005.spw31-43.tb.disk.fits2020-10-07 12:54 520K 
[IMG]eovsa_20201005.spw31-43.tb.disk.jp22020-10-07 12:54 104K 
[   ]eovsa_20201005.spw31-43.tb.fits2020-10-07 12:54 520K 
[IMG]eovsa_20201005.spw31-43.tb.jp22020-10-07 12:54 105K 
[   ]eovsa_20201005.spw44-49.tb.disk.fits2020-10-07 12:54 537K 
[IMG]eovsa_20201005.spw44-49.tb.disk.jp22020-10-07 12:54 104K 
[   ]eovsa_20201005.spw44-49.tb.fits2020-10-07 12:54 537K 
[IMG]eovsa_20201005.spw44-49.tb.jp22020-10-07 12:54 104K 

44 |
Apache/2.4.18 (Ubuntu) Server at ovsa.njit.edu Port 80
45 | 46 | -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/data/eovsa_resp2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Index of /fits/synoptic/2020/10/06 5 | 6 | 7 |

Index of /fits/synoptic/2020/10/06

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
[ICO]NameLast modifiedSizeDescription

[PARENTDIR]Parent Directory  -  
[   ]EOVSA_TPall_20201006.fts2020-10-07 09:59 52M 
[   ]EOVSA_XPall_20201006.fts2020-10-07 10:00 52M 
[   ]eovsa_20201006.spw00-01.tb.disk.fits2020-10-08 12:59 928K 
[IMG]eovsa_20201006.spw00-01.tb.disk.jp22020-10-08 12:59 104K 
[   ]eovsa_20201006.spw00-01.tb.fits2020-10-08 12:59 928K 
[IMG]eovsa_20201006.spw00-01.tb.jp22020-10-08 12:59 104K 
[   ]eovsa_20201006.spw02-05.tb.disk.fits2020-10-08 12:59 751K 
[IMG]eovsa_20201006.spw02-05.tb.disk.jp22020-10-08 12:59 104K 
[   ]eovsa_20201006.spw02-05.tb.fits2020-10-08 12:59 751K 
[IMG]eovsa_20201006.spw02-05.tb.jp22020-10-08 12:59 104K 
[   ]eovsa_20201006.spw06-10.tb.disk.fits2020-10-08 12:59 518K 
[IMG]eovsa_20201006.spw06-10.tb.disk.jp22020-10-08 12:59 105K 
[   ]eovsa_20201006.spw06-10.tb.fits2020-10-08 12:59 518K 
[IMG]eovsa_20201006.spw06-10.tb.jp22020-10-08 12:59 104K 
[   ]eovsa_20201006.spw11-20.tb.disk.fits2020-10-08 12:59 520K 
[IMG]eovsa_20201006.spw11-20.tb.disk.jp22020-10-08 12:59 104K 
[   ]eovsa_20201006.spw11-20.tb.fits2020-10-08 12:59 518K 
[IMG]eovsa_20201006.spw11-20.tb.jp22020-10-08 12:59 105K 
[   ]eovsa_20201006.spw21-30.tb.disk.fits2020-10-08 12:59 529K 
[IMG]eovsa_20201006.spw21-30.tb.disk.jp22020-10-08 12:59 104K 
[   ]eovsa_20201006.spw21-30.tb.fits2020-10-08 12:59 529K 
[IMG]eovsa_20201006.spw21-30.tb.jp22020-10-08 12:59 104K 
[   ]eovsa_20201006.spw31-43.tb.disk.fits2020-10-08 12:59 520K 
[IMG]eovsa_20201006.spw31-43.tb.disk.jp22020-10-08 12:59 105K 
[   ]eovsa_20201006.spw31-43.tb.fits2020-10-08 12:59 520K 
[IMG]eovsa_20201006.spw31-43.tb.jp22020-10-08 12:59 104K 
[   ]eovsa_20201006.spw44-49.tb.disk.fits2020-10-08 12:59 537K 
[IMG]eovsa_20201006.spw44-49.tb.disk.jp22020-10-08 12:59 104K 
[   ]eovsa_20201006.spw44-49.tb.fits2020-10-08 12:59 537K 
[IMG]eovsa_20201006.spw44-49.tb.jp22020-10-08 12:59 104K 

44 |
Apache/2.4.18 (Ubuntu) Server at ovsa.njit.edu Port 80
45 | 46 | -------------------------------------------------------------------------------- /radiospectra/net/sources/tests/data/ecallisto_resp_alt_format.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | [   ] 4 | 5 | 6 | PHOENIX3-B1_20100327_075314i.fit.gz 7 | 8 | 2010-03-29 05:30 9 | 615K 10 |   11 | 12 | 13 | 14 | [   ] 15 | 16 | 17 | PHOENIX3-B1_20100327_075314p.fit.gz 18 | 19 | 2010-03-28 05:32 20 | 1.3M 21 |   22 | 23 | 24 | 25 | [   ] 26 | 27 | 28 | PHOENIX3-B1_20100327_075714i.fit.gz 29 | 30 | 2010-03-30 05:30 31 | 724K 32 |   33 | 34 | 35 | 36 | [   ] 37 | 38 | 39 | PHOENIX3-B1_20100327_075714p.fit.gz 40 | 41 | 2010-03-28 05:33 42 | 1.4M 43 |   44 | 45 | 46 | 47 | [   ] 48 | 49 | 50 | PHOENIX3-B1_20100327_080115i.fit.gz 51 | 52 | 2010-03-28 05:34 53 | 673K 54 |   55 | 56 | 57 | 58 | [   ] 59 | 60 | 61 | PHOENIX3-B1_20100327_080115p.fit.gz 62 | 63 | 2010-03-28 05:34 64 | 1.4M 65 |   66 | 67 | 68 | 69 | [   ] 70 | 71 | 72 | PHOENIX3-B1_20100327_093726i.fit.gz 73 | 74 | 2010-03-28 05:35 75 | 651K 76 |   77 | 78 | 79 | 80 | [   ] 81 | 82 | 83 | PHOENIX3-B1_20100327_093726p.fit.gz 84 | 85 | 2010-03-28 05:35 86 | 1.4M 87 |   88 | 89 | 90 | 91 | [   ] 92 | 93 | 94 | PHOENIX3-B1_20100327_094126i.fit.gz 95 | 96 | 2010-03-28 05:36 97 | 645K 98 |   99 | 100 | 101 | 102 | [   ] 103 | 104 | 105 | PHOENIX3-B1_20100327_094126p.fit.gz 106 | 107 | 2010-03-28 05:37 108 | 1.4M 109 |   110 | 111 | 112 | 113 | [   ] 114 | 115 | 116 | PHOENIX3-B1_20100327_094526i.fit.gz 117 | 118 | 2010-03-28 05:37 119 | 661K 120 |   121 | 122 | 123 | 124 | [   ] 125 | 126 | 127 | PHOENIX3-B1_20100327_094526p.fit.gz 128 | 129 | 2010-03-28 05:38 130 | 1.4M 131 |   132 | 133 | 134 | 135 | [   ] 136 | 137 | 138 | PHOENIX3-B1_20100327_100529i.fit.gz 139 | 140 | 2010-03-31 05:30 141 | 640K 142 |   143 | 144 | 145 | 146 | [   ] 147 | 148 | 149 | PHOENIX3-B1_20100327_100529p.fit.gz 150 | 151 | 2010-04-01 05:30 152 | 1.3M 153 |   154 | 155 | 156 | 157 | [   ] 158 | 159 | 160 | PHOENIX3-B1_20100327_100929i.fit.gz 161 | 162 | 2010-03-31 05:32 163 | 634K 164 |   165 | 166 | 167 | 168 | [   ] 169 | 170 | 171 | PHOENIX3-B1_20100327_100929p.fit.gz 172 | 173 | 2010-03-31 05:32 174 | 1.3M 175 |   176 | 177 | 178 | 179 | [   ] 180 | 181 | 182 | PHOENIX3-B1_20100327_101330i.fit.gz 183 | 184 | 2010-03-31 05:33 185 | 649K 186 |   187 | 188 | 189 | 190 | [   ] 191 | 192 | 193 | PHOENIX3-B1_20100327_101330p.fit.gz 194 | 195 | 2010-03-31 05:34 196 | 1.3M 197 |   198 | 199 | 200 | 201 | [   ] 202 | 203 | 204 | PHOENIX3-B1_20100327_101730i.fit.gz 205 | 206 | 2010-03-31 05:34 207 | 640K 208 |   209 | 210 | 211 | 212 | [   ] 213 | 214 | 215 | PHOENIX3-B1_20100327_101730p.fit.gz 216 | 217 | 2010-03-31 05:35 218 | 1.3M 219 |   220 | 221 | 222 | 223 | [   ] 224 | 225 | 226 | PHOENIX3-B1_20100327_115341i.fit.gz 227 | 228 | 2010-03-31 05:36 229 | 624K 230 |   231 | 232 | 233 | 234 | [   ] 235 | 236 | 237 | PHOENIX3-B1_20100327_115341p.fit.gz 238 | 239 | 2010-03-31 05:36 240 | 1.3M 241 |   242 | 243 | 244 | 245 | [   ] 246 | 247 | 248 | PHOENIX3-B1_20100327_121343i.fit.gz 249 | 250 | 2010-03-31 05:37 251 | 657K 252 |   253 | 254 | 255 | 256 | [   ] 257 | 258 | 259 | PHOENIX3-B1_20100327_121343p.fit.gz 260 | 261 | 2010-03-31 05:38 262 | 1.4M 263 |   264 | 265 | 266 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/tests/test_callisto.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from datetime import datetime 3 | from unittest import mock 4 | from unittest.mock import MagicMock 5 | 6 | import numpy as np 7 | 8 | import astropy.units as u 9 | from astropy.time import Time 10 | 11 | from sunpy.net import attrs as a 12 | 13 | from radiospectra.spectrogram import Spectrogram 14 | from radiospectra.spectrogram.sources import CALISTOSpectrogram 15 | 16 | 17 | @mock.patch("radiospectra.spectrogram.spectrogram_factory.parse_path") 18 | def test_callisto(parse_path_moc): 19 | start_time = Time("2011-06-07 06:24:00.213") 20 | meta = { 21 | "fits_meta": { 22 | "OBS_LAC": "N", 23 | "OBS_LAT": 53.0941390991211, 24 | "OBS_LOC": "E", 25 | "OBS_LON": 7.92012977600098, 26 | "OBS_ALT": 416.5, 27 | }, 28 | "detector": "e-CALLISTO", 29 | "instrument": "e-CALLISTO", 30 | "observatory": "BIR", 31 | "start_time": Time("2011-06-07 06:24:00.213"), 32 | "end_time": Time("2011-06-07 06:39:00.000"), 33 | "wavelength": a.Wavelength(20000.0 * u.kHz, 91813.00 * u.kHz), 34 | "times": start_time + np.arange(3600) * 0.25 * u.s, 35 | "freqs": [ 36 | 91.81300354003906, 37 | 91.25, 38 | 91.06300354003906, 39 | 90.625, 40 | 90.43800354003906, 41 | 89.75, 42 | 89.68800354003906, 43 | 89.0, 44 | 88.625, 45 | 88.25, 46 | 88.06300354003906, 47 | 87.56300354003906, 48 | 87.43800354003906, 49 | 87.06300354003906, 50 | 86.5, 51 | 86.06300354003906, 52 | 85.875, 53 | 85.56300354003906, 54 | 84.875, 55 | 84.68800354003906, 56 | 84.31300354003906, 57 | 83.875, 58 | 83.68800354003906, 59 | 83.0, 60 | 82.75, 61 | 82.43800354003906, 62 | 81.875, 63 | 81.75, 64 | 81.18800354003906, 65 | 80.75, 66 | 80.625, 67 | 80.25, 68 | 79.68800354003906, 69 | 79.25, 70 | 79.125, 71 | 78.68800354003906, 72 | 78.43800354003906, 73 | 78.06300354003906, 74 | 77.43800354003906, 75 | 77.0, 76 | 76.625, 77 | 76.56300354003906, 78 | 76.0, 79 | 75.56300354003906, 80 | 75.125, 81 | 75.0, 82 | 74.68800354003906, 83 | 74.31300354003906, 84 | 73.68800354003906, 85 | 73.31300354003906, 86 | 72.875, 87 | 72.625, 88 | 72.125, 89 | 71.75, 90 | 71.56300354003906, 91 | 71.0, 92 | 70.93800354003906, 93 | 70.25, 94 | 70.18800354003906, 95 | 69.68800354003906, 96 | 69.43800354003906, 97 | 69.06300354003906, 98 | 68.43800354003906, 99 | 68.06300354003906, 100 | 67.93800354003906, 101 | 67.31300354003906, 102 | 66.93800354003906, 103 | 66.81300354003906, 104 | 66.125, 105 | 66.06300354003906, 106 | 65.43800354003906, 107 | 65.0, 108 | 64.875, 109 | 64.25, 110 | 64.06300354003906, 111 | 63.8129997253418, 112 | 63.3129997253418, 113 | 62.75, 114 | 62.5, 115 | 62.0, 116 | 61.9379997253418, 117 | 61.5629997253418, 118 | 60.875, 119 | 60.75, 120 | 60.3129997253418, 121 | 59.9379997253418, 122 | 59.6879997253418, 123 | 59.0, 124 | 58.625, 125 | 58.25, 126 | 58.1879997253418, 127 | 57.5, 128 | 57.125, 129 | 56.9379997253418, 130 | 56.5, 131 | 56.3129997253418, 132 | 55.9379997253418, 133 | 55.4379997253418, 134 | 54.9379997253418, 135 | 54.8129997253418, 136 | 54.4379997253418, 137 | 53.9379997253418, 138 | 53.4379997253418, 139 | 53.125, 140 | 52.9379997253418, 141 | 52.5629997253418, 142 | 51.875, 143 | 51.5629997253418, 144 | 51.1879997253418, 145 | 50.8129997253418, 146 | 50.5629997253418, 147 | 50.0629997253418, 148 | 49.6879997253418, 149 | 49.3129997253418, 150 | 48.9379997253418, 151 | 48.8129997253418, 152 | 48.125, 153 | 48.0629997253418, 154 | 47.4379997253418, 155 | 47.25, 156 | 46.9379997253418, 157 | 46.25, 158 | 45.875, 159 | 45.8129997253418, 160 | 45.3129997253418, 161 | 45.0629997253418, 162 | 44.375, 163 | 44.1879997253418, 164 | 43.8129997253418, 165 | 43.3129997253418, 166 | 43.1879997253418, 167 | 42.5, 168 | 42.125, 169 | 42.0629997253418, 170 | 41.5629997253418, 171 | 41.1879997253418, 172 | 40.6879997253418, 173 | 40.4379997253418, 174 | 39.875, 175 | 39.8129997253418, 176 | 39.4379997253418, 177 | 38.75, 178 | 38.375, 179 | 38.0629997253418, 180 | 37.6879997253418, 181 | 37.5629997253418, 182 | 36.875, 183 | 36.8129997253418, 184 | 36.25, 185 | 36.0629997253418, 186 | 35.6879997253418, 187 | 35.0629997253418, 188 | 34.875, 189 | 34.5, 190 | 34.125, 191 | 33.8129997253418, 192 | 33.125, 193 | 33.0, 194 | 32.5629997253418, 195 | 32.0629997253418, 196 | 31.937999725341797, 197 | 31.25, 198 | 30.875, 199 | 30.562999725341797, 200 | 30.125, 201 | 30.062999725341797, 202 | 29.375, 203 | 29.312999725341797, 204 | 28.875, 205 | 28.25, 206 | 28.187999725341797, 207 | 27.562999725341797, 208 | 27.125, 209 | 26.75, 210 | 26.687999725341797, 211 | 26.125, 212 | 25.75, 213 | 25.375, 214 | 25.0, 215 | 24.812999725341797, 216 | 24.125, 217 | 23.812999725341797, 218 | 23.437999725341797, 219 | 23.125, 220 | 22.812999725341797, 221 | 22.437999725341797, 222 | 21.875, 223 | 21.812999725341797, 224 | 21.125, 225 | 20.75, 226 | 20.375, 227 | 20.0, 228 | 20.0, 229 | 20.0, 230 | 20.0, 231 | 20.0, 232 | 20.0, 233 | 20.0, 234 | 20.0, 235 | 20.0, 236 | ] 237 | * u.MHz, 238 | } 239 | array = np.zeros((200, 3600)) 240 | parse_path_moc.return_value = [(array, meta)] 241 | file = Path("fake.fit.gz") 242 | spec = Spectrogram(file) 243 | assert isinstance(spec, CALISTOSpectrogram) 244 | assert spec.observatory == "BIR" 245 | assert spec.instrument == "E-CALLISTO" 246 | assert spec.detector == "E-CALLISTO" 247 | assert spec.start_time.datetime == datetime(2011, 6, 7, 6, 24, 0, 213000) 248 | assert spec.end_time.datetime == datetime(2011, 6, 7, 6, 39) 249 | assert spec.wavelength.min.to(u.MHz) == 20 * u.MHz 250 | assert spec.wavelength.max.to(u.MHz).round(1) == 91.8 * u.MHz 251 | assert spec.observatory_location.value.tolist() == (3801942.212601484, 528924.6036780173, 5077174.568618115) 252 | assert spec.observatory_location.unit == u.m 253 | 254 | 255 | @mock.patch("sunpy.util.io.is_file") 256 | @mock.patch("radiospectra.spectrogram.spectrogram_factory.fits.open") 257 | def test_callisto_hour_rollover(hdul_moc, is_file_mock): 258 | is_file_mock.return_value = True 259 | freqs = [ 260 | 91.81300354003906, 261 | 91.25, 262 | 91.06300354003906, 263 | 90.625, 264 | 90.43800354003906, 265 | 89.75, 266 | 89.68800354003906, 267 | 89.0, 268 | 88.625, 269 | 88.25, 270 | 88.06300354003906, 271 | 87.56300354003906, 272 | 87.43800354003906, 273 | 87.06300354003906, 274 | 86.5, 275 | 86.06300354003906, 276 | 85.875, 277 | 85.56300354003906, 278 | 84.875, 279 | 84.68800354003906, 280 | 84.31300354003906, 281 | 83.875, 282 | 83.68800354003906, 283 | 83.0, 284 | 82.75, 285 | 82.43800354003906, 286 | 81.875, 287 | 81.75, 288 | 81.18800354003906, 289 | 80.75, 290 | 80.625, 291 | 80.25, 292 | 79.68800354003906, 293 | 79.25, 294 | 79.125, 295 | 78.68800354003906, 296 | 78.43800354003906, 297 | 78.06300354003906, 298 | 77.43800354003906, 299 | 77.0, 300 | 76.625, 301 | 76.56300354003906, 302 | 76.0, 303 | 75.56300354003906, 304 | 75.125, 305 | 75.0, 306 | 74.68800354003906, 307 | 74.31300354003906, 308 | 73.68800354003906, 309 | 73.31300354003906, 310 | 72.875, 311 | 72.625, 312 | 72.125, 313 | 71.75, 314 | 71.56300354003906, 315 | 71.0, 316 | 70.93800354003906, 317 | 70.25, 318 | 70.18800354003906, 319 | 69.68800354003906, 320 | 69.43800354003906, 321 | 69.06300354003906, 322 | 68.43800354003906, 323 | 68.06300354003906, 324 | 67.93800354003906, 325 | 67.31300354003906, 326 | 66.93800354003906, 327 | 66.81300354003906, 328 | 66.125, 329 | 66.06300354003906, 330 | 65.43800354003906, 331 | 65.0, 332 | 64.875, 333 | 64.25, 334 | 64.06300354003906, 335 | 63.8129997253418, 336 | 63.3129997253418, 337 | 62.75, 338 | 62.5, 339 | 62.0, 340 | 61.9379997253418, 341 | 61.5629997253418, 342 | 60.875, 343 | 60.75, 344 | 60.3129997253418, 345 | 59.9379997253418, 346 | 59.6879997253418, 347 | 59.0, 348 | 58.625, 349 | 58.25, 350 | 58.1879997253418, 351 | 57.5, 352 | 57.125, 353 | 56.9379997253418, 354 | 56.5, 355 | 56.3129997253418, 356 | 55.9379997253418, 357 | 55.4379997253418, 358 | 54.9379997253418, 359 | 54.8129997253418, 360 | 54.4379997253418, 361 | 53.9379997253418, 362 | 53.4379997253418, 363 | 53.125, 364 | 52.9379997253418, 365 | 52.5629997253418, 366 | 51.875, 367 | 51.5629997253418, 368 | 51.1879997253418, 369 | 50.8129997253418, 370 | 50.5629997253418, 371 | 50.0629997253418, 372 | 49.6879997253418, 373 | 49.3129997253418, 374 | 48.9379997253418, 375 | 48.8129997253418, 376 | 48.125, 377 | 48.0629997253418, 378 | 47.4379997253418, 379 | 47.25, 380 | 46.9379997253418, 381 | 46.25, 382 | 45.875, 383 | 45.8129997253418, 384 | 45.3129997253418, 385 | 45.0629997253418, 386 | 44.375, 387 | 44.1879997253418, 388 | 43.8129997253418, 389 | 43.3129997253418, 390 | 43.1879997253418, 391 | 42.5, 392 | 42.125, 393 | 42.0629997253418, 394 | 41.5629997253418, 395 | 41.1879997253418, 396 | 40.6879997253418, 397 | 40.4379997253418, 398 | 39.875, 399 | 39.8129997253418, 400 | 39.4379997253418, 401 | 38.75, 402 | 38.375, 403 | 38.0629997253418, 404 | 37.6879997253418, 405 | 37.5629997253418, 406 | 36.875, 407 | 36.8129997253418, 408 | 36.25, 409 | 36.0629997253418, 410 | 35.6879997253418, 411 | 35.0629997253418, 412 | 34.875, 413 | 34.5, 414 | 34.125, 415 | 33.8129997253418, 416 | 33.125, 417 | 33.0, 418 | 32.5629997253418, 419 | 32.0629997253418, 420 | 31.937999725341797, 421 | 31.25, 422 | 30.875, 423 | 30.562999725341797, 424 | 30.125, 425 | 30.062999725341797, 426 | 29.375, 427 | 29.312999725341797, 428 | 28.875, 429 | 28.25, 430 | 28.187999725341797, 431 | 27.562999725341797, 432 | 27.125, 433 | 26.75, 434 | 26.687999725341797, 435 | 26.125, 436 | 25.75, 437 | 25.375, 438 | 25.0, 439 | 24.812999725341797, 440 | 24.125, 441 | 23.812999725341797, 442 | 23.437999725341797, 443 | 23.125, 444 | 22.812999725341797, 445 | 22.437999725341797, 446 | 21.875, 447 | 21.812999725341797, 448 | 21.125, 449 | 20.75, 450 | 20.375, 451 | 20.0, 452 | 20.0, 453 | 20.0, 454 | 20.0, 455 | 20.0, 456 | 20.0, 457 | 20.0, 458 | 20.0, 459 | 20.0, 460 | ] 461 | 462 | header = { 463 | "CONTENT": "e-CALLISTO", 464 | "DATE-OBS": "2011/06/07", 465 | "TIME-OBS": "23:46:06", 466 | "DATE-END": "2011/06/07", 467 | "TIME-END": "24:01:06", 468 | "INSTRUME": "Bir", 469 | "OBS_LAC": "N", 470 | "OBS_LAT": 53.0941390991211, 471 | "OBS_LOC": "E", 472 | "OBS_LON": 7.92012977600098, 473 | "OBS_ALT": 416.5, 474 | } 475 | array = np.zeros((200, 3600)) 476 | hdu0 = MagicMock() 477 | hdu1 = MagicMock() 478 | hdu0.header = header 479 | hdu0.data = array 480 | hdu1.data = { 481 | "TIME": np.arange(3600) * 0.25, 482 | "FREQUENCY": np.array(freqs), 483 | } 484 | hdul_moc.return_value = [hdu0, hdu1] 485 | file = Path("fake.fit.gz") 486 | spec = Spectrogram(file) 487 | assert isinstance(spec, CALISTOSpectrogram) 488 | assert spec.observatory == "BIR" 489 | assert spec.instrument == "E-CALLISTO" 490 | assert spec.detector == "E-CALLISTO" 491 | assert spec.start_time.datetime == datetime(2011, 6, 7, 23, 46, 6, 0) 492 | assert spec.end_time.datetime == datetime(2011, 6, 8, 0, 1, 6, 0) 493 | assert spec.wavelength.min.to(u.MHz) == 20 * u.MHz 494 | assert spec.wavelength.max.to(u.MHz).round(1) == 91.8 * u.MHz 495 | assert spec.observatory_location.value.tolist() == (3801942.212601484, 528924.6036780173, 5077174.568618115) 496 | assert spec.observatory_location.unit == u.m 497 | -------------------------------------------------------------------------------- /radiospectra/spectrogram/sources/tests/test_eovsa.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from datetime import datetime 3 | from unittest import mock 4 | 5 | import numpy as np 6 | import pytest 7 | 8 | import astropy.units as u 9 | from astropy.time import Time 10 | 11 | from sunpy.net import attrs as a 12 | 13 | from radiospectra.spectrogram import Spectrogram 14 | from radiospectra.spectrogram.sources import EOVSASpectrogram 15 | 16 | 17 | @pytest.fixture 18 | def eovsa_data(): 19 | start_time = Time("2021-02-13 15:41:20.999") 20 | end_time = Time("2021-02-13 20:43:18.999") 21 | meta = { 22 | "fits_meta": {"POLARIZA": "I"}, 23 | "detector": "EOVSA", 24 | "instrument": "EOVSA", 25 | "observatory": "Owens Valley", 26 | "start_time": start_time, 27 | "end_time": end_time, 28 | "wavelength": a.Wavelength(1105371.117591858 * u.kHz, 17979686.737060547 * u.kHz), 29 | "times": np.linspace(0, (end_time - start_time).to_value("s"), 15463), 30 | "freqs": [ 31 | 1.1053711, 32 | 1.1161133, 33 | 1.1268555, 34 | 1.1375977, 35 | 1.1483399, 36 | 1.159082, 37 | 1.1698242, 38 | 1.1805664, 39 | 1.1913086, 40 | 1.2020508, 41 | 1.212793, 42 | 1.2235352, 43 | 1.2342774, 44 | 1.2450196, 45 | 1.2557617, 46 | 1.2665039, 47 | 1.2772461, 48 | 1.2879883, 49 | 1.2987305, 50 | 1.3094727, 51 | 1.3202149, 52 | 1.330957, 53 | 1.3416992, 54 | 1.3524414, 55 | 1.3631836, 56 | 1.3739258, 57 | 1.384668, 58 | 1.3954102, 59 | 1.4061524, 60 | 1.4168946, 61 | 1.432373, 62 | 1.4471191, 63 | 1.4618652, 64 | 1.4766114, 65 | 1.4913574, 66 | 1.5061035, 67 | 1.5208496, 68 | 1.5355957, 69 | 1.5503418, 70 | 1.5650879, 71 | 1.579834, 72 | 1.59458, 73 | 1.6093261, 74 | 1.6240723, 75 | 1.6388184, 76 | 1.6535645, 77 | 1.6683105, 78 | 1.6830566, 79 | 1.6978028, 80 | 1.7125489, 81 | 1.7272949, 82 | 1.742041, 83 | 2.4125, 84 | 2.4375, 85 | 2.4625, 86 | 2.4875, 87 | 2.5125, 88 | 2.5375, 89 | 2.5625, 90 | 2.5875, 91 | 2.6125, 92 | 2.6375, 93 | 2.6625, 94 | 2.6875, 95 | 2.7125, 96 | 2.7385254, 97 | 2.7655761, 98 | 2.7926269, 99 | 2.8196778, 100 | 2.8467286, 101 | 2.8737793, 102 | 2.90083, 103 | 2.9278808, 104 | 2.9549317, 105 | 2.9819825, 106 | 3.0090332, 107 | 3.036084, 108 | 3.0647461, 109 | 3.0942383, 110 | 3.1237304, 111 | 3.1532226, 112 | 3.182715, 113 | 3.212207, 114 | 3.2416992, 115 | 3.2711914, 116 | 3.3006835, 117 | 3.3301759, 118 | 3.359668, 119 | 3.391211, 120 | 3.4236329, 121 | 3.4560547, 122 | 3.4884765, 123 | 3.5208983, 124 | 3.5533204, 125 | 3.5857422, 126 | 3.618164, 127 | 3.650586, 128 | 3.6830077, 129 | 3.7180176, 130 | 3.7540526, 131 | 3.790088, 132 | 3.826123, 133 | 3.8621583, 134 | 3.8981934, 135 | 3.9342284, 136 | 3.9702637, 137 | 4.006299, 138 | 4.0453124, 139 | 4.0859375, 140 | 4.1265626, 141 | 4.1671877, 142 | 4.2078123, 143 | 4.2484374, 144 | 4.2890625, 145 | 4.3296876, 146 | 4.3703127, 147 | 4.4109373, 148 | 4.4515624, 149 | 4.4921875, 150 | 4.5328126, 151 | 4.5734377, 152 | 4.6140623, 153 | 4.6546874, 154 | 4.6953125, 155 | 4.7359376, 156 | 4.7765627, 157 | 4.8171873, 158 | 4.8578124, 159 | 4.8984375, 160 | 4.9390626, 161 | 4.9796877, 162 | 5.0203123, 163 | 5.0609374, 164 | 5.1015625, 165 | 5.1421876, 166 | 5.1828127, 167 | 5.2234373, 168 | 5.2640624, 169 | 5.3046875, 170 | 5.3453126, 171 | 5.3859377, 172 | 5.4265623, 173 | 5.4671874, 174 | 5.5078125, 175 | 5.5484376, 176 | 5.5890627, 177 | 5.6296873, 178 | 5.6703124, 179 | 5.7109375, 180 | 5.7515626, 181 | 5.7921877, 182 | 5.8328123, 183 | 5.8734374, 184 | 5.9140625, 185 | 5.9546876, 186 | 5.9953127, 187 | 6.0359373, 188 | 6.0765624, 189 | 6.1171875, 190 | 6.1578126, 191 | 6.1984377, 192 | 6.2390623, 193 | 6.2796874, 194 | 6.3203125, 195 | 6.3609376, 196 | 6.4015627, 197 | 6.4421873, 198 | 6.4828124, 199 | 6.5234375, 200 | 6.5640626, 201 | 6.6046877, 202 | 6.6453123, 203 | 6.6859374, 204 | 6.7265625, 205 | 6.7671876, 206 | 6.8078127, 207 | 6.8484373, 208 | 6.8890624, 209 | 6.9296875, 210 | 6.9703126, 211 | 7.0109377, 212 | 7.0515623, 213 | 7.0921874, 214 | 7.1328125, 215 | 7.1734376, 216 | 7.2140627, 217 | 7.2546873, 218 | 7.2953124, 219 | 7.3359375, 220 | 7.3765626, 221 | 7.4171877, 222 | 7.4578123, 223 | 7.4984374, 224 | 7.5390625, 225 | 7.5796876, 226 | 7.6203127, 227 | 7.6609373, 228 | 7.7015624, 229 | 7.7421875, 230 | 7.7828126, 231 | 7.8234377, 232 | 7.8640623, 233 | 7.9046874, 234 | 7.9453125, 235 | 7.9859376, 236 | 8.026563, 237 | 8.067187, 238 | 8.107813, 239 | 8.1484375, 240 | 8.189062, 241 | 8.229688, 242 | 8.270312, 243 | 8.310938, 244 | 8.3515625, 245 | 8.392187, 246 | 8.432813, 247 | 8.473437, 248 | 8.514063, 249 | 8.5546875, 250 | 8.595312, 251 | 8.635938, 252 | 8.676562, 253 | 8.717188, 254 | 8.7578125, 255 | 8.798437, 256 | 8.839063, 257 | 8.879687, 258 | 8.920313, 259 | 8.9609375, 260 | 9.001562, 261 | 9.042188, 262 | 9.082812, 263 | 9.123438, 264 | 9.1640625, 265 | 9.204687, 266 | 9.245313, 267 | 9.285937, 268 | 9.326563, 269 | 9.3671875, 270 | 9.407812, 271 | 9.448438, 272 | 9.489062, 273 | 9.529688, 274 | 9.5703125, 275 | 9.610937, 276 | 9.651563, 277 | 9.692187, 278 | 9.732813, 279 | 9.7734375, 280 | 9.814062, 281 | 9.854688, 282 | 9.895312, 283 | 9.935938, 284 | 9.9765625, 285 | 10.017187, 286 | 10.057813, 287 | 10.098437, 288 | 10.139063, 289 | 10.1796875, 290 | 10.220312, 291 | 10.260938, 292 | 10.301562, 293 | 10.342188, 294 | 10.3828125, 295 | 10.423437, 296 | 10.464063, 297 | 10.504687, 298 | 10.545313, 299 | 10.5859375, 300 | 10.626562, 301 | 10.667188, 302 | 10.707812, 303 | 10.748438, 304 | 10.7890625, 305 | 10.829687, 306 | 10.870313, 307 | 10.910937, 308 | 10.951563, 309 | 10.9921875, 310 | 11.032812, 311 | 11.073438, 312 | 11.114062, 313 | 11.154688, 314 | 11.1953125, 315 | 11.235937, 316 | 11.276563, 317 | 11.317187, 318 | 11.357813, 319 | 11.3984375, 320 | 11.439062, 321 | 11.479688, 322 | 11.520312, 323 | 11.560938, 324 | 11.6015625, 325 | 11.642187, 326 | 11.682813, 327 | 11.723437, 328 | 11.764063, 329 | 11.8046875, 330 | 11.845312, 331 | 11.885938, 332 | 11.926562, 333 | 11.967188, 334 | 12.0078125, 335 | 12.048437, 336 | 12.089063, 337 | 12.129687, 338 | 12.170313, 339 | 12.2109375, 340 | 12.251562, 341 | 12.292188, 342 | 12.332812, 343 | 12.373438, 344 | 12.4140625, 345 | 12.454687, 346 | 12.495313, 347 | 12.535937, 348 | 12.576563, 349 | 12.6171875, 350 | 12.657812, 351 | 12.698438, 352 | 12.739062, 353 | 12.779688, 354 | 12.8203125, 355 | 12.860937, 356 | 12.901563, 357 | 12.942187, 358 | 12.982813, 359 | 13.0234375, 360 | 13.064062, 361 | 13.104688, 362 | 13.145312, 363 | 13.185938, 364 | 13.2265625, 365 | 13.267187, 366 | 13.307813, 367 | 13.348437, 368 | 13.389063, 369 | 13.4296875, 370 | 13.470312, 371 | 13.510938, 372 | 13.551562, 373 | 13.592188, 374 | 13.6328125, 375 | 13.673437, 376 | 13.714063, 377 | 13.754687, 378 | 13.795313, 379 | 13.8359375, 380 | 13.876562, 381 | 13.917188, 382 | 13.957812, 383 | 13.998438, 384 | 14.0390625, 385 | 14.079687, 386 | 14.120313, 387 | 14.160937, 388 | 14.201563, 389 | 14.2421875, 390 | 14.282812, 391 | 14.323438, 392 | 14.364062, 393 | 14.404688, 394 | 14.4453125, 395 | 14.485937, 396 | 14.526563, 397 | 14.567187, 398 | 14.607813, 399 | 14.6484375, 400 | 14.689062, 401 | 14.729688, 402 | 14.770312, 403 | 14.810938, 404 | 14.8515625, 405 | 14.892187, 406 | 14.932813, 407 | 14.973437, 408 | 15.014063, 409 | 15.0546875, 410 | 15.095312, 411 | 15.135938, 412 | 15.176562, 413 | 15.217188, 414 | 15.2578125, 415 | 15.298437, 416 | 15.339063, 417 | 15.379687, 418 | 15.420313, 419 | 15.4609375, 420 | 15.501562, 421 | 15.542188, 422 | 15.582812, 423 | 15.623438, 424 | 15.6640625, 425 | 15.704687, 426 | 15.745313, 427 | 15.785937, 428 | 15.826563, 429 | 15.8671875, 430 | 15.907812, 431 | 15.948438, 432 | 15.989062, 433 | 16.029688, 434 | 16.070312, 435 | 16.110937, 436 | 16.151562, 437 | 16.192188, 438 | 16.232813, 439 | 16.273438, 440 | 16.314062, 441 | 16.354687, 442 | 16.395313, 443 | 16.435938, 444 | 16.476562, 445 | 16.517187, 446 | 16.557812, 447 | 16.598438, 448 | 16.639063, 449 | 16.679688, 450 | 16.720312, 451 | 16.760937, 452 | 16.801563, 453 | 16.842188, 454 | 16.882812, 455 | 16.923437, 456 | 16.964062, 457 | 17.004688, 458 | 17.045313, 459 | 17.085938, 460 | 17.126562, 461 | 17.167187, 462 | 17.207813, 463 | 17.248438, 464 | 17.289062, 465 | 17.329687, 466 | 17.370312, 467 | 17.410938, 468 | 17.451563, 469 | 17.492188, 470 | 17.532812, 471 | 17.573437, 472 | 17.614063, 473 | 17.654688, 474 | 17.695312, 475 | 17.735937, 476 | 17.776562, 477 | 17.817188, 478 | 17.857813, 479 | 17.898438, 480 | 17.939062, 481 | 17.979687, 482 | ] 483 | * u.GHz, 484 | } 485 | array = np.zeros((451, 15463)) 486 | return meta, array 487 | 488 | 489 | @mock.patch("radiospectra.spectrogram.spectrogram_factory.parse_path") 490 | def test_eovsa_xpall(parse_path_moc, eovsa_data): 491 | meta, array = eovsa_data 492 | parse_path_moc.return_value = [(array, meta)] 493 | file = Path("fake.fts") 494 | spec = Spectrogram(file) 495 | assert isinstance(spec, EOVSASpectrogram) 496 | assert spec.observatory == "OWENS VALLEY" 497 | assert spec.instrument == "EOVSA" 498 | assert spec.detector == "EOVSA" 499 | assert spec.start_time.datetime == datetime(2021, 2, 13, 15, 41, 20, 999000) 500 | assert spec.end_time.datetime == datetime(2021, 2, 13, 20, 43, 18, 999000) 501 | assert spec.wavelength.min.to(u.GHz) == 1.105371117591858 * u.GHz 502 | assert spec.wavelength.max.to(u.GHz) == 17.979686737060547 * u.GHz 503 | assert spec.polarisation == "I" 504 | 505 | 506 | @mock.patch("radiospectra.spectrogram.spectrogram_factory.parse_path") 507 | def test_eovsa_tpall(parse_path_moc, eovsa_data): 508 | meta, array = eovsa_data 509 | meta["fits_meta"]["POLARIZA"] = "I" 510 | parse_path_moc.return_value = [(array, meta)] 511 | file = Path("fake.fts") 512 | spec = Spectrogram(file) 513 | assert isinstance(spec, EOVSASpectrogram) 514 | assert spec.observatory == "OWENS VALLEY" 515 | assert spec.instrument == "EOVSA" 516 | assert spec.detector == "EOVSA" 517 | assert spec.start_time.datetime == datetime(2021, 2, 13, 15, 41, 20, 999000) 518 | assert spec.end_time.datetime == datetime(2021, 2, 13, 20, 43, 18, 999000) 519 | assert spec.wavelength.min.to(u.GHz) == 1.105371117591858 * u.GHz 520 | assert spec.wavelength.max.to(u.GHz) == 17.979686737060547 * u.GHz 521 | assert spec.polarisation == "I" 522 | --------------------------------------------------------------------------------