EventBuilder reads raw detector data from .dat files and assembles complete physics events by grouping detector subsystem fragments based on timestamp coherence.
Overview
The EventBuilder operates as a Framework producer that:
- Reads binary frames from a
.dat file using the Reader class for incremental streaming
- Parses frame headers (RogueFrameHeader for StreamWriter framing)
- Extracts fragment data in either RoR (LDMXRoRHeader) or packing subsystem format
- Buffers fragments by timestamp in a FragmentBuffer
- Assembles events when fragments from ≥3 different subsystems arrive within a 5ms coherence window
- Outputs events to:
- ROOT file with event-by-event subsystem data (Framework output)
- Binary file (
events.bin) for offline analysis
- CSV summary (
event_summaries.txt) for quick validation
Architecture
Core Classes
EventBuilder (EventBuilder.hh)
- Main Producer class that drives event assembly
configure(): Opens the .dat file with Reader
produce(): Main event loop - reads frames, parses fragments, buffers them, and assembles events
FragmentBuffer (FragmentBuffer.hh)
- Maintains a map of fragments organized by timestamp
add_fragment(): Stores incoming fragments, sets reference time on first fragment
try_build_event(): Collects all fragments within [ref_time ± coherence_window], requires ≥3 unique subsystems
- Returns false if completeness check fails (incomplete event)
DataFragment (Fragment.hh)
- Header: subsystem_id, timestamp, contributor
- Trailer: CRC checksum
- Payload: Raw byte data from device
PhysicsEventData (Event/PhysicsEventData.hh)
- Event-level data structure for binary output
- Contains: event_id, timestamp, blocks (one per subsystem), systems_readout list
GenericDataBlock (Event/GenericDataBlock.hh)
- Per-subsystem data in an event
- Contains: subsystem_id, timestamp_ns, data (uint8_t vector), checksum
EventSummary (EventSummary.hh)
- CSV row for quick event validation
- Contains: event_id, timestamp_ns, # subsystems, subsystem_ids, total_payload_size, error_flags
Data Flow
.dat file (binary frames)
↓
RogueFrameHeader (frame demux, skip non-data channels)
↓
LDMXRoRHeader or SubsystemPacket (format detection & parsing)
↓
DataFragment (structured fragment)
↓
FragmentBuffer (timestamp-based buffering)
↓
try_build_event (coherence window assembly, subsystem completeness check)
↓
PhysicsEventData (event payload) → Binary file + CSV summary
↓
Framework::Event → Root file (subsystem branches)
Usage
Python Configuration
from LDMX.Framework import ldmxcfg
from LDMX.EventBuilder import eventbuilder
import sys
p = ldmxcfg.Process("unpack")
p.run = 1
p.max_events = 100
p.verbose_parse = True
p.sequence = [
eventbuilder.from_dat_file(sys.argv[1])
]
p.output_files = [sys.argv[2]]
p.pause()
Run with:
python run_eventbuilder.py input.dat output.root
Configuration Parameters
- dat_file: Path to input binary file (or set EVENTBUILDER_INPUT env var)
- output_name: Name of object on event bus (default: "PhysicsEventData")
- verbose_parse: Enable debug logging (default: false)
Output Files
- output.root (Framework output)
- One TTree with event entries
- Branches: TDAQ_Trigger, TS_DAQ, TS_Trigger, Tracker_DAQ, ECAL_DAQ, ECAL_Trigger, HCAL_DAQ, HCAL_Trigger
- Each branch contains
std::vector<uint8_t> for that subsystem's raw data
- All subsystems present in an event are grouped in the same tree entry
- events.bin (Binary format)
- Compact binary stream of assembled events
- Format:
[event_id: u64][timestamp: u64][nblocks: u32][blocks...]
- Each block:
[subsys_id: u64][ts: u64][size: u32][checksum: u32][data: bytes]
- event_summaries.txt (CSV)
- One line per event
- Fields: event_id, timestamp_ns, nsystems, system_ids (semicolon-separated), payload_size, error_flags
- Quick validation without parsing binary
Subsystem ID Mapping
Events can contain data from these subsystems (IDs 1-9):
| ID | Name | Description |
| 1 | TDAQ_Trigger | TDAQ trigger data |
| 2 | TS_DAQ | Target spectrometer DAQ |
| 3 | TS_Trigger | Target spectrometer trigger |
| 4 | Tracker_DAQ | Tracker detector |
| 5 | ECAL_DAQ | Electromagnetic calorimeter |
| 6 | ECAL_Trigger | ECAL trigger |
| 7 | HCAL_DAQ | Hadronic calorimeter |
| 8 | HCAL_Trigger | HCAL trigger |
| 9 | Generic | Other/unknown subsystem |
Validation
Method 1: Check CSV Summary
Example output:
1,51489468331918879,4,1;2;4;5,47140,0
2,51489468448398879,4,1;2;4;5,47120,0
Event 1: 4 subsystems (IDs 1,2,4,5), timestamp 51489468331918879 ns, 47140 bytes total
Method 2: Read Binary File
python EventBuilder/scripts/read_events.py events.bin
Example output:
================================================================================
Event 1:
Event ID: 1
Timestamp: 51489468331918879 ns
Number of blocks: 4
Blocks:
[0] Subsystem ID: 1
Timestamp: 51489468331918879 ns
Payload size: 32 bytes
Data (hex): 06990d66edb60000...
[1] Subsystem ID: 2
Timestamp: 51489468331918879 ns
Payload size: 6736 bytes
...
Method 3: Inspect ROOT File
root output.root
root [0] Events->Draw("Entries$(ECAL_DAQ)")
root [1] Events->Print()
Check:
- Number of entries matches
event_summaries.txt
- Branches present match expected subsystems
- Data sizes are non-zero
Coherence Window & Event Assembly
Coherence Window: 5 milliseconds (5,000,000 nanoseconds)
When a fragment arrives:
- If buffer is empty, set reference time = fragment timestamp
- If fragment is within ±5ms of reference time, add to current event batch
- If fragment is outside the window, finalize current event and start new batch with this fragment
Completeness Check: Event is complete when it contains ≥3 unique subsystems
This ensures:
- Fragments from the same physics event (arriving within detector timing) group together
- Fragments from different beam spills (separated by >5ms) don't merge
- Partial events (missing subsystems) are rejected until they timeout or new fragment outside window triggers assembly
Development Notes
- Reader class: From Packing library, enables incremental binary streaming without loading entire file
- RogueFrameHeader: StreamWriter framing from Rogue DAQ system
- LDMXRoRHeader: Custom header format used by LDMX RoR protocol
- SubsystemPacket: Alternative packet format from Packing/RawDataFile
- Reference time tracking: FragmentBuffer maintains window center for robust multi-subsystem grouping
- No ROOT dictionaries: Event data is stored as native
std::vector<uint8_t> which ROOT serializes natively