Line data Source code
1 : #pragma once
2 :
3 : #include "TypeTraits.hpp"
4 : #include "elsaDefines.h"
5 : #include "ExpressionPredicates.h"
6 : #include "DataDescriptor.h"
7 : #include "Error.h"
8 : #include "FormatConfig.h"
9 : #include "TypeCasts.hpp"
10 : #include "ContiguousStorage.h"
11 : #include "Span.h"
12 : #include "transforms/FFTPolicy.h"
13 : #include "Overloaded.hpp"
14 : #include "NdView.h"
15 :
16 : #include <thrust/complex.h>
17 :
18 : #include <variant>
19 : #include <memory>
20 : #include <type_traits>
21 :
22 : namespace elsa
23 : {
24 : /**
25 : * @brief class representing and storing a linearized n-dimensional signal
26 : *
27 : * This class provides a container for a signal that is stored in memory. This signal can
28 : * be n-dimensional, and will be stored in memory in a linearized fashion. The information
29 : * on how this linearization is performed is provided by an associated DataDescriptor.
30 : *
31 : * @tparam data_t data type that is stored in the DataContainer, defaulting to real_t.
32 : *
33 : * @author
34 : * - Matthias Wieczorek - initial code
35 : * - Tobias Lasser - rewrite, modularization, modernization
36 : * - David Frank - added DataHandler concept, iterators, integrated unified memory
37 : * - Nikola Dinev - add block support
38 : * - Jens Petit - expression templates
39 : * - Jonas Jelten - various enhancements, fft, complex handling, pretty formatting
40 : */
41 : template <typename data_t>
42 : class DataContainer
43 : {
44 : public:
45 : /// Scalar alias
46 : using Scalar = data_t;
47 :
48 : using reference = typename ContiguousStorage<data_t>::reference;
49 : using const_reference = typename ContiguousStorage<data_t>::const_reference;
50 :
51 : /// iterator for DataContainer (random access and continuous)
52 : using iterator = typename ContiguousStorage<data_t>::iterator;
53 :
54 : /// const iterator for DataContainer (random access and continuous)
55 : using const_iterator = typename ContiguousStorage<data_t>::const_iterator;
56 :
57 : enum class ImportStrategy {
58 : View,
59 : HostCopy,
60 : DeviceCopy,
61 : };
62 :
63 : /// delete default constructor (without metadata there can be no valid container)
64 : DataContainer() = delete;
65 :
66 : /**
67 : * @brief Constructor for empty DataContainer, no initialisation is performed,
68 : * but the underlying space is allocated.
69 : *
70 : * @param[in] dataDescriptor containing the associated metadata
71 : */
72 : explicit DataContainer(const DataDescriptor& dataDescriptor);
73 :
74 : /**
75 : * @brief Constructor for DataContainer, initializing it with a DataVector
76 : *
77 : * @param[in] dataDescriptor containing the associated metadata
78 : * @param[in] data vector containing the initialization data
79 : */
80 : DataContainer(const DataDescriptor& dataDescriptor,
81 : const Eigen::Matrix<data_t, Eigen::Dynamic, 1>& data);
82 :
83 : /// constructor accepting a DataDescriptor and a DataHandler
84 : DataContainer(const DataDescriptor& dataDescriptor,
85 : const ContiguousStorage<data_t>& storage);
86 :
87 : /// constructor accepting a DataDescriptor and a DataHandler
88 : DataContainer(const DataDescriptor& dataDescriptor, ContiguousStorageView<data_t> storage);
89 :
90 : /// constructor accepting a DataDescriptor and an NdView. Dimensions in the DataDescriptor
91 : /// must match the dimensions of the NdView.
92 : template <mr::StorageType tag>
93 : DataContainer(const DataDescriptor& dataDescriptor, NdViewTagged<data_t, tag>& view);
94 :
95 : /**
96 : * @brief Copy constructor for DataContainer
97 : *
98 : * @param[in] other DataContainer to copy
99 : */
100 : DataContainer(const DataContainer<data_t>& other);
101 :
102 : /**
103 : * @brief copy assignment for DataContainer
104 : *
105 : * @param[in] other DataContainer to copy
106 : *
107 : * Note that a copy assignment with a DataContainer on a different device (CPU vs GPU) will
108 : * result in an "infectious" copy which means that afterwards the current container will use
109 : * the same device as "other".
110 : */
111 : DataContainer<data_t>& operator=(const DataContainer<data_t>& other);
112 :
113 : /**
114 : * @brief Move constructor for DataContainer
115 : *
116 : * @param[in] other DataContainer to move from
117 : *
118 : * The moved-from objects remains in a valid state. However, as preconditions are not
119 : * fulfilled for any member functions, the object should not be used. After move- or copy-
120 : * assignment, this is possible again.
121 : */
122 : DataContainer(DataContainer<data_t>&& other) noexcept;
123 :
124 : /**
125 : * @brief Move assignment for DataContainer
126 : *
127 : * @param[in] other DataContainer to move from
128 : *
129 : * The moved-from objects remains in a valid state. However, as preconditions are not
130 : * fulfilled for any member functions, the object should not be used. After move- or copy-
131 : * assignment, this is possible again.
132 : *
133 : * Note that a copy assignment with a DataContainer on a different device (CPU vs GPU) will
134 : * result in an "infectious" copy which means that afterwards the current container will use
135 : * the same device as "other".
136 : */
137 : DataContainer<data_t>& operator=(DataContainer<data_t>&& other) noexcept;
138 :
139 : static DataContainer<data_t> fromRawData(data_t* raw_data, mr::StorageType storageType,
140 : const IndexVector_t& shape,
141 : const IndexVector_t& strides,
142 : const DataDescriptor& desc,
143 : std::function<void()> destructor);
144 :
145 : /// return nd-view describing the current data container
146 : NdView<data_t> toNdView() const;
147 :
148 : /// return the current DataDescriptor
149 : const DataDescriptor& getDataDescriptor() const;
150 :
151 : /// return true, if the current DataContainer is owning its memory
152 : bool isOwning() const;
153 :
154 : /// return true, if the current DataContainer is a view, i.e. is not owning its memory
155 : bool isView() const;
156 :
157 : ContiguousStorage<data_t>& storage();
158 :
159 : const ContiguousStorage<data_t>& storage() const;
160 :
161 : /// return the size of the stored data (i.e. the number of elements in the linearized
162 : /// signal)
163 : index_t getSize() const;
164 :
165 : /// get the number of blocks the signal is created from. If the descriptor
166 : /// is not of type `BlockDescriptor` 1 is returned;
167 : index_t getNumberOfBlocks() const;
168 :
169 : /// return the index-th element of linearized signal (not bounds-checked!)
170 : reference operator[](index_t index);
171 :
172 : /// return the index-th element of the linearized signal as read-only (not bounds-checked!)
173 : const_reference operator[](index_t index) const;
174 :
175 : /// return an element by n-dimensional coordinate (not bounds-checked!)
176 : /// The indexing follows the convention `(x1, x2, ..., xn)`. Specifically,
177 : /// in 2D `(x, y)` and 3D `(x, y, z)`.
178 : reference operator()(const IndexVector_t& coordinate);
179 :
180 : /// return an element by n-dimensional coordinate as read-only (not bounds-checked!)
181 : /// The indexing follows the convention `(x1, x2, ..., xn)`. Specifically,
182 : /// in 2D `(x, y)` and 3D `(x, y, z)`.
183 : const_reference operator()(const IndexVector_t& coordinate) const;
184 :
185 : data_t at(const IndexVector_t& coordinate) const;
186 :
187 : /// return an element by its coordinates (not bounds-checked!)
188 : /// The indexing follows the convention `(x1, x2, ..., xn)`. Specifically,
189 : /// in 2D `(x, y)` and 3D `(x, y, z)`.
190 : template <typename idx0_t, typename... idx_t,
191 : typename = std::enable_if_t<
192 : std::is_integral_v<idx0_t> && (... && std::is_integral_v<idx_t>)>>
193 : reference operator()(idx0_t idx0, idx_t... indices)
194 636709 : {
195 636709 : IndexVector_t coordinate(sizeof...(indices) + 1);
196 636709 : ((coordinate << idx0), ..., indices);
197 636709 : return operator()(coordinate);
198 636709 : }
199 :
200 : /// return an element by its coordinates as read-only (not bounds-checked!)
201 : /// The indexing follows the convention `(x1, x2, ..., xn)`. Specifically,
202 : /// in 2D `(x, y)` and 3D `(x, y, z)`.
203 : template <typename idx0_t, typename... idx_t,
204 : typename = std::enable_if_t<
205 : std::is_integral_v<idx0_t> && (... && std::is_integral_v<idx_t>)>>
206 : const_reference operator()(idx0_t idx0, idx_t... indices) const
207 2240 : {
208 2240 : IndexVector_t coordinate(sizeof...(indices) + 1);
209 2240 : ((coordinate << idx0), ..., indices);
210 2240 : return operator()(coordinate);
211 2240 : }
212 :
213 : /// return the dot product of this signal with the one from container other
214 : data_t dot(const DataContainer<data_t>& other) const;
215 :
216 : /// return the squared l2 norm of this signal (dot product with itself)
217 : GetFloatingPointType_t<data_t> squaredL2Norm() const;
218 :
219 : /// return the l2 norm of this signal (square root of dot product with itself)
220 : GetFloatingPointType_t<data_t> l2Norm() const;
221 :
222 : /// return the pointwise l2 norm of this signal. Pointwise norms assume
223 : /// blocked DataContainers, and then the norm is taken column-wise.
224 : /// The pointwise l2 norm is equivalent to the following NumPy code
225 : /// snippet: `np.sqrt(np.sum(x ** 2, axis=0))`, assuming a signal,
226 : /// where the first axis indexes each block of the data.
227 : DataContainer<GetFloatingPointType_t<data_t>> pL2Norm() const;
228 :
229 : /// return the l0 pseudo-norm of this signal (number of non-zero values)
230 : index_t l0PseudoNorm() const;
231 :
232 : /// return the l1 norm of this signal (sum of absolute values)
233 : GetFloatingPointType_t<data_t> l1Norm() const;
234 :
235 : /// return the pointwise l1 norm of this signal. Pointwise norms assume
236 : /// blocked DataContainers, and then the norm is taken column-wise.
237 : ///
238 : /// The pointwise l1 norm is equivalent to the following NumPy code
239 : /// snippet: `np.sum(np.abs(x), axis=0)`, assuming a signal,
240 : /// where the first axis indexes each block of the data.
241 : DataContainer<GetFloatingPointType_t<data_t>> pL1Norm() const;
242 :
243 : /// return the linf norm of this signal (maximum of absolute values)
244 : GetFloatingPointType_t<data_t> lInfNorm() const;
245 :
246 : /// return the mixed L21 norm of this signal
247 : data_t l21MixedNorm() const;
248 :
249 : /// return the mixed L21 norm of this signal with smoothing parameter epsilon
250 : data_t l21SmoothMixedNorm(data_t epsilon) const;
251 :
252 : /// return the sum of all elements of this signal
253 : data_t sum() const;
254 :
255 : /// return the min of all elements of this signal
256 : data_t minElement() const;
257 :
258 : /// return the max of all elements of this signal
259 : data_t maxElement() const;
260 :
261 : /// convert to the fourier transformed signal.
262 : /// @param policy controls the choice of implementation. If the requested policy
263 : /// cannot be applied, a runtime error is generated.
264 : /// @see FFTPolicy
265 : void fft(FFTNorm norm, FFTPolicy policy = FFTPolicy::AUTO);
266 :
267 : /// convert to the inverse fourier transformed signal.
268 : /// @param policy controls the choice of implementation. If the requested policy
269 : /// cannot be applied, a runtime error is generated.
270 : /// @see FFTPolicy
271 : void ifft(FFTNorm norm, FFTPolicy policy = FFTPolicy::AUTO);
272 :
273 : /// copy the values from other DataContainer to this DataContainer
274 : void assign(const DataContainer<data_t>& other);
275 :
276 : /// Set all values of the DataContainer to zero
277 : DataContainer<data_t>& zero() &;
278 :
279 : /// Set all values of the DataContainer to zero
280 : DataContainer<data_t> zero() &&;
281 :
282 : /// Set all values of the DataContainer to one
283 : DataContainer<data_t>& one() &;
284 :
285 : /// Set all values of the DataContainer to one
286 : DataContainer<data_t> one() &&;
287 :
288 : /// Set all values of the DataContainer to the given value
289 : DataContainer<data_t>& fill(SelfType_t<data_t> value) &;
290 :
291 : /// Set all values of the DataContainer to the given value
292 : DataContainer<data_t> fill(SelfType_t<data_t> value) &&;
293 :
294 : /// if the datacontainer is already complex, return itself.
295 : DataContainer<add_complex_t<data_t>> asComplex() const;
296 :
297 : /// compute in-place element-wise addition of another container
298 : DataContainer<data_t>& operator+=(const DataContainer<data_t>& dc);
299 :
300 : /// compute in-place element-wise subtraction of another container
301 : DataContainer<data_t>& operator-=(const DataContainer<data_t>& dc);
302 :
303 : /// compute in-place element-wise multiplication with another container
304 : DataContainer<data_t>& operator*=(const DataContainer<data_t>& dc);
305 :
306 : /// compute in-place element-wise division by another container
307 : DataContainer<data_t>& operator/=(const DataContainer<data_t>& dc);
308 :
309 : /// compute in-place addition of a scalar
310 : DataContainer<data_t>& operator+=(data_t scalar);
311 :
312 : /// compute in-place subtraction of a scalar
313 : DataContainer<data_t>& operator-=(data_t scalar);
314 :
315 : /// compute in-place multiplication with a scalar
316 : DataContainer<data_t>& operator*=(data_t scalar);
317 :
318 : /// compute in-place division by a scalar
319 : DataContainer<data_t>& operator/=(data_t scalar);
320 :
321 : /// assign a scalar to the DataContainer
322 : DataContainer<data_t>& operator=(data_t scalar);
323 :
324 : /// comparison with another DataContainer
325 : bool operator==(const DataContainer<data_t>& other) const;
326 :
327 : /// comparison with another DataContainer
328 : bool operator!=(const DataContainer<data_t>& other) const;
329 :
330 : /// returns a reference to the i-th block, wrapped in a DataContainer
331 : DataContainer<data_t> getBlock(index_t i);
332 :
333 : /// returns a const reference to the i-th block, wrapped in a DataContainer
334 : const DataContainer<data_t> getBlock(index_t i) const;
335 :
336 : /// return a view of this DataContainer with a different descriptor
337 : DataContainer<data_t> viewAs(const DataDescriptor& dataDescriptor);
338 :
339 : /// return a const view of this DataContainer with a different descriptor
340 : const DataContainer<data_t> viewAs(const DataDescriptor& dataDescriptor) const;
341 :
342 : /// @brief Slice the container in the last dimension
343 : ///
344 : /// Access a portion of the container via a slice. The slicing always is in the last
345 : /// dimension. So for a 3D volume, the slice would be an sliced in the z direction and would
346 : /// be a part of the x-y plane.
347 : ///
348 : /// A slice is always the same dimension as the original DataContainer, but with a thickness
349 : /// of 1 in the last dimension (i.e. the coefficient of the last dimension is 1)
350 : const DataContainer<data_t> slice(index_t i) const;
351 :
352 : /// @brief Slice the container in the last dimension, non-const overload
353 : ///
354 : /// @overload
355 : /// @see slice(index_t) const
356 : DataContainer<data_t> slice(index_t i);
357 :
358 : /// returns iterator to the first element of the container
359 : iterator begin();
360 :
361 : /// returns const iterator to the first element of the container (cannot mutate data)
362 : const_iterator begin() const;
363 :
364 : /// returns const iterator to the first element of the container (cannot mutate data)
365 : const_iterator cbegin() const;
366 :
367 : /// returns iterator to one past the last element of the container
368 : iterator end();
369 :
370 : /// returns const iterator to one past the last element of the container (cannot mutate
371 : /// data)
372 : const_iterator end() const;
373 :
374 : /// returns const iterator to one past the last element of the container (cannot mutate
375 : /// data)
376 : const_iterator cend() const;
377 :
378 : /// value_type of the DataContainer elements for iterators
379 : using value_type = data_t;
380 : /// pointer type of DataContainer elements for iterators
381 : using pointer = data_t*;
382 : /// const pointer type of DataContainer elements for iterators
383 : using const_pointer = const data_t*;
384 : /// difference type for iterators
385 : using difference_type = std::ptrdiff_t;
386 :
387 : /// write a pretty-formatted string representation to stream
388 : void format(std::ostream& os, format_config cfg = {}) const;
389 :
390 : private:
391 : /// the current DataDescriptor
392 : std::unique_ptr<DataDescriptor> _dataDescriptor;
393 :
394 : /// the current DataHandler
395 : std::variant<ContiguousStorage<data_t>, ContiguousStorageView<data_t>> storage_;
396 : };
397 :
398 : /// pretty output formatting.
399 : /// for configurable output, use `DataContainerFormatter` directly.
400 : template <typename T>
401 : std::ostream& operator<<(std::ostream& os, const elsa::DataContainer<T>& dc)
402 0 : {
403 0 : dc.format(os);
404 0 : return os;
405 0 : }
406 :
407 : /// clip the container values outside of the interval, to the interval edges
408 : template <typename data_t>
409 : [[nodiscard]] DataContainer<data_t> clip(const DataContainer<data_t>& dc, data_t min,
410 : data_t max);
411 :
412 : /// Concatenate two DataContainers to one (requires copying of both)
413 : template <typename data_t>
414 : [[nodiscard]] DataContainer<data_t> concatenate(const DataContainer<data_t>& dc1,
415 : const DataContainer<data_t>& dc2);
416 :
417 : /// Perform the (n-dimensional) FFT shift operation to the provided signal. Refer to
418 : /// https://numpy.org/doc/stable/reference/generated/numpy.fft.fftshift.html for further
419 : /// details.
420 : template <typename data_t>
421 : [[nodiscard]] DataContainer<data_t> fftShift(const DataContainer<data_t>& dc);
422 :
423 : /// Perform the (n-dimensional) IFFT shift operation to the provided signal. Refer to
424 : /// https://numpy.org/doc/stable/reference/generated/numpy.fft.ifftshift.html for further
425 : /// details.
426 : template <typename data_t>
427 : [[nodiscard]] DataContainer<data_t> ifftShift(const DataContainer<data_t>& dc);
428 :
429 : /// Perform the FFT shift operation to the provided signal. Refer to
430 : /// https://numpy.org/doc/stable/reference/generated/numpy.fft.fftshift.html for further
431 : /// details.
432 : template <typename data_t>
433 : [[nodiscard]] DataContainer<data_t> fftShift2D(const DataContainer<data_t>& dc);
434 :
435 : /// Perform the IFFT shift operation to the provided signal. Refer to
436 : /// https://numpy.org/doc/stable/reference/generated/numpy.fft.ifftshift.html for further
437 : /// details.
438 : template <typename data_t>
439 : [[nodiscard]] DataContainer<data_t> ifftShift2D(const DataContainer<data_t>& dc);
440 :
441 : /// Unary plus operator
442 : template <typename data_t>
443 : [[nodiscard]] inline DataContainer<data_t> operator+(const DataContainer<data_t>& x)
444 7 : {
445 7 : return x;
446 7 : }
447 :
448 : /// Unary negation operator
449 : template <typename data_t>
450 : [[nodiscard]] inline DataContainer<data_t> operator-(const DataContainer<data_t>& x)
451 1887 : {
452 1887 : return static_cast<data_t>(-1) * x;
453 1887 : }
454 :
455 : /// Multiplying two DataContainers
456 : template <typename data_t>
457 : [[nodiscard]] inline DataContainer<data_t> operator*(const DataContainer<data_t>& lhs,
458 : const DataContainer<data_t>& rhs)
459 10229 : {
460 10229 : DataContainer<data_t> copy(lhs.getDataDescriptor());
461 10229 : copy.assign(lhs);
462 10229 : copy *= rhs;
463 10229 : return copy;
464 10229 : }
465 :
466 : template <typename data_t, typename Scalar,
467 : typename = std::enable_if_t<std::is_arithmetic_v<GetFloatingPointType_t<
468 : Scalar>> && std::is_convertible_v<Scalar, data_t>>>
469 : [[nodiscard]] inline DataContainer<data_t> operator*(const DataContainer<data_t>& dc,
470 : const Scalar& s)
471 357 : {
472 357 : DataContainer<data_t> copy(dc.getDataDescriptor());
473 357 : copy.assign(dc);
474 357 : copy *= static_cast<data_t>(s);
475 357 : return copy;
476 357 : }
477 :
478 : template <typename data_t, typename Scalar,
479 : typename = std::enable_if_t<std::is_arithmetic_v<GetFloatingPointType_t<
480 : Scalar>> && std::is_convertible_v<Scalar, data_t>>>
481 : [[nodiscard]] inline DataContainer<data_t> operator*(const Scalar& s,
482 : const DataContainer<data_t>& dc)
483 5062 : {
484 5062 : DataContainer<data_t> copy(dc.getDataDescriptor());
485 5062 : copy.assign(dc);
486 5062 : copy *= static_cast<data_t>(s);
487 5062 : return copy;
488 5062 : }
489 :
490 : /// Add two DataContainers
491 : template <typename data_t>
492 : [[nodiscard]] inline DataContainer<data_t> operator+(const DataContainer<data_t>& lhs,
493 : const DataContainer<data_t>& rhs)
494 1463 : {
495 1463 : DataContainer<data_t> copy(lhs.getDataDescriptor());
496 1463 : copy.assign(lhs);
497 1463 : copy += rhs;
498 1463 : return copy;
499 1463 : }
500 :
501 : template <typename data_t, typename Scalar,
502 : typename = std::enable_if_t<std::is_arithmetic_v<GetFloatingPointType_t<
503 : Scalar>> && std::is_convertible_v<Scalar, data_t>>>
504 : [[nodiscard]] inline DataContainer<data_t> operator+(const DataContainer<data_t>& dc,
505 : const Scalar& s)
506 7 : {
507 7 : DataContainer<data_t> copy(dc.getDataDescriptor());
508 7 : copy.assign(dc);
509 7 : copy += static_cast<data_t>(s);
510 7 : return copy;
511 7 : }
512 :
513 : template <typename data_t, typename Scalar,
514 : typename = std::enable_if_t<std::is_arithmetic_v<GetFloatingPointType_t<
515 : Scalar>> && std::is_convertible_v<Scalar, data_t>>>
516 : [[nodiscard]] inline DataContainer<data_t> operator+(const Scalar& s,
517 : const DataContainer<data_t>& dc)
518 9 : {
519 9 : auto copy = DataContainer<data_t>(dc.getDataDescriptor());
520 9 : copy.assign(dc);
521 9 : copy += static_cast<data_t>(s);
522 9 : return copy;
523 9 : }
524 :
525 : /// Subtract two DataContainers
526 : template <typename data_t>
527 : [[nodiscard]] DataContainer<data_t> operator-(const DataContainer<data_t>& lhs,
528 : const DataContainer<data_t>& rhs);
529 :
530 : template <typename data_t, typename Scalar,
531 : typename = std::enable_if_t<std::is_arithmetic_v<GetFloatingPointType_t<
532 : Scalar>> && std::is_convertible_v<Scalar, data_t>>>
533 : [[nodiscard]] DataContainer<std::common_type_t<data_t, Scalar>>
534 : operator-(const DataContainer<data_t>& dc, const Scalar& s);
535 :
536 : template <typename Scalar, typename data_t,
537 : typename = std::enable_if_t<std::is_arithmetic_v<GetFloatingPointType_t<
538 : Scalar>> && std::is_convertible_v<Scalar, data_t>>>
539 : [[nodiscard]] DataContainer<std::common_type_t<Scalar, data_t>>
540 : operator-(const Scalar& s, const DataContainer<data_t>& dc);
541 :
542 : /// Divide two DataContainers
543 : template <typename data_t>
544 : [[nodiscard]] DataContainer<data_t> operator/(const DataContainer<data_t>& lhs,
545 : const DataContainer<data_t>& rhs);
546 :
547 : /// Divide DataContainer by scalar
548 : template <typename data_t, typename Scalar,
549 : typename = std::enable_if_t<std::is_arithmetic_v<GetFloatingPointType_t<
550 : Scalar>> && std::is_convertible_v<Scalar, data_t>>>
551 : [[nodiscard]] DataContainer<std::common_type_t<data_t, Scalar>>
552 : operator/(const DataContainer<data_t>& dc, const Scalar& s);
553 :
554 : /// Divide scalar with DataContainer
555 : template <typename Scalar, typename data_t,
556 : typename = std::enable_if_t<std::is_arithmetic_v<GetFloatingPointType_t<
557 : Scalar>> && std::is_convertible_v<Scalar, data_t>>>
558 : [[nodiscard]] DataContainer<std::common_type_t<Scalar, data_t>>
559 : operator/(const Scalar& s, const DataContainer<data_t>& dc);
560 :
561 : template <typename xdata_t, typename ydata_t>
562 : [[nodiscard]] DataContainer<value_type_of_t<std::common_type_t<xdata_t, ydata_t>>>
563 : cwiseMax(const DataContainer<xdata_t>& lhs, const DataContainer<ydata_t>& rhs);
564 :
565 : template <typename xdata_t, typename ydata_t>
566 : [[nodiscard]] DataContainer<value_type_of_t<std::common_type_t<xdata_t, ydata_t>>>
567 : cwiseMin(const DataContainer<xdata_t>& lhs, const DataContainer<ydata_t>& rhs);
568 :
569 : /// @brief Compute a coefficient wise square for each element of the `DataContainer`
570 : template <typename data_t>
571 : [[nodiscard]] DataContainer<data_t> square(const DataContainer<data_t>& dc);
572 :
573 : /// @brief Short convenience name for `square(dc)`.
574 : template <typename data_t>
575 : [[nodiscard]] DataContainer<data_t> sq(const DataContainer<data_t>& dc);
576 :
577 : /// @brief Compute a coefficient wise square root for each element of the `DataContainer`
578 : template <typename data_t>
579 : [[nodiscard]] DataContainer<data_t> sqrt(const DataContainer<data_t>& dc);
580 :
581 : /// @brief Compute a coefficient wise exponential for each element of the `DataContainer`
582 : template <typename data_t>
583 : [[nodiscard]] DataContainer<data_t> exp(const DataContainer<data_t>& dc);
584 :
585 : /// @brief Compute a coefficient wise log for each element of the `DataContainer`
586 : template <typename data_t>
587 : [[nodiscard]] DataContainer<data_t> log(const DataContainer<data_t>& dc);
588 :
589 : /// @brief Compute an element-wise log of modified bessel function of the first kind of
590 : /// order 0 for each element of the `DataContainer`
591 : template <typename data_t>
592 : DataContainer<data_t> bessel_log_0(const DataContainer<data_t>& dc);
593 :
594 : /// @brief Compute an element-wise modified bessel function of the first kind of order 1
595 : /// divided by that of the order 0 for each element of the `DataContainer`
596 : template <typename data_t>
597 : DataContainer<data_t> bessel_1_0(const DataContainer<data_t>& dc);
598 :
599 : /// @brief Compute a coefficient wise minimum with a scalar.
600 : /// For each element in `x_i` the given `DataContainer`, compute
601 : /// `min(x_i, scalar)`
602 : template <typename data_t>
603 : [[nodiscard]] DataContainer<data_t> minimum(const DataContainer<data_t>& dc,
604 : SelfType_t<data_t> scalar);
605 :
606 : /// @brief Compute a coefficient wise maximum with a scalar.
607 : /// For each element in `x_i` the given `DataContainer`, compute
608 : /// `max(x_i, scalar)`
609 : template <typename data_t>
610 : [[nodiscard]] DataContainer<data_t> maximum(const DataContainer<data_t>& dc,
611 : SelfType_t<data_t> scalar);
612 :
613 : /// @brief Return an owning DataContainer, if given an non-owning one, the data is copied to a
614 : /// new owning buffer.
615 : template <class data_t>
616 : [[nodiscard]] DataContainer<data_t> materialize(const DataContainer<data_t>& x);
617 :
618 : /// @brief Compute the absolute value for each of the coefficients of the given DataContainer
619 : template <typename data_t>
620 : [[nodiscard]] DataContainer<value_type_of_t<data_t>> cwiseAbs(const DataContainer<data_t>& dc);
621 :
622 : /**
623 : * @brief compute the sign of each entry of the input DataContainer. The
624 : * function is defined as:
625 : * \f[
626 : * \operatorname{sign}(x_i) =
627 : * \begin{cases}
628 : * 1 & \text{if } x_i > 0 \\
629 : * -1 & \text{if } x_i < 0 \\
630 : * 0 & \text{elsa}
631 : * \end{cases} \quad \forall i
632 : * \f]
633 : * For complex numbers, the definition is givens as:
634 : * \f[
635 : * \operatorname{sign}(x_i) =
636 : * \begin{cases}
637 : * 1 & \text{if } \mathrm{Re}(x_i) > 0 \\
638 : * -1 & \text{if } \mathrm{Re}(x_i) < 0 \\
639 : * sign(\mathrm{Im}(x_i)) & \text{elsa}
640 : * \end{cases} \quad \forall i
641 : * \f]
642 : */
643 : template <typename data_t>
644 : [[nodiscard]] DataContainer<value_type_of_t<data_t>> sign(const DataContainer<data_t>& dc);
645 :
646 : /// @brief Return a DataContainer with complex data type. If the input vector is real, all
647 : /// coefficients will be converted to complex values with a zero imaginary part. If the input is
648 : /// complex already, a copy of the container is returned
649 : template <typename data_t>
650 : [[nodiscard]] DataContainer<add_complex_t<data_t>> asComplex(const DataContainer<data_t>& dc);
651 :
652 : /// @brief Return the real part a complex DataContainer. If the input DataContainer is real,
653 : /// it is assumed that the imaginary part is zero and a copy is returned
654 : template <typename data_t>
655 : [[nodiscard]] DataContainer<value_type_of_t<data_t>> real(const DataContainer<data_t>& dc);
656 :
657 : /// @brief Return the imaginary part a complex DataContainer. If the input DataContainer is
658 : /// real, it is assumed that the imaginary part is zero and zero initialized DataContainer of
659 : /// the same size and DataDescriptor is returned
660 : template <typename data_t>
661 : [[nodiscard]] DataContainer<value_type_of_t<data_t>> imag(const DataContainer<data_t>& dc);
662 :
663 : /// Compute the linear combination of \f$a * x + b * y\f$.
664 : ///
665 : /// This function can be used as a memory efficient version for the computation
666 : /// of the above expression, as for such an expression (without expression template)
667 : /// multiple copies need to be created and allocated.
668 : ///
669 : /// The function throws, if x and y do not have the same data descriptor
670 : template <class data_t>
671 : [[nodiscard]] DataContainer<data_t>
672 : lincomb(SelfType_t<data_t> a, const DataContainer<data_t>& x, SelfType_t<data_t> b,
673 : const DataContainer<data_t>& y);
674 :
675 : /// Compute the linear combination of \f$a * x + b * y\f$, and write it to
676 : /// the output variable.
677 : ///
678 : /// This function can be used as a memory efficient version for the computation
679 : /// of the above expression, as for such an expression (without expression template)
680 : /// multiple copies need to be created and allocated.
681 : ///
682 : /// The function throws, if x, y and out do not have the same data descriptor
683 : template <class data_t>
684 : void lincomb(SelfType_t<data_t> alpha, const DataContainer<data_t>& x, SelfType_t<data_t> b,
685 : const DataContainer<data_t>& y, DataContainer<data_t>& out);
686 :
687 : /// Create a DataContainer filled with zeros and the given DataDescriptor
688 : template <class data_t>
689 : [[nodiscard]] DataContainer<data_t> zeros(const DataDescriptor& desc);
690 :
691 : /// Create a DataContainer filled with zeros and the DataDescriptor of the given DataContainer
692 : template <class data_t>
693 : [[nodiscard]] DataContainer<data_t> zeroslike(const DataContainer<data_t>& dc);
694 :
695 : /// Create a DataContainer filled with ones values and the given DataDescriptor
696 : template <class data_t>
697 : [[nodiscard]] DataContainer<data_t> ones(const DataDescriptor& desc);
698 :
699 : /// Create a DataContainer filled with ones and the DataDescriptor of the given DataContainer
700 : template <class data_t>
701 : [[nodiscard]] DataContainer<data_t> oneslike(const DataContainer<data_t>& dc);
702 :
703 : /// Create a DataContainer filled with the given value and DataDescriptor
704 : template <class data_t>
705 : [[nodiscard]] DataContainer<data_t> full(const DataDescriptor& desc, SelfType_t<data_t> value);
706 :
707 : /// Create a DataContainer filled with the given value and the DataDescriptor of the given
708 : /// DataContainer
709 : template <class data_t>
710 : [[nodiscard]] DataContainer<data_t> fulllike(const DataContainer<data_t>& dc,
711 : SelfType_t<data_t> value);
712 :
713 : /// Create an uninitialized DataContainer, the caller is responsible to fill DataContainer
714 : /// before use
715 : template <class data_t>
716 : [[nodiscard]] DataContainer<data_t> empty(const DataDescriptor& desc);
717 :
718 : /// Create an uninitialized DataContainer with the DataDescriptor of the given DataContainer.
719 : /// The caller is responsible to fill DataContainer before use
720 : template <class data_t>
721 : [[nodiscard]] DataContainer<data_t> emptylike(const DataContainer<data_t>& dc);
722 :
723 : /// Perform a real-to-complex FFT on the given real DataContainer, normalizing with the given
724 : /// norm. This exploits the symmetrical spectrum of the FT of a real signal and computes only
725 : /// the complex halfspace. This means that the last dimension will have length floor(N/2) + 1,
726 : /// given the last dimension of the input is N. For further reference, see numpy, FFTW, Eigen or
727 : /// cuFFT documentation. Note that the input need not be padded to the output size.
728 : /// @param policy controls the choice of implementation. If the requested policy
729 : /// cannot be applied, a runtime error is generated.
730 : /// @see FFTPolicy
731 : template <typename data_t>
732 : [[nodiscard]] DataContainer<complex<data_t>> rfft(const DataContainer<data_t>& dc, FFTNorm norm,
733 : FFTPolicy policy = FFTPolicy::AUTO);
734 :
735 : /// Perform a complex-to-real FFT on the given complex DataContainer, normalizing with the given
736 : /// norm. This exploits the symmetrical spectrum of the FT of a real signal and assumes that the
737 : /// complex halfspace is given as input. The input must be laid out as returned from rfft()!
738 : /// Note that, due to information lost in the floor(N/2) operation, the dimensions of
739 : /// dc.rfft().irfft() may not match the dimensions of dc. For further reference, see numpy,
740 : /// FFTW, Eigen or cuFFT documentation. Note that the output is not padded to the input size.
741 : /// @param policy controls the choice of implementation. If the requested policy
742 : /// cannot be applied, a runtime error is generated.
743 : /// @see FFTPolicy
744 : template <typename data_t>
745 : [[nodiscard]] DataContainer<data_t> irfft(const DataContainer<complex<data_t>>& dc,
746 : FFTNorm norm, FFTPolicy policy = FFTPolicy::AUTO);
747 :
748 : } // namespace elsa
|