imap-processing 0.8.0__py3-none-any.whl → 0.9.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 (99) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/ccsds/excel_to_xtce.py +2 -0
  3. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +100 -1
  4. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +14 -0
  5. imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +63 -1
  6. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +7 -0
  7. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +574 -231
  8. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +326 -0
  9. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +33 -23
  10. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +7 -4
  11. imap_processing/cdf/utils.py +3 -5
  12. imap_processing/cli.py +13 -4
  13. imap_processing/codice/codice_l1a.py +5 -5
  14. imap_processing/codice/constants.py +9 -9
  15. imap_processing/codice/decompress.py +6 -2
  16. imap_processing/glows/l1a/glows_l1a.py +1 -2
  17. imap_processing/hi/l1a/hi_l1a.py +4 -4
  18. imap_processing/hi/l1a/histogram.py +106 -108
  19. imap_processing/hi/l1a/science_direct_event.py +91 -224
  20. imap_processing/hi/packet_definitions/TLM_HI_COMBINED_SCI.xml +3994 -0
  21. imap_processing/hit/l0/constants.py +2 -2
  22. imap_processing/hit/l0/decom_hit.py +12 -101
  23. imap_processing/hit/l1a/hit_l1a.py +164 -23
  24. imap_processing/ialirt/l0/process_codicelo.py +153 -0
  25. imap_processing/ialirt/l0/process_hit.py +5 -5
  26. imap_processing/ialirt/packet_definitions/ialirt_codicelo.xml +281 -0
  27. imap_processing/ialirt/process_ephemeris.py +212 -0
  28. imap_processing/idex/idex_l1a.py +55 -75
  29. imap_processing/idex/idex_l1b.py +192 -0
  30. imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +33 -0
  31. imap_processing/idex/packet_definitions/idex_packet_definition.xml +97 -595
  32. imap_processing/lo/l0/decompression_tables/decompression_tables.py +16 -0
  33. imap_processing/lo/l0/lo_science.py +44 -12
  34. imap_processing/lo/l1a/lo_l1a.py +76 -8
  35. imap_processing/lo/packet_definitions/lo_xtce.xml +9877 -87
  36. imap_processing/mag/l1a/mag_l1a.py +1 -2
  37. imap_processing/mag/l1a/mag_l1a_data.py +1 -2
  38. imap_processing/mag/l1b/mag_l1b.py +2 -1
  39. imap_processing/spice/geometry.py +37 -19
  40. imap_processing/spice/time.py +144 -2
  41. imap_processing/swapi/l1/swapi_l1.py +3 -3
  42. imap_processing/swapi/packet_definitions/swapi_packet_definition.xml +1535 -446
  43. imap_processing/swe/l2/swe_l2.py +134 -17
  44. imap_processing/tests/ccsds/test_data/expected_output.xml +1 -1
  45. imap_processing/tests/codice/test_codice_l1a.py +8 -8
  46. imap_processing/tests/codice/test_decompress.py +4 -4
  47. imap_processing/tests/conftest.py +46 -43
  48. imap_processing/tests/hi/test_data/l0/H90_NHK_20241104.bin +0 -0
  49. imap_processing/tests/hi/test_data/l0/H90_sci_cnt_20241104.bin +0 -0
  50. imap_processing/tests/hi/test_data/l0/H90_sci_de_20241104.bin +0 -0
  51. imap_processing/tests/hi/test_hi_l1b.py +2 -2
  52. imap_processing/tests/hi/test_l1a.py +31 -58
  53. imap_processing/tests/hi/test_science_direct_event.py +58 -0
  54. imap_processing/tests/hit/test_data/sci_sample1.ccsds +0 -0
  55. imap_processing/tests/hit/test_decom_hit.py +60 -50
  56. imap_processing/tests/hit/test_hit_l1a.py +327 -12
  57. imap_processing/tests/hit/test_hit_l1b.py +76 -0
  58. imap_processing/tests/hit/validation_data/hskp_sample_eu.csv +89 -0
  59. imap_processing/tests/hit/validation_data/sci_sample_raw1.csv +29 -0
  60. imap_processing/tests/ialirt/test_data/l0/apid01152.tlm +0 -0
  61. imap_processing/tests/ialirt/test_data/l0/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  62. imap_processing/tests/ialirt/unit/test_process_codicelo.py +106 -0
  63. imap_processing/tests/ialirt/unit/test_process_ephemeris.py +109 -0
  64. imap_processing/tests/ialirt/unit/test_process_hit.py +9 -6
  65. imap_processing/tests/idex/conftest.py +1 -1
  66. imap_processing/tests/idex/test_idex_l0.py +1 -1
  67. imap_processing/tests/idex/test_idex_l1a.py +7 -1
  68. imap_processing/tests/idex/test_idex_l1b.py +126 -0
  69. imap_processing/tests/lo/test_lo_l1a.py +7 -16
  70. imap_processing/tests/lo/test_lo_science.py +67 -3
  71. imap_processing/tests/lo/test_pkts/imap_lo_l0_raw_20240803_v002.pkts +0 -0
  72. imap_processing/tests/lo/validation_data/Instrument_FM1_T104_R129_20240803_ILO_SCI_DE_dec_DN_with_fills.csv +1999 -0
  73. imap_processing/tests/mag/test_mag_l1b.py +39 -5
  74. imap_processing/tests/spice/test_geometry.py +32 -6
  75. imap_processing/tests/spice/test_time.py +135 -6
  76. imap_processing/tests/swapi/test_swapi_decom.py +75 -69
  77. imap_processing/tests/swapi/test_swapi_l1.py +4 -4
  78. imap_processing/tests/swe/test_swe_l2.py +64 -8
  79. imap_processing/tests/test_utils.py +1 -1
  80. imap_processing/tests/ultra/test_data/l0/ultra45_raw_sc_ultrarawimg_withFSWcalcs_FM45_40P_Phi28p5_BeamCal_LinearScan_phi2850_theta-000_20240207T102740.csv +3314 -3314
  81. imap_processing/tests/ultra/unit/test_de.py +8 -3
  82. imap_processing/tests/ultra/unit/test_spatial_utils.py +125 -0
  83. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +39 -29
  84. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +2 -25
  85. imap_processing/ultra/constants.py +4 -0
  86. imap_processing/ultra/l1b/de.py +8 -14
  87. imap_processing/ultra/l1b/ultra_l1b_extended.py +29 -70
  88. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +1 -36
  89. imap_processing/ultra/utils/spatial_utils.py +221 -0
  90. {imap_processing-0.8.0.dist-info → imap_processing-0.9.0.dist-info}/METADATA +1 -1
  91. {imap_processing-0.8.0.dist-info → imap_processing-0.9.0.dist-info}/RECORD +94 -76
  92. imap_processing/hi/l0/__init__.py +0 -0
  93. imap_processing/hi/l0/decom_hi.py +0 -24
  94. imap_processing/hi/packet_definitions/hi_packet_definition.xml +0 -482
  95. imap_processing/tests/hi/test_decom.py +0 -55
  96. imap_processing/tests/hi/test_l1a_sci_de.py +0 -72
  97. {imap_processing-0.8.0.dist-info → imap_processing-0.9.0.dist-info}/LICENSE +0 -0
  98. {imap_processing-0.8.0.dist-info → imap_processing-0.9.0.dist-info}/WHEEL +0 -0
  99. {imap_processing-0.8.0.dist-info → imap_processing-0.9.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,221 @@
1
+ """IMAP Ultra utils for spatial binning and grid creation."""
2
+
3
+ import typing
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+
8
+
9
+ def build_spatial_bins(
10
+ az_spacing: float = 0.5,
11
+ el_spacing: float = 0.5,
12
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
13
+ """
14
+ Build spatial bin boundaries for azimuth and elevation.
15
+
16
+ Parameters
17
+ ----------
18
+ az_spacing : float, optional
19
+ The azimuth bin spacing in degrees (default is 0.5 degrees).
20
+ el_spacing : float, optional
21
+ The elevation bin spacing in degrees (default is 0.5 degrees).
22
+
23
+ Returns
24
+ -------
25
+ az_bin_edges : np.ndarray
26
+ Array of azimuth bin boundary values.
27
+ el_bin_edges : np.ndarray
28
+ Array of elevation bin boundary values.
29
+ az_bin_midpoints : np.ndarray
30
+ Array of azimuth bin midpoint values.
31
+ el_bin_midpoints : np.ndarray
32
+ Array of elevation bin midpoint values.
33
+ """
34
+ # Azimuth bins from 0 to 360 degrees.
35
+ az_bin_edges = np.arange(0, 360 + az_spacing, az_spacing)
36
+ az_bin_midpoints = az_bin_edges[:-1] + az_spacing / 2 # Midpoints between edges
37
+
38
+ # Elevation bins from -90 to 90 degrees.
39
+ el_bin_edges = np.arange(-90, 90 + el_spacing, el_spacing)
40
+ el_bin_midpoints = el_bin_edges[:-1] + el_spacing / 2 # Midpoints between edges
41
+
42
+ return az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints
43
+
44
+
45
+ def build_solid_angle_map(
46
+ spacing: float, input_degrees: bool = True, output_degrees: bool = False
47
+ ) -> NDArray:
48
+ """
49
+ Build a solid angle map for a given spacing in degrees.
50
+
51
+ Parameters
52
+ ----------
53
+ spacing : float
54
+ The bin spacing in the specified units.
55
+ input_degrees : bool, optional
56
+ If True, the input spacing is in degrees
57
+ (default is True for radians).
58
+ output_degrees : bool, optional
59
+ If True, the output solid angle map is in square degrees
60
+ (default is False for steradians).
61
+
62
+ Returns
63
+ -------
64
+ solid_angle_grid : np.ndarray
65
+ The solid angle map grid in steradians (default) or square degrees.
66
+ First index is latitude/el, second index is longitude/az.
67
+ """
68
+ if input_degrees:
69
+ spacing = np.deg2rad(spacing)
70
+
71
+ if spacing <= 0:
72
+ raise ValueError("Spacing must be positive valued, non-zero.")
73
+
74
+ if not np.isclose((np.pi / spacing) % 1, 0):
75
+ raise ValueError("Spacing must divide evenly into pi radians.")
76
+
77
+ latitudes = np.arange(-np.pi / 2, np.pi / 2 + spacing, step=spacing)
78
+ sine_latitudes = np.sin(latitudes)
79
+ delta_sine_latitudes = np.diff(sine_latitudes)
80
+ solid_angle_by_latitude = np.abs(spacing * delta_sine_latitudes)
81
+
82
+ solid_angle_grid = np.repeat(
83
+ solid_angle_by_latitude[:, np.newaxis], (2 * np.pi) / spacing, axis=1
84
+ )
85
+
86
+ if output_degrees:
87
+ solid_angle_grid *= (180 / np.pi) ** 2
88
+
89
+ return solid_angle_grid
90
+
91
+
92
+ def build_az_el_grid(
93
+ spacing: float,
94
+ input_degrees: bool = False,
95
+ output_degrees: bool = False,
96
+ centered_azimuth: bool = False,
97
+ centered_elevation: bool = True,
98
+ ) -> tuple[NDArray, NDArray, NDArray, NDArray]:
99
+ """
100
+ Build a 2D grid of azimuth and elevation angles.
101
+
102
+ Azimuth and Elevation values represent the center of each grid cell,
103
+ so the grid is offset by half the spacing.
104
+
105
+ Parameters
106
+ ----------
107
+ spacing : float
108
+ Spacing of the grid in degrees if `input_degrees` is True, else radians.
109
+ input_degrees : bool, optional
110
+ Whether the spacing is specified in degrees and must be converted to radians,
111
+ by default False (indicating radians).
112
+ output_degrees : bool, optional
113
+ Whether the output azimuth and elevation angles should be in degrees,
114
+ by default False (indicating radians).
115
+ centered_azimuth : bool, optional
116
+ Whether the azimuth grid should be centered around 0 degrees/0 radians,
117
+ i.e. from -pi to pi radians, by default False, indicating 0 to 2pi radians.
118
+ If True, the azimuth grid will be from -pi to pi radians.
119
+ centered_elevation : bool, optional
120
+ Whether the elevation grid should be centered around 0 degrees/0 radians,
121
+ i.e. from -pi/2 to pi/2 radians, by default True.
122
+ If False, the elevation grid will be from 0 to pi radians.
123
+
124
+ Returns
125
+ -------
126
+ tuple[NDArray, NDArray, NDArray, NDArray]
127
+ - The evenly spaced, 1D range of azimuth angles
128
+ e.g.(0, 0.5, 1, ..., 359.5) deg.
129
+ - The evenly spaced, 1D range of elevation angles
130
+ e.g.(-90, -89.5, ..., 89.5) deg.
131
+ - The 2D grid of azimuth angles (azimuths for each elevation).
132
+ This grid will be constant along the elevation (0th) axis.
133
+ - The 2D grid of elevation angles (elevations for each azimuth).
134
+ This grid will be constant along the azimuth (1st) axis.
135
+
136
+ Raises
137
+ ------
138
+ ValueError
139
+ If the spacing is not positive or does not divide evenly into pi radians.
140
+ """
141
+ if input_degrees:
142
+ spacing = np.deg2rad(spacing)
143
+
144
+ if spacing <= 0:
145
+ raise ValueError("Spacing must be positive valued, non-zero.")
146
+
147
+ if not np.isclose((np.pi / spacing) % 1, 0):
148
+ raise ValueError("Spacing must divide evenly into pi radians.")
149
+
150
+ el_range = np.arange(spacing / 2, np.pi, spacing)
151
+ az_range = np.arange(spacing / 2, 2 * np.pi, spacing)
152
+ if centered_azimuth:
153
+ az_range = az_range - np.pi
154
+ if centered_elevation:
155
+ el_range = el_range - np.pi / 2
156
+
157
+ # Reverse the elevation range so that the grid is in the order
158
+ # defined by the Ultra prototype code (`build_dps_grid.m`).
159
+ el_range = el_range[::-1]
160
+
161
+ az_grid, el_grid = np.meshgrid(az_range, el_range)
162
+
163
+ if output_degrees:
164
+ az_range = np.rad2deg(az_range)
165
+ el_range = np.rad2deg(el_range)
166
+ az_grid = np.rad2deg(az_grid)
167
+ el_grid = np.rad2deg(el_grid)
168
+
169
+ return az_range, el_range, az_grid, el_grid
170
+
171
+
172
+ @typing.no_type_check
173
+ def rewrap_even_spaced_el_az_grid(
174
+ raveled_values: NDArray,
175
+ shape: typing.Optional[tuple[int]] = None,
176
+ extra_axis: bool = False,
177
+ ) -> NDArray:
178
+ """
179
+ Take an unwrapped (raveled) 1D array and reshapes it into a 2D el/az grid.
180
+
181
+ Assumes the following must be true of the original grid:
182
+ 1. Grid was evenly spaced in angular space,
183
+ 2. Grid had the same spacing in both azimuth and elevation.
184
+ 3. Elevation is the 0th axis (and extends a total of 180 degrees),
185
+ 4. Azimuth is the 1st axis (and extends a total of 360 degrees).
186
+ 5. The grid was raveled in Fortran (F) order.
187
+
188
+ Parameters
189
+ ----------
190
+ raveled_values : NDArray
191
+ 1D array of values to be reshaped into a 2D grid.
192
+ shape : tuple[int], optional
193
+ The shape of the original grid, if known, by default None.
194
+ If None, the shape will be inferred from the size of the input array.
195
+ extra_axis : bool, optional
196
+ If True, input is a 2D array with latter axis being 'extra', non-spatial axis.
197
+ This axis (e.g. energy bins) will be preserved in the reshaped grid.
198
+
199
+ Returns
200
+ -------
201
+ NDArray
202
+ The reshaped 2D grid of values.
203
+
204
+ Raises
205
+ ------
206
+ ValueError
207
+ If the input is not a 1D array or 2D array with an extra axis.
208
+ """
209
+ if raveled_values.ndim not in (1, 2) or (
210
+ raveled_values.ndim == 2 and not extra_axis
211
+ ):
212
+ raise ValueError("Input must be a 1D array or 2D array with extra axis.")
213
+
214
+ # We can infer the shape if its evenly spaced and 2D
215
+ if not shape:
216
+ spacing_deg = 1 / np.sqrt(raveled_values.shape[0] / (360 * 180))
217
+ shape = (int(180 // spacing_deg), int(360 // spacing_deg))
218
+
219
+ if extra_axis:
220
+ shape = (shape[0], shape[1], raveled_values.shape[1])
221
+ return raveled_values.reshape(shape, order="F")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: imap-processing
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: IMAP Science Operations Center Processing
5
5
  License: MIT
6
6
  Keywords: IMAP,SDC,SOC,Science Operations