Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix slow C3DFileAdpaterReading issue #1698 #1730

Merged
merged 10 commits into from
May 25, 2017
166 changes: 91 additions & 75 deletions OpenSim/Common/C3DFileAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,19 @@ C3DFileAdapter::extendRead(const std::string& fileName) const {
reader->Update();
auto acquisition = reader->GetOutput();

EventTable event_table{};
auto events = acquisition->GetEvents();
for (auto it = events->Begin();
it != events->End();
++it) {
auto et = *it;
event_table.push_back({ et->GetLabel(),
et->GetTime(),
et->GetFrame(),
et->GetDescription() });
}

Copy link
Member

Choose a reason for hiding this comment

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

Is there a particular reason for moving this?

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought it read better to get the events first, which are then added to both the marker and force tables that are created in the two code blocks below. I also thought I might be able to extract the start time for the acquisition at some point.

OutputTables tables{};
auto& marker_table = *(new TimeSeriesTableVec3{});
auto& force_table = *(new TimeSeriesTableVec3{});
tables.emplace(_markers,
std::shared_ptr<TimeSeriesTableVec3>(&marker_table));
tables.emplace(_forces,
std::shared_ptr<TimeSeriesTableVec3>(&force_table));

auto marker_pts = btk::PointCollection::New();

Expand All @@ -88,45 +94,53 @@ C3DFileAdapter::extendRead(const std::string& fileName) const {
}

if(marker_pts->GetItemNumber() != 0) {
marker_table.
updTableMetaData().
setValueForKey("DataRate",
std::to_string(acquisition->GetPointFrequency()));

marker_table.
updTableMetaData().
setValueForKey("Units",
acquisition->GetPointUnit());

ValueArray<std::string> marker_labels{};
for(auto it = marker_pts->Begin();
it != marker_pts->End();
++it) {
marker_labels.
upd().
push_back(SimTK::Value<std::string>((*it)->GetLabel()));
}
int marker_nrow = marker_pts->GetFrontItem()->GetFrameNumber();
int marker_ncol = marker_pts->GetItemNumber();

TimeSeriesTableVec3::DependentsMetaData marker_dep_metadata{};
marker_dep_metadata.setValueArrayForKey("labels", marker_labels);
marker_table.setDependentsMetaData(marker_dep_metadata);
std::vector<double> marker_times(marker_nrow);
SimTK::Matrix_<SimTK::Vec3> marker_matrix(marker_nrow, marker_ncol);

std::vector<std::string> marker_labels{};
for (auto it = marker_pts->Begin(); it != marker_pts->End(); ++it) {
marker_labels.push_back(SimTK::Value<std::string>((*it)->GetLabel()));
}

double time_step{1.0 / acquisition->GetPointFrequency()};
for(int f = 0;
f < marker_pts->GetFrontItem()->GetFrameNumber();
++f) {
SimTK::RowVector_<SimTK::Vec3> row{marker_pts->GetItemNumber()};
int m{0};
for(auto it = marker_pts->Begin();
it != marker_pts->End();
++it) {
for(auto it = marker_pts->Begin(); it != marker_pts->End(); ++it) {
auto pt = *it;
row[m++] = SimTK::Vec3{pt->GetValues().coeff(f, 0),
pt->GetValues().coeff(f, 1),
pt->GetValues().coeff(f, 2)};
}
marker_table.appendRow(0 + f * time_step, row);

marker_matrix.updRow(f) = row;
marker_times[f] = 0 + f * time_step; // pt->GetValues();
Copy link
Member

Choose a reason for hiding this comment

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

Could you either provide more detail about the comment or remove it?

}

// Create the data
auto& marker_table = *new
TimeSeriesTableVec3 (marker_times, marker_matrix, marker_labels);
Copy link
Member

Choose a reason for hiding this comment

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

Space between Vec3 and (


marker_table.
updTableMetaData().
setValueForKey("DataRate",
std::to_string(acquisition->GetPointFrequency()));

marker_table.
updTableMetaData().
setValueForKey("Units",
acquisition->GetPointUnit());

marker_table.updTableMetaData().setValueForKey("events", event_table);

tables.emplace(_markers,
std::shared_ptr<TimeSeriesTableVec3>(&marker_table));
}

// This is probably the right way to get the raw forces data from force
Expand Down Expand Up @@ -174,56 +188,35 @@ C3DFileAdapter::extendRead(const std::string& fileName) const {
}

if(fp_force_pts->GetItemNumber() != 0) {
force_table.
updTableMetaData().
setValueForKey("CalibrationMatrices", std::move(fpCalMatrices));

force_table.
updTableMetaData().
setValueForKey("Corners", std::move(fpCorners));

force_table.
updTableMetaData().
setValueForKey("Origins", std::move(fpOrigins));

force_table.
updTableMetaData().
setValueForKey("Types", std::move(fpTypes));

force_table.
updTableMetaData().
setValueForKey("DataRate",
std::to_string(acquisition->GetAnalogFrequency()));

ValueArray<std::string> labels{};
std::vector<std::string> labels{};
ValueArray<std::string> units{};
for(int fp = 1; fp <= fp_force_pts->GetItemNumber(); ++fp) {
auto fp_str = std::to_string(fp);

labels.upd().push_back(SimTK::Value<std::string>("f" + fp_str));
labels.push_back(SimTK::Value<std::string>("f" + fp_str));
auto force_unit = acquisition->GetPointUnits().
at(_unit_index.at("force"));
units.upd().push_back(SimTK::Value<std::string>(force_unit));

labels.upd().push_back(SimTK::Value<std::string>("m" + fp_str));
labels.push_back(SimTK::Value<std::string>("m" + fp_str));
auto moment_unit = acquisition->GetPointUnits().
at(_unit_index.at("moment"));
units.upd().push_back(SimTK::Value<std::string>(moment_unit));

labels.upd().push_back(SimTK::Value<std::string>("p" + fp_str));
labels.push_back(SimTK::Value<std::string>("p" + fp_str));
auto position_unit = acquisition->GetPointUnits().
at(_unit_index.at("marker"));
units.upd().push_back(SimTK::Value<std::string>(position_unit));
}
TimeSeriesTableVec3::DependentsMetaData force_dep_metadata{};
force_dep_metadata.setValueArrayForKey("labels", labels);
force_dep_metadata.setValueArrayForKey("units", units);
force_table.setDependentsMetaData(force_dep_metadata);

const size_t nf = fp_force_pts->GetFrontItem()->GetFrameNumber();

std::vector<double> force_times(nf);
SimTK::Matrix_<SimTK::Vec3> force_matrix(nf, labels.size());

double time_step{1.0 / acquisition->GetAnalogFrequency()};
for(int f = 0;
f < fp_force_pts->GetFrontItem()->GetFrameNumber();
++f) {
for(int f = 0; f < force_times.size(); ++f) {
Copy link
Member

Choose a reason for hiding this comment

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

Consider using nf instead of force_times.size().

Copy link
Member

Choose a reason for hiding this comment

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

A straight replacement would cause a compiler warning (type mismatch). Would need to do

for (int f = 0; f < static_cast<int>(nf); ++f)

SimTK::RowVector_<SimTK::Vec3>
row{fp_force_pts->GetItemNumber() * 3};
int col{0};
Expand All @@ -247,23 +240,46 @@ C3DFileAdapter::extendRead(const std::string& fileName) const {
(*pit)->GetValues().coeff(f, 2)};
++col;
}
force_table.appendRow(0 + f * time_step, row);
force_matrix.updRow(f) = row;
force_times[f] = 0 + f * time_step;
}
}

EventTable event_table{};
auto events = acquisition->GetEvents();
for(auto it = events->Begin();
it != events->End();
++it) {
auto et = *it;
event_table.push_back({et->GetLabel(),
et->GetTime(),
et->GetFrame(),
et->GetDescription()});
}
marker_table.updTableMetaData().setValueForKey("events", event_table);
auto& force_table =
*(new TimeSeriesTableVec3(force_times, force_matrix, labels));

TimeSeriesTableVec3::DependentsMetaData force_dep_metadata
= force_table.getDependentsMetaData();

// add units to the dependent meta data
force_dep_metadata.setValueArrayForKey("units", units);
force_table.setDependentsMetaData(force_dep_metadata);

force_table.
updTableMetaData().
setValueForKey("CalibrationMatrices", std::move(fpCalMatrices));

force_table.
updTableMetaData().
setValueForKey("Corners", std::move(fpCorners));

force_table.
updTableMetaData().
setValueForKey("Origins", std::move(fpOrigins));

force_table.
updTableMetaData().
setValueForKey("Types", std::move(fpTypes));

force_table.
updTableMetaData().
setValueForKey("DataRate",
std::to_string(acquisition->GetAnalogFrequency()));

tables.emplace(_forces,
std::shared_ptr<TimeSeriesTableVec3>(&force_table));

force_table.updTableMetaData().setValueForKey("events", event_table);
}

return tables;
}
Expand Down
28 changes: 22 additions & 6 deletions OpenSim/Common/DataTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,8 @@ class DataTable_ : public AbstractDataTable {
// No "labels". So no operation.
}
_depData.resize(1, depRow.size());
} else
}
else
_depData.resizeKeep(_depData.nrow() + 1, _depData.ncol());

_depData.updRow(_depData.nrow() - 1) = depRow;
Expand Down Expand Up @@ -1158,6 +1159,23 @@ class DataTable_ : public AbstractDataTable {
}

protected:
/** Convenience Constructor to efficiently populate a data table from
known data. This is primarily useful for reading in large data tables
without having to reallocate and copy memory. NOTE derived tables
must validate the table according to the needs of the concrete type.*/
DataTable_(const std::vector<ETX>& indVec,
const SimTK::Matrix_<ETY>& depData,
const std::vector<std::string>& labels) {
OPENSIM_THROW_IF(indVec.size() != depData.nrow(), InvalidArgument,
"Length of independent column does not match number of rows of dependent data.");
Copy link
Member

Choose a reason for hiding this comment

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

80 columns?

OPENSIM_THROW_IF(labels.size() != depData.ncol(), InvalidArgument,
"Number of labels does not match number of columns of dependent data.");

setColumnLabels(labels);
_indData = indVec;
_depData = depData;
}

// Implement toString.
std::string toString_impl(std::vector<int> rows = {},
std::vector<int> cols = {},
Expand Down Expand Up @@ -1452,11 +1470,9 @@ class DataTable_ : public AbstractDataTable {

for(const std::string& key : _dependentsMetaData.getKeys()) {
OPENSIM_THROW_IF(numCols !=
_dependentsMetaData.
getValueArrayForKey(key).size(),
IncorrectMetaDataLength, key, numCols,
_dependentsMetaData.
getValueArrayForKey(key).size());
_dependentsMetaData.getValueArrayForKey(key).size(),
IncorrectMetaDataLength, key, numCols,
_dependentsMetaData.getValueArrayForKey(key).size());
}
}

Expand Down
6 changes: 6 additions & 0 deletions OpenSim/Common/Test/testC3DFileAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,14 @@ void compare_tables(const OpenSim::TimeSeriesTableVec3& table1,

void test(const std::string filename) {
using namespace OpenSim;

std::clock_t startTime = std::clock();
auto tables = C3DFileAdapter::read(filename);

std::cout << "\nC3DFileAdapter '" << filename << "' read time = "
<< 1.e3*(std::clock() - startTime) / CLOCKS_PER_SEC
<< "ms\n" << std::endl;

auto& marker_table = tables.at("markers");
auto& force_table = tables.at("forces");

Expand Down
23 changes: 23 additions & 0 deletions OpenSim/Common/TimeSeriesTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,29 @@ class TimeSeriesTable_ : public DataTable_<double, ETY> {
TimeSeriesTable_& operator=(const TimeSeriesTable_&) = default;
TimeSeriesTable_& operator=(TimeSeriesTable_&&) = default;
~TimeSeriesTable_() = default;

/** Convenience Constructor to efficiently populate a time series table
Copy link
Member

Choose a reason for hiding this comment

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

Constructor -> constructor.

from available data. This is primarily useful for constructing with large
data read in from file without having to reallocate and copy memory.*/
TimeSeriesTable_(const std::vector<double>& indVec,
const SimTK::Matrix_<ETY>& depData,
const std::vector<std::string>& labels) :
DataTable_<double, ETY>(indVec, depData, labels) {
try {
// Perform the validation of the data a TimeSeriesTable
validateDependentsMetaData();
for (size_t i = 0; i < indVec.size(); ++i) {
validateRow(i, indVec[i], depData.row(i));
}
}
catch (std::exception& x) {
// wipe out the data loaded if any
_indData.clear();
_depData.clear();
removeDependentsMetaDataForKey("labels");
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to rethrow?

}
}

#ifndef SWIG
using DataTable_<double, ETY>::DataTable_;
using DataTable_<double, ETY>::operator=;
Expand Down