ReUseX
0.0.5
3D Point Cloud Processing for Building Reuse
Toggle main menu visibility
Loading...
Searching...
No Matches
Dataloader.hpp
Go to the documentation of this file.
1
// SPDX-FileCopyrightText: 2026 Povl Filip Sonne-Frederiksen
2
//
3
// SPDX-License-Identifier: GPL-3.0-or-later
4
#pragma once
5
#include "reusex/vision/IData.hpp"
6
#include "reusex/vision/IDataset.hpp"
7
8
#include <atomic>
9
#include <condition_variable>
10
#include <cstdint>
11
#include <map>
12
#include <memory>
13
#include <mutex>
14
#include <optional>
15
#include <span>
16
#include <thread>
17
#include <vector>
18
19
namespace
reusex::vision
{
20
21
/* * Dataloader is a class that provides an iterable interface to a dataset.
22
* It loads batches of data from the dataset in a separate thread and provides
23
* them to the user when requested. It supports shuffling, multiple worker
24
* threads, and prefetching batches. The user can iterate over the dataloader
25
* using a range-based for loop or by manually creating an iterator. The
26
* dataloader will automatically stop the worker threads when the iteration is
27
* complete or when the dataloader is destroyed.
28
* */
29
class
Dataloader
{
30
public
:
31
using
Pair
=
IDataset::Pair
;
32
using
Batch
= std::vector<Pair>;
33
using
BatchView
= std::span<Pair>;
34
35
/* * The default seed used for deterministic shuffling when the caller does
36
* not request entropy. Per docs/STANDARDS.md §6, algorithms with randomness
37
* default to a fixed seed (42) so runs are reproducible; pass
38
* std::nullopt to opt into non-determinism via std::random_device.
39
* */
40
static
constexpr
uint32_t
default_seed
= 42;
41
42
/* * Constructs a Dataloader for the given dataset with the specified batch
43
* size, shuffle option, number of worker threads, and number of prefetch
44
* batches.
45
* @param dataset The dataset to load data from.
46
* @param batch_size The number of samples in each batch.
47
* @param shuffle Whether to shuffle the dataset at the beginning of each
48
* epoch.
49
* @param num_workers The number of worker threads to use for loading batches.
50
* @param prefetch_batches The number of batches to prefetch in the
51
* background.
52
* @param seed Seed for the shuffle RNG. Defaults to a fixed value
53
* (default_seed) so shuffling is deterministic and runs are reproducible.
54
* Pass std::nullopt to seed from std::random_device (non-deterministic).
55
* Ignored when shuffle is false.
56
* */
57
Dataloader
(
IDataset
&dataset,
size_t
batch_size,
bool
shuffle =
false
,
58
size_t
num_workers = 4,
size_t
prefetch_batches = 2,
59
std::optional<uint32_t> seed =
default_seed
);
60
61
~Dataloader
();
62
63
/* * Produces the (optionally shuffled) index order for a dataset of the given
64
* size. When seed holds a value, the shuffle is deterministic and repeatable;
65
* when seed is std::nullopt, entropy is drawn from std::random_device. This
66
* is the single source of truth for shuffle ordering, exposed as a static
67
* helper so the ordering can be unit-tested without a live dataset.
68
* @param count The number of indices to produce (0..count-1).
69
* @param seed Optional RNG seed; std::nullopt draws entropy from the device.
70
* @return A vector of size count containing a permutation of [0, count).
71
* */
72
static
std::vector<size_t>
shuffled_indices
(
size_t
count,
73
std::optional<uint32_t> seed);
74
75
/* * Iterator is a class that provides an input iterator interface to the
76
* Dataloader. It allows the user to iterate over the batches of data in the
77
* dataloader using a range-based for loop or by manually creating an
78
* iterator. The iterator will automatically load batches from the dataloader
79
* as needed and will stop when the iteration is complete.
80
* */
81
class
Iterator
{
82
public
:
83
using
iterator_category
= std::input_iterator_tag;
84
using
value_type
=
Batch
;
85
using
difference_type
= std::ptrdiff_t;
86
using
pointer
=
Batch
*;
87
using
reference
=
Batch
&;
88
89
/* * Constructs an iterator for the given dataloader and batch index.
90
* @param loader The dataloader to iterate over.
91
* @param batch_idx The index of the batch to start iterating from.
92
* */
93
Iterator
(
Dataloader
*loader,
size_t
batch_idx);
94
95
/* * Copy constructor and copy assignment operator for the iterator. The
96
* current batch is not copied and will be reloaded when the iterator is
97
* dereferenced.
98
* @param other The iterator to copy from.
99
* @return A reference to the copied iterator.
100
* */
101
Iterator
(
const
Iterator
&other)
102
: loader_(other.loader_), batch_idx_(other.batch_idx_),
103
current_batch_(std::nullopt) {}
104
105
/* * Copy assignment operator for the iterator. The current batch is not
106
* copied and will be reloaded when the iterator is dereferenced.
107
* @param other The iterator to copy from.
108
* @return A reference to the copied iterator.
109
* */
110
Iterator
&
operator=
(
const
Iterator
&other) {
111
if
(
this
!= &other) {
112
loader_ = other.loader_;
113
batch_idx_ = other.batch_idx_;
114
current_batch_ = std::nullopt;
115
}
116
return
*
this
;
117
}
118
119
/* * Move constructor and move assignment operator for the iterator. The
120
* current batch is not moved and will be reloaded when the iterator is
121
* dereferenced.
122
* @param other The iterator to move from.
123
* @return A reference to the moved iterator.
124
* */
125
Iterator
(
Iterator
&&) =
default
;
126
127
/* * Move assignment operator for the iterator. The current batch is not
128
* moved and will be reloaded when the iterator is dereferenced.
129
* @param other The iterator to move from.
130
* @return A reference to the moved iterator.
131
* */
132
Iterator
&
operator=
(
Iterator
&&) =
default
;
133
134
/* * Dereference operator for the iterator. It returns a view of the current
135
* batch of data. If the current batch is not loaded, it will be loaded
136
* from the dataloader.
137
* @return A view of the current batch of data.
138
* @throws std::runtime_error if no batch is available for this index
139
* because the epoch has already finished — i.e. the loader was stopped
140
* (reconfigured or destroyed) while this iterator was live. Dereferencing
141
* an iterator of a stopped epoch is a caller error, and is reported rather
142
* than silently reading an empty optional (#280).
143
* */
144
BatchView
operator*
()
const
;
145
146
/* * Moves the current batch out of the iterator. If the current batch is
147
* not loaded, it will be loaded from the dataloader first.
148
* @return An rvalue reference to the current batch.
149
* @throws std::runtime_error under the same stopped-epoch condition as
150
* operator*.
151
* */
152
Batch
&&
move_batch
();
153
154
/* * Pre-increment operator for the iterator. It advances the iterator to
155
* the next batch of data. If the next batch is not loaded, it will be
156
* loaded from the dataloader.
157
* @return A reference to the advanced iterator.
158
* */
159
Iterator
&
operator++
();
160
161
/* * Post-increment operator for the iterator. It is deleted to prevent
162
* inefficient copying of batches. Use the pre-increment operator instead.
163
* */
164
Iterator
operator++
(
int
) =
delete
;
165
166
/* * Equality operator for the iterator. It checks if two iterators are
167
* equal by comparing their dataloader pointers and batch indices.
168
* @param other The iterator to compare with.
169
* @return True if the iterators are equal, false otherwise.
170
* */
171
bool
operator==
(
const
Iterator
&other)
const
;
172
173
/* * Inequality operator for the iterator. It checks if two iterators are
174
* not equal by comparing their dataloader pointers and batch indices.
175
* @param other The iterator to compare with.
176
* @return True if the iterators are not equal, false otherwise.
177
* */
178
bool
operator!=
(
const
Iterator
&other)
const
;
179
180
private
:
181
/* * The dataloader that this iterator belongs to. It is used to load
182
* batches of data when the iterator is dereferenced or advanced.
183
* */
184
Dataloader
*loader_;
185
/* * The index of the current batch that this iterator points to. It is used
186
* to determine which batch to load from the dataloader when the iterator is
187
* dereferenced or advanced.
188
* */
189
size_t
batch_idx_;
190
/* * The current batch of data that this iterator points to. It is stored as
191
* an optional value because it may not be loaded yet. When the iterator is
192
* dereferenced or advanced, the current batch will be loaded from the
193
* dataloader if it is not already loaded.
194
* */
195
mutable
std::optional<Batch> current_batch_;
196
};
197
198
/* * Returns an iterator to the beginning of the dataloader. The iterator will
199
* point to the first batch of data in the dataloader.
200
* @return An iterator to the beginning of the dataloader.
201
* */
202
Iterator
begin
();
203
204
/* * Returns an iterator to the end of the dataloader. The iterator will point
205
* to one past the last batch of data in the dataloader.
206
* @return An iterator to the end of the dataloader.
207
* */
208
Iterator
end
();
209
210
/* * Returns the total number of batches in the dataloader. This is calculated
211
* based on the size of the dataset and the batch size.
212
* @return The total number of batches in the dataloader.
213
* */
214
size_t
size
()
const
;
215
216
/* * Sets the number of worker threads to use for loading batches. This will
217
* affect the performance of the dataloader, as more worker threads can load
218
* batches in parallel, but may also increase the overhead of thread
219
* management. The default number of worker threads is 4.
220
* @param num_workers The number of worker threads to use for loading batches.
221
* */
222
void
set_num_workers
(
size_t
num_workers);
223
224
/* * Sets the number of batches to prefetch in the background. This will
225
* affect the performance of the dataloader, as more prefetch batches can
226
* reduce the waiting time for batches to be loaded, but may also increase the
227
* memory usage of the dataloader. The default number of prefetch batches is
228
* 2.
229
* @param prefetch_batches The number of batches to prefetch in the
230
* background.
231
* */
232
void
set_prefetch_batches
(
size_t
prefetch_batches);
233
234
/* * Returns the number of worker threads currently used for loading batches.
235
* @return The number of worker threads currently used for loading batches.
236
* */
237
size_t
get_num_workers
()
const
;
238
239
/* * Returns the number of batches currently prefetched in the background.
240
* @return The number of batches currently prefetched in the background.
241
* */
242
size_t
get_prefetch_batches
()
const
;
243
244
private
:
245
/* * Starts the worker threads for loading batches. This function is called at
246
* the beginning of each epoch to initialize the worker threads and start
247
* loading batches from the dataset. The worker threads will continue to load
248
* batches in the background until the epoch is finished or the dataloader is
249
* destroyed.
250
* */
251
void
start_epoch();
252
253
/* * Stops the worker threads for loading batches. This function is called at
254
* the end of each epoch or when the dataloader is destroyed to signal the
255
* worker threads to stop loading batches and exit. The worker threads will
256
* check for this signal and exit gracefully when it is set.
257
* */
258
void
stop();
259
260
/* * The function that each worker thread runs to load batches of data from
261
* the dataset. Each worker thread will continuously load batches of data from
262
* the dataset and add them to the batch queue until the epoch is finished or
263
* the dataloader is destroyed. The worker threads will synchronize access to
264
* the batch queue using mutexes and condition variables to ensure thread
265
* safety.
266
* */
267
void
worker_thread();
268
269
/* * Loads a batch of data from the dataset for the given batch index. This
270
* function is called by the worker threads to load batches of data from the
271
* dataset. It will calculate the indices of the samples in the batch based on
272
* the batch index and batch size, and then load the corresponding samples
273
* from the dataset. The loaded batch will be returned as a vector of pairs.
274
* @param batch_idx The index of the batch to load.
275
* @return A vector of pairs representing the loaded batch of data.
276
* */
277
Batch
load_batch(
size_t
batch_idx);
278
279
/* * Retrieves a batch of data from the batch queue for the given batch index.
280
* This function is called by the iterator to retrieve batches of data from
281
* the dataloader. It will check if the requested batch is already loaded in
282
* the batch queue, and if so, it will return it. If the requested batch is
283
* not loaded yet, it will wait for the worker threads to load it and add it
284
* to the batch queue. The function will return an optional value, which will
285
* be empty if the epoch is finished or if the dataloader is destroyed.
286
* @param batch_idx The index of the batch to retrieve.
287
* @return An optional value containing the retrieved batch of data, or empty
288
* if the epoch is finished or if the dataloader is destroyed.
289
* */
290
std::optional<Batch> get_batch(
size_t
batch_idx);
291
292
/* * The dataset that this dataloader loads data from. It is a reference to an
293
* IDataset object, which provides the interface for accessing the samples in
294
* the dataset. The dataloader will use this dataset to load batches of data
295
* in the worker threads.
296
* */
297
IDataset
&dataset_;
298
299
/* * The batch size that this dataloader uses to load batches of data. It
300
* determines how many samples are included in each batch that the dataloader
301
* loads from the dataset. The batch size is set at the construction of the
302
* dataloader and cannot be changed afterwards.
303
* */
304
size_t
batch_size_;
305
306
/* * Whether to shuffle the dataset at the beginning of each epoch. If true,
307
* the dataloader will shuffle the indices of the samples in the dataset at
308
* the beginning of each epoch, which will result in different batches being
309
* loaded in each epoch. If false, the dataloader will load batches in a fixed
310
* order based on the original order of the samples in the dataset.
311
* */
312
bool
shuffle_;
313
314
/* * The seed used for the shuffle RNG. When it holds a value the shuffle is
315
* deterministic (repeatable across epochs and runs); when it is std::nullopt
316
* the RNG is seeded from std::random_device for non-deterministic ordering.
317
* Only consulted when shuffle_ is true.
318
* */
319
std::optional<uint32_t> seed_;
320
321
/* * The number of worker threads to use for loading batches. This determines
322
* how many threads will be running in the background to load batches of data
323
* from the dataset. More worker threads can load batches in parallel, but may
324
* also increase the overhead of thread management. The default number of
325
* worker threads is 4.
326
* */
327
size_t
num_workers_;
328
329
/* * The number of batches to prefetch in the background. This determines how
330
* many batches will be loaded in advance by the worker threads while the user
331
* is consuming the batches. More prefetch batches can reduce the waiting time
332
* for batches to be loaded, but may also increase the memory usage of the
333
* dataloader. The default number of prefetch batches is 2.
334
* */
335
size_t
prefetch_batches_;
336
337
/* * The total number of samples in the dataset. This is obtained from the
338
* dataset object and is used to calculate the total number of batches in the
339
* dataloader.
340
* */
341
size_t
dataset_size_;
342
343
/* * The total number of batches in the dataloader. This is calculated based
344
* on the size of the dataset and the batch size. It is used to determine how
345
* many batches are available for iteration in the dataloader.
346
* */
347
size_t
num_batches_;
348
349
/* * The indices of the samples in the dataset. This is a vector of size equal
350
* to the number of samples in the dataset, containing the indices of the
351
* samples in the original order. If shuffle is true, this vector will be
352
* shuffled at the beginning of each epoch to provide different batches in
353
* each epoch.
354
* */
355
std::vector<size_t> indices_;
356
357
/* * The worker threads that are responsible for loading batches of data from
358
* the dataset. This is a vector of threads that are created at the beginning
359
* of each epoch and run the worker_thread function to load batches in the
360
* background. The worker threads will continue to run until the epoch is
361
* finished or the dataloader is destroyed.
362
* */
363
std::vector<std::thread> workers_;
364
365
/* * The batch queue that holds the batches of data that have been loaded by
366
* the worker threads. This is a queue of pairs, where each pair consists of a
367
* batch index and the corresponding batch of data. The worker threads will
368
* add batches to this queue as they load them, and the iterator will retrieve
369
* batches from this queue when requested. The batch queue is synchronized
370
* using mutexes and condition variables to ensure thread safety.
371
* */
372
std::map<size_t, Batch> batch_queue_;
373
374
/* * Mutex and condition variables for synchronizing access to the batch queue
375
* and signaling the worker threads. The queue_mutex is used to protect access
376
* to the batch_queue, while the queue_cv is used to signal the worker threads
377
* when a new batch is added to the queue, and the ready_cv is used to signal
378
* the iterator when a batch is ready to be retrieved from the queue.
379
* */
380
std::mutex queue_mutex_;
381
382
/* * The condition variable used to signal the worker threads when a new batch
383
* is added to the batch queue. The worker threads will wait on this condition
384
* variable when they are idle and will be notified when a new batch is added
385
* to the queue, allowing them to wake up and continue loading batches.
386
* */
387
std::condition_variable queue_cv_;
388
389
/* * The condition variable used to signal the iterator when a batch is ready
390
* to be retrieved from the batch queue. The iterator will wait on this
391
* condition variable when it is waiting for a batch to be loaded, and will be
392
* notified when a new batch is added to the queue, allowing it to wake up and
393
* retrieve the batch.
394
* */
395
std::condition_variable ready_cv_;
396
397
/* * Atomic flags for controlling the worker threads and the epoch state. The
398
* stop_workers_ flag is used to signal the worker threads to stop loading
399
* batches and exit when the epoch is finished or the dataloader is destroyed.
400
* The epoch_finished_ flag is used to indicate whether the current epoch is
401
* finished, which can be checked by the worker threads to determine when to
402
* stop loading batches.
403
* */
404
std::atomic<bool> stop_workers_;
405
406
/* * The epoch_finished_ flag is used to indicate whether the current epoch is
407
* finished. It is set to true when the iteration over the dataloader is
408
* complete or when the dataloader is destroyed, and it is checked by the
409
* worker threads to determine when to stop loading batches. When this flag is
410
* set to true, the worker threads will stop loading batches and exit
411
* gracefully.
412
* */
413
std::atomic<bool> epoch_finished_;
414
415
/* * The current batch index that the iterator is pointing to. This is used by
416
* the iterator to determine which batch to load from the dataloader when it
417
* is dereferenced or advanced. The current batch index is updated by the
418
* iterator as it advances through the batches, and it is used to retrieve the
419
* correct batch from the dataloader when requested.
420
* */
421
size_t
current_batch_idx_ = 0;
422
};
423
424
}
// namespace reusex::vision
reusex::vision::Dataloader::Iterator
Definition
Dataloader.hpp:81
reusex::vision::Dataloader::Iterator::operator=
Iterator & operator=(const Iterator &other)
Definition
Dataloader.hpp:110
reusex::vision::Dataloader::Iterator::move_batch
Batch && move_batch()
reusex::vision::Dataloader::Iterator::operator++
Iterator & operator++()
reusex::vision::Dataloader::Iterator::reference
Batch & reference
Definition
Dataloader.hpp:87
reusex::vision::Dataloader::Iterator::value_type
Batch value_type
Definition
Dataloader.hpp:84
reusex::vision::Dataloader::Iterator::operator=
Iterator & operator=(Iterator &&)=default
reusex::vision::Dataloader::Iterator::operator!=
bool operator!=(const Iterator &other) const
reusex::vision::Dataloader::Iterator::operator*
BatchView operator*() const
reusex::vision::Dataloader::Iterator::iterator_category
std::input_iterator_tag iterator_category
Definition
Dataloader.hpp:83
reusex::vision::Dataloader::Iterator::Iterator
Iterator(const Iterator &other)
Definition
Dataloader.hpp:101
reusex::vision::Dataloader::Iterator::operator==
bool operator==(const Iterator &other) const
reusex::vision::Dataloader::Iterator::difference_type
std::ptrdiff_t difference_type
Definition
Dataloader.hpp:85
reusex::vision::Dataloader::Iterator::operator++
Iterator operator++(int)=delete
reusex::vision::Dataloader::Iterator::Iterator
Iterator(Iterator &&)=default
reusex::vision::Dataloader::Iterator::Iterator
Iterator(Dataloader *loader, size_t batch_idx)
reusex::vision::Dataloader::Iterator::pointer
Batch * pointer
Definition
Dataloader.hpp:86
reusex::vision::Dataloader::end
Iterator end()
reusex::vision::Dataloader::set_prefetch_batches
void set_prefetch_batches(size_t prefetch_batches)
reusex::vision::Dataloader::default_seed
static constexpr uint32_t default_seed
Definition
Dataloader.hpp:40
reusex::vision::Dataloader::get_num_workers
size_t get_num_workers() const
reusex::vision::Dataloader::Pair
IDataset::Pair Pair
Definition
Dataloader.hpp:31
reusex::vision::Dataloader::Batch
std::vector< Pair > Batch
Definition
Dataloader.hpp:32
reusex::vision::Dataloader::BatchView
std::span< Pair > BatchView
Definition
Dataloader.hpp:33
reusex::vision::Dataloader::~Dataloader
~Dataloader()
reusex::vision::Dataloader::size
size_t size() const
reusex::vision::Dataloader::get_prefetch_batches
size_t get_prefetch_batches() const
reusex::vision::Dataloader::shuffled_indices
static std::vector< size_t > shuffled_indices(size_t count, std::optional< uint32_t > seed)
reusex::vision::Dataloader::Dataloader
Dataloader(IDataset &dataset, size_t batch_size, bool shuffle=false, size_t num_workers=4, size_t prefetch_batches=2, std::optional< uint32_t > seed=default_seed)
reusex::vision::Dataloader::set_num_workers
void set_num_workers(size_t num_workers)
reusex::vision::Dataloader::begin
Iterator begin()
reusex::vision::IDataset
Definition
IDataset.hpp:36
reusex::vision::IDataset::Pair
std::pair< std::unique_ptr< IData >, size_t > Pair
Definition
IDataset.hpp:44
reusex::vision
Definition
annotate.hpp:12
libs
reusex
include
vision
Dataloader.hpp
Generated by
1.17.0