imap-processing 0.16.2__py3-none-any.whl → 0.18.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of imap-processing might be problematic. Click here for more details.

Files changed (110) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/ccsds/excel_to_xtce.py +12 -0
  3. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -6
  4. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +35 -0
  5. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +35 -0
  6. imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +24 -0
  7. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +8 -8
  8. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +1 -1
  9. imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +163 -100
  10. imap_processing/cdf/config/imap_hit_l2_variable_attrs.yaml +398 -415
  11. imap_processing/cdf/config/imap_ialirt_l1_variable_attrs.yaml +97 -54
  12. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +9 -9
  13. imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +233 -57
  14. imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +16 -90
  15. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +30 -0
  16. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +15 -1
  17. imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +19 -0
  18. imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +20 -0
  19. imap_processing/cdf/config/imap_swe_l2_variable_attrs.yaml +39 -0
  20. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +168 -0
  21. imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +103 -2
  22. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +91 -11
  23. imap_processing/cdf/utils.py +7 -1
  24. imap_processing/cli.py +42 -13
  25. imap_processing/codice/codice_l1a.py +125 -78
  26. imap_processing/codice/codice_l1b.py +1 -1
  27. imap_processing/codice/codice_l2.py +0 -9
  28. imap_processing/codice/constants.py +481 -498
  29. imap_processing/hi/hi_l1a.py +4 -4
  30. imap_processing/hi/hi_l1b.py +2 -2
  31. imap_processing/hi/packet_definitions/TLM_HI_COMBINED_SCI.xml +218 -38
  32. imap_processing/hit/hit_utils.py +2 -2
  33. imap_processing/hit/l0/decom_hit.py +4 -3
  34. imap_processing/hit/l1a/hit_l1a.py +64 -24
  35. imap_processing/hit/l1b/constants.py +5 -0
  36. imap_processing/hit/l1b/hit_l1b.py +18 -16
  37. imap_processing/hit/l2/constants.py +1 -1
  38. imap_processing/hit/l2/hit_l2.py +4 -4
  39. imap_processing/ialirt/constants.py +21 -0
  40. imap_processing/ialirt/generate_coverage.py +188 -0
  41. imap_processing/ialirt/l0/parse_mag.py +62 -5
  42. imap_processing/ialirt/l0/process_swapi.py +1 -1
  43. imap_processing/ialirt/l0/process_swe.py +23 -7
  44. imap_processing/ialirt/utils/constants.py +22 -16
  45. imap_processing/ialirt/utils/create_xarray.py +42 -19
  46. imap_processing/idex/idex_constants.py +8 -5
  47. imap_processing/idex/idex_l2b.py +554 -58
  48. imap_processing/idex/idex_l2c.py +30 -196
  49. imap_processing/lo/l0/lo_apid.py +1 -0
  50. imap_processing/lo/l0/lo_star_sensor.py +48 -0
  51. imap_processing/lo/l1a/lo_l1a.py +74 -30
  52. imap_processing/lo/packet_definitions/lo_xtce.xml +5359 -106
  53. imap_processing/mag/constants.py +1 -0
  54. imap_processing/mag/l0/decom_mag.py +9 -6
  55. imap_processing/mag/l0/mag_l0_data.py +46 -0
  56. imap_processing/mag/l1d/__init__.py +0 -0
  57. imap_processing/mag/l1d/mag_l1d.py +133 -0
  58. imap_processing/mag/l1d/mag_l1d_data.py +588 -0
  59. imap_processing/mag/l2/__init__.py +0 -0
  60. imap_processing/mag/l2/mag_l2.py +25 -20
  61. imap_processing/mag/l2/mag_l2_data.py +191 -130
  62. imap_processing/quality_flags.py +20 -2
  63. imap_processing/spice/geometry.py +25 -3
  64. imap_processing/spice/pointing_frame.py +1 -1
  65. imap_processing/spice/spin.py +4 -0
  66. imap_processing/spice/time.py +51 -0
  67. imap_processing/swapi/l1/swapi_l1.py +12 -2
  68. imap_processing/swapi/l2/swapi_l2.py +59 -14
  69. imap_processing/swapi/swapi_utils.py +1 -1
  70. imap_processing/swe/l1b/swe_l1b.py +11 -4
  71. imap_processing/swe/l2/swe_l2.py +111 -17
  72. imap_processing/ultra/constants.py +49 -1
  73. imap_processing/ultra/l0/decom_tools.py +28 -14
  74. imap_processing/ultra/l0/decom_ultra.py +225 -15
  75. imap_processing/ultra/l0/ultra_utils.py +281 -8
  76. imap_processing/ultra/l1a/ultra_l1a.py +77 -8
  77. imap_processing/ultra/l1b/cullingmask.py +3 -3
  78. imap_processing/ultra/l1b/de.py +53 -15
  79. imap_processing/ultra/l1b/extendedspin.py +26 -2
  80. imap_processing/ultra/l1b/lookup_utils.py +171 -50
  81. imap_processing/ultra/l1b/quality_flag_filters.py +14 -0
  82. imap_processing/ultra/l1b/ultra_l1b_culling.py +198 -5
  83. imap_processing/ultra/l1b/ultra_l1b_extended.py +304 -66
  84. imap_processing/ultra/l1c/helio_pset.py +54 -7
  85. imap_processing/ultra/l1c/spacecraft_pset.py +9 -1
  86. imap_processing/ultra/l1c/ultra_l1c.py +2 -0
  87. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +106 -109
  88. imap_processing/ultra/packet_definitions/ULTRA_SCI_COMBINED.xml +3 -3
  89. imap_processing/ultra/utils/ultra_l1_utils.py +13 -1
  90. imap_processing/utils.py +20 -42
  91. {imap_processing-0.16.2.dist-info → imap_processing-0.18.0.dist-info}/METADATA +2 -2
  92. {imap_processing-0.16.2.dist-info → imap_processing-0.18.0.dist-info}/RECORD +95 -103
  93. imap_processing/lo/l0/data_classes/star_sensor.py +0 -98
  94. imap_processing/lo/l0/utils/lo_base.py +0 -57
  95. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +0 -526
  96. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +0 -526
  97. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +0 -526
  98. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +0 -524
  99. imap_processing/ultra/lookup_tables/EgyNorm.mem.csv +0 -32769
  100. imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
  101. imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
  102. imap_processing/ultra/lookup_tables/dps_grid45_compressed.cdf +0 -0
  103. imap_processing/ultra/lookup_tables/ultra45_back-pos-luts.csv +0 -4097
  104. imap_processing/ultra/lookup_tables/ultra45_tdc_norm.csv +0 -2050
  105. imap_processing/ultra/lookup_tables/ultra90_back-pos-luts.csv +0 -4097
  106. imap_processing/ultra/lookup_tables/ultra90_tdc_norm.csv +0 -2050
  107. imap_processing/ultra/lookup_tables/yadjust.csv +0 -257
  108. {imap_processing-0.16.2.dist-info → imap_processing-0.18.0.dist-info}/LICENSE +0 -0
  109. {imap_processing-0.16.2.dist-info → imap_processing-0.18.0.dist-info}/WHEEL +0 -0
  110. {imap_processing-0.16.2.dist-info → imap_processing-0.18.0.dist-info}/entry_points.txt +0 -0
@@ -4,12 +4,16 @@ import numpy as np
4
4
  import xarray as xr
5
5
 
6
6
  from imap_processing.cdf.utils import parse_filename_like
7
+ from imap_processing.quality_flags import ImapDEUltraFlags
7
8
  from imap_processing.spice.geometry import SpiceFrame
9
+ from imap_processing.ultra.l1b.lookup_utils import get_geometric_factor
8
10
  from imap_processing.ultra.l1b.ultra_l1b_annotated import (
9
11
  get_annotated_particle_velocity,
10
12
  )
11
13
  from imap_processing.ultra.l1b.ultra_l1b_extended import (
12
14
  StopType,
15
+ determine_ebin_pulse_height,
16
+ determine_ebin_ssd,
13
17
  determine_species,
14
18
  get_coincidence_positions,
15
19
  get_ctof,
@@ -25,6 +29,7 @@ from imap_processing.ultra.l1b.ultra_l1b_extended import (
25
29
  get_path_length,
26
30
  get_ph_tof_and_back_positions,
27
31
  get_phi_theta,
32
+ get_spin_number,
28
33
  get_ssd_back_position_and_tof_offset,
29
34
  get_ssd_tof,
30
35
  )
@@ -59,7 +64,10 @@ def calculate_de(
59
64
 
60
65
  # Define epoch and spin.
61
66
  de_dict["epoch"] = de_dataset["epoch"].data
62
- de_dict["spin"] = de_dataset["spin"].data
67
+ spin_number = get_spin_number(
68
+ de_dataset["shcoarse"].values, de_dataset["spin"].values
69
+ )
70
+ de_dict["spin"] = spin_number
63
71
 
64
72
  # Add already populated fields.
65
73
  keys = [
@@ -108,6 +116,7 @@ def calculate_de(
108
116
  ctof = np.full(len(de_dataset["epoch"]), FILLVAL_FLOAT32, dtype=np.float32)
109
117
  magnitude_v = np.full(len(de_dataset["epoch"]), FILLVAL_FLOAT32, dtype=np.float32)
110
118
  energy = np.full(len(de_dataset["epoch"]), FILLVAL_FLOAT32, dtype=np.float32)
119
+ e_bin = np.full(len(de_dataset["epoch"]), FILLVAL_UINT8, dtype=np.uint8)
111
120
  species_bin = np.full(len(de_dataset["epoch"]), FILLVAL_UINT8, dtype=np.uint8)
112
121
  t2 = np.full(len(de_dataset["epoch"]), FILLVAL_FLOAT32, dtype=np.float32)
113
122
  event_times = np.full(len(de_dataset["epoch"]), FILLVAL_FLOAT32, dtype=np.float32)
@@ -116,40 +125,44 @@ def calculate_de(
116
125
  sc_dps_velocity = np.full(shape, FILLVAL_FLOAT32, dtype=np.float32)
117
126
  helio_velocity = np.full(shape, FILLVAL_FLOAT32, dtype=np.float32)
118
127
  spin_starts = np.full(len(de_dataset["epoch"]), FILLVAL_FLOAT32, dtype=np.float64)
119
- spin_period_sec = np.full(
120
- len(de_dataset["epoch"]), FILLVAL_FLOAT32, dtype=np.float64
121
- )
128
+
122
129
  start_type = np.full(len(de_dataset["epoch"]), FILLVAL_UINT8, dtype=np.uint8)
130
+ quality_flags = np.full(
131
+ de_dataset["epoch"].shape, ImapDEUltraFlags.NONE.value, dtype=np.uint16
132
+ )
123
133
 
124
134
  xf[valid_indices] = get_front_x_position(
125
135
  de_dataset["start_type"].data[valid_indices],
126
136
  de_dataset["start_pos_tdc"].data[valid_indices],
127
137
  f"ultra{sensor}",
138
+ ancillary_files,
128
139
  )
129
140
  start_type[valid_indices] = de_dataset["start_type"].data[valid_indices]
130
141
 
131
142
  (
132
143
  event_times[valid_indices],
133
144
  spin_starts[valid_indices],
134
- spin_period_sec[valid_indices],
145
+ _,
135
146
  ) = get_eventtimes(
136
- de_dataset["spin"].data[valid_indices],
147
+ de_dict["spin"][valid_indices],
137
148
  de_dataset["phase_angle"].data[valid_indices],
138
149
  )
139
150
 
140
151
  # Pulse height
141
152
  tof[ph_indices], t2[ph_indices], xb[ph_indices], yb[ph_indices] = (
142
- get_ph_tof_and_back_positions(de_dataset, xf, f"ultra{sensor}")
153
+ get_ph_tof_and_back_positions(de_dataset, xf, f"ultra{sensor}", ancillary_files)
143
154
  )
144
155
  d[ph_indices], yf[ph_indices] = get_front_y_position(
145
- de_dataset["start_type"].data[ph_indices], yb[ph_indices]
156
+ de_dataset["start_type"].data[ph_indices], yb[ph_indices], ancillary_files
146
157
  )
147
- energy[ph_indices] = get_energy_pulse_height(
158
+ energy[ph_indices], _ = get_energy_pulse_height(
148
159
  de_dataset["stop_type"].data[ph_indices],
149
160
  de_dataset["energy_ph"].data[ph_indices],
150
161
  xb[ph_indices],
151
162
  yb[ph_indices],
152
163
  f"ultra{sensor}",
164
+ ancillary_files,
165
+ quality_flags[ph_indices],
153
166
  )
154
167
  r[ph_indices] = get_path_length(
155
168
  (xf[ph_indices], yf[ph_indices]),
@@ -161,26 +174,32 @@ def calculate_de(
161
174
  (xb[ph_indices], yb[ph_indices]),
162
175
  d[ph_indices],
163
176
  )
177
+ e_bin[ph_indices] = determine_ebin_pulse_height(
178
+ energy[ph_indices], tof[ph_indices], r[ph_indices]
179
+ )
164
180
  species_bin[ph_indices] = determine_species(tof[ph_indices], r[ph_indices], "PH")
165
181
  etof[ph_indices], xc[ph_indices] = get_coincidence_positions(
166
- de_dataset.isel(epoch=ph_indices), t2[ph_indices], f"ultra{sensor}"
182
+ de_dataset.isel(epoch=ph_indices),
183
+ t2[ph_indices],
184
+ f"ultra{sensor}",
185
+ ancillary_files,
167
186
  )
168
187
  ctof[ph_indices], magnitude_v[ph_indices] = get_ctof(
169
188
  tof[ph_indices], r[ph_indices], "PH"
170
189
  )
171
190
 
172
191
  # SSD
173
- tof[ssd_indices] = get_ssd_tof(de_dataset, xf, f"ultra{sensor}")
192
+ tof[ssd_indices] = get_ssd_tof(de_dataset, xf, f"ultra{sensor}", ancillary_files)
174
193
  yb[ssd_indices], _, ssd_number = get_ssd_back_position_and_tof_offset(
175
- de_dataset, f"ultra{sensor}"
194
+ de_dataset, f"ultra{sensor}", ancillary_files
176
195
  )
177
196
  xc[ssd_indices] = np.zeros(len(ssd_indices))
178
197
  xb[ssd_indices] = np.zeros(len(ssd_indices))
179
198
  etof[ssd_indices] = np.zeros(len(ssd_indices))
180
199
  d[ssd_indices], yf[ssd_indices] = get_front_y_position(
181
- de_dataset["start_type"].data[ssd_indices], yb[ssd_indices]
200
+ de_dataset["start_type"].data[ssd_indices], yb[ssd_indices], ancillary_files
182
201
  )
183
- energy[ssd_indices] = get_energy_ssd(de_dataset, ssd_number)
202
+ energy[ssd_indices] = get_energy_ssd(de_dataset, ssd_number, ancillary_files)
184
203
  r[ssd_indices] = get_path_length(
185
204
  (xf[ssd_indices], yf[ssd_indices]),
186
205
  (xb[ssd_indices], yb[ssd_indices]),
@@ -191,6 +210,9 @@ def calculate_de(
191
210
  (xb[ssd_indices], yb[ssd_indices]),
192
211
  d[ssd_indices],
193
212
  )
213
+ e_bin[ssd_indices] = determine_ebin_ssd(
214
+ energy[ssd_indices], tof[ssd_indices], r[ssd_indices]
215
+ )
194
216
  species_bin[ssd_indices] = determine_species(
195
217
  tof[ssd_indices], r[ssd_indices], "SSD"
196
218
  )
@@ -202,7 +224,6 @@ def calculate_de(
202
224
  de_dict["x_front"] = xf.astype(np.float32)
203
225
  de_dict["event_times"] = event_times
204
226
  de_dict["spin_starts"] = spin_starts
205
- de_dict["spin_period"] = spin_period_sec
206
227
  de_dict["y_front"] = yf
207
228
  de_dict["x_back"] = xb
208
229
  de_dict["y_back"] = yb
@@ -228,6 +249,7 @@ def calculate_de(
228
249
 
229
250
  de_dict["tof_energy"] = get_de_energy_kev(v, species_bin)
230
251
  de_dict["energy"] = energy
252
+ de_dict["ebin"] = e_bin
231
253
  de_dict["species"] = species_bin
232
254
 
233
255
  # Annotated Events.
@@ -261,10 +283,26 @@ def calculate_de(
261
283
  de_dict["tof_energy"],
262
284
  de_dict["phi"],
263
285
  de_dict["theta"],
286
+ ancillary_files,
264
287
  )
265
288
  de_dict["event_efficiency"] = get_efficiency(
266
289
  de_dict["tof_energy"], de_dict["phi"], de_dict["theta"], ancillary_files
267
290
  )
291
+ de_dict["geometric_factor_blades"] = get_geometric_factor(
292
+ ancillary_files,
293
+ "l1b-sensor-gf-blades",
294
+ de_dict["phi"],
295
+ de_dict["theta"],
296
+ quality_flags,
297
+ )
298
+ de_dict["geometric_factor_noblades"] = get_geometric_factor(
299
+ ancillary_files,
300
+ "l1b-sensor-gf-noblades",
301
+ de_dict["phi"],
302
+ de_dict["theta"],
303
+ quality_flags,
304
+ )
305
+ de_dict["quality_fov"] = quality_flags
268
306
 
269
307
  dataset = create_dataset(de_dict, name, "l1b")
270
308
 
@@ -5,11 +5,16 @@ import xarray as xr
5
5
 
6
6
  from imap_processing.ultra.l1b.ultra_l1b_culling import (
7
7
  flag_attitude,
8
- flag_spin,
8
+ flag_hk,
9
+ flag_imap_instruments,
10
+ flag_rates,
9
11
  get_energy_histogram,
12
+ get_pulses_per_spin,
10
13
  )
11
14
  from imap_processing.ultra.utils.ultra_l1_utils import create_dataset
12
15
 
16
+ FILLVAL_UINT16 = 65535
17
+
13
18
 
14
19
  def calculate_extendedspin(
15
20
  dict_datasets: dict[str, xr.Dataset],
@@ -34,10 +39,11 @@ def calculate_extendedspin(
34
39
  Dataset containing the data.
35
40
  """
36
41
  aux_dataset = dict_datasets[f"imap_ultra_l1a_{instrument_id}sensor-aux"]
42
+ rates_dataset = dict_datasets[f"imap_ultra_l1a_{instrument_id}sensor-rates"]
37
43
  de_dataset = dict_datasets[f"imap_ultra_l1b_{instrument_id}sensor-de"]
38
44
 
39
45
  extendedspin_dict = {}
40
- rates_qf, spin, energy_midpoints, n_sigma_per_energy = flag_spin(
46
+ rates_qf, spin, energy_midpoints, n_sigma_per_energy = flag_rates(
41
47
  de_dataset["spin"].values,
42
48
  de_dataset["energy"].values,
43
49
  )
@@ -47,12 +53,19 @@ def calculate_extendedspin(
47
53
  attitude_qf, spin_rates, spin_period, spin_starttime = flag_attitude(
48
54
  de_dataset["spin"].values, aux_dataset
49
55
  )
56
+ # TODO: We will add to this later
57
+ hk_qf = flag_hk(de_dataset["spin"].values)
58
+ inst_qf = flag_imap_instruments(de_dataset["spin"].values)
59
+
50
60
  # Get the first epoch for each spin.
51
61
  mask = xr.DataArray(np.isin(de_dataset["spin"], spin), dims="epoch")
52
62
  filtered_dataset = de_dataset.where(mask, drop=True)
53
63
  _, first_indices = np.unique(filtered_dataset["spin"].values, return_index=True)
54
64
  first_epochs = filtered_dataset["epoch"].values[first_indices]
55
65
 
66
+ # Get the number of pulses per spin.
67
+ start_per_spin, stop_per_spin, coin_per_spin = get_pulses_per_spin(rates_dataset)
68
+
56
69
  # These will be the coordinates.
57
70
  extendedspin_dict["epoch"] = first_epochs
58
71
  extendedspin_dict["spin_number"] = spin
@@ -63,8 +76,19 @@ def calculate_extendedspin(
63
76
  extendedspin_dict["spin_start_time"] = spin_starttime
64
77
  extendedspin_dict["spin_period"] = spin_period
65
78
  extendedspin_dict["spin_rate"] = spin_rates
79
+ extendedspin_dict["start_pulses_per_spin"] = start_per_spin
80
+ extendedspin_dict["stop_pulses_per_spin"] = stop_per_spin
81
+ extendedspin_dict["coin_pulses_per_spin"] = coin_per_spin
82
+ # TODO: this will be used to track rejected events in each
83
+ # spin based on quality flags in de l1b data.
84
+ extendedspin_dict["rejected_events_per_spin"] = np.full_like(
85
+ spin, FILLVAL_UINT16, dtype=np.uint16
86
+ )
87
+
66
88
  extendedspin_dict["quality_attitude"] = attitude_qf
67
89
  extendedspin_dict["quality_ena_rates"] = rates_qf
90
+ extendedspin_dict["quality_hk"] = hk_qf
91
+ extendedspin_dict["quality_instruments"] = inst_qf
68
92
 
69
93
  extendedspin_dataset = create_dataset(extendedspin_dict, name, "l1b")
70
94
 
@@ -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 imap_module_directory
9
-
10
- BASE_PATH = imap_module_directory / "ultra" / "lookup_tables"
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 ImapDEUltraFlags
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
- return _YADJUST_DF["dYAdj"].iloc[dy_lut].values
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(dn: xr.DataArray, key: str, file_label: str) -> npt.NDArray:
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 = _TDC_NORM_DF_ULTRA45
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 = _TDC_NORM_DF_ULTRA90
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(back_index: np.ndarray, key: str, file_label: str) -> npt.NDArray:
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 = _BACK_POS_DF_ULTRA45
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 = _BACK_POS_DF_ULTRA90
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(ssd: np.ndarray, composite_energy: np.ndarray) -> npt.NDArray:
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
148
+ norm_lookup = pd.read_csv(ancillary_files["l1b-egynorm-lookup"])
149
+ return norm_lookup["NormEnergy"].iloc[row_number]
156
150
 
157
- return _ENERGY_NORM_DF["NormEnergy"].iloc[row_number]
158
151
 
159
-
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
- lookup_table = _IMAGE_PARAMS_DF[sensor]
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(start_type: str, sensor: str) -> pd.DataFrame:
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
- lookup_table = _FWHM_TABLES[(start_type.lower(), sensor)]
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,121 @@ 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 get_geometric_factor(
236
+ ancillary_files: dict,
237
+ filename: str,
238
+ phi: NDArray,
239
+ theta: NDArray,
240
+ quality_flag: NDArray,
241
+ ) -> tuple[NDArray, NDArray]:
242
+ """
243
+ Lookup table for geometric factor using nearest neighbor.
244
+
245
+ Parameters
246
+ ----------
247
+ ancillary_files : dict[Path]
248
+ Ancillary files.
249
+ filename : str
250
+ Name of the file in ancillary_files to use.
251
+ phi : NDArray
252
+ Azimuth angles in degrees.
253
+ theta : NDArray
254
+ Elevation angles in degrees.
255
+ quality_flag : NDArray
256
+ Quality flag to set when geometric factor is zero.
257
+
258
+ Returns
259
+ -------
260
+ geometric_factor : NDArray
261
+ Geometric factor.
262
+ """
263
+ gf_table = pd.read_csv(
264
+ ancillary_files[filename], header=None, skiprows=6, nrows=301
265
+ ).to_numpy(dtype=float)
266
+ theta_table = pd.read_csv(
267
+ ancillary_files[filename], header=None, skiprows=308, nrows=301
268
+ ).to_numpy(dtype=float)
269
+ phi_table = pd.read_csv(
270
+ ancillary_files[filename], header=None, skiprows=610, nrows=301
271
+ ).to_numpy(dtype=float)
272
+
273
+ # Assume uniform grids: extract 1D arrays from first row/col
274
+ theta_vals = theta_table[0, :] # columns represent theta
275
+ phi_vals = phi_table[:, 0] # rows represent phi
276
+
277
+ # Find nearest index in table for each input value
278
+ phi_idx = np.abs(phi_vals[:, None] - phi).argmin(axis=0)
279
+ theta_idx = np.abs(theta_vals[:, None] - theta).argmin(axis=0)
280
+
281
+ # Fetch geometric factor values at nearest (phi, theta) pairs
282
+ geometric_factor = gf_table[phi_idx, theta_idx]
283
+
284
+ phi_rad = np.deg2rad(phi)
285
+ numerator = 5.0 * np.cos(phi_rad)
286
+ denominator = 1 + 2.80 * np.cos(phi_rad)
287
+ # Equation 19 in the Ultra Algorithm Document.
288
+ theta_nom = np.arctan(numerator / denominator)
289
+ theta_nom = np.rad2deg(theta_nom)
290
+
291
+ outside_fov = np.abs(theta) > theta_nom
292
+ quality_flag[outside_fov] |= ImapDEUltraFlags.FOV.value
293
+
294
+ return geometric_factor
295
+
296
+
297
+ def get_ph_corrected(
298
+ sensor: str,
299
+ location: str,
300
+ ancillary_files: dict,
301
+ xlut: NDArray,
302
+ ylut: NDArray,
303
+ quality_flag: NDArray,
304
+ ) -> tuple[NDArray, NDArray]:
305
+ """
306
+ PH correction for stop anodes, top and bottom.
307
+
308
+ Further description is available starting on
309
+ page 207 of the Ultra Flight Software Document.
310
+
311
+ Parameters
312
+ ----------
313
+ sensor : str
314
+ Sensor name: "ultra45" or "ultra90".
315
+ location : str
316
+ Location: "tp" or "bt".
317
+ ancillary_files : dict[Path]
318
+ Ancillary files.
319
+ xlut : NDArray
320
+ X lookup index for PH correction.
321
+ ylut : NDArray
322
+ Y lookup index for PH correction.
323
+ quality_flag : NDArray
324
+ Quality flag to set when there is an outlier.
325
+
326
+ Returns
327
+ -------
328
+ ph_correction : NDArray
329
+ Correction for pulse height.
330
+ quality_flag : NDArray
331
+ Quality flag updated with PH correction flags.
332
+ """
333
+ ph_correct = pd.read_csv(
334
+ ancillary_files[f"l1b-{sensor[-2:]}sensor-sp{location}phcorr"], header=None
335
+ )
336
+ ph_correct_array = ph_correct.to_numpy()
337
+
338
+ max_x, max_y = ph_correct_array.shape[0] - 1, ph_correct_array.shape[1] - 1
339
+
340
+ # Clamp indices to nearest valid value
341
+ xlut_clamped = np.clip(xlut.astype(int), 0, max_x)
342
+ ylut_clamped = np.clip(ylut.astype(int), 0, max_y)
343
+
344
+ # Flag where clamping occurred
345
+ flagged_mask = (xlut != xlut_clamped) | (ylut != ylut_clamped)
346
+ quality_flag[flagged_mask] |= ImapDEUltraFlags.PHCORR.value
347
+
348
+ ph_correction = ph_correct_array[xlut_clamped, ylut_clamped]
349
+
350
+ return ph_correction, quality_flag
@@ -0,0 +1,14 @@
1
+ """Contains list of QFs to use for filtering."""
2
+
3
+ from imap_processing.quality_flags import (
4
+ FlagNameMixin,
5
+ ImapRatesUltraFlags,
6
+ )
7
+
8
+ QUALITY_FLAG_FILTERS: dict[str, list[FlagNameMixin]] = {
9
+ "quality_attitude": [],
10
+ "quality_ena_rates": [
11
+ ImapRatesUltraFlags.FIRSTSPIN,
12
+ ImapRatesUltraFlags.LASTSPIN,
13
+ ],
14
+ }