aiapy 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aiapy/CITATION.rst +31 -0
- aiapy/__init__.py +29 -0
- aiapy/_version.py +16 -0
- aiapy/calibrate/__init__.py +9 -0
- aiapy/calibrate/meta.py +191 -0
- aiapy/calibrate/prep.py +231 -0
- aiapy/calibrate/spikes.py +184 -0
- aiapy/calibrate/tests/__init__.py +0 -0
- aiapy/calibrate/tests/test_meta.py +131 -0
- aiapy/calibrate/tests/test_prep.py +267 -0
- aiapy/calibrate/tests/test_spikes.py +104 -0
- aiapy/calibrate/tests/test_uncertainty.py +69 -0
- aiapy/calibrate/tests/test_util.py +138 -0
- aiapy/calibrate/transform.py +42 -0
- aiapy/calibrate/uncertainty.py +135 -0
- aiapy/calibrate/util.py +253 -0
- aiapy/conftest.py +70 -0
- aiapy/data/__init__.py +0 -0
- aiapy/data/_sample.py +136 -0
- aiapy/data/sample.py +79 -0
- aiapy/psf/__init__.py +2 -0
- aiapy/psf/deconvolve.py +101 -0
- aiapy/psf/psf.py +328 -0
- aiapy/psf/tests/__init__.py +0 -0
- aiapy/psf/tests/conftest.py +12 -0
- aiapy/psf/tests/test_deconvolve.py +34 -0
- aiapy/psf/tests/test_psf.py +27 -0
- aiapy/response/__init__.py +5 -0
- aiapy/response/channel.py +427 -0
- aiapy/response/tests/__init__.py +0 -0
- aiapy/response/tests/test_channel.py +236 -0
- aiapy/tests/__init__.py +0 -0
- aiapy/tests/data/__init__.py +25 -0
- aiapy/tests/data/aia_V3_error_table.txt +11 -0
- aiapy/tests/data/aia_V8_20171210_050627_response_table.txt +89 -0
- aiapy/tests/test_idl.py +87 -0
- aiapy/tests/test_init.py +5 -0
- aiapy/util/__init__.py +6 -0
- aiapy/util/decorators.py +49 -0
- aiapy/util/exceptions.py +24 -0
- aiapy/util/tests/__init__.py +0 -0
- aiapy/util/tests/test_util.py +19 -0
- aiapy/util/util.py +87 -0
- aiapy/version.py +23 -0
- aiapy-0.8.0.dist-info/LICENSE.rst +25 -0
- aiapy-0.8.0.dist-info/METADATA +101 -0
- aiapy-0.8.0.dist-info/RECORD +49 -0
- aiapy-0.8.0.dist-info/WHEEL +5 -0
- aiapy-0.8.0.dist-info/top_level.txt +1 -0
aiapy/CITATION.rst
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Acknowledging or Citing aiapy
|
|
2
|
+
=============================
|
|
3
|
+
|
|
4
|
+
If you use aiapy in your scientific work, we would appreciate you citing it in your publications.
|
|
5
|
+
|
|
6
|
+
Please add the following line within your methods, conclusion or acknowledgements sections:
|
|
7
|
+
|
|
8
|
+
*This research used version X.Y.Z (software citation) of the aiapy open source
|
|
9
|
+
software package (project citation).*
|
|
10
|
+
|
|
11
|
+
The project citation should be to the `aiapy paper`_ and the software citation should be the specific `Zenodo DOI`_ for the version used within your work.
|
|
12
|
+
|
|
13
|
+
.. code:: bibtex
|
|
14
|
+
|
|
15
|
+
@article{Barnes2020,
|
|
16
|
+
doi = {10.21105/joss.02801},
|
|
17
|
+
url = {https://doi.org/10.21105/joss.02801},
|
|
18
|
+
year = {2020},
|
|
19
|
+
publisher = {The Open Journal},
|
|
20
|
+
volume = {5},
|
|
21
|
+
number = {55},
|
|
22
|
+
pages = {2801},
|
|
23
|
+
author = {Will T. Barnes and Mark C. M. Cheung and Monica G. Bobra and Paul F. Boerner and Georgios Chintzoglou and Drew Leonard and Stuart J. Mumford and Nicholas Padmanabhan and Albert Y. Shih and Nina Shirman and David Stansby and Paul J. Wright},
|
|
24
|
+
title = {aiapy: A Python Package for Analyzing Solar EUV Image Data from AIA},
|
|
25
|
+
journal = {Journal of Open Source Software}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
You can also obtain this information with ``aiapy.__citation__``.
|
|
29
|
+
|
|
30
|
+
.. _Zenodo DOI: https://zenodo.org/record/4274931
|
|
31
|
+
.. _aiapy paper: https://joss.theoj.org/papers/10.21105/joss.02801
|
aiapy/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from itertools import compress
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from .version import version as __version__
|
|
5
|
+
|
|
6
|
+
_SSW_MIRRORS = [
|
|
7
|
+
"https://soho.nascom.nasa.gov/solarsoft/",
|
|
8
|
+
"https://hesperia.gsfc.nasa.gov/ssw/",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _get_bibtex():
|
|
13
|
+
import textwrap
|
|
14
|
+
|
|
15
|
+
# Set the bibtex entry to the article referenced in CITATION.rst
|
|
16
|
+
citation_file = Path(__file__).parent / "CITATION.rst"
|
|
17
|
+
|
|
18
|
+
# Explicitly specify UTF-8 encoding in case the system's default encoding is problematic
|
|
19
|
+
with Path.open(citation_file, "r", encoding="utf-8") as citation:
|
|
20
|
+
# Extract the first bibtex block:
|
|
21
|
+
ref = citation.read().partition(".. code:: bibtex\n\n")[2]
|
|
22
|
+
lines = ref.split("\n")
|
|
23
|
+
# Only read the lines which are indented
|
|
24
|
+
lines = list(compress(lines, [line.startswith(" ") for line in lines]))
|
|
25
|
+
return textwrap.dedent("\n".join(lines))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
__citation__ = __bibtex__ = _get_bibtex()
|
|
29
|
+
__all__ = ["__version__", "__citation__", "_SSW_MIRRORS"]
|
aiapy/_version.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file generated by setuptools_scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '0.8.0'
|
|
16
|
+
__version_tuple__ = version_tuple = (0, 8, 0)
|
aiapy/calibrate/meta.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Functions for updating/fixing header keywords.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
import astropy.time
|
|
9
|
+
import astropy.units as u
|
|
10
|
+
import numpy as np
|
|
11
|
+
from astropy.coordinates import CartesianRepresentation, HeliocentricMeanEcliptic, SkyCoord
|
|
12
|
+
from sunpy.map import contains_full_disk
|
|
13
|
+
|
|
14
|
+
from aiapy.calibrate.util import get_pointing_table
|
|
15
|
+
from aiapy.util.exceptions import AiapyUserWarning
|
|
16
|
+
|
|
17
|
+
__all__ = ["fix_observer_location", "update_pointing"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def fix_observer_location(smap):
|
|
21
|
+
"""
|
|
22
|
+
Fix inaccurate ``HGS_LON`` and ``HGS_LAT`` FITS keywords.
|
|
23
|
+
|
|
24
|
+
The heliographic Stonyhurst latitude and longitude locations in the
|
|
25
|
+
AIA FITS headers are incorrect. This function fixes the values of these
|
|
26
|
+
keywords using the heliocentric aries ecliptic keywords, ``HAEX_OBS,
|
|
27
|
+
HAEY_OBS, HAEZ_OBS``.
|
|
28
|
+
|
|
29
|
+
.. note::
|
|
30
|
+
|
|
31
|
+
`~sunpy.map.sources.AIAMap` already accounts for the inaccurate
|
|
32
|
+
HGS keywords by using the HAE keywords to construct the
|
|
33
|
+
derived observer location.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
smap : `~sunpy.map.sources.AIAMap`
|
|
38
|
+
Input map.
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
`~sunpy.map.sources.AIAMap`
|
|
43
|
+
"""
|
|
44
|
+
# Create observer coordinate from HAE coordinates
|
|
45
|
+
coord = SkyCoord(
|
|
46
|
+
x=smap.meta["haex_obs"] * u.m,
|
|
47
|
+
y=smap.meta["haey_obs"] * u.m,
|
|
48
|
+
z=smap.meta["haez_obs"] * u.m,
|
|
49
|
+
representation_type=CartesianRepresentation,
|
|
50
|
+
frame=HeliocentricMeanEcliptic,
|
|
51
|
+
obstime=smap.date,
|
|
52
|
+
).heliographic_stonyhurst
|
|
53
|
+
# Update header
|
|
54
|
+
new_meta = copy.deepcopy(smap.meta)
|
|
55
|
+
new_meta["hgln_obs"] = coord.lon.to(u.degree).value
|
|
56
|
+
new_meta["hglt_obs"] = coord.lat.to(u.degree).value
|
|
57
|
+
new_meta["dsun_obs"] = coord.radius.to(u.m).value
|
|
58
|
+
|
|
59
|
+
return smap._new_instance(smap.data, new_meta, plot_settings=smap.plot_settings, mask=smap.mask) # NOQA: SLF001
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def update_pointing(smap, *, pointing_table=None):
|
|
63
|
+
"""
|
|
64
|
+
Update the pointing information in the input map header.
|
|
65
|
+
|
|
66
|
+
This function updates the pointing information in ``smap`` by
|
|
67
|
+
updating the ``CRPIX1, CRPIX2, CDELT1, CDELT2, CROTA2`` keywords
|
|
68
|
+
in the header using the information provided in ``pointing_table``.
|
|
69
|
+
If ``pointing_table`` is not specified, the 3-hour pointing
|
|
70
|
+
information is queried from the `JSOC <http://jsoc.stanford.edu/>`_.
|
|
71
|
+
|
|
72
|
+
.. note::
|
|
73
|
+
|
|
74
|
+
The method removes any ``PCi_j`` matrix keys in the header and
|
|
75
|
+
updates the ``CROTA2`` keyword.
|
|
76
|
+
|
|
77
|
+
.. note::
|
|
78
|
+
|
|
79
|
+
If correcting pointing information for a large number of images,
|
|
80
|
+
it is strongly recommended to query the table once for the
|
|
81
|
+
appropriate interval and then pass this table in rather than
|
|
82
|
+
executing repeated queries.
|
|
83
|
+
|
|
84
|
+
.. warning::
|
|
85
|
+
|
|
86
|
+
This function is only intended to be used for full-disk images
|
|
87
|
+
at the full resolution of 4096x4096 pixels. It will raise a
|
|
88
|
+
``ValueError`` if the input map does not meet these criteria.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
smap : `~sunpy.map.sources.AIAMap`
|
|
93
|
+
Input map.
|
|
94
|
+
pointing_table : `~astropy.table.QTable`, optional
|
|
95
|
+
Table of pointing information. If not specified, the table
|
|
96
|
+
will be retrieved from JSOC.
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
`~sunpy.map.sources.AIAMap`
|
|
101
|
+
|
|
102
|
+
See Also
|
|
103
|
+
--------
|
|
104
|
+
`aiapy.calibrate.util.get_pointing_table`
|
|
105
|
+
"""
|
|
106
|
+
if not contains_full_disk(smap):
|
|
107
|
+
msg = "Input must be a full disk image."
|
|
108
|
+
raise ValueError(msg)
|
|
109
|
+
shape_full_frame = (4096, 4096)
|
|
110
|
+
if not all(d == (s * u.pixel) for d, s in zip(smap.dimensions, shape_full_frame, strict=True)):
|
|
111
|
+
msg = f"Input must be at the full resolution of {shape_full_frame}"
|
|
112
|
+
raise ValueError(msg)
|
|
113
|
+
if pointing_table is None:
|
|
114
|
+
# Make range wide enough to get closest 3-hour pointing
|
|
115
|
+
pointing_table = get_pointing_table(smap.date - 12 * u.h, smap.date + 12 * u.h)
|
|
116
|
+
# Find row in which T_START <= T_OBS < T_STOP
|
|
117
|
+
# The following notes are from a private communication with J. Serafin (LMSAL)
|
|
118
|
+
# and are preserved here to explain the reasoning for selecting the particular
|
|
119
|
+
# entry from the master pointing table (MPT).
|
|
120
|
+
# NOTE: The 3 hour MPT entries are computed from limb fits of images with T_OBS
|
|
121
|
+
# between T_START and T_START + 3hr, so any image with T_OBS equal to
|
|
122
|
+
# T_START + 3hr - epsilon should still use the 3hr MPT entry for that T_START.
|
|
123
|
+
# NOTE: For SDO data, T_OBS is preferred to DATE-OBS in the case of the
|
|
124
|
+
# MPT, using DATE-OBS from near the slot boundary might result in selecting
|
|
125
|
+
# an incorrect MPT record.
|
|
126
|
+
t_obs = smap.meta.get("T_OBS")
|
|
127
|
+
if t_obs is None:
|
|
128
|
+
warnings.warn(
|
|
129
|
+
"T_OBS key is missing from metadata. Falling back to Map.date. "
|
|
130
|
+
"This may result in selecting in incorrect record from the "
|
|
131
|
+
"master pointing table.",
|
|
132
|
+
AiapyUserWarning,
|
|
133
|
+
stacklevel=3,
|
|
134
|
+
)
|
|
135
|
+
t_obs = smap.date
|
|
136
|
+
t_obs = astropy.time.Time(t_obs)
|
|
137
|
+
t_obs_in_interval = np.logical_and(t_obs >= pointing_table["T_START"], t_obs < pointing_table["T_STOP"])
|
|
138
|
+
if not t_obs_in_interval.any():
|
|
139
|
+
msg = (
|
|
140
|
+
f"No valid entries for {t_obs} in pointing table "
|
|
141
|
+
f'with first T_START date of {pointing_table[0]["T_START"]} '
|
|
142
|
+
f'and a last T_STOP date of {pointing_table[-1]["T_STOP"]}.'
|
|
143
|
+
)
|
|
144
|
+
raise IndexError(msg)
|
|
145
|
+
i_nearest = np.where(t_obs_in_interval)[0][0]
|
|
146
|
+
w_str = f"{smap.wavelength.to(u.angstrom).value:03.0f}"
|
|
147
|
+
new_meta = copy.deepcopy(smap.meta)
|
|
148
|
+
# Extract new pointing parameters
|
|
149
|
+
# The x0 and y0 keywords denote the location of the center
|
|
150
|
+
# of the Sun in CCD pixel coordinates (0-based), but FITS WCS indexing is
|
|
151
|
+
# 1-based. See Section 2.2 of
|
|
152
|
+
# http://jsoc.stanford.edu/~jsoc/keywords/AIA/AIA02840_K_AIA-SDO_FITS_Keyword_Document.pdf
|
|
153
|
+
x0_mp = pointing_table[f"A_{w_str}_X0"][i_nearest].to("pix").value
|
|
154
|
+
y0_mp = pointing_table[f"A_{w_str}_Y0"][i_nearest].to("pix").value
|
|
155
|
+
crpix1 = x0_mp + 1
|
|
156
|
+
crpix2 = y0_mp + 1
|
|
157
|
+
cdelt = pointing_table[f"A_{w_str}_IMSCALE"][i_nearest].to("arcsecond / pixel").value
|
|
158
|
+
# CROTA2 is the sum of INSTROT and SAT_ROT.
|
|
159
|
+
# See http://jsoc.stanford.edu/~jsoc/keywords/AIA/AIA02840_H_AIA-SDO_FITS_Keyword_Document.pdf
|
|
160
|
+
# NOTE: Is the value of SAT_ROT in the header accurate?
|
|
161
|
+
crota2 = pointing_table[f"A_{w_str}_INSTROT"][i_nearest] + smap.meta["SAT_ROT"] * u.degree
|
|
162
|
+
crota2 = crota2.to("deg").value
|
|
163
|
+
# Update headers
|
|
164
|
+
for key, value in [
|
|
165
|
+
("crpix1", crpix1),
|
|
166
|
+
("crpix2", crpix2),
|
|
167
|
+
("x0_mp", x0_mp), # x0_mp and y0_mp are not standard FITS keywords but they are
|
|
168
|
+
("y0_mp", y0_mp), # used when respiking submaps so we update them here.
|
|
169
|
+
("cdelt1", cdelt),
|
|
170
|
+
("cdelt2", cdelt),
|
|
171
|
+
("crota2", crota2),
|
|
172
|
+
]:
|
|
173
|
+
if np.isnan(value):
|
|
174
|
+
# There are some entries in the pointing table returned from the JSOC that are marked as
|
|
175
|
+
# MISSING. These get converted to NaNs when we cast it to an astropy quantity table. In
|
|
176
|
+
# these cases, we just want to skip updating the pointing information.
|
|
177
|
+
warnings.warn(
|
|
178
|
+
f"Missing value in pointing table for {key}. This key will not be updated.",
|
|
179
|
+
AiapyUserWarning,
|
|
180
|
+
stacklevel=3,
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
new_meta[key] = value
|
|
184
|
+
|
|
185
|
+
# sunpy map converts crota to a PCi_j matrix, so we remove it to force the
|
|
186
|
+
# re-conversion.
|
|
187
|
+
new_meta.pop("PC1_1")
|
|
188
|
+
new_meta.pop("PC1_2")
|
|
189
|
+
new_meta.pop("PC2_1")
|
|
190
|
+
new_meta.pop("PC2_2")
|
|
191
|
+
return smap._new_instance(smap.data, new_meta, plot_settings=smap.plot_settings, mask=smap.mask) # NOQA: SLF001
|
aiapy/calibrate/prep.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Functions for calibrating AIA images.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
import astropy.units as u
|
|
8
|
+
import numpy as np
|
|
9
|
+
from sunpy.map import contains_full_disk
|
|
10
|
+
from sunpy.map.sources.sdo import AIAMap, HMIMap
|
|
11
|
+
from sunpy.util.decorators import add_common_docstring
|
|
12
|
+
|
|
13
|
+
from aiapy.calibrate.transform import _rotation_function_names
|
|
14
|
+
from aiapy.calibrate.util import _select_epoch_from_correction_table, get_correction_table
|
|
15
|
+
from aiapy.util import AiapyUserWarning
|
|
16
|
+
from aiapy.util.decorators import validate_channel
|
|
17
|
+
|
|
18
|
+
__all__ = ["register", "correct_degradation", "degradation"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@add_common_docstring(rotation_function_names=_rotation_function_names)
|
|
22
|
+
def register(smap, *, missing=None, order=3, method="scipy"):
|
|
23
|
+
"""
|
|
24
|
+
Processes a full-disk level 1 `~sunpy.map.sources.AIAMap` into a level
|
|
25
|
+
1.5 `~sunpy.map.sources.AIAMap`.
|
|
26
|
+
|
|
27
|
+
Rotates, scales and translates the image so that solar North is aligned
|
|
28
|
+
with the y axis, each pixel is 0.6 arcsec across, and the center of the
|
|
29
|
+
Sun is at the center of the image. The actual transformation is done by
|
|
30
|
+
the `~sunpy.map.GenericMap.rotate` method.
|
|
31
|
+
|
|
32
|
+
.. warning::
|
|
33
|
+
|
|
34
|
+
This function might not return a 4096 by 4096 data array
|
|
35
|
+
due to the nature of rotating and scaling the image.
|
|
36
|
+
If you need a 4096 by 4096 image, you will need to pad the array manually,
|
|
37
|
+
update header: crpix1 and crpix2 by the difference divided by 2 in size along that axis.
|
|
38
|
+
Then create a new map.
|
|
39
|
+
|
|
40
|
+
Please open an issue on the `aiapy GitHub page <https://github.com/LM-SAL/aiapy/issues>`__
|
|
41
|
+
if you would like to see this changed.
|
|
42
|
+
|
|
43
|
+
.. note::
|
|
44
|
+
|
|
45
|
+
This routine modifies the header information to the standard
|
|
46
|
+
``PCi_j`` WCS formalism. The FITS header resulting in saving a file
|
|
47
|
+
after this procedure will therefore differ from the original
|
|
48
|
+
file.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
smap : `~sunpy.map.sources.AIAMap` or `~sunpy.map.sources.sdo.HMIMap`
|
|
53
|
+
A `~sunpy.map.Map` containing a full-disk AIA image or HMI magnetogram
|
|
54
|
+
missing : `float`, optional
|
|
55
|
+
If there are missing values after the interpolation, they will be
|
|
56
|
+
filled in with ``missing``. If `None`, the default value will be the
|
|
57
|
+
minimum value of ``smap``
|
|
58
|
+
order : `int`, optional
|
|
59
|
+
Order of the spline interpolation.
|
|
60
|
+
method : {{{rotation_function_names}}}, optional
|
|
61
|
+
Rotation function to use. Defaults to ``'scipy'``.
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
`~sunpy.map.sources.AIAMap` or `~sunpy.map.sources.sdo.HMIMap`:
|
|
66
|
+
A level 1.5 copy of `~sunpy.map.sources.AIAMap` or
|
|
67
|
+
`~sunpy.map.sources.sdo.HMIMap`.
|
|
68
|
+
"""
|
|
69
|
+
# This implementation is taken directly from the `aiaprep` method in
|
|
70
|
+
# sunpy.instr.aia.aiaprep under the terms of the BSD 2 Clause license.
|
|
71
|
+
# See licenses/SUNPY.rst.
|
|
72
|
+
if not isinstance(smap, AIAMap | HMIMap):
|
|
73
|
+
msg = "Input must be an AIAMap or HMIMap."
|
|
74
|
+
raise TypeError(msg)
|
|
75
|
+
if not contains_full_disk(smap):
|
|
76
|
+
msg = "Input must be a full disk image."
|
|
77
|
+
raise ValueError(msg)
|
|
78
|
+
if smap.processing_level is None or smap.processing_level > 1:
|
|
79
|
+
warnings.warn(
|
|
80
|
+
"Image registration should only be applied to level 1 data",
|
|
81
|
+
AiapyUserWarning,
|
|
82
|
+
stacklevel=3,
|
|
83
|
+
)
|
|
84
|
+
# Target scale is 0.6 arcsec/pixel, but this needs to be adjusted if the
|
|
85
|
+
# map has already been rescaled.
|
|
86
|
+
if (smap.scale[0] / 0.6).round() != 1.0 * u.arcsec / u.pix and smap.data.shape != (
|
|
87
|
+
4096,
|
|
88
|
+
4096,
|
|
89
|
+
):
|
|
90
|
+
scale = (smap.scale[0] / 0.6).round() * 0.6 * u.arcsec
|
|
91
|
+
else:
|
|
92
|
+
scale = 0.6 * u.arcsec # pragma: no cover # needs a full res image
|
|
93
|
+
scale_factor = smap.scale[0] / scale
|
|
94
|
+
missing = smap.min() if missing is None else missing
|
|
95
|
+
tempmap = smap.rotate(
|
|
96
|
+
recenter=True,
|
|
97
|
+
scale=scale_factor.value,
|
|
98
|
+
order=order,
|
|
99
|
+
missing=missing,
|
|
100
|
+
method=method,
|
|
101
|
+
)
|
|
102
|
+
# extract center from padded smap.rotate output
|
|
103
|
+
# crpix1 and crpix2 will be equal (recenter=True), as prep does not work with submaps
|
|
104
|
+
center = np.floor(tempmap.meta["crpix1"])
|
|
105
|
+
range_side = (center + np.array([-1, 1]) * smap.data.shape[0] / 2) * u.pix
|
|
106
|
+
newmap = tempmap.submap(
|
|
107
|
+
u.Quantity([range_side[0], range_side[0]]),
|
|
108
|
+
top_right=u.Quantity([range_side[1], range_side[1]]) - 1 * u.pix,
|
|
109
|
+
)
|
|
110
|
+
newmap.meta["r_sun"] = newmap.meta["rsun_obs"] / newmap.meta["cdelt1"]
|
|
111
|
+
newmap.meta["lvl_num"] = 1.5
|
|
112
|
+
newmap.meta["bitpix"] = -64
|
|
113
|
+
return newmap
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def correct_degradation(smap, *, correction_table=None, calibration_version=None):
|
|
117
|
+
"""
|
|
118
|
+
Apply time-dependent degradation correction to an AIA map.
|
|
119
|
+
|
|
120
|
+
This function applies a time-dependent correction to an AIA observation by
|
|
121
|
+
dividing the observed intensity by the correction factor calculated by
|
|
122
|
+
`degradation`. Any keyword arguments that can be passed to `degradation`
|
|
123
|
+
can also be passed in here.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
smap : `~sunpy.map.sources.AIAMap`
|
|
128
|
+
Map to be corrected.
|
|
129
|
+
correction_table : `~astropy.table.Table` or `str`, optional
|
|
130
|
+
Table of correction parameters or path to correction table file.
|
|
131
|
+
If not specified, it will be queried from JSOC. See
|
|
132
|
+
`aiapy.calibrate.util.get_correction_table` for more information.
|
|
133
|
+
If you are processing many images, it is recommended to
|
|
134
|
+
read the correction table once and pass it with this argument to avoid
|
|
135
|
+
multiple redundant network calls.
|
|
136
|
+
calibration_version : `int`, optional
|
|
137
|
+
The version of the calibration to use when calculating the degradation.
|
|
138
|
+
By default, this is the most recent version available from JSOC. If you
|
|
139
|
+
are using a specific calibration response file, you may need to specify
|
|
140
|
+
this according to the version in that file.
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
`~sunpy.map.sources.AIAMap`
|
|
145
|
+
|
|
146
|
+
See Also
|
|
147
|
+
--------
|
|
148
|
+
degradation
|
|
149
|
+
"""
|
|
150
|
+
d = degradation(
|
|
151
|
+
smap.wavelength,
|
|
152
|
+
smap.date,
|
|
153
|
+
correction_table=correction_table,
|
|
154
|
+
calibration_version=calibration_version,
|
|
155
|
+
)
|
|
156
|
+
return smap / d
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@u.quantity_input
|
|
160
|
+
@validate_channel("channel")
|
|
161
|
+
def degradation(
|
|
162
|
+
channel: u.angstrom,
|
|
163
|
+
obstime,
|
|
164
|
+
*,
|
|
165
|
+
correction_table=None,
|
|
166
|
+
calibration_version=None,
|
|
167
|
+
) -> u.dimensionless_unscaled:
|
|
168
|
+
r"""
|
|
169
|
+
Correction to account for time-dependent degradation of the instrument.
|
|
170
|
+
|
|
171
|
+
The correction factor to account for the time-varying degradation of
|
|
172
|
+
the telescopes is given by a normalization to the calibration epoch
|
|
173
|
+
closest to ``obstime`` and an interpolation within that epoch to
|
|
174
|
+
``obstime``,
|
|
175
|
+
|
|
176
|
+
.. math::
|
|
177
|
+
|
|
178
|
+
\frac{A_{eff}(t_{e})}{A_{eff}(t_0)}(1 + p_1\delta t + p_2\delta t^2 + p_3\delta t^3)
|
|
179
|
+
|
|
180
|
+
where :math:`A_{eff}(t_e)` is the effective area calculated at the
|
|
181
|
+
calibration epoch for ``obstime``, :math:`A_{eff}(t_0)` is the effective
|
|
182
|
+
area at the first calibration epoch (i.e. at launch),
|
|
183
|
+
:math:`p_1,p_2,p_3` are the interpolation coefficients for the
|
|
184
|
+
``obstime`` epoch, and :math:`\delta t` is the difference between the
|
|
185
|
+
start time of the epoch and ``obstime``.
|
|
186
|
+
All calibration terms are taken from the ``aia.response`` series in JSOC
|
|
187
|
+
or read from the table input by the user.
|
|
188
|
+
|
|
189
|
+
.. note:: This function is adapted directly from the
|
|
190
|
+
`aia_bp_corrections.pro <https://sohoftp.nascom.nasa.gov/solarsoft/sdo/aia/idl/response/aia_bp_corrections.pro>`__
|
|
191
|
+
routine in SolarSoft.
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
channel : `~astropy.units.Quantity`
|
|
196
|
+
obstime : `~astropy.time.Time`
|
|
197
|
+
correction_table : `~astropy.table.Table` or `str`, optional
|
|
198
|
+
Table of correction parameters or path to correction table file.
|
|
199
|
+
If not specified, it will be queried from JSOC. See
|
|
200
|
+
`aiapy.calibrate.util.get_correction_table` for more information.
|
|
201
|
+
If you are processing many images, it is recommended to
|
|
202
|
+
read the correction table once and pass it with this argument to avoid
|
|
203
|
+
multiple redundant network calls.
|
|
204
|
+
calibration_version : `int`, optional
|
|
205
|
+
The version of the calibration to use when calculating the degradation.
|
|
206
|
+
By default, this is the most recent version available from JSOC. If you
|
|
207
|
+
are using a specific calibration response file, you may need to specify
|
|
208
|
+
this according to the version in that file.
|
|
209
|
+
|
|
210
|
+
See Also
|
|
211
|
+
--------
|
|
212
|
+
aiapy.calibrate.util.get_correction_table
|
|
213
|
+
aiapy.response.Channel.wavelength_response
|
|
214
|
+
aiapy.response.Channel.eve_correction
|
|
215
|
+
"""
|
|
216
|
+
if obstime.shape == ():
|
|
217
|
+
obstime = obstime.reshape((1,))
|
|
218
|
+
ratio = np.zeros(obstime.shape)
|
|
219
|
+
poly = np.zeros(obstime.shape)
|
|
220
|
+
# Do this outside of the loop to avoid repeated queries
|
|
221
|
+
correction_table = get_correction_table(correction_table=correction_table)
|
|
222
|
+
for i, t in enumerate(obstime):
|
|
223
|
+
table = _select_epoch_from_correction_table(channel, t, correction_table, version=calibration_version)
|
|
224
|
+
|
|
225
|
+
# Time difference between obstime and start of epoch
|
|
226
|
+
dt = (t - table["T_START"][-1]).to(u.day).value
|
|
227
|
+
# Correction to most recent epoch
|
|
228
|
+
ratio[i] = table["EFF_AREA"][-1] / table["EFF_AREA"][0]
|
|
229
|
+
# Polynomial correction to interpolate within epoch
|
|
230
|
+
poly[i] = table["EFFA_P1"][-1] * dt + table["EFFA_P2"][-1] * dt**2 + table["EFFA_P3"][-1] * dt**3 + 1.0
|
|
231
|
+
return u.Quantity(poly * ratio)
|