libtcspc C++ API
Streaming TCSPC and time tag data processing
Loading...
Searching...
No Matches
binning.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 "arg_wrappers.hpp"
10#include "common.hpp"
11#include "context.hpp"
12#include "data_types.hpp"
13#include "histogram_events.hpp"
14#include "int_arith.hpp"
15#include "int_types.hpp"
16#include "introspect.hpp"
17#include "processor.hpp"
18
19#include <algorithm>
20#include <cassert>
21#include <cstddef>
22#include <functional>
23#include <iterator>
24#include <limits>
25#include <optional>
26#include <span>
27#include <stdexcept>
28#include <type_traits>
29#include <utility>
30#include <vector>
31
32namespace tcspc {
33
34namespace internal {
35
36template <typename Event, typename DataTypes, typename DataMapper,
37 typename Downstream>
38class map_to_datapoints {
39 static_assert(std::is_same_v<std::invoke_result_t<DataMapper, Event>,
40 typename DataTypes::datapoint_type>);
41
42 static_assert(processor<Downstream, datapoint_event<DataTypes>>);
43
44 DataMapper mapper;
45
46 Downstream downstream;
47
48 public:
49 explicit map_to_datapoints(DataMapper mapper, Downstream downstream)
50 : mapper(std::move(mapper)), downstream(std::move(downstream)) {}
51
52 [[nodiscard]] auto introspect_node() const -> processor_info {
53 return processor_info(this, "map_to_datapoints");
54 }
55
56 [[nodiscard]] auto introspect_graph() const -> processor_graph {
57 return downstream.introspect_graph().push_entry_point(this);
58 }
59
60 void handle(Event const &event) {
61 downstream.handle(
62 datapoint_event<DataTypes>{std::invoke(mapper, event)});
63 }
64
65 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
66 void handle(Event &&event) { handle(static_cast<Event const &>(event)); }
67
68 template <typename OtherEvent>
69 requires handler_for<Downstream, std::remove_cvref_t<OtherEvent>>
70 void handle(OtherEvent &&event) {
71 downstream.handle(std::forward<OtherEvent>(event));
72 }
73
74 void flush() { downstream.flush(); }
75};
76
77} // namespace internal
78
110template <typename Event, typename DataTypes = default_data_types,
111 typename DataMapper, typename Downstream>
112auto map_to_datapoints(DataMapper mapper, Downstream downstream) {
113 return internal::map_to_datapoints<Event, DataTypes, DataMapper,
114 Downstream>(std::move(mapper),
115 std::move(downstream));
116}
117
127template <typename DataTypes = default_data_types> class difftime_data_mapper {
128 public:
130 template <typename Event>
131 auto operator()(Event const &event) const ->
132 typename DataTypes::datapoint_type {
133 static_assert(
134 internal::representable_in<decltype(event.difftime),
135 typename DataTypes::datapoint_type>,
136 "difftime_data_mapper does not allow narrowing conversion");
137 return event.difftime;
138 }
139};
140
150template <typename DataTypes = default_data_types> class count_data_mapper {
151 public:
153 template <typename Event>
154 auto operator()(Event const &event) const ->
155 typename DataTypes::datapoint_type {
156 static_assert(
157 internal::representable_in<decltype(event.count),
158 typename DataTypes::datapoint_type>,
159 "count_data_mapper does not allow narrowing conversion");
160 return event.count;
161 }
162};
163
173template <typename DataTypes = default_data_types> class channel_data_mapper {
174 public:
176 template <typename Event>
177 auto operator()(Event const &event) const ->
178 typename DataTypes::datapoint_type {
179 static_assert(
180 internal::representable_in<decltype(event.channel),
181 typename DataTypes::datapoint_type>,
182 "channel_data_mapper does not allow narrowing conversion");
183 return event.channel;
184 }
185};
186
187namespace internal {
188
189template <typename DataTypes, typename BinMapper, typename Downstream>
190class map_to_bins {
191 static_assert(processor<Downstream, bin_increment_event<DataTypes>>);
192
193 static_assert(
194 std::is_same_v<std::invoke_result_t<
195 BinMapper, typename DataTypes::datapoint_type>,
196 std::optional<typename DataTypes::bin_index_type>>);
197
198 BinMapper bin_mapper;
199 Downstream downstream;
200
201 static_assert(std::is_unsigned_v<typename DataTypes::bin_index_type>,
202 "The bin_index_type must be an unsigned integer type");
203
204 public:
205 explicit map_to_bins(BinMapper bin_mapper, Downstream downstream)
206 : bin_mapper(std::move(bin_mapper)),
207 downstream(std::move(downstream)) {}
208
209 [[nodiscard]] auto introspect_node() const -> processor_info {
210 return processor_info(this, "map_to_bins");
211 }
212
213 [[nodiscard]] auto introspect_graph() const -> processor_graph {
214 return downstream.introspect_graph().push_entry_point(this);
215 }
216
217 template <typename DT> void handle(datapoint_event<DT> const &event) {
218 static_assert(std::is_same_v<typename DT::datapoint_type,
219 typename DataTypes::datapoint_type>);
220 auto bin = std::invoke(bin_mapper, event.value);
221 if (bin)
222 downstream.handle(bin_increment_event<DataTypes>{bin.value()});
223 }
224
225 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
226 template <typename DT> void handle(datapoint_event<DT> &&event) {
227 handle(static_cast<datapoint_event<DT> const &>(event));
228 }
229
230 template <typename OtherEvent>
231 requires handler_for<Downstream, std::remove_cvref_t<OtherEvent>>
232 void handle(OtherEvent &&event) {
233 downstream.handle(std::forward<OtherEvent>(event));
234 }
235
236 void flush() { downstream.flush(); }
237};
238
239} // namespace internal
240
271template <typename DataTypes = default_data_types, typename BinMapper,
272 typename Downstream>
273auto map_to_bins(BinMapper bin_mapper, Downstream downstream) {
274 return internal::map_to_bins<DataTypes, BinMapper, Downstream>(
275 std::move(bin_mapper), std::move(downstream));
276}
277
303template <unsigned NDataBits, unsigned NHistoBits, bool Flip = false,
304 typename DataTypes = default_data_types>
306 static_assert(NHistoBits <= 64); // Assumption made below.
307 static_assert(NDataBits >= NHistoBits);
308 static_assert(NDataBits <= 8 * sizeof(typename DataTypes::datapoint_type));
309 static_assert(NHistoBits <=
310 8 * sizeof(typename DataTypes::bin_index_type));
311 // Bin indices are always non-negative.
312 static_assert(std::is_unsigned_v<typename DataTypes::bin_index_type> ||
313 NHistoBits < 8 * sizeof(typename DataTypes::bin_index_type));
314
316 [[nodiscard]] auto n_bins() const -> std::size_t {
317 return std::size_t{1} << NHistoBits;
318 }
319
321 auto operator()(typename DataTypes::datapoint_type datapoint) const
322 -> std::optional<typename DataTypes::bin_index_type> {
323 using datapoint_type = typename DataTypes::datapoint_type;
324 using bin_index_type = typename DataTypes::bin_index_type;
325
326 static constexpr int shift = NDataBits - NHistoBits;
327 static_assert(shift <= 8 * sizeof(datapoint_type)); // Ensured above.
328 if constexpr (shift == 8 * sizeof(datapoint_type))
329 return bin_index_type{0}; // Shift would be UB.
330
331 // Convert to unsigned (if necessary) _after_ the shift so that
332 // negative datapoints are mapped to indices above max.
333 auto const shifted = internal::ensure_unsigned(datapoint >> shift);
334 static constexpr auto max_bin_index = static_cast<bin_index_type>(
335 NHistoBits == 64 ? std::numeric_limits<u64>::max()
336 : (u64(1) << NHistoBits) - 1);
337 if (shifted > max_bin_index)
338 return std::nullopt;
339
340 auto const bin_index = static_cast<bin_index_type>(shifted);
341 if constexpr (Flip)
342 return static_cast<bin_index_type>(max_bin_index - bin_index);
343 else
344 return bin_index;
345 }
346};
347
356template <typename DataTypes = default_data_types> class linear_bin_mapper {
357 typename DataTypes::datapoint_type off;
358 typename DataTypes::datapoint_type bwidth;
359 typename DataTypes::bin_index_type max_index;
360 bool clamp;
361
362 static_assert(std::is_integral_v<typename DataTypes::datapoint_type>,
363 "datapoint_type must be an integer type");
364 static_assert(std::is_integral_v<typename DataTypes::bin_index_type>,
365 "bin_index_type must be an integer type");
366
367 // Assumptions used by implementation.
368 static_assert(sizeof(typename DataTypes::datapoint_type) <= sizeof(u64));
369 static_assert(sizeof(typename DataTypes::bin_index_type) <= sizeof(u64));
370
371 public:
394 arg::clamp<bool> clamp = arg::clamp{false})
395 : off(offset.value), bwidth(bin_width.value),
396 max_index(max_bin_index.value), clamp(clamp.value) {
397 if (bwidth == 0)
398 throw std::invalid_argument(
399 "linear_bin_mapper bin_width must not be zero");
400 if (max_index < 0)
401 throw std::invalid_argument(
402 "linear_bin_mapper max_bin_index must not be negative");
403 }
404
406 [[nodiscard]] auto n_bins() const -> std::size_t {
407 return std::size_t(max_index) + 1;
408 }
409
411 auto operator()(typename DataTypes::datapoint_type datapoint) const
412 -> std::optional<typename DataTypes::bin_index_type> {
413 using bin_index_type = typename DataTypes::bin_index_type;
414
415 if (bwidth < 0 ? datapoint > off : datapoint < off)
416 return clamp ? std::make_optional(bin_index_type{0})
417 : std::nullopt;
418 // Note we always divide non-negative by positive or non-positive by
419 // negative, to avoid being affected by truncation toward zero.
420 auto const scaled = (datapoint - off) / bwidth;
421 assert(scaled >= 0);
422 if (u64(scaled) > u64(max_index))
423 return clamp ? std::make_optional(max_index) : std::nullopt;
424 return static_cast<bin_index_type>(scaled);
425 }
426};
427
433template <typename T> class unique_bin_mapper_access {
434 std::function<std::vector<T>()> values_fn;
435
436 public:
438 template <typename Func>
439 explicit unique_bin_mapper_access(Func values_func)
440 : values_fn(values_func) {}
441
445 auto values() -> std::vector<T> { return values_fn(); }
446};
447
465template <typename DataTypes = default_data_types> class unique_bin_mapper {
466 using datapoint_type = typename DataTypes::datapoint_type;
467 using bin_index_type = typename DataTypes::bin_index_type;
468 bin_index_type max_index;
469 std::vector<datapoint_type> values;
471
472 public:
483 &&tracker,
485 : max_index(max_bin_index.value), trk(std::move(tracker)) {
486 if (max_index < 0)
487 throw std::invalid_argument(
488 "unique_bin_mapper max_bin_index must not be negative");
489 trk.register_access_factory([](auto &tracker) {
490 auto *self =
493 [self] { return self->values; });
494 });
495 }
496
498 [[nodiscard]] auto n_bins() const -> std::size_t {
499 return std::size_t(max_index) + 1;
500 }
501
503 auto operator()(typename DataTypes::datapoint_type datapoint)
504 -> std::optional<typename DataTypes::bin_index_type> {
505 auto it = std::find(values.begin(), values.end(), datapoint);
506 if (it == values.end()) {
507 values.push_back(datapoint);
508 it = std::prev(values.end());
509 }
510 auto idx = std::distance(values.begin(), it);
511 return u64(idx) <= u64(max_index)
512 ? std::make_optional(bin_index_type(idx))
513 : std::nullopt;
514 }
515};
516
517namespace internal {
518
519template <typename StartEvent, typename StopEvent, typename DataTypes,
520 typename Downstream>
522 static_assert(
523 processor<Downstream, bin_increment_cluster_event<DataTypes>>);
524
525 bool in_cluster = false;
526 std::vector<typename DataTypes::bin_index_type> cur_cluster;
527
528 Downstream downstream;
529
530 public:
531 explicit cluster_bin_increments(Downstream downstream)
532 : downstream(std::move(downstream)) {}
533
534 [[nodiscard]] auto introspect_node() const -> processor_info {
535 return processor_info(this, "cluster_bin_increments");
536 }
537
538 [[nodiscard]] auto introspect_graph() const -> processor_graph {
539 return downstream.introspect_graph().push_entry_point(this);
540 }
541
542 template <typename DT> void handle(bin_increment_event<DT> const &event) {
543 static_assert(std::is_same_v<typename DT::bin_index_type,
544 typename DataTypes::bin_index_type>);
545 if (in_cluster)
546 cur_cluster.push_back(event.bin_index);
547 }
548
549 void handle(StartEvent const & /* event */) {
550 cur_cluster.clear();
551 in_cluster = true;
552 }
553
554 void handle(StopEvent const & /* event */) {
555 if (in_cluster) {
556 auto const e = bin_increment_cluster_event<DataTypes>{
557 ad_hoc_bucket(std::span(cur_cluster))};
558 downstream.handle(e);
559 in_cluster = false;
560 }
561 }
562
563 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
564 template <typename DT> void handle(bin_increment_event<DT> &&event) {
565 handle(static_cast<bin_increment_event<DT> const &>(event));
566 }
567
568 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
569 void handle(StartEvent &&event) {
570 handle(static_cast<StartEvent const &>(event));
571 }
572
573 // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
574 void handle(StopEvent &&event) {
575 handle(static_cast<StopEvent const &>(event));
576 }
577
578 template <typename OtherEvent>
579 requires handler_for<Downstream, std::remove_cvref_t<OtherEvent>>
580 void handle(OtherEvent &&event) {
581 downstream.handle(std::forward<OtherEvent>(event));
582 }
583
584 void flush() { downstream.flush(); }
585};
586
587} // namespace internal
588
615template <typename StartEvent, typename StopEvent,
616 typename DataTypes = default_data_types, typename Downstream>
617auto cluster_bin_increments(Downstream downstream) {
618 return internal::cluster_bin_increments<StartEvent, StopEvent, DataTypes,
619 Downstream>(std::move(downstream));
620}
621
622} // namespace tcspc
Tracker that mediates access to objects via a tcspc::context.
Definition context.hpp:39
Data mapper mapping channel to the data value.
Definition binning.hpp:173
auto operator()(Event const &event) const -> typename DataTypes::datapoint_type
Implements data mapper requirement.
Definition binning.hpp:177
Data mapper mapping count to the data value.
Definition binning.hpp:150
auto operator()(Event const &event) const -> typename DataTypes::datapoint_type
Implements data mapper requirement.
Definition binning.hpp:154
Data mapper mapping difference time to the data value.
Definition binning.hpp:127
auto operator()(Event const &event) const -> typename DataTypes::datapoint_type
Implements data mapper requirement.
Definition binning.hpp:131
linear_bin_mapper(arg::offset< typename DataTypes::datapoint_type > offset, arg::bin_width< typename DataTypes::datapoint_type > bin_width, arg::max_bin_index< typename DataTypes::bin_index_type > max_bin_index, arg::clamp< bool > clamp=arg::clamp{false})
Construct with parameters.
Definition binning.hpp:390
auto n_bins() const -> std::size_t
Implements bin mapper requirement.
Definition binning.hpp:406
auto operator()(typename DataTypes::datapoint_type datapoint) const -> std::optional< typename DataTypes::bin_index_type >
Implements bin mapper requirement.
Definition binning.hpp:411
Access for tcspc::unique_bin_mapper data.
Definition binning.hpp:433
auto values() -> std::vector< T >
Return the datapoint values assigned to bin indices.
Definition binning.hpp:445
auto n_bins() const -> std::size_t
Implements bin mapper requirement.
Definition binning.hpp:498
auto operator()(typename DataTypes::datapoint_type datapoint) -> std::optional< typename DataTypes::bin_index_type >
Implements bin mapper requirement.
Definition binning.hpp:503
unique_bin_mapper(access_tracker< unique_bin_mapper_access< typename DataTypes::datapoint_type > > &&tracker, arg::max_bin_index< typename DataTypes::bin_index_type > max_bin_index)
Construct with context and parameter.
Definition binning.hpp:480
auto ad_hoc_bucket(std::span< T > s) -> bucket< T >
Create a tcspc::bucket referencing a span.
Definition bucket.hpp:489
#define LIBTCSPC_OBJECT_FROM_TRACKER(obj_type, tracker_field_name, tracker)
Recover the object address from a tcspc::access_tracker embedded in the object.
Definition context.hpp:253
auto map_to_datapoints(DataMapper mapper, Downstream downstream)
Create a processor that maps arbitrary time-tagged events to datapoint events.
Definition binning.hpp:112
auto map_to_bins(BinMapper bin_mapper, Downstream downstream)
Create a processor that maps datapoints to histogram bin indices.
Definition binning.hpp:273
auto cluster_bin_increments(Downstream downstream)
Create a processor collecting binned data into clusters.
Definition binning.hpp:617
std::uint64_t u64
Short name for uint64_t.
Definition int_types.hpp:33
libtcspc namespace.
Definition acquire.hpp:29
Function argument wrapper for bin width parameter.
Definition arg_wrappers.hpp:57
Function argument wrapper for clamp parameter.
Definition arg_wrappers.hpp:87
Function argument wrapper for maximum bin index parameter.
Definition arg_wrappers.hpp:217
Function argument wrapper for offset parameter.
Definition arg_wrappers.hpp:347
The default data type set.
Definition data_types.hpp:24
Bin mapper that discards the least significant bits.
Definition binning.hpp:305
auto operator()(typename DataTypes::datapoint_type datapoint) const -> std::optional< typename DataTypes::bin_index_type >
Implements bin mapper requirement.
Definition binning.hpp:321
auto n_bins() const -> std::size_t
Implements bin mapper requirement.
Definition binning.hpp:316