libtcspc C++ API
Streaming TCSPC and time tag data processing
Loading...
Searching...
No Matches
picoquant_t3.hpp
1/*
2 * This file is part of libtcspc
3 * Copyright 2019-2026 Board of Regents of the University of Wisconsin System
4 * SPDX-License-Identifier: MIT
5 */
6
7#pragma once
8
9#include "common.hpp"
10#include "core.hpp"
11#include "data_types.hpp"
12#include "int_arith.hpp"
13#include "int_types.hpp"
14#include "introspect.hpp"
15#include "npint.hpp"
16#include "npint_ops.hpp"
17#include "processor.hpp"
18#include "read_integers.hpp"
19#include "time_tagged_events.hpp"
20
21#include <array>
22#include <cstddef>
23#include <ostream>
24#include <span>
25#include <stdexcept>
26#include <type_traits>
27#include <utility>
28
29// When editing this file, maintain the partial symmetry with picoquant_t2.hpp.
30
31namespace tcspc {
32
33// PicoQuant raw time tag event ("TTTR") formats are documented in the html
34// files contained in this repository:
35// https://github.com/PicoQuant/PicoQuant-Time-Tagged-File-Format-Demos
36
37// Vendor documentation does not specify, but the 32-bit records are to be
38// viewed as little-endian integers when interpreting the documented bit
39// locations.
40
41// The two T3 formats (pqt3_picoharp300_event and basic_pqt3_event) use
42// matching member names for static polymorphism. This allows
43// decode_pqt3<PQT3Event> to handle 3 different formats with the same code.
44
56 std::array<std::byte, 4> bytes;
57
61 static constexpr i32 nsync_overflow_period = 65536;
62
66 [[nodiscard]] constexpr auto channel() const noexcept -> u8np {
67 return read_u8_at<3>(std::span(bytes)) >> 4;
68 }
69
73 [[nodiscard]] constexpr auto dtime() const noexcept -> u16np {
74 return read_u16le_at<2>(std::span(bytes)) & 0x0fff_u16np;
75 }
76
81 [[nodiscard]] constexpr auto nsync() const noexcept -> u16np {
82 return read_u16le_at<0>(std::span(bytes));
83 }
84
88 [[nodiscard]] constexpr auto is_special() const noexcept -> bool {
89 return channel() == 15_u8np;
90 }
91
95 [[nodiscard]] constexpr auto is_nsync_overflow() const noexcept -> bool {
96 return is_special() && dtime() == 0_u16np;
97 }
98
103 [[nodiscard]] static constexpr auto nsync_overflow_count() noexcept
104 -> u16np {
105 return 1_u16np;
106 }
107
111 [[nodiscard]] constexpr auto is_external_marker() const noexcept -> bool {
112 // Vendor docs do not specifically say markers are 4 bits in PicoHarp
113 // 300 T3, but they are in a 4-bit field in T2, and in
114 // HydraHarp/Generic T2/T3 the marker bits explicitly range 1-15. Let's
115 // just behave consistently here and check the upper limit.
116 return is_special() && dtime() > 0_u16np && dtime() <= 15_u16np;
117 }
118
123 [[nodiscard]] constexpr auto external_marker_bits() const noexcept
124 -> u16np {
125 return dtime();
126 }
127
139 static constexpr auto make_nonspecial(u16np nsync, u8np channel,
140 u16np dtime)
142 if (channel > 14_u8np)
143 throw std::invalid_argument(
144 "pqt3_picoharp300_event channel must be in the range 0-14");
145 return make_from_fields(channel, dtime, nsync);
146 }
147
153 static constexpr auto make_nsync_overflow() noexcept
155 return make_from_fields(15_u8np, 0_u16np, 0_u16np);
156 }
157
167 static constexpr auto make_external_marker(u16np nsync, u8np marker_bits)
169 if (marker_bits == 0_u8np)
170 throw std::invalid_argument(
171 "pqt3_picoharp300_event marker_bits must not be zero");
172 return make_from_fields(15_u8np, u16np(marker_bits & 0x0f_u8np),
173 nsync);
174 }
175
177 friend auto operator==(pqt3_picoharp300_event const &lhs,
178 pqt3_picoharp300_event const &rhs) noexcept
179 -> bool = default;
180
182 friend auto operator<<(std::ostream &stream,
183 pqt3_picoharp300_event const &event)
184 -> std::ostream & {
185 return stream << "pqt3_picoharp(channel="
186 << unsigned(event.channel().value())
187 << ", dtime=" << event.dtime()
188 << ", nsync=" << event.nsync() << ")";
189 }
190
191 private:
192 static constexpr auto make_from_fields(u8np channel, u16np dtime,
193 u16np nsync)
196 std::byte(u8np(nsync).value()),
197 std::byte(u8np(nsync >> 8).value()),
198 std::byte(u8np(dtime).value()),
199 std::byte(
200 ((channel << 4) | (u8np(dtime >> 8) & 0x0f_u8np)).value()),
201 }};
202 }
203};
204
218template <bool IsNSyncOverflowAlwaysSingle> struct basic_pqt3_event {
222 std::array<std::byte, 4> bytes;
223
227 static constexpr i32 nsync_overflow_period = 1024;
228
232 [[nodiscard]] constexpr auto channel() const noexcept -> u8np {
233 return (read_u8_at<3>(std::span(bytes)) & 0x7f_u8np) >> 1;
234 }
235
239 [[nodiscard]] constexpr auto dtime() const noexcept -> u16np {
240 auto const lo6 = u16np(read_u8_at<1>(std::span(bytes))) >> 2;
241 auto const mid8 = u16np(read_u8_at<2>(std::span(bytes)));
242 auto const hi1 = u16np(read_u8_at<3>(std::span(bytes))) & 1_u16np;
243 return lo6 | (mid8 << 6) | (hi1 << 14);
244 }
245
250 [[nodiscard]] constexpr auto nsync() const noexcept -> u16np {
251 return read_u16le_at<0>(std::span(bytes)) & 0x03ff_u16np;
252 }
253
257 [[nodiscard]] constexpr auto is_special() const noexcept -> bool {
258 return (read_u8_at<3>(std::span(bytes)) & (1_u8np << 7)) != 0_u8np;
259 }
260
264 [[nodiscard]] constexpr auto is_nsync_overflow() const noexcept -> bool {
265 return is_special() && channel() == 63_u8np;
266 }
267
272 [[nodiscard]] constexpr auto nsync_overflow_count() const noexcept
273 -> u16np {
274 if (IsNSyncOverflowAlwaysSingle)
275 return 1_u16np;
276 return nsync();
277 }
278
282 [[nodiscard]] constexpr auto is_external_marker() const noexcept -> bool {
283 return is_special() && channel() > 0_u8np && channel() <= 15_u8np;
284 }
285
290 [[nodiscard]] constexpr auto external_marker_bits() const noexcept
291 -> u8np {
292 return channel();
293 }
294
306 static constexpr auto make_nonspecial(u16np nsync, u8np channel,
308 return make_from_fields(false, channel, dtime, nsync);
309 }
310
321 static constexpr auto make_nsync_overflow(u16np count)
323 static_assert(
324 not IsNSyncOverflowAlwaysSingle,
325 "multiple nsync overflow is not available in HydraHarp V1 format");
326 return make_from_fields(true, 63_u8np, 0_u16np, count);
327 }
328
334 static constexpr auto make_nsync_overflow() noexcept -> basic_pqt3_event {
335 return make_from_fields(true, 63_u8np, 0_u16np, 1_u16np);
336 }
337
347 static constexpr auto make_external_marker(u16np nsync, u8np marker_bits)
349 return make_from_fields(true, marker_bits, 0_u16np, nsync);
350 }
351
353 friend auto operator==(basic_pqt3_event const &lhs,
354 basic_pqt3_event const &rhs) noexcept
355 -> bool = default;
356
358 friend auto operator<<(std::ostream &stream, basic_pqt3_event const &event)
359 -> std::ostream & {
360 static constexpr auto version = IsNSyncOverflowAlwaysSingle ? 1 : 2;
361 return stream << "pqt3_hydraharpv" << version
362 << "(special=" << event.is_special()
363 << ", channel=" << unsigned(event.channel().value())
364 << ", dtime=" << event.dtime()
365 << ", nsync=" << event.nsync() << ")";
366 }
367
368 private:
369 static constexpr auto make_from_fields(bool special, u8np channel,
372 return basic_pqt3_event{{
373 std::byte(u8np(nsync).value()),
374 std::byte(
375 (u8np(dtime << 2) | (u8np(nsync >> 8) & 0x03_u8np)).value()),
376 std::byte(u8np(dtime >> 6).value()),
377 std::byte(((u8np(u8(special)) << 7) |
378 ((channel & 0x3f_u8np) << 1) |
379 (u8np(dtime >> 14) & 0x01_u8np))
380 .value()),
381 }};
382 }
383};
384
393
403
404namespace internal {
405
406// Common implementation for decode_pqt3_*.
407// PQT3Event is the binary record event class.
408template <typename DataTypes, typename PQT3Event, typename Downstream>
409class decode_pqt3 {
413
414 // 16-bit abstime would overflow so fast that we just prohibit. 32-bit can
415 // work for a few seconds, though 64-bit is recommended.
416 static_assert(sizeof(typename DataTypes::abstime_type) >= 4);
417 static_assert(std::in_range<typename DataTypes::channel_type>(63));
418 static_assert(std::in_range<typename DataTypes::difftime_type>(32767));
419
420 using abstime_type = typename DataTypes::abstime_type;
421
422 abstime_type nsync_base = 0;
423
424 Downstream downstream;
425
426 LIBTCSPC_NOINLINE void issue_warning(char const *message) {
427 downstream.handle(warning_event{message});
428 }
429
430 public:
431 explicit decode_pqt3(Downstream downstream)
432 : downstream(std::move(downstream)) {}
433
434 [[nodiscard]] auto introspect_node() const -> processor_info {
435 return processor_info(this, "decode_pqt3");
436 }
437
438 [[nodiscard]] auto introspect_graph() const -> processor_graph {
439 return downstream.introspect_graph().push_entry_point(this);
440 }
441
442 template <typename Event>
443 requires std::convertible_to<std::remove_cvref_t<Event>, PQT3Event>
444 void handle(Event &&event) {
445 if (event.is_nsync_overflow()) {
446 nsync_base += abstime_type(PQT3Event::nsync_overflow_period) *
447 event.nsync_overflow_count().value();
448 return downstream.handle(
450 }
451
452 abstime_type const nsync = nsync_base + event.nsync().value();
453
454 if (not event.is_special()) {
456 nsync, event.channel().value(), event.dtime().value()});
457 } else if (event.is_external_marker()) {
458 for_each_set_bit(u32np(event.external_marker_bits()), [&](int b) {
459 downstream.handle(marker_event<DataTypes>{nsync, b});
460 });
461 } else {
462 issue_warning("invalid special event encountered");
463 }
464 }
465
466 template <typename Event>
467 requires(
468 not std::convertible_to<std::remove_cvref_t<Event>, PQT3Event> and
470 void handle(Event &&event) {
471 downstream.handle(std::forward<Event>(event));
472 }
473
474 void flush() { downstream.flush(); }
475};
476} // namespace internal
477
501template <typename DataTypes = default_data_types, typename Downstream>
502auto decode_pqt3_picoharp300(Downstream downstream) {
503 return internal::decode_pqt3<DataTypes, pqt3_picoharp300_event,
504 Downstream>(std::move(downstream));
505}
506
530template <typename DataTypes = default_data_types, typename Downstream>
531auto decode_pqt3_hydraharpv1(Downstream downstream) {
532 return internal::decode_pqt3<DataTypes, pqt3_hydraharpv1_event,
533 Downstream>(std::move(downstream));
534}
535
560template <typename DataTypes = default_data_types, typename Downstream>
561auto decode_pqt3_generic(Downstream downstream) {
562 return internal::decode_pqt3<DataTypes, pqt3_generic_event, Downstream>(
563 std::move(downstream));
564}
565
566} // namespace tcspc
Value type representing a directed acyclic graph of processors.
Definition introspect.hpp:198
Value type representing metadata of a processor.
Definition introspect.hpp:58
Concept that is satisfied when a processor handles the given event types.
Definition processor.hpp:118
Concept that is satisfied when a processor handles the given event types and flush.
Definition processor.hpp:143
basic_pqt3_event< true > pqt3_hydraharpv1_event
Binary record interpretation for HydraHarp V1 T3 format.
Definition picoquant_t3.hpp:392
basic_pqt3_event< false > pqt3_generic_event
Binary record interpretation for HydraHarp V2, MultiHarp, TimeHarp 260, and PicoHarp 330 "Generic" T3...
Definition picoquant_t3.hpp:402
npint< u32 > u32np
Non-promoted unsigned 32-bit integer.
Definition npint.hpp:306
constexpr auto read_u8_at(std::span< T, N > bytes) noexcept -> u8np
Read an 8-bit unsigned integer from bytes at offset.
Definition read_integers.hpp:77
npint< u8 > u8np
Non-promoted unsigned 8-bit integer.
Definition npint.hpp:292
npint< u16 > u16np
Non-promoted unsigned 16-bit integer.
Definition npint.hpp:299
constexpr auto read_u16le_at(std::span< T, N > bytes) noexcept -> u16np
Read a little-endian 16-bit unsigned integer from bytes at offset.
Definition read_integers.hpp:90
auto decode_pqt3_picoharp300(Downstream downstream)
Create a processor that decodes PicoQuant PicoHarp 300 T3 events.
Definition picoquant_t3.hpp:502
auto decode_pqt3_generic(Downstream downstream)
Create a processor that decodes PicoQuant HydraHarp V2, MultiHarp, TimeHarp 260, and PicoHarp 330 "Ge...
Definition picoquant_t3.hpp:561
auto decode_pqt3_hydraharpv1(Downstream downstream)
Create a processor that decodes PicoQuant HydraHarp V1 T3 events.
Definition picoquant_t3.hpp:531
auto count(access_tracker< count_access > &&tracker, Downstream downstream)
Create a processor that counts events of a given type.
Definition count.hpp:313
std::int32_t i32
Short name for int32_t.
Definition int_types.hpp:42
std::uint8_t u8
Short name for uint8_t.
Definition int_types.hpp:24
libtcspc namespace.
Definition acquire.hpp:29
Implementation for binary record interpretation for HydraHarp, MultiHarp, TimeHarp 260,...
Definition picoquant_t3.hpp:218
static constexpr auto make_external_marker(u16np nsync, u8np marker_bits) -> basic_pqt3_event
Make an event representing an external marker.
Definition picoquant_t3.hpp:347
static constexpr auto make_nsync_overflow() noexcept -> basic_pqt3_event
Make an event representing a single nsync overflow.
Definition picoquant_t3.hpp:334
friend auto operator==(basic_pqt3_event const &lhs, basic_pqt3_event const &rhs) noexcept -> bool=default
Equality comparison operator.
constexpr auto dtime() const noexcept -> u16np
Read the difference time if this event is a non-special event.
Definition picoquant_t3.hpp:239
constexpr auto is_special() const noexcept -> bool
Determine if this event is a special event.
Definition picoquant_t3.hpp:257
std::array< std::byte, 4 > bytes
Definition picoquant_t3.hpp:222
constexpr auto is_nsync_overflow() const noexcept -> bool
Determine if this event represents an nsync overflow.
Definition picoquant_t3.hpp:264
constexpr auto nsync_overflow_count() const noexcept -> u16np
Read the nsync overflow count if this event represents an nsync overflow.
Definition picoquant_t3.hpp:272
constexpr auto external_marker_bits() const noexcept -> u8np
Read the marker bits (mask) if this event represents external markers.
Definition picoquant_t3.hpp:290
static constexpr auto make_nonspecial(u16np nsync, u8np channel, u16np dtime) -> basic_pqt3_event
Make an event representing a non-special (photon) event.
Definition picoquant_t3.hpp:306
constexpr auto nsync() const noexcept -> u16np
Read the nsync counter value if this event is a non-special event or an external marker event.
Definition picoquant_t3.hpp:250
friend auto operator<<(std::ostream &stream, basic_pqt3_event const &event) -> std::ostream &
Stream insertion operator.
Definition picoquant_t3.hpp:358
constexpr auto channel() const noexcept -> u8np
Read the channel if this event is a non-special event.
Definition picoquant_t3.hpp:232
static constexpr i32 nsync_overflow_period
Definition picoquant_t3.hpp:227
static constexpr auto make_nsync_overflow(u16np count) -> basic_pqt3_event
Make an event representing an nsync overflow.
Definition picoquant_t3.hpp:321
constexpr auto is_external_marker() const noexcept -> bool
Determine if this event represents external markers.
Definition picoquant_t3.hpp:282
Event indicating a timing marker.
Definition time_tagged_events.hpp:320
Binary record interpretation for PicoHarp 300 T3 Format.
Definition picoquant_t3.hpp:52
static constexpr auto make_nonspecial(u16np nsync, u8np channel, u16np dtime) -> pqt3_picoharp300_event
Make an event representing a non-special (photon) event.
Definition picoquant_t3.hpp:139
constexpr auto external_marker_bits() const noexcept -> u16np
Read the marker bits (mask) if this event represents external markers.
Definition picoquant_t3.hpp:123
constexpr auto channel() const noexcept -> u8np
Read the channel if this event is a non-special event.
Definition picoquant_t3.hpp:66
constexpr auto dtime() const noexcept -> u16np
Read the difference time if this event is a non-special event.
Definition picoquant_t3.hpp:73
constexpr auto nsync() const noexcept -> u16np
Read the nsync counter value if this event is a non-special event or an external marker event.
Definition picoquant_t3.hpp:81
constexpr auto is_external_marker() const noexcept -> bool
Determine if this event represents external markers.
Definition picoquant_t3.hpp:111
std::array< std::byte, 4 > bytes
Bytes of the 32-bit raw device event.
Definition picoquant_t3.hpp:56
constexpr auto is_nsync_overflow() const noexcept -> bool
Determine if this event represents an nsync overflow.
Definition picoquant_t3.hpp:95
static constexpr auto nsync_overflow_count() noexcept -> u16np
Read the nsync overflow count if this event represents an nsync overflow.
Definition picoquant_t3.hpp:103
friend auto operator==(pqt3_picoharp300_event const &lhs, pqt3_picoharp300_event const &rhs) noexcept -> bool=default
Equality comparison operator.
friend auto operator<<(std::ostream &stream, pqt3_picoharp300_event const &event) -> std::ostream &
Stream insertion operator.
Definition picoquant_t3.hpp:182
static constexpr auto make_external_marker(u16np nsync, u8np marker_bits) -> pqt3_picoharp300_event
Make an event representing an external marker.
Definition picoquant_t3.hpp:167
static constexpr i32 nsync_overflow_period
The nsync overflow period of this event type.
Definition picoquant_t3.hpp:61
static constexpr auto make_nsync_overflow() noexcept -> pqt3_picoharp300_event
Make an event representing an nsync overflow.
Definition picoquant_t3.hpp:153
constexpr auto is_special() const noexcept -> bool
Determine if this event is a special event.
Definition picoquant_t3.hpp:88
Event indicating a detected count (typically photon) with difference time.
Definition time_tagged_events.hpp:267
Event indicating latest abstime reached.
Definition time_tagged_events.hpp:41
An event type indicating a warning.
Definition core.hpp:31