imap-processing 0.19.0__py3-none-any.whl → 0.19.2__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (64) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -0
  3. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +31 -894
  4. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +279 -255
  5. imap_processing/cdf/config/imap_enamaps_l2-common_variable_attrs.yaml +11 -0
  6. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +3 -1
  7. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +5 -4
  8. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +20 -8
  9. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +33 -31
  10. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +61 -1
  11. imap_processing/cli.py +62 -71
  12. imap_processing/codice/codice_l0.py +2 -1
  13. imap_processing/codice/codice_l1a.py +47 -49
  14. imap_processing/codice/codice_l1b.py +42 -32
  15. imap_processing/codice/codice_l2.py +105 -7
  16. imap_processing/codice/constants.py +50 -8
  17. imap_processing/codice/data/lo_stepping_values.csv +1 -1
  18. imap_processing/ena_maps/ena_maps.py +39 -18
  19. imap_processing/ena_maps/utils/corrections.py +291 -0
  20. imap_processing/ena_maps/utils/map_utils.py +20 -4
  21. imap_processing/glows/l1b/glows_l1b.py +38 -23
  22. imap_processing/glows/l1b/glows_l1b_data.py +10 -11
  23. imap_processing/hi/hi_l1c.py +4 -109
  24. imap_processing/hi/hi_l2.py +34 -23
  25. imap_processing/hi/utils.py +109 -0
  26. imap_processing/ialirt/l0/ialirt_spice.py +1 -0
  27. imap_processing/ialirt/utils/create_xarray.py +1 -1
  28. imap_processing/lo/ancillary_data/imap_lo_hydrogen-geometric-factor_v001.csv +75 -0
  29. imap_processing/lo/ancillary_data/imap_lo_oxygen-geometric-factor_v001.csv +75 -0
  30. imap_processing/lo/l1b/lo_l1b.py +90 -16
  31. imap_processing/lo/l1c/lo_l1c.py +164 -50
  32. imap_processing/lo/l2/lo_l2.py +941 -127
  33. imap_processing/mag/l1d/mag_l1d_data.py +36 -3
  34. imap_processing/mag/l2/mag_l2.py +2 -0
  35. imap_processing/mag/l2/mag_l2_data.py +4 -3
  36. imap_processing/quality_flags.py +14 -0
  37. imap_processing/spice/geometry.py +15 -8
  38. imap_processing/spice/pointing_frame.py +4 -2
  39. imap_processing/spice/repoint.py +49 -0
  40. imap_processing/ultra/constants.py +29 -0
  41. imap_processing/ultra/l1b/badtimes.py +35 -11
  42. imap_processing/ultra/l1b/de.py +15 -9
  43. imap_processing/ultra/l1b/extendedspin.py +24 -12
  44. imap_processing/ultra/l1b/goodtimes.py +112 -0
  45. imap_processing/ultra/l1b/lookup_utils.py +1 -1
  46. imap_processing/ultra/l1b/ultra_l1b.py +7 -7
  47. imap_processing/ultra/l1b/ultra_l1b_culling.py +8 -4
  48. imap_processing/ultra/l1b/ultra_l1b_extended.py +79 -43
  49. imap_processing/ultra/l1c/helio_pset.py +68 -39
  50. imap_processing/ultra/l1c/l1c_lookup_utils.py +45 -12
  51. imap_processing/ultra/l1c/spacecraft_pset.py +81 -37
  52. imap_processing/ultra/l1c/ultra_l1c.py +27 -22
  53. imap_processing/ultra/l1c/ultra_l1c_culling.py +7 -0
  54. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +41 -41
  55. imap_processing/ultra/l2/ultra_l2.py +54 -10
  56. imap_processing/ultra/utils/ultra_l1_utils.py +10 -5
  57. {imap_processing-0.19.0.dist-info → imap_processing-0.19.2.dist-info}/METADATA +1 -1
  58. {imap_processing-0.19.0.dist-info → imap_processing-0.19.2.dist-info}/RECORD +62 -60
  59. imap_processing/ultra/l1b/cullingmask.py +0 -90
  60. imap_processing/ultra/l1c/histogram.py +0 -36
  61. /imap_processing/glows/ancillary/{imap_glows_pipeline_settings_20250923_v002.json → imap_glows_pipeline-settings_20250923_v002.json} +0 -0
  62. {imap_processing-0.19.0.dist-info → imap_processing-0.19.2.dist-info}/LICENSE +0 -0
  63. {imap_processing-0.19.0.dist-info → imap_processing-0.19.2.dist-info}/WHEEL +0 -0
  64. {imap_processing-0.19.0.dist-info → imap_processing-0.19.2.dist-info}/entry_points.txt +0 -0
@@ -9,6 +9,7 @@ import numpy as np
9
9
  import xarray as xr
10
10
 
11
11
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
12
+ from imap_processing.lo import lo_ancillary
12
13
  from imap_processing.lo.l1b.tof_conversions import (
13
14
  TOF0_CONV,
14
15
  TOF1_CONV,
@@ -16,20 +17,24 @@ from imap_processing.lo.l1b.tof_conversions import (
16
17
  TOF3_CONV,
17
18
  )
18
19
  from imap_processing.spice.geometry import SpiceFrame, instrument_pointing
20
+ from imap_processing.spice.repoint import get_pointing_times
21
+ from imap_processing.spice.spin import get_spin_number
19
22
  from imap_processing.spice.time import met_to_ttj2000ns, ttj2000ns_to_et
20
23
 
21
24
  logger = logging.getLogger(__name__)
22
25
  logger.setLevel(logging.INFO)
23
26
 
24
27
 
25
- def lo_l1b(dependencies: dict) -> list[Path]:
28
+ def lo_l1b(sci_dependencies: dict, anc_dependencies: list) -> list[Path]:
26
29
  """
27
30
  Will process IMAP-Lo L1A data into L1B CDF data products.
28
31
 
29
32
  Parameters
30
33
  ----------
31
- dependencies : dict
34
+ sci_dependencies : dict
32
35
  Dictionary of datasets needed for L1B data product creation in xarray Datasets.
36
+ anc_dependencies : list
37
+ List of ancillary file paths needed for L1B data product creation.
33
38
 
34
39
  Returns
35
40
  -------
@@ -43,17 +48,20 @@ def lo_l1b(dependencies: dict) -> list[Path]:
43
48
  # create the attribute manager to access L1A fillval attributes
44
49
  attr_mgr_l1a = ImapCdfAttributes()
45
50
  attr_mgr_l1a.add_instrument_variable_attrs(instrument="lo", level="l1a")
46
- logger.info(f"\n Dependencies: {list(dependencies.keys())}\n")
51
+ logger.info(f"\n Dependencies: {list(sci_dependencies.keys())}\n")
47
52
  # if the dependencies are used to create Annotated Direct Events
48
- if "imap_lo_l1a_de" in dependencies and "imap_lo_l1a_spin" in dependencies:
53
+ if "imap_lo_l1a_de" in sci_dependencies and "imap_lo_l1a_spin" in sci_dependencies:
49
54
  logger.info("\nProcessing IMAP-Lo L1B Direct Events...")
50
55
  logical_source = "imap_lo_l1b_de"
51
56
  # get the dependency dataset for l1b direct events
52
- l1a_de = dependencies["imap_lo_l1a_de"]
53
- spin_data = dependencies["imap_lo_l1a_spin"]
57
+ l1a_de = sci_dependencies["imap_lo_l1a_de"]
58
+ spin_data = sci_dependencies["imap_lo_l1a_spin"]
54
59
 
55
60
  # Initialize the L1B DE dataset
56
61
  l1b_de = initialize_l1b_de(l1a_de, attr_mgr_l1b, logical_source)
62
+ pointing_start_met, pointing_end_met = get_pointing_times(
63
+ l1a_de["met"].values[0].item()
64
+ )
57
65
  # Get the start and end times for each spin epoch
58
66
  acq_start, acq_end = convert_start_end_acq_times(spin_data)
59
67
  # Get the average spin durations for each epoch
@@ -66,7 +74,7 @@ def lo_l1b(dependencies: dict) -> list[Path]:
66
74
  # spin bins are 0 - 60 bins
67
75
  l1b_de = set_spin_bin(l1b_de, spin_angle)
68
76
  # set the spin cycle for each direct event
69
- l1b_de = set_spin_cycle(l1a_de, l1b_de)
77
+ l1b_de = set_spin_cycle(pointing_start_met, l1a_de, l1b_de)
70
78
  # get spin start times for each event
71
79
  spin_start_time = get_spin_start_times(l1a_de, l1b_de, spin_data, acq_end)
72
80
  # get the absolute met for each event
@@ -75,6 +83,10 @@ def lo_l1b(dependencies: dict) -> list[Path]:
75
83
  )
76
84
  # set the epoch for each event
77
85
  l1b_de = set_each_event_epoch(l1b_de)
86
+ # Set the ESA mode for each direct event
87
+ l1b_de = set_esa_mode(
88
+ pointing_start_met, pointing_end_met, anc_dependencies, l1b_de
89
+ )
78
90
  # Set the average spin duration for each direct event
79
91
  l1b_de = set_avg_spin_durations_per_event(
80
92
  l1a_de, l1b_de, avg_spin_durations_per_cycle
@@ -133,7 +145,7 @@ def initialize_l1b_de(
133
145
  # TODO: Add pos to YAML file
134
146
  # attrs=attr_mgr.get_variable_attributes("pos"),
135
147
  )
136
- l1b_de["mode"] = xr.DataArray(
148
+ l1b_de["mode_bit"] = xr.DataArray(
137
149
  l1a_de["mode"].values,
138
150
  dims=["epoch"],
139
151
  # TODO: Add mode to YAML file
@@ -155,6 +167,65 @@ def initialize_l1b_de(
155
167
  return l1b_de
156
168
 
157
169
 
170
+ def set_esa_mode(
171
+ pointing_start_met: float,
172
+ pointing_end_met: float,
173
+ anc_dependencies: list,
174
+ l1b_de: xr.Dataset,
175
+ ) -> xr.Dataset:
176
+ """
177
+ Set the ESA mode for each direct event.
178
+
179
+ The ESA mode is determined from the sweep table for the time period of the pointing.
180
+
181
+ Parameters
182
+ ----------
183
+ pointing_start_met : float
184
+ Start time for the pointing in MET seconds.
185
+ pointing_end_met : float
186
+ End time for the pointing in MET seconds.
187
+ anc_dependencies : list
188
+ List of ancillary file paths.
189
+ l1b_de : xarray.Dataset
190
+ The L1B DE dataset.
191
+
192
+ Returns
193
+ -------
194
+ l1b_de : xr.Dataset
195
+ The L1B DE dataset with the ESA mode added.
196
+ """
197
+ # Read the sweep table from the ancillary files
198
+ sweep_df = lo_ancillary.read_ancillary_file(
199
+ next(str(s) for s in anc_dependencies if "sweep-table" in str(s))
200
+ )
201
+
202
+ # Get the sweep table rows that correspond to the time period of the pointing
203
+ pointing_sweep_df = sweep_df[
204
+ (sweep_df["GoodTime_start"] >= pointing_start_met)
205
+ & (sweep_df["GoodTime_start"] <= pointing_end_met)
206
+ ]
207
+
208
+ # Check that there is only one ESA mode in the sweep table for the pointing
209
+ if len(pointing_sweep_df["ESA_Mode"].unique()) == 1:
210
+ # Update the ESA mode strings to be 0 for HiRes and 1 for HiThr
211
+ sweep_df["esa_mode"] = sweep_df["ESA_Mode"].map({"HiRes": 0, "HiThr": 1})
212
+ # Get the ESA mode for the pointing
213
+ esa_mode = sweep_df["esa_mode"].values[0]
214
+ # Repeat the ESA mode for each direct event in the pointing
215
+ esa_mode_array = np.repeat(esa_mode, len(l1b_de["epoch"]))
216
+ else:
217
+ raise ValueError("Multiple ESA modes found in sweep table for pointing.")
218
+
219
+ l1b_de["esa_mode"] = xr.DataArray(
220
+ esa_mode_array,
221
+ dims=["epoch"],
222
+ # TODO: Add esa_mode to YAML file
223
+ # attrs=attr_mgr.get_variable_attributes("esa_mode"),
224
+ )
225
+
226
+ return l1b_de
227
+
228
+
158
229
  def convert_start_end_acq_times(
159
230
  spin_data: xr.Dataset,
160
231
  ) -> tuple[xr.DataArray, xr.DataArray]:
@@ -252,7 +323,9 @@ def set_spin_bin(l1b_de: xr.Dataset, spin_angle: np.ndarray) -> xr.Dataset:
252
323
  return l1b_de
253
324
 
254
325
 
255
- def set_spin_cycle(l1a_de: xr.Dataset, l1b_de: xr.Dataset) -> xr.Dataset:
326
+ def set_spin_cycle(
327
+ pointing_start_met: float, l1a_de: xr.Dataset, l1b_de: xr.Dataset
328
+ ) -> xr.Dataset:
256
329
  """
257
330
  Set the spin cycle for each direct event.
258
331
 
@@ -265,6 +338,8 @@ def set_spin_cycle(l1a_de: xr.Dataset, l1b_de: xr.Dataset) -> xr.Dataset:
265
338
 
266
339
  Parameters
267
340
  ----------
341
+ pointing_start_met : float
342
+ The start time of the pointing in MET seconds.
268
343
  l1a_de : xarray.Dataset
269
344
  The L1A DE dataset.
270
345
  l1b_de : xarray.Dataset
@@ -275,19 +350,18 @@ def set_spin_cycle(l1a_de: xr.Dataset, l1b_de: xr.Dataset) -> xr.Dataset:
275
350
  l1b_de : xarray.Dataset
276
351
  The L1B DE dataset with the spin cycle added for each direct event.
277
352
  """
353
+ spin_start_num = get_spin_number(pointing_start_met)
278
354
  counts = l1a_de["de_count"].values
279
355
  # split the esa_steps into ASC groups
280
356
  de_asc_groups = np.split(l1a_de["esa_step"].values, np.cumsum(counts)[:-1])
281
357
  spin_cycle = []
282
- for i, esa_asc_group in enumerate(de_asc_groups):
283
- # TODO: Spin Number does not reset for each pointing. Need to figure out
284
- # how to retain this information across days
285
- # increment the spin_start by 28 after each aggregated science cycle
286
- spin_start = i * 28
358
+ for esa_asc_group in de_asc_groups:
287
359
  # calculate the spin cycle for each DE in the ASC group
288
360
  # TODO: Add equation number in algorithm document when new version is
289
- # available. Add to docstring as well
290
- spin_cycle.extend(spin_start + 7 + (esa_asc_group - 1) * 2)
361
+ # available. Add to docstring as well
362
+ spin_cycle.extend(spin_start_num + 7 + (esa_asc_group - 1) * 2)
363
+ # increment the spin start number by 28 for the next ASC
364
+ spin_start_num += 28
291
365
 
292
366
  l1b_de["spin_cycle"] = xr.DataArray(
293
367
  spin_cycle,
@@ -1,5 +1,6 @@
1
1
  """IMAP-Lo L1C Data Processing."""
2
2
 
3
+ import logging
3
4
  from dataclasses import Field
4
5
  from enum import Enum
5
6
 
@@ -67,14 +68,36 @@ def lo_l1c(sci_dependencies: dict, anc_dependencies: list) -> list[xr.Dataset]:
67
68
  logical_source = "imap_lo_l1c_pset"
68
69
  l1b_de = sci_dependencies["imap_lo_l1b_de"]
69
70
  l1b_goodtimes_only = filter_goodtimes(l1b_de, anc_dependencies)
70
- pset = initialize_pset(l1b_goodtimes_only, attr_mgr, logical_source)
71
- full_counts = create_pset_counts(l1b_goodtimes_only)
72
71
 
73
72
  # Set the pointing start and end times based on the first epoch
74
73
  pointing_start_met, pointing_end_met = get_pointing_times(
75
74
  ttj2000ns_to_met(l1b_goodtimes_only["epoch"][0].item())
76
75
  )
77
76
 
77
+ pset = xr.Dataset(
78
+ coords={"epoch": np.array([met_to_ttj2000ns(pointing_start_met)])},
79
+ attrs=attr_mgr.get_global_attributes(logical_source),
80
+ )
81
+
82
+ # ESA mode needs to be added to L1B DE. Adding try statement
83
+ # to avoid error until it's available in the dataset
84
+ if "esa_mode" not in l1b_de:
85
+ logging.debug(
86
+ "ESA mode not found in L1B DE dataset. \
87
+ Setting to default value of 0 for Hi-Res."
88
+ )
89
+ pset["esa_mode"] = xr.DataArray(
90
+ np.array([0]),
91
+ dims=["epoch"],
92
+ attrs=attr_mgr.get_variable_attributes("esa_mode"),
93
+ )
94
+ else:
95
+ pset["esa_mode"] = xr.DataArray(
96
+ np.array([l1b_de["esa_mode"].values[0]]),
97
+ dims=["epoch"],
98
+ attrs=attr_mgr.get_variable_attributes("esa_mode"),
99
+ )
100
+
78
101
  pset["pointing_start_met"] = xr.DataArray(
79
102
  np.array([pointing_start_met]),
80
103
  dims="epoch",
@@ -86,12 +109,6 @@ def lo_l1c(sci_dependencies: dict, anc_dependencies: list) -> list[xr.Dataset]:
86
109
  attrs=attr_mgr.get_variable_attributes("pointing_end_met"),
87
110
  )
88
111
 
89
- # Set the epoch to the start of the pointing
90
- pset["epoch"] = xr.DataArray(
91
- met_to_ttj2000ns(pset["pointing_start_met"].values),
92
- attrs=attr_mgr.get_variable_attributes("epoch"),
93
- )
94
-
95
112
  # Get the start and end spin numbers based on the pointing start and end MET
96
113
  pset["start_spin_number"] = xr.DataArray(
97
114
  [get_spin_number(pset["pointing_start_met"].item())],
@@ -104,6 +121,8 @@ def lo_l1c(sci_dependencies: dict, anc_dependencies: list) -> list[xr.Dataset]:
104
121
  attrs=attr_mgr.get_variable_attributes("end_spin_number"),
105
122
  )
106
123
 
124
+ full_counts = create_pset_counts(l1b_de, FilterType.NONE)
125
+
107
126
  # Set the counts
108
127
  pset["triples_counts"] = create_pset_counts(
109
128
  l1b_goodtimes_only, FilterType.TRIPLES
@@ -118,6 +137,32 @@ def lo_l1c(sci_dependencies: dict, anc_dependencies: list) -> list[xr.Dataset]:
118
137
  pset["exposure_time"] = calculate_exposure_times(
119
138
  full_counts, l1b_goodtimes_only
120
139
  )
140
+
141
+ # Set backgrounds
142
+ (
143
+ pset["h_background_rates"],
144
+ pset["h_background_rates_stat_uncert"],
145
+ pset["h_background_rates_sys_err"],
146
+ ) = set_background_rates(
147
+ pset["pointing_start_met"].item(),
148
+ pset["pointing_end_met"].item(),
149
+ FilterType.HYDROGEN,
150
+ anc_dependencies,
151
+ attr_mgr,
152
+ )
153
+
154
+ (
155
+ pset["o_background_rates"],
156
+ pset["o_background_rates_stat_uncert"],
157
+ pset["o_background_rates_sys_err"],
158
+ ) = set_background_rates(
159
+ pset["pointing_start_met"].item(),
160
+ pset["pointing_end_met"].item(),
161
+ FilterType.OXYGEN,
162
+ anc_dependencies,
163
+ attr_mgr,
164
+ )
165
+
121
166
  pset.attrs = attr_mgr.get_global_attributes(logical_source)
122
167
 
123
168
  pset = pset.assign_coords(
@@ -131,44 +176,6 @@ def lo_l1c(sci_dependencies: dict, anc_dependencies: list) -> list[xr.Dataset]:
131
176
  return [pset]
132
177
 
133
178
 
134
- def initialize_pset(
135
- l1b_de: xr.Dataset, attr_mgr: ImapCdfAttributes, logical_source: str
136
- ) -> xr.Dataset:
137
- """
138
- Initialize the PSET dataset and set the Epoch.
139
-
140
- The Epoch time is set to the first of the L1B
141
- Direct Event times. There is one Epoch per PSET file.
142
-
143
- Parameters
144
- ----------
145
- l1b_de : xarray.Dataset
146
- L1B Direct Event dataset.
147
- attr_mgr : ImapCdfAttributes
148
- Attribute manager used to get the L1C attributes.
149
- logical_source : str
150
- The logical source of the pset.
151
-
152
- Returns
153
- -------
154
- pset : xarray.Dataset
155
- Initialized PSET dataset.
156
- """
157
- pset = xr.Dataset(
158
- attrs=attr_mgr.get_global_attributes(logical_source),
159
- )
160
- # TODO: Need to create utility to get start of repointing to use
161
- # for the pset epoch time. Setting to first DE for now
162
- pset_epoch = l1b_de["epoch"][0].item()
163
- pset["epoch"] = xr.DataArray(
164
- np.array([pset_epoch]),
165
- dims=["epoch"],
166
- attrs=attr_mgr.get_variable_attributes("epoch"),
167
- )
168
-
169
- return pset
170
-
171
-
172
179
  def filter_goodtimes(l1b_de: xr.Dataset, anc_dependencies: list) -> xr.Dataset:
173
180
  """
174
181
  Filter the L1B Direct Event dataset to only include good times.
@@ -189,10 +196,12 @@ def filter_goodtimes(l1b_de: xr.Dataset, anc_dependencies: list) -> xr.Dataset:
189
196
  Filtered L1B Direct Event dataset.
190
197
  """
191
198
  # the goodtimes are currently the only ancillary file needed for L1C processing
192
- goodtimes_table_df = lo_ancillary.read_ancillary_file(anc_dependencies[0])
199
+ goodtimes_table_df = lo_ancillary.read_ancillary_file(
200
+ next(str(s) for s in anc_dependencies if "good-times" in str(s))
201
+ )
193
202
 
194
203
  # convert goodtimes from MET to TTJ2000
195
- goodtimes_start = met_to_ttj2000ns(goodtimes_table_df["GoodTime_strt"])
204
+ goodtimes_start = met_to_ttj2000ns(goodtimes_table_df["GoodTime_start"])
196
205
  goodtimes_end = met_to_ttj2000ns(goodtimes_table_df["GoodTime_end"])
197
206
 
198
207
  # Create a mask for epochs within any of the start/end time ranges
@@ -254,9 +263,9 @@ def create_pset_counts(
254
263
  "001000",
255
264
  ],
256
265
  # hydrogen species identifier
257
- FilterType.HYDROGEN: "h",
266
+ FilterType.HYDROGEN: "H",
258
267
  # oxygen species identifier
259
- FilterType.OXYGEN: "o",
268
+ FilterType.OXYGEN: "O",
260
269
  }
261
270
 
262
271
  # if the filter string is triples or doubles, filter using the coincidence type
@@ -484,3 +493,108 @@ def create_datasets(
484
493
  )
485
494
 
486
495
  return dataset
496
+
497
+
498
+ def set_background_rates(
499
+ pointing_start_met: float,
500
+ pointing_end_met: float,
501
+ species: FilterType,
502
+ anc_dependencies: list,
503
+ attr_mgr: ImapCdfAttributes,
504
+ ) -> tuple[xr.DataArray, xr.DataArray, xr.DataArray]:
505
+ """
506
+ Set the background rates for the specified species.
507
+
508
+ The background rates are set to a constant value of 0.01 counts/s for all bins.
509
+
510
+ Parameters
511
+ ----------
512
+ pointing_start_met : float
513
+ The start MET time of the pointing.
514
+ pointing_end_met : float
515
+ The end MET time of the pointing.
516
+ species : FilterType
517
+ The species to set the background rates for. Can be "h" or "o".
518
+ anc_dependencies : list
519
+ Ancillary files needed for L1C data product creation.
520
+ attr_mgr : ImapCdfAttributes
521
+ Attribute manager used to get the L1C attributes.
522
+
523
+ Returns
524
+ -------
525
+ background_rates : tuple[xr.DataArray, xr.DataArray, xr.DataArray]
526
+ Tuple containing:
527
+ - The background rates for the specified species.
528
+ - The statistical uncertainties for the background rates.
529
+ - The systematic errors for the background rates.
530
+ """
531
+ if species not in {FilterType.HYDROGEN, FilterType.OXYGEN}:
532
+ raise ValueError(f"Species must be 'h' or 'o', but got {species.value}.")
533
+
534
+ bg_rates = np.zeros(
535
+ (N_ESA_ENERGY_STEPS, N_SPIN_ANGLE_BINS, N_OFF_ANGLE_BINS), dtype=np.float16
536
+ )
537
+ bg_stat_uncert = np.zeros(
538
+ (N_ESA_ENERGY_STEPS, N_SPIN_ANGLE_BINS, N_OFF_ANGLE_BINS), dtype=np.float16
539
+ )
540
+ bg_sys_err = np.zeros(
541
+ (N_ESA_ENERGY_STEPS, N_SPIN_ANGLE_BINS, N_OFF_ANGLE_BINS), dtype=np.float16
542
+ )
543
+
544
+ # read in the background rates from ancillary file
545
+ if species == FilterType.HYDROGEN:
546
+ background_df = lo_ancillary.read_ancillary_file(
547
+ next(str(s) for s in anc_dependencies if "hydrogen-background" in str(s))
548
+ )
549
+ else:
550
+ background_df = lo_ancillary.read_ancillary_file(
551
+ next(str(s) for s in anc_dependencies if "oxygen-background" in str(s))
552
+ )
553
+
554
+ # find to the rows for the current pointing
555
+ pointing_bg_df = background_df[
556
+ (background_df["GoodTime_strt"] >= pointing_start_met)
557
+ & (background_df["GoodTime_end"] <= pointing_end_met)
558
+ ]
559
+
560
+ # convert the bin start and end resolution from 6 degrees to .1 degrees
561
+ pointing_bg_df["bin_strt"] = pointing_bg_df["bin_strt"] * 60
562
+ # The last bin end in the file is 0, which means 60 degrees. This is
563
+ # converted to 0.1 degree resolution of 3600
564
+ pointing_bg_df["bin_end"] = pointing_bg_df["bin_end"] * 60
565
+ pointing_bg_df.loc[pointing_bg_df["bin_end"] == 0, "bin_end"] = 3600
566
+ # for each row in the bg ancillary file for this pointing
567
+ for _, row in pointing_bg_df.iterrows():
568
+ bin_start = int(row["bin_strt"])
569
+ bin_end = int(row["bin_end"])
570
+ # for each energy step, set the background rate and uncertainty
571
+ for esa_step in range(0, 7):
572
+ value = row[f"E-Step{esa_step + 1}"]
573
+ if row["type"] == "rate":
574
+ bg_rates[esa_step, bin_start:bin_end, :] = value
575
+ elif row["type"] == "sigma":
576
+ bg_stat_uncert[esa_step, bin_start:bin_end, :] = value
577
+ else:
578
+ raise ValueError("Unknown background type in ancillary file.")
579
+ # set the background rates, uncertainties, and systematic errors
580
+ bg_rates_data = xr.DataArray(
581
+ data=bg_rates,
582
+ dims=["esa_energy_step", "spin_angle", "off_angle"],
583
+ attrs=attr_mgr.get_variable_attributes(f"{species.value}_background_rates"),
584
+ )
585
+ bg_stat_uncert_data = xr.DataArray(
586
+ data=bg_stat_uncert,
587
+ dims=["esa_energy_step", "spin_angle", "off_angle"],
588
+ attrs=attr_mgr.get_variable_attributes(
589
+ f"{species.value}_background_rates_stat_uncert"
590
+ ),
591
+ )
592
+ bg_sys_err_data = xr.DataArray(
593
+ data=bg_sys_err,
594
+ dims=["esa_energy_step", "spin_angle", "off_angle"],
595
+ attrs=attr_mgr.get_variable_attributes(
596
+ f"{species.value}_background_rates_sys_err"
597
+ ),
598
+ )
599
+
600
+ return bg_rates_data, bg_stat_uncert_data, bg_sys_err_data