##// END OF EJS Templates
Spectrograms implementation (5)...
Alexandre Leroux -
r994:8ed1be475894
parent child
Show More
@@ -1,101 +1,105
1 1 #ifndef SCIQLOP_AMDARESULTPARSERHELPER_H
2 2 #define SCIQLOP_AMDARESULTPARSERHELPER_H
3 3
4 4 #include "AmdaResultParserDefs.h"
5 5
6 6 #include <QtCore/QLoggingCategory>
7 7 #include <QtCore/QString>
8 8
9 9 #include <memory>
10 10
11 11 class IDataSeries;
12 12
13 13 Q_DECLARE_LOGGING_CATEGORY(LOG_AmdaResultParserHelper)
14 14
15 15 /**
16 16 * Helper used to interpret the data of an AMDA result file and generate the corresponding data
17 17 * series.
18 18 *
19 19 * It proposes methods allowing to read line by line an AMDA file and to extract the properties
20 20 * (from the header) and the values corresponding to the data series
21 21 *
22 22 * @sa DataSeries
23 23 */
24 24 struct IAmdaResultParserHelper {
25 25 virtual ~IAmdaResultParserHelper() noexcept = default;
26 26
27 27 /// Verifies that the extracted properties are well formed and possibly applies other treatments
28 28 /// on them
29 29 /// @return true if the properties are well formed, false otherwise
30 30 virtual bool checkProperties() = 0;
31 31
32 32 /// Creates the data series from the properties and values extracted from the AMDA file.
33 33 /// @warning as the data are moved in the data series, the helper shouldn't be used after
34 34 /// calling this method
35 35 /// @return the data series created
36 36 virtual std::shared_ptr<IDataSeries> createSeries() = 0;
37 37
38 38 /// Reads a line from the AMDA file to extract a property that will be used to generate the data
39 39 /// series
40 40 /// @param line tahe line to interpret
41 41 virtual void readPropertyLine(const QString &line) = 0;
42 42
43 43 /// Reads a line from the AMDA file to extract a value that will be set in the data series
44 44 /// @param line the line to interpret
45 45 virtual void readResultLine(const QString &line) = 0;
46 46 };
47 47
48 48 /**
49 49 * Implementation of @sa IAmdaResultParserHelper for scalars
50 50 */
51 51 class ScalarParserHelper : public IAmdaResultParserHelper {
52 52 public:
53 53 bool checkProperties() override;
54 54 std::shared_ptr<IDataSeries> createSeries() override;
55 55 void readPropertyLine(const QString &line) override;
56 56 void readResultLine(const QString &line) override;
57 57
58 58 private:
59 59 /// @return the reading order of the "value" columns for a result line of the AMDA file
60 60 std::vector<int> valuesIndexes() const;
61 61
62 62 Properties m_Properties{};
63 63 std::vector<double> m_XAxisData{};
64 64 std::vector<double> m_ValuesData{};
65 65 };
66 66
67 67 /**
68 68 * Implementation of @sa IAmdaResultParserHelper for spectrograms
69 69 */
70 70 class SpectrogramParserHelper : public IAmdaResultParserHelper {
71 71 public:
72 72 bool checkProperties() override;
73 73 std::shared_ptr<IDataSeries> createSeries() override;
74 74 void readPropertyLine(const QString &line) override;
75 75 void readResultLine(const QString &line) override;
76 76
77 77 private:
78 78 Properties m_Properties{};
79 std::vector<double> m_XAxisData{};
79 80 std::vector<double> m_YAxisData{};
81 std::vector<double> m_ValuesData{};
82 std::vector<int> m_ValuesIndexes{};
83 double m_FillValue{std::numeric_limits<double>::quiet_NaN()};
80 84 };
81 85
82 86 /**
83 87 * Implementation of @sa IAmdaResultParserHelper for vectors
84 88 */
85 89 class VectorParserHelper : public IAmdaResultParserHelper {
86 90 public:
87 91 bool checkProperties() override;
88 92 std::shared_ptr<IDataSeries> createSeries() override;
89 93 void readPropertyLine(const QString &line) override;
90 94 void readResultLine(const QString &line) override;
91 95
92 96 private:
93 97 /// @return the reading order of the "value" columns for a result line of the AMDA file
94 98 std::vector<int> valuesIndexes() const;
95 99
96 100 Properties m_Properties{};
97 101 std::vector<double> m_XAxisData{};
98 102 std::vector<double> m_ValuesData{};
99 103 };
100 104
101 105 #endif // SCIQLOP_AMDARESULTPARSERHELPER_H
@@ -1,367 +1,385
1 1 #include "AmdaResultParserHelper.h"
2 2
3 3 #include <Common/DateUtils.h>
4 #include <Common/SortUtils.h>
4 5
5 6 #include <Data/ScalarSeries.h>
7 #include <Data/SpectrogramSeries.h>
6 8 #include <Data/Unit.h>
7 9 #include <Data/VectorSeries.h>
8 10
9 11 #include <QtCore/QDateTime>
10 12 #include <QtCore/QRegularExpression>
11 13
12 14 Q_LOGGING_CATEGORY(LOG_AmdaResultParserHelper, "AmdaResultParserHelper")
13 15
14 16 namespace {
15 17
16 18 // ///////// //
17 19 // Constants //
18 20 // ///////// //
19 21
20 22 /// Separator between values in a result line
21 23 const auto RESULT_LINE_SEPARATOR = QRegularExpression{QStringLiteral("\\s+")};
22 24
23 25 /// Format for dates in result files
24 26 const auto DATE_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz");
25 27
26 28 // /////// //
27 29 // Methods //
28 30 // /////// //
29 31
30 32 /**
31 33 * Checks that the properties contain a specific unit and that this unit is valid
32 34 * @param properties the properties map in which to search unit
33 35 * @param key the key to search for the unit in the properties
34 36 * @param errorMessage the error message to log in case the unit is invalid
35 37 * @return true if the unit is valid, false it it's invalid or was not found in the properties
36 38 */
37 39 bool checkUnit(const Properties &properties, const QString &key, const QString &errorMessage)
38 40 {
39 41 auto unit = properties.value(key).value<Unit>();
40 42 if (unit.m_Name.isEmpty()) {
41 43 qCWarning(LOG_AmdaResultParserHelper()) << errorMessage;
42 44 return false;
43 45 }
44 46
45 47 return true;
46 48 }
47 49
48 50 QDateTime dateTimeFromString(const QString &stringDate) noexcept
49 51 {
50 52 #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
51 53 return QDateTime::fromString(stringDate, Qt::ISODateWithMs);
52 54 #else
53 55 return QDateTime::fromString(stringDate, DATE_FORMAT);
54 56 #endif
55 57 }
56 58
57 59 /// Converts a string date to a double date
58 60 /// @return a double that represents the date in seconds, NaN if the string date can't be converted
59 61 double doubleDate(const QString &stringDate) noexcept
60 62 {
61 63 // Format: yyyy-MM-ddThh:mm:ss.zzz
62 64 auto dateTime = dateTimeFromString(stringDate);
63 65 dateTime.setTimeSpec(Qt::UTC);
64 66 return dateTime.isValid() ? DateUtils::secondsSinceEpoch(dateTime)
65 67 : std::numeric_limits<double>::quiet_NaN();
66 68 }
67 69
68 70 /**
69 71 * Reads a line from the AMDA file and tries to extract a x-axis data and value data from it
70 72 * @param xAxisData the vector in which to store the x-axis data extracted
71 73 * @param valuesData the vector in which to store the value extracted
72 74 * @param line the line to read to extract the property
73 75 * @param valuesIndexes indexes of insertion of read values. For example, if the line contains three
74 76 * columns of values, and valuesIndexes are {2, 0, 1}, the value of the third column will be read
75 77 * and inserted first, then the value of the first column, and finally the value of the second
76 78 * column.
77 79 * @param fillValue value that tags an invalid data. For example, if fillValue is -1 and a read
78 80 * value is -1, then this value is considered as invalid and converted to NaN
79 81 */
80 82 void tryReadResult(std::vector<double> &xAxisData, std::vector<double> &valuesData,
81 83 const QString &line, const std::vector<int> &valuesIndexes,
82 84 double fillValue = std::numeric_limits<double>::quiet_NaN())
83 85 {
84 86 auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts);
85 87
86 88 // Checks that the line contains expected number of values + x-axis value
87 89 if (lineData.size() == valuesIndexes.size() + 1) {
88 90 // X : the data is converted from date to double (in secs)
89 91 auto x = doubleDate(lineData.at(0));
90 92
91 93 // Adds result only if x is valid. Then, if value is invalid, it is set to NaN
92 94 if (!std::isnan(x)) {
93 95 xAxisData.push_back(x);
94 96
95 97 // Values
96 98 for (auto valueIndex : valuesIndexes) {
97 99 bool valueOk;
98 100 // we use valueIndex + 1 to skip column 0 (x-axis value)
99 101 auto value = lineData.at(valueIndex + 1).toDouble(&valueOk);
100 102
101 103 if (!valueOk) {
102 104 qCWarning(LOG_AmdaResultParserHelper())
103 105 << QObject::tr(
104 106 "Value from (line %1, column %2) is invalid and will be "
105 107 "converted to NaN")
106 108 .arg(line, valueIndex);
107 109 value = std::numeric_limits<double>::quiet_NaN();
108 110 }
109 111
110 112 // Handles fill value
111 113 if (!std::isnan(fillValue) && !std::isnan(value) && fillValue == value) {
112 114 value = std::numeric_limits<double>::quiet_NaN();
113 115 }
114 116
115 117 valuesData.push_back(value);
116 118 }
117 119 }
118 120 else {
119 121 qCWarning(LOG_AmdaResultParserHelper())
120 122 << QObject::tr("Can't retrieve results from line %1: x is invalid").arg(line);
121 123 }
122 124 }
123 125 else {
124 126 qCWarning(LOG_AmdaResultParserHelper())
125 127 << QObject::tr("Can't retrieve results from line %1: invalid line").arg(line);
126 128 }
127 129 }
128 130
129 131 /**
130 132 * Reads a line from the AMDA file and tries to extract a property from it
131 133 * @param properties the properties map in which to put the property extracted from the line
132 134 * @param key the key to which the property is added in the properties map
133 135 * @param line the line to read to extract the property
134 136 * @param regex the expected regex to extract the property. If the line matches this regex, the
135 137 * property is generated
136 138 * @param fun the function used to generate the property
137 139 * @return true if the property could be generated, false if the line does not match the regex, or
138 140 * if a property has already been generated for the key
139 141 */
140 142 template <typename GeneratePropertyFun>
141 143 bool tryReadProperty(Properties &properties, const QString &key, const QString &line,
142 144 const QRegularExpression &regex, GeneratePropertyFun fun)
143 145 {
144 146 if (properties.contains(key)) {
145 147 return false;
146 148 }
147 149
148 150 auto match = regex.match(line);
149 151 if (match.hasMatch()) {
150 152 properties.insert(key, fun(match));
151 153 }
152 154
153 155 return match.hasMatch();
154 156 }
155 157
156 158 /**
157 159 * Reads a line from the AMDA file and tries to extract a double from it
158 160 * @sa tryReadProperty()
159 161 */
160 162 bool tryReadDouble(Properties &properties, const QString &key, const QString &line,
161 163 const QRegularExpression &regex)
162 164 {
163 165 return tryReadProperty(properties, key, line, regex, [](const auto &match) {
164 166 bool ok;
165 167
166 168 // If the value can't be converted to double, it is set to NaN
167 169 auto doubleValue = match.captured(1).toDouble(&ok);
168 170 if (!ok) {
169 171 doubleValue = std::numeric_limits<double>::quiet_NaN();
170 172 }
171 173
172 174 return QVariant::fromValue(doubleValue);
173 175 });
174 176 }
175 177
176 178 /**
177 179 * Reads a line from the AMDA file and tries to extract a vector of doubles from it
178 180 * @param sep the separator of double values in the line
179 181 * @sa tryReadProperty()
180 182 */
181 183 bool tryReadDoubles(Properties &properties, const QString &key, const QString &line,
182 184 const QRegularExpression &regex, const QString &sep = QStringLiteral(","))
183 185 {
184 186 return tryReadProperty(properties, key, line, regex, [sep](const auto &match) {
185 187 std::vector<double> doubleValues{};
186 188
187 189 // If the value can't be converted to double, it is set to NaN
188 190 auto values = match.captured(1).split(sep);
189 191 for (auto value : values) {
190 192 bool ok;
191 193
192 194 auto doubleValue = value.toDouble(&ok);
193 195 if (!ok) {
194 196 doubleValue = std::numeric_limits<double>::quiet_NaN();
195 197 }
196 198
197 199 doubleValues.push_back(doubleValue);
198 200 }
199 201
200 202 return QVariant::fromValue(doubleValues);
201 203 });
202 204 }
203 205
204 206 /**
205 207 * Reads a line from the AMDA file and tries to extract a unit from it
206 208 * @sa tryReadProperty()
207 209 */
208 210 bool tryReadUnit(Properties &properties, const QString &key, const QString &line,
209 211 const QRegularExpression &regex, bool timeUnit = false)
210 212 {
211 213 return tryReadProperty(properties, key, line, regex, [timeUnit](const auto &match) {
212 214 return QVariant::fromValue(Unit{match.captured(1), timeUnit});
213 215 });
214 216 }
215 217
216 218 } // namespace
217 219
218 220 // ////////////////// //
219 221 // ScalarParserHelper //
220 222 // ////////////////// //
221 223
222 224 bool ScalarParserHelper::checkProperties()
223 225 {
224 226 return checkUnit(m_Properties, X_AXIS_UNIT_PROPERTY,
225 227 QObject::tr("The x-axis unit could not be found in the file"));
226 228 }
227 229
228 230 std::shared_ptr<IDataSeries> ScalarParserHelper::createSeries()
229 231 {
230 232 return std::make_shared<ScalarSeries>(std::move(m_XAxisData), std::move(m_ValuesData),
231 233 m_Properties.value(X_AXIS_UNIT_PROPERTY).value<Unit>(),
232 234 m_Properties.value(VALUES_UNIT_PROPERTY).value<Unit>());
233 235 }
234 236
235 237 void ScalarParserHelper::readPropertyLine(const QString &line)
236 238 {
237 239 tryReadUnit(m_Properties, X_AXIS_UNIT_PROPERTY, line, DEFAULT_X_AXIS_UNIT_REGEX, true);
238 240 }
239 241
240 242 void ScalarParserHelper::readResultLine(const QString &line)
241 243 {
242 244 tryReadResult(m_XAxisData, m_ValuesData, line, valuesIndexes());
243 245 }
244 246
245 247 std::vector<int> ScalarParserHelper::valuesIndexes() const
246 248 {
247 249 // Only one value to read
248 250 static auto result = std::vector<int>{0};
249 251 return result;
250 252 }
251 253
252 254 // /////////////////////// //
253 255 // SpectrogramParserHelper //
254 256 // /////////////////////// //
255 257
256 258 bool SpectrogramParserHelper::checkProperties()
257 259 {
258 260 // Generates y-axis data from bands extracted (take the middle of the intervals)
259 261 auto minBands = m_Properties.value(MIN_BANDS_PROPERTY).value<std::vector<double> >();
260 262 auto maxBands = m_Properties.value(MAX_BANDS_PROPERTY).value<std::vector<double> >();
261 263
262 264 if (minBands.size() != maxBands.size()) {
263 265 qCWarning(LOG_AmdaResultParserHelper()) << QObject::tr(
264 266 "Can't generate y-axis data from bands extracted: bands intervals are invalid");
265 267 return false;
266 268 }
267 269
268 270 std::transform(
269 271 minBands.begin(), minBands.end(), maxBands.begin(), std::back_inserter(m_YAxisData),
270 272 [](const auto &minValue, const auto &maxValue) { return (minValue + maxValue) / 2.; });
271 273
274 // Generates values indexes, i.e. the order in which each value will be retrieved (in ascending
275 // order of the associated bands)
276 m_ValuesIndexes = SortUtils::sortPermutation(m_YAxisData, std::less<double>());
277
278 // Sorts y-axis data accoding to the ascending order
279 m_YAxisData = SortUtils::sort(m_YAxisData, 1, m_ValuesIndexes);
280
281 // Sets fill value
282 m_FillValue = m_Properties.value(FILL_VALUE_PROPERTY).value<double>();
283
284 /// @todo: handle min/max samplings?
285
272 286 return true;
273 287 }
274 288
275 289 std::shared_ptr<IDataSeries> SpectrogramParserHelper::createSeries()
276 290 {
277 /// @todo ALX
291 return std::make_shared<SpectrogramSeries>(
292 std::move(m_XAxisData), std::move(m_YAxisData), std::move(m_ValuesData),
293 Unit{"t", true}, // x-axis unit is always a time unit
294 m_Properties.value(Y_AXIS_UNIT_PROPERTY).value<Unit>(),
295 m_Properties.value(VALUES_UNIT_PROPERTY).value<Unit>());
278 296 }
279 297
280 298 void SpectrogramParserHelper::readPropertyLine(const QString &line)
281 299 {
282 300 // Set of functions to test on the line to generate a property. If a function is valid (i.e. a
283 301 // property has been generated for the line), the line is treated as processed and the other
284 302 // functions are not called
285 303 std::vector<std::function<bool()> > functions{
286 304 // values unit
287 305 [&] {
288 306 return tryReadUnit(m_Properties, VALUES_UNIT_PROPERTY, line,
289 307 SPECTROGRAM_VALUES_UNIT_REGEX);
290 308 },
291 309 // y-axis unit
292 310 [&] {
293 311 return tryReadUnit(m_Properties, Y_AXIS_UNIT_PROPERTY, line,
294 312 SPECTROGRAM_Y_AXIS_UNIT_REGEX);
295 313 },
296 314 // min sampling
297 315 [&] {
298 316 return tryReadDouble(m_Properties, MIN_SAMPLING_PROPERTY, line,
299 317 SPECTROGRAM_MIN_SAMPLING_REGEX);
300 318 },
301 319 // max sampling
302 320 [&] {
303 321 return tryReadDouble(m_Properties, MAX_SAMPLING_PROPERTY, line,
304 322 SPECTROGRAM_MAX_SAMPLING_REGEX);
305 323 },
306 324 // fill value
307 325 [&] {
308 326 return tryReadDouble(m_Properties, FILL_VALUE_PROPERTY, line,
309 327 SPECTROGRAM_FILL_VALUE_REGEX);
310 328 },
311 329 // min bounds of each band
312 330 [&] {
313 331 return tryReadDoubles(m_Properties, MIN_BANDS_PROPERTY, line,
314 332 SPECTROGRAM_MIN_BANDS_REGEX);
315 333 },
316 334 // max bounds of each band
317 335 [&] {
318 336 return tryReadDoubles(m_Properties, MAX_BANDS_PROPERTY, line,
319 337 SPECTROGRAM_MAX_BANDS_REGEX);
320 338 }};
321 339
322 340 for (auto function : functions) {
323 341 // Stops at the first function that is valid
324 342 if (function()) {
325 343 return;
326 344 }
327 345 }
328 346 }
329 347
330 348 void SpectrogramParserHelper::readResultLine(const QString &line)
331 349 {
332 /// @todo ALX
350 tryReadResult(m_XAxisData, m_ValuesData, line, m_ValuesIndexes, m_FillValue);
333 351 }
334 352
335 353 // ////////////////// //
336 354 // VectorParserHelper //
337 355 // ////////////////// //
338 356
339 357 bool VectorParserHelper::checkProperties()
340 358 {
341 359 return checkUnit(m_Properties, X_AXIS_UNIT_PROPERTY,
342 360 QObject::tr("The x-axis unit could not be found in the file"));
343 361 }
344 362
345 363 std::shared_ptr<IDataSeries> VectorParserHelper::createSeries()
346 364 {
347 365 return std::make_shared<VectorSeries>(std::move(m_XAxisData), std::move(m_ValuesData),
348 366 m_Properties.value(X_AXIS_UNIT_PROPERTY).value<Unit>(),
349 367 m_Properties.value(VALUES_UNIT_PROPERTY).value<Unit>());
350 368 }
351 369
352 370 void VectorParserHelper::readPropertyLine(const QString &line)
353 371 {
354 372 tryReadUnit(m_Properties, X_AXIS_UNIT_PROPERTY, line, DEFAULT_X_AXIS_UNIT_REGEX, true);
355 373 }
356 374
357 375 void VectorParserHelper::readResultLine(const QString &line)
358 376 {
359 377 tryReadResult(m_XAxisData, m_ValuesData, line, valuesIndexes());
360 378 }
361 379
362 380 std::vector<int> VectorParserHelper::valuesIndexes() const
363 381 {
364 382 // 3 values to read, in order in the file (x, y, z)
365 383 static auto result = std::vector<int>{0, 1, 2};
366 384 return result;
367 385 }
General Comments 0
You need to be logged in to leave comments. Login now