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.
- imap_processing/_version.py +2 -2
- imap_processing/ancillary/ancillary_dataset_combiner.py +161 -1
- imap_processing/ccsds/excel_to_xtce.py +12 -0
- imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -6
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +312 -274
- imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +39 -28
- imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +1048 -183
- imap_processing/cdf/config/imap_constant_attrs.yaml +4 -2
- imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +12 -0
- imap_processing/cdf/config/imap_hi_global_cdf_attrs.yaml +5 -0
- imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +10 -4
- imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +163 -100
- imap_processing/cdf/config/imap_hit_l2_variable_attrs.yaml +4 -4
- imap_processing/cdf/config/imap_ialirt_l1_variable_attrs.yaml +97 -54
- imap_processing/cdf/config/imap_idex_l2a_variable_attrs.yaml +33 -4
- imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +44 -44
- imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +77 -61
- imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +30 -0
- imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +4 -15
- imap_processing/cdf/config/imap_lo_l1c_variable_attrs.yaml +189 -98
- imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +99 -2
- imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +24 -1
- imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +60 -0
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +99 -11
- imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +50 -7
- imap_processing/cli.py +121 -44
- imap_processing/codice/codice_l1a.py +165 -77
- imap_processing/codice/codice_l1b.py +1 -1
- imap_processing/codice/codice_l2.py +118 -19
- imap_processing/codice/constants.py +1217 -1089
- imap_processing/decom.py +1 -4
- imap_processing/ena_maps/ena_maps.py +32 -25
- imap_processing/ena_maps/utils/naming.py +8 -2
- imap_processing/glows/ancillary/imap_glows_exclusions-by-instr-team_20250923_v002.dat +10 -0
- imap_processing/glows/ancillary/imap_glows_map-of-excluded-regions_20250923_v002.dat +393 -0
- imap_processing/glows/ancillary/imap_glows_map-of-uv-sources_20250923_v002.dat +593 -0
- imap_processing/glows/ancillary/imap_glows_pipeline_settings_20250923_v002.json +54 -0
- imap_processing/glows/ancillary/imap_glows_suspected-transients_20250923_v002.dat +10 -0
- imap_processing/glows/l1b/glows_l1b.py +99 -9
- imap_processing/glows/l1b/glows_l1b_data.py +350 -38
- imap_processing/glows/l2/glows_l2.py +11 -0
- imap_processing/hi/hi_l1a.py +124 -3
- imap_processing/hi/hi_l1b.py +154 -71
- imap_processing/hi/hi_l2.py +84 -51
- imap_processing/hi/utils.py +153 -8
- imap_processing/hit/l0/constants.py +3 -0
- imap_processing/hit/l0/decom_hit.py +5 -8
- imap_processing/hit/l1a/hit_l1a.py +375 -45
- imap_processing/hit/l1b/constants.py +5 -0
- imap_processing/hit/l1b/hit_l1b.py +61 -131
- imap_processing/hit/l2/constants.py +1 -1
- imap_processing/hit/l2/hit_l2.py +10 -11
- imap_processing/ialirt/calculate_ingest.py +219 -0
- imap_processing/ialirt/constants.py +32 -1
- imap_processing/ialirt/generate_coverage.py +201 -0
- imap_processing/ialirt/l0/ialirt_spice.py +5 -2
- imap_processing/ialirt/l0/parse_mag.py +337 -29
- imap_processing/ialirt/l0/process_hit.py +5 -3
- imap_processing/ialirt/l0/process_swapi.py +41 -25
- imap_processing/ialirt/l0/process_swe.py +23 -7
- imap_processing/ialirt/process_ephemeris.py +70 -14
- imap_processing/ialirt/utils/constants.py +22 -16
- imap_processing/ialirt/utils/create_xarray.py +42 -19
- imap_processing/idex/idex_constants.py +1 -5
- imap_processing/idex/idex_l0.py +2 -2
- imap_processing/idex/idex_l1a.py +2 -3
- imap_processing/idex/idex_l1b.py +2 -3
- imap_processing/idex/idex_l2a.py +130 -4
- imap_processing/idex/idex_l2b.py +313 -119
- imap_processing/idex/idex_utils.py +1 -3
- imap_processing/lo/l0/lo_apid.py +1 -0
- imap_processing/lo/l0/lo_science.py +25 -24
- imap_processing/lo/l1a/lo_l1a.py +44 -0
- imap_processing/lo/l1b/lo_l1b.py +3 -3
- imap_processing/lo/l1c/lo_l1c.py +116 -50
- imap_processing/lo/l2/lo_l2.py +29 -29
- imap_processing/lo/lo_ancillary.py +55 -0
- imap_processing/lo/packet_definitions/lo_xtce.xml +5359 -106
- imap_processing/mag/constants.py +1 -0
- imap_processing/mag/l1a/mag_l1a.py +1 -0
- imap_processing/mag/l1a/mag_l1a_data.py +26 -0
- imap_processing/mag/l1b/mag_l1b.py +3 -2
- imap_processing/mag/l1c/interpolation_methods.py +14 -15
- imap_processing/mag/l1c/mag_l1c.py +23 -6
- imap_processing/mag/l1d/__init__.py +0 -0
- imap_processing/mag/l1d/mag_l1d.py +176 -0
- imap_processing/mag/l1d/mag_l1d_data.py +725 -0
- imap_processing/mag/l2/__init__.py +0 -0
- imap_processing/mag/l2/mag_l2.py +25 -20
- imap_processing/mag/l2/mag_l2_data.py +199 -130
- imap_processing/quality_flags.py +28 -2
- imap_processing/spice/geometry.py +101 -36
- imap_processing/spice/pointing_frame.py +1 -7
- imap_processing/spice/repoint.py +29 -2
- imap_processing/spice/spin.py +32 -8
- imap_processing/spice/time.py +60 -19
- imap_processing/swapi/l1/swapi_l1.py +10 -4
- imap_processing/swapi/l2/swapi_l2.py +66 -24
- imap_processing/swapi/swapi_utils.py +1 -1
- imap_processing/swe/l1b/swe_l1b.py +3 -6
- imap_processing/ultra/constants.py +28 -3
- imap_processing/ultra/l0/decom_tools.py +15 -8
- imap_processing/ultra/l0/decom_ultra.py +35 -11
- imap_processing/ultra/l0/ultra_utils.py +102 -12
- imap_processing/ultra/l1a/ultra_l1a.py +26 -6
- imap_processing/ultra/l1b/cullingmask.py +6 -3
- imap_processing/ultra/l1b/de.py +122 -26
- imap_processing/ultra/l1b/extendedspin.py +29 -2
- imap_processing/ultra/l1b/lookup_utils.py +424 -50
- imap_processing/ultra/l1b/quality_flag_filters.py +23 -0
- imap_processing/ultra/l1b/ultra_l1b_culling.py +356 -5
- imap_processing/ultra/l1b/ultra_l1b_extended.py +534 -90
- imap_processing/ultra/l1c/helio_pset.py +127 -7
- imap_processing/ultra/l1c/l1c_lookup_utils.py +256 -0
- imap_processing/ultra/l1c/spacecraft_pset.py +90 -15
- imap_processing/ultra/l1c/ultra_l1c.py +6 -0
- imap_processing/ultra/l1c/ultra_l1c_culling.py +85 -0
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +446 -341
- imap_processing/ultra/l2/ultra_l2.py +0 -1
- imap_processing/ultra/utils/ultra_l1_utils.py +40 -3
- imap_processing/utils.py +3 -4
- {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/METADATA +3 -3
- {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/RECORD +126 -126
- imap_processing/idex/idex_l2c.py +0 -250
- imap_processing/spice/kernels.py +0 -187
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +0 -526
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +0 -526
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +0 -526
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +0 -524
- imap_processing/ultra/lookup_tables/EgyNorm.mem.csv +0 -32769
- imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
- imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
- imap_processing/ultra/lookup_tables/dps_grid45_compressed.cdf +0 -0
- imap_processing/ultra/lookup_tables/ultra45_back-pos-luts.csv +0 -4097
- imap_processing/ultra/lookup_tables/ultra45_tdc_norm.csv +0 -2050
- imap_processing/ultra/lookup_tables/ultra90_back-pos-luts.csv +0 -4097
- imap_processing/ultra/lookup_tables/ultra90_tdc_norm.csv +0 -2050
- imap_processing/ultra/lookup_tables/yadjust.csv +0 -257
- {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/LICENSE +0 -0
- {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/WHEEL +0 -0
- {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/entry_points.txt +0 -0
|
@@ -54,6 +54,8 @@ class CoDICEL1aPipeline:
|
|
|
54
54
|
|
|
55
55
|
Methods
|
|
56
56
|
-------
|
|
57
|
+
apply_despinning()
|
|
58
|
+
Apply the despinning algorithm to lo- angular and priority products.
|
|
57
59
|
decompress_data(science_values)
|
|
58
60
|
Perform decompression on the data.
|
|
59
61
|
define_coordinates()
|
|
@@ -87,6 +89,73 @@ class CoDICEL1aPipeline:
|
|
|
87
89
|
self.plan_step = plan_step
|
|
88
90
|
self.view_id = view_id
|
|
89
91
|
|
|
92
|
+
def apply_despinning(self) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Apply the despinning algorithm to lo- angular and priority products.
|
|
95
|
+
|
|
96
|
+
This only applies to CoDICE-Lo angular and priority data products. See
|
|
97
|
+
sections 9.3.4 and 9.3.5 of the algorithm document for more details.
|
|
98
|
+
"""
|
|
99
|
+
# Determine the appropriate dimensions for the despun data
|
|
100
|
+
num_energies = self.config["dims"]["esa_step"]
|
|
101
|
+
num_spin_sectors = self.config["dims"]["spin_sector"]
|
|
102
|
+
num_spins = num_spin_sectors * 2
|
|
103
|
+
num_counters = self.config["num_counters"]
|
|
104
|
+
num_positions = self.config["dims"].get(
|
|
105
|
+
"inst_az"
|
|
106
|
+
) # Defaults to None if not present
|
|
107
|
+
|
|
108
|
+
# The dimensions are dependent on the specific data product
|
|
109
|
+
if "angular" in self.config["dataset_name"]:
|
|
110
|
+
despun_dims: tuple[int, ...] = (
|
|
111
|
+
num_energies,
|
|
112
|
+
num_positions,
|
|
113
|
+
num_spins,
|
|
114
|
+
num_counters,
|
|
115
|
+
)
|
|
116
|
+
elif "priority" in self.config["dataset_name"]:
|
|
117
|
+
despun_dims = (num_energies, num_spins, num_counters)
|
|
118
|
+
|
|
119
|
+
# Placeholder for finalized despun data
|
|
120
|
+
self.data: list[np.ndarray] # Needed to appease mypy
|
|
121
|
+
despun_data = [np.zeros(despun_dims) for _ in range(len(self.data))]
|
|
122
|
+
|
|
123
|
+
# Iterate over the energy and spin sector indices, and determine the
|
|
124
|
+
# appropriate pixel orientation. The combination of the pixel
|
|
125
|
+
# orientation and the azimuth determine which spin sector the data
|
|
126
|
+
# gets stored in.
|
|
127
|
+
# TODO: All these nested for-loops are bad. Try to find a better
|
|
128
|
+
# solution.
|
|
129
|
+
for i, epoch_data in enumerate(self.data):
|
|
130
|
+
for energy_index in range(num_energies):
|
|
131
|
+
pixel_orientation = constants.PIXEL_ORIENTATIONS[energy_index]
|
|
132
|
+
for spin_sector_index in range(num_spin_sectors):
|
|
133
|
+
for azimuth_index in range(num_spins):
|
|
134
|
+
if pixel_orientation == "A" and azimuth_index < 12:
|
|
135
|
+
despun_spin_sector = spin_sector_index
|
|
136
|
+
elif pixel_orientation == "A" and azimuth_index >= 12:
|
|
137
|
+
despun_spin_sector = spin_sector_index + 12
|
|
138
|
+
elif pixel_orientation == "B" and azimuth_index < 12:
|
|
139
|
+
despun_spin_sector = spin_sector_index + 12
|
|
140
|
+
elif pixel_orientation == "B" and azimuth_index >= 12:
|
|
141
|
+
despun_spin_sector = spin_sector_index
|
|
142
|
+
|
|
143
|
+
if "angular" in self.config["dataset_name"]:
|
|
144
|
+
spin_data = epoch_data[
|
|
145
|
+
energy_index, :, spin_sector_index, :
|
|
146
|
+
] # (5, 4)
|
|
147
|
+
despun_data[i][energy_index, :, despun_spin_sector, :] = (
|
|
148
|
+
spin_data
|
|
149
|
+
)
|
|
150
|
+
elif "priority" in self.config["dataset_name"]:
|
|
151
|
+
spin_data = epoch_data[energy_index, spin_sector_index, :]
|
|
152
|
+
despun_data[i][energy_index, despun_spin_sector, :] = (
|
|
153
|
+
spin_data
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Replace original data
|
|
157
|
+
self.data = despun_data
|
|
158
|
+
|
|
90
159
|
def decompress_data(self, science_values: list[NDArray[str]] | list[str]) -> None:
|
|
91
160
|
"""
|
|
92
161
|
Perform decompression on the data.
|
|
@@ -122,7 +191,7 @@ class CoDICEL1aPipeline:
|
|
|
122
191
|
|
|
123
192
|
else:
|
|
124
193
|
for packet_data, byte_count in zip(
|
|
125
|
-
science_values, self.dataset.byte_count.data
|
|
194
|
+
science_values, self.dataset.byte_count.data, strict=False
|
|
126
195
|
):
|
|
127
196
|
# Convert from numpy array to byte object
|
|
128
197
|
values = packet_data[()]
|
|
@@ -134,17 +203,20 @@ class CoDICEL1aPipeline:
|
|
|
134
203
|
decompressed_values = decompress(values, compression_algorithm)
|
|
135
204
|
self.raw_data.append(decompressed_values)
|
|
136
205
|
|
|
137
|
-
def define_coordinates(self) -> None:
|
|
206
|
+
def define_coordinates(self) -> None: # noqa: PLR0912 (too many branches)
|
|
138
207
|
"""
|
|
139
208
|
Create ``xr.DataArrays`` for the coords needed in the final dataset.
|
|
140
209
|
|
|
141
210
|
The coordinates for the dataset depend on the data product being made.
|
|
211
|
+
|
|
212
|
+
# TODO: Split this function up or simplify it to avoid too many branches
|
|
213
|
+
# error.
|
|
142
214
|
"""
|
|
143
215
|
self.coords = {}
|
|
144
216
|
|
|
145
217
|
coord_names = [
|
|
146
|
-
*self.config["
|
|
147
|
-
*[key + "_label" for key in self.config["
|
|
218
|
+
*self.config["dims"].keys(),
|
|
219
|
+
*[key + "_label" for key in self.config["dims"].keys()],
|
|
148
220
|
]
|
|
149
221
|
|
|
150
222
|
# Define epoch coordinates
|
|
@@ -169,12 +241,17 @@ class CoDICEL1aPipeline:
|
|
|
169
241
|
if name in [
|
|
170
242
|
"esa_step",
|
|
171
243
|
"inst_az",
|
|
172
|
-
"spin_sector",
|
|
173
244
|
"spin_sector_pairs",
|
|
174
245
|
"spin_sector_index",
|
|
175
246
|
"ssd_index",
|
|
176
247
|
]:
|
|
177
|
-
values = np.arange(self.config["
|
|
248
|
+
values = np.arange(self.config["dims"][name])
|
|
249
|
+
dims = [name]
|
|
250
|
+
elif name == "spin_sector":
|
|
251
|
+
if self.config["dataset_name"] in constants.REQUIRES_DESPINNING:
|
|
252
|
+
values = np.arange(24)
|
|
253
|
+
else:
|
|
254
|
+
values = np.arange(self.config["dims"][name])
|
|
178
255
|
dims = [name]
|
|
179
256
|
elif name == "spin_sector_pairs_label":
|
|
180
257
|
values = np.array(
|
|
@@ -188,16 +265,29 @@ class CoDICEL1aPipeline:
|
|
|
188
265
|
]
|
|
189
266
|
)
|
|
190
267
|
dims = [name]
|
|
268
|
+
elif name == "inst_az_label":
|
|
269
|
+
if self.config["dataset_name"] == "imap_codice_l1a_lo-nsw-angular":
|
|
270
|
+
values = [str(x) for x in range(4, 23)]
|
|
271
|
+
elif self.config["dataset_name"] == "imap_codice_l1a_lo-sw-angular":
|
|
272
|
+
values = ["1", "2", "3", "23", "24"]
|
|
273
|
+
else:
|
|
274
|
+
values = np.arange(self.config["dims"]["inst_az"]).astype(str)
|
|
275
|
+
dims = ["inst_az"]
|
|
191
276
|
elif name in [
|
|
192
|
-
"spin_sector_label",
|
|
193
277
|
"esa_step_label",
|
|
194
|
-
"inst_az_label",
|
|
195
278
|
"spin_sector_index_label",
|
|
196
279
|
"ssd_index_label",
|
|
197
280
|
]:
|
|
198
281
|
key = name.removesuffix("_label")
|
|
199
|
-
values = np.arange(self.config["
|
|
282
|
+
values = np.arange(self.config["dims"][key]).astype(str)
|
|
200
283
|
dims = [key]
|
|
284
|
+
elif name == "spin_sector_label":
|
|
285
|
+
key = name.removesuffix("_label")
|
|
286
|
+
dims = [key]
|
|
287
|
+
if self.config["dataset_name"] in constants.REQUIRES_DESPINNING:
|
|
288
|
+
values = np.arange(24).astype(str)
|
|
289
|
+
else:
|
|
290
|
+
values = np.arange(self.config["dims"][key]).astype(str)
|
|
201
291
|
|
|
202
292
|
coord = xr.DataArray(
|
|
203
293
|
values,
|
|
@@ -230,16 +320,16 @@ class CoDICEL1aPipeline:
|
|
|
230
320
|
# Stack the data so that it is easier to reshape and iterate over
|
|
231
321
|
all_data = np.stack(self.data)
|
|
232
322
|
|
|
233
|
-
# The dimension of all_data is something like (epoch,
|
|
234
|
-
#
|
|
323
|
+
# The dimension of all_data is something like (epoch, num_energy_steps,
|
|
324
|
+
# num_positions, num_spin_sectors, num_counters) (or may be slightly
|
|
235
325
|
# different depending on the data product). In any case, iterate over
|
|
236
326
|
# the num_counters dimension to isolate the data for each counter so
|
|
237
327
|
# each counter's data can be placed in a separate CDF data variable.
|
|
238
328
|
for counter, variable_name in zip(
|
|
239
|
-
range(all_data.shape[1]), self.config["variable_names"]
|
|
329
|
+
range(all_data.shape[-1]), self.config["variable_names"], strict=False
|
|
240
330
|
):
|
|
241
331
|
# Extract the counter data
|
|
242
|
-
counter_data = all_data[
|
|
332
|
+
counter_data = all_data[..., counter]
|
|
243
333
|
|
|
244
334
|
# Get the CDF attributes
|
|
245
335
|
descriptor = self.config["dataset_name"].split("imap_codice_l1a_")[-1]
|
|
@@ -249,7 +339,7 @@ class CoDICEL1aPipeline:
|
|
|
249
339
|
# For most products, the final CDF dimensions always has "epoch" as
|
|
250
340
|
# the first dimension followed by the dimensions for the specific
|
|
251
341
|
# data product
|
|
252
|
-
dims = ["epoch", *list(self.config["
|
|
342
|
+
dims = ["epoch", *list(self.config["dims"].keys())]
|
|
253
343
|
|
|
254
344
|
# However, CoDICE-Hi products use specific energy bins for the
|
|
255
345
|
# energy dimension
|
|
@@ -306,7 +396,7 @@ class CoDICEL1aPipeline:
|
|
|
306
396
|
``xarray`` dataset for the data product, with added energy variables.
|
|
307
397
|
"""
|
|
308
398
|
energy_bin_name = f"energy_{species}"
|
|
309
|
-
centers,
|
|
399
|
+
centers, deltas_minus, deltas_plus = self.get_hi_energy_table_data(
|
|
310
400
|
energy_bin_name.split("energy_")[-1]
|
|
311
401
|
)
|
|
312
402
|
|
|
@@ -319,11 +409,19 @@ class CoDICEL1aPipeline:
|
|
|
319
409
|
check_schema=False,
|
|
320
410
|
),
|
|
321
411
|
)
|
|
322
|
-
dataset[f"{energy_bin_name}
|
|
323
|
-
|
|
324
|
-
dims=[f"{energy_bin_name}
|
|
412
|
+
dataset[f"{energy_bin_name}_minus"] = xr.DataArray(
|
|
413
|
+
deltas_minus,
|
|
414
|
+
dims=[f"{energy_bin_name}_minus"],
|
|
415
|
+
attrs=self.cdf_attrs.get_variable_attributes(
|
|
416
|
+
f"{self.config['dataset_name'].split('_')[-1]}-{energy_bin_name}_minus",
|
|
417
|
+
check_schema=False,
|
|
418
|
+
),
|
|
419
|
+
)
|
|
420
|
+
dataset[f"{energy_bin_name}_plus"] = xr.DataArray(
|
|
421
|
+
deltas_plus,
|
|
422
|
+
dims=[f"{energy_bin_name}_plus"],
|
|
325
423
|
attrs=self.cdf_attrs.get_variable_attributes(
|
|
326
|
-
f"{self.config['dataset_name'].split('_')[-1]}-{energy_bin_name}
|
|
424
|
+
f"{self.config['dataset_name'].split('_')[-1]}-{energy_bin_name}_plus",
|
|
327
425
|
check_schema=False,
|
|
328
426
|
),
|
|
329
427
|
)
|
|
@@ -398,6 +496,12 @@ class CoDICEL1aPipeline:
|
|
|
398
496
|
dims = ["epoch"]
|
|
399
497
|
attrs = self.cdf_attrs.get_variable_attributes("spin_period")
|
|
400
498
|
|
|
499
|
+
# The k-factor is a constant that maps voltages to energies
|
|
500
|
+
elif variable_name == "k_factor":
|
|
501
|
+
variable_data = np.array([constants.K_FACTOR], dtype=np.float32)
|
|
502
|
+
dims = [""]
|
|
503
|
+
attrs = self.cdf_attrs.get_variable_attributes("k_factor")
|
|
504
|
+
|
|
401
505
|
# Add variable to the dataset
|
|
402
506
|
dataset[variable_name] = xr.DataArray(
|
|
403
507
|
variable_data,
|
|
@@ -475,7 +579,7 @@ class CoDICEL1aPipeline:
|
|
|
475
579
|
|
|
476
580
|
def get_hi_energy_table_data(
|
|
477
581
|
self, species: str
|
|
478
|
-
) -> tuple[NDArray[float], NDArray[float]]:
|
|
582
|
+
) -> tuple[NDArray[float], NDArray[float], NDArray[float]]:
|
|
479
583
|
"""
|
|
480
584
|
Retrieve energy table data for CoDICE-Hi products.
|
|
481
585
|
|
|
@@ -493,22 +597,25 @@ class CoDICEL1aPipeline:
|
|
|
493
597
|
-------
|
|
494
598
|
centers : NDArray[float]
|
|
495
599
|
An array whose values represent the centers of the energy bins.
|
|
496
|
-
|
|
497
|
-
An array whose values represent the deltas of the energy bins.
|
|
600
|
+
deltas_minus : NDArray[float]
|
|
601
|
+
An array whose values represent the minus deltas of the energy bins.
|
|
602
|
+
deltas_plus : NDArray[float]
|
|
603
|
+
An array whose values represent the plus deltas of the energy bins.
|
|
498
604
|
"""
|
|
499
605
|
data_product = self.config["dataset_name"].split("-")[-1].upper()
|
|
500
|
-
energy_table =
|
|
501
|
-
|
|
502
|
-
# Find the centers and deltas of the energy bins
|
|
503
|
-
centers = np.array(
|
|
504
|
-
[
|
|
505
|
-
(energy_table[i] + energy_table[i + 1]) / 2
|
|
506
|
-
for i in range(len(energy_table) - 1)
|
|
507
|
-
]
|
|
606
|
+
energy_table = np.array(
|
|
607
|
+
getattr(constants, f"{data_product}_ENERGY_TABLE")[species]
|
|
508
608
|
)
|
|
509
|
-
deltas = energy_table[1:] - centers
|
|
510
609
|
|
|
511
|
-
|
|
610
|
+
# Find the geometric centers and deltas of the energy bins
|
|
611
|
+
# The delta minus is the difference between the center of the bin
|
|
612
|
+
# and the 'left edge' of the bin. The delta plus is the difference
|
|
613
|
+
# between the 'right edge' of the bin and the center of the bin
|
|
614
|
+
centers = np.sqrt(energy_table[:-1] * energy_table[1:])
|
|
615
|
+
deltas_minus = centers - energy_table[:-1]
|
|
616
|
+
deltas_plus = energy_table[1:] - centers
|
|
617
|
+
|
|
618
|
+
return centers, deltas_minus, deltas_plus
|
|
512
619
|
|
|
513
620
|
def reshape_binned_data(self, dataset: xr.Dataset) -> dict[str, list]:
|
|
514
621
|
"""
|
|
@@ -589,52 +696,31 @@ class CoDICEL1aPipeline:
|
|
|
589
696
|
|
|
590
697
|
These data need to be divided up by species or priorities (or
|
|
591
698
|
what I am calling "counters" as a general term), and re-arranged into
|
|
592
|
-
|
|
593
|
-
and energies (depending on the data product).
|
|
699
|
+
multidimensional arrays representing dimensions such as time,
|
|
700
|
+
spin sectors, positions, and energies (depending on the data product).
|
|
594
701
|
|
|
595
702
|
However, the existence and order of these dimensions can vary depending
|
|
596
|
-
on the specific data product, so we define this in the "
|
|
597
|
-
|
|
598
|
-
defines how the dimensions are written into the packet data, while
|
|
599
|
-
"output_dims" defines how the dimensions should be written to the final
|
|
600
|
-
CDF product.
|
|
703
|
+
on the specific data product, so we define this in the "dims" key of the
|
|
704
|
+
configuration dictionary.
|
|
601
705
|
"""
|
|
602
706
|
# This will contain the reshaped data for all counters
|
|
603
707
|
self.data = []
|
|
604
708
|
|
|
605
|
-
#
|
|
606
|
-
# the packet data. The number of counters is the
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
self.config["num_counters"],
|
|
612
|
-
*self.config["input_dims"].values(),
|
|
613
|
-
)
|
|
614
|
-
else:
|
|
615
|
-
reshape_dims = (
|
|
616
|
-
*self.config["input_dims"].values(),
|
|
617
|
-
self.config["num_counters"],
|
|
618
|
-
)
|
|
619
|
-
|
|
620
|
-
# Then, transpose the data based on how the dimensions should be written
|
|
621
|
-
# to the CDF file. Since this is specific to each data product, we need
|
|
622
|
-
# to determine this dynamically based on the "output_dims" config.
|
|
623
|
-
# Again, lo-counters-aggregated is treated slightly differently
|
|
624
|
-
input_keys = ["num_counters", *self.config["input_dims"].keys()]
|
|
625
|
-
output_keys = ["num_counters", *self.config["output_dims"].keys()]
|
|
626
|
-
if self.config["dataset_name"] != "imap_codice_l1a_lo-counters-aggregated":
|
|
627
|
-
transpose_axes = [input_keys.index(dim) for dim in output_keys]
|
|
628
|
-
else:
|
|
629
|
-
transpose_axes = [1, 2, 0] # [esa_step, spin_sector_pairs, num_counters]
|
|
630
|
-
|
|
709
|
+
# Reshape the data based on how it is written to the data array of
|
|
710
|
+
# the packet data. The number of counters is the last dimension / axis.
|
|
711
|
+
reshape_dims = (
|
|
712
|
+
*self.config["dims"].values(),
|
|
713
|
+
self.config["num_counters"],
|
|
714
|
+
)
|
|
631
715
|
for packet_data in self.raw_data:
|
|
632
716
|
reshaped_packet_data = np.array(packet_data, dtype=np.uint32).reshape(
|
|
633
717
|
reshape_dims
|
|
634
718
|
)
|
|
635
|
-
|
|
719
|
+
self.data.append(reshaped_packet_data)
|
|
636
720
|
|
|
637
|
-
|
|
721
|
+
# Apply despinning if necessary
|
|
722
|
+
if self.config["dataset_name"] in constants.REQUIRES_DESPINNING:
|
|
723
|
+
self.apply_despinning()
|
|
638
724
|
|
|
639
725
|
# No longer need to keep the raw data around
|
|
640
726
|
del self.raw_data
|
|
@@ -950,9 +1036,9 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
|
|
|
950
1036
|
|
|
951
1037
|
# Create the dataset to hold the data variables
|
|
952
1038
|
if apid == CODICEAPID.COD_LO_PHA:
|
|
953
|
-
attrs = cdf_attrs.get_global_attributes("imap_codice_l1a_lo-
|
|
1039
|
+
attrs = cdf_attrs.get_global_attributes("imap_codice_l1a_lo-direct-events")
|
|
954
1040
|
elif apid == CODICEAPID.COD_HI_PHA:
|
|
955
|
-
attrs = cdf_attrs.get_global_attributes("imap_codice_l1a_hi-
|
|
1041
|
+
attrs = cdf_attrs.get_global_attributes("imap_codice_l1a_hi-direct-events")
|
|
956
1042
|
dataset = xr.Dataset(
|
|
957
1043
|
coords={
|
|
958
1044
|
"epoch": epoch,
|
|
@@ -967,9 +1053,9 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
|
|
|
967
1053
|
# Create the CDF data variables for each Priority and Field
|
|
968
1054
|
for i in range(constants.DE_DATA_PRODUCT_CONFIGURATIONS[apid]["num_priorities"]):
|
|
969
1055
|
for field in constants.DE_DATA_PRODUCT_CONFIGURATIONS[apid]["cdf_fields"]:
|
|
970
|
-
variable_name = f"
|
|
1056
|
+
variable_name = f"p{i}_{field}"
|
|
971
1057
|
attrs = cdf_attrs.get_variable_attributes(variable_name)
|
|
972
|
-
if field in ["
|
|
1058
|
+
if field in ["num_events", "data_quality"]:
|
|
973
1059
|
dims = ["epoch"]
|
|
974
1060
|
else:
|
|
975
1061
|
dims = ["epoch", "event_num"]
|
|
@@ -1427,13 +1513,15 @@ def reshape_de_data(
|
|
|
1427
1513
|
for priority_num in range(num_priorities):
|
|
1428
1514
|
for field in bit_structure:
|
|
1429
1515
|
if field not in ["Priority", "Spare"]:
|
|
1430
|
-
data[f"
|
|
1516
|
+
data[f"p{priority_num}_{field}"] = np.full(
|
|
1431
1517
|
(num_epochs, 10000),
|
|
1432
1518
|
bit_structure[field]["fillval"],
|
|
1433
1519
|
dtype=bit_structure[field]["dtype"],
|
|
1434
1520
|
)
|
|
1435
|
-
data[f"
|
|
1436
|
-
|
|
1521
|
+
data[f"p{priority_num}_num_events"] = np.full(
|
|
1522
|
+
num_epochs, 65535, dtype=np.uint16
|
|
1523
|
+
)
|
|
1524
|
+
data[f"p{priority_num}_data_quality"] = np.full(num_epochs, 255, dtype=np.uint8)
|
|
1437
1525
|
|
|
1438
1526
|
# decompressed_data is one large list of values of length
|
|
1439
1527
|
# (<number of epochs> * <number of priorities>)
|
|
@@ -1457,8 +1545,8 @@ def reshape_de_data(
|
|
|
1457
1545
|
|
|
1458
1546
|
# Number of events and data quality can be determined at this stage
|
|
1459
1547
|
num_events = num_events_arr[epoch_start:epoch_end][i]
|
|
1460
|
-
data[f"
|
|
1461
|
-
data[f"
|
|
1548
|
+
data[f"p{priority_num}_num_events"][epoch_index] = num_events
|
|
1549
|
+
data[f"p{priority_num}_data_quality"][epoch_index] = data_quality[i]
|
|
1462
1550
|
|
|
1463
1551
|
# Iterate over each event
|
|
1464
1552
|
for event_index in range(num_events):
|
|
@@ -1489,7 +1577,7 @@ def reshape_de_data(
|
|
|
1489
1577
|
)
|
|
1490
1578
|
|
|
1491
1579
|
# Set the value into the data array
|
|
1492
|
-
data[f"
|
|
1580
|
+
data[f"p{priority_num}_{field_name}"][epoch_index, event_index] = (
|
|
1493
1581
|
value
|
|
1494
1582
|
)
|
|
1495
1583
|
bit_position += field_components["bit_length"]
|
|
@@ -116,7 +116,7 @@ def process_codice_l1b(file_path: Path) -> xr.Dataset:
|
|
|
116
116
|
descriptor = dataset_name.removeprefix("imap_codice_l1b_")
|
|
117
117
|
|
|
118
118
|
# Direct event data products do not have a level L1B
|
|
119
|
-
if descriptor in ["lo-
|
|
119
|
+
if descriptor in ["lo-direct-events", "hi-direct-events"]:
|
|
120
120
|
logger.warning("Encountered direct event data product. Skipping L1b processing")
|
|
121
121
|
return None
|
|
122
122
|
|
|
@@ -45,22 +45,112 @@ def process_codice_l2(file_path: Path) -> xr.Dataset:
|
|
|
45
45
|
# TODO: Could clean this up by using imap-data-access methods?
|
|
46
46
|
dataset_name = l1_dataset.attrs["Logical_source"]
|
|
47
47
|
data_level = dataset_name.removeprefix("imap_codice_").split("_")[0]
|
|
48
|
-
descriptor = dataset_name.removeprefix(f"imap_codice_{data_level}_")
|
|
49
48
|
dataset_name = dataset_name.replace(data_level, "l2")
|
|
50
49
|
|
|
51
|
-
# TODO: Temporary work-around to replace "PHA" naming convention with
|
|
52
|
-
# "direct events" This will eventually be changed at the L1 level and
|
|
53
|
-
# thus this will eventually be removed.
|
|
54
|
-
if descriptor == "lo-pha":
|
|
55
|
-
dataset_name = dataset_name.replace("lo-pha", "lo-direct-events")
|
|
56
|
-
elif descriptor == "hi-pha":
|
|
57
|
-
dataset_name = dataset_name.replace("hi-pha", "hi-direct-events")
|
|
58
|
-
|
|
59
50
|
# Use the L1 data product as a starting point for L2
|
|
60
51
|
l2_dataset = l1_dataset.copy()
|
|
61
52
|
|
|
62
53
|
# Get the L2 CDF attributes
|
|
63
54
|
cdf_attrs = ImapCdfAttributes()
|
|
55
|
+
l2_dataset = add_dataset_attributes(l2_dataset, dataset_name, cdf_attrs)
|
|
56
|
+
|
|
57
|
+
if dataset_name in [
|
|
58
|
+
"imap_codice_l2_hi-counters-singles",
|
|
59
|
+
"imap_codice_l2_hi-counters-aggregated",
|
|
60
|
+
"imap_codice_l2_lo-counters-singles",
|
|
61
|
+
"imap_codice_l2_lo-counters-aggregated",
|
|
62
|
+
"imap_codice_l2_lo-sw-priority",
|
|
63
|
+
"imap_codice_l2_lo-nsw-priority",
|
|
64
|
+
]:
|
|
65
|
+
# No changes needed. Just save to an L2 CDF file.
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
elif dataset_name == "imap_codice_l2_hi-direct-events":
|
|
69
|
+
# Convert the following data variables to physical units using
|
|
70
|
+
# calibration data:
|
|
71
|
+
# - ssd_energy
|
|
72
|
+
# - tof
|
|
73
|
+
# - elevation_angle
|
|
74
|
+
# - spin_angle
|
|
75
|
+
# These converted variables are *in addition* to the existing L1 variables
|
|
76
|
+
# The other data variables require no changes
|
|
77
|
+
# See section 11.1.2 of algorithm document
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
elif dataset_name == "imap_codice_l2_hi-sectored":
|
|
81
|
+
# Convert the sectored count rates using equation described in section
|
|
82
|
+
# 11.1.3 of algorithm document.
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
elif dataset_name == "imap_codice_l2_hi-omni":
|
|
86
|
+
# Calculate the omni-directional intensity for each species using
|
|
87
|
+
# equation described in section 11.1.4 of algorithm document
|
|
88
|
+
# hopefully this can also apply to hi-ialirt
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
elif dataset_name == "imap_codice_l2_lo-direct-events":
|
|
92
|
+
# Convert the following data variables to physical units using
|
|
93
|
+
# calibration data:
|
|
94
|
+
# - apd_energy
|
|
95
|
+
# - elevation_angle
|
|
96
|
+
# - tof
|
|
97
|
+
# - spin_sector
|
|
98
|
+
# - esa_step
|
|
99
|
+
# These converted variables are *in addition* to the existing L1 variables
|
|
100
|
+
# The other data variables require no changes
|
|
101
|
+
# See section 11.1.2 of algorithm document
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
elif dataset_name == "imap_codice_l2_lo-sw-angular":
|
|
105
|
+
# Calculate the sunward angular intensities using equation described in
|
|
106
|
+
# section 11.2.3 of algorithm document.
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
elif dataset_name == "imap_codice_l2_lo-nsw-angular":
|
|
110
|
+
# Calculate the non-sunward angular intensities using equation described
|
|
111
|
+
# in section 11.2.3 of algorithm document.
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
elif dataset_name == "imap_codice_l2_lo-sw-species":
|
|
115
|
+
# Calculate the sunward solar wind species intensities using equation
|
|
116
|
+
# described in section 11.2.4 of algorithm document.
|
|
117
|
+
# Calculate the pickup ion sunward solar wind intensities using equation
|
|
118
|
+
# described in section 11.2.4 of algorithm document.
|
|
119
|
+
# Hopefully this can also apply to lo-ialirt
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
elif dataset_name == "imap_codice_l2_lo-nsw-species":
|
|
123
|
+
# Calculate the non-sunward solar wind species intensities using
|
|
124
|
+
# equation described in section 11.2.4 of algorithm document.
|
|
125
|
+
# Calculate the pickup ion non-sunward solar wind intensities using
|
|
126
|
+
# equation described in section 11.2.4 of algorithm document.
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
logger.info(f"\nFinal data product:\n{l2_dataset}\n")
|
|
130
|
+
|
|
131
|
+
return l2_dataset
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def add_dataset_attributes(
|
|
135
|
+
l2_dataset: xr.Dataset, dataset_name: str, cdf_attrs: ImapCdfAttributes
|
|
136
|
+
) -> xr.Dataset:
|
|
137
|
+
"""
|
|
138
|
+
Add the global and variable attributes to the dataset.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
l2_dataset : xarray.Dataset
|
|
143
|
+
The dataset to update.
|
|
144
|
+
dataset_name : str
|
|
145
|
+
The name of the dataset.
|
|
146
|
+
cdf_attrs : ImapCdfAttributes
|
|
147
|
+
The attribute manager for CDF attributes.
|
|
148
|
+
|
|
149
|
+
Returns
|
|
150
|
+
-------
|
|
151
|
+
xarray.Dataset
|
|
152
|
+
The updated dataset.
|
|
153
|
+
"""
|
|
64
154
|
cdf_attrs.add_instrument_global_attrs("codice")
|
|
65
155
|
cdf_attrs.add_instrument_variable_attrs("codice", "l2")
|
|
66
156
|
|
|
@@ -68,14 +158,23 @@ def process_codice_l2(file_path: Path) -> xr.Dataset:
|
|
|
68
158
|
l2_dataset.attrs = cdf_attrs.get_global_attributes(dataset_name)
|
|
69
159
|
|
|
70
160
|
# Set the variable attributes
|
|
71
|
-
for variable_name in l2_dataset:
|
|
72
|
-
|
|
73
|
-
variable_name
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
161
|
+
for variable_name in l2_dataset.data_vars.keys():
|
|
162
|
+
try:
|
|
163
|
+
l2_dataset[variable_name].attrs = cdf_attrs.get_variable_attributes(
|
|
164
|
+
variable_name, check_schema=False
|
|
165
|
+
)
|
|
166
|
+
except KeyError:
|
|
167
|
+
# Some variables may have a product descriptor prefix in the
|
|
168
|
+
# cdf attributes key if they are common to multiple products.
|
|
169
|
+
descriptor = dataset_name.split("imap_codice_l2_")[-1]
|
|
170
|
+
cdf_attrs_key = f"{descriptor}-{variable_name}"
|
|
171
|
+
try:
|
|
172
|
+
l2_dataset[variable_name].attrs = cdf_attrs.get_variable_attributes(
|
|
173
|
+
f"{cdf_attrs_key}", check_schema=False
|
|
174
|
+
)
|
|
175
|
+
except KeyError:
|
|
176
|
+
logger.error(
|
|
177
|
+
f"Field '{variable_name}' and '{cdf_attrs_key}' not found in "
|
|
178
|
+
f"attribute manager."
|
|
179
|
+
)
|
|
81
180
|
return l2_dataset
|