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

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

Potentially problematic release.


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

Files changed (122) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/ancillary/ancillary_dataset_combiner.py +161 -1
  3. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -0
  4. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +221 -1057
  5. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +307 -283
  6. imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +1044 -203
  7. imap_processing/cdf/config/imap_constant_attrs.yaml +4 -2
  8. imap_processing/cdf/config/imap_enamaps_l2-common_variable_attrs.yaml +11 -0
  9. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +15 -1
  10. imap_processing/cdf/config/imap_hi_global_cdf_attrs.yaml +5 -0
  11. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +10 -4
  12. imap_processing/cdf/config/imap_idex_l2a_variable_attrs.yaml +33 -4
  13. imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +8 -91
  14. imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +106 -16
  15. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +5 -4
  16. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +4 -15
  17. imap_processing/cdf/config/imap_lo_l1c_variable_attrs.yaml +189 -98
  18. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +85 -2
  19. imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +24 -1
  20. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +20 -8
  21. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +45 -35
  22. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +110 -7
  23. imap_processing/cli.py +138 -93
  24. imap_processing/codice/codice_l0.py +2 -1
  25. imap_processing/codice/codice_l1a.py +167 -69
  26. imap_processing/codice/codice_l1b.py +42 -32
  27. imap_processing/codice/codice_l2.py +215 -9
  28. imap_processing/codice/constants.py +790 -603
  29. imap_processing/codice/data/lo_stepping_values.csv +1 -1
  30. imap_processing/decom.py +1 -4
  31. imap_processing/ena_maps/ena_maps.py +71 -43
  32. imap_processing/ena_maps/utils/corrections.py +291 -0
  33. imap_processing/ena_maps/utils/map_utils.py +20 -4
  34. imap_processing/ena_maps/utils/naming.py +8 -2
  35. imap_processing/glows/ancillary/imap_glows_exclusions-by-instr-team_20250923_v002.dat +10 -0
  36. imap_processing/glows/ancillary/imap_glows_map-of-excluded-regions_20250923_v002.dat +393 -0
  37. imap_processing/glows/ancillary/imap_glows_map-of-uv-sources_20250923_v002.dat +593 -0
  38. imap_processing/glows/ancillary/imap_glows_pipeline-settings_20250923_v002.json +54 -0
  39. imap_processing/glows/ancillary/imap_glows_suspected-transients_20250923_v002.dat +10 -0
  40. imap_processing/glows/l1b/glows_l1b.py +123 -18
  41. imap_processing/glows/l1b/glows_l1b_data.py +358 -47
  42. imap_processing/glows/l2/glows_l2.py +11 -0
  43. imap_processing/hi/hi_l1a.py +124 -3
  44. imap_processing/hi/hi_l1b.py +154 -71
  45. imap_processing/hi/hi_l1c.py +4 -109
  46. imap_processing/hi/hi_l2.py +104 -60
  47. imap_processing/hi/utils.py +262 -8
  48. imap_processing/hit/l0/constants.py +3 -0
  49. imap_processing/hit/l0/decom_hit.py +3 -6
  50. imap_processing/hit/l1a/hit_l1a.py +311 -21
  51. imap_processing/hit/l1b/hit_l1b.py +54 -126
  52. imap_processing/hit/l2/hit_l2.py +6 -6
  53. imap_processing/ialirt/calculate_ingest.py +219 -0
  54. imap_processing/ialirt/constants.py +12 -2
  55. imap_processing/ialirt/generate_coverage.py +15 -2
  56. imap_processing/ialirt/l0/ialirt_spice.py +6 -2
  57. imap_processing/ialirt/l0/parse_mag.py +293 -42
  58. imap_processing/ialirt/l0/process_hit.py +5 -3
  59. imap_processing/ialirt/l0/process_swapi.py +41 -25
  60. imap_processing/ialirt/process_ephemeris.py +70 -14
  61. imap_processing/ialirt/utils/create_xarray.py +1 -1
  62. imap_processing/idex/idex_l0.py +2 -2
  63. imap_processing/idex/idex_l1a.py +2 -3
  64. imap_processing/idex/idex_l1b.py +2 -3
  65. imap_processing/idex/idex_l2a.py +130 -4
  66. imap_processing/idex/idex_l2b.py +158 -143
  67. imap_processing/idex/idex_utils.py +1 -3
  68. imap_processing/lo/ancillary_data/imap_lo_hydrogen-geometric-factor_v001.csv +75 -0
  69. imap_processing/lo/ancillary_data/imap_lo_oxygen-geometric-factor_v001.csv +75 -0
  70. imap_processing/lo/l0/lo_science.py +25 -24
  71. imap_processing/lo/l1b/lo_l1b.py +93 -19
  72. imap_processing/lo/l1c/lo_l1c.py +273 -93
  73. imap_processing/lo/l2/lo_l2.py +949 -135
  74. imap_processing/lo/lo_ancillary.py +55 -0
  75. imap_processing/mag/l1a/mag_l1a.py +1 -0
  76. imap_processing/mag/l1a/mag_l1a_data.py +26 -0
  77. imap_processing/mag/l1b/mag_l1b.py +3 -2
  78. imap_processing/mag/l1c/interpolation_methods.py +14 -15
  79. imap_processing/mag/l1c/mag_l1c.py +23 -6
  80. imap_processing/mag/l1d/mag_l1d.py +57 -14
  81. imap_processing/mag/l1d/mag_l1d_data.py +202 -32
  82. imap_processing/mag/l2/mag_l2.py +2 -0
  83. imap_processing/mag/l2/mag_l2_data.py +14 -5
  84. imap_processing/quality_flags.py +23 -1
  85. imap_processing/spice/geometry.py +89 -39
  86. imap_processing/spice/pointing_frame.py +4 -8
  87. imap_processing/spice/repoint.py +78 -2
  88. imap_processing/spice/spin.py +28 -8
  89. imap_processing/spice/time.py +12 -22
  90. imap_processing/swapi/l1/swapi_l1.py +10 -4
  91. imap_processing/swapi/l2/swapi_l2.py +15 -17
  92. imap_processing/swe/l1b/swe_l1b.py +1 -2
  93. imap_processing/ultra/constants.py +30 -24
  94. imap_processing/ultra/l0/ultra_utils.py +9 -11
  95. imap_processing/ultra/l1a/ultra_l1a.py +1 -2
  96. imap_processing/ultra/l1b/badtimes.py +35 -11
  97. imap_processing/ultra/l1b/de.py +95 -31
  98. imap_processing/ultra/l1b/extendedspin.py +31 -16
  99. imap_processing/ultra/l1b/goodtimes.py +112 -0
  100. imap_processing/ultra/l1b/lookup_utils.py +281 -28
  101. imap_processing/ultra/l1b/quality_flag_filters.py +10 -1
  102. imap_processing/ultra/l1b/ultra_l1b.py +7 -7
  103. imap_processing/ultra/l1b/ultra_l1b_culling.py +169 -7
  104. imap_processing/ultra/l1b/ultra_l1b_extended.py +311 -69
  105. imap_processing/ultra/l1c/helio_pset.py +139 -37
  106. imap_processing/ultra/l1c/l1c_lookup_utils.py +289 -0
  107. imap_processing/ultra/l1c/spacecraft_pset.py +140 -29
  108. imap_processing/ultra/l1c/ultra_l1c.py +33 -24
  109. imap_processing/ultra/l1c/ultra_l1c_culling.py +92 -0
  110. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +400 -292
  111. imap_processing/ultra/l2/ultra_l2.py +54 -11
  112. imap_processing/ultra/utils/ultra_l1_utils.py +37 -7
  113. imap_processing/utils.py +3 -4
  114. {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/METADATA +2 -2
  115. {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/RECORD +118 -109
  116. imap_processing/idex/idex_l2c.py +0 -84
  117. imap_processing/spice/kernels.py +0 -187
  118. imap_processing/ultra/l1b/cullingmask.py +0 -87
  119. imap_processing/ultra/l1c/histogram.py +0 -36
  120. {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/LICENSE +0 -0
  121. {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/WHEEL +0 -0
  122. {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/entry_points.txt +0 -0
@@ -1,15 +1,30 @@
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
 
6
7
  import numpy as np
7
- import pandas as pd
8
8
  import xarray as xr
9
9
  from scipy.stats import binned_statistic_dd
10
10
 
11
11
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
12
- from imap_processing.spice.time import met_to_ttj2000ns
12
+ from imap_processing.lo import lo_ancillary
13
+ from imap_processing.spice.repoint import get_pointing_times
14
+ from imap_processing.spice.spin import get_spin_number
15
+ from imap_processing.spice.time import met_to_ttj2000ns, ttj2000ns_to_met
16
+
17
+ N_ESA_ENERGY_STEPS = 7
18
+ N_SPIN_ANGLE_BINS = 3600
19
+ N_OFF_ANGLE_BINS = 40
20
+ # 1 time, 7 energy steps, 3600 spin angle bins, and 40 off angle bins
21
+ PSET_SHAPE = (1, N_ESA_ENERGY_STEPS, N_SPIN_ANGLE_BINS, N_OFF_ANGLE_BINS)
22
+ PSET_DIMS = ["epoch", "esa_energy_step", "spin_angle", "off_angle"]
23
+ ESA_ENERGY_STEPS = np.arange(N_ESA_ENERGY_STEPS) + 1 # 1 to 7 inclusive
24
+ SPIN_ANGLE_BIN_EDGES = np.linspace(0, 360, N_SPIN_ANGLE_BINS + 1)
25
+ SPIN_ANGLE_BIN_CENTERS = (SPIN_ANGLE_BIN_EDGES[:-1] + SPIN_ANGLE_BIN_EDGES[1:]) / 2
26
+ OFF_ANGLE_BIN_EDGES = np.linspace(-2, 2, N_OFF_ANGLE_BINS + 1)
27
+ OFF_ANGLE_BIN_CENTERS = (OFF_ANGLE_BIN_EDGES[:-1] + OFF_ANGLE_BIN_EDGES[1:]) / 2
13
28
 
14
29
 
15
30
  class FilterType(str, Enum):
@@ -52,10 +67,63 @@ def lo_l1c(sci_dependencies: dict, anc_dependencies: list) -> list[xr.Dataset]:
52
67
  if "imap_lo_l1b_de" in sci_dependencies:
53
68
  logical_source = "imap_lo_l1c_pset"
54
69
  l1b_de = sci_dependencies["imap_lo_l1b_de"]
55
-
56
70
  l1b_goodtimes_only = filter_goodtimes(l1b_de, anc_dependencies)
57
- pset = initialize_pset(l1b_goodtimes_only, attr_mgr, logical_source)
58
- full_counts = create_pset_counts(l1b_goodtimes_only)
71
+
72
+ # Set the pointing start and end times based on the first epoch
73
+ pointing_start_met, pointing_end_met = get_pointing_times(
74
+ ttj2000ns_to_met(l1b_goodtimes_only["epoch"][0].item())
75
+ )
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
+
101
+ pset["pointing_start_met"] = xr.DataArray(
102
+ np.array([pointing_start_met]),
103
+ dims="epoch",
104
+ attrs=attr_mgr.get_variable_attributes("pointing_start_met"),
105
+ )
106
+ pset["pointing_end_met"] = xr.DataArray(
107
+ np.array([pointing_end_met]),
108
+ dims="epoch",
109
+ attrs=attr_mgr.get_variable_attributes("pointing_end_met"),
110
+ )
111
+
112
+ # Get the start and end spin numbers based on the pointing start and end MET
113
+ pset["start_spin_number"] = xr.DataArray(
114
+ [get_spin_number(pset["pointing_start_met"].item())],
115
+ dims="epoch",
116
+ attrs=attr_mgr.get_variable_attributes("start_spin_number"),
117
+ )
118
+ pset["end_spin_number"] = xr.DataArray(
119
+ [get_spin_number(pset["pointing_end_met"].item())],
120
+ dims="epoch",
121
+ attrs=attr_mgr.get_variable_attributes("end_spin_number"),
122
+ )
123
+
124
+ full_counts = create_pset_counts(l1b_de, FilterType.NONE)
125
+
126
+ # Set the counts
59
127
  pset["triples_counts"] = create_pset_counts(
60
128
  l1b_goodtimes_only, FilterType.TRIPLES
61
129
  )
@@ -64,63 +132,50 @@ def lo_l1c(sci_dependencies: dict, anc_dependencies: list) -> list[xr.Dataset]:
64
132
  )
65
133
  pset["h_counts"] = create_pset_counts(l1b_goodtimes_only, FilterType.HYDROGEN)
66
134
  pset["o_counts"] = create_pset_counts(l1b_goodtimes_only, FilterType.OXYGEN)
135
+
136
+ # Set the exposure time
67
137
  pset["exposure_time"] = calculate_exposure_times(
68
138
  full_counts, l1b_goodtimes_only
69
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
+
70
166
  pset.attrs = attr_mgr.get_global_attributes(logical_source)
71
- # TODO: Temp fix before adding attribute variables.
72
- # CDF won't open if DEPEND_0 is not deleted currently.
73
- del pset["epoch"].attrs["DEPEND_0"]
74
167
 
75
168
  pset = pset.assign_coords(
76
169
  {
77
- "energy": np.arange(1, 8),
78
- "longitude": np.arange(3600),
79
- "latitude": np.arange(40),
170
+ "esa_energy_step": ESA_ENERGY_STEPS,
171
+ "spin_angle": SPIN_ANGLE_BIN_CENTERS,
172
+ "off_angle": OFF_ANGLE_BIN_CENTERS,
80
173
  }
81
174
  )
82
175
 
83
176
  return [pset]
84
177
 
85
178
 
86
- def initialize_pset(
87
- l1b_de: xr.Dataset, attr_mgr: ImapCdfAttributes, logical_source: str
88
- ) -> xr.Dataset:
89
- """
90
- Initialize the PSET dataset and set the Epoch.
91
-
92
- The Epoch time is set to the first of the L1B
93
- Direct Event times. There is one Epoch per PSET file.
94
-
95
- Parameters
96
- ----------
97
- l1b_de : xarray.Dataset
98
- L1B Direct Event dataset.
99
- attr_mgr : ImapCdfAttributes
100
- Attribute manager used to get the L1C attributes.
101
- logical_source : str
102
- The logical source of the pset.
103
-
104
- Returns
105
- -------
106
- pset : xarray.Dataset
107
- Initialized PSET dataset.
108
- """
109
- pset = xr.Dataset(
110
- attrs=attr_mgr.get_global_attributes(logical_source),
111
- )
112
- # TODO: Need to create utility to get start of repointing to use
113
- # for the pset epoch time. Setting to first DE for now
114
- pset_epoch = l1b_de["epoch"][0].item()
115
- pset["epoch"] = xr.DataArray(
116
- np.array([pset_epoch]),
117
- dims=["epoch"],
118
- attrs=attr_mgr.get_variable_attributes("epoch"),
119
- )
120
-
121
- return pset
122
-
123
-
124
179
  def filter_goodtimes(l1b_de: xr.Dataset, anc_dependencies: list) -> xr.Dataset:
125
180
  """
126
181
  Filter the L1B Direct Event dataset to only include good times.
@@ -141,17 +196,19 @@ def filter_goodtimes(l1b_de: xr.Dataset, anc_dependencies: list) -> xr.Dataset:
141
196
  Filtered L1B Direct Event dataset.
142
197
  """
143
198
  # the goodtimes are currently the only ancillary file needed for L1C processing
144
- goodtimes_table_df = pd.read_csv(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
+ )
145
202
 
146
203
  # convert goodtimes from MET to TTJ2000
147
- goodtimes_start = met_to_ttj2000ns(goodtimes_table_df["GoodTime_strt"])
204
+ goodtimes_start = met_to_ttj2000ns(goodtimes_table_df["GoodTime_start"])
148
205
  goodtimes_end = met_to_ttj2000ns(goodtimes_table_df["GoodTime_end"])
149
206
 
150
207
  # Create a mask for epochs within any of the start/end time ranges
151
208
  goodtimes_mask = np.zeros_like(l1b_de["epoch"], dtype=bool)
152
209
 
153
210
  # Iterate over the good times and create a mask
154
- for start, end in zip(goodtimes_start, goodtimes_end):
211
+ for start, end in zip(goodtimes_start, goodtimes_end, strict=False):
155
212
  goodtimes_mask |= (l1b_de["epoch"] >= start) & (l1b_de["epoch"] < end)
156
213
 
157
214
  # Filter the dataset using the mask
@@ -206,9 +263,9 @@ def create_pset_counts(
206
263
  "001000",
207
264
  ],
208
265
  # hydrogen species identifier
209
- FilterType.HYDROGEN: "h",
266
+ FilterType.HYDROGEN: "H",
210
267
  # oxygen species identifier
211
- FilterType.OXYGEN: "o",
268
+ FilterType.OXYGEN: "O",
212
269
  }
213
270
 
214
271
  # if the filter string is triples or doubles, filter using the coincidence type
@@ -249,7 +306,7 @@ def create_pset_counts(
249
306
 
250
307
  counts = xr.DataArray(
251
308
  data=hist.astype(np.int16),
252
- dims=["epoch", "energy", "longitude", "latitude"],
309
+ dims=PSET_DIMS,
253
310
  )
254
311
 
255
312
  return counts
@@ -275,11 +332,6 @@ def calculate_exposure_times(counts: xr.DataArray, l1b_de: xr.Dataset) -> xr.Dat
275
332
  exposure_time : xarray.DataArray
276
333
  The exposure times for the L1B Direct Event dataset.
277
334
  """
278
- # Create bin edges
279
- lon_edges = np.arange(3601)
280
- lat_edges = np.arange(41)
281
- energy_edges = np.arange(8)
282
-
283
335
  data = np.column_stack(
284
336
  (l1b_de["esa_step"], l1b_de["pointing_bin_lon"], l1b_de["pointing_bin_lat"])
285
337
  )
@@ -289,14 +341,19 @@ def calculate_exposure_times(counts: xr.DataArray, l1b_de: xr.Dataset) -> xr.Dat
289
341
  # exposure time equation from Lo Alg Document 10.1.1.4
290
342
  4 * l1b_de["avg_spin_durations"].to_numpy() / 3600,
291
343
  statistic="mean",
292
- bins=[energy_edges, lon_edges, lat_edges],
344
+ # NOTE: The l1b pointing_bin_lon is bin number, not actual angle
345
+ bins=[
346
+ np.arange(N_ESA_ENERGY_STEPS + 1),
347
+ np.arange(N_SPIN_ANGLE_BINS + 1),
348
+ np.arange(N_OFF_ANGLE_BINS + 1),
349
+ ],
293
350
  )
294
351
 
295
352
  stat = result.statistic[np.newaxis, :, :, :]
296
353
 
297
354
  exposure_time = xr.DataArray(
298
355
  data=stat.astype(np.float16),
299
- dims=["epoch", "energy", "longitude", "latitude"],
356
+ dims=PSET_DIMS,
300
357
  )
301
358
 
302
359
  return exposure_time
@@ -328,8 +385,6 @@ def create_datasets(
328
385
  # can be used direction
329
386
  epoch_converted_time = [1]
330
387
 
331
- # Create a data array for the epoch time
332
- # TODO: might need to update the attrs to use new YAML file
333
388
  epoch_time = xr.DataArray(
334
389
  data=epoch_converted_time,
335
390
  name="epoch",
@@ -338,38 +393,54 @@ def create_datasets(
338
393
  )
339
394
 
340
395
  if logical_source == "imap_lo_l1c_pset":
341
- esa_step = xr.DataArray(
342
- data=[1, 2, 3, 4, 5, 6, 7],
343
- name="esa_step",
344
- dims=["esa_step"],
345
- attrs=attr_mgr.get_variable_attributes("esa_step"),
346
- )
347
- pointing_bins = xr.DataArray(
348
- data=np.arange(3600),
349
- name="pointing_bins",
350
- dims=["pointing_bins"],
351
- attrs=attr_mgr.get_variable_attributes("pointing_bins"),
396
+ esa_energy_step = xr.DataArray(
397
+ data=ESA_ENERGY_STEPS,
398
+ name="esa_energy_step",
399
+ dims=["esa_energy_step"],
400
+ attrs=attr_mgr.get_variable_attributes("esa_energy_step"),
352
401
  )
353
-
354
- esa_step_label = xr.DataArray(
355
- esa_step.values.astype(str),
402
+ esa_energy_step_label = xr.DataArray(
403
+ esa_energy_step.values.astype(str),
356
404
  name="esa_step_label",
357
405
  dims=["esa_step_label"],
358
406
  attrs=attr_mgr.get_variable_attributes("esa_step_label"),
359
407
  )
360
- pointing_bins_label = xr.DataArray(
361
- pointing_bins.values.astype(str),
362
- name="pointing_bins_label",
363
- dims=["pointing_bins_label"],
364
- attrs=attr_mgr.get_variable_attributes("pointing_bins_label"),
408
+
409
+ spin_angle = xr.DataArray(
410
+ data=SPIN_ANGLE_BIN_CENTERS,
411
+ name="spin_angle",
412
+ dims=["spin_angle"],
413
+ attrs=attr_mgr.get_variable_attributes("spin_angle"),
414
+ )
415
+ spin_angle_label = xr.DataArray(
416
+ spin_angle.values.astype(str),
417
+ name="spin_angle_label",
418
+ dims=["spin_angle_label"],
419
+ attrs=attr_mgr.get_variable_attributes("spin_angle_label"),
420
+ )
421
+
422
+ off_angle = xr.DataArray(
423
+ data=OFF_ANGLE_BIN_CENTERS,
424
+ name="off_angle",
425
+ dims=["off_angle"],
426
+ attrs=attr_mgr.get_variable_attributes("off_angle"),
427
+ )
428
+ off_angle_label = xr.DataArray(
429
+ off_angle.values.astype(str),
430
+ name="off_angle_label",
431
+ dims=["off_angle_label"],
432
+ attrs=attr_mgr.get_variable_attributes("off_angle_label"),
365
433
  )
434
+
366
435
  dataset = xr.Dataset(
367
436
  coords={
368
437
  "epoch": epoch_time,
369
- "pointing_bins": pointing_bins,
370
- "pointing_bins_label": pointing_bins_label,
371
- "esa_step": esa_step,
372
- "esa_step_label": esa_step_label,
438
+ "esa_energy_step": esa_energy_step,
439
+ "esa_energy_step_label": esa_energy_step_label,
440
+ "spin_angle": spin_angle,
441
+ "spin_angle_label": spin_angle_label,
442
+ "off_angle": off_angle,
443
+ "off_angle_label": off_angle_label,
373
444
  },
374
445
  attrs=attr_mgr.get_global_attributes(logical_source),
375
446
  )
@@ -389,32 +460,141 @@ def create_datasets(
389
460
 
390
461
  # Create a data array for the current field and add it to the dataset
391
462
  # TODO: TEMPORARY. need to update to use l1b data once that's available.
392
- if field in ["pointing_start", "pointing_end", "mode", "pivot_angle"]:
463
+ if field in [
464
+ "pointing_start_met",
465
+ "pointing_end_met",
466
+ "esa_mode",
467
+ "pivot_angle",
468
+ ]:
393
469
  dataset[field] = xr.DataArray(
394
470
  data=[1],
395
471
  dims=dims,
396
472
  attrs=attr_mgr.get_variable_attributes(field),
397
473
  )
398
474
  # TODO: This is temporary.
399
- # The data type will be set in the data class when that's created
400
475
  elif field == "exposure_time":
401
476
  dataset[field] = xr.DataArray(
402
- data=np.ones((1, 7), dtype=np.float16),
477
+ data=np.ones((1, 7, 3600, 40), dtype=np.float16),
403
478
  dims=dims,
404
479
  attrs=attr_mgr.get_variable_attributes(field),
405
480
  )
406
481
 
407
- elif "rate" in field:
482
+ elif "rates" in field:
408
483
  dataset[field] = xr.DataArray(
409
- data=np.ones((1, 3600, 7), dtype=np.float16),
484
+ data=np.ones(PSET_SHAPE, dtype=np.float16),
410
485
  dims=dims,
411
486
  attrs=attr_mgr.get_variable_attributes(field),
412
487
  )
413
488
  else:
414
489
  dataset[field] = xr.DataArray(
415
- data=np.ones((1, 3600, 7), dtype=np.int16),
490
+ data=np.ones(PSET_SHAPE, dtype=np.int16),
416
491
  dims=dims,
417
492
  attrs=attr_mgr.get_variable_attributes(field),
418
493
  )
419
494
 
420
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