imap-processing 0.18.0__py3-none-any.whl → 0.19.2__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/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -0
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +221 -1057
- imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +307 -283
- imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +1044 -203
- imap_processing/cdf/config/imap_constant_attrs.yaml +4 -2
- imap_processing/cdf/config/imap_enamaps_l2-common_variable_attrs.yaml +11 -0
- imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +15 -1
- 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_idex_l2a_variable_attrs.yaml +33 -4
- imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +8 -91
- imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +106 -16
- imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +5 -4
- 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 +85 -2
- imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +24 -1
- imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +20 -8
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +45 -35
- imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +110 -7
- imap_processing/cli.py +138 -93
- imap_processing/codice/codice_l0.py +2 -1
- imap_processing/codice/codice_l1a.py +167 -69
- imap_processing/codice/codice_l1b.py +42 -32
- imap_processing/codice/codice_l2.py +215 -9
- imap_processing/codice/constants.py +790 -603
- imap_processing/codice/data/lo_stepping_values.csv +1 -1
- imap_processing/decom.py +1 -4
- imap_processing/ena_maps/ena_maps.py +71 -43
- imap_processing/ena_maps/utils/corrections.py +291 -0
- imap_processing/ena_maps/utils/map_utils.py +20 -4
- 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 +123 -18
- imap_processing/glows/l1b/glows_l1b_data.py +358 -47
- 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_l1c.py +4 -109
- imap_processing/hi/hi_l2.py +104 -60
- imap_processing/hi/utils.py +262 -8
- imap_processing/hit/l0/constants.py +3 -0
- imap_processing/hit/l0/decom_hit.py +3 -6
- imap_processing/hit/l1a/hit_l1a.py +311 -21
- imap_processing/hit/l1b/hit_l1b.py +54 -126
- imap_processing/hit/l2/hit_l2.py +6 -6
- imap_processing/ialirt/calculate_ingest.py +219 -0
- imap_processing/ialirt/constants.py +12 -2
- imap_processing/ialirt/generate_coverage.py +15 -2
- imap_processing/ialirt/l0/ialirt_spice.py +6 -2
- imap_processing/ialirt/l0/parse_mag.py +293 -42
- imap_processing/ialirt/l0/process_hit.py +5 -3
- imap_processing/ialirt/l0/process_swapi.py +41 -25
- imap_processing/ialirt/process_ephemeris.py +70 -14
- imap_processing/ialirt/utils/create_xarray.py +1 -1
- 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 +158 -143
- imap_processing/idex/idex_utils.py +1 -3
- imap_processing/lo/ancillary_data/imap_lo_hydrogen-geometric-factor_v001.csv +75 -0
- imap_processing/lo/ancillary_data/imap_lo_oxygen-geometric-factor_v001.csv +75 -0
- imap_processing/lo/l0/lo_science.py +25 -24
- imap_processing/lo/l1b/lo_l1b.py +93 -19
- imap_processing/lo/l1c/lo_l1c.py +273 -93
- imap_processing/lo/l2/lo_l2.py +949 -135
- imap_processing/lo/lo_ancillary.py +55 -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/mag_l1d.py +57 -14
- imap_processing/mag/l1d/mag_l1d_data.py +202 -32
- imap_processing/mag/l2/mag_l2.py +2 -0
- imap_processing/mag/l2/mag_l2_data.py +14 -5
- imap_processing/quality_flags.py +23 -1
- imap_processing/spice/geometry.py +89 -39
- imap_processing/spice/pointing_frame.py +4 -8
- imap_processing/spice/repoint.py +78 -2
- imap_processing/spice/spin.py +28 -8
- imap_processing/spice/time.py +12 -22
- imap_processing/swapi/l1/swapi_l1.py +10 -4
- imap_processing/swapi/l2/swapi_l2.py +15 -17
- imap_processing/swe/l1b/swe_l1b.py +1 -2
- imap_processing/ultra/constants.py +30 -24
- imap_processing/ultra/l0/ultra_utils.py +9 -11
- imap_processing/ultra/l1a/ultra_l1a.py +1 -2
- imap_processing/ultra/l1b/badtimes.py +35 -11
- imap_processing/ultra/l1b/de.py +95 -31
- imap_processing/ultra/l1b/extendedspin.py +31 -16
- imap_processing/ultra/l1b/goodtimes.py +112 -0
- imap_processing/ultra/l1b/lookup_utils.py +281 -28
- imap_processing/ultra/l1b/quality_flag_filters.py +10 -1
- imap_processing/ultra/l1b/ultra_l1b.py +7 -7
- imap_processing/ultra/l1b/ultra_l1b_culling.py +169 -7
- imap_processing/ultra/l1b/ultra_l1b_extended.py +311 -69
- imap_processing/ultra/l1c/helio_pset.py +139 -37
- imap_processing/ultra/l1c/l1c_lookup_utils.py +289 -0
- imap_processing/ultra/l1c/spacecraft_pset.py +140 -29
- imap_processing/ultra/l1c/ultra_l1c.py +33 -24
- imap_processing/ultra/l1c/ultra_l1c_culling.py +92 -0
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +400 -292
- imap_processing/ultra/l2/ultra_l2.py +54 -11
- imap_processing/ultra/utils/ultra_l1_utils.py +37 -7
- imap_processing/utils.py +3 -4
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/METADATA +2 -2
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/RECORD +118 -109
- imap_processing/idex/idex_l2c.py +0 -84
- imap_processing/spice/kernels.py +0 -187
- imap_processing/ultra/l1b/cullingmask.py +0 -87
- imap_processing/ultra/l1c/histogram.py +0 -36
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/LICENSE +0 -0
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/WHEEL +0 -0
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/entry_points.txt +0 -0
imap_processing/hi/hi_l1b.py
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
from enum import IntEnum
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Union
|
|
7
6
|
|
|
8
7
|
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
9
|
import xarray as xr
|
|
10
10
|
|
|
11
11
|
from imap_processing import imap_module_directory
|
|
@@ -15,6 +15,7 @@ from imap_processing.hi.hi_l1a import HALF_CLOCK_TICK_S
|
|
|
15
15
|
from imap_processing.hi.utils import (
|
|
16
16
|
HIAPID,
|
|
17
17
|
CoincidenceBitmap,
|
|
18
|
+
EsaEnergyStepLookupTable,
|
|
18
19
|
HiConstants,
|
|
19
20
|
create_dataset_variables,
|
|
20
21
|
parse_sensor_number,
|
|
@@ -27,7 +28,7 @@ from imap_processing.spice.spin import (
|
|
|
27
28
|
get_instrument_spin_phase,
|
|
28
29
|
get_spacecraft_spin_phase,
|
|
29
30
|
)
|
|
30
|
-
from imap_processing.spice.time import met_to_sclkticks, sct_to_et
|
|
31
|
+
from imap_processing.spice.time import met_to_sclkticks, met_to_utc, sct_to_et
|
|
31
32
|
from imap_processing.utils import packet_file_to_datasets
|
|
32
33
|
|
|
33
34
|
|
|
@@ -45,52 +46,7 @@ ATTR_MGR.add_instrument_global_attrs("hi")
|
|
|
45
46
|
ATTR_MGR.add_instrument_variable_attrs(instrument="hi", level=None)
|
|
46
47
|
|
|
47
48
|
|
|
48
|
-
def
|
|
49
|
-
"""
|
|
50
|
-
High level IMAP-HI L1B processing function.
|
|
51
|
-
|
|
52
|
-
Parameters
|
|
53
|
-
----------
|
|
54
|
-
dependency : str or xarray.Dataset
|
|
55
|
-
Path to L0 file or L1A dataset to process.
|
|
56
|
-
|
|
57
|
-
Returns
|
|
58
|
-
-------
|
|
59
|
-
l1b_dataset : list[xarray.Dataset]
|
|
60
|
-
Processed xarray datasets.
|
|
61
|
-
"""
|
|
62
|
-
# Housekeeping processing
|
|
63
|
-
if isinstance(dependency, (Path, str)):
|
|
64
|
-
logger.info(f"Running Hi L1B processing on file: {dependency}")
|
|
65
|
-
l1b_datasets = housekeeping(dependency)
|
|
66
|
-
elif isinstance(dependency, xr.Dataset):
|
|
67
|
-
l1a_dataset = dependency
|
|
68
|
-
logger.info(
|
|
69
|
-
f"Running Hi L1B processing on dataset: "
|
|
70
|
-
f"{l1a_dataset.attrs['Logical_source']}"
|
|
71
|
-
)
|
|
72
|
-
logical_source_parts = parse_filename_like(l1a_dataset.attrs["Logical_source"])
|
|
73
|
-
# TODO: apid is not currently stored in all L1A data but should be.
|
|
74
|
-
# Use apid to determine what L1B processing function to call
|
|
75
|
-
|
|
76
|
-
# DE processing
|
|
77
|
-
if logical_source_parts["descriptor"].endswith("de"):
|
|
78
|
-
l1b_datasets = [annotate_direct_events(l1a_dataset)]
|
|
79
|
-
l1b_datasets[0].attrs["Logical_source"] = (
|
|
80
|
-
l1b_datasets[0]
|
|
81
|
-
.attrs["Logical_source"]
|
|
82
|
-
.format(sensor=logical_source_parts["sensor"])
|
|
83
|
-
)
|
|
84
|
-
else:
|
|
85
|
-
raise NotImplementedError(
|
|
86
|
-
f"No Hi L1B processing defined for file type: "
|
|
87
|
-
f"{l1a_dataset.attrs['Logical_source']}"
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
return l1b_datasets
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def housekeeping(packet_file_path: Union[str, Path]) -> list[xr.Dataset]:
|
|
49
|
+
def housekeeping(packet_file_path: str | Path) -> list[xr.Dataset]:
|
|
94
50
|
"""
|
|
95
51
|
Will process IMAP raw data to l1b housekeeping dataset.
|
|
96
52
|
|
|
@@ -108,6 +64,7 @@ def housekeeping(packet_file_path: Union[str, Path]) -> list[xr.Dataset]:
|
|
|
108
64
|
processed_data : list[xarray.Dataset]
|
|
109
65
|
Housekeeping datasets with engineering units.
|
|
110
66
|
"""
|
|
67
|
+
logger.info(f"Running Hi L1B processing on file: {packet_file_path}")
|
|
111
68
|
packet_def_file = (
|
|
112
69
|
imap_module_directory / "hi/packet_definitions/TLM_HI_COMBINED_SCI.xml"
|
|
113
70
|
)
|
|
@@ -137,33 +94,46 @@ def housekeeping(packet_file_path: Union[str, Path]) -> list[xr.Dataset]:
|
|
|
137
94
|
return datasets
|
|
138
95
|
|
|
139
96
|
|
|
140
|
-
def annotate_direct_events(
|
|
97
|
+
def annotate_direct_events(
|
|
98
|
+
l1a_de_dataset: xr.Dataset, l1b_hk_dataset: xr.Dataset, esa_energies_anc: Path
|
|
99
|
+
) -> list[xr.Dataset]:
|
|
141
100
|
"""
|
|
142
101
|
Perform Hi L1B processing on direct event data.
|
|
143
102
|
|
|
144
103
|
Parameters
|
|
145
104
|
----------
|
|
146
|
-
|
|
105
|
+
l1a_de_dataset : xarray.Dataset
|
|
147
106
|
L1A direct event data.
|
|
107
|
+
l1b_hk_dataset : xarray.Dataset
|
|
108
|
+
L1B housekeeping data coincident with the L1A DE data.
|
|
109
|
+
esa_energies_anc : pathlib.Path
|
|
110
|
+
Location of the esa-energies ancillary csv file.
|
|
148
111
|
|
|
149
112
|
Returns
|
|
150
113
|
-------
|
|
151
|
-
|
|
152
|
-
L1B direct event
|
|
114
|
+
l1b_datasets : list[xarray.Dataset]
|
|
115
|
+
List containing exactly one L1B direct event dataset.
|
|
153
116
|
"""
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
117
|
+
logger.info(
|
|
118
|
+
f"Running Hi L1B processing on dataset: "
|
|
119
|
+
f"{l1a_de_dataset.attrs['Logical_source']}"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
l1b_de_dataset = l1a_de_dataset.copy()
|
|
123
|
+
l1b_de_dataset.update(
|
|
124
|
+
de_esa_energy_step(l1b_de_dataset, l1b_hk_dataset, esa_energies_anc)
|
|
125
|
+
)
|
|
126
|
+
l1b_de_dataset.update(compute_coincidence_type_and_tofs(l1b_de_dataset))
|
|
127
|
+
l1b_de_dataset.update(de_nominal_bin_and_spin_phase(l1b_de_dataset))
|
|
128
|
+
l1b_de_dataset.update(compute_hae_coordinates(l1b_de_dataset))
|
|
129
|
+
l1b_de_dataset.update(
|
|
160
130
|
create_dataset_variables(
|
|
161
131
|
["quality_flag"],
|
|
162
|
-
|
|
132
|
+
l1b_de_dataset["event_met"].size,
|
|
163
133
|
att_manager_lookup_str="hi_de_{0}",
|
|
164
134
|
)
|
|
165
135
|
)
|
|
166
|
-
|
|
136
|
+
l1b_de_dataset = l1b_de_dataset.drop_vars(
|
|
167
137
|
[
|
|
168
138
|
"src_seq_ctr",
|
|
169
139
|
"pkt_len",
|
|
@@ -179,8 +149,13 @@ def annotate_direct_events(l1a_dataset: xr.Dataset) -> xr.Dataset:
|
|
|
179
149
|
)
|
|
180
150
|
|
|
181
151
|
de_global_attrs = ATTR_MGR.get_global_attributes("imap_hi_l1b_de_attrs")
|
|
182
|
-
|
|
183
|
-
|
|
152
|
+
l1b_de_dataset.attrs.update(**de_global_attrs)
|
|
153
|
+
|
|
154
|
+
logical_source_parts = parse_filename_like(l1a_de_dataset.attrs["Logical_source"])
|
|
155
|
+
l1b_de_dataset.attrs["Logical_source"] = l1b_de_dataset.attrs[
|
|
156
|
+
"Logical_source"
|
|
157
|
+
].format(sensor=logical_source_parts["sensor"])
|
|
158
|
+
return [l1b_de_dataset]
|
|
184
159
|
|
|
185
160
|
|
|
186
161
|
def compute_coincidence_type_and_tofs(
|
|
@@ -387,18 +362,20 @@ def compute_hae_coordinates(dataset: xr.Dataset) -> dict[str, xr.DataArray]:
|
|
|
387
362
|
return new_vars
|
|
388
363
|
|
|
389
364
|
|
|
390
|
-
def de_esa_energy_step(
|
|
365
|
+
def de_esa_energy_step(
|
|
366
|
+
l1b_de_ds: xr.Dataset, l1b_hk_ds: xr.Dataset, esa_energies_anc: Path
|
|
367
|
+
) -> dict[str, xr.DataArray]:
|
|
391
368
|
"""
|
|
392
369
|
Compute esa_energy_step for each direct event.
|
|
393
370
|
|
|
394
|
-
TODO: For now this function just returns the esa_step from the input dataset.
|
|
395
|
-
Eventually, it will take L1B housekeeping data and determine the esa
|
|
396
|
-
energy steps from that data.
|
|
397
|
-
|
|
398
371
|
Parameters
|
|
399
372
|
----------
|
|
400
|
-
|
|
373
|
+
l1b_de_ds : xarray.Dataset
|
|
401
374
|
The partial L1B dataset.
|
|
375
|
+
l1b_hk_ds : xarray.Dataset
|
|
376
|
+
L1B housekeeping data coincident with the L1A DE data.
|
|
377
|
+
esa_energies_anc : pathlib.Path
|
|
378
|
+
Location of the esa-energies ancillary csv file.
|
|
402
379
|
|
|
403
380
|
Returns
|
|
404
381
|
-------
|
|
@@ -407,10 +384,116 @@ def de_esa_energy_step(dataset: xr.Dataset) -> dict[str, xr.DataArray]:
|
|
|
407
384
|
"""
|
|
408
385
|
new_vars = create_dataset_variables(
|
|
409
386
|
["esa_energy_step"],
|
|
410
|
-
len(
|
|
387
|
+
len(l1b_de_ds.epoch),
|
|
411
388
|
att_manager_lookup_str="hi_de_{0}",
|
|
412
389
|
)
|
|
413
|
-
|
|
414
|
-
|
|
390
|
+
|
|
391
|
+
# Get the LUT object using the HK data and esa-energies ancillary csv
|
|
392
|
+
esa_energies_lut = pd.read_csv(esa_energies_anc, comment="#")
|
|
393
|
+
esa_to_esa_energy_step_lut = get_esa_to_esa_energy_step_lut(
|
|
394
|
+
l1b_hk_ds, esa_energies_lut
|
|
395
|
+
)
|
|
396
|
+
new_vars["esa_energy_step"].values = esa_to_esa_energy_step_lut.query(
|
|
397
|
+
l1b_de_ds["ccsds_met"].data, l1b_de_ds["esa_step"].data
|
|
398
|
+
)
|
|
415
399
|
|
|
416
400
|
return new_vars
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def get_esa_to_esa_energy_step_lut(
|
|
404
|
+
l1b_hk_ds: xr.Dataset, esa_energies_lut: pd.DataFrame
|
|
405
|
+
) -> EsaEnergyStepLookupTable:
|
|
406
|
+
"""
|
|
407
|
+
Generate a lookup table that associates an esa_step to an esa_energy_step.
|
|
408
|
+
|
|
409
|
+
Parameters
|
|
410
|
+
----------
|
|
411
|
+
l1b_hk_ds : xarray.Dataset
|
|
412
|
+
L1B housekeeping dataset.
|
|
413
|
+
esa_energies_lut : pandas.DataFrame
|
|
414
|
+
Esa energies lookup table derived from ancillary file.
|
|
415
|
+
|
|
416
|
+
Returns
|
|
417
|
+
-------
|
|
418
|
+
esa_energy_step_lut : EsaEnergyStepLookupTable
|
|
419
|
+
A lookup table object that can be used to query by MET time and esa_step
|
|
420
|
+
for the associated esa_energy_step values.
|
|
421
|
+
|
|
422
|
+
Notes
|
|
423
|
+
-----
|
|
424
|
+
Algorithm definition in section 2.1.2 of IMAP Hi Algorithm Document.
|
|
425
|
+
"""
|
|
426
|
+
# Instantiate a lookup table object
|
|
427
|
+
esa_energy_step_lut = EsaEnergyStepLookupTable()
|
|
428
|
+
# Get the set of esa_steps visited
|
|
429
|
+
esa_steps = list(sorted(set(l1b_hk_ds["sci_esa_step"].data)))
|
|
430
|
+
# Break into contiguous segments where op_mode == "HVSCI"
|
|
431
|
+
# Pad the boolean array `op_mode == HVSCI` with False values on each end.
|
|
432
|
+
# This treats starting or ending in HVSCI mode as a transition in the next
|
|
433
|
+
# step where np.diff is used to find op_mode transitions into and out of
|
|
434
|
+
# HVSCI
|
|
435
|
+
padded_mask = np.pad(
|
|
436
|
+
l1b_hk_ds["op_mode"].data == "HVSCI", (1, 1), constant_values=False
|
|
437
|
+
)
|
|
438
|
+
mode_changes = np.diff(padded_mask.astype(int))
|
|
439
|
+
hsvsci_starts = np.nonzero(mode_changes == 1)[0]
|
|
440
|
+
hsvsci_ends = np.nonzero(mode_changes == -1)[0]
|
|
441
|
+
for i_start, i_end in zip(hsvsci_starts, hsvsci_ends, strict=False):
|
|
442
|
+
contiguous_hvsci_ds = l1b_hk_ds.isel(dict(epoch=slice(i_start, i_end)))
|
|
443
|
+
# Find median inner and outer ESA voltages for each ESA step
|
|
444
|
+
for esa_step in esa_steps:
|
|
445
|
+
single_esa_ds = contiguous_hvsci_ds.where(
|
|
446
|
+
contiguous_hvsci_ds["sci_esa_step"] == esa_step, drop=True
|
|
447
|
+
)
|
|
448
|
+
if len(single_esa_ds["epoch"].data) == 0:
|
|
449
|
+
logger.debug(
|
|
450
|
+
f"No instances of sci_esa_step == {esa_step} "
|
|
451
|
+
f"present in contiguous HVSCI block with interval: "
|
|
452
|
+
f"({met_to_utc(contiguous_hvsci_ds['shcoarse'].data[[0, -1]])})"
|
|
453
|
+
)
|
|
454
|
+
continue
|
|
455
|
+
inner_esa_voltage = np.where(
|
|
456
|
+
single_esa_ds["inner_esa_state"].data == "LO",
|
|
457
|
+
single_esa_ds["inner_esa_lo"].data,
|
|
458
|
+
single_esa_ds["inner_esa_hi"].data,
|
|
459
|
+
)
|
|
460
|
+
median_inner_esa = np.median(inner_esa_voltage)
|
|
461
|
+
median_outer_esa = np.median(single_esa_ds["outer_esa"].data)
|
|
462
|
+
# Match median voltages to ESA Energies LUT
|
|
463
|
+
inner_voltage_match = (
|
|
464
|
+
np.abs(median_inner_esa - esa_energies_lut["inner_esa_voltage"])
|
|
465
|
+
<= esa_energies_lut["inner_esa_delta_v"]
|
|
466
|
+
)
|
|
467
|
+
outer_voltage_match = (
|
|
468
|
+
np.abs(median_outer_esa - esa_energies_lut["outer_esa_voltage"])
|
|
469
|
+
<= esa_energies_lut["outer_esa_delta_v"]
|
|
470
|
+
)
|
|
471
|
+
matching_esa_energy = esa_energies_lut[
|
|
472
|
+
np.logical_and(inner_voltage_match, outer_voltage_match)
|
|
473
|
+
]
|
|
474
|
+
if len(matching_esa_energy) != 1:
|
|
475
|
+
if len(matching_esa_energy) == 0:
|
|
476
|
+
logger.critical(
|
|
477
|
+
f"No esa_energy_step matches found for esa_step "
|
|
478
|
+
f"{esa_step} during interval: "
|
|
479
|
+
f"({met_to_utc(single_esa_ds['shcoarse'].data[[0, -1]])}) "
|
|
480
|
+
f"with median esa voltages: "
|
|
481
|
+
f"{median_inner_esa}, {median_outer_esa}."
|
|
482
|
+
)
|
|
483
|
+
if len(matching_esa_energy) > 1:
|
|
484
|
+
logger.critical(
|
|
485
|
+
f"Multiple esa_energy_step matches found for esa_step "
|
|
486
|
+
f"{esa_step} during interval: "
|
|
487
|
+
f"({met_to_utc(single_esa_ds['shcoarse'].data[[0, -1]])}) "
|
|
488
|
+
f"with median esa voltages: "
|
|
489
|
+
f"{median_inner_esa}, {median_outer_esa}."
|
|
490
|
+
)
|
|
491
|
+
continue
|
|
492
|
+
# Set LUT to matching esa_energy_step for time range
|
|
493
|
+
esa_energy_step_lut.add_entry(
|
|
494
|
+
contiguous_hvsci_ds["shcoarse"].data[0],
|
|
495
|
+
contiguous_hvsci_ds["shcoarse"].data[-1],
|
|
496
|
+
esa_step,
|
|
497
|
+
matching_esa_energy["esa_energy_step"].values[0],
|
|
498
|
+
)
|
|
499
|
+
return esa_energy_step_lut
|
imap_processing/hi/hi_l1c.py
CHANGED
|
@@ -19,7 +19,7 @@ from imap_processing.hi.hi_l1a import (
|
|
|
19
19
|
HALF_CLOCK_TICK_S,
|
|
20
20
|
)
|
|
21
21
|
from imap_processing.hi.utils import (
|
|
22
|
-
|
|
22
|
+
CalibrationProductConfig,
|
|
23
23
|
create_dataset_variables,
|
|
24
24
|
full_dataarray,
|
|
25
25
|
parse_sensor_number,
|
|
@@ -378,8 +378,9 @@ def pset_counts(
|
|
|
378
378
|
filtered_de_df["spin_phase"].to_numpy() * N_SPIN_BINS
|
|
379
379
|
).astype(int)
|
|
380
380
|
# When iterating over rows of a dataframe, the names of the multi-index
|
|
381
|
-
# are not preserved. Below, `config_row.Index[0]` gets the
|
|
382
|
-
# value from the namedtuple representing the
|
|
381
|
+
# are not preserved. Below, `config_row.Index[0]` gets the
|
|
382
|
+
# calibration_prod value from the namedtuple representing the
|
|
383
|
+
# dataframe row.
|
|
383
384
|
np.add.at(
|
|
384
385
|
counts_var["counts"].data[0, i_esa, config_row.Index[0]],
|
|
385
386
|
spin_bin_indices,
|
|
@@ -684,109 +685,3 @@ def good_time_and_phase_mask(
|
|
|
684
685
|
"""
|
|
685
686
|
# TODO: Implement this once we have Goodtimes data product defined.
|
|
686
687
|
return np.full_like(tick_mets, True, dtype=bool)
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
@pd.api.extensions.register_dataframe_accessor("cal_prod_config")
|
|
690
|
-
class CalibrationProductConfig:
|
|
691
|
-
"""
|
|
692
|
-
Register custom accessor for calibration product configuration DataFrames.
|
|
693
|
-
|
|
694
|
-
Parameters
|
|
695
|
-
----------
|
|
696
|
-
pandas_obj : pandas.DataFrame
|
|
697
|
-
Object to run validation and use accessor functions on.
|
|
698
|
-
"""
|
|
699
|
-
|
|
700
|
-
index_columns = (
|
|
701
|
-
"cal_prod_num",
|
|
702
|
-
"esa_energy_step",
|
|
703
|
-
)
|
|
704
|
-
tof_detector_pairs = ("ab", "ac1", "bc1", "c1c2")
|
|
705
|
-
required_columns = (
|
|
706
|
-
"coincidence_type_list",
|
|
707
|
-
*[
|
|
708
|
-
f"tof_{det_pair}_{limit}"
|
|
709
|
-
for det_pair in tof_detector_pairs
|
|
710
|
-
for limit in ["low", "high"]
|
|
711
|
-
],
|
|
712
|
-
)
|
|
713
|
-
|
|
714
|
-
def __init__(self, pandas_obj: pd.DataFrame) -> None:
|
|
715
|
-
self._validate(pandas_obj)
|
|
716
|
-
self._obj = pandas_obj
|
|
717
|
-
self._add_coincidence_values_column()
|
|
718
|
-
|
|
719
|
-
def _validate(self, df: pd.DataFrame) -> None:
|
|
720
|
-
"""
|
|
721
|
-
Validate the current configuration.
|
|
722
|
-
|
|
723
|
-
Parameters
|
|
724
|
-
----------
|
|
725
|
-
df : pandas.DataFrame
|
|
726
|
-
Object to validate.
|
|
727
|
-
|
|
728
|
-
Raises
|
|
729
|
-
------
|
|
730
|
-
AttributeError : If the dataframe does not pass validation.
|
|
731
|
-
"""
|
|
732
|
-
for index_name in self.index_columns:
|
|
733
|
-
if index_name in df.index:
|
|
734
|
-
raise AttributeError(
|
|
735
|
-
f"Required index {index_name} not present in dataframe."
|
|
736
|
-
)
|
|
737
|
-
# Verify that the Dataframe has all the required columns
|
|
738
|
-
for col in self.required_columns:
|
|
739
|
-
if col not in df.columns:
|
|
740
|
-
raise AttributeError(f"Required column {col} not present in dataframe.")
|
|
741
|
-
# TODO: Verify that the same ESA energy steps exist in all unique calibration
|
|
742
|
-
# product numbers
|
|
743
|
-
|
|
744
|
-
def _add_coincidence_values_column(self) -> None:
|
|
745
|
-
"""Generate and add the coincidence_type_values column to the dataframe."""
|
|
746
|
-
# Add a column that consists of the coincidence type strings converted
|
|
747
|
-
# to integer values
|
|
748
|
-
self._obj["coincidence_type_values"] = self._obj.apply(
|
|
749
|
-
lambda row: tuple(
|
|
750
|
-
CoincidenceBitmap.detector_hit_str_to_int(entry)
|
|
751
|
-
for entry in row["coincidence_type_list"]
|
|
752
|
-
),
|
|
753
|
-
axis=1,
|
|
754
|
-
)
|
|
755
|
-
|
|
756
|
-
@classmethod
|
|
757
|
-
def from_csv(cls, path: Path) -> pd.DataFrame:
|
|
758
|
-
"""
|
|
759
|
-
Read configuration CSV file into a pandas.DataFrame.
|
|
760
|
-
|
|
761
|
-
Parameters
|
|
762
|
-
----------
|
|
763
|
-
path : pathlib.Path
|
|
764
|
-
Location of the Calibration Product configuration CSV file.
|
|
765
|
-
|
|
766
|
-
Returns
|
|
767
|
-
-------
|
|
768
|
-
dataframe : pandas.DataFrame
|
|
769
|
-
Validated calibration product configuration data frame.
|
|
770
|
-
"""
|
|
771
|
-
df = pd.read_csv(
|
|
772
|
-
path,
|
|
773
|
-
index_col=cls.index_columns,
|
|
774
|
-
converters={"coincidence_type_list": lambda s: tuple(s.split("|"))},
|
|
775
|
-
comment="#",
|
|
776
|
-
)
|
|
777
|
-
# Force the _init_ method to run by using the namespace
|
|
778
|
-
_ = df.cal_prod_config.number_of_products
|
|
779
|
-
return df
|
|
780
|
-
|
|
781
|
-
@property
|
|
782
|
-
def number_of_products(self) -> int:
|
|
783
|
-
"""
|
|
784
|
-
Get the number of calibration products in the current configuration.
|
|
785
|
-
|
|
786
|
-
Returns
|
|
787
|
-
-------
|
|
788
|
-
number_of_products : int
|
|
789
|
-
The maximum number of calibration products defined in the list of
|
|
790
|
-
calibration product definitions.
|
|
791
|
-
"""
|
|
792
|
-
return len(self._obj.index.unique(level="cal_prod_num"))
|