imap-processing 0.17.0__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 (89) 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 +11 -0
  5. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +11 -0
  6. imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +24 -0
  7. imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +163 -100
  8. imap_processing/cdf/config/imap_hit_l2_variable_attrs.yaml +4 -4
  9. imap_processing/cdf/config/imap_ialirt_l1_variable_attrs.yaml +97 -54
  10. imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +119 -36
  11. imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +16 -90
  12. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +30 -0
  13. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +15 -1
  14. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +60 -0
  15. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +91 -11
  16. imap_processing/cli.py +28 -5
  17. imap_processing/codice/codice_l1a.py +36 -48
  18. imap_processing/codice/codice_l1b.py +1 -1
  19. imap_processing/codice/codice_l2.py +0 -9
  20. imap_processing/codice/constants.py +481 -498
  21. imap_processing/hit/l0/decom_hit.py +2 -2
  22. imap_processing/hit/l1a/hit_l1a.py +64 -24
  23. imap_processing/hit/l1b/constants.py +5 -0
  24. imap_processing/hit/l1b/hit_l1b.py +18 -16
  25. imap_processing/hit/l2/constants.py +1 -1
  26. imap_processing/hit/l2/hit_l2.py +4 -5
  27. imap_processing/ialirt/constants.py +21 -0
  28. imap_processing/ialirt/generate_coverage.py +188 -0
  29. imap_processing/ialirt/l0/parse_mag.py +62 -5
  30. imap_processing/ialirt/l0/process_swapi.py +1 -1
  31. imap_processing/ialirt/l0/process_swe.py +23 -7
  32. imap_processing/ialirt/utils/constants.py +22 -16
  33. imap_processing/ialirt/utils/create_xarray.py +42 -19
  34. imap_processing/idex/idex_constants.py +1 -5
  35. imap_processing/idex/idex_l2b.py +246 -67
  36. imap_processing/idex/idex_l2c.py +30 -196
  37. imap_processing/lo/l0/lo_apid.py +1 -0
  38. imap_processing/lo/l1a/lo_l1a.py +44 -0
  39. imap_processing/lo/packet_definitions/lo_xtce.xml +5359 -106
  40. imap_processing/mag/constants.py +1 -0
  41. imap_processing/mag/l1d/__init__.py +0 -0
  42. imap_processing/mag/l1d/mag_l1d.py +133 -0
  43. imap_processing/mag/l1d/mag_l1d_data.py +588 -0
  44. imap_processing/mag/l2/__init__.py +0 -0
  45. imap_processing/mag/l2/mag_l2.py +25 -20
  46. imap_processing/mag/l2/mag_l2_data.py +191 -130
  47. imap_processing/quality_flags.py +20 -2
  48. imap_processing/spice/geometry.py +25 -3
  49. imap_processing/spice/pointing_frame.py +1 -1
  50. imap_processing/spice/spin.py +4 -0
  51. imap_processing/spice/time.py +51 -0
  52. imap_processing/swapi/l2/swapi_l2.py +52 -8
  53. imap_processing/swapi/swapi_utils.py +1 -1
  54. imap_processing/swe/l1b/swe_l1b.py +2 -4
  55. imap_processing/ultra/constants.py +49 -1
  56. imap_processing/ultra/l0/decom_tools.py +15 -8
  57. imap_processing/ultra/l0/decom_ultra.py +35 -11
  58. imap_processing/ultra/l0/ultra_utils.py +97 -5
  59. imap_processing/ultra/l1a/ultra_l1a.py +25 -4
  60. imap_processing/ultra/l1b/cullingmask.py +3 -3
  61. imap_processing/ultra/l1b/de.py +53 -15
  62. imap_processing/ultra/l1b/extendedspin.py +26 -2
  63. imap_processing/ultra/l1b/lookup_utils.py +171 -50
  64. imap_processing/ultra/l1b/quality_flag_filters.py +14 -0
  65. imap_processing/ultra/l1b/ultra_l1b_culling.py +198 -5
  66. imap_processing/ultra/l1b/ultra_l1b_extended.py +304 -66
  67. imap_processing/ultra/l1c/helio_pset.py +54 -7
  68. imap_processing/ultra/l1c/spacecraft_pset.py +9 -1
  69. imap_processing/ultra/l1c/ultra_l1c.py +2 -0
  70. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +106 -109
  71. imap_processing/ultra/utils/ultra_l1_utils.py +13 -1
  72. {imap_processing-0.17.0.dist-info → imap_processing-0.18.0.dist-info}/METADATA +2 -2
  73. {imap_processing-0.17.0.dist-info → imap_processing-0.18.0.dist-info}/RECORD +76 -83
  74. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +0 -526
  75. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +0 -526
  76. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +0 -526
  77. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +0 -524
  78. imap_processing/ultra/lookup_tables/EgyNorm.mem.csv +0 -32769
  79. imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
  80. imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
  81. imap_processing/ultra/lookup_tables/dps_grid45_compressed.cdf +0 -0
  82. imap_processing/ultra/lookup_tables/ultra45_back-pos-luts.csv +0 -4097
  83. imap_processing/ultra/lookup_tables/ultra45_tdc_norm.csv +0 -2050
  84. imap_processing/ultra/lookup_tables/ultra90_back-pos-luts.csv +0 -4097
  85. imap_processing/ultra/lookup_tables/ultra90_tdc_norm.csv +0 -2050
  86. imap_processing/ultra/lookup_tables/yadjust.csv +0 -257
  87. {imap_processing-0.17.0.dist-info → imap_processing-0.18.0.dist-info}/LICENSE +0 -0
  88. {imap_processing-0.17.0.dist-info → imap_processing-0.18.0.dist-info}/WHEEL +0 -0
  89. {imap_processing-0.17.0.dist-info → imap_processing-0.18.0.dist-info}/entry_points.txt +0 -0
@@ -767,10 +767,8 @@ def swe_l1b_science(dependencies: ProcessingInputCollection) -> xr.Dataset:
767
767
 
768
768
  # Store ESA energies of full cycle for L2 purposes.
769
769
  esa_energies = get_esa_energy_pattern(esa_lut_files[0])
770
- # Repeat energies to be in the same shape as the science data
771
- esa_energies = np.repeat(esa_energies, total_packets // 4).reshape(
772
- -1, swe_constants.N_ESA_STEPS, swe_constants.N_ANGLE_SECTORS
773
- )
770
+ # Repeat the (24, 30) energy pattern n_cycles times along a new first axis
771
+ esa_energies = np.repeat(esa_energies[np.newaxis, :, :], total_packets // 4, axis=0)
774
772
  # Convert voltage to electron energy in eV by apply conversion factor
775
773
  esa_energies = esa_energies * swe_constants.ENERGY_CONVERSION_FACTOR
776
774
  # ------------------------------------------------------------------
@@ -80,4 +80,52 @@ class UltraConstants:
80
80
  CULLING_RPM_MAX = 6.0
81
81
 
82
82
  # Thresholds for culling based on counts.
83
- CULLING_ENERGY_BIN_EDGES: ClassVar[list] = [0, 10, 20, 1e5]
83
+ CULLING_ENERGY_BIN_EDGES: ClassVar[list] = [
84
+ 3.385,
85
+ 4.13722222222222,
86
+ 4.13722222222222,
87
+ 5.05660493827161,
88
+ 5.05660493827161,
89
+ 6.18029492455419,
90
+ 6.18029492455419,
91
+ 7.55369379667734,
92
+ 7.55369379667734,
93
+ 9.23229241816119,
94
+ 9.23229241816119,
95
+ 11.2839129555303,
96
+ 11.2839129555303,
97
+ 13.7914491678704,
98
+ 13.7914491678704,
99
+ 16.8562156496194,
100
+ 16.8562156496194,
101
+ 20.6020413495348,
102
+ 20.6020413495348,
103
+ 25.1802727605426,
104
+ 25.1802727605426,
105
+ 30.775888929552,
106
+ 30.775888929552,
107
+ 37.6149753583414,
108
+ 37.6149753583414,
109
+ 45.9738587713061,
110
+ 45.9738587713061,
111
+ 56.1902718315964,
112
+ 56.1902718315964,
113
+ 68.6769989052845,
114
+ 68.6769989052845,
115
+ 83.93855421757,
116
+ 83.93855421757,
117
+ 102.591566265919,
118
+ 102.591566265919,
119
+ 125.38969210279,
120
+ 125.38969210279,
121
+ 153.254068125632,
122
+ 153.254068125632,
123
+ 187.310527709106,
124
+ 187.310527709106,
125
+ 228.93508942224,
126
+ 228.93508942224,
127
+ 279.809553738294,
128
+ 279.809553738294,
129
+ 341.989454569026,
130
+ 1e5,
131
+ ]
@@ -4,6 +4,7 @@ import numpy as np
4
4
  from numpy.typing import NDArray
5
5
 
6
6
  from imap_processing.ultra.l0.ultra_utils import (
7
+ PacketProperties,
7
8
  parse_event,
8
9
  )
9
10
  from imap_processing.utils import convert_to_binary_string
@@ -155,8 +156,7 @@ def decompress_binary(
155
156
  def decompress_image(
156
157
  pixel0: int,
157
158
  binary_data: str,
158
- width_bit: int,
159
- mantissa_bit_length: int,
159
+ packet_props: PacketProperties,
160
160
  ) -> NDArray:
161
161
  """
162
162
  Will decompress a binary string representing an image into a matrix of pixel values.
@@ -171,10 +171,9 @@ def decompress_image(
171
171
  The first, unmodified pixel p0,0.
172
172
  binary_data : str
173
173
  Binary string.
174
- width_bit : int
175
- The bit width that describes the width of data in the block.
176
- mantissa_bit_length : int
177
- The bit length of the mantissa.
174
+ packet_props : PacketProperties
175
+ Properties of the packet, including width bit, mantissa bit length and pixel
176
+ window dimensions.
178
177
 
179
178
  Returns
180
179
  -------
@@ -187,10 +186,18 @@ def decompress_image(
187
186
  This process is described starting on page 168 in IMAP-Ultra Flight
188
187
  Software Specification document.
189
188
  """
190
- rows = 54
191
- cols = 180
189
+ rows = packet_props.pixel_window_rows
190
+ cols = packet_props.pixel_window_columns
191
+ width_bit = packet_props.width
192
+ mantissa_bit_length = packet_props.mantissa_bit_length
192
193
  pixels_per_block = 15
193
194
 
195
+ if width_bit is None or rows is None or cols is None or mantissa_bit_length is None:
196
+ raise ValueError(
197
+ "Packet properties must specify pixel window dimensions, "
198
+ "width bit, and mantissa bit length for this packet type."
199
+ )
200
+
194
201
  blocks_per_row = cols // pixels_per_block
195
202
 
196
203
  # Compressed pixel matrix
@@ -1,6 +1,7 @@
1
1
  """Decommutates Ultra CCSDS packets."""
2
2
 
3
3
  import logging
4
+ import math
4
5
  from collections import defaultdict
5
6
  from typing import cast
6
7
 
@@ -29,7 +30,7 @@ from imap_processing.ultra.l0.ultra_utils import (
29
30
  ULTRA_PRI_3_EVENTS,
30
31
  ULTRA_PRI_4_EVENTS,
31
32
  ULTRA_RATES,
32
- ULTRA_TOF,
33
+ PacketProperties,
33
34
  )
34
35
  from imap_processing.utils import convert_to_binary_string
35
36
 
@@ -37,7 +38,7 @@ logging.basicConfig(level=logging.INFO)
37
38
  logger = logging.getLogger(__name__)
38
39
 
39
40
 
40
- def process_ultra_tof(ds: xr.Dataset) -> xr.Dataset:
41
+ def process_ultra_tof(ds: xr.Dataset, packet_props: PacketProperties) -> xr.Dataset:
41
42
  """
42
43
  Unpack and decode Ultra TOF packets.
43
44
 
@@ -45,6 +46,9 @@ def process_ultra_tof(ds: xr.Dataset) -> xr.Dataset:
45
46
  ----------
46
47
  ds : xarray.Dataset
47
48
  TOF dataset.
49
+ packet_props : PacketProperties
50
+ Information that defines properties of the packet including the pixel window
51
+ dimensions of images and number of image panes.
48
52
 
49
53
  Returns
50
54
  -------
@@ -53,14 +57,35 @@ def process_ultra_tof(ds: xr.Dataset) -> xr.Dataset:
53
57
  """
54
58
  scalar_keys = [key for key in ds.data_vars if key not in ("packetdata", "sid")]
55
59
 
60
+ image_planes = packet_props.image_planes
61
+ rows = packet_props.pixel_window_rows
62
+ cols = packet_props.pixel_window_columns
63
+ planes_per_packet = packet_props.image_planes_per_packet
64
+
65
+ if (
66
+ image_planes is None
67
+ or rows is None
68
+ or cols is None
69
+ or planes_per_packet is None
70
+ ):
71
+ raise ValueError(
72
+ "Packet properties must specify pixel window dimensions, "
73
+ "width bit, image planes, and image planes per packet for this packet type."
74
+ )
75
+ # Calculate the number of image packets based on the number of image panes and
76
+ # planes per packet.
77
+ # There may be cases where the last packet has fewer planes than the
78
+ # planes_per_packet, to account for this, we use ceiling division.
79
+ num_image_packets = math.ceil(image_planes / planes_per_packet)
80
+
56
81
  decom_data: defaultdict[str, list[np.ndarray]] = defaultdict(list)
57
82
  decom_data["packetdata"] = []
58
83
  valid_epoch = []
59
- width = cast(int, ULTRA_TOF.width)
60
- mantissa_bit_length = cast(int, ULTRA_TOF.mantissa_bit_length)
61
84
 
62
85
  for val, group in ds.groupby("epoch"):
63
- if set(group["sid"].values) >= set(range(8)):
86
+ if set(group["sid"].values) >= set(
87
+ np.arange(0, image_planes, planes_per_packet)
88
+ ):
64
89
  valid_epoch.append(val)
65
90
  group.sortby("sid")
66
91
 
@@ -68,13 +93,12 @@ def process_ultra_tof(ds: xr.Dataset) -> xr.Dataset:
68
93
  decom_data[key].append(group[key].values)
69
94
 
70
95
  image = []
71
- for i in range(8):
96
+ for i in range(num_image_packets):
72
97
  binary = convert_to_binary_string(group["packetdata"].values[i])
73
98
  decompressed = decompress_image(
74
99
  group["p00"].values[i],
75
100
  binary,
76
- width,
77
- mantissa_bit_length,
101
+ packet_props,
78
102
  )
79
103
  image.append(decompressed)
80
104
 
@@ -87,9 +111,9 @@ def process_ultra_tof(ds: xr.Dataset) -> xr.Dataset:
87
111
 
88
112
  coords = {
89
113
  "epoch": np.array(valid_epoch, dtype=np.uint64),
90
- "sid": xr.DataArray(np.arange(8), dims=["sid"], name="sid"),
91
- "row": xr.DataArray(np.arange(54), dims=["row"], name="row"),
92
- "column": xr.DataArray(np.arange(180), dims=["column"], name="column"),
114
+ "sid": xr.DataArray(np.arange(num_image_packets), dims=["sid"], name="sid"),
115
+ "row": xr.DataArray(np.arange(rows), dims=["row"], name="row"),
116
+ "column": xr.DataArray(np.arange(cols), dims=["column"], name="column"),
93
117
  }
94
118
 
95
119
  dataset = xr.Dataset(coords=coords)
@@ -10,15 +10,23 @@ class PacketProperties(NamedTuple):
10
10
  logical_source: list # List of logical sources
11
11
  addition_to_logical_desc: str # Description of the logical source
12
12
  width: Union[int, None] # Width of binary data (could be None).
13
+ # Block, image_planes, pixel_window_rows, and pixel_window_columns are important for
14
+ # decompressing the images and a description is available on page 171 of IMAP-Ultra
15
+ # Flight Software Specification document (7523-9009_Rev_-.pdf).
13
16
  block: Union[int, None] # Number of values in each block (could be None).
14
- # This is important for decompressing the images and
15
- # a description is available on page 171 of IMAP-Ultra Flight
16
- # Software Specification document (7523-9009_Rev_-.pdf).
17
17
  len_array: Union[
18
18
  int, None
19
19
  ] # Length of the array to be decompressed (could be None).
20
20
  mantissa_bit_length: Union[int, None] # used to determine the level of
21
21
  # precision that can be recovered from compressed data (could be None).
22
+ image_planes: Union[int, None] = None
23
+ # number of images. See table 11 in the FSSD.
24
+ pixel_window_rows: Union[int, None] = None
25
+ # number of rows in each image. See table 49 in the FSSD.
26
+ pixel_window_columns: Union[int, None] = None
27
+ # number of columns in each image. See table 49 in the FSSD.
28
+ image_planes_per_packet: Union[int, None] = None
29
+ # number of image planes in each packet. See table 52 in the FSSD.
22
30
 
23
31
 
24
32
  # Define PacketProperties instances directly in the module namespace
@@ -64,15 +72,99 @@ ULTRA_ENERGY_SPECTRA = PacketProperties(
64
72
  len_array=1,
65
73
  mantissa_bit_length=5,
66
74
  )
67
- ULTRA_TOF = PacketProperties(
75
+ ULTRA_PHXTOF_HIGH_ANGULAR = PacketProperties(
68
76
  apid=[883, 947],
69
77
  logical_source=[
70
78
  "imap_ultra_l1a_45sensor-histogram-ena-phxtof-hi-ang",
71
79
  "imap_ultra_l1a_90sensor-histogram-ena-phxtof-hi-ang",
72
80
  ],
73
- addition_to_logical_desc="Time of Flight Images",
81
+ addition_to_logical_desc="Pulse Height Time of Flight High Angular Images",
74
82
  width=4,
75
83
  block=15,
84
+ image_planes=8,
85
+ pixel_window_rows=54,
86
+ pixel_window_columns=180,
87
+ image_planes_per_packet=1,
88
+ len_array=None,
89
+ mantissa_bit_length=4,
90
+ )
91
+ ULTRA_PHXTOF_HIGH_ENERGY = PacketProperties(
92
+ apid=[884, 948],
93
+ logical_source=[
94
+ "imap_ultra_l1a_45sensor-histogram-ena-phxtof-hi-nrg",
95
+ "imap_ultra_l1a_90sensor-histogram-ena-phxtof-hi-nrg",
96
+ ],
97
+ addition_to_logical_desc="Pulse Height By Time of Flight High Energy Images",
98
+ width=4,
99
+ block=15,
100
+ image_planes=28,
101
+ pixel_window_rows=27,
102
+ pixel_window_columns=90,
103
+ image_planes_per_packet=1,
104
+ len_array=None,
105
+ mantissa_bit_length=4,
106
+ )
107
+ ULTRA_PHXTOF_HIGH_TIME = PacketProperties(
108
+ apid=[885, 949],
109
+ logical_source=[
110
+ "imap_ultra_l1a_45sensor-histogram-ena-phxtof-hi-time",
111
+ "imap_ultra_l1a_90sensor-histogram-ena-phxtof-hi-time",
112
+ ],
113
+ addition_to_logical_desc="Time of Flight High Time Images",
114
+ width=4,
115
+ block=15,
116
+ image_planes=8,
117
+ pixel_window_rows=18,
118
+ pixel_window_columns=60,
119
+ image_planes_per_packet=2,
120
+ len_array=None,
121
+ mantissa_bit_length=4,
122
+ )
123
+ ULTRA_EXTOF_HIGH_ANGULAR = PacketProperties(
124
+ apid=[886, 950],
125
+ logical_source=[
126
+ "imap_ultra_l1a_45sensor-histogram-ena-extof-hi-ang",
127
+ "imap_ultra_l1a_90sensor-histogram-ena-extof-hi-ang",
128
+ ],
129
+ addition_to_logical_desc="Energy By Time of Flight High Angular Images",
130
+ width=4,
131
+ block=15,
132
+ image_planes=12,
133
+ pixel_window_rows=18,
134
+ pixel_window_columns=60,
135
+ image_planes_per_packet=2,
136
+ len_array=None,
137
+ mantissa_bit_length=4,
138
+ )
139
+ ULTRA_EXTOF_HIGH_TIME = PacketProperties(
140
+ apid=[888, 952],
141
+ logical_source=[
142
+ "imap_ultra_l1a_45sensor-histogram-ena-extof-hi-time",
143
+ "imap_ultra_l1a_90sensor-histogram-ena-extof-hi-time",
144
+ ],
145
+ addition_to_logical_desc="Energy By Time of Flight High Time Images",
146
+ width=4,
147
+ block=15,
148
+ image_planes=4,
149
+ pixel_window_rows=9,
150
+ pixel_window_columns=30,
151
+ image_planes_per_packet=8,
152
+ len_array=None,
153
+ mantissa_bit_length=4,
154
+ )
155
+ ULTRA_EXTOF_HIGH_ENERGY = PacketProperties(
156
+ apid=[887, 951],
157
+ logical_source=[
158
+ "imap_ultra_l1a_45sensor-histogram-ena-extof-hi-nrg",
159
+ "imap_ultra_l1a_90sensor-histogram-ena-extof-hi-nrg",
160
+ ],
161
+ addition_to_logical_desc="Energy By Time of Flight High Energy Images",
162
+ width=4,
163
+ block=15,
164
+ image_planes=44,
165
+ pixel_window_rows=9,
166
+ pixel_window_columns=30,
167
+ image_planes_per_packet=8,
76
168
  len_array=None,
77
169
  mantissa_bit_length=4,
78
170
  )
@@ -24,14 +24,19 @@ from imap_processing.ultra.l0.ultra_utils import (
24
24
  ULTRA_ENERGY_RATES,
25
25
  ULTRA_ENERGY_SPECTRA,
26
26
  ULTRA_EVENTS,
27
+ ULTRA_EXTOF_HIGH_ANGULAR,
28
+ ULTRA_EXTOF_HIGH_ENERGY,
29
+ ULTRA_EXTOF_HIGH_TIME,
27
30
  ULTRA_HK,
28
31
  ULTRA_MACROS_CHECKSUM,
32
+ ULTRA_PHXTOF_HIGH_ANGULAR,
33
+ ULTRA_PHXTOF_HIGH_ENERGY,
34
+ ULTRA_PHXTOF_HIGH_TIME,
29
35
  ULTRA_PRI_1_EVENTS,
30
36
  ULTRA_PRI_2_EVENTS,
31
37
  ULTRA_PRI_3_EVENTS,
32
38
  ULTRA_PRI_4_EVENTS,
33
39
  ULTRA_RATES,
34
- ULTRA_TOF,
35
40
  )
36
41
  from imap_processing.utils import packet_file_to_datasets
37
42
 
@@ -87,6 +92,19 @@ def ultra_l1a( # noqa: PLR0912
87
92
  for i, apid in enumerate(group.apid)
88
93
  }
89
94
 
95
+ all_l1a_image_apids = {
96
+ apid: group
97
+ for group in [
98
+ ULTRA_PHXTOF_HIGH_ANGULAR,
99
+ ULTRA_PHXTOF_HIGH_ENERGY,
100
+ ULTRA_PHXTOF_HIGH_TIME,
101
+ ULTRA_EXTOF_HIGH_ANGULAR,
102
+ ULTRA_EXTOF_HIGH_TIME,
103
+ ULTRA_EXTOF_HIGH_ENERGY,
104
+ ]
105
+ for apid in group.apid
106
+ }
107
+
90
108
  # Update dataset global attributes
91
109
  attr_mgr = ImapCdfAttributes()
92
110
  attr_mgr.add_instrument_global_attrs("ultra")
@@ -96,9 +114,12 @@ def ultra_l1a( # noqa: PLR0912
96
114
  if apid in ULTRA_AUX.apid:
97
115
  decom_ultra_dataset = datasets_by_apid[apid]
98
116
  gattr_key = ULTRA_AUX.logical_source[ULTRA_AUX.apid.index(apid)]
99
- elif apid in ULTRA_TOF.apid:
100
- decom_ultra_dataset = process_ultra_tof(datasets_by_apid[apid])
101
- gattr_key = ULTRA_TOF.logical_source[ULTRA_TOF.apid.index(apid)]
117
+ elif apid in all_l1a_image_apids:
118
+ packet_props = all_l1a_image_apids[apid]
119
+ decom_ultra_dataset = process_ultra_tof(
120
+ datasets_by_apid[apid], packet_props
121
+ )
122
+ gattr_key = packet_props.logical_source[packet_props.apid.index(apid)]
102
123
  elif apid in ULTRA_RATES.apid:
103
124
  decom_ultra_dataset = process_ultra_rates(datasets_by_apid[apid])
104
125
  decom_ultra_dataset = decom_ultra_dataset.drop_vars("fastdata_00")
@@ -3,7 +3,7 @@
3
3
  import numpy as np
4
4
  import xarray as xr
5
5
 
6
- from imap_processing.quality_flags import ImapAttitudeUltraFlags, ImapRatesUltraFlags
6
+ from imap_processing.ultra.l1b.quality_flag_filters import QUALITY_FLAG_FILTERS
7
7
  from imap_processing.ultra.utils.ultra_l1_utils import create_dataset, extract_data_dict
8
8
 
9
9
  FILLVAL_UINT16 = 65535
@@ -32,14 +32,14 @@ def calculate_cullingmask(extendedspin_dataset: xr.Dataset, name: str) -> xr.Dat
32
32
  good_mask = (
33
33
  (
34
34
  extendedspin_dataset["quality_attitude"]
35
- & ImapAttitudeUltraFlags.SPINRATE.value
35
+ & sum(flag.value for flag in QUALITY_FLAG_FILTERS["quality_attitude"])
36
36
  )
37
37
  == 0
38
38
  ) & (
39
39
  (
40
40
  (
41
41
  extendedspin_dataset["quality_ena_rates"]
42
- & ImapRatesUltraFlags.HIGHRATES.value
42
+ & sum(flag.value for flag in QUALITY_FLAG_FILTERS["quality_ena_rates"])
43
43
  )
44
44
  == 0
45
45
  ).all(dim="energy_bin_geometric_mean")
@@ -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