imap-processing 0.17.0__py3-none-any.whl → 0.19.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.

Potentially problematic release.


This version of imap-processing might be problematic. Click here for more details.

Files changed (141) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/ancillary/ancillary_dataset_combiner.py +161 -1
  3. imap_processing/ccsds/excel_to_xtce.py +12 -0
  4. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -6
  5. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +312 -274
  6. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +39 -28
  7. imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +1048 -183
  8. imap_processing/cdf/config/imap_constant_attrs.yaml +4 -2
  9. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +12 -0
  10. imap_processing/cdf/config/imap_hi_global_cdf_attrs.yaml +5 -0
  11. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +10 -4
  12. imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +163 -100
  13. imap_processing/cdf/config/imap_hit_l2_variable_attrs.yaml +4 -4
  14. imap_processing/cdf/config/imap_ialirt_l1_variable_attrs.yaml +97 -54
  15. imap_processing/cdf/config/imap_idex_l2a_variable_attrs.yaml +33 -4
  16. imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +44 -44
  17. imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +77 -61
  18. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +30 -0
  19. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +4 -15
  20. imap_processing/cdf/config/imap_lo_l1c_variable_attrs.yaml +189 -98
  21. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +99 -2
  22. imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +24 -1
  23. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +60 -0
  24. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +99 -11
  25. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +50 -7
  26. imap_processing/cli.py +121 -44
  27. imap_processing/codice/codice_l1a.py +165 -77
  28. imap_processing/codice/codice_l1b.py +1 -1
  29. imap_processing/codice/codice_l2.py +118 -19
  30. imap_processing/codice/constants.py +1217 -1089
  31. imap_processing/decom.py +1 -4
  32. imap_processing/ena_maps/ena_maps.py +32 -25
  33. imap_processing/ena_maps/utils/naming.py +8 -2
  34. imap_processing/glows/ancillary/imap_glows_exclusions-by-instr-team_20250923_v002.dat +10 -0
  35. imap_processing/glows/ancillary/imap_glows_map-of-excluded-regions_20250923_v002.dat +393 -0
  36. imap_processing/glows/ancillary/imap_glows_map-of-uv-sources_20250923_v002.dat +593 -0
  37. imap_processing/glows/ancillary/imap_glows_pipeline_settings_20250923_v002.json +54 -0
  38. imap_processing/glows/ancillary/imap_glows_suspected-transients_20250923_v002.dat +10 -0
  39. imap_processing/glows/l1b/glows_l1b.py +99 -9
  40. imap_processing/glows/l1b/glows_l1b_data.py +350 -38
  41. imap_processing/glows/l2/glows_l2.py +11 -0
  42. imap_processing/hi/hi_l1a.py +124 -3
  43. imap_processing/hi/hi_l1b.py +154 -71
  44. imap_processing/hi/hi_l2.py +84 -51
  45. imap_processing/hi/utils.py +153 -8
  46. imap_processing/hit/l0/constants.py +3 -0
  47. imap_processing/hit/l0/decom_hit.py +5 -8
  48. imap_processing/hit/l1a/hit_l1a.py +375 -45
  49. imap_processing/hit/l1b/constants.py +5 -0
  50. imap_processing/hit/l1b/hit_l1b.py +61 -131
  51. imap_processing/hit/l2/constants.py +1 -1
  52. imap_processing/hit/l2/hit_l2.py +10 -11
  53. imap_processing/ialirt/calculate_ingest.py +219 -0
  54. imap_processing/ialirt/constants.py +32 -1
  55. imap_processing/ialirt/generate_coverage.py +201 -0
  56. imap_processing/ialirt/l0/ialirt_spice.py +5 -2
  57. imap_processing/ialirt/l0/parse_mag.py +337 -29
  58. imap_processing/ialirt/l0/process_hit.py +5 -3
  59. imap_processing/ialirt/l0/process_swapi.py +41 -25
  60. imap_processing/ialirt/l0/process_swe.py +23 -7
  61. imap_processing/ialirt/process_ephemeris.py +70 -14
  62. imap_processing/ialirt/utils/constants.py +22 -16
  63. imap_processing/ialirt/utils/create_xarray.py +42 -19
  64. imap_processing/idex/idex_constants.py +1 -5
  65. imap_processing/idex/idex_l0.py +2 -2
  66. imap_processing/idex/idex_l1a.py +2 -3
  67. imap_processing/idex/idex_l1b.py +2 -3
  68. imap_processing/idex/idex_l2a.py +130 -4
  69. imap_processing/idex/idex_l2b.py +313 -119
  70. imap_processing/idex/idex_utils.py +1 -3
  71. imap_processing/lo/l0/lo_apid.py +1 -0
  72. imap_processing/lo/l0/lo_science.py +25 -24
  73. imap_processing/lo/l1a/lo_l1a.py +44 -0
  74. imap_processing/lo/l1b/lo_l1b.py +3 -3
  75. imap_processing/lo/l1c/lo_l1c.py +116 -50
  76. imap_processing/lo/l2/lo_l2.py +29 -29
  77. imap_processing/lo/lo_ancillary.py +55 -0
  78. imap_processing/lo/packet_definitions/lo_xtce.xml +5359 -106
  79. imap_processing/mag/constants.py +1 -0
  80. imap_processing/mag/l1a/mag_l1a.py +1 -0
  81. imap_processing/mag/l1a/mag_l1a_data.py +26 -0
  82. imap_processing/mag/l1b/mag_l1b.py +3 -2
  83. imap_processing/mag/l1c/interpolation_methods.py +14 -15
  84. imap_processing/mag/l1c/mag_l1c.py +23 -6
  85. imap_processing/mag/l1d/__init__.py +0 -0
  86. imap_processing/mag/l1d/mag_l1d.py +176 -0
  87. imap_processing/mag/l1d/mag_l1d_data.py +725 -0
  88. imap_processing/mag/l2/__init__.py +0 -0
  89. imap_processing/mag/l2/mag_l2.py +25 -20
  90. imap_processing/mag/l2/mag_l2_data.py +199 -130
  91. imap_processing/quality_flags.py +28 -2
  92. imap_processing/spice/geometry.py +101 -36
  93. imap_processing/spice/pointing_frame.py +1 -7
  94. imap_processing/spice/repoint.py +29 -2
  95. imap_processing/spice/spin.py +32 -8
  96. imap_processing/spice/time.py +60 -19
  97. imap_processing/swapi/l1/swapi_l1.py +10 -4
  98. imap_processing/swapi/l2/swapi_l2.py +66 -24
  99. imap_processing/swapi/swapi_utils.py +1 -1
  100. imap_processing/swe/l1b/swe_l1b.py +3 -6
  101. imap_processing/ultra/constants.py +28 -3
  102. imap_processing/ultra/l0/decom_tools.py +15 -8
  103. imap_processing/ultra/l0/decom_ultra.py +35 -11
  104. imap_processing/ultra/l0/ultra_utils.py +102 -12
  105. imap_processing/ultra/l1a/ultra_l1a.py +26 -6
  106. imap_processing/ultra/l1b/cullingmask.py +6 -3
  107. imap_processing/ultra/l1b/de.py +122 -26
  108. imap_processing/ultra/l1b/extendedspin.py +29 -2
  109. imap_processing/ultra/l1b/lookup_utils.py +424 -50
  110. imap_processing/ultra/l1b/quality_flag_filters.py +23 -0
  111. imap_processing/ultra/l1b/ultra_l1b_culling.py +356 -5
  112. imap_processing/ultra/l1b/ultra_l1b_extended.py +534 -90
  113. imap_processing/ultra/l1c/helio_pset.py +127 -7
  114. imap_processing/ultra/l1c/l1c_lookup_utils.py +256 -0
  115. imap_processing/ultra/l1c/spacecraft_pset.py +90 -15
  116. imap_processing/ultra/l1c/ultra_l1c.py +6 -0
  117. imap_processing/ultra/l1c/ultra_l1c_culling.py +85 -0
  118. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +446 -341
  119. imap_processing/ultra/l2/ultra_l2.py +0 -1
  120. imap_processing/ultra/utils/ultra_l1_utils.py +40 -3
  121. imap_processing/utils.py +3 -4
  122. {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/METADATA +3 -3
  123. {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/RECORD +126 -126
  124. imap_processing/idex/idex_l2c.py +0 -250
  125. imap_processing/spice/kernels.py +0 -187
  126. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +0 -526
  127. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +0 -526
  128. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +0 -526
  129. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +0 -524
  130. imap_processing/ultra/lookup_tables/EgyNorm.mem.csv +0 -32769
  131. imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
  132. imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
  133. imap_processing/ultra/lookup_tables/dps_grid45_compressed.cdf +0 -0
  134. imap_processing/ultra/lookup_tables/ultra45_back-pos-luts.csv +0 -4097
  135. imap_processing/ultra/lookup_tables/ultra45_tdc_norm.csv +0 -2050
  136. imap_processing/ultra/lookup_tables/ultra90_back-pos-luts.csv +0 -4097
  137. imap_processing/ultra/lookup_tables/ultra90_tdc_norm.csv +0 -2050
  138. imap_processing/ultra/lookup_tables/yadjust.csv +0 -257
  139. {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/LICENSE +0 -0
  140. {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/WHEEL +0 -0
  141. {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/entry_points.txt +0 -0
@@ -1,250 +0,0 @@
1
- """
2
- Perform IDEX L2c Processing.
3
-
4
- Examples
5
- --------
6
- .. code-block:: python
7
- from imap_processing.idex.idex_l1a import PacketParser
8
- from imap_processing.idex.idex_l1b import idex_l1b
9
- from imap_processing.idex.idex_l2a import idex_l2a
10
- from imap_processing.idex.idex_l2b import idex_l2b
11
- from imap_processing.cdf.utils import write_cdf
12
-
13
- l0_file = "imap_processing/tests/idex/imap_idex_l0_sci_20231214_v001.pkts"
14
- l1a_data = PacketParser(l0_file)
15
- l1b_data = idex_l1b(l1a_data)
16
- l2a_data = idex_l2a(l1b_data)
17
- l2b_data = idex_l2b(l2a_data)
18
- write_cdf(l2b_data)
19
- """
20
-
21
- import logging
22
-
23
- import astropy_healpix.healpy as hp
24
- import numpy as np
25
- import xarray as xr
26
-
27
- from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
28
- from imap_processing.ena_maps.ena_maps import SkyTilingType
29
- from imap_processing.ena_maps.utils.coordinates import CoordNames
30
- from imap_processing.ena_maps.utils.spatial_utils import AzElSkyGrid
31
- from imap_processing.idex.idex_constants import (
32
- IDEX_EVENT_REFERENCE_FRAME,
33
- IDEX_HEALPIX_NESTED,
34
- IDEX_HEALPIX_NSIDE,
35
- IDEX_SPACING_DEG,
36
- )
37
- from imap_processing.idex.idex_utils import get_idex_attrs
38
-
39
- logger = logging.getLogger(__name__)
40
-
41
-
42
- def idex_l2c(l2b_dataset: xr.Dataset) -> list[xr.Dataset]:
43
- """
44
- Will process IDEX l2b data to create l2c data products.
45
-
46
- Parameters
47
- ----------
48
- l2b_dataset : xarray.Dataset
49
- IDEX L2b dataset.
50
-
51
- Returns
52
- -------
53
- l2b_dataset : list[xarray.Dataset]
54
- The``xarray`` dataset containing the science data and supporting metadata.
55
- """
56
- logger.info(
57
- f"Running IDEX L2C processing on datasets: "
58
- f"{l2b_dataset.attrs['Logical_source']}"
59
- )
60
- # create the attribute manager for this data level
61
- idex_attrs = get_idex_attrs("l2c")
62
- # Epoch should be the start of the collection period.
63
- # TODO should epoch be start of sci acquisition?
64
- epoch = xr.DataArray(
65
- l2b_dataset["epoch"].data[0:1].astype(np.int64),
66
- name="epoch",
67
- dims=["epoch"],
68
- attrs=idex_attrs.get_variable_attributes(
69
- "epoch_collection_set", check_schema=False
70
- ),
71
- )
72
- l2c_healpix_dataset = idex_healpix_map(l2b_dataset, epoch, idex_attrs)
73
- l2c_rectangular_dataset = idex_rectangular_map(l2b_dataset, epoch, idex_attrs)
74
-
75
- # TODO exposure time
76
- logger.info("IDEX L2C science data processing completed.")
77
- return [l2c_healpix_dataset, l2c_rectangular_dataset]
78
-
79
-
80
- def idex_healpix_map(
81
- l1b_dataset: xr.Dataset,
82
- epoch_da: xr.DataArray,
83
- idex_attrs: ImapCdfAttributes,
84
- nside: int = IDEX_HEALPIX_NSIDE,
85
- nested: bool = IDEX_HEALPIX_NESTED,
86
- ) -> xr.Dataset:
87
- """
88
- Create a healpix map out of a l1b dataset.
89
-
90
- Parameters
91
- ----------
92
- l1b_dataset : xarray.Dataset
93
- IDEX L2b dataset.
94
- epoch_da : xarray.DataArray
95
- Epoch data array of the collection. Size: (1,).
96
- idex_attrs : ImapCdfAttributes
97
- The attribute manager for this data level.
98
- nside : int
99
- Healpix nside parameter.
100
- nested : bool
101
- Healpix nested parameter.
102
-
103
- Returns
104
- -------
105
- map : xarray.Dataset
106
- Spatially binned dust counts in a healpix map format.
107
- """
108
- longitude = l1b_dataset["longitude"]
109
- latitude = l1b_dataset["latitude"]
110
-
111
- # Get the healpix indices
112
- hpix_idx = hp.ang2pix(
113
- nside, nest=nested, lonlat=True, theta=longitude, phi=latitude
114
- )
115
-
116
- n_pix = hp.nside2npix(nside)
117
- healpix = xr.DataArray(
118
- np.arange(n_pix),
119
- name=CoordNames.HEALPIX_INDEX.value,
120
- dims=CoordNames.HEALPIX_INDEX.value,
121
- attrs=idex_attrs.get_variable_attributes("pixel_index", check_schema=False),
122
- )
123
-
124
- # Create a histogram of the raw dust event counts for each pixel
125
- counts = np.histogram(hpix_idx, bins=n_pix, range=(0, n_pix))[0]
126
- # Add epoch dimension
127
- counts_da = xr.DataArray(
128
- counts[np.newaxis, :].astype(np.int64),
129
- name="counts",
130
- dims=("epoch", CoordNames.HEALPIX_INDEX.value),
131
- attrs=idex_attrs.get_variable_attributes("healpix_counts"),
132
- )
133
- pixel_label = xr.DataArray(
134
- healpix.astype(str),
135
- name="pixel_label",
136
- dims="pixel_index",
137
- attrs=idex_attrs.get_variable_attributes("pixel_label", check_schema=False),
138
- )
139
- l2c_dataset = xr.Dataset(
140
- coords={CoordNames.HEALPIX_INDEX.value: healpix, "epoch": epoch_da},
141
- data_vars={
142
- "counts": counts_da,
143
- "longitude": longitude,
144
- "latitude": latitude,
145
- "pixel_label": pixel_label,
146
- },
147
- )
148
- map_attrs = {
149
- "Sky_tiling_type": SkyTilingType.HEALPIX.value,
150
- "HEALPix_nside": str(nside),
151
- "HEALPix_nest": str(nested),
152
- "Spice_reference_frame": IDEX_EVENT_REFERENCE_FRAME.name,
153
- "num_points": str(n_pix),
154
- } | idex_attrs.get_global_attributes("imap_idex_l2c_sci-healpix")
155
- l2c_dataset.attrs.update(map_attrs)
156
-
157
- return l2c_dataset
158
-
159
-
160
- def idex_rectangular_map(
161
- l1b_dataset: xr.Dataset,
162
- epoch_da: xr.DataArray,
163
- idex_attrs: ImapCdfAttributes,
164
- spacing_deg: int = IDEX_SPACING_DEG,
165
- ) -> xr.Dataset:
166
- """
167
- Create a rectangular map out of a l1b dataset.
168
-
169
- Parameters
170
- ----------
171
- l1b_dataset : xarray.Dataset
172
- IDEX L2b dataset.
173
- epoch_da : xarray.DataArray
174
- Epoch data array of the collection. Size: (1,).
175
- idex_attrs : ImapCdfAttributes
176
- The attribute manager for this data level.
177
- spacing_deg : int
178
- The spacing in degrees for the rectangular grid.
179
-
180
- Returns
181
- -------
182
- map : xarray.Dataset
183
- Spatially binned dust counts in a rectangular map format.
184
- """
185
- # Get the rectangular grid with the specified spacing
186
- grid = AzElSkyGrid(spacing_deg)
187
- # Make sure longitude values are in the range [0, 360)
188
- longitude_wrapped = np.mod(l1b_dataset["longitude"], 360)
189
- latitude = l1b_dataset["latitude"]
190
- # Create a 2d histogram of the raw dust event counts for each pixel using the grid
191
- # bin edges
192
- counts, _, _ = np.histogram2d(
193
- longitude_wrapped, latitude, bins=[grid.az_bin_edges, grid.el_bin_edges]
194
- )
195
- counts_da = xr.DataArray(
196
- counts[np.newaxis, :, :].astype(np.int64),
197
- name="counts",
198
- dims=("epoch", "rectangular_lon_pixel", "rectangular_lat_pixel"),
199
- attrs=idex_attrs.get_variable_attributes("rectangular_counts"),
200
- )
201
- rec_lon_pixels = xr.DataArray(
202
- name="rectangular_lon_pixel",
203
- data=grid.az_bin_midpoints,
204
- dims="rectangular_lon_pixel",
205
- attrs=idex_attrs.get_variable_attributes(
206
- "rectangular_lon_pixel", check_schema=False
207
- ),
208
- )
209
- rec_lat_pixels = xr.DataArray(
210
- name="rectangular_lat_pixel",
211
- data=grid.el_bin_midpoints,
212
- dims="rectangular_lat_pixel",
213
- attrs=idex_attrs.get_variable_attributes(
214
- "rectangular_lat_pixel", check_schema=False
215
- ),
216
- )
217
-
218
- l2c_dataset = xr.Dataset(
219
- coords={
220
- "epoch": epoch_da,
221
- "rectangular_lon_pixel": rec_lon_pixels,
222
- "rectangular_lat_pixel": rec_lat_pixels,
223
- },
224
- data_vars={
225
- "counts": counts_da,
226
- "longitude": longitude_wrapped,
227
- "latitude": latitude,
228
- "rectangular_lon_pixel_label": rec_lon_pixels.astype(str),
229
- "rectangular_lat_pixel_label": rec_lat_pixels.astype(str),
230
- },
231
- )
232
- l2c_dataset[
233
- "rectangular_lon_pixel_label"
234
- ].attrs = idex_attrs.get_variable_attributes(
235
- "rectangular_lon_pixel_label", check_schema=False
236
- )
237
- l2c_dataset[
238
- "rectangular_lat_pixel_label"
239
- ].attrs = idex_attrs.get_variable_attributes(
240
- "rectangular_lat_pixel_label", check_schema=False
241
- )
242
- map_attrs = {
243
- "sky_tiling_type": SkyTilingType.RECTANGULAR.value,
244
- "Spacing_degrees": str(spacing_deg),
245
- "Spice_reference_frame": IDEX_EVENT_REFERENCE_FRAME.name,
246
- "num_points": str(counts.size),
247
- } | idex_attrs.get_global_attributes("imap_idex_l2c_sci-rectangular")
248
-
249
- l2c_dataset.attrs.update(map_attrs)
250
- return l2c_dataset
@@ -1,187 +0,0 @@
1
- """Functions that generate, furnish, and retrieve metadata from SPICE kernels."""
2
-
3
- import functools
4
- import logging
5
- import os
6
- from typing import Any, Callable, Optional, Union, overload
7
-
8
- import spiceypy
9
- from spiceypy.utils.exceptions import SpiceyError
10
-
11
- from imap_processing import imap_module_directory
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
-
16
- # Declarations to help with typing. Taken from mypy documentation on
17
- # decorator-factories:
18
- # https://mypy.readthedocs.io/en/stable/generics.html#decorator-factories
19
- # Bare decorator usage
20
- @overload
21
- def ensure_spice(
22
- __func: Callable[..., Any],
23
- ) -> Callable[..., Any]: ... # numpydoc ignore=GL08
24
- # Decorator with arguments
25
- @overload
26
- def ensure_spice(
27
- *, time_kernels_only: bool = False
28
- ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: ... # numpydoc ignore=GL08
29
- # Implementation
30
- def ensure_spice(
31
- __func: Optional[Callable[..., Any]] = None, *, time_kernels_only: bool = False
32
- ) -> Union[Callable[..., Any], Callable[[Callable[..., Any]], Callable[..., Any]]]:
33
- """
34
- Decorator/wrapper that automatically furnishes SPICE kernels.
35
-
36
- Parameters
37
- ----------
38
- __func : Callable
39
- The function requiring SPICE that we are going to wrap if being used
40
- explicitly, otherwise None, in which case ensure_spice is being used,
41
- not as a function wrapper (see l2a_processing.py) but as a true
42
- decorator without an explicit function argument.
43
- time_kernels_only : bool
44
- Specify that we only need to furnish time kernels (if SPICE_METAKERNEL
45
- is set, we still just furnish that metakernel and assume the time
46
- kernels are included.
47
-
48
- Returns
49
- -------
50
- Callable
51
- Decorated function, with spice error handling.
52
-
53
- Notes
54
- -----
55
- Before trying to understand this piece of code, read this:
56
- https://stackoverflow.com/questions/5929107/decorators-with-parameters/60832711#60832711
57
-
58
- **Control flow overview:**
59
-
60
- 1. Try simply calling the wrapped function naively.
61
- * SUCCESS? Great! We're done.
62
- * SpiceyError? Go to step 2.
63
-
64
- 2. Furnish metakernel at SPICE_METAKERNEL
65
- * SUCCESS? Great, return the original function again (so it can be
66
- re-run).
67
- * KeyError? Seems like SPICE_METAKERNEL isn't set, no problem. Go to
68
- step 3.
69
-
70
- 3. Did we get the parameter time_kernels_only=True?
71
- * YES? We only need LSK and SCLK kernels to run this function. Go fetch
72
- those and furnish and return the original function (so it can be re-run).
73
- * NO? Dang. This is sort of the end of the line. Re-raise the error
74
- generated from the failed spiceypy function call but add a better
75
- message to it.
76
-
77
- Examples
78
- --------
79
- There are three ways to use this object
80
-
81
- 1. A decorator with no arguments
82
-
83
- >>> @ensure_spice
84
- ... def my_spicey_func(a, b):
85
- ... pass
86
-
87
- 2. A decorator with parameters. This is useful
88
- if we only need the latest SCLK and LSK kernels for the function involved.
89
-
90
- >>> @ensure_spice(time_kernels_only=True)
91
- ... def my_spicey_time_func(a, b):
92
- ... pass
93
-
94
- 3. An explicit wrapper function, providing a dynamically set value for
95
- parameters, e.g. time_kernels_only
96
-
97
- >>> wrapped = ensure_spice(spicey_func, time_kernels_only=True)
98
- ... result = wrapped(args, kwargs)
99
- """
100
-
101
- def _decorator(func: Callable[..., Callable]) -> Callable:
102
- """
103
- Decorate or wrap input function depending on how ensure_spice is used.
104
-
105
- Parameters
106
- ----------
107
- func : Callable
108
- The function to be decorated/wrapped.
109
-
110
- Returns
111
- -------
112
- Callable
113
- If used as a function wrapper, the decorated function is returned.
114
- """
115
-
116
- @functools.wraps(func)
117
- def wrapper_ensure_spice(*args: Any, **kwargs: Any) -> Any:
118
- """
119
- Wrap the function that ensure_spice is used on.
120
-
121
- Parameters
122
- ----------
123
- *args : list
124
- The positional arguments passed to the decorated function.
125
- **kwargs
126
- The keyword arguments passed to the decorated function.
127
-
128
- Returns
129
- -------
130
- Object
131
- Output from wrapped function.
132
- """
133
- try:
134
- # Step 1.
135
- return func(
136
- *args, **kwargs
137
- ) # Naive first try. Maybe SPICE is already furnished.
138
- except SpiceyError as spicey_err:
139
- try:
140
- # Step 2.
141
- if os.getenv("SPICE_METAKERNEL"):
142
- metakernel_path = os.getenv("SPICE_METAKERNEL")
143
- spiceypy.furnsh(metakernel_path)
144
- else:
145
- furnish_time_kernel()
146
- except KeyError:
147
- # TODO: An additional step that was used on EMUS was to get
148
- # a custom metakernel from the SDC API based on an input
149
- # time range.
150
- if time_kernels_only:
151
- # Step 3.
152
- # TODO: Decide if this is useful for IMAP. Possible
153
- # implementation could include downloading
154
- # the most recent leapsecond kernel from NAIF (see:
155
- # https://lasp.colorado.edu/nucleus/projects/LIBSDC/repos/libera_utils/browse/libera_utils/spice_utils.py
156
- # for LIBERA implementation of downloading and caching
157
- # kernels) and finding the most recent IMAP clock
158
- # kernel in EFS.
159
- raise NotImplementedError from spicey_err
160
- else:
161
- raise SpiceyError(
162
- "When calling a function requiring SPICE, we failed "
163
- "to load a metakernel. SPICE_METAKERNEL is not set,"
164
- "and time_kernels_only is not set to True"
165
- ) from spicey_err
166
- return func(*args, **kwargs)
167
-
168
- return wrapper_ensure_spice
169
-
170
- # Note: This return was originally implemented as a ternary operator, but
171
- # this caused mypy to fail due to this bug:
172
- # https://github.com/python/mypy/issues/4134
173
- if callable(__func):
174
- return _decorator(__func)
175
- else:
176
- return _decorator
177
-
178
-
179
- def furnish_time_kernel() -> None:
180
- """Furnish the time kernels."""
181
- spice_test_data_path = imap_module_directory / "tests/spice/test_data"
182
-
183
- # TODO: we need to load these kernels from EFS volumen that is
184
- # mounted to batch volume and extend this to generate metakernell
185
- # which is TBD.
186
- spiceypy.furnsh(str(spice_test_data_path / "imap_sclk_0000.tsc"))
187
- spiceypy.furnsh(str(spice_test_data_path / "naif0012.tls"))