Skip to content

Implement manual deep copy for C3DData instead of relying on ezc3d's shallow copy constructor#277

Merged
petersteneteg merged 15 commits intomasterfrom
copilot/add-c3d-file-loader-module
Mar 27, 2026
Merged

Implement manual deep copy for C3DData instead of relying on ezc3d's shallow copy constructor#277
petersteneteg merged 15 commits intomasterfrom
copilot/add-c3d-file-loader-module

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 17, 2026

ezc3d::c3d stores Header, Parameters, and Data as shared_ptr internally, and Frame similarly stores Points, Analogs, Rotations as shared_ptr. The copy constructor only copies the pointers, so the previous deepCopy() via std::make_shared<ezc3d::c3d>(*data_) produced a shallow copy where mutations to the copy would affect the original.

Replaced with a manual recursive reconstruction:

  • Point/Analog registration — calls c3d::point(name) and c3d::analog(name) to initialize the POINT/ANALOG configuration consistently in the new object
  • Frame-level deep copy — rebuilds each frame from scratch:
    • Points: copies x, y, z, residual, cameraMask per point
    • Analogs: copies each subframe's channel data values
    • Rotations: copies all 4×4 matrix elements + reliability via Rotation::set()
  • Parameter groups — copies non-auto-managed groups (FORCE_PLATFORM, TRIAL, custom metadata) via c3d::parameter(); POINT/ANALOG groups are skipped since they're already initialized by the registration step
// Before: shallow — src and copy share the same internal data
return C3DData{std::make_shared<ezc3d::c3d>(*data_), source_};

// After: fully independent copy
auto dst = std::make_shared<ezc3d::c3d>();
for (const auto& name : src.pointNames()) { dst->point(name); }
for (const auto& name : src.channelNames()) { dst->analog(name); }
// ... rebuild each frame element by element ...
return C3DData{std::move(dst), source_};

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Add Inviwo module for loading C3D files Add C3D module for loading biomechanics motion capture files via ezc3d Mar 17, 2026
Copilot AI requested a review from petersteneteg March 17, 2026 15:15
Copilot AI changed the title Add C3D module for loading biomechanics motion capture files via ezc3d Hide ezc3d include from public headers using forward declaration Mar 18, 2026
Copilot AI changed the title Hide ezc3d include from public headers using forward declaration Hide ezc3d include from public C3D module headers Mar 18, 2026
Copilot AI changed the title Hide ezc3d include from public C3D module headers Change C3DToMesh frame property to MinMaxProperty for multi-frame extraction Mar 18, 2026
Copilot AI changed the title Change C3DToMesh frame property to MinMaxProperty for multi-frame extraction Add MinMaxProperty frame range and picking support to C3DToMesh Mar 18, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 18, 2026

Cpp-Linter Report ⚠️

Some files did not pass the configured checks!

clang-tidy (v19.1.1) reports: 3 concern(s)
  • misc/c3d/include/inviwo/c3d/c3dmodule.h:36:26: warning: [cppcoreguidelines-special-member-functions]

    class 'C3DModule' defines a default destructor but does not define a copy constructor, a copy assignment operator, a move constructor or a move assignment operator

       36 | class IVW_MODULE_C3D_API C3DModule : public InviwoModule {
          |                          ^
  • misc/c3d/src/processors/c3dpointalignment.cpp:62:53: warning: [modernize-return-braced-init-list]

    avoid repeating the return type from the declaration; use a braced initializer list instead

       62 | glm::dvec3 toGLM(const Eigen::Vector3d& v) { return glm::dvec3(v(0), v(1), v(2)); }
          |                                                     ^~~~~~~~~~~                ~
          |                                                     {                          }
  • misc/c3d/src/processors/c3dtodataframe.cpp:72:22: warning: [readability-function-cognitive-complexity]

    function 'process' has cognitive complexity of 38 (threshold 25)

       72 | void C3DToDataFrame::process() {
          |                      ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:88:9: note: +1, including nesting penalty of 0, nesting level increased to 1
       88 |         for (size_t f = 0; f < nbFrames; ++f) {
          |         ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:90:48: note: +2, including nesting penalty of 1, nesting level increased to 2
       90 |             timeValues[f] = (frameRate > 0.0f) ? static_cast<float>(f) / frameRate : 0.0f;
          |                                                ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:96:9: note: +1, including nesting penalty of 0, nesting level increased to 1
       96 |         for (size_t p = 0; p < nbPoints; ++p) {
          |         ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:103:13: note: +2, including nesting penalty of 1, nesting level increased to 2
      103 |             if (includeResiduals_) {
          |             ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:107:13: note: +2, including nesting penalty of 1, nesting level increased to 2
      107 |             for (size_t f = 0; f < nbFrames; ++f) {
          |             ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:112:17: note: +3, including nesting penalty of 2, nesting level increased to 3
      112 |                 if (includeResiduals_) {
          |                 ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:120:13: note: +2, including nesting penalty of 1, nesting level increased to 2
      120 |             if (includeResiduals_) {
          |             ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:137:9: note: +1, including nesting penalty of 0, nesting level increased to 1
      137 |         if (nbAnalogs > 0 && nbAnalogByFrame > 0) {
          |         ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:137:27: note: +1
      137 |         if (nbAnalogs > 0 && nbAnalogByFrame > 0) {
          |                           ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:147:36: note: +2, including nesting penalty of 1, nesting level increased to 2
      147 |                 (frameRate > 0.0f) ? frameRate * static_cast<float>(nbSubframes) : 0.0f;
          |                                    ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:149:13: note: +2, including nesting penalty of 1, nesting level increased to 2
      149 |             for (size_t f = 0; f < nbFrames; ++f) {
          |             ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:150:17: note: +3, including nesting penalty of 2, nesting level increased to 3
      150 |                 for (size_t sf = 0; sf < nbSubframes; ++sf) {
          |                 ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:155:45: note: +4, including nesting penalty of 3, nesting level increased to 4
      155 |                         (analogRate > 0.0f) ? static_cast<float>(idx) / analogRate : 0.0f;
          |                                             ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:164:13: note: +2, including nesting penalty of 1, nesting level increased to 2
      164 |             for (size_t ch = 0; ch < nbAnalogs; ++ch) {
          |             ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:167:17: note: +3, including nesting penalty of 2, nesting level increased to 3
      167 |                 for (size_t f = 0; f < nbFrames; ++f) {
          |                 ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:169:21: note: +4, including nesting penalty of 3, nesting level increased to 4
      169 |                     for (size_t sf = 0; sf < nbSubframes; ++sf) {
          |                     ^
    /home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:176:48: note: +3, including nesting penalty of 2, nesting level increased to 3
      176 |                     (ch < channelNames.size()) ? channelNames[ch] : fmt::format("Channel_{}", ch);
          |                                                ^

Have any feedback or feature suggestions? Share it here.

Copilot AI and others added 5 commits March 19, 2026 11:20
Co-authored-by: petersteneteg <3638222+petersteneteg@users.noreply.github.com>
Replace #include <ezc3d/ezc3d.h> in c3ddata.h with a forward
declaration of ezc3d::c3d, following the DICOM module pattern.
Move DataTraits info() implementation to c3ddatatraits.cpp to
keep the external library confined to .cpp files only.

Co-authored-by: petersteneteg <3638222+petersteneteg@users.noreply.github.com>
…rame extraction

Co-authored-by: petersteneteg <3638222+petersteneteg@users.noreply.github.com>
Co-authored-by: petersteneteg <3638222+petersteneteg@users.noreply.github.com>
Copilot AI changed the title Add MinMaxProperty frame range and picking support to C3DToMesh Add picking support and deep copy to C3D module Mar 21, 2026
…py()

Co-authored-by: petersteneteg <3638222+petersteneteg@users.noreply.github.com>
Agent-Logs-Url: https://github.com/inviwo/modules/sessions/bf174c8c-18ac-496c-ba0d-bf3876b29763
Copilot AI changed the title Add picking support and deep copy to C3D module Implement manual deep copy for C3DData instead of relying on ezc3d's shallow copy constructor Mar 21, 2026
@petersteneteg petersteneteg marked this pull request as ready for review March 26, 2026 12:29
Copy link
Copy Markdown
Contributor

@martinfalk martinfalk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks reasonable

"Motion Tracking", // Category
CodeState::Experimental, // Code state
Tags::CPU | Tag{"C3D"}, // Tags
R"(<Explanation of how to use the processor.>)"_unindentHelp,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix

Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cpp-linter Review

Used clang-tidy v19.1.1

Click here for the full clang-tidy patch
diff --git a/misc/c3d/src/processors/c3dpointalignment.cpp b/misc/c3d/src/processors/c3dpointalignment.cpp
index 675f3d3..2bb3669 100644
--- a/misc/c3d/src/processors/c3dpointalignment.cpp
+++ b/misc/c3d/src/processors/c3dpointalignment.cpp
@@ -62 +62 @@ glm::dmat4 toGLM(const Eigen::Matrix3d& R, const Eigen::Vector3d& t) {
-glm::dvec3 toGLM(const Eigen::Vector3d& v) { return glm::dvec3(v(0), v(1), v(2)); }
+glm::dvec3 toGLM(const Eigen::Vector3d& v) { return {v(0), v(1), v(2)}; }

Have any feedback or feature suggestions? Share it here.


namespace inviwo {

class IVW_MODULE_C3D_API C3DModule : public InviwoModule {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy diagnostic

misc/c3d/include/inviwo/c3d/c3dmodule.h:36:26: warning: [cppcoreguidelines-special-member-functions]

class 'C3DModule' defines a default destructor but does not define a copy constructor, a copy assignment operator, a move constructor or a move assignment operator

   36 | class IVW_MODULE_C3D_API C3DModule : public InviwoModule {
      |                          ^

return M;
}

glm::dvec3 toGLM(const Eigen::Vector3d& v) { return glm::dvec3(v(0), v(1), v(2)); }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy diagnostics

Suggested change
glm::dvec3 toGLM(const Eigen::Vector3d& v) { return glm::dvec3(v(0), v(1), v(2)); }
glm::dvec3 toGLM(const Eigen::Vector3d& v) { return {v(0), v(1), v(2)}; }

addProperties(includeResiduals_);
}

void C3DToDataFrame::process() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy diagnostic

misc/c3d/src/processors/c3dtodataframe.cpp:72:22: warning: [readability-function-cognitive-complexity]

function 'process' has cognitive complexity of 38 (threshold 25)

   72 | void C3DToDataFrame::process() {
      |                      ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:88:9: note: +1, including nesting penalty of 0, nesting level increased to 1
   88 |         for (size_t f = 0; f < nbFrames; ++f) {
      |         ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:90:48: note: +2, including nesting penalty of 1, nesting level increased to 2
   90 |             timeValues[f] = (frameRate > 0.0f) ? static_cast<float>(f) / frameRate : 0.0f;
      |                                                ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:96:9: note: +1, including nesting penalty of 0, nesting level increased to 1
   96 |         for (size_t p = 0; p < nbPoints; ++p) {
      |         ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:103:13: note: +2, including nesting penalty of 1, nesting level increased to 2
  103 |             if (includeResiduals_) {
      |             ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:107:13: note: +2, including nesting penalty of 1, nesting level increased to 2
  107 |             for (size_t f = 0; f < nbFrames; ++f) {
      |             ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:112:17: note: +3, including nesting penalty of 2, nesting level increased to 3
  112 |                 if (includeResiduals_) {
      |                 ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:120:13: note: +2, including nesting penalty of 1, nesting level increased to 2
  120 |             if (includeResiduals_) {
      |             ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:137:9: note: +1, including nesting penalty of 0, nesting level increased to 1
  137 |         if (nbAnalogs > 0 && nbAnalogByFrame > 0) {
      |         ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:137:27: note: +1
  137 |         if (nbAnalogs > 0 && nbAnalogByFrame > 0) {
      |                           ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:147:36: note: +2, including nesting penalty of 1, nesting level increased to 2
  147 |                 (frameRate > 0.0f) ? frameRate * static_cast<float>(nbSubframes) : 0.0f;
      |                                    ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:149:13: note: +2, including nesting penalty of 1, nesting level increased to 2
  149 |             for (size_t f = 0; f < nbFrames; ++f) {
      |             ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:150:17: note: +3, including nesting penalty of 2, nesting level increased to 3
  150 |                 for (size_t sf = 0; sf < nbSubframes; ++sf) {
      |                 ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:155:45: note: +4, including nesting penalty of 3, nesting level increased to 4
  155 |                         (analogRate > 0.0f) ? static_cast<float>(idx) / analogRate : 0.0f;
      |                                             ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:164:13: note: +2, including nesting penalty of 1, nesting level increased to 2
  164 |             for (size_t ch = 0; ch < nbAnalogs; ++ch) {
      |             ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:167:17: note: +3, including nesting penalty of 2, nesting level increased to 3
  167 |                 for (size_t f = 0; f < nbFrames; ++f) {
      |                 ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:169:21: note: +4, including nesting penalty of 3, nesting level increased to 4
  169 |                     for (size_t sf = 0; sf < nbSubframes; ++sf) {
      |                     ^
/home/runner/work/modules/modules/inviwo/misc/c3d/src/processors/c3dtodataframe.cpp:176:48: note: +3, including nesting penalty of 2, nesting level increased to 3
  176 |                     (ch < channelNames.size()) ? channelNames[ch] : fmt::format("Channel_{}", ch);
      |                                                ^

@petersteneteg petersteneteg merged commit a329142 into master Mar 27, 2026
18 checks passed
@petersteneteg petersteneteg deleted the copilot/add-c3d-file-loader-module branch March 27, 2026 06:58
github-actions bot pushed a commit to inviwo/regression that referenced this pull request Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants