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
|
@@ -4,43 +4,12 @@ import numpy as np
|
|
|
4
4
|
import numpy.typing as npt
|
|
5
5
|
import pandas as pd
|
|
6
6
|
import xarray as xr
|
|
7
|
+
from numpy.typing import NDArray
|
|
7
8
|
|
|
8
|
-
from imap_processing import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
_YADJUST_DF = pd.read_csv(BASE_PATH / "yadjust.csv").set_index("dYLUT")
|
|
13
|
-
_TDC_NORM_DF_ULTRA45 = pd.read_csv(
|
|
14
|
-
BASE_PATH / "ultra45_tdc_norm.csv", header=1, index_col="Index"
|
|
15
|
-
)
|
|
16
|
-
_TDC_NORM_DF_ULTRA90 = pd.read_csv(
|
|
17
|
-
BASE_PATH / "ultra90_tdc_norm.csv", header=1, index_col="Index"
|
|
18
|
-
)
|
|
19
|
-
_BACK_POS_DF_ULTRA45 = pd.read_csv(
|
|
20
|
-
BASE_PATH / "ultra45_back-pos-luts.csv", index_col="Index_offset"
|
|
21
|
-
)
|
|
22
|
-
_BACK_POS_DF_ULTRA90 = pd.read_csv(
|
|
23
|
-
BASE_PATH / "ultra90_back-pos-luts.csv", index_col="Index_offset"
|
|
24
|
-
)
|
|
25
|
-
_ENERGY_NORM_DF = pd.read_csv(BASE_PATH / "EgyNorm.mem.csv")
|
|
26
|
-
_IMAGE_PARAMS_DF = {
|
|
27
|
-
"ultra45": pd.read_csv(BASE_PATH / "FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv"),
|
|
28
|
-
"ultra90": pd.read_csv(BASE_PATH / "FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv"),
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
_FWHM_TABLES = {
|
|
32
|
-
("left", "ultra45"): pd.read_csv(BASE_PATH / "Angular_Profiles_FM45_LeftSlit.csv"),
|
|
33
|
-
("right", "ultra45"): pd.read_csv(
|
|
34
|
-
BASE_PATH / "Angular_Profiles_FM45_RightSlit.csv"
|
|
35
|
-
),
|
|
36
|
-
("left", "ultra90"): pd.read_csv(BASE_PATH / "Angular_Profiles_FM90_LeftSlit.csv"),
|
|
37
|
-
("right", "ultra90"): pd.read_csv(
|
|
38
|
-
BASE_PATH / "Angular_Profiles_FM90_RightSlit.csv"
|
|
39
|
-
),
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def get_y_adjust(dy_lut: np.ndarray) -> npt.NDArray:
|
|
9
|
+
from imap_processing.quality_flags import ImapDEOutliersUltraFlags
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_y_adjust(dy_lut: np.ndarray, ancillary_files: dict) -> npt.NDArray:
|
|
44
13
|
"""
|
|
45
14
|
Adjust the front yf position based on the particle's trajectory.
|
|
46
15
|
|
|
@@ -52,16 +21,21 @@ def get_y_adjust(dy_lut: np.ndarray) -> npt.NDArray:
|
|
|
52
21
|
----------
|
|
53
22
|
dy_lut : np.ndarray
|
|
54
23
|
Change in y direction used for the lookup table (mm).
|
|
24
|
+
ancillary_files : dict[Path]
|
|
25
|
+
Ancillary files containing the lookup tables.
|
|
55
26
|
|
|
56
27
|
Returns
|
|
57
28
|
-------
|
|
58
29
|
yadj : np.ndarray
|
|
59
30
|
Y adjustment (mm).
|
|
60
31
|
"""
|
|
61
|
-
|
|
32
|
+
yadjust_df = pd.read_csv(ancillary_files["l1b-yadjust-lookup"]).set_index("dYLUT")
|
|
33
|
+
return yadjust_df["dYAdj"].iloc[dy_lut].values
|
|
62
34
|
|
|
63
35
|
|
|
64
|
-
def get_norm(
|
|
36
|
+
def get_norm(
|
|
37
|
+
dn: xr.DataArray, key: str, file_label: str, ancillary_files: dict
|
|
38
|
+
) -> npt.NDArray:
|
|
65
39
|
"""
|
|
66
40
|
Correct mismatches between the stop Time to Digital Converters (TDCs).
|
|
67
41
|
|
|
@@ -82,6 +56,8 @@ def get_norm(dn: xr.DataArray, key: str, file_label: str) -> npt.NDArray:
|
|
|
82
56
|
BtSpNNorm, BtSpSNorm, BtSpENorm, or BtSpWNorm.
|
|
83
57
|
file_label : str
|
|
84
58
|
Instrument (ultra45 or ultra90).
|
|
59
|
+
ancillary_files : dict[Path]
|
|
60
|
+
Ancillary files containing the lookup tables.
|
|
85
61
|
|
|
86
62
|
Returns
|
|
87
63
|
-------
|
|
@@ -89,16 +65,22 @@ def get_norm(dn: xr.DataArray, key: str, file_label: str) -> npt.NDArray:
|
|
|
89
65
|
Normalized DNs.
|
|
90
66
|
"""
|
|
91
67
|
if file_label == "ultra45":
|
|
92
|
-
tdc_norm_df =
|
|
68
|
+
tdc_norm_df = pd.read_csv(
|
|
69
|
+
ancillary_files["l1b-45sensor-tdc-norm-lookup"], header=1, index_col="Index"
|
|
70
|
+
)
|
|
93
71
|
else:
|
|
94
|
-
tdc_norm_df =
|
|
72
|
+
tdc_norm_df = pd.read_csv(
|
|
73
|
+
ancillary_files["l1b-90sensor-tdc-norm-lookup"], header=1, index_col="Index"
|
|
74
|
+
)
|
|
95
75
|
|
|
96
76
|
dn_norm = tdc_norm_df[key].iloc[dn].values
|
|
97
77
|
|
|
98
78
|
return dn_norm
|
|
99
79
|
|
|
100
80
|
|
|
101
|
-
def get_back_position(
|
|
81
|
+
def get_back_position(
|
|
82
|
+
back_index: np.ndarray, key: str, file_label: str, ancillary_files: dict
|
|
83
|
+
) -> npt.NDArray:
|
|
102
84
|
"""
|
|
103
85
|
Convert normalized TDC values using lookup tables.
|
|
104
86
|
|
|
@@ -117,6 +99,8 @@ def get_back_position(back_index: np.ndarray, key: str, file_label: str) -> npt.
|
|
|
117
99
|
XBkTp, YBkTp, XBkBt, or YBkBt.
|
|
118
100
|
file_label : str
|
|
119
101
|
Instrument (ultra45 or ultra90).
|
|
102
|
+
ancillary_files : dict[Path]
|
|
103
|
+
Ancillary files containing the lookup tables.
|
|
120
104
|
|
|
121
105
|
Returns
|
|
122
106
|
-------
|
|
@@ -124,14 +108,20 @@ def get_back_position(back_index: np.ndarray, key: str, file_label: str) -> npt.
|
|
|
124
108
|
Converted DNs to Units of hundredths of a millimeter.
|
|
125
109
|
"""
|
|
126
110
|
if file_label == "ultra45":
|
|
127
|
-
back_pos_df =
|
|
111
|
+
back_pos_df = pd.read_csv(
|
|
112
|
+
ancillary_files["l1b-45sensor-back-pos-lookup"], index_col="Index_offset"
|
|
113
|
+
)
|
|
128
114
|
else:
|
|
129
|
-
back_pos_df =
|
|
115
|
+
back_pos_df = pd.read_csv(
|
|
116
|
+
ancillary_files["l1b-90sensor-back-pos-lookup"], index_col="Index_offset"
|
|
117
|
+
)
|
|
130
118
|
|
|
131
119
|
return back_pos_df[key].values[back_index]
|
|
132
120
|
|
|
133
121
|
|
|
134
|
-
def get_energy_norm(
|
|
122
|
+
def get_energy_norm(
|
|
123
|
+
ssd: np.ndarray, composite_energy: np.ndarray, ancillary_files: dict
|
|
124
|
+
) -> npt.NDArray:
|
|
135
125
|
"""
|
|
136
126
|
Normalize composite energy per SSD using a lookup table.
|
|
137
127
|
|
|
@@ -146,6 +136,8 @@ def get_energy_norm(ssd: np.ndarray, composite_energy: np.ndarray) -> npt.NDArra
|
|
|
146
136
|
Acts as index 1.
|
|
147
137
|
composite_energy : np.ndarray
|
|
148
138
|
Acts as index 2.
|
|
139
|
+
ancillary_files : dict[Path]
|
|
140
|
+
Ancillary files containing the lookup tables.
|
|
149
141
|
|
|
150
142
|
Returns
|
|
151
143
|
-------
|
|
@@ -153,11 +145,11 @@ def get_energy_norm(ssd: np.ndarray, composite_energy: np.ndarray) -> npt.NDArra
|
|
|
153
145
|
Normalized composite energy.
|
|
154
146
|
"""
|
|
155
147
|
row_number = ssd * 4096 + composite_energy
|
|
156
|
-
|
|
157
|
-
return
|
|
148
|
+
norm_lookup = pd.read_csv(ancillary_files["l1b-egynorm-lookup"])
|
|
149
|
+
return norm_lookup["NormEnergy"].iloc[row_number]
|
|
158
150
|
|
|
159
151
|
|
|
160
|
-
def get_image_params(image: str, sensor: str) -> np.float64:
|
|
152
|
+
def get_image_params(image: str, sensor: str, ancillary_files: dict) -> np.float64:
|
|
161
153
|
"""
|
|
162
154
|
Lookup table for image parameters.
|
|
163
155
|
|
|
@@ -171,18 +163,26 @@ def get_image_params(image: str, sensor: str) -> np.float64:
|
|
|
171
163
|
The column name to lookup in the CSV file, e.g., 'XFTLTOFF' or 'XFTRTOFF'.
|
|
172
164
|
sensor : str
|
|
173
165
|
Sensor name: "ultra45" or "ultra90".
|
|
166
|
+
ancillary_files : dict[Path]
|
|
167
|
+
Ancillary files containing the lookup tables.
|
|
174
168
|
|
|
175
169
|
Returns
|
|
176
170
|
-------
|
|
177
171
|
value : np.float64
|
|
178
172
|
Image parameter value from the CSV file.
|
|
179
173
|
"""
|
|
180
|
-
|
|
174
|
+
if sensor == "ultra45":
|
|
175
|
+
lookup_table = pd.read_csv(ancillary_files["l1b-45sensor-imgparams-lookup"])
|
|
176
|
+
else:
|
|
177
|
+
lookup_table = pd.read_csv(ancillary_files["l1b-90sensor-imgparams-lookup"])
|
|
178
|
+
|
|
181
179
|
value: np.float64 = lookup_table[image].values[0]
|
|
182
180
|
return value
|
|
183
181
|
|
|
184
182
|
|
|
185
|
-
def get_angular_profiles(
|
|
183
|
+
def get_angular_profiles(
|
|
184
|
+
start_type: str, sensor: str, ancillary_files: dict
|
|
185
|
+
) -> pd.DataFrame:
|
|
186
186
|
"""
|
|
187
187
|
Lookup table for FWHM for theta and phi.
|
|
188
188
|
|
|
@@ -195,13 +195,16 @@ def get_angular_profiles(start_type: str, sensor: str) -> pd.DataFrame:
|
|
|
195
195
|
Start Type: Left, Right.
|
|
196
196
|
sensor : str
|
|
197
197
|
Sensor name: "ultra45" or "ultra90".
|
|
198
|
+
ancillary_files : dict[Path]
|
|
199
|
+
Ancillary files.
|
|
198
200
|
|
|
199
201
|
Returns
|
|
200
202
|
-------
|
|
201
203
|
lookup_table : DataFrame
|
|
202
204
|
Angular profile lookup table for a given start_type and sensor.
|
|
203
205
|
"""
|
|
204
|
-
|
|
206
|
+
lut_descriptor = f"l1b-{sensor[-2:]}sensor-{start_type.lower()}slit-lookup"
|
|
207
|
+
lookup_table = pd.read_csv(ancillary_files[lut_descriptor])
|
|
205
208
|
|
|
206
209
|
return lookup_table
|
|
207
210
|
|
|
@@ -227,3 +230,374 @@ def get_energy_efficiencies(ancillary_files: dict) -> pd.DataFrame:
|
|
|
227
230
|
lookup_table = pd.read_csv(ancillary_files["l1b-45sensor-logistic-interpolation"])
|
|
228
231
|
|
|
229
232
|
return lookup_table
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def load_geometric_factor_tables(
|
|
236
|
+
ancillary_files: dict,
|
|
237
|
+
filename: str,
|
|
238
|
+
) -> dict:
|
|
239
|
+
"""
|
|
240
|
+
Lookup tables for geometric factor.
|
|
241
|
+
|
|
242
|
+
Parameters
|
|
243
|
+
----------
|
|
244
|
+
ancillary_files : dict[Path]
|
|
245
|
+
Ancillary files.
|
|
246
|
+
filename : str
|
|
247
|
+
Name of the file in ancillary_files to use.
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
geometric_factor_tables : dict
|
|
252
|
+
Geometric factor lookup tables.
|
|
253
|
+
"""
|
|
254
|
+
gf_table = pd.read_csv(
|
|
255
|
+
ancillary_files[filename], header=None, skiprows=6, nrows=301
|
|
256
|
+
).to_numpy(dtype=float)
|
|
257
|
+
theta_table = pd.read_csv(
|
|
258
|
+
ancillary_files[filename], header=None, skiprows=308, nrows=301
|
|
259
|
+
).to_numpy(dtype=float)
|
|
260
|
+
phi_table = pd.read_csv(
|
|
261
|
+
ancillary_files[filename], header=None, skiprows=610, nrows=301
|
|
262
|
+
).to_numpy(dtype=float)
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
"gf_table": gf_table,
|
|
266
|
+
"theta_table": theta_table,
|
|
267
|
+
"phi_table": phi_table,
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def get_geometric_factor(
|
|
272
|
+
phi: NDArray,
|
|
273
|
+
theta: NDArray,
|
|
274
|
+
quality_flag: NDArray,
|
|
275
|
+
ancillary_files: dict | None = None,
|
|
276
|
+
filename: str | None = None,
|
|
277
|
+
geometric_factor_tables: dict | None = None,
|
|
278
|
+
) -> tuple[NDArray, NDArray]:
|
|
279
|
+
"""
|
|
280
|
+
Lookup table for geometric factor using nearest neighbor.
|
|
281
|
+
|
|
282
|
+
Parameters
|
|
283
|
+
----------
|
|
284
|
+
phi : NDArray
|
|
285
|
+
Azimuth angles in degrees.
|
|
286
|
+
theta : NDArray
|
|
287
|
+
Elevation angles in degrees.
|
|
288
|
+
quality_flag : NDArray
|
|
289
|
+
Quality flag to set when geometric factor is zero.
|
|
290
|
+
ancillary_files : dict[Path], optional
|
|
291
|
+
Ancillary files.
|
|
292
|
+
filename : str, optional
|
|
293
|
+
Name of the file in ancillary_files to use.
|
|
294
|
+
geometric_factor_tables : dict, optional
|
|
295
|
+
Preloaded geometric factor lookup tables. If not provided, will load.
|
|
296
|
+
|
|
297
|
+
Returns
|
|
298
|
+
-------
|
|
299
|
+
geometric_factor : NDArray
|
|
300
|
+
Geometric factor.
|
|
301
|
+
"""
|
|
302
|
+
if geometric_factor_tables is None:
|
|
303
|
+
if ancillary_files is None or filename is None:
|
|
304
|
+
raise ValueError(
|
|
305
|
+
"ancillary_files and filename must be provided if "
|
|
306
|
+
"geometric_factor_tables is not supplied."
|
|
307
|
+
)
|
|
308
|
+
geometric_factor_tables = load_geometric_factor_tables(
|
|
309
|
+
ancillary_files, filename
|
|
310
|
+
)
|
|
311
|
+
# Assume uniform grids: extract 1D arrays from first row/col
|
|
312
|
+
theta_vals = geometric_factor_tables["theta_table"][0, :] # columns represent theta
|
|
313
|
+
phi_vals = geometric_factor_tables["phi_table"][:, 0] # rows represent phi
|
|
314
|
+
|
|
315
|
+
# Find nearest index in table for each input value
|
|
316
|
+
phi_idx = np.abs(phi_vals[:, None] - phi).argmin(axis=0)
|
|
317
|
+
theta_idx = np.abs(theta_vals[:, None] - theta).argmin(axis=0)
|
|
318
|
+
|
|
319
|
+
# Fetch geometric factor values at nearest (phi, theta) pairs
|
|
320
|
+
geometric_factor = geometric_factor_tables["gf_table"][phi_idx, theta_idx]
|
|
321
|
+
|
|
322
|
+
outside_fov = ~is_inside_fov(np.deg2rad(phi), np.deg2rad(theta))
|
|
323
|
+
quality_flag[outside_fov] |= ImapDEOutliersUltraFlags.FOV.value
|
|
324
|
+
|
|
325
|
+
return geometric_factor
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def load_scattering_lookup_tables(ancillary_files: dict, instrument_id: int) -> dict:
|
|
329
|
+
"""
|
|
330
|
+
Load scattering coefficient lookup tables for the specified instrument.
|
|
331
|
+
|
|
332
|
+
Parameters
|
|
333
|
+
----------
|
|
334
|
+
ancillary_files : dict
|
|
335
|
+
Ancillary files.
|
|
336
|
+
instrument_id : int
|
|
337
|
+
Instrument ID, either 45 or 90.
|
|
338
|
+
|
|
339
|
+
Returns
|
|
340
|
+
-------
|
|
341
|
+
dict
|
|
342
|
+
Dictionary containing arrays for theta_grid, phi_grid, a_theta, g_theta,
|
|
343
|
+
a_phi, g_phi.
|
|
344
|
+
"""
|
|
345
|
+
# TODO remove the line below when the 45 sensor scattering coefficients are
|
|
346
|
+
# delivered.
|
|
347
|
+
instrument_id = 90
|
|
348
|
+
descriptor = f"l1b-{instrument_id}sensor-scattering-calibration"
|
|
349
|
+
theta_grid = pd.read_csv(
|
|
350
|
+
ancillary_files[descriptor], header=None, skiprows=7, nrows=241
|
|
351
|
+
).to_numpy(dtype=float)
|
|
352
|
+
phi_grid = pd.read_csv(
|
|
353
|
+
ancillary_files[descriptor], header=None, skiprows=249, nrows=241
|
|
354
|
+
).to_numpy(dtype=float)
|
|
355
|
+
a_theta = pd.read_csv(
|
|
356
|
+
ancillary_files[descriptor], header=None, skiprows=491, nrows=241
|
|
357
|
+
).to_numpy(dtype=float)
|
|
358
|
+
g_theta = pd.read_csv(
|
|
359
|
+
ancillary_files[descriptor], header=None, skiprows=733, nrows=241
|
|
360
|
+
).to_numpy(dtype=float)
|
|
361
|
+
a_phi = pd.read_csv(
|
|
362
|
+
ancillary_files[descriptor], header=None, skiprows=975, nrows=241
|
|
363
|
+
).to_numpy(dtype=float)
|
|
364
|
+
g_phi = pd.read_csv(
|
|
365
|
+
ancillary_files[descriptor], header=None, skiprows=1217, nrows=241
|
|
366
|
+
).to_numpy(dtype=float)
|
|
367
|
+
return {
|
|
368
|
+
"theta_grid": theta_grid,
|
|
369
|
+
"phi_grid": phi_grid,
|
|
370
|
+
"a_theta": a_theta,
|
|
371
|
+
"g_theta": g_theta,
|
|
372
|
+
"a_phi": a_phi,
|
|
373
|
+
"g_phi": g_phi,
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def get_scattering_coefficients(
|
|
378
|
+
theta: NDArray,
|
|
379
|
+
phi: NDArray,
|
|
380
|
+
lookup_tables: dict | None = None,
|
|
381
|
+
ancillary_files: dict | None = None,
|
|
382
|
+
instrument_id: int | None = None,
|
|
383
|
+
) -> tuple[NDArray, NDArray]:
|
|
384
|
+
"""
|
|
385
|
+
Get a and g coefficients for theta and phi to compute scattering FWHM.
|
|
386
|
+
|
|
387
|
+
Parameters
|
|
388
|
+
----------
|
|
389
|
+
theta : NDArray
|
|
390
|
+
Elevation angles in degrees.
|
|
391
|
+
phi : NDArray
|
|
392
|
+
Azimuth angles in degrees.
|
|
393
|
+
lookup_tables : dict, optional
|
|
394
|
+
Preloaded lookup tables. If not provided, will load using ancillary_files and
|
|
395
|
+
instrument_id.
|
|
396
|
+
ancillary_files : dict, optional
|
|
397
|
+
Ancillary files, required if lookup_tables is not provided.
|
|
398
|
+
instrument_id : int, optional
|
|
399
|
+
Instrument ID, required if lookup_tables is not provided.
|
|
400
|
+
|
|
401
|
+
Returns
|
|
402
|
+
-------
|
|
403
|
+
tuple
|
|
404
|
+
Scattering a and g values corresponding to the given theta and phi values.
|
|
405
|
+
"""
|
|
406
|
+
if lookup_tables is None:
|
|
407
|
+
if ancillary_files is None or instrument_id is None:
|
|
408
|
+
raise ValueError(
|
|
409
|
+
"ancillary_files and instrument_id must be provided if lookup_tables "
|
|
410
|
+
"is not supplied."
|
|
411
|
+
)
|
|
412
|
+
lookup_tables = load_scattering_lookup_tables(ancillary_files, instrument_id)
|
|
413
|
+
|
|
414
|
+
theta_grid = lookup_tables["theta_grid"]
|
|
415
|
+
phi_grid = lookup_tables["phi_grid"]
|
|
416
|
+
a_theta = lookup_tables["a_theta"]
|
|
417
|
+
g_theta = lookup_tables["g_theta"]
|
|
418
|
+
a_phi = lookup_tables["a_phi"]
|
|
419
|
+
g_phi = lookup_tables["g_phi"]
|
|
420
|
+
|
|
421
|
+
theta_vals = theta_grid[0, :] # columns represent theta
|
|
422
|
+
phi_vals = phi_grid[:, 0] # rows represent phi
|
|
423
|
+
|
|
424
|
+
phi_idx = np.abs(phi_vals[:, None] - phi).argmin(axis=0)
|
|
425
|
+
theta_idx = np.abs(theta_vals[:, None] - theta).argmin(axis=0)
|
|
426
|
+
|
|
427
|
+
a_theta_val = a_theta[phi_idx, theta_idx]
|
|
428
|
+
g_theta_val = g_theta[phi_idx, theta_idx]
|
|
429
|
+
a_phi_val = a_phi[phi_idx, theta_idx]
|
|
430
|
+
g_phi_val = g_phi[phi_idx, theta_idx]
|
|
431
|
+
|
|
432
|
+
return np.column_stack([a_theta_val, g_theta_val]), np.column_stack(
|
|
433
|
+
[a_phi_val, g_phi_val]
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def is_inside_fov(phi: np.ndarray, theta: np.ndarray) -> np.ndarray:
|
|
438
|
+
"""
|
|
439
|
+
Determine angles in the field of view (FOV).
|
|
440
|
+
|
|
441
|
+
This function is used in the deadtime correction to determine whether a given
|
|
442
|
+
(theta, phi) angle is within the instrument's Field of View (FOV).
|
|
443
|
+
Only pixels inside the FOV are considered for time accumulation. The FOV boundary
|
|
444
|
+
is defined by equation 19 in the Ultra Algorithm Document.
|
|
445
|
+
|
|
446
|
+
Parameters
|
|
447
|
+
----------
|
|
448
|
+
phi : np.ndarray
|
|
449
|
+
Azimuth angles in radians.
|
|
450
|
+
theta : np.ndarray
|
|
451
|
+
Elevation angles in radians.
|
|
452
|
+
|
|
453
|
+
Returns
|
|
454
|
+
-------
|
|
455
|
+
numpy.ndarray
|
|
456
|
+
Boolean array indicating if the angle is in the FOV, False otherwise.
|
|
457
|
+
"""
|
|
458
|
+
numerator = 5.0 * np.cos(phi)
|
|
459
|
+
denominator = 1 + 2.80 * np.cos(phi)
|
|
460
|
+
# Equation 19 in the Ultra Algorithm Document.
|
|
461
|
+
theta_nom = np.arctan(numerator / denominator)
|
|
462
|
+
return np.abs(theta) <= theta_nom
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def get_ph_corrected(
|
|
466
|
+
sensor: str,
|
|
467
|
+
location: str,
|
|
468
|
+
ancillary_files: dict,
|
|
469
|
+
xlut: NDArray,
|
|
470
|
+
ylut: NDArray,
|
|
471
|
+
quality_flag: NDArray,
|
|
472
|
+
) -> tuple[NDArray, NDArray]:
|
|
473
|
+
"""
|
|
474
|
+
PH correction for stop anodes, top and bottom.
|
|
475
|
+
|
|
476
|
+
Further description is available starting on
|
|
477
|
+
page 207 of the Ultra Flight Software Document.
|
|
478
|
+
|
|
479
|
+
Parameters
|
|
480
|
+
----------
|
|
481
|
+
sensor : str
|
|
482
|
+
Sensor name: "ultra45" or "ultra90".
|
|
483
|
+
location : str
|
|
484
|
+
Location: "tp" or "bt".
|
|
485
|
+
ancillary_files : dict[Path]
|
|
486
|
+
Ancillary files.
|
|
487
|
+
xlut : NDArray
|
|
488
|
+
X lookup index for PH correction.
|
|
489
|
+
ylut : NDArray
|
|
490
|
+
Y lookup index for PH correction.
|
|
491
|
+
quality_flag : NDArray
|
|
492
|
+
Quality flag to set when there is an outlier.
|
|
493
|
+
|
|
494
|
+
Returns
|
|
495
|
+
-------
|
|
496
|
+
ph_correction : NDArray
|
|
497
|
+
Correction for pulse height.
|
|
498
|
+
quality_flag : NDArray
|
|
499
|
+
Quality flag updated with PH correction flags.
|
|
500
|
+
"""
|
|
501
|
+
ph_correct = pd.read_csv(
|
|
502
|
+
ancillary_files[f"l1b-{sensor[-2:]}sensor-sp{location}phcorr"], header=None
|
|
503
|
+
)
|
|
504
|
+
ph_correct_array = ph_correct.to_numpy()
|
|
505
|
+
|
|
506
|
+
max_x, max_y = ph_correct_array.shape[0] - 1, ph_correct_array.shape[1] - 1
|
|
507
|
+
|
|
508
|
+
# Clamp indices to nearest valid value
|
|
509
|
+
xlut_clamped = np.clip(xlut.astype(int), 0, max_x)
|
|
510
|
+
ylut_clamped = np.clip(ylut.astype(int), 0, max_y)
|
|
511
|
+
|
|
512
|
+
# Flag where clamping occurred
|
|
513
|
+
flagged_mask = (xlut != xlut_clamped) | (ylut != ylut_clamped)
|
|
514
|
+
quality_flag[flagged_mask] |= ImapDEOutliersUltraFlags.PHCORR.value
|
|
515
|
+
|
|
516
|
+
ph_correction = ph_correct_array[xlut_clamped, ylut_clamped]
|
|
517
|
+
|
|
518
|
+
return ph_correction, quality_flag
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def get_ebins(
|
|
522
|
+
lut: str,
|
|
523
|
+
energy: NDArray,
|
|
524
|
+
ctof: NDArray,
|
|
525
|
+
ebins: NDArray,
|
|
526
|
+
ancillary_files: dict,
|
|
527
|
+
) -> NDArray:
|
|
528
|
+
"""
|
|
529
|
+
Get energy bins from the lookup table.
|
|
530
|
+
|
|
531
|
+
Parameters
|
|
532
|
+
----------
|
|
533
|
+
lut : str
|
|
534
|
+
Lookup table name, e.g., "l1b-tofxpht".
|
|
535
|
+
energy : NDArray
|
|
536
|
+
Energy from the event (keV).
|
|
537
|
+
ctof : NDArray
|
|
538
|
+
Corrected TOF (tenths of a ns).
|
|
539
|
+
ebins : NDArray
|
|
540
|
+
Energy bins to fill with values.
|
|
541
|
+
ancillary_files : dict[Path]
|
|
542
|
+
Ancillary files.
|
|
543
|
+
|
|
544
|
+
Returns
|
|
545
|
+
-------
|
|
546
|
+
ebins : NDArray
|
|
547
|
+
Energy bins from the lookup table.
|
|
548
|
+
"""
|
|
549
|
+
with open(ancillary_files[lut]) as f:
|
|
550
|
+
all_lines = f.readlines()
|
|
551
|
+
pixel_text = "".join(all_lines[4:])
|
|
552
|
+
|
|
553
|
+
lut_array = np.fromstring(pixel_text, sep=" ", dtype=int).reshape((2048, 4096))
|
|
554
|
+
# Note that the LUT is indexed [energy, ctof] for l1b-tofxph
|
|
555
|
+
# and [ctof, energy] for everything else.
|
|
556
|
+
if lut == "l1b-tofxph":
|
|
557
|
+
energy_lookup = (2048 - np.floor(energy)).astype(int)
|
|
558
|
+
ctof_lookup = np.floor(ctof).astype(int)
|
|
559
|
+
valid = (
|
|
560
|
+
(energy_lookup >= 0)
|
|
561
|
+
& (energy_lookup < 2048)
|
|
562
|
+
& (ctof_lookup >= 0)
|
|
563
|
+
& (ctof_lookup < 4096)
|
|
564
|
+
)
|
|
565
|
+
ebins[valid] = lut_array[energy_lookup[valid], ctof_lookup[valid]]
|
|
566
|
+
else:
|
|
567
|
+
energy_lookup = np.floor(energy).astype(int)
|
|
568
|
+
ctof_lookup = (2048 - np.floor(ctof)).astype(int)
|
|
569
|
+
valid = (
|
|
570
|
+
(energy_lookup >= 0)
|
|
571
|
+
& (energy_lookup < 4096)
|
|
572
|
+
& (ctof_lookup >= 0)
|
|
573
|
+
& (ctof_lookup < 2048)
|
|
574
|
+
)
|
|
575
|
+
ebins[valid] = lut_array[ctof_lookup[valid], energy_lookup[valid]]
|
|
576
|
+
|
|
577
|
+
return ebins
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def get_scattering_thresholds(ancillary_files: dict) -> dict:
|
|
581
|
+
"""
|
|
582
|
+
Load scattering culling thresholds as a function of energy from a lookup table.
|
|
583
|
+
|
|
584
|
+
Parameters
|
|
585
|
+
----------
|
|
586
|
+
ancillary_files : dict[Path]
|
|
587
|
+
Ancillary files.
|
|
588
|
+
|
|
589
|
+
Returns
|
|
590
|
+
-------
|
|
591
|
+
threshold_dict
|
|
592
|
+
Dictionary containing energy ranges and the corresponding scattering culling
|
|
593
|
+
threshold.
|
|
594
|
+
"""
|
|
595
|
+
# Culling FWHM Scattering values as a function of energy.
|
|
596
|
+
thresholds = pd.read_csv(
|
|
597
|
+
ancillary_files["l1b-scattering-thresholds-per-energy"], header=None, skiprows=1
|
|
598
|
+
).to_numpy(dtype=np.float64)
|
|
599
|
+
# The first two columns represent the energy range (min, max) in keV, and the
|
|
600
|
+
# value is the FWHM scattering threshold in degrees
|
|
601
|
+
threshold_dict = {(row[0], row[1]): row[2] for row in thresholds}
|
|
602
|
+
|
|
603
|
+
return threshold_dict
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Contains list of QFs to use for filtering."""
|
|
2
|
+
|
|
3
|
+
from imap_processing.quality_flags import (
|
|
4
|
+
FlagNameMixin,
|
|
5
|
+
ImapDEOutliersUltraFlags,
|
|
6
|
+
ImapDEScatteringUltraFlags,
|
|
7
|
+
ImapRatesUltraFlags,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
SPIN_QUALITY_FLAG_FILTERS: dict[str, list[FlagNameMixin]] = {
|
|
11
|
+
"quality_attitude": [],
|
|
12
|
+
"quality_ena_rates": [
|
|
13
|
+
ImapRatesUltraFlags.FIRSTSPIN,
|
|
14
|
+
ImapRatesUltraFlags.LASTSPIN,
|
|
15
|
+
],
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
DE_QUALITY_FLAG_FILTERS: dict[str, list[FlagNameMixin]] = {
|
|
19
|
+
"quality_outliers": [ImapDEOutliersUltraFlags.FOV],
|
|
20
|
+
"quality_scattering": [
|
|
21
|
+
ImapDEScatteringUltraFlags.ABOVE_THRESHOLD,
|
|
22
|
+
],
|
|
23
|
+
}
|