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