1515"""
1616
1717import logging
18- from enum import Enum
18+ from enum import Enum , IntEnum
1919
20+ import numpy as np
2021import pandas as pd
2122import xarray as xr
23+ from numpy .typing import NDArray
24+ from xarray import DataArray
2225
2326from imap_processing import imap_module_directory
2427from imap_processing .cdf .imap_cdf_manager import ImapCdfAttributes
4245logger = logging .getLogger (__name__ )
4346
4447
48+ class TriggerOrigin (IntEnum ):
49+ """Enum class for event trigger origins."""
50+
51+ HS_ADC0I_TOF_HG = 0
52+ HS_ADC0Q_TOF_LG = 1
53+ HS_ADC1Q_TOF_MG = 2
54+ LS_ADC1_TARGET_HG = 3
55+ SW_TRIGGER = 4
56+ EXTERNAL_TRIGGER = 5
57+
58+
59+ TRIGGER_LABELS = {
60+ TriggerOrigin .HS_ADC0I_TOF_HG : "HS ADC0I trigger (TOF HG)" ,
61+ TriggerOrigin .HS_ADC0Q_TOF_LG : "HS ADC0Q trigger (TOF LG)" ,
62+ TriggerOrigin .HS_ADC1Q_TOF_MG : "HS ADC1Q trigger (TOF MG)" ,
63+ TriggerOrigin .LS_ADC1_TARGET_HG : "LS ADC1 trigger (Target HG / low range)" ,
64+ TriggerOrigin .SW_TRIGGER : "SW trigger" ,
65+ TriggerOrigin .EXTERNAL_TRIGGER : "external trigger" ,
66+ }
67+
68+
4569class TriggerMode (Enum ):
4670 """
4771 Enum class for data collection trigger Modes.
@@ -117,21 +141,21 @@ def idex_l1b(l1a_dataset: xr.Dataset) -> xr.Dataset:
117141 # used for calculations yet but are saved in the CDF for reference.
118142 spice_data = get_spice_data (l1a_dataset , idex_attrs )
119143
120- trigger_settings = get_trigger_mode_and_level (l1a_dataset )
121- if trigger_settings :
122- trigger_settings ["triggerlevel" ].attrs = idex_attrs .get_variable_attributes (
123- "trigger_level"
124- )
125- trigger_settings ["triggermode" ].attrs = idex_attrs .get_variable_attributes (
126- "trigger_mode"
127- )
128-
144+ trigger_settings = get_trigger_mode_and_level (l1a_dataset , idex_attrs )
145+ trigger_origin = get_trigger_origin (
146+ l1a_dataset ["idx__txhdrtrigid" ].data , idex_attrs
147+ )
129148 # Create l1b Dataset
130149 prefixes = ["shcoarse" , "shfine" , "time_high_sample" , "time_low_sample" ]
131- data_vars = processed_vars | waveforms_converted | trigger_settings | spice_data
150+ data_vars = (
151+ processed_vars
152+ | waveforms_converted
153+ | trigger_settings
154+ | spice_data
155+ | trigger_origin
156+ )
132157 l1b_dataset = setup_dataset (l1a_dataset , prefixes , idex_attrs , data_vars )
133158 l1b_dataset .attrs = idex_attrs .get_global_attributes ("imap_idex_l1b_sci" )
134-
135159 # Convert variables
136160 l1b_dataset = convert_raw_to_eu (
137161 l1b_dataset ,
@@ -225,6 +249,7 @@ def convert_waveforms(
225249
226250def get_trigger_mode_and_level (
227251 l1a_dataset : xr .Dataset ,
252+ idex_attrs : ImapCdfAttributes ,
228253) -> dict [str , xr .DataArray ] | dict :
229254 """
230255 Determine the trigger mode and threshold level for each event.
@@ -233,6 +258,8 @@ def get_trigger_mode_and_level(
233258 ----------
234259 l1a_dataset : xarray.Dataset
235260 IDEX L1a dataset containing the six waveform arrays and instrument settings.
261+ idex_attrs : ImapCdfAttributes
262+ CDF attribute manager object.
236263
237264 Returns
238265 -------
@@ -243,8 +270,8 @@ def get_trigger_mode_and_level(
243270 channels = ["lg" , "mg" , "hg" ]
244271 # 10 bit mask
245272 mask = 0b1111111111
246- trigger_modes = []
247- trigger_levels = []
273+ # Initialize a dict to hold the mode labels and threshold levels for each channel
274+ data_dict = {}
248275
249276 def compute_trigger_values (
250277 trigger_mode : int , trigger_controls : int , gain_channel : str
@@ -302,28 +329,63 @@ def compute_trigger_values(
302329 vectorize = True ,
303330 output_dtypes = [object , float ],
304331 )
305- trigger_modes .append (mode_array .rename ("trigger_mode" ))
306- trigger_levels .append (level_array .rename ("trigger_level" ))
307-
308- try :
309332 # There should be an array of modes and threshold levels for each channel.
310- # At each index (event) only one of the three arrays should have a value that is
311- # not 'None' because each event can only be triggered by one channel.
312- # By merging the three arrays, we get value for each event.
313- merged_modes = xr .merge ([trigger_modes [0 ], xr .merge (trigger_modes [1 :])])
314- merged_levels = xr .merge ([trigger_levels [0 ], xr .merge (trigger_levels [1 :])])
315-
316- return {
317- "triggermode" : merged_modes .trigger_mode ,
318- "triggerlevel" : merged_levels .trigger_level ,
319- }
320-
321- except xr .MergeError as e :
322- raise ValueError (
323- f"Only one channel can trigger a dust event. Please make sure "
324- f"there is only one valid trigger value per event. This "
325- f"caused Merge Error: { e } "
326- ) from e
333+ # write each of them out as separate variables because there may be
334+ # multiple channels that can trigger an event. The trigger origin variable
335+ # can be used to determine which channel(s) triggered the event.
336+ mode_array .attrs = idex_attrs .get_variable_attributes (f"trigger_mode_{ channel } " )
337+ data_dict [f"trigger_mode_{ channel } " ] = mode_array
338+ level_array .attrs = idex_attrs .get_variable_attributes (
339+ f"trigger_level_{ channel } "
340+ )
341+ data_dict [f"trigger_level_{ channel } " ] = level_array
342+
343+ return data_dict
344+
345+
346+ def get_trigger_origin (
347+ trigger_id : NDArray , idex_attrs : ImapCdfAttributes
348+ ) -> dict [str , DataArray ]:
349+ """
350+ Determine the trigger origin for each event.
351+
352+ Parameters
353+ ----------
354+ trigger_id : numpy.ndarray
355+ Array of raw trigger ID values from the l1a dataset. The trigger ID is a 32-bit
356+ integer where the lower 10 bits contain information about the trigger origin.
357+ idex_attrs : ImapCdfAttributes
358+ CDF attribute manager object.
359+
360+ Returns
361+ -------
362+ dict[str, xarray.DataArray]
363+ A dictionary containing the trigger_origin DataArray with the trigger
364+ origin info for each event.
365+ """
366+ # extract the lower 10 bits of the trigger ID to get the trigger origin information
367+ trigger_bits = trigger_id & 0x3FF
368+ # For each event, determine which bits are set and get the corresponding trigger
369+ # origin labels
370+ origin_labels = np .array (
371+ [
372+ ", " .join (
373+ [TRIGGER_LABELS [TriggerOrigin (i )] for i in range (6 ) if (bits >> i ) & 1 ]
374+ )
375+ for bits in trigger_bits
376+ ],
377+ dtype = object ,
378+ )
379+ # Update any events with no trigger bits set to "unknown trigger origin"
380+ origin_labels [origin_labels == "" ] = "Unknown trigger origin"
381+ return {
382+ "trigger_origin" : xr .DataArray (
383+ name = "trigger_origin" ,
384+ data = np .squeeze (origin_labels ),
385+ dims = "epoch" ,
386+ attrs = idex_attrs .get_variable_attributes ("trigger_origin" ),
387+ )
388+ }
327389
328390
329391def get_spice_data (
0 commit comments