##// END OF EJS Templates
Merge branch 'feature/AmdaProviderUpdate' into develop
Alexandre Leroux -
r398:6ff08786d807 merge
parent child
Show More
@@ -0,0 +1,6
1 #Sampling Time : 60
2 #Time Format : YYYY-MM-DDThh:mm:ss.mls
3 #imf(0) - Type : Local Parameter @ CDPP/AMDA - Name : bx_gse - Units : nT - Size : 1 - Frame : GSE - Mission : ACE - Instrument : MFI - Dataset : mfi_final-prelim
4 2013-09-23T09:00:30.000 NaN
5 2013-09-23T09:01:30.000 -2.71850
6 2013-09-23T09:02:30.000 -2.52150 No newline at end of file
@@ -0,0 +1,2
1 #Sampling Time : 60
2 #Time Format : YYYY-MM-DDThh:mm:ss.mls No newline at end of file
@@ -0,0 +1,6
1 #Sampling Time : 60
2 #Time Format : YYYY-MM-DDThh:mm:ss.mls
3 #imf(0) - Type : Local Parameter @ CDPP/AMDA - Name : bx_gse - Units : nT - Size : 1 - Frame : GSE - Mission : ACE - Instrument : MFI - Dataset : mfi_final-prelim
4 2013-09-23T09:00:30.000 -2.83950 1.05141 3.01547
5 2013-09-23T09:01:30.000 -2.71850
6 2013-09-23T09:02:30.000 -2.52150 No newline at end of file
@@ -0,0 +1,13
1 #Sampling Time : 60
2 #Time Format : YYYY-MM-DDThh:mm:ss.mls
3 #imf(0) - Type : Local Parameter @ CDPP/AMDA - Name : bx_gse - Units : nT - Size : 1 - Frame : GSE - Mission : ACE - Instrument : MFI - Dataset : mfi_final-prelim
4 2013-09-23T09:00:30.000 -2.83950
5 2013-09-23T09:01:30.000 -2.71850
6 2013-09-23T09:02:30.000 -2.52150
7 2013-09-23T09:03:30.000 -2.57633
8 2013-09-23T09:04:30.000 -2.58050
9 2013-09-23T09:05:30.000 -2.48325
10 2013-09-23T09:06:30.000 -2.63025
11 2013-09-23T09:07:30.000 -2.55800
12 2013-09-23T09:08:30.000 -2.43250
13 2013-09-23T09:09:30.000 -2.42200 No newline at end of file
@@ -0,0 +1,6
1 #Sampling Time : 60
2 #Time Format : YYYY-MM-DDThh:mm:ss.mls
3 #imf(0) - Type : Local Parameter @ CDPP/AMDA - Name : bx_gse - Units : nT - Size : 1 - Frame : GSE - Mission : ACE - Instrument : MFI - Dataset : mfi_final-prelim
4 23/09/2013 07:50:30 -2.83950
5 2013-09-23T09:01:30.000 -2.71850
6 2013-09-23T09:02:30.000 -2.52150 No newline at end of file
@@ -0,0 +1,6
1 #Sampling Time : 60
2 #Time Format : YYYY-MM-DDThh:mm:ss.mls
3 #Wrong unit comment
4 2013-09-23T09:00:30.000 -2.83950
5 2013-09-23T09:01:30.000 -2.71850
6 2013-09-23T09:02:30.000 -2.52150 No newline at end of file
@@ -0,0 +1,6
1 #Sampling Time : 60
2 #Time Format : YYYY-MM-DDThh:mm:ss.mls
3 #imf(0) - Type : Local Parameter @ CDPP/AMDA - Name : bx_gse - Units : nT - Size : 1 - Frame : GSE - Mission : ACE - Instrument : MFI - Dataset : mfi_final-prelim
4 2013-09-23T09:00:30.000 abc
5 2013-09-23T09:01:30.000 -2.71850
6 2013-09-23T09:02:30.000 -2.52150 No newline at end of file
@@ -0,0 +1,179
1 #include "AmdaResultParser.h"
2
3 #include <Data/ScalarSeries.h>
4
5 #include <QObject>
6 #include <QtTest>
7
8 namespace {
9
10 /// Path for the tests
11 const auto TESTS_RESOURCES_PATH
12 = QFileInfo{QString{AMDA_TESTS_RESOURCES_DIR}, "TestAmdaResultParser"}.absoluteFilePath();
13
14 QString inputFilePath(const QString &inputFileName)
15 {
16 return QFileInfo{TESTS_RESOURCES_PATH, inputFileName}.absoluteFilePath();
17 }
18
19 struct ExpectedResults {
20 explicit ExpectedResults() = default;
21
22 /// Ctor with QVector<QDateTime> as x-axis data. Datetimes are converted to doubles
23 explicit ExpectedResults(Unit xAxisUnit, Unit valuesUnit, const QVector<QDateTime> &xAxisData,
24 QVector<double> valuesData)
25 : m_ParsingOK{true},
26 m_XAxisUnit{xAxisUnit},
27 m_ValuesUnit{valuesUnit},
28 m_XAxisData{},
29 m_ValuesData{std::move(valuesData)}
30 {
31 // Converts QVector<QDateTime> to QVector<double>
32 std::transform(xAxisData.cbegin(), xAxisData.cend(), std::back_inserter(m_XAxisData),
33 [](const auto &dateTime) { return dateTime.toMSecsSinceEpoch() / 1000.; });
34 }
35
36 /**
37 * Validates a DataSeries compared to the expected results
38 * @param results the DataSeries to validate
39 */
40 void validate(std::shared_ptr<IDataSeries> results)
41 {
42 if (m_ParsingOK) {
43 auto scalarSeries = dynamic_cast<ScalarSeries *>(results.get());
44 QVERIFY(scalarSeries != nullptr);
45
46 // Checks units
47 QVERIFY(scalarSeries->xAxisUnit() == m_XAxisUnit);
48 QVERIFY(scalarSeries->valuesUnit() == m_ValuesUnit);
49
50 // Checks values
51 QVERIFY(scalarSeries->xAxisData()->data() == m_XAxisData);
52 QVERIFY(scalarSeries->valuesData()->data() == m_ValuesData);
53 }
54 else {
55 QVERIFY(results == nullptr);
56 }
57 }
58
59 // Parsing was successfully completed
60 bool m_ParsingOK{false};
61 // Expected x-axis unit
62 Unit m_XAxisUnit{};
63 // Expected values unit
64 Unit m_ValuesUnit{};
65 // Expected x-axis data
66 QVector<double> m_XAxisData{};
67 // Expected values data
68 QVector<double> m_ValuesData{};
69 };
70
71 } // namespace
72
73 Q_DECLARE_METATYPE(ExpectedResults)
74
75 class TestAmdaResultParser : public QObject {
76 Q_OBJECT
77 private slots:
78 /// Input test data
79 /// @sa testTxtJson()
80 void testReadTxt_data();
81
82 /// Tests parsing of a TXT file
83 void testReadTxt();
84 };
85
86 void TestAmdaResultParser::testReadTxt_data()
87 {
88 // ////////////// //
89 // Test structure //
90 // ////////////// //
91
92 // Name of TXT file to read
93 QTest::addColumn<QString>("inputFileName");
94 // Expected results
95 QTest::addColumn<ExpectedResults>("expectedResults");
96
97 // ////////// //
98 // Test cases //
99 // ////////// //
100
101 auto dateTime = [](int year, int month, int day, int hours, int minutes, int seconds) {
102 return QDateTime{{year, month, day}, {hours, minutes, seconds}};
103 };
104
105 // Valid file
106 QTest::newRow("Valid file")
107 << QStringLiteral("ValidScalar1.txt")
108 << ExpectedResults{
109 Unit{QStringLiteral("nT"), true}, Unit{},
110 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
111 dateTime(2013, 9, 23, 9, 2, 30), dateTime(2013, 9, 23, 9, 3, 30),
112 dateTime(2013, 9, 23, 9, 4, 30), dateTime(2013, 9, 23, 9, 5, 30),
113 dateTime(2013, 9, 23, 9, 6, 30), dateTime(2013, 9, 23, 9, 7, 30),
114 dateTime(2013, 9, 23, 9, 8, 30), dateTime(2013, 9, 23, 9, 9, 30)},
115 QVector<double>{-2.83950, -2.71850, -2.52150, -2.57633, -2.58050, -2.48325, -2.63025,
116 -2.55800, -2.43250, -2.42200}};
117
118 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
119 QTest::newRow("No unit file") << QStringLiteral("NoUnit.txt")
120 << ExpectedResults{Unit{QStringLiteral(""), true}, Unit{},
121 QVector<QDateTime>{}, QVector<double>{}};
122 QTest::newRow("Wrong unit file")
123 << QStringLiteral("WrongUnit.txt")
124 << ExpectedResults{Unit{QStringLiteral(""), true}, Unit{},
125 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30),
126 dateTime(2013, 9, 23, 9, 1, 30),
127 dateTime(2013, 9, 23, 9, 2, 30)},
128 QVector<double>{-2.83950, -2.71850, -2.52150}};
129
130 QTest::newRow("Wrong results file (date of first line is invalid")
131 << QStringLiteral("WrongDate.txt")
132 << ExpectedResults{
133 Unit{QStringLiteral("nT"), true}, Unit{},
134 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
135 QVector<double>{-2.71850, -2.52150}};
136
137 QTest::newRow("Wrong results file (too many values for first line")
138 << QStringLiteral("TooManyValues.txt")
139 << ExpectedResults{
140 Unit{QStringLiteral("nT"), true}, Unit{},
141 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
142 QVector<double>{-2.71850, -2.52150}};
143
144 QTest::newRow("Wrong results file (value of first line is invalid")
145 << QStringLiteral("WrongValue.txt")
146 << ExpectedResults{
147 Unit{QStringLiteral("nT"), true}, Unit{},
148 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
149 QVector<double>{-2.71850, -2.52150}};
150
151 QTest::newRow("Wrong results file (value of first line is NaN")
152 << QStringLiteral("NaNValue.txt")
153 << ExpectedResults{
154 Unit{QStringLiteral("nT"), true}, Unit{},
155 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
156 QVector<double>{-2.71850, -2.52150}};
157
158 // Invalid file
159 QTest::newRow("Invalid file (unexisting file)")
160 << QStringLiteral("UnexistingFile.txt") << ExpectedResults{};
161 }
162
163 void TestAmdaResultParser::testReadTxt()
164 {
165 QFETCH(QString, inputFileName);
166 QFETCH(ExpectedResults, expectedResults);
167
168 // Parses file
169 auto filePath = inputFilePath(inputFileName);
170 auto results = AmdaResultParser::readTxt(filePath);
171
172 // ///////////////// //
173 // Validates results //
174 // ///////////////// //
175 expectedResults.validate(results);
176 }
177
178 QTEST_MAIN(TestAmdaResultParser)
179 #include "TestAmdaResultParser.moc"
@@ -27,6 +27,17 public:
27 27 m_Data[0].resize(nbColumns);
28 28 }
29 29
30 /**
31 * Ctor for a unidimensional ArrayData
32 * @param data the data the ArrayData will hold
33 */
34 template <int D = Dim, typename = std::enable_if_t<D == 1> >
35 explicit ArrayData(QVector<double> data) : m_Data{1, QVector<double>{}}
36 {
37 QWriteLocker locker{&m_Lock};
38 m_Data[0] = std::move(data);
39 }
40
30 41 /// Copy ctor
31 42 explicit ArrayData(const ArrayData &other)
32 43 {
@@ -16,6 +16,12 struct Unit {
16 16 {
17 17 }
18 18
19 inline bool operator==(const Unit &other) const
20 {
21 return std::tie(m_Name, m_TimeUnit) == std::tie(other.m_Name, other.m_TimeUnit);
22 }
23 inline bool operator!=(const Unit &other) const { return !(*this == other); }
24
19 25 QString m_Name; ///< Unit name
20 26 bool m_TimeUnit; ///< The unit is a unit of time
21 27 };
@@ -17,6 +17,15 public:
17 17 explicit ScalarSeries(int size, const Unit &xAxisUnit, const Unit &valuesUnit);
18 18
19 19 /**
20 * Ctor with two vectors. The vectors must have the same size, otherwise a ScalarSeries with no
21 * values will be created.
22 * @param xAxisData x-axis data
23 * @param valuesData values data
24 */
25 explicit ScalarSeries(QVector<double> xAxisData, QVector<double> valuesData,
26 const Unit &xAxisUnit, const Unit &valuesUnit);
27
28 /**
20 29 * Sets data for a specific index. The index has to be valid to be effective
21 30 * @param index the index to which the data will be set
22 31 * @param x the x-axis data
@@ -6,6 +6,13 ScalarSeries::ScalarSeries(int size, const Unit &xAxisUnit, const Unit &valuesUn
6 6 {
7 7 }
8 8
9 ScalarSeries::ScalarSeries(QVector<double> xAxisData, QVector<double> valuesData,
10 const Unit &xAxisUnit, const Unit &valuesUnit)
11 : DataSeries{std::make_shared<ArrayData<1> >(std::move(xAxisData)), xAxisUnit,
12 std::make_shared<ArrayData<1> >(std::move(valuesData)), valuesUnit}
13 {
14 }
15
9 16 void ScalarSeries::setData(int index, double x, double value) noexcept
10 17 {
11 18 xAxisData()->setData(index, x);
@@ -4,6 +4,7
4 4
5 5 #include <QDateTime>
6 6 #include <QFile>
7 #include <QRegularExpression>
7 8
8 9 Q_LOGGING_CATEGORY(LOG_AmdaResultParser, "AmdaResultParser")
9 10
@@ -12,59 +13,120 namespace {
12 13 /// Format for dates in result files
13 14 const auto DATE_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz");
14 15
15 /// @todo ALX
16 /// Separator between values in a result line
17 const auto RESULT_LINE_SEPARATOR = QRegularExpression{QStringLiteral("\\s+")};
18
19 /// Regex to find unit in a line. Examples of valid lines:
20 /// ... - Units : nT - ...
21 /// ... -Units:nT- ...
22 /// ... -Units: mΒ²- ...
23 /// ... - Units : m/s - ...
24 const auto UNIT_REGEX = QRegularExpression{QStringLiteral("-\\s*Units\\s*:\\s*(.+?)\\s*-")};
25
26 /// Converts a string date to a double date
27 /// @return a double that represents the date in seconds, NaN if the string date can't be converted
16 28 double doubleDate(const QString &stringDate) noexcept
17 29 {
18 30 auto dateTime = QDateTime::fromString(stringDate, DATE_FORMAT);
19 return dateTime.toMSecsSinceEpoch() / 1000.;
31 return dateTime.isValid() ? (dateTime.toMSecsSinceEpoch() / 1000.)
32 : std::numeric_limits<double>::quiet_NaN();
20 33 }
21 34
22 } // namespace
23
24 std::shared_ptr<IDataSeries> AmdaResultParser::readTxt(const QString &filePath) noexcept
35 /**
36 * Reads stream to retrieve x-axis unit
37 * @param stream the stream to read
38 * @return the unit that has been read in the stream, a default unit (time unit with no label) if an
39 * error occured during reading
40 */
41 Unit readXAxisUnit(QTextStream &stream)
25 42 {
26 QFile file{filePath};
43 QString line{};
27 44
28 if (!file.open(QFile::ReadOnly | QIODevice::Text)) {
29 qCCritical(LOG_AmdaResultParser())
30 << QObject::tr("Can't retrieve AMDA data from file %1: %2")
31 .arg(filePath, file.errorString());
32 return nullptr;
45 if (stream.readLineInto(&line)) {
46 auto match = UNIT_REGEX.match(line);
47 if (match.hasMatch()) {
48 return Unit{match.captured(1), true};
49 }
50 else {
51 qCWarning(LOG_AmdaResultParser())
52 << QObject::tr("Can't read unit: invalid line %1").arg(line);
53 }
54 }
55 else {
56 qCWarning(LOG_AmdaResultParser()) << QObject::tr("Can't read unit: end of file");
57 }
58
59 // Error cases
60 return Unit{{}, true};
33 61 }
34 62
63 /**
64 * Reads stream to retrieve results
65 * @param stream the stream to read
66 * @return the pair of vectors x-axis data/values data that has been read in the stream
67 */
68 QPair<QVector<double>, QVector<double> > readResults(QTextStream &stream)
69 {
35 70 auto xData = QVector<double>{};
36 71 auto valuesData = QVector<double>{};
37 72
38 QTextStream stream{&file};
39
40 // Ignore comment lines (3 lines)
41 stream.readLine();
42 stream.readLine();
43 stream.readLine();
44
45 73 QString line{};
46 auto lineRegex = QRegExp{QStringLiteral("\\s+")};
47 74 while (stream.readLineInto(&line)) {
48 auto lineData = line.split(lineRegex, QString::SkipEmptyParts);
75 auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts);
49 76 if (lineData.size() == 2) {
50 77 // X : the data is converted from date to double (in secs)
51 xData.push_back(doubleDate(lineData.at(0)));
78 auto x = doubleDate(lineData.at(0));
52 79
53 80 // Value
54 valuesData.push_back(lineData.at(1).toDouble());
81 bool valueOk;
82 auto value = lineData.at(1).toDouble(&valueOk);
83
84 // Adds result only if x and value are valid
85 if (!std::isnan(x) && !std::isnan(value) && valueOk) {
86 xData.push_back(x);
87 valuesData.push_back(value);
88 }
89 else {
90 qCWarning(LOG_AmdaResultParser())
91 << QObject::tr(
92 "Can't retrieve results from line %1: x and/or value are invalid")
93 .arg(line);
94 }
55 95 }
56 96 else {
57 /// @todo ALX : log
97 qCWarning(LOG_AmdaResultParser())
98 << QObject::tr("Can't retrieve results from line %1: invalid line").arg(line);
99 }
58 100 }
101
102 return qMakePair(std::move(xData), std::move(valuesData));
59 103 }
60 104
61 /// @todo ALX : handle units
62 auto scalarSeries = std::make_shared<ScalarSeries>(xData.size(), Unit{"nT", true}, Unit{});
105 } // namespace
106
107 std::shared_ptr<IDataSeries> AmdaResultParser::readTxt(const QString &filePath) noexcept
108 {
109 QFile file{filePath};
63 110
64 const auto count = xData.size();
65 for (auto i = 0; i < count; ++i) {
66 scalarSeries->setData(i, xData.at(i), valuesData.at(i));
111 if (!file.open(QFile::ReadOnly | QIODevice::Text)) {
112 qCCritical(LOG_AmdaResultParser())
113 << QObject::tr("Can't retrieve AMDA data from file %1: %2")
114 .arg(filePath, file.errorString());
115 return nullptr;
67 116 }
68 117
69 return scalarSeries;
118 QTextStream stream{&file};
119
120 // Ignore first two lines (comments lines)
121 stream.readLine();
122 stream.readLine();
123
124 // Reads x-axis unit
125 auto xAxisUnit = readXAxisUnit(stream);
126
127 // Reads results
128 auto results = readResults(stream);
129
130 return std::make_shared<ScalarSeries>(std::move(results.first), std::move(results.second),
131 xAxisUnit, Unit{});
70 132 }
General Comments 0
You need to be logged in to leave comments. Login now