Skip to content

Commit 1d608f8

Browse files
committed
XsensDataReader Improvements and Tests
1 parent 6c3da40 commit 1d608f8

17 files changed

+5041
-316
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ This is not a comprehensive list of changes but rather a hand-curated collection
88

99
v4.6
1010
=====
11+
- Improvements for the `XsensDataReader`. Add a configuration option to XSensDataReaderSettings to specify a known data rate (sampling frequency).
12+
Automatically detect the delimiter used in the file. Support the new Xsens export rotations formats (Rotation Matrix, Quaternion, or Euler angles) values from Xsens files. Update the parser to handle the path separator for data_folder and fix a memory leak. Verify integrity and uniformity of all files. Add tests with additional new and old Xsens formats. (#4063)
13+
14+
1115

1216

1317
v4.5.2

OpenSim/Common/CommonUtilities.cpp

+27
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <iomanip>
3232
#include <memory>
3333
#include <sstream>
34+
#include <unordered_map>
3435

3536
#include <SimTKcommon/internal/Pathname.h>
3637

@@ -69,6 +70,32 @@ std::string OpenSim::getFormattedDateTime(
6970
return ss.str();
7071
}
7172

73+
std::string OpenSim::detectDelimiter(
74+
const std::string& input, const std::vector<std::string>& delimiters) {
75+
76+
std::unordered_map<std::string, std::size_t> counts;
77+
78+
// Count occurrences of common delimiters in the input string
79+
std::transform(delimiters.begin(), delimiters.end(),
80+
std::inserter(counts, counts.end()), [&input](const std::string& d) {
81+
std::size_t count = 0;
82+
std::size_t pos = 0;
83+
// Find all occurrences of delimiter in input string
84+
while ((pos = input.find(d, pos)) != std::string::npos) {
85+
++count;
86+
pos += d.length(); // Move past the current delimiter
87+
}
88+
return std::pair<std::string, std::size_t>(d, count);
89+
});
90+
91+
// Find the delimiter with the highest frequency
92+
auto maxElem = std::max_element(counts.begin(), counts.end(),
93+
[](const auto& a, const auto& b) { return a.second < b.second; });
94+
95+
// If a delimiter is found, return it, otherwise return an empty string
96+
return (maxElem != counts.end() && maxElem->second > 0) ? maxElem->first : "";
97+
}
98+
7299
SimTK::Vector OpenSim::createVectorLinspace(
73100
int length, double start, double end) {
74101
SimTK::Vector v(length);

OpenSim/Common/CommonUtilities.h

+21-1
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,15 @@
2727
#include "Assertion.h"
2828
#include <algorithm>
2929
#include <cmath>
30+
#include <condition_variable>
3031
#include <functional>
3132
#include <iostream>
3233
#include <memory>
3334
#include <mutex>
3435
#include <numeric>
3536
#include <stack>
36-
#include <condition_variable>
3737
#include <utility>
38+
#include <vector>
3839

3940
#include <SimTKcommon/internal/BigMatrix.h>
4041

@@ -121,6 +122,25 @@ OSIMCOMMON_API
121122
SimTK::Vector createVector(std::initializer_list<SimTK::Real> elements);
122123
#endif
123124

125+
/**
126+
* @brief Detects the most likely string delimiter in an input string.
127+
*
128+
* This function identifies the most frequent delimiter from a predefined list
129+
* of common delimiters based on occurrences in the input string.
130+
*
131+
* @param input The string to analyze.
132+
* @param delimiters A vector of candidate delimiters.
133+
*
134+
* @return: The most likely delimiter (as an std::string),
135+
* or an empty string if none was found.
136+
*
137+
* @note Supports single-character delimiters.
138+
*/
139+
/// @ingroup commonutil
140+
OSIMCOMMON_API
141+
std::string detectDelimiter(
142+
const std::string& input, const std::vector<std::string>& delimiters);
143+
124144
/**
125145
* @brief Checks if the elements of a vector are uniformly spaced.
126146
*

OpenSim/Common/FileAdapter.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,12 @@ class TableMissingHeader : public Exception {
166166
public:
167167
TableMissingHeader(const std::string& file,
168168
size_t line,
169-
const std::string& func) :
169+
const std::string& func,
170+
const std::string& message = "") :
170171
Exception(file, line, func) {
171172
std::string msg = "Table does not have metadata for 'header'.";
173+
if(!message.empty())
174+
msg += " " + message;
172175

173176
addMessage(msg);
174177
}

OpenSim/Common/Test/testCommonUtilities.cpp

+46
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,49 @@ TEST_CASE("isUniform tests with createVectorLinspace(SimTK::Vector) and createVe
261261
REQUIRE_THAT(result_simtk.second, Catch::Matchers::WithinAbs(rate, tol));
262262
}
263263
}
264+
265+
TEST_CASE("detectDelimiter") {
266+
const std::vector<std::string> delimiters = {",", ";", "|", "\t", ":", " "};
267+
SECTION("Comma") {
268+
std::string input = "a,b,c,d";
269+
auto delim = detectDelimiter(input, delimiters);
270+
REQUIRE(delim == ",");
271+
}
272+
273+
SECTION("Pipe") {
274+
std::string input = "a|b|c|d";
275+
auto delim = detectDelimiter(input, delimiters);
276+
REQUIRE(delim == "|");
277+
}
278+
279+
SECTION("Tab") {
280+
std::string input = "a\tb\tc\td";
281+
auto delim = detectDelimiter(input, delimiters);
282+
REQUIRE(delim == "\t");
283+
}
284+
285+
SECTION("Semicolon") {
286+
std::string input = "a;b;c;d";
287+
auto delim = detectDelimiter(input, delimiters);
288+
REQUIRE(delim == ";");
289+
}
290+
291+
SECTION("Space") {
292+
std::string input = "a b c d";
293+
auto delim = detectDelimiter(input, delimiters);
294+
REQUIRE(delim == " ");
295+
}
296+
297+
SECTION("No Valid Delimiter") {
298+
std::string input = "abcd";
299+
auto delim = detectDelimiter(input, delimiters);
300+
REQUIRE(delim == "");
301+
}
302+
303+
SECTION("Delimiter Exclusion") {
304+
std::vector<std::string> small_delimiters = {",", ";"};
305+
std::string input = "a|b|c|d";
306+
auto delim = detectDelimiter(input, small_delimiters);
307+
REQUIRE(delim == "");
308+
}
309+
}

0 commit comments

Comments
 (0)