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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# TODO: Come back and add in FSW logic.
|
|
4
4
|
import logging
|
|
5
|
+
from collections import namedtuple
|
|
5
6
|
from enum import Enum
|
|
6
7
|
|
|
7
8
|
import numpy as np
|
|
@@ -16,15 +17,21 @@ from imap_processing.ultra.constants import UltraConstants
|
|
|
16
17
|
from imap_processing.ultra.l1b.lookup_utils import (
|
|
17
18
|
get_angular_profiles,
|
|
18
19
|
get_back_position,
|
|
20
|
+
get_ebins,
|
|
19
21
|
get_energy_efficiencies,
|
|
20
22
|
get_energy_norm,
|
|
21
23
|
get_image_params,
|
|
22
24
|
get_norm,
|
|
25
|
+
get_ph_corrected,
|
|
23
26
|
get_y_adjust,
|
|
24
27
|
)
|
|
25
28
|
|
|
26
29
|
logger = logging.getLogger(__name__)
|
|
27
30
|
|
|
31
|
+
FILLVAL_UINT8 = 255
|
|
32
|
+
FILLVAL_FLOAT32 = -1.0e31
|
|
33
|
+
FILLVAL_FLOAT64 = -1.0e31
|
|
34
|
+
|
|
28
35
|
|
|
29
36
|
class StartType(Enum):
|
|
30
37
|
"""Start Type: 1=Left, 2=Right."""
|
|
@@ -49,8 +56,11 @@ class CoinType(Enum):
|
|
|
49
56
|
Bottom = 2
|
|
50
57
|
|
|
51
58
|
|
|
59
|
+
PHTOFResult = namedtuple("PHTOFResult", ["tof", "t2", "xb", "yb", "tofx", "tofy"])
|
|
60
|
+
|
|
61
|
+
|
|
52
62
|
def get_front_x_position(
|
|
53
|
-
start_type: ndarray, start_position_tdc: ndarray, sensor: str
|
|
63
|
+
start_type: ndarray, start_position_tdc: ndarray, sensor: str, ancillary_files: dict
|
|
54
64
|
) -> ndarray:
|
|
55
65
|
"""
|
|
56
66
|
Calculate the front xf position.
|
|
@@ -68,6 +78,8 @@ def get_front_x_position(
|
|
|
68
78
|
Start Position Time to Digital Converter (TDC).
|
|
69
79
|
sensor : str
|
|
70
80
|
Sensor name.
|
|
81
|
+
ancillary_files : dict[Path]
|
|
82
|
+
Ancillary files containing the lookup tables.
|
|
71
83
|
|
|
72
84
|
Returns
|
|
73
85
|
-------
|
|
@@ -77,9 +89,9 @@ def get_front_x_position(
|
|
|
77
89
|
# Left and right start types.
|
|
78
90
|
indices = np.nonzero((start_type == 1) | (start_type == 2))
|
|
79
91
|
|
|
80
|
-
xftsc = get_image_params("XFTSC", sensor)
|
|
81
|
-
xft_lt_off = get_image_params("XFTLTOFF", sensor)
|
|
82
|
-
xft_rt_off = get_image_params("XFTRTOFF", sensor)
|
|
92
|
+
xftsc = get_image_params("XFTSC", sensor, ancillary_files)
|
|
93
|
+
xft_lt_off = get_image_params("XFTLTOFF", sensor, ancillary_files)
|
|
94
|
+
xft_rt_off = get_image_params("XFTRTOFF", sensor, ancillary_files)
|
|
83
95
|
xft_off = np.where(start_type[indices] == 1, xft_lt_off, xft_rt_off)
|
|
84
96
|
|
|
85
97
|
# Calculate xf and convert to hundredths of a millimeter
|
|
@@ -88,7 +100,9 @@ def get_front_x_position(
|
|
|
88
100
|
return xf
|
|
89
101
|
|
|
90
102
|
|
|
91
|
-
def get_front_y_position(
|
|
103
|
+
def get_front_y_position(
|
|
104
|
+
start_type: ndarray, yb: ndarray, ancillary_files: dict
|
|
105
|
+
) -> tuple[ndarray, ndarray]:
|
|
92
106
|
"""
|
|
93
107
|
Compute the adjustments for the front y position and distance front to back.
|
|
94
108
|
|
|
@@ -102,6 +116,8 @@ def get_front_y_position(start_type: ndarray, yb: ndarray) -> tuple[ndarray, nda
|
|
|
102
116
|
Start Type: 1=Left, 2=Right.
|
|
103
117
|
yb : np.array
|
|
104
118
|
Y back position in hundredths of a millimeter.
|
|
119
|
+
ancillary_files : dict[Path]
|
|
120
|
+
Ancillary files containing the lookup tables.
|
|
105
121
|
|
|
106
122
|
Returns
|
|
107
123
|
-------
|
|
@@ -125,7 +141,7 @@ def get_front_y_position(start_type: ndarray, yb: ndarray) -> tuple[ndarray, nda
|
|
|
125
141
|
+ 0.5
|
|
126
142
|
)
|
|
127
143
|
# y adjustment in mm
|
|
128
|
-
y_adjust_left = get_y_adjust(dy_lut_left) / 100
|
|
144
|
+
y_adjust_left = get_y_adjust(dy_lut_left, ancillary_files) / 100
|
|
129
145
|
# hundredths of a millimeter
|
|
130
146
|
yf[index_left] = (UltraConstants.YF_ESTIMATE_LEFT - y_adjust_left) * 100
|
|
131
147
|
# distance adjustment in mm
|
|
@@ -141,7 +157,7 @@ def get_front_y_position(start_type: ndarray, yb: ndarray) -> tuple[ndarray, nda
|
|
|
141
157
|
+ 0.5
|
|
142
158
|
)
|
|
143
159
|
# y adjustment in mm
|
|
144
|
-
y_adjust_right = get_y_adjust(dy_lut_right) / 100
|
|
160
|
+
y_adjust_right = get_y_adjust(dy_lut_right, ancillary_files) / 100
|
|
145
161
|
# hundredths of a millimeter
|
|
146
162
|
yf[index_right] = (UltraConstants.YF_ESTIMATE_RIGHT + y_adjust_right) * 100
|
|
147
163
|
# distance adjustment in mm
|
|
@@ -153,8 +169,8 @@ def get_front_y_position(start_type: ndarray, yb: ndarray) -> tuple[ndarray, nda
|
|
|
153
169
|
|
|
154
170
|
|
|
155
171
|
def get_ph_tof_and_back_positions(
|
|
156
|
-
de_dataset: xarray.Dataset, xf: np.ndarray, sensor: str
|
|
157
|
-
) ->
|
|
172
|
+
de_dataset: xarray.Dataset, xf: np.ndarray, sensor: str, ancillary_files: dict
|
|
173
|
+
) -> PHTOFResult:
|
|
158
174
|
"""
|
|
159
175
|
Calculate back xb, yb position and tof.
|
|
160
176
|
|
|
@@ -176,6 +192,8 @@ def get_ph_tof_and_back_positions(
|
|
|
176
192
|
Has same length as de_dataset.
|
|
177
193
|
sensor : str
|
|
178
194
|
Sensor name.
|
|
195
|
+
ancillary_files : dict[Path]
|
|
196
|
+
Ancillary files containing the lookup tables.
|
|
179
197
|
|
|
180
198
|
Returns
|
|
181
199
|
-------
|
|
@@ -187,6 +205,10 @@ def get_ph_tof_and_back_positions(
|
|
|
187
205
|
Back positions in x direction (hundredths of a millimeter).
|
|
188
206
|
yb : np.array
|
|
189
207
|
Back positions in y direction (hundredths of a millimeter).
|
|
208
|
+
tofx : np.array
|
|
209
|
+
X front position tof offset (tenths of a nanosecond).
|
|
210
|
+
tofy : np.array
|
|
211
|
+
Y front position tof offset (tenths of a nanosecond).
|
|
190
212
|
"""
|
|
191
213
|
indices = np.nonzero(
|
|
192
214
|
np.isin(de_dataset["stop_type"], [StopType.Top.value, StopType.Bottom.value])
|
|
@@ -197,10 +219,18 @@ def get_ph_tof_and_back_positions(
|
|
|
197
219
|
|
|
198
220
|
# There are mismatches between the stop TDCs, i.e., SpN, SpS, SpE, and SpW.
|
|
199
221
|
# This normalizes the TDCs
|
|
200
|
-
sp_n_norm = get_norm(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
222
|
+
sp_n_norm = get_norm(
|
|
223
|
+
de_filtered["stop_north_tdc"].data, "SpN", sensor, ancillary_files
|
|
224
|
+
)
|
|
225
|
+
sp_s_norm = get_norm(
|
|
226
|
+
de_filtered["stop_south_tdc"].data, "SpS", sensor, ancillary_files
|
|
227
|
+
)
|
|
228
|
+
sp_e_norm = get_norm(
|
|
229
|
+
de_filtered["stop_east_tdc"].data, "SpE", sensor, ancillary_files
|
|
230
|
+
)
|
|
231
|
+
sp_w_norm = get_norm(
|
|
232
|
+
de_filtered["stop_west_tdc"].data, "SpW", sensor, ancillary_files
|
|
233
|
+
)
|
|
204
234
|
|
|
205
235
|
# Convert normalized TDC values into units of hundredths of a
|
|
206
236
|
# millimeter using lookup tables.
|
|
@@ -227,37 +257,41 @@ def get_ph_tof_and_back_positions(
|
|
|
227
257
|
# Convert converts normalized TDC values into units of
|
|
228
258
|
# hundredths of a millimeter using lookup tables.
|
|
229
259
|
stop_type_top = de_filtered["stop_type"].data == StopType.Top.value
|
|
230
|
-
xb[stop_type_top] = get_back_position(
|
|
231
|
-
|
|
260
|
+
xb[stop_type_top] = get_back_position(
|
|
261
|
+
xb_index[stop_type_top], "XBkTp", sensor, ancillary_files
|
|
262
|
+
)
|
|
263
|
+
yb[stop_type_top] = get_back_position(
|
|
264
|
+
yb_index[stop_type_top], "YBkTp", sensor, ancillary_files
|
|
265
|
+
)
|
|
232
266
|
|
|
233
267
|
# Correction for the propagation delay of the start anode and other effects.
|
|
234
|
-
t2[stop_type_top] = get_image_params("TOFSC", sensor) * t1[
|
|
268
|
+
t2[stop_type_top] = get_image_params("TOFSC", sensor, ancillary_files) * t1[
|
|
235
269
|
stop_type_top
|
|
236
|
-
] + get_image_params("TOFTPOFF", sensor)
|
|
270
|
+
] + get_image_params("TOFTPOFF", sensor, ancillary_files)
|
|
237
271
|
# Variable xf_ph divided by 10 to convert to mm.
|
|
238
272
|
tof[stop_type_top] = t2[stop_type_top] + xf_ph[
|
|
239
273
|
stop_type_top
|
|
240
|
-
] / 10 * get_image_params("XFTTOF", sensor)
|
|
274
|
+
] / 10 * get_image_params("XFTTOF", sensor, ancillary_files)
|
|
241
275
|
|
|
242
276
|
stop_type_bottom = de_filtered["stop_type"].data == StopType.Bottom.value
|
|
243
277
|
xb[stop_type_bottom] = get_back_position(
|
|
244
|
-
xb_index[stop_type_bottom], "XBkBt", sensor
|
|
278
|
+
xb_index[stop_type_bottom], "XBkBt", sensor, ancillary_files
|
|
245
279
|
)
|
|
246
280
|
yb[stop_type_bottom] = get_back_position(
|
|
247
|
-
yb_index[stop_type_bottom], "YBkBt", sensor
|
|
281
|
+
yb_index[stop_type_bottom], "YBkBt", sensor, ancillary_files
|
|
248
282
|
)
|
|
249
283
|
|
|
250
284
|
# Correction for the propagation delay of the start anode and other effects.
|
|
251
|
-
t2[stop_type_bottom] = get_image_params("TOFSC", sensor) * t1[
|
|
285
|
+
t2[stop_type_bottom] = get_image_params("TOFSC", sensor, ancillary_files) * t1[
|
|
252
286
|
stop_type_bottom
|
|
253
|
-
] + get_image_params("TOFBTOFF", sensor) # 10*ns
|
|
287
|
+
] + get_image_params("TOFBTOFF", sensor, ancillary_files) # 10*ns
|
|
254
288
|
|
|
255
289
|
# Variable xf_ph divided by 10 to convert to mm.
|
|
256
290
|
tof[stop_type_bottom] = t2[stop_type_bottom] + xf_ph[
|
|
257
291
|
stop_type_bottom
|
|
258
|
-
] / 10 * get_image_params("XFTTOF", sensor)
|
|
292
|
+
] / 10 * get_image_params("XFTTOF", sensor, ancillary_files)
|
|
259
293
|
|
|
260
|
-
return tof, t2, xb, yb
|
|
294
|
+
return PHTOFResult(tof=tof, t2=t2, xb=xb, yb=yb, tofx=tofx, tofy=tofy)
|
|
261
295
|
|
|
262
296
|
|
|
263
297
|
def get_path_length(
|
|
@@ -290,7 +324,7 @@ def get_path_length(
|
|
|
290
324
|
|
|
291
325
|
|
|
292
326
|
def get_ssd_back_position_and_tof_offset(
|
|
293
|
-
de_dataset: xarray.Dataset, sensor: str
|
|
327
|
+
de_dataset: xarray.Dataset, sensor: str, ancillary_files: dict
|
|
294
328
|
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
295
329
|
"""
|
|
296
330
|
Lookup the Y SSD positions (yb), TOF Offset, and SSD number.
|
|
@@ -301,6 +335,8 @@ def get_ssd_back_position_and_tof_offset(
|
|
|
301
335
|
The input dataset containing STOP_TYPE and SSD_FLAG data.
|
|
302
336
|
sensor : str
|
|
303
337
|
Sensor name.
|
|
338
|
+
ancillary_files : dict[Path]
|
|
339
|
+
Ancillary files containing the lookup tables.
|
|
304
340
|
|
|
305
341
|
Returns
|
|
306
342
|
-------
|
|
@@ -326,21 +362,27 @@ def get_ssd_back_position_and_tof_offset(
|
|
|
326
362
|
ssd_flag_mask = de_filtered[f"ssd_flag_{i}"].data == 1
|
|
327
363
|
|
|
328
364
|
# Multiply ybs times 100 to convert to hundredths of a millimeter.
|
|
329
|
-
yb[ssd_flag_mask] =
|
|
365
|
+
yb[ssd_flag_mask] = (
|
|
366
|
+
get_image_params(f"YBKSSD{i}", sensor, ancillary_files) * 100
|
|
367
|
+
)
|
|
330
368
|
ssd_number[ssd_flag_mask] = i
|
|
331
369
|
|
|
332
370
|
tof_offset[
|
|
333
371
|
(de_filtered["start_type"] == StartType.Left.value) & ssd_flag_mask
|
|
334
|
-
] = get_image_params(f"TOFSSDLTOFF{i}", sensor)
|
|
372
|
+
] = get_image_params(f"TOFSSDLTOFF{i}", sensor, ancillary_files)
|
|
335
373
|
tof_offset[
|
|
336
374
|
(de_filtered["start_type"] == StartType.Right.value) & ssd_flag_mask
|
|
337
|
-
] = get_image_params(f"TOFSSDRTOFF{i}", sensor)
|
|
375
|
+
] = get_image_params(f"TOFSSDRTOFF{i}", sensor, ancillary_files)
|
|
338
376
|
|
|
339
377
|
return yb, tof_offset, ssd_number
|
|
340
378
|
|
|
341
379
|
|
|
342
380
|
def calculate_etof_xc(
|
|
343
|
-
de_subset: xarray.Dataset,
|
|
381
|
+
de_subset: xarray.Dataset,
|
|
382
|
+
particle_tof: np.ndarray,
|
|
383
|
+
sensor: str,
|
|
384
|
+
location: str,
|
|
385
|
+
ancillary_files: dict,
|
|
344
386
|
) -> tuple[np.ndarray, np.ndarray]:
|
|
345
387
|
"""
|
|
346
388
|
Calculate the etof and xc values for the given subset.
|
|
@@ -355,6 +397,8 @@ def calculate_etof_xc(
|
|
|
355
397
|
Sensor name.
|
|
356
398
|
location : str
|
|
357
399
|
Location indicator, either 'TP' (Top) or 'BT' (Bottom).
|
|
400
|
+
ancillary_files : dict[Path]
|
|
401
|
+
Ancillary files containing the lookup tables.
|
|
358
402
|
|
|
359
403
|
Returns
|
|
360
404
|
-------
|
|
@@ -365,17 +409,21 @@ def calculate_etof_xc(
|
|
|
365
409
|
X coincidence position (millimeters).
|
|
366
410
|
"""
|
|
367
411
|
# CoinNNorm
|
|
368
|
-
coin_n_norm = get_norm(
|
|
412
|
+
coin_n_norm = get_norm(
|
|
413
|
+
de_subset["coin_north_tdc"], "CoinN", sensor, ancillary_files
|
|
414
|
+
)
|
|
369
415
|
# CoinSNorm
|
|
370
|
-
coin_s_norm = get_norm(
|
|
371
|
-
|
|
416
|
+
coin_s_norm = get_norm(
|
|
417
|
+
de_subset["coin_south_tdc"], "CoinS", sensor, ancillary_files
|
|
418
|
+
)
|
|
419
|
+
xc = get_image_params(f"XCOIN{location}SC", sensor, ancillary_files) * (
|
|
372
420
|
coin_s_norm - coin_n_norm
|
|
373
|
-
) + get_image_params(f"XCOIN{location}OFF", sensor) # millimeter
|
|
421
|
+
) + get_image_params(f"XCOIN{location}OFF", sensor, ancillary_files) # millimeter
|
|
374
422
|
|
|
375
423
|
# Time for the electrons to travel back to coincidence anode.
|
|
376
|
-
t2 = get_image_params("ETOFSC", sensor) * (
|
|
424
|
+
t2 = get_image_params("ETOFSC", sensor, ancillary_files) * (
|
|
377
425
|
coin_n_norm + coin_s_norm
|
|
378
|
-
) + get_image_params(f"ETOF{location}OFF", sensor)
|
|
426
|
+
) + get_image_params(f"ETOF{location}OFF", sensor, ancillary_files)
|
|
379
427
|
|
|
380
428
|
# Multiply by 10 to convert to tenths of a nanosecond.
|
|
381
429
|
etof = t2 * 10 - particle_tof
|
|
@@ -384,7 +432,10 @@ def calculate_etof_xc(
|
|
|
384
432
|
|
|
385
433
|
|
|
386
434
|
def get_coincidence_positions(
|
|
387
|
-
de_dataset: xarray.Dataset,
|
|
435
|
+
de_dataset: xarray.Dataset,
|
|
436
|
+
particle_tof: np.ndarray,
|
|
437
|
+
sensor: str,
|
|
438
|
+
ancillary_files: dict,
|
|
388
439
|
) -> tuple[np.ndarray, np.ndarray]:
|
|
389
440
|
"""
|
|
390
441
|
Calculate coincidence positions.
|
|
@@ -408,6 +459,8 @@ def get_coincidence_positions(
|
|
|
408
459
|
(tenths of a nanosecond).
|
|
409
460
|
sensor : str
|
|
410
461
|
Sensor name.
|
|
462
|
+
ancillary_files : dict[Path]
|
|
463
|
+
Ancillary files containing the lookup tables.
|
|
411
464
|
|
|
412
465
|
Returns
|
|
413
466
|
-------
|
|
@@ -431,12 +484,14 @@ def get_coincidence_positions(
|
|
|
431
484
|
# Normalized TDCs
|
|
432
485
|
# For the stop anode, there are mismatches between the coincidence TDCs,
|
|
433
486
|
# i.e., CoinN and CoinS. They must be normalized via lookup tables.
|
|
434
|
-
etof_top, xc_top = calculate_etof_xc(
|
|
487
|
+
etof_top, xc_top = calculate_etof_xc(
|
|
488
|
+
de_top, particle_tof[index_top], sensor, "TP", ancillary_files
|
|
489
|
+
)
|
|
435
490
|
etof[index_top] = etof_top
|
|
436
491
|
xc_array[index_top] = xc_top
|
|
437
492
|
|
|
438
493
|
etof_bottom, xc_bottom = calculate_etof_xc(
|
|
439
|
-
de_bottom, particle_tof[index_bottom], sensor, "BT"
|
|
494
|
+
de_bottom, particle_tof[index_bottom], sensor, "BT", ancillary_files
|
|
440
495
|
)
|
|
441
496
|
etof[index_bottom] = etof_bottom
|
|
442
497
|
xc_array[index_bottom] = xc_bottom
|
|
@@ -488,9 +543,9 @@ def get_de_velocity(
|
|
|
488
543
|
v_y = delta_v[:, 1] / tof * 1e3
|
|
489
544
|
v_z = delta_v[:, 2] / tof * 1e3
|
|
490
545
|
|
|
491
|
-
v_x[tof < 0] =
|
|
492
|
-
v_y[tof < 0] =
|
|
493
|
-
v_z[tof < 0] =
|
|
546
|
+
v_x[tof < 0] = FILLVAL_FLOAT32 # used as fillvals
|
|
547
|
+
v_y[tof < 0] = FILLVAL_FLOAT32
|
|
548
|
+
v_z[tof < 0] = FILLVAL_FLOAT32
|
|
494
549
|
|
|
495
550
|
velocities = np.vstack((v_x, v_y, v_z)).T
|
|
496
551
|
|
|
@@ -501,7 +556,7 @@ def get_de_velocity(
|
|
|
501
556
|
|
|
502
557
|
|
|
503
558
|
def get_ssd_tof(
|
|
504
|
-
de_dataset: xarray.Dataset, xf: np.ndarray, sensor: str
|
|
559
|
+
de_dataset: xarray.Dataset, xf: np.ndarray, sensor: str, ancillary_files: dict
|
|
505
560
|
) -> NDArray[np.float64]:
|
|
506
561
|
"""
|
|
507
562
|
Calculate back xb, yb position for the SSDs.
|
|
@@ -527,25 +582,32 @@ def get_ssd_tof(
|
|
|
527
582
|
Front x position (hundredths of a millimeter).
|
|
528
583
|
sensor : str
|
|
529
584
|
Sensor name.
|
|
585
|
+
ancillary_files : dict[Path]
|
|
586
|
+
Ancillary files containing the lookup tables.
|
|
530
587
|
|
|
531
588
|
Returns
|
|
532
589
|
-------
|
|
533
590
|
tof : np.ndarray
|
|
534
591
|
Time of flight (tenths of a nanosecond).
|
|
535
592
|
"""
|
|
536
|
-
_, tof_offset, ssd_number = get_ssd_back_position_and_tof_offset(
|
|
593
|
+
_, tof_offset, ssd_number = get_ssd_back_position_and_tof_offset(
|
|
594
|
+
de_dataset, sensor, ancillary_files
|
|
595
|
+
)
|
|
537
596
|
indices = np.nonzero(np.isin(de_dataset["stop_type"], [StopType.SSD.value]))[0]
|
|
538
597
|
|
|
539
598
|
de_discrete = de_dataset.isel(epoch=indices)["coin_discrete_tdc"]
|
|
540
599
|
|
|
541
|
-
time =
|
|
600
|
+
time = (
|
|
601
|
+
get_image_params("TOFSSDSC", sensor, ancillary_files) * de_discrete.values
|
|
602
|
+
+ tof_offset
|
|
603
|
+
)
|
|
542
604
|
|
|
543
605
|
# The scale factor and offsets, and a multiplier to convert xf to a tof offset.
|
|
544
606
|
# Convert xf to mm by dividing by 100.
|
|
545
607
|
tof = (
|
|
546
608
|
time
|
|
547
|
-
+ get_image_params("TOFSSDTOTOFF", sensor)
|
|
548
|
-
+ xf[indices] / 100 * get_image_params("XFTTOF", sensor)
|
|
609
|
+
+ get_image_params("TOFSSDTOTOFF", sensor, ancillary_files)
|
|
610
|
+
+ xf[indices] / 100 * get_image_params("XFTTOF", sensor, ancillary_files)
|
|
549
611
|
) * 10
|
|
550
612
|
|
|
551
613
|
# Convert TOF to tenths of a nanosecond.
|
|
@@ -572,12 +634,17 @@ def get_de_energy_kev(v: np.ndarray, species: np.ndarray) -> NDArray:
|
|
|
572
634
|
# Compute the sum of squares.
|
|
573
635
|
v2 = np.sum(vv**2, axis=1)
|
|
574
636
|
|
|
575
|
-
|
|
576
|
-
|
|
637
|
+
# Only compute where species == 1 and v is valid
|
|
638
|
+
index_hydrogen = species == 1
|
|
639
|
+
valid_velocity = np.isfinite(v2)
|
|
640
|
+
valid_mask = index_hydrogen & valid_velocity
|
|
641
|
+
|
|
642
|
+
energy = np.full_like(v2, FILLVAL_FLOAT32)
|
|
577
643
|
|
|
644
|
+
# TODO: we will calculate the energies of the different species here.
|
|
578
645
|
# 1/2 mv^2 in Joules, convert to keV
|
|
579
|
-
energy[
|
|
580
|
-
0.5 * UltraConstants.MASS_H * v2[
|
|
646
|
+
energy[valid_mask] = (
|
|
647
|
+
0.5 * UltraConstants.MASS_H * v2[valid_mask] * UltraConstants.J_KEV
|
|
581
648
|
)
|
|
582
649
|
|
|
583
650
|
return energy
|
|
@@ -589,7 +656,9 @@ def get_energy_pulse_height(
|
|
|
589
656
|
xb: np.ndarray,
|
|
590
657
|
yb: np.ndarray,
|
|
591
658
|
sensor: str,
|
|
592
|
-
|
|
659
|
+
ancillary_files: dict,
|
|
660
|
+
quality_flags: NDArray,
|
|
661
|
+
) -> tuple[NDArray, NDArray]:
|
|
593
662
|
"""
|
|
594
663
|
Calculate the pulse-height energy.
|
|
595
664
|
|
|
@@ -611,6 +680,10 @@ def get_energy_pulse_height(
|
|
|
611
680
|
Y back position (hundredths of a millimeter).
|
|
612
681
|
sensor : str
|
|
613
682
|
Sensor name.
|
|
683
|
+
ancillary_files : dict[Path]
|
|
684
|
+
Ancillary files containing the lookup tables.
|
|
685
|
+
quality_flags : NDArray
|
|
686
|
+
Quality flag to set when there is an outlier.
|
|
614
687
|
|
|
615
688
|
Returns
|
|
616
689
|
-------
|
|
@@ -625,28 +698,59 @@ def get_energy_pulse_height(
|
|
|
625
698
|
ylut = np.zeros(len(stop_type), dtype=np.float64)
|
|
626
699
|
energy_ph = np.zeros(len(stop_type), dtype=np.float64)
|
|
627
700
|
|
|
701
|
+
# Full-length correction arrays
|
|
702
|
+
ph_correction = np.zeros(len(stop_type), dtype=np.float64)
|
|
703
|
+
|
|
628
704
|
# Stop type 1
|
|
629
|
-
xlut[indices_top] = (xb[indices_top] / 100 -
|
|
705
|
+
xlut[indices_top] = (xb[indices_top] / 100 - 24.5 / 2) * 20 / 50 # mm
|
|
630
706
|
ylut[indices_top] = (yb[indices_top] / 100 + 82 / 2) * 32 / 82 # mm
|
|
631
707
|
# Stop type 2
|
|
632
|
-
xlut[indices_bottom] = (xb[indices_bottom] / 100 + 50 +
|
|
708
|
+
xlut[indices_bottom] = (xb[indices_bottom] / 100 + 50 + 24.5 / 2) * 20 / 50 # mm
|
|
633
709
|
ylut[indices_bottom] = (yb[indices_bottom] / 100 + 82 / 2) * 32 / 82 # mm
|
|
634
710
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
"
|
|
638
|
-
|
|
639
|
-
|
|
711
|
+
ph_correction_top, updated_flags_top = get_ph_corrected(
|
|
712
|
+
"ultra45",
|
|
713
|
+
"tp",
|
|
714
|
+
ancillary_files,
|
|
715
|
+
np.round(xlut[indices_top]),
|
|
716
|
+
np.round(ylut[indices_top]),
|
|
717
|
+
quality_flags[indices_top].copy(),
|
|
718
|
+
)
|
|
719
|
+
quality_flags[indices_top] = updated_flags_top
|
|
720
|
+
ph_correction_bottom, updated_flags_bottom = get_ph_corrected(
|
|
721
|
+
"ultra45",
|
|
722
|
+
"bt",
|
|
723
|
+
ancillary_files,
|
|
724
|
+
np.round(xlut[indices_bottom]),
|
|
725
|
+
np.round(ylut[indices_bottom]),
|
|
726
|
+
quality_flags[indices_bottom].copy(),
|
|
727
|
+
)
|
|
728
|
+
quality_flags[indices_bottom] = updated_flags_bottom
|
|
729
|
+
|
|
730
|
+
ph_correction[indices_top] = ph_correction_top / 1024
|
|
731
|
+
ph_correction[indices_bottom] = ph_correction_bottom / 1024
|
|
732
|
+
|
|
733
|
+
energy_ph[indices_top] = (
|
|
734
|
+
(energy[indices_top] - get_image_params("SPTPPHOFF", sensor, ancillary_files))
|
|
735
|
+
* ph_correction_top
|
|
736
|
+
/ 1024
|
|
737
|
+
)
|
|
640
738
|
|
|
641
|
-
energy_ph[indices_bottom] =
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
739
|
+
energy_ph[indices_bottom] = (
|
|
740
|
+
(
|
|
741
|
+
energy[indices_bottom]
|
|
742
|
+
- get_image_params("SPBTPHOFF", sensor, ancillary_files)
|
|
743
|
+
)
|
|
744
|
+
* ph_correction_bottom
|
|
745
|
+
/ 1024.0
|
|
746
|
+
)
|
|
645
747
|
|
|
646
|
-
return energy_ph
|
|
748
|
+
return energy_ph, ph_correction
|
|
647
749
|
|
|
648
750
|
|
|
649
|
-
def get_energy_ssd(
|
|
751
|
+
def get_energy_ssd(
|
|
752
|
+
de_dataset: xarray.Dataset, ssd: np.ndarray, ancillary_files: dict
|
|
753
|
+
) -> NDArray[np.float64]:
|
|
650
754
|
"""
|
|
651
755
|
Get SSD energy.
|
|
652
756
|
|
|
@@ -664,6 +768,8 @@ def get_energy_ssd(de_dataset: xarray.Dataset, ssd: np.ndarray) -> NDArray[np.fl
|
|
|
664
768
|
Events dataset.
|
|
665
769
|
ssd : np.ndarray
|
|
666
770
|
SSD number.
|
|
771
|
+
ancillary_files : dict[Path]
|
|
772
|
+
Ancillary files containing the lookup tables.
|
|
667
773
|
|
|
668
774
|
Returns
|
|
669
775
|
-------
|
|
@@ -685,7 +791,7 @@ def get_energy_ssd(de_dataset: xarray.Dataset, ssd: np.ndarray) -> NDArray[np.fl
|
|
|
685
791
|
energy < UltraConstants.COMPOSITE_ENERGY_THRESHOLD
|
|
686
792
|
]
|
|
687
793
|
|
|
688
|
-
energy_norm = get_energy_norm(ssd, composite_energy)
|
|
794
|
+
energy_norm = get_energy_norm(ssd, composite_energy, ancillary_files)
|
|
689
795
|
|
|
690
796
|
return energy_norm
|
|
691
797
|
|
|
@@ -760,14 +866,9 @@ def determine_species(tof: np.ndarray, path_length: np.ndarray, type: str) -> ND
|
|
|
760
866
|
"""
|
|
761
867
|
# Event TOF normalization to Z axis
|
|
762
868
|
ctof, _ = get_ctof(tof, path_length, type)
|
|
763
|
-
#
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
# Assign Species 1 ("H") to bins where cTOF is within the specified range
|
|
767
|
-
species_bin[
|
|
768
|
-
(ctof > UltraConstants.CTOF_SPECIES_MIN)
|
|
769
|
-
& (ctof < UltraConstants.CTOF_SPECIES_MAX)
|
|
770
|
-
] = 1
|
|
869
|
+
# Assign Species 1 ("H") to bins
|
|
870
|
+
# TODO: this is a placeholder for future species assignments.
|
|
871
|
+
species_bin = np.full(len(ctof), 1, dtype=np.uint8)
|
|
771
872
|
|
|
772
873
|
return species_bin
|
|
773
874
|
|
|
@@ -805,6 +906,68 @@ def get_phi_theta(
|
|
|
805
906
|
return np.degrees(phi), np.degrees(theta)
|
|
806
907
|
|
|
807
908
|
|
|
909
|
+
def get_spin_number(de_met: NDArray, de_spin: NDArray) -> NDArray:
|
|
910
|
+
"""
|
|
911
|
+
Get the spin number.
|
|
912
|
+
|
|
913
|
+
Parameters
|
|
914
|
+
----------
|
|
915
|
+
de_met : NDArray
|
|
916
|
+
Mission elapsed time.
|
|
917
|
+
de_spin : NDArray
|
|
918
|
+
Spin number 0-255.
|
|
919
|
+
|
|
920
|
+
Returns
|
|
921
|
+
-------
|
|
922
|
+
assigned_spin_number : NDArray
|
|
923
|
+
Spin number for DE data product.
|
|
924
|
+
"""
|
|
925
|
+
# DE packet data.
|
|
926
|
+
# Since the spin number in the direct events packet
|
|
927
|
+
# is only 8 bits it goes from 0-255.
|
|
928
|
+
# Within a pointing that means we will always have duplicate spin numbers.
|
|
929
|
+
# In other words, different spins will be represented by the same spin number.
|
|
930
|
+
# Just to make certain that we won't accidentally combine
|
|
931
|
+
# multiple spins we need to sort by time here.
|
|
932
|
+
sort_idx = np.argsort(de_met)
|
|
933
|
+
de_met_sorted = de_met[sort_idx]
|
|
934
|
+
de_spin_sorted = de_spin[sort_idx]
|
|
935
|
+
# Here we are finding the start and end indices of each spin in the sorted array.
|
|
936
|
+
is_new_spin = np.concatenate([[True], de_spin_sorted[1:] != de_spin_sorted[:-1]])
|
|
937
|
+
spin_start_indices = np.where(is_new_spin)[0]
|
|
938
|
+
spin_end_indices = np.append(spin_start_indices[1:], len(de_met_sorted))
|
|
939
|
+
|
|
940
|
+
# Universal Spin Table.
|
|
941
|
+
spin_df = get_spin_data()
|
|
942
|
+
# Retrieve the met values of the start of the spin.
|
|
943
|
+
spin_start_mets = spin_df["spin_start_met"].values
|
|
944
|
+
# Retrieve the corresponding spin numbers.
|
|
945
|
+
spin_numbers = spin_df["spin_number"].values
|
|
946
|
+
assigned_spin_number_sorted = np.empty(de_spin_sorted.shape, dtype=np.uint32)
|
|
947
|
+
# These last 8 bits are the same as the spin number in the DE packet.
|
|
948
|
+
# So this will give us choices of which spins are
|
|
949
|
+
# available to assign to the DE data.
|
|
950
|
+
possible_spins = spin_numbers & 0xFF
|
|
951
|
+
|
|
952
|
+
# Assign each group based on time.
|
|
953
|
+
for start, end in zip(spin_start_indices, spin_end_indices, strict=False):
|
|
954
|
+
# Now that we have the possible spins from the Universal Spin Table,
|
|
955
|
+
# we match the times of those spins to the nearest times in the DE data.
|
|
956
|
+
possible_times = spin_start_mets[possible_spins == de_spin_sorted[start]]
|
|
957
|
+
# Get nearest time for matching spins.
|
|
958
|
+
nearest_idx = np.abs(possible_times - de_met_sorted[start]).argmin()
|
|
959
|
+
nearest_value = possible_times[nearest_idx]
|
|
960
|
+
assigned_spin_number_sorted[start:end] = spin_numbers[
|
|
961
|
+
spin_start_mets == nearest_value
|
|
962
|
+
]
|
|
963
|
+
|
|
964
|
+
# Undo the sort to match original order.
|
|
965
|
+
assigned_spin_number = np.empty_like(assigned_spin_number_sorted)
|
|
966
|
+
assigned_spin_number[sort_idx] = assigned_spin_number_sorted
|
|
967
|
+
|
|
968
|
+
return assigned_spin_number
|
|
969
|
+
|
|
970
|
+
|
|
808
971
|
def get_eventtimes(
|
|
809
972
|
spin: NDArray, phase_angle: NDArray
|
|
810
973
|
) -> tuple[NDArray, NDArray, NDArray]:
|
|
@@ -884,8 +1047,11 @@ def interpolate_fwhm(
|
|
|
884
1047
|
)
|
|
885
1048
|
|
|
886
1049
|
# Note: will return nan for those out-of-bounds inputs.
|
|
887
|
-
|
|
888
|
-
|
|
1050
|
+
phi_vals = interp_phi((energy, phi_inst))
|
|
1051
|
+
theta_vals = interp_theta((energy, theta_inst))
|
|
1052
|
+
|
|
1053
|
+
phi_interp = np.where(np.isnan(phi_vals), FILLVAL_FLOAT32, phi_vals)
|
|
1054
|
+
theta_interp = np.where(np.isnan(theta_vals), FILLVAL_FLOAT32, theta_vals)
|
|
889
1055
|
|
|
890
1056
|
return phi_interp, theta_interp
|
|
891
1057
|
|
|
@@ -896,6 +1062,7 @@ def get_fwhm(
|
|
|
896
1062
|
energy: NDArray,
|
|
897
1063
|
phi_inst: NDArray,
|
|
898
1064
|
theta_inst: NDArray,
|
|
1065
|
+
ancillary_files: dict,
|
|
899
1066
|
) -> tuple[NDArray, NDArray]:
|
|
900
1067
|
"""
|
|
901
1068
|
Interpolate phi and theta FWHM values for each event based on start type.
|
|
@@ -912,6 +1079,8 @@ def get_fwhm(
|
|
|
912
1079
|
Instrument-frame azimuth angle for each event.
|
|
913
1080
|
theta_inst : NDArray
|
|
914
1081
|
Instrument-frame elevation angle for each event.
|
|
1082
|
+
ancillary_files : dict
|
|
1083
|
+
Ancillary files containing lookup tables for angular profiles.
|
|
915
1084
|
|
|
916
1085
|
Returns
|
|
917
1086
|
-------
|
|
@@ -920,10 +1089,10 @@ def get_fwhm(
|
|
|
920
1089
|
theta_interp : NDArray
|
|
921
1090
|
Interpolated theta FWHM values.
|
|
922
1091
|
"""
|
|
923
|
-
phi_interp = np.full_like(phi_inst,
|
|
924
|
-
theta_interp = np.full_like(theta_inst,
|
|
925
|
-
lt_table = get_angular_profiles("left", sensor)
|
|
926
|
-
rt_table = get_angular_profiles("right", sensor)
|
|
1092
|
+
phi_interp = np.full_like(phi_inst, FILLVAL_FLOAT64, dtype=np.float64)
|
|
1093
|
+
theta_interp = np.full_like(theta_inst, FILLVAL_FLOAT64, dtype=np.float64)
|
|
1094
|
+
lt_table = get_angular_profiles("left", sensor, ancillary_files)
|
|
1095
|
+
rt_table = get_angular_profiles("right", sensor, ancillary_files)
|
|
927
1096
|
|
|
928
1097
|
# Left start type
|
|
929
1098
|
idx_left = start_type == StartType.Left.value
|
|
@@ -940,20 +1109,12 @@ def get_fwhm(
|
|
|
940
1109
|
return phi_interp, theta_interp
|
|
941
1110
|
|
|
942
1111
|
|
|
943
|
-
def
|
|
944
|
-
energy: NDArray, phi_inst: NDArray, theta_inst: NDArray, ancillary_files: dict
|
|
945
|
-
) -> NDArray:
|
|
1112
|
+
def get_efficiency_interpolator(ancillary_files: dict) -> RegularGridInterpolator:
|
|
946
1113
|
"""
|
|
947
|
-
|
|
1114
|
+
Return a callable function that interpolates efficiency values for each event.
|
|
948
1115
|
|
|
949
1116
|
Parameters
|
|
950
1117
|
----------
|
|
951
|
-
energy : NDArray
|
|
952
|
-
Energy values for each event.
|
|
953
|
-
phi_inst : NDArray
|
|
954
|
-
Instrument-frame azimuth angle for each event.
|
|
955
|
-
theta_inst : NDArray
|
|
956
|
-
Instrument-frame elevation angle for each event.
|
|
957
1118
|
ancillary_files : dict
|
|
958
1119
|
Ancillary files.
|
|
959
1120
|
|
|
@@ -978,7 +1139,290 @@ def get_efficiency(
|
|
|
978
1139
|
(theta_vals, phi_vals, energy_vals),
|
|
979
1140
|
efficiency_grid,
|
|
980
1141
|
bounds_error=False,
|
|
981
|
-
fill_value=
|
|
1142
|
+
fill_value=FILLVAL_FLOAT32,
|
|
982
1143
|
)
|
|
983
1144
|
|
|
1145
|
+
return interpolator
|
|
1146
|
+
|
|
1147
|
+
|
|
1148
|
+
def get_efficiency(
|
|
1149
|
+
energy: NDArray,
|
|
1150
|
+
phi_inst: NDArray,
|
|
1151
|
+
theta_inst: NDArray,
|
|
1152
|
+
ancillary_files: dict,
|
|
1153
|
+
interpolator: RegularGridInterpolator = None,
|
|
1154
|
+
) -> np.ndarray:
|
|
1155
|
+
"""
|
|
1156
|
+
Return interpolated efficiency values for each event.
|
|
1157
|
+
|
|
1158
|
+
Parameters
|
|
1159
|
+
----------
|
|
1160
|
+
energy : NDArray
|
|
1161
|
+
Energy values for each event.
|
|
1162
|
+
phi_inst : NDArray
|
|
1163
|
+
Instrument-frame azimuth angle for each event.
|
|
1164
|
+
theta_inst : NDArray
|
|
1165
|
+
Instrument-frame elevation angle for each event.
|
|
1166
|
+
ancillary_files : dict
|
|
1167
|
+
Ancillary files.
|
|
1168
|
+
interpolator : RegularGridInterpolator, optional
|
|
1169
|
+
Precomputed interpolator to use for efficiency lookup.
|
|
1170
|
+
If None, a new interpolator will be created from the ancillary files.
|
|
1171
|
+
|
|
1172
|
+
Returns
|
|
1173
|
+
-------
|
|
1174
|
+
efficiency : NDArray
|
|
1175
|
+
Interpolated efficiency values.
|
|
1176
|
+
"""
|
|
1177
|
+
if not interpolator:
|
|
1178
|
+
interpolator = get_efficiency_interpolator(ancillary_files)
|
|
1179
|
+
|
|
984
1180
|
return interpolator((theta_inst, phi_inst, energy))
|
|
1181
|
+
|
|
1182
|
+
|
|
1183
|
+
def determine_ebin_pulse_height(
|
|
1184
|
+
energy: NDArray,
|
|
1185
|
+
tof: NDArray,
|
|
1186
|
+
path_length: NDArray,
|
|
1187
|
+
backtofvalid: NDArray,
|
|
1188
|
+
coinphvalid: NDArray,
|
|
1189
|
+
ancillary_files: dict,
|
|
1190
|
+
) -> NDArray:
|
|
1191
|
+
"""
|
|
1192
|
+
Determine the species for pulse-height events.
|
|
1193
|
+
|
|
1194
|
+
Species is determined from the particle energy and velocity.
|
|
1195
|
+
For velocity, the particle TOF is normalized with respect
|
|
1196
|
+
to a fixed distance dmin between the front and back detectors.
|
|
1197
|
+
The normalized TOF is termed the corrected TOF (ctof).
|
|
1198
|
+
Particle species are determined from
|
|
1199
|
+
the energy and ctof using a lookup table.
|
|
1200
|
+
|
|
1201
|
+
Further description is available on pages 42-44 of
|
|
1202
|
+
IMAP-Ultra Flight Software Specification document
|
|
1203
|
+
(7523-9009_Rev_-.pdf).
|
|
1204
|
+
|
|
1205
|
+
Parameters
|
|
1206
|
+
----------
|
|
1207
|
+
energy : NDArray
|
|
1208
|
+
Energy from the PH event (keV).
|
|
1209
|
+
tof : NDArray
|
|
1210
|
+
Time of flight of the PH event (tenths of a nanosecond).
|
|
1211
|
+
path_length : NDArray
|
|
1212
|
+
Path length (r) (hundredths of a millimeter).
|
|
1213
|
+
backtofvalid : NDArray
|
|
1214
|
+
Boolean array indicating if the back TOF is valid.
|
|
1215
|
+
coinphvalid : NDArray
|
|
1216
|
+
Boolean array indicating if the Coincidence PH is valid.
|
|
1217
|
+
ancillary_files : dict
|
|
1218
|
+
Ancillary files containing the lookup tables.
|
|
1219
|
+
|
|
1220
|
+
Returns
|
|
1221
|
+
-------
|
|
1222
|
+
bin : np.array
|
|
1223
|
+
Species bin.
|
|
1224
|
+
"""
|
|
1225
|
+
# PH event TOF normalization to Z axis
|
|
1226
|
+
ctof, _ = get_ctof(tof, path_length, type="PH")
|
|
1227
|
+
|
|
1228
|
+
ebins = np.full(path_length.shape, FILLVAL_UINT8, dtype=np.uint8)
|
|
1229
|
+
valid = backtofvalid & coinphvalid
|
|
1230
|
+
ebins[valid] = get_ebins(
|
|
1231
|
+
"l1b-tofxph", energy[valid], ctof[valid], ebins[valid], ancillary_files
|
|
1232
|
+
)
|
|
1233
|
+
|
|
1234
|
+
return ebins
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
def determine_ebin_ssd(
|
|
1238
|
+
energy: NDArray,
|
|
1239
|
+
tof: NDArray,
|
|
1240
|
+
path_length: NDArray,
|
|
1241
|
+
sensor: str,
|
|
1242
|
+
ancillary_files: dict,
|
|
1243
|
+
) -> NDArray:
|
|
1244
|
+
"""
|
|
1245
|
+
Determine the species for SSD events.
|
|
1246
|
+
|
|
1247
|
+
Species is determined from the particle's energy and velocity.
|
|
1248
|
+
For velocity, the particle's TOF is normalized with respect
|
|
1249
|
+
to a fixed distance dmin between the front and back detectors.
|
|
1250
|
+
For SSD events, an adjustment is also made to the path length
|
|
1251
|
+
to account for the shorter distances that such events
|
|
1252
|
+
travel to reach the detector. The normalized TOF is termed
|
|
1253
|
+
the corrected tof (ctof). Particle species are determined from
|
|
1254
|
+
the energy and cTOF using a lookup table.
|
|
1255
|
+
|
|
1256
|
+
Further description is available on pages 42-44 of
|
|
1257
|
+
IMAP-Ultra Flight Software Specification document
|
|
1258
|
+
(7523-9009_Rev_-.pdf).
|
|
1259
|
+
|
|
1260
|
+
Parameters
|
|
1261
|
+
----------
|
|
1262
|
+
energy : NDArray
|
|
1263
|
+
Energy from the SSD event (keV).
|
|
1264
|
+
tof : NDArray
|
|
1265
|
+
Time of flight of the SSD event (tenths of a nanosecond).
|
|
1266
|
+
path_length : NDArray
|
|
1267
|
+
Path length (r) (hundredths of a millimeter).
|
|
1268
|
+
sensor : str
|
|
1269
|
+
Sensor name: "ultra45" or "ultra90".
|
|
1270
|
+
ancillary_files : dict
|
|
1271
|
+
Ancillary files containing the lookup tables.
|
|
1272
|
+
|
|
1273
|
+
Returns
|
|
1274
|
+
-------
|
|
1275
|
+
bin : NDArray
|
|
1276
|
+
Species bin.
|
|
1277
|
+
"""
|
|
1278
|
+
# SSD event TOF normalization to Z axis
|
|
1279
|
+
ctof, _ = get_ctof(tof, path_length, type="SSD")
|
|
1280
|
+
|
|
1281
|
+
ebins = np.full(path_length.shape, FILLVAL_UINT8, dtype=np.uint8)
|
|
1282
|
+
steep_path_length = get_image_params("PathSteepThresh", sensor, ancillary_files)
|
|
1283
|
+
medium_path_length = get_image_params("PathMediumThresh", sensor, ancillary_files)
|
|
1284
|
+
|
|
1285
|
+
steep_mask = path_length < steep_path_length
|
|
1286
|
+
medium_mask = (path_length >= steep_path_length) & (
|
|
1287
|
+
path_length < medium_path_length
|
|
1288
|
+
)
|
|
1289
|
+
flat_mask = path_length >= medium_path_length
|
|
1290
|
+
|
|
1291
|
+
ebins[steep_mask] = get_ebins(
|
|
1292
|
+
f"l1b-{sensor[5::]}sensor-tofxesteep",
|
|
1293
|
+
energy[steep_mask],
|
|
1294
|
+
ctof[steep_mask],
|
|
1295
|
+
ebins[steep_mask],
|
|
1296
|
+
ancillary_files,
|
|
1297
|
+
)
|
|
1298
|
+
ebins[medium_mask] = get_ebins(
|
|
1299
|
+
f"l1b-{sensor[5::]}sensor-tofxemedium",
|
|
1300
|
+
energy[medium_mask],
|
|
1301
|
+
ctof[medium_mask],
|
|
1302
|
+
ebins[medium_mask],
|
|
1303
|
+
ancillary_files,
|
|
1304
|
+
)
|
|
1305
|
+
ebins[flat_mask] = get_ebins(
|
|
1306
|
+
f"l1b-{sensor[5::]}sensor-tofxeflat",
|
|
1307
|
+
energy[flat_mask],
|
|
1308
|
+
ctof[flat_mask],
|
|
1309
|
+
ebins[flat_mask],
|
|
1310
|
+
ancillary_files,
|
|
1311
|
+
)
|
|
1312
|
+
|
|
1313
|
+
return ebins
|
|
1314
|
+
|
|
1315
|
+
|
|
1316
|
+
def is_back_tof_valid(
|
|
1317
|
+
de_dataset: xarray.Dataset,
|
|
1318
|
+
xf: NDArray,
|
|
1319
|
+
sensor: str,
|
|
1320
|
+
ancillary_files: dict,
|
|
1321
|
+
) -> NDArray:
|
|
1322
|
+
"""
|
|
1323
|
+
Determine whether back TOF is valid based on stop type.
|
|
1324
|
+
|
|
1325
|
+
Parameters
|
|
1326
|
+
----------
|
|
1327
|
+
de_dataset : xarray.Dataset
|
|
1328
|
+
Data in xarray format.
|
|
1329
|
+
xf : NDArray
|
|
1330
|
+
X front position in (hundredths of a millimeter).
|
|
1331
|
+
Has same length as de_dataset.
|
|
1332
|
+
sensor : str
|
|
1333
|
+
Sensor name: "ultra45" or "ultra90".
|
|
1334
|
+
ancillary_files : dict
|
|
1335
|
+
Ancillary files for lookup.
|
|
1336
|
+
|
|
1337
|
+
Returns
|
|
1338
|
+
-------
|
|
1339
|
+
valid_mask : NDArray
|
|
1340
|
+
Boolean array indicating whether back TOF is valid.
|
|
1341
|
+
|
|
1342
|
+
Notes
|
|
1343
|
+
-----
|
|
1344
|
+
From page 33 of the IMAP-Ultra Flight Software Specification document.
|
|
1345
|
+
"""
|
|
1346
|
+
_, _, _, _, tofx, tofy = get_ph_tof_and_back_positions(
|
|
1347
|
+
de_dataset, xf, "ultra45", ancillary_files
|
|
1348
|
+
)
|
|
1349
|
+
diff = tofy - tofx
|
|
1350
|
+
|
|
1351
|
+
indices = np.nonzero(
|
|
1352
|
+
np.isin(de_dataset["stop_type"], [StopType.Top.value, StopType.Bottom.value])
|
|
1353
|
+
)[0]
|
|
1354
|
+
de_ph = de_dataset.isel(epoch=indices)
|
|
1355
|
+
|
|
1356
|
+
top_mask = de_ph["stop_type"] == StopType.Top.value
|
|
1357
|
+
bottom_mask = de_ph["stop_type"] == StopType.Bottom.value
|
|
1358
|
+
|
|
1359
|
+
valid = np.zeros_like(diff, dtype=bool)
|
|
1360
|
+
|
|
1361
|
+
diff_tp_min = get_image_params("TOFDiffTpMin", sensor, ancillary_files)
|
|
1362
|
+
diff_tp_max = get_image_params("TOFDiffTpMax", sensor, ancillary_files)
|
|
1363
|
+
diff_bt_min = get_image_params("TOFDiffBtMin", sensor, ancillary_files)
|
|
1364
|
+
diff_bt_max = get_image_params("TOFDiffBtMax", sensor, ancillary_files)
|
|
1365
|
+
|
|
1366
|
+
valid[top_mask] = (diff[top_mask] >= diff_tp_min) & (diff[top_mask] <= diff_tp_max)
|
|
1367
|
+
valid[bottom_mask] = (diff[bottom_mask] >= diff_bt_min) & (
|
|
1368
|
+
diff[bottom_mask] <= diff_bt_max
|
|
1369
|
+
)
|
|
1370
|
+
|
|
1371
|
+
return valid
|
|
1372
|
+
|
|
1373
|
+
|
|
1374
|
+
def is_coin_ph_valid(
|
|
1375
|
+
etof: NDArray,
|
|
1376
|
+
xc: NDArray,
|
|
1377
|
+
xb: NDArray,
|
|
1378
|
+
sensor: str,
|
|
1379
|
+
ancillary_files: dict,
|
|
1380
|
+
) -> NDArray:
|
|
1381
|
+
"""
|
|
1382
|
+
Determine whether Coincidence-PH data are valid.
|
|
1383
|
+
|
|
1384
|
+
This is based on thresholds defined in the IMAP-Ultra Flight Software Specification
|
|
1385
|
+
(see page 36).
|
|
1386
|
+
|
|
1387
|
+
Parameters
|
|
1388
|
+
----------
|
|
1389
|
+
etof : NDArray
|
|
1390
|
+
Electron TOF (tenths of a nanosecond).
|
|
1391
|
+
xc : NDArray
|
|
1392
|
+
Coincidence X position (hundredths of a mm).
|
|
1393
|
+
xb : NDArray
|
|
1394
|
+
Back X position (hundredths of a mm).
|
|
1395
|
+
sensor : str
|
|
1396
|
+
Sensor name: "ultra45" or "ultra90".
|
|
1397
|
+
ancillary_files : dict
|
|
1398
|
+
Ancillary files for lookup.
|
|
1399
|
+
|
|
1400
|
+
Returns
|
|
1401
|
+
-------
|
|
1402
|
+
valid_mask : NDArray
|
|
1403
|
+
Boolean array indicating Coin-PH validity.
|
|
1404
|
+
|
|
1405
|
+
Notes
|
|
1406
|
+
-----
|
|
1407
|
+
Logic derived from page 36 of the IMAP-Ultra Flight Software Specification document.
|
|
1408
|
+
"""
|
|
1409
|
+
etof_min = get_image_params("eTOFMin", sensor, ancillary_files)
|
|
1410
|
+
etof_max = get_image_params("eTOFMax", sensor, ancillary_files)
|
|
1411
|
+
|
|
1412
|
+
etof_valid = (etof >= etof_min) & (etof <= etof_max)
|
|
1413
|
+
|
|
1414
|
+
diff_x = xc - xb
|
|
1415
|
+
etof_offset1 = get_image_params("eTOFOff1", sensor, ancillary_files)
|
|
1416
|
+
etof_offset2 = get_image_params("eTOFOff2", sensor, ancillary_files)
|
|
1417
|
+
etof_slope1 = get_image_params("eTOFSlope1", sensor, ancillary_files)
|
|
1418
|
+
etof_slope2 = get_image_params("eTOFSlope2", sensor, ancillary_files)
|
|
1419
|
+
|
|
1420
|
+
t1 = (etof - etof_offset1) * etof_slope1 / 1024
|
|
1421
|
+
t2 = (etof - etof_offset2) * etof_slope2 / 1024
|
|
1422
|
+
|
|
1423
|
+
condition_1 = (diff_x >= t1) & (diff_x <= t2)
|
|
1424
|
+
condition_2 = (diff_x >= -t2) & (diff_x <= -t1)
|
|
1425
|
+
|
|
1426
|
+
spatial_valid = condition_1 | condition_2
|
|
1427
|
+
|
|
1428
|
+
return etof_valid & spatial_valid
|