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.
Files changed (49) hide show
  1. aiapy/CITATION.rst +31 -0
  2. aiapy/__init__.py +29 -0
  3. aiapy/_version.py +16 -0
  4. aiapy/calibrate/__init__.py +9 -0
  5. aiapy/calibrate/meta.py +191 -0
  6. aiapy/calibrate/prep.py +231 -0
  7. aiapy/calibrate/spikes.py +184 -0
  8. aiapy/calibrate/tests/__init__.py +0 -0
  9. aiapy/calibrate/tests/test_meta.py +131 -0
  10. aiapy/calibrate/tests/test_prep.py +267 -0
  11. aiapy/calibrate/tests/test_spikes.py +104 -0
  12. aiapy/calibrate/tests/test_uncertainty.py +69 -0
  13. aiapy/calibrate/tests/test_util.py +138 -0
  14. aiapy/calibrate/transform.py +42 -0
  15. aiapy/calibrate/uncertainty.py +135 -0
  16. aiapy/calibrate/util.py +253 -0
  17. aiapy/conftest.py +70 -0
  18. aiapy/data/__init__.py +0 -0
  19. aiapy/data/_sample.py +136 -0
  20. aiapy/data/sample.py +79 -0
  21. aiapy/psf/__init__.py +2 -0
  22. aiapy/psf/deconvolve.py +101 -0
  23. aiapy/psf/psf.py +328 -0
  24. aiapy/psf/tests/__init__.py +0 -0
  25. aiapy/psf/tests/conftest.py +12 -0
  26. aiapy/psf/tests/test_deconvolve.py +34 -0
  27. aiapy/psf/tests/test_psf.py +27 -0
  28. aiapy/response/__init__.py +5 -0
  29. aiapy/response/channel.py +427 -0
  30. aiapy/response/tests/__init__.py +0 -0
  31. aiapy/response/tests/test_channel.py +236 -0
  32. aiapy/tests/__init__.py +0 -0
  33. aiapy/tests/data/__init__.py +25 -0
  34. aiapy/tests/data/aia_V3_error_table.txt +11 -0
  35. aiapy/tests/data/aia_V8_20171210_050627_response_table.txt +89 -0
  36. aiapy/tests/test_idl.py +87 -0
  37. aiapy/tests/test_init.py +5 -0
  38. aiapy/util/__init__.py +6 -0
  39. aiapy/util/decorators.py +49 -0
  40. aiapy/util/exceptions.py +24 -0
  41. aiapy/util/tests/__init__.py +0 -0
  42. aiapy/util/tests/test_util.py +19 -0
  43. aiapy/util/util.py +87 -0
  44. aiapy/version.py +23 -0
  45. aiapy-0.8.0.dist-info/LICENSE.rst +25 -0
  46. aiapy-0.8.0.dist-info/METADATA +101 -0
  47. aiapy-0.8.0.dist-info/RECORD +49 -0
  48. aiapy-0.8.0.dist-info/WHEEL +5 -0
  49. 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)
@@ -0,0 +1,9 @@
1
+ """
2
+ Subpackage for calibrating AIA imaging data.
3
+ """
4
+
5
+ from .meta import * # NOQA: F403
6
+ from .prep import * # NOQA: F403
7
+ from .spikes import * # NOQA: F403
8
+ from .transform import * # NOQA: F403
9
+ from .uncertainty import * # NOQA: F403
@@ -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
@@ -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)