Skip to content

Commit ac12472

Browse files
authored
Add new transform types and utility functions (#69)
1 parent 52f37ad commit ac12472

File tree

9 files changed

+935
-38
lines changed

9 files changed

+935
-38
lines changed

core/include/vc/core/types/Transforms.hpp

Lines changed: 220 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ namespace volcart
4646
class Transform3D
4747
{
4848
public:
49+
/** @brief Transform type string constant */
50+
static constexpr std::string_view TYPE{"Transform3D"};
51+
4952
/** Pointer type */
5053
using Pointer = std::shared_ptr<Transform3D>;
5154

@@ -60,14 +63,16 @@ class Transform3D
6063
auto operator=(Transform3D&& other) -> Transform3D& = delete;
6164

6265
/** @brief Return a string representation of the transform type */
63-
[[nodiscard]] virtual auto type() const -> std::string = 0;
66+
[[nodiscard]] virtual auto type() const -> std::string_view = 0;
6467
/** @brief Clone the transform */
6568
[[nodiscard]] virtual auto clone() const -> Pointer = 0;
6669

6770
/** @brief Return whether the underlying transform is invertible */
6871
[[nodiscard]] virtual auto invertible() const -> bool;
6972
/** @brief Return the inverted transform */
7073
[[nodiscard]] virtual auto invert() const -> Pointer;
74+
/** @brief Return whether the underlying transform is composable */
75+
[[nodiscard]] virtual auto composable() const -> bool;
7176

7277
/**
7378
* @brief Reset the transform parameters
@@ -79,7 +84,7 @@ class Transform3D
7984
*/
8085
virtual void reset() = 0;
8186
/** @brief Clears all parameters and properties of the transform */
82-
virtual void clear();
87+
void clear();
8388

8489
/**
8590
* @brief Set the identifier for the source space
@@ -134,6 +139,33 @@ class Transform3D
134139
[[nodiscard]] auto applyPointAndNormal(
135140
const cv::Vec6d& ptN, bool normalize = true) const -> cv::Vec6d;
136141

142+
/**
143+
* @brief Compose two transforms into a single new transform
144+
*
145+
* Returns a pair of transform pointers. If the composition fails, the pair
146+
* will contain pointers to both of the original inputs. If the second
147+
* value in the pair is nullptr, then the composition was successful, and
148+
* the new transform is available from the first pointer.
149+
*
150+
* @code{.cpp}
151+
* // Two transforms
152+
* Transform3D::Pointer lhs = AffineTransform::New();
153+
* Transform3D::Pointer rhs = AffineTransform::New();
154+
*
155+
* // Compose and assign the results to existing variables
156+
* std::tie(lhs, rhs) = Transform3D::Compose(lhs, rhs);
157+
*
158+
* // Check the result
159+
* if(rhs) {
160+
* std::cout << "Failed to compose transforms!\n";
161+
* } else {
162+
* std::cout << "Composition successful!\n";
163+
* }
164+
* @endcode
165+
*/
166+
static auto Compose(const Pointer& lhs, const Pointer& rhs)
167+
-> std::pair<Pointer, Pointer>;
168+
137169
/** @brief Save a transform to a JSON file */
138170
static void Save(const filesystem::path& path, const Pointer& transform);
139171
/** @brief Load a transform from a JSON file */
@@ -147,8 +179,20 @@ class Transform3D
147179
/** Only derived classes can copy */
148180
auto operator=(const Transform3D& other) -> Transform3D& = default;
149181

182+
/**
183+
* Helper compose function. The base implementation returns a nullptr.
184+
* Implementing derived classes should set the returned transform's source
185+
* to this->source() and the target to rhs->target().
186+
*/
187+
[[nodiscard]] virtual auto compose_(const Transform3D::Pointer& rhs) const
188+
-> Transform3D::Pointer;
189+
150190
/** On-disk metadata type */
151191
using Metadata = nlohmann::ordered_json;
192+
/** Serialize the transform to metadata */
193+
static auto Serialize(const Pointer& transform) -> Metadata;
194+
/** Deserialize the transform from metadata */
195+
static auto Deserialize(const Metadata& meta) -> Pointer;
152196
/** Serialize the derived class parameters */
153197
virtual void to_meta_(Metadata& meta) = 0;
154198
/** Deserialize the derived class parameters */
@@ -161,6 +205,18 @@ class Transform3D
161205
std::string tgt_;
162206
};
163207

208+
/**
209+
* @brief Compose transform convenience operator
210+
*
211+
* Same as Transform3D::Compose but only returns the composed transform. If
212+
* composition fails for any reason, will throw an exception.
213+
*
214+
* @throws std::invalid_argument if lhs or rhs are not composable
215+
* @throws std::runtime_error if transform composition failed
216+
*/
217+
auto operator*(const Transform3D::Pointer& lhs, const Transform3D::Pointer& rhs)
218+
-> Transform3D::Pointer;
219+
164220
/**
165221
* @brief 3D affine transform
166222
*
@@ -169,7 +225,7 @@ class Transform3D
169225
* transform with the stored affine transform. For example, the following
170226
* transform will scale, rotate, and translate the 3D point, in that order:
171227
*
172-
* @code
228+
* @code{.cpp}
173229
* auto tfm = AffineTransform::New();
174230
* // scale by 5
175231
* tfm->scale(5);
@@ -184,6 +240,9 @@ class Transform3D
184240
class AffineTransform : public Transform3D
185241
{
186242
public:
243+
/** @copydoc Transform3D::TYPE */
244+
static constexpr std::string_view TYPE{"AffineTransform"};
245+
187246
/** Parameters type: 4x4 matrix */
188247
using Parameters = cv::Matx<double, 4, 4>;
189248

@@ -194,17 +253,17 @@ class AffineTransform : public Transform3D
194253
static auto New() -> Pointer;
195254

196255
/** @copydoc Transform3D::type() */
197-
[[nodiscard]] auto type() const -> std::string final;
256+
[[nodiscard]] auto type() const -> std::string_view final;
198257
/** @copydoc Transform3D::clone() */
199258
[[nodiscard]] auto clone() const -> Transform3D::Pointer final;
200259
/** @copydoc Transform3D::invertible() */
201260
[[nodiscard]] auto invertible() const -> bool final;
202261
/** @copydoc Transform3D::invert() */
203262
[[nodiscard]] auto invert() const -> Transform3D::Pointer final;
263+
/** @copydoc Transform3D::composable() */
264+
[[nodiscard]] auto composable() const -> bool final;
204265
/** @copydoc Transform3D::reset() */
205266
void reset() final;
206-
/** @copydoc Transform3D::clear() */
207-
void clear() final;
208267

209268
/** @copydoc Transform3D::applyPoint() */
210269
[[nodiscard]] auto applyPoint(const cv::Vec3d& point) const
@@ -269,6 +328,161 @@ class AffineTransform : public Transform3D
269328
AffineTransform() = default;
270329
/** Current parameters */
271330
Parameters params_{cv::Matx<double, 4, 4>::eye()};
331+
/** @copydoc Transform3D::compose_() */
332+
[[nodiscard]] auto compose_(const Transform3D::Pointer& rhs) const
333+
-> Transform3D::Pointer final;
334+
/** @copydoc Transform3D::to_meta_() */
335+
void to_meta_(Metadata& meta) final;
336+
/** @copydoc Transform3D::from_meta_() */
337+
void from_meta_(const Metadata& meta) final;
338+
};
339+
340+
/**
341+
* @brief Identity transform
342+
*
343+
* Identity transform that simply returns input parameters. Useful for
344+
* creating explicit mappings between a source and a target which share the
345+
* same coordinate space.
346+
*
347+
* @code{.cpp}
348+
* auto tfm = IdentityTransform::New();
349+
* auto pt = tfm->applyPoint({0, 1, 0}); // {0, 1, 0}
350+
* @endcode
351+
*/
352+
class IdentityTransform : public Transform3D
353+
{
354+
public:
355+
/** @copydoc Transform3D::TYPE */
356+
static constexpr std::string_view TYPE{"IdentityTransform"};
357+
358+
/** Pointer type */
359+
using Pointer = std::shared_ptr<IdentityTransform>;
360+
361+
/** @brief Create a new IdentityTransform */
362+
static auto New() -> Pointer;
363+
364+
/** @copydoc Transform3D::type() */
365+
[[nodiscard]] auto type() const -> std::string_view final;
366+
/** @copydoc Transform3D::clone() */
367+
[[nodiscard]] auto clone() const -> Transform3D::Pointer final;
368+
/** @copydoc Transform3D::invertible() */
369+
[[nodiscard]] auto invertible() const -> bool final;
370+
/** @copydoc Transform3D::invert() */
371+
[[nodiscard]] auto invert() const -> Transform3D::Pointer final;
372+
/** @copydoc Transform3D::composable() */
373+
[[nodiscard]] auto composable() const -> bool final;
374+
/** @copydoc Transform3D::reset() */
375+
void reset() final;
376+
377+
/** @copydoc Transform3D::applyPoint() */
378+
[[nodiscard]] auto applyPoint(const cv::Vec3d& point) const
379+
-> cv::Vec3d final;
380+
/** @copydoc Transform3D::applyVector() */
381+
[[nodiscard]] auto applyVector(const cv::Vec3d& vector) const
382+
-> cv::Vec3d final;
383+
384+
private:
385+
/** Don't allow construction on the stack */
386+
IdentityTransform() = default;
387+
/** @copydoc Transform3D::compose_() */
388+
[[nodiscard]] auto compose_(const Transform3D::Pointer& rhs) const
389+
-> Transform3D::Pointer final;
390+
/** @copydoc Transform3D::to_meta_() */
391+
void to_meta_(Metadata& meta) final;
392+
/** @copydoc Transform3D::from_meta_() */
393+
void from_meta_(const Metadata& meta) final;
394+
};
395+
396+
/**
397+
* @brief Collection of transforms
398+
*
399+
* A convenience class which holds a list of transforms. When transforming
400+
* points and vectors, each transform is applied sequentially to the input.
401+
*
402+
* @code{.cpp}
403+
* // New transform
404+
* auto tfm = CompositeTransform::New();
405+
*
406+
* // Add some transforms
407+
* auto t = AffineTransform::New();
408+
* t->translate(1, 2, 3);
409+
* tfm->push_back(t);
410+
* t->reset();
411+
* t->scale(4);
412+
* tfm->push_back(t);
413+
*
414+
* // Apply all transforms to an input
415+
* auto pt = tfm->applyPoint({0, 1, 0}); // {4, 12, 12}
416+
* @endcode
417+
*
418+
* It can often be preferable, for both performance and numerical stability, to
419+
* simplify all adjacent, composable transforms (e.g. AffineTransform,
420+
* IdentityTransform) into a single transform.
421+
*
422+
* @code{.cpp}
423+
* // Add some composable transforms
424+
* tfm->push_back(AffineTransform::New());
425+
* tfm->push_back(IdentityTransform::New());
426+
* tfm->push_back(AffineTransform::New());
427+
* tfm->size(); // 3
428+
*
429+
* // Simplify the transform
430+
* tfm->simplify();
431+
* tfm->size(); // 1
432+
* @endcode
433+
*/
434+
class CompositeTransform : public Transform3D
435+
{
436+
public:
437+
/** @copydoc Transform3D::TYPE */
438+
static constexpr std::string_view TYPE{"CompositeTransform"};
439+
440+
/** Pointer type */
441+
using Pointer = std::shared_ptr<CompositeTransform>;
442+
443+
/** @brief Create a new CompositeTransform */
444+
static auto New() -> Pointer;
445+
446+
/** @copydoc Transform3D::type() */
447+
[[nodiscard]] auto type() const -> std::string_view final;
448+
/** @copydoc Transform3D::clone() */
449+
[[nodiscard]] auto clone() const -> Transform3D::Pointer final;
450+
/** @copydoc Transform3D::reset() */
451+
void reset() final;
452+
453+
/** @copydoc Transform3D::applyPoint() */
454+
[[nodiscard]] auto applyPoint(const cv::Vec3d& point) const
455+
-> cv::Vec3d final;
456+
/** @copydoc Transform3D::applyVector() */
457+
[[nodiscard]] auto applyVector(const cv::Vec3d& vector) const
458+
-> cv::Vec3d final;
459+
460+
/**
461+
* @brief Add a transform to the end of the composite transform stack
462+
*
463+
* The transform is cloned before being added to the transform stack. If
464+
* the transform is also a CompositeTransform, its transform stack is
465+
* expanded and copied to the end of this transform's stack.
466+
*/
467+
void push_back(const Transform3D::Pointer& t);
468+
469+
/** @brief Get the number of transforms in the composite transform */
470+
[[nodiscard]] auto size() const noexcept -> std::size_t;
471+
472+
/**
473+
* @brief Compose all composable transforms
474+
*
475+
* Simplifies the transform by composing all adjacent, composable
476+
* transforms in the composite transform list. This can lead to better
477+
* runtime performance and numerical stability for the apply functions.
478+
*/
479+
void simplify();
480+
481+
private:
482+
/** Don't allow construction on the stack */
483+
CompositeTransform() = default;
484+
/** Transform list */
485+
std::vector<Transform3D::Pointer> tfms_;
272486
/** @copydoc Transform3D::to_meta_() */
273487
void to_meta_(Metadata& meta) final;
274488
/** @copydoc Transform3D::from_meta_() */

0 commit comments

Comments
 (0)