Skip to content

Commit

Permalink
Merge pull request #305 from pariterre/dev
Browse files Browse the repository at this point in the history
Fixed number of frames for nframes > 32767 and made creating them much faster
  • Loading branch information
pariterre authored Dec 11, 2023
2 parents e8eb29c + e983ddb commit 827b2cd
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 29 deletions.
6 changes: 4 additions & 2 deletions binding/matlab/ezc3dWrite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,8 @@ void mexFunction(
}
mxDouble* allDataAnalogs = mxGetDoubles(dataAnalogs);
mxDouble* allDataRotations = mxGetDoubles(dataRotations);

std::vector<ezc3d::DataNS::Frame> frames;
for (size_t f=0; f<nFrames; ++f){
ezc3d::DataNS::Frame frame;
ezc3d::DataNS::Points3dNS::Points pts;
Expand Down Expand Up @@ -564,9 +566,9 @@ void mexFunction(
}

frame.add(pts, analogs, rotations);
c3d.frame(frame); // Add the previously created frame
frames.push_back(frame);
}

c3d.frames(frames);

c3d.write(path);
return;
Expand Down
6 changes: 4 additions & 2 deletions binding/octave/ezc3dWrite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,8 @@ void mexFunction(
}
mxDouble* allDataAnalogs = mxGetPr(dataAnalogs);
mxDouble* allDataRotations = mxGetPr(dataRotations);

std::vector<ezc3d::DataNS::Frame> frames;
for (size_t f=0; f<nFrames; ++f){
ezc3d::DataNS::Frame frame;
ezc3d::DataNS::Points3dNS::Points pts;
Expand Down Expand Up @@ -565,10 +567,10 @@ void mexFunction(
}

frame.add(pts, analogs, rotations);
c3d.frame(frame); // Add the previously created frame
frames.push_back(frame);
}
c3d.frames(frames);


c3d.write(path);
return;
}
2 changes: 1 addition & 1 deletion binding/python3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ def write(self, path: str, *, first_frame_as_zero: bool = False):
for i in range(nb_analog_subframes):
analogs.subframe(subframe)

# # Fill the data
# Fill the data
new_c3d.import_numpy_data(
data_points, data_meta_points["residuals"], data_meta_points["camera_masks"], data_analogs, data_rotations
)
Expand Down
5 changes: 4 additions & 1 deletion binding/python3/ezc3d_python.i
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,10 @@ PyArrayObject *helper_getPyArrayObject( PyObject *input, int type) {
ezc3d::DataNS::RotationNS::SubFrame rotationsSubframe;
ezc3d::DataNS::RotationNS::Rotations rotations;

std::vector<ezc3d::DataNS::Frame> allFrames;
ezc3d::DataNS::Frame currFrame;


for(size_t f = 0; f < nbFrames; ++f){
for(size_t i = 0; i < nbPoints; ++i){
const double x = *static_cast<double*>(PyArray_GETPTR3(pointsData, 0, i, f));
Expand Down Expand Up @@ -321,8 +323,9 @@ PyArrayObject *helper_getPyArrayObject( PyObject *input, int type) {
}

currFrame.add(pts, analogs, rotations);
self->frame(currFrame);
allFrames.push_back(currFrame);
}
self->frames(allFrames);
}
%}

Expand Down
26 changes: 25 additions & 1 deletion include/ezc3d/ezc3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ class EZC3D_API ezc3d::c3d {
/// \brief Add/replace a frame to the data set
/// \param frame The frame to copy to the data
/// \param idx The index of the frame in the data set
/// \param skipInternalUpdates If the updates of Parameters and Headers should be skipped
///
/// Add or replace a frame to the data set.
///
Expand All @@ -456,6 +457,10 @@ class EZC3D_API ezc3d::c3d {
/// If idx is larger than the number of frames, it resize the frames accordingly and add the frame
/// where it belongs but leaves the other created frames empty.
///
/// If [skipInternalUpdates] is set to true, then no checks or updates are done to the parameters and
/// the headers. This greatly improves the speed of creating a new file, but conversely it can create
/// corrupted C3D.
///
/// Throw a std::runtime_error if the number of points defined in POINT:USED parameter doesn't correspond
/// to the number of point in the frame.
///
Expand All @@ -467,7 +472,26 @@ class EZC3D_API ezc3d::c3d {
///
void frame(
const ezc3d::DataNS::Frame &frame,
size_t idx = SIZE_MAX);
size_t idx = SIZE_MAX,
bool skipInternalUpdates = false
);

///
/// \brief Add/replace a batch of frames, calling the frame() method repeateadly but skipping the updateParameter
/// \param frames All the frame to add
/// \param firstFrameIdx The index of the frame of frames[0]. The others are assumed to be increments of one
///
/// See frame() for the description of the function with [skipInternalUpdates] set to true for all but the first and last
/// frames. If no [firstFrameIdx] is sent, then all the frames are appended to the existing values.
///
/// WARNING: since no checks are performed on the frames (apart from the first and the last), all the frames must have the
/// same number of subframes, points and analogs. Failing to do so will not throw an error, but will create a corrupted C3D
/// file.
///
void frames(
const std::vector<ezc3d::DataNS::Frame> frames,
size_t firstFrameidx = SIZE_MAX
);

///
/// \brief Create a point to the data set of name pointName
Expand Down
10 changes: 7 additions & 3 deletions src/Data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,19 @@ ezc3d::DataNS::Data::Data(
ezc3d::DataNS::RotationNS::Info rotationsInfo(c3d);

for (size_t j = 0; j < c3d.header().nbFrames(); ++j){
if (file.eof())
break;

ezc3d::DataNS::Frame f;
// Read point 3d
f.add(ezc3d::DataNS::Points3dNS::Points(c3d, file, pointsInfo));

// Read analogs
f.add(ezc3d::DataNS::AnalogsNS::Analogs(c3d, file, analogsInfo));

// If we ran out of space, then leave. The reason we test here is because
// is set after failing, resulting in one extra frame added if this if
// is after the push_back
if (file.eof())
break;

_frames.push_back(f);
}

Expand Down
9 changes: 3 additions & 6 deletions src/Header.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,13 @@ void ezc3d::Header::write(
) const {
// write the checksum byte and the start point of header
int parameterAddessDefault(2);
f.write(reinterpret_cast<const char*>(
&parameterAddessDefault), ezc3d::BYTE);
f.write(reinterpret_cast<const char*>(&parameterAddessDefault), ezc3d::BYTE);
int checksum(0x50);
f.write(reinterpret_cast<const char*>(&checksum), ezc3d::BYTE);

// Number of data
f.write(reinterpret_cast<const char*>(&_nb3dPoints),
1*ezc3d::DATA_TYPE::WORD);
f.write(reinterpret_cast<const char*>(&_nbAnalogsMeasurement),
1*ezc3d::DATA_TYPE::WORD);
f.write(reinterpret_cast<const char*>(&_nb3dPoints), 1*ezc3d::DATA_TYPE::WORD);
f.write(reinterpret_cast<const char*>(&_nbAnalogsMeasurement), 1*ezc3d::DATA_TYPE::WORD);

// Idx of first and last frame
size_t firstFrame(_firstFrame + (forceZeroBasedOnFrameCount ? 0 : 1)); // 1-based!
Expand Down
27 changes: 23 additions & 4 deletions src/ezc3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -564,17 +564,26 @@ void ezc3d::c3d::unlockGroup(

void ezc3d::c3d::frame(
const ezc3d::DataNS::Frame &f,
size_t idx) {
size_t idx,
bool skipInternalUpdates
) {

// Replace the jth frame
_data->frame(f, idx);

if (skipInternalUpdates) return;

// Make sure f.points().points() is the same as data.f[ANY].points()
size_t nPoints(static_cast<size_t>(parameters().group("POINT")
.parameter("USED").valuesAsInt()[0]));
if (nPoints != 0 && f.points().nbPoints() != nPoints)
throw std::runtime_error(
"Number of points in POINT:USED parameter must equal"
"Number of points in POINT:USED parameter must equal to "
"the number of points sent in the frame");

std::vector<std::string> labels(parameters().group("POINT")
.parameter("LABELS").valuesAsString());

try {
// Check if all the labels are in the actual LABELSX parameter
const std::vector<std::string> &namesParameter(pointNames());
Expand Down Expand Up @@ -618,11 +627,21 @@ void ezc3d::c3d::frame(
"the number of analogs sent in the frame");
}

// Replace the jth frame
_data->frame(f, idx);
// Finalize the internal structure
updateParameters();
}

void ezc3d::c3d::frames(
const std::vector<ezc3d::DataNS::Frame> frames,
size_t firstFrameidx) {

for (int i = 0; i < frames.size(); i++) {
// Only performs internal updates on the first and last frames
bool skipInternalUpdates = i > 0 && i < frames.size() - 1;
frame(frames[i], firstFrameidx == SIZE_MAX ? SIZE_MAX : firstFrameidx + i, skipInternalUpdates);
}
}

void ezc3d::c3d::point(
const std::string &pointName) {
if (data().nbFrames() > 0){
Expand Down
37 changes: 28 additions & 9 deletions test/test_ezc3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct c3dTestStruct{
std::vector<std::string> analogNames;
};

void fillC3D(c3dTestStruct& c3dStruc, bool withPoints, bool withAnalogs){
void fillC3D(c3dTestStruct& c3dStruc, bool withPoints, bool withAnalogs, int nFrames = 10){
// Setup some variables
if (withPoints){
c3dStruc.pointNames = {"point1", "point2", "point3"};
Expand All @@ -59,7 +59,7 @@ void fillC3D(c3dTestStruct& c3dStruc, bool withPoints, bool withAnalogs){
c3dStruc.c3d.analog(c3dStruc.analogNames[a]);
}

c3dStruc.nFrames = 10;
c3dStruc.nFrames = nFrames;
c3dStruc.pointFrameRate = 100;
if (withPoints){
ezc3d::ParametersNS::GroupNS::Parameter pointRate("RATE");
Expand All @@ -76,9 +76,11 @@ void fillC3D(c3dTestStruct& c3dStruc, bool withPoints, bool withAnalogs){
static_cast<double>(c3dStruc.analogFrameRate)});
c3dStruc.c3d.parameter("ANALOG", analogRate);
}

std::vector<ezc3d::DataNS::Frame> frames;
for (size_t f = 0; f < c3dStruc.nFrames; ++f){
ezc3d::DataNS::Frame frame;

ezc3d::DataNS::Points3dNS::Points pts;
if (withPoints){
for (size_t m = 0; m < c3dStruc.nPoints; ++m){
Expand Down Expand Up @@ -110,8 +112,10 @@ void fillC3D(c3dTestStruct& c3dStruc, bool withPoints, bool withAnalogs){
frame.add(pts);
else if (withAnalogs)
frame.add(analogs);
c3dStruc.c3d.frame(frame);

frames.push_back(frame);
}
c3dStruc.c3d.frames(frames);
}

void defaultHeaderTest(const ezc3d::c3d& new_c3d, HEADER_TYPE type = HEADER_TYPE::ALL){
Expand Down Expand Up @@ -919,7 +923,6 @@ TEST(c3dModifier, addPoints) {
remove(savepath.c_str());
}


TEST(c3dModifier, specificPoint){
// Create an empty c3d
c3dTestStruct new_c3d;
Expand Down Expand Up @@ -1720,6 +1723,22 @@ TEST(c3dFileIO, CreateWriteAndReadBackWithNan){
EXPECT_TRUE(std::isnan(channel.data()));
}

TEST(c3dFileIO, writeLotOfFrames) {
// Create an empty c3d fill it with data and reopen
c3dTestStruct ref_c3d;
fillC3D(ref_c3d, true, false, 0xFFFF);

// Write the file
std::string savePath("temporary.c3d");
ref_c3d.c3d.write(savePath.c_str());

// Open it back and delete it
ezc3d::c3d read_c3d(savePath.c_str());
remove(savePath.c_str());

EXPECT_EQ(ref_c3d.c3d.header().nbFrames(), read_c3d.header().nbFrames());
}

TEST(c3dFileIO, readC3DWithRotation){
ezc3d::c3d c3d("c3dTestFiles/C3DRotationExample.c3d");

Expand Down Expand Up @@ -2127,8 +2146,8 @@ TEST(c3dFileIO, readOptotrakC3D){


EXPECT_EQ(Optotrak.header().firstFrame(), 0);
EXPECT_EQ(Optotrak.header().lastFrame(), 29);
EXPECT_EQ(Optotrak.header().nbFrames(), 30);
EXPECT_EQ(Optotrak.header().lastFrame(), 28);
EXPECT_EQ(Optotrak.header().nbFrames(), 29);


// Parameter tests
Expand All @@ -2144,7 +2163,7 @@ TEST(c3dFileIO, readOptotrakC3D){
EXPECT_EQ(Optotrak.parameters().group("POINT").parameter("RATE").type(), ezc3d::FLOAT);
EXPECT_EQ(Optotrak.parameters().group("POINT").parameter("RATE").valuesAsDouble().size(), 1);
EXPECT_FLOAT_EQ(Optotrak.parameters().group("POINT").parameter("RATE").valuesAsDouble()[0], 30);
EXPECT_EQ(Optotrak.parameters().group("POINT").parameter("FRAMES").valuesAsInt()[0], 30); // ignore because it changes if analog is present
EXPECT_EQ(Optotrak.parameters().group("POINT").parameter("FRAMES").valuesAsInt()[0], 29); // ignore because it changes if analog is present
EXPECT_EQ(Optotrak.parameters().group("POINT").parameter("FRAMES").type(), ezc3d::INT);
EXPECT_EQ(Optotrak.parameters().group("POINT").parameter("LABELS").type(), ezc3d::CHAR);
EXPECT_EQ(Optotrak.parameters().group("POINT").parameter("LABELS").valuesAsString().size(), 54);
Expand All @@ -2157,7 +2176,7 @@ TEST(c3dFileIO, readOptotrakC3D){
defaultParametersTest(Optotrak, PARAMETER_TYPE::FORCE_PLATFORM);

// DATA
for (size_t f = 0; f < 30; ++f)
for (size_t f = 0; f < 29; ++f)
EXPECT_EQ(Optotrak.data().frame(f).points().nbPoints(), 54);
}

Expand Down

0 comments on commit 827b2cd

Please sign in to comment.