EventLoaderEUDAQ2.cpp 24.1 KB
Newer Older
1
2
/**
 * @file
3
 * @brief Implementation of EventLoaderEUDAQ2 module
4
 * @copyright Copyright (c) 2019 CERN and the Corryvreckan authors.
5
6
7
8
9
10
 * This software is distributed under the terms of the MIT License, copied verbatim in the file "LICENSE.md".
 * In applying this license, CERN does not waive the privileges and immunities granted to it by virtue of its status as an
 * Intergovernmental Organization or submit itself to any jurisdiction.
 */

#include "EventLoaderEUDAQ2.h"
11
#include "eudaq/FileReader.hh"
12
13
14

using namespace corryvreckan;

15
16
EventLoaderEUDAQ2::EventLoaderEUDAQ2(Configuration config, std::shared_ptr<Detector> detector)
    : Module(std::move(config), detector), m_detector(detector) {
17

18
    m_filename = m_config.getPath("file_name", true);
19
    m_get_time_residuals = m_config.get<bool>("get_time_residuals", false);
20
    m_get_tag_vectors = m_config.get<bool>("get_tag_vectors", false);
21
    m_ignore_bore = m_config.get<bool>("ignore_bore", true);
22
    m_skip_time = m_config.get("skip_time", 0.);
23
24
    m_adjust_event_times = m_config.getMatrix<std::string>("adjust_event_times", {});
    m_buffer_depth = m_config.get<int>("buffer_depth", 0);
25

26
27
    m_inclusive = m_config.get("inclusive", true);

28
29
30
31
32
33
34
35
36
37
38
39
40
    // Forward all settings to EUDAQ
    // WARNING: the EUDAQ Configuration class is not very flexible and e.g. booleans have to be passed as 1 and 0.
    eudaq::Configuration cfg;
    auto configs = m_config.getAll();
    for(const auto& key : configs) {
        LOG(DEBUG) << "Forwarding key \"" << key.first << " = " << key.second << "\" to EUDAQ converter";
        cfg.Set(key.first, key.second);
    }

    // Converting the newly built configuration to a shared pointer of a cont configuration object
    // Unfortunbately EUDAQ does not provide appropriate member functions for their configuration class to avoid this dance
    const eudaq::Configuration eu_cfg = cfg;
    eudaq_config_ = std::make_shared<const eudaq::Configuration>(eu_cfg);
41
}
42

43
void EventLoaderEUDAQ2::initialise() {
44

45
    // Declare histograms
46
    std::string title = "hitmap;column;row;# events";
47
48
49
50
51
52
53
54
55
    hitmap = new TH2F("hitmap",
                      title.c_str(),
                      m_detector->nPixels().X(),
                      0,
                      m_detector->nPixels().X(),
                      m_detector->nPixels().Y(),
                      0,
                      m_detector->nPixels().Y());

56
57
    title = ";hit time [ms];# events";
    hPixelTimes = new TH1F("hPixelTimes", title.c_str(), 3e6, 0, 3e3);
58

59
    title = ";hit timestamp [s];# events";
60
    hPixelTimes_long = new TH1F("hPixelTimes_long", title.c_str(), 3e6, 0, 3e3);
61

62
    title = ";pixel raw values;# events";
63
    hPixelRawValues = new TH1F("hPixelRawValues", title.c_str(), 1024, 0, 1024);
64

65
66
    title = "Pixel Multiplicity per Corry Event;# pixels;# events";
    hPixelMultiplicity = new TH1F("pixelMultiplicity", title.c_str(), 1000, 0, 1000);
67

68
69
    title = ";EUDAQ event start time[ms];# entries";
    hEudaqEventStart = new TH1D("eudaqEventStart", title.c_str(), 3e6, 0, 3e3);
70

71
72
73
    title = ";EUDAQ event start time[s];# entries";
    hEudaqEventStart_long = new TH1D("eudaqEventStart_long", title.c_str(), 3e6, 0, 3e3);

74
75
    title = "Corryvreckan event start times (on clipboard); Corryvreckan event start time [ms];# entries";
    hClipboardEventStart = new TH1D("clipboardEventStart", title.c_str(), 3e6, 0, 3e3);
76

77
78
79
    title = "Corryvreckan event start times (on clipboard); Corryvreckan event start time [s];# entries";
    hClipboardEventStart_long = new TH1D("clipboardEventStart_long", title.c_str(), 3e6, 0, 3e3);

80
81
    title = "Corryvreckan event end times (on clipboard); Corryvreckan event end time [ms];# entries";
    hClipboardEventEnd = new TH1D("clipboardEventEnd", title.c_str(), 3e6, 0, 3e3);
82

83
84
    title = "Corryvreckan event end times (on clipboard); Corryvreckan event duration [ms];# entries";
    hClipboardEventDuration = new TH1D("clipboardEventDuration", title.c_str(), 3e6, 0, 3e3);
85

86
87
    hTriggersPerEvent = new TH1D("hTriggersPerEvent", "hTriggersPerEvent;triggers per event;entries", 20, 0, 20);

88
    if(m_get_time_residuals) {
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
        hPixelTimeEventBeginResidual =
            new TH1F("hPixelTimeEventBeginResidual",
                     "hPixelTimeEventBeginResidual;pixel_ts - clipboard event begin [us]; # entries",
                     2.1e5,
                     -10,
                     200);

        hPixelTimeEventBeginResidual_wide =
            new TH1F("hPixelTimeEventBeginResidual_wide",
                     "hPixelTimeEventBeginResidual_wide;pixel_ts - clipboard event begin [us]; # entries",
                     1e5,
                     -5000,
                     5000);
        hPixelTimeEventBeginResidualOverTime =
            new TH2F("hPixelTimeEventBeginResidualOverTime",
                     "hPixelTimeEventBeginResidualOverTime; pixel time [s];pixel_ts - clipboard event begin [us]",
                     3e3,
                     0,
                     3e3,
                     2.1e4,
                     -10,
                     200);
111
        std::string histTitle = "hPixelTriggerTimeResidualOverTime_0;time [us];pixel_ts - trigger_ts [us];# entries";
112
113
114
        hPixelTriggerTimeResidualOverTime =
            new TH2D("hPixelTriggerTimeResidualOverTime_0", histTitle.c_str(), 3e3, 0, 3e3, 1e4, -50, 50);
    }
115

116
117
118
119
120
121
122
123
    // open the input file with the eudaq reader
    try {
        reader_ = eudaq::Factory<eudaq::FileReader>::MakeUnique(eudaq::str2hash("native"), m_filename);
    } catch(...) {
        LOG(ERROR) << "eudaq::FileReader could not read the input file ' " << m_filename
                   << " '. Please verify that the path and file name are correct.";
        throw InvalidValueError(m_config, "file_path", "Parsing error!");
    }
124

125
    // Check if all elements of m_adjust_event_times have a valid size of 3, if not throw error.
126
    for(auto& shift_times : m_adjust_event_times) {
127
128
129
        if(shift_times.size() != 3) {
            throw InvalidValueError(
                m_config,
130
                "adjust_event_times",
131
                "Parameter needs 3 values per row: [\"event type\", shift event start, shift event end]");
132
        }
133
    }
134
135
}

136
137
std::shared_ptr<eudaq::StandardEvent> EventLoaderEUDAQ2::get_next_sorted_std_event() {

138
    // Refill the event buffer if necessary:
139
140
141
142
    while(static_cast<int>(sorted_events_.size()) < m_buffer_depth) {
        LOG(DEBUG) << "Filling buffer with new event.";
        // fill buffer with new std event:
        auto new_event = get_next_std_event();
143
        sorted_events_.push(new_event);
144
145
    }

146
147
148
    // get first element of queue and erase it
    auto stdevt = sorted_events_.top();
    sorted_events_.pop();
149
    return stdevt;
150
151
152
}

std::shared_ptr<eudaq::StandardEvent> EventLoaderEUDAQ2::get_next_std_event() {
153
154
155
    auto stdevt = eudaq::StandardEvent::MakeShared();
    bool decoding_failed = true;
    do {
156
157
158
        // Create new StandardEvent
        stdevt = eudaq::StandardEvent::MakeShared();

159
160
161
        // Check if we need a new full event or if we still have some in the cache:
        if(events_.empty()) {
            LOG(TRACE) << "Reading new EUDAQ event from file";
162
            auto new_event = reader_->GetNextEvent();
163
164
165
166
            if(!new_event) {
                LOG(DEBUG) << "Reached EOF";
                throw EndOfFile();
            }
167
168
            // Build buffer from all sub-events:
            events_ = new_event->GetSubEvents();
169
            // The main event might also contain data, so add it to the buffer:
Simon Spannagel's avatar
Simon Spannagel committed
170
            if(events_.empty()) {
171
                events_.push_back(new_event);
Simon Spannagel's avatar
Simon Spannagel committed
172
            }
173
        }
174
        LOG(TRACE) << "Buffer contains " << events_.size() << " (sub-) events:";
175
176
        for(auto& evt : events_) {
            LOG(TRACE) << "  (sub-) event of type " << evt->GetDescription();
177
        }
178
179

        // Retrieve first and remove from buffer:
180
181
        auto event = events_.front();
        events_.erase(events_.begin());
182

183
        // If this is a Begin-of-Run event and we should ignore it, please do so:
184
        if(event->IsBORE() && m_ignore_bore) {
185
186
187
188
            LOG(DEBUG) << "Found EUDAQ2 BORE event, ignoring it";
            continue;
        }

189
        // Read and store tag information:
190
        if(m_get_tag_vectors) {
191
192
            retrieve_event_tags(event);
        }
193

194
        decoding_failed = !eudaq::StdEventConverter::Convert(event, stdevt, eudaq_config_);
195
        LOG(DEBUG) << event->GetDescription() << ": EventConverter returned " << (decoding_failed ? "false" : "true");
196
197
198
    } while(decoding_failed);
    return stdevt;
}
199

200
void EventLoaderEUDAQ2::retrieve_event_tags(const eudaq::EventSPC evt) {
201
202
203
204
205
206
207
208
209
210
    auto tags = evt->GetTags();

    for(auto tag_pair : tags) {
        // Trying to convert tag value to double:
        try {
            double value = std::stod(tag_pair.second);

            // Check if histogram exists already, if not: create it
            if(hTagValues.find(tag_pair.first) == hTagValues.end()) {
                std::string histName = "hTagValues_" + tag_pair.first;
211
                std::string histTitle = "tag_" + tag_pair.first + ";event / 1000;tag value";
212
213
                hTagValues[tag_pair.first] = new TProfile(histName.c_str(), histTitle.c_str(), 2e5, 0, 100);
            }
214
            hTagValues[tag_pair.first]->Fill(evt->GetEventN() / 1000, value, 1);
215
216
217
218
        } catch(std::exception& e) {
        }
    }
}
219
Event::Position EventLoaderEUDAQ2::is_within_event(std::shared_ptr<Clipboard> clipboard,
220
                                                   std::shared_ptr<eudaq::StandardEvent> evt) const {
221

222
    // Check if this event has timestamps available:
223
    if(evt->GetTimeBegin() == 0 && evt->GetTimeEnd() == 0) {
224
        LOG(DEBUG) << evt->GetDescription() << ": Event has no timestamp, comparing trigger IDs";
225

226
        // If there is no event defined yet, there is little we can do:
227
        if(!clipboard->isEventDefined()) {
228
            LOG(DEBUG) << "No Corryvreckan event defined - cannot define without timetamps.";
229
            return Event::Position::UNKNOWN;
230
        }
231

232
        // Get position of this time frame with respect to the defined event:
233
        auto trigger_position = clipboard->getEvent()->getTriggerPosition(evt->GetTriggerN());
234
235
236
237
238
239
240
241
242
243
        if(trigger_position == Event::Position::BEFORE) {
            LOG(DEBUG) << "Trigger ID " << evt->GetTriggerN() << " before triggers registered in Corryvreckan event";
        } else if(trigger_position == Event::Position::AFTER) {
            LOG(DEBUG) << "Trigger ID " << evt->GetTriggerN() << " after triggers registered in Corryvreckan event";
        } else if(trigger_position == Event::Position::UNKNOWN) {
            LOG(DEBUG) << "Trigger ID " << evt->GetTriggerN() << " within Corryvreckan event range but not registered";
        } else {
            LOG(DEBUG) << "Trigger ID " << evt->GetTriggerN() << " found in Corryvreckan event";
        }
        return trigger_position;
244
    }
245

246
    // Read time from EUDAQ2 event and convert from picoseconds to nanoseconds:
247
248
    double event_start = static_cast<double>(evt->GetTimeBegin()) / 1000 + m_detector->timingOffset();
    double event_end = static_cast<double>(evt->GetTimeEnd()) / 1000 + m_detector->timingOffset();
249
250
    // Store the original position of the event before adjusting its length:
    double event_timestamp = event_start;
251
252
    LOG(DEBUG) << "event_start = " << Units::display(event_start, "us")
               << ", event_end = " << Units::display(event_end, "us");
Jens Kroeger's avatar
Jens Kroeger committed
253

254
    // If adjustment of event start/end is required:
255
256
    const auto it = std::find_if(m_adjust_event_times.begin(),
                                 m_adjust_event_times.end(),
257
                                 [evt](const std::vector<std::string>& x) { return x.front() == evt->GetDescription(); });
258

259
260
261
262
263
264
265
266
267
268
269
    if(it != m_adjust_event_times.end()) {
        event_start += corryvreckan::from_string<double>(it->at(1));
        event_end += corryvreckan::from_string<double>(it->at(2));
        LOG(DEBUG) << "Adjusting " << it->at(0) << " event_start by "
                   << Units::display(corryvreckan::from_string<double>(it->at(1)), {"us", "ns"}) << ", event_end by "
                   << Units::display(corryvreckan::from_string<double>(it->at(2)), {"us", "ns"});
        LOG(DEBUG) << "Adjusted event: " << Units::display(event_start, {"us", "ns"}) << " - "
                   << Units::display(event_end, {"us", "ns"}) << ", length "
                   << Units::display(event_end - event_start, {"us", "ns"});
    }

270
271
272
273
    // Skip if later start is requested:
    if(event_start < m_skip_time) {
        LOG(DEBUG) << "Event start before requested skip time: " << Units::display(event_start, {"us", "ns"}) << " < "
                   << Units::display(m_skip_time, {"us", "ns"});
274
        return Event::Position::BEFORE;
275
276
    }

Simon Spannagel's avatar
Simon Spannagel committed
277
    // Check if an event is defined or if we need to create it:
278
    if(!clipboard->isEventDefined()) {
279
        LOG(DEBUG) << "Defining Corryvreckan event: " << Units::display(event_start, {"us", "ns"}) << " - "
Simon Spannagel's avatar
Simon Spannagel committed
280
281
                   << Units::display(event_end, {"us", "ns"}) << ", length "
                   << Units::display(event_end - event_start, {"us", "ns"});
282
        clipboard->putEvent(std::make_shared<Event>(event_start, event_end));
283
284
    } else {
        LOG(DEBUG) << "Corryvreckan event found on clipboard.";
285
    }
286

287
    // Get position of this time frame with respect to the defined event:
288
    auto position = clipboard->getEvent()->getFramePosition(event_start, event_end, m_inclusive);
289
    if(position == Event::Position::BEFORE) {
290
        LOG(DEBUG) << "Event start before Corryvreckan event: " << Units::display(event_start, {"us", "ns"}) << " < "
291
                   << Units::display(clipboard->getEvent()->start(), {"us", "ns"});
292
    } else if(position == Event::Position::AFTER) {
293
        LOG(DEBUG) << "Event end after Corryvreckan event: " << Units::display(event_end, {"us", "ns"}) << " > "
294
                   << Units::display(clipboard->getEvent()->end(), {"us", "ns"});
295
    } else {
296
297
        // check if event has valid trigger ID (flag = 0x10):
        if(evt->IsFlagTrigger()) {
298
            // Store potential trigger numbers, assign to center of event:
299
300
301
            clipboard->getEvent()->addTrigger(evt->GetTriggerN(), event_timestamp);
            LOG(DEBUG) << "Stored trigger ID " << evt->GetTriggerN() << " at "
                       << Units::display(event_timestamp, {"us", "ns"});
302
        }
303
    }
304
305

    return position;
306
}
307

308
309
std::shared_ptr<PixelVector> EventLoaderEUDAQ2::get_pixel_data(std::shared_ptr<eudaq::StandardEvent> evt,
                                                               int plane_id) const {
310

311
    auto pixels = std::make_shared<PixelVector>();
312

313
314
315
316
    // No plane found:
    if(plane_id < 0) {
        return pixels;
    }
317

318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
    auto plane = evt->GetPlane(static_cast<size_t>(plane_id));

    // Concatenate plane name according to naming convention: sensor_type + "_" + int
    auto plane_name = plane.Sensor() + "_" + std::to_string(plane.ID());
    auto detector_name = m_detector->name();
    // Convert to lower case before string comparison to avoid errors by the user:
    std::transform(plane_name.begin(), plane_name.end(), plane_name.begin(), ::tolower);
    std::transform(detector_name.begin(), detector_name.end(), detector_name.begin(), ::tolower);
    LOG(TRACE) << plane_name << " (ID " << plane_id << ") with " << plane.HitPixels() << " pixel hits";

    // Loop over all hits and add to pixels vector:
    for(unsigned int i = 0; i < plane.HitPixels(); i++) {
        auto col = static_cast<int>(plane.GetX(i));
        auto row = static_cast<int>(plane.GetY(i));
        auto raw = static_cast<int>(plane.GetPixel(i)); // generic pixel raw value (could be ToT, ADC, ...)
        auto ts = static_cast<double>(plane.GetTimestamp(i)) / 1000 + m_detector->timingOffset();

        if(col >= m_detector->nPixels().X() || row >= m_detector->nPixels().Y()) {
Lennart Huth's avatar
Lennart Huth committed
336
337
            LOG(WARNING) << "Pixel address " << col << ", " << row << " is outside of pixel matrix with size "
                         << m_detector->nPixels();
338
        }
339

340
341
        if(m_detector->masked(col, row)) {
            LOG(TRACE) << "Masking pixel (col, row) = (" << col << ", " << row << ")";
342
            continue;
343
344
        } else {
            LOG(TRACE) << "Storing (col, row) = (" << col << ", " << row << ") from EUDAQ2 event data";
345
        }
Jens Kroeger's avatar
Jens Kroeger committed
346

347
348
        // when calibration is not available, set charge = raw
        Pixel* pixel = new Pixel(m_detector->name(), col, row, raw, raw, ts);
349

350
351
352
353
        hitmap->Fill(col, row);
        hPixelTimes->Fill(static_cast<double>(Units::convert(ts, "ms")));
        hPixelTimes_long->Fill(static_cast<double>(Units::convert(ts, "s")));
        hPixelRawValues->Fill(raw);
354

355
        pixels->push_back(pixel);
356
    }
357
358
    hPixelMultiplicity->Fill(static_cast<int>(pixels->size()));
    LOG(DEBUG) << m_detector->name() << ": Plane contains " << pixels->size() << " pixels";
359

360
    return pixels;
361
362
}

363
bool EventLoaderEUDAQ2::filter_detectors(std::shared_ptr<eudaq::StandardEvent> evt, int& plane_id) const {
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
    // Check if the detector type matches the currently processed detector type:
    auto detector_type = evt->GetDetectorType();
    std::transform(detector_type.begin(), detector_type.end(), detector_type.begin(), ::tolower);
    // Fall back to parsing the description if not set:
    if(detector_type.empty()) {
        LOG(TRACE) << "Using fallback comparison with EUDAQ2 event description";
        auto description = evt->GetDescription();
        std::transform(description.begin(), description.end(), description.begin(), ::tolower);
        if(description.find(m_detector->type()) == std::string::npos) {
            LOG(DEBUG) << "Ignoring event because description doesn't match type " << m_detector->type() << ": "
                       << description;
            return false;
        }
    } else if(detector_type != m_detector->type()) {
        LOG(DEBUG) << "Ignoring event because detector type doesn't match: " << detector_type;
        return false;
    }

    // To the best of our knowledge, this is the detector we are looking for.
    LOG(DEBUG) << "Found matching event for detector type " << m_detector->type();
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
    if(evt->NumPlanes() == 0) {
        return true;
    }

    // Check if we can identify the detector itself among the planes:
    for(size_t i_plane = 0; i_plane < evt->NumPlanes(); i_plane++) {
        auto plane = evt->GetPlane(i_plane);

        // Concatenate plane name according to naming convention: sensor_type + "_" + int
        auto plane_name = plane.Sensor() + "_" + std::to_string(plane.ID());
        auto detector_name = m_detector->name();
        // Convert to lower case before string comparison to avoid errors by the user:
        std::transform(plane_name.begin(), plane_name.end(), plane_name.begin(), ::tolower);
        std::transform(detector_name.begin(), detector_name.end(), detector_name.begin(), ::tolower);

        if(detector_name == plane_name) {
400
            plane_id = static_cast<int>(i_plane);
401
402
403
404
405
406
407
408
            LOG(DEBUG) << "Found matching plane in event for detector " << m_detector->name();
            return true;
        }
    }

    // Detector not found among planes of this event
    LOG(DEBUG) << "Ignoring event because no matching plane could be found for detector " << m_detector->name();
    return false;
409
410
}

411
412
StatusCode EventLoaderEUDAQ2::run(std::shared_ptr<Clipboard> clipboard) {

413
    auto pixels = std::make_shared<PixelVector>();
414

415
    Event::Position current_position = Event::Position::UNKNOWN;
416
    while(1) {
417
418
419
        // Retrieve next event from file/buffer:
        if(!event_) {
            try {
420
                if(m_buffer_depth == 0) {
421
422
423
424
425
426
                    // simply get next decoded EUDAQ StandardEvent from buffer
                    event_ = get_next_std_event();
                } else {
                    // get next decoded EUDAQ StandardEvent from timesorted buffer
                    event_ = get_next_sorted_std_event();
                }
427
428
            } catch(EndOfFile&) {
                return StatusCode::EndRun;
429
            }
430
        }
431

432
433
434
        // Filter out "wrong" detectors and store plane ID if found:
        int plane_id = -1;
        if(!filter_detectors(event_, plane_id)) {
435
436
437
438
            event_.reset();
            continue;
        }

439
440
        // Check if this event is within the currently defined Corryvreckan event:
        current_position = is_within_event(clipboard, event_);
441

442
        if(current_position == Event::Position::DURING) {
443
            LOG(DEBUG) << "Is within current Corryvreckan event, storing data";
444
            // Store data on the clipboard
445
            auto new_pixels = get_pixel_data(event_, plane_id);
446
            pixels->insert(pixels->end(), new_pixels->begin(), new_pixels->end());
447
        }
448

449
450
        // If this event was after the current event or if we have not enough information, stop reading:
        if(current_position == Event::Position::AFTER || current_position == Event::Position::UNKNOWN) {
451
452
            break;
        }
453

454
        // Do not fill if current_position == Event::Position::AFTER to avoid double-counting!
455
        // Converting EUDAQ2 picoseconds into Corryvreckan nanoseconds:
456
        hEudaqEventStart->Fill(static_cast<double>(event_->GetTimeBegin()) / 1e9);       // here convert from ps to ms
457
        hEudaqEventStart_long->Fill(static_cast<double>(event_->GetTimeBegin()) / 1e12); // here convert from ps to seconds
458
459
460
461
        if(clipboard->isEventDefined()) {
            hClipboardEventStart->Fill(static_cast<double>(Units::convert(clipboard->getEvent()->start(), "ms")));
            hClipboardEventStart_long->Fill(static_cast<double>(Units::convert(clipboard->getEvent()->start(), "s")));
            hClipboardEventEnd->Fill(static_cast<double>(Units::convert(clipboard->getEvent()->end(), "ms")));
462
            hClipboardEventDuration->Fill(
463
                static_cast<double>(Units::convert(clipboard->getEvent()->end() - clipboard->getEvent()->start(), "ms")));
464
        }
465

Simon Spannagel's avatar
Simon Spannagel committed
466
        // Reset this shared event pointer to get a new event from the stack:
467
        event_.reset();
468
    }
469

470
    auto event = clipboard->getEvent();
Simon Spannagel's avatar
Simon Spannagel committed
471
472
    hTriggersPerEvent->Fill(static_cast<double>(event->triggerList().size()));
    LOG(DEBUG) << "Triggers on clipboard event: " << event->triggerList().size();
473
    for(auto& trigger : event->triggerList()) {
Simon Spannagel's avatar
Simon Spannagel committed
474
        LOG(DEBUG) << "\t ID: " << trigger.first << ", time: " << Units::display(trigger.second, "us");
475
476
    }

477
    // Loop over pixels for plotting
478
    if(m_get_time_residuals) {
479
480
481
482
483
484
485
486
487
488
489
490
491
492
        for(auto& pixel : (*pixels)) {
            hPixelTimeEventBeginResidual->Fill(
                static_cast<double>(Units::convert(pixel->timestamp() - event->start(), "us")));
            hPixelTimeEventBeginResidual_wide->Fill(
                static_cast<double>(Units::convert(pixel->timestamp() - event->start(), "us")));
            hPixelTimeEventBeginResidualOverTime->Fill(
                static_cast<double>(Units::convert(pixel->timestamp(), "s")),
                static_cast<double>(Units::convert(pixel->timestamp() - event->start(), "us")));

            size_t iTrigger = 0;
            for(auto& trigger : event->triggerList()) {
                // check if histogram exists already, if not: create it
                if(hPixelTriggerTimeResidual.find(iTrigger) == hPixelTriggerTimeResidual.end()) {
                    std::string histName = "hPixelTriggerTimeResidual_" + to_string(iTrigger);
493
                    std::string histTitle = histName + ";pixel_ts - trigger_ts [us];# entries";
494
495
496
497
498
                    hPixelTriggerTimeResidual[iTrigger] = new TH1D(histName.c_str(), histTitle.c_str(), 2e5, -100, 100);
                }
                // use iTrigger, not trigger ID (=trigger.first) (which is unique and continuously incrementing over the
                // runtime)
                hPixelTriggerTimeResidual[iTrigger]->Fill(
499
                    static_cast<double>(Units::convert(pixel->timestamp() - trigger.second, "us")));
500
501
502
503
504
505
                if(iTrigger == 0) { // fill only for 0th trigger
                    hPixelTriggerTimeResidualOverTime->Fill(
                        static_cast<double>(Units::convert(pixel->timestamp(), "s")),
                        static_cast<double>(Units::convert(pixel->timestamp() - trigger.second, "us")));
                }
                iTrigger++;
506
            }
507
        }
508
509
510
    }

    // Store the full event data on the clipboard:
511
    clipboard->putData(pixels, m_detector->name());
512

513
    LOG(DEBUG) << "Finished Corryvreckan event";
514
    return StatusCode::Success;
515
}