##// END OF EJS Templates
Fixes compilation
Alexandre Leroux -
r1040:bcdcc2f6c21f
parent child
Show More
@@ -1,227 +1,228
1 1 #ifndef SCIQLOP_DATASERIESUTILS_H
2 2 #define SCIQLOP_DATASERIESUTILS_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <Data/DataSeriesIterator.h>
7 7
8 #include <cmath>
8 9 #include <QLoggingCategory>
9 10
10 11 Q_DECLARE_LOGGING_CATEGORY(LOG_DataSeriesUtils)
11 12
12 13 /**
13 14 * Utility class with methods for data series
14 15 */
15 16 struct SCIQLOP_CORE_EXPORT DataSeriesUtils {
16 17 /**
17 18 * Define a meshs.
18 19 *
19 20 * A mesh is a regular grid representing cells of the same width (in x) and of the same height
20 21 * (in y). At each mesh point is associated a value.
21 22 *
22 23 * Each axis of the mesh is defined by a minimum value, a number of values is a mesh step.
23 24 * For example: if min = 1, nbValues = 5 and step = 2 => the axis of the mesh will be [1, 3, 5,
24 25 * 7, 9].
25 26 *
26 27 * The values are defined in an array of size {nbX * nbY}. The data is stored along the X axis.
27 28 *
28 29 * For example, the mesh:
29 30 * Y = 2 [ 7 ; 8 ; 9
30 31 * Y = 1 4 ; 5 ; 6
31 32 * Y = 0 1 ; 2 ; 3 ]
32 33 * X = 0 X = 1 X = 2
33 34 *
34 35 * will be represented by data [1, 2, 3, 4, 5, 6, 7, 8, 9]
35 36 */
36 37 struct Mesh {
37 38 explicit Mesh() = default;
38 39 explicit Mesh(int nbX, double xMin, double xStep, int nbY, double yMin, double yStep)
39 40 : m_NbX{nbX},
40 41 m_XMin{xMin},
41 42 m_XStep{xStep},
42 43 m_NbY{nbY},
43 44 m_YMin{yMin},
44 45 m_YStep{yStep},
45 46 m_Data(nbX * nbY)
46 47 {
47 48 }
48 49
49 50 inline bool isEmpty() const { return m_Data.size() == 0; }
50 51 inline double xMax() const { return m_XMin + (m_NbX - 1) * m_XStep; }
51 52 inline double yMax() const { return m_YMin + (m_NbY - 1) * m_YStep; }
52 53
53 54 int m_NbX{0};
54 55 double m_XMin{};
55 56 double m_XStep{};
56 57 int m_NbY{0};
57 58 double m_YMin{};
58 59 double m_YStep{};
59 60 std::vector<double> m_Data{};
60 61 };
61 62
62 63 /**
63 64 * Represents a resolution used to generate the data of a mesh on the x-axis or in Y.
64 65 *
65 66 * A resolution is represented by a value and flag indicating if it's in the logarithmic scale
66 67 * @sa Mesh
67 68 */
68 69 struct Resolution {
69 70 double m_Val{std::numeric_limits<double>::quiet_NaN()};
70 71 bool m_Logarithmic{false};
71 72 };
72 73
73 74 /**
74 75 * Processes data from a data series to complete the data holes with a fill value.
75 76 *
76 77 * A data hole is determined by the resolution passed in parameter: if, between two continuous
77 78 * data on the x-axis, the difference between these data is greater than the resolution, then
78 79 * there is one or more holes between them. The holes are filled by adding:
79 80 * - for the x-axis, new data corresponding to the 'step resolution' starting from the first
80 81 * data;
81 82 * - for values, a default value (fill value) for each new data added on the x-axis.
82 83 *
83 84 * For example, with :
84 85 * - xAxisData = [0, 1, 5, 7, 14 ]
85 86 * - valuesData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (two components per x-axis data)
86 87 * - fillValue = NaN
87 88 * - and resolution = 2;
88 89 *
89 90 * For the x axis, we calculate as data holes: [3, 9, 11, 13]. These holes are added to the
90 91 * x-axis data, and NaNs (two per x-axis data) are added to the values:
91 92 * => xAxisData = [0, 1, 3, 5, 7, 9, 11, 13, 14 ]
92 93 * => valuesData = [0, 1, 2, 3, NaN, NaN, 4, 5, 6, 7, NaN, NaN, NaN, NaN, NaN, NaN, 8, 9]
93 94 *
94 95 * It is also possible to set bounds for the data series. If these bounds are defined and exceed
95 96 * the limits of the data series, data holes are added to the series at the beginning and/or the
96 97 * end.
97 98 *
98 99 * The generation of data holes at the beginning/end of the data series is performed starting
99 100 * from the x-axis series limit and adding data holes at each 'resolution step' as long as the
100 101 * new bound is not reached.
101 102 *
102 103 * For example, with :
103 104 * - xAxisData = [3, 4, 5, 6, 7 ]
104 105 * - valuesData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
105 106 * - fillValue = NaN
106 107 * - minBound = 0
107 108 * - maxBound = 12
108 109 * - and resolution = 2;
109 110 *
110 111 * => Starting from 3 and decreasing 2 by 2 until reaching 0 : a data hole at value 1 will be
111 112 * added to the beginning of the series
112 113 * => Starting from 7 and increasing 2 by 2 until reaching 12 : data holes at values 9 and 11
113 114 * will be added to the end of the series
114 115 *
115 116 * So :
116 117 * => xAxisData = [1, 3, 4, 5, 6, 7, 9, 11 ]
117 118 * => valuesData = [NaN, NaN, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, NaN, NaN, NaN, NaN]
118 119 *
119 120 * @param xAxisData the x-axis data of the data series
120 121 * @param valuesData the values data of the data series
121 122 * @param resolution the resoultion (on x-axis) used to determinate data holes
122 123 * @param fillValue the fill value used for data holes in the values data
123 124 * @param minBound the limit at which to start filling data holes for the series. If set to NaN,
124 125 * the limit is not used
125 126 * @param maxBound the limit at which to end filling data holes for the series. If set to NaN,
126 127 * the limit is not used
127 128 *
128 129 * @remarks There is no control over the consistency between x-axis data and values data. The
129 130 * method considers that the data is well formed (the total number of values data is a multiple
130 131 * of the number of x-axis data)
131 132 */
132 133 static void fillDataHoles(std::vector<double> &xAxisData, std::vector<double> &valuesData,
133 134 double resolution,
134 135 double fillValue = std::numeric_limits<double>::quiet_NaN(),
135 136 double minBound = std::numeric_limits<double>::quiet_NaN(),
136 137 double maxBound = std::numeric_limits<double>::quiet_NaN());
137 138 /**
138 139 * Computes the resolution of a dataset passed as a parameter.
139 140 *
140 141 * The resolution of a dataset is the minimum difference between two values that follow in the
141 142 * set.
142 143 * For example:
143 144 * - for the set [0, 2, 4, 8, 10, 11, 13] => the resolution is 1 (difference between 10 and 11).
144 145 *
145 146 * A resolution can be calculated on the logarithmic scale (base of 10). In this case, the
146 147 * dataset is first converted to logarithmic values.
147 148 * For example:
148 149 * - for the set [10, 100, 10000, 1000000], the values are converted to [1, 2, 4, 6] => the
149 150 * logarithmic resolution is 1 (difference between 1 and 2).
150 151 *
151 152 * @param begin the iterator pointing to the beginning of the dataset
152 153 * @param end the iterator pointing to the end of the dataset
153 154 * @param logarithmic computes a logarithmic resolution or not
154 155 * @return the resolution computed
155 156 * @warning the method considers the dataset as sorted and doesn't control it.
156 157 */
157 158 template <typename Iterator>
158 159 static Resolution resolution(Iterator begin, Iterator end, bool logarithmic = false);
159 160
160 161 /**
161 162 * Computes a regular mesh for a data series, according to resolutions for x-axis and y-axis
162 163 * passed as parameters.
163 164 *
164 165 * The mesh is created from the resolutions in x and y and the boundaries delimiting the data
165 166 * series. If the resolutions do not allow to obtain a regular mesh, they are recalculated.
166 167 *
167 168 * For example :
168 169 * Let x-axis data = [0, 1, 3, 5, 9], its associated values ​​= [0, 10, 30, 50, 90] and
169 170 * xResolution = 2.
170 171 * Based on the resolution, the mesh would be [0, 2, 4, 6, 8, 10] and would be invalid because
171 172 * it exceeds the maximum bound of the data. The resolution is thus recalculated so that the
172 173 * mesh holds between the data terminals.
173 174 * So => resolution is 1.8 and the mesh is [0, 1.8, 3.6, 5.4, 7.2, 9].
174 175 *
175 176 * Once the mesh is generated in x and y, the values ​​are associated with each mesh point,
176 177 * based on the data in the series, finding the existing data at which the mesh point would be
177 178 * or would be closest to, without exceeding it.
178 179 *
179 180 * In the example, we determine the value of each mesh point:
180 181 * - x = 0 => value = 0 (existing x in the data series)
181 182 * - x = 1.8 => value = 10 (the closest existing x: 1)
182 183 * - x = 3.6 => value = 30 (the closest existing x: 3)
183 184 * - x = 5.4 => value = 50 (the closest existing x: 5)
184 185 * - x = 7.2 => value = 50 (the closest existing x: 5)
185 186 * - x = 9 => value = 90 (existing x in the data series)
186 187 *
187 188 * Same algorithm is applied for y-axis.
188 189 *
189 190 * @param begin the iterator pointing to the beginning of the data series
190 191 * @param end the iterator pointing to the end of the data series
191 192 * @param xResolution the resolution expected for the mesh's x-axis
192 193 * @param yResolution the resolution expected for the mesh's y-axis
193 194 * @return the mesh created, an empty mesh if the input data do not allow to generate a regular
194 195 * mesh (empty data, null resolutions, logarithmic x-axis)
195 196 * @warning the method considers the dataset as sorted and doesn't control it.
196 197 */
197 198 static Mesh regularMesh(DataSeriesIterator begin, DataSeriesIterator end,
198 199 Resolution xResolution, Resolution yResolution);
199 200 };
200 201
201 202 template <typename Iterator>
202 203 DataSeriesUtils::Resolution DataSeriesUtils::resolution(Iterator begin, Iterator end,
203 204 bool logarithmic)
204 205 {
205 206 // Retrieves data into a work dataset
206 207 using ValueType = typename Iterator::value_type;
207 208 std::vector<ValueType> values{};
208 209 std::copy(begin, end, std::back_inserter(values));
209 210
210 211 // Converts data if logarithmic flag is activated
211 212 if (logarithmic) {
212 213 std::for_each(values.begin(), values.end(),
213 214 [logarithmic](auto &val) { val = std::log10(val); });
214 215 }
215 216
216 217 // Computes the differences between the values in the dataset
217 218 std::adjacent_difference(values.begin(), values.end(), values.begin());
218 219
219 220 // Retrieves the smallest difference
220 221 auto resolutionIt = std::min_element(values.begin(), values.end());
221 222 auto resolution
222 223 = resolutionIt != values.end() ? *resolutionIt : std::numeric_limits<double>::quiet_NaN();
223 224
224 225 return Resolution{resolution, logarithmic};
225 226 }
226 227
227 228 #endif // SCIQLOP_DATASERIESUTILS_H
@@ -1,196 +1,194
1 1 #include "Data/DataSeriesUtils.h"
2 2
3 #include <cmath>
4
5 3 Q_LOGGING_CATEGORY(LOG_DataSeriesUtils, "DataSeriesUtils")
6 4
7 5 void DataSeriesUtils::fillDataHoles(std::vector<double> &xAxisData, std::vector<double> &valuesData,
8 6 double resolution, double fillValue, double minBound,
9 7 double maxBound)
10 8 {
11 9 if (resolution == 0. || std::isnan(resolution)) {
12 10 qCWarning(LOG_DataSeriesUtils())
13 11 << "Can't fill data holes with a null resolution, no changes will be made";
14 12 return;
15 13 }
16 14
17 15 if (xAxisData.empty()) {
18 16 qCWarning(LOG_DataSeriesUtils())
19 17 << "Can't fill data holes for empty data, no changes will be made";
20 18 return;
21 19 }
22 20
23 21 // Gets the number of values per x-axis data
24 22 auto nbComponents = valuesData.size() / xAxisData.size();
25 23
26 24 // Generates fill values that will be used to complete values data
27 25 std::vector<double> fillValues(nbComponents, fillValue);
28 26
29 27 // Checks if there are data holes on the beginning of the data and generates the hole at the
30 28 // extremity if it's the case
31 29 auto minXAxisData = xAxisData.front();
32 30 if (!std::isnan(minBound) && minBound < minXAxisData) {
33 31 auto holeSize = static_cast<int>((minXAxisData - minBound) / resolution);
34 32 if (holeSize > 0) {
35 33 xAxisData.insert(xAxisData.begin(), minXAxisData - holeSize * resolution);
36 34 valuesData.insert(valuesData.begin(), fillValues.begin(), fillValues.end());
37 35 }
38 36 }
39 37
40 38 // Same for the end of the data
41 39 auto maxXAxisData = xAxisData.back();
42 40 if (!std::isnan(maxBound) && maxBound > maxXAxisData) {
43 41 auto holeSize = static_cast<int>((maxBound - maxXAxisData) / resolution);
44 42 if (holeSize > 0) {
45 43 xAxisData.insert(xAxisData.end(), maxXAxisData + holeSize * resolution);
46 44 valuesData.insert(valuesData.end(), fillValues.begin(), fillValues.end());
47 45 }
48 46 }
49 47
50 48 // Generates other data holes
51 49 auto xAxisIt = xAxisData.begin();
52 50 while (xAxisIt != xAxisData.end()) {
53 51 // Stops at first value which has a gap greater than resolution with the value next to it
54 52 xAxisIt = std::adjacent_find(
55 53 xAxisIt, xAxisData.end(),
56 54 [resolution](const auto &a, const auto &b) { return (b - a) > resolution; });
57 55
58 56 if (xAxisIt != xAxisData.end()) {
59 57 auto nextXAxisIt = xAxisIt + 1;
60 58
61 59 // Gets the values that has a gap greater than resolution between them
62 60 auto lowValue = *xAxisIt;
63 61 auto highValue = *nextXAxisIt;
64 62
65 63 // Completes holes between the two values by creating new values (according to the
66 64 // resolution)
67 65 for (auto i = lowValue + resolution; i < highValue; i += resolution) {
68 66 // Gets the iterator of values data from which to insert fill values
69 67 auto nextValuesIt = valuesData.begin()
70 68 + std::distance(xAxisData.begin(), nextXAxisIt) * nbComponents;
71 69
72 70 // New value is inserted before nextXAxisIt
73 71 nextXAxisIt = xAxisData.insert(nextXAxisIt, i) + 1;
74 72
75 73 // New values are inserted before nextValuesIt
76 74 valuesData.insert(nextValuesIt, fillValues.begin(), fillValues.end());
77 75 }
78 76
79 77 // Moves to the next value to continue loop on the x-axis data
80 78 xAxisIt = nextXAxisIt;
81 79 }
82 80 }
83 81 }
84 82
85 83 namespace {
86 84
87 85 /**
88 86 * Generates axis's mesh properties according to data and resolution
89 87 * @param begin the iterator pointing to the beginning of the data
90 88 * @param end the iterator pointing to the end of the data
91 89 * @param fun the function to retrieve data from the data iterators
92 90 * @param resolution the resolution to use for the axis' mesh
93 91 * @return a tuple representing the mesh properties : <nb values, min value, value step>
94 92 */
95 93 template <typename Iterator, typename IteratorFun>
96 94 std::tuple<int, double, double> meshProperties(Iterator begin, Iterator end, IteratorFun fun,
97 95 double resolution)
98 96 {
99 97 // Computes the gap between min and max data. This will be used to determinate the step between
100 98 // each data of the mesh
101 99 auto min = fun(begin);
102 100 auto max = fun(end - 1);
103 101 auto gap = max - min;
104 102
105 103 // Computes the step trying to use the fixed resolution. If the resolution doesn't separate the
106 104 // values evenly , it is recalculated.
107 105 // For example, for a resolution of 2.0:
108 106 // - for interval [0; 8] => resolution is valid, the generated mesh will be [0, 2, 4, 6, 8]
109 107 // - for interval [0; 9] => it's impossible to create a regular mesh with this resolution
110 108 // The resolution is recalculated and is worth 1.8. The generated mesh will be [0, 1.8, 3.6,
111 109 // 5.4, 7.2, 9]
112 110 auto nbVal = static_cast<int>(std::ceil(gap / resolution));
113 111 auto step = gap / nbVal;
114 112
115 113 // last data is included in the total number of values
116 114 return std::make_tuple(nbVal + 1, min, step);
117 115 }
118 116
119 117 } // namespace
120 118
121 119 DataSeriesUtils::Mesh DataSeriesUtils::regularMesh(DataSeriesIterator begin, DataSeriesIterator end,
122 120 Resolution xResolution, Resolution yResolution)
123 121 {
124 122 // Checks preconditions
125 123 if (xResolution.m_Val == 0. || std::isnan(xResolution.m_Val) || yResolution.m_Val == 0.
126 124 || std::isnan(yResolution.m_Val)) {
127 125 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh with a null resolution";
128 126 return Mesh{};
129 127 }
130 128
131 129 if (xResolution.m_Logarithmic) {
132 130 qCWarning(LOG_DataSeriesUtils())
133 131 << "Can't generate mesh with a logarithmic x-axis resolution";
134 132 return Mesh{};
135 133 }
136 134
137 135 if (std::distance(begin, end) == 0) {
138 136 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh for empty data";
139 137 return Mesh{};
140 138 }
141 139
142 140 auto yData = begin->y();
143 141 if (yData.empty()) {
144 142 qCWarning(LOG_DataSeriesUtils()) << "Can't generate mesh for data with no y-axis";
145 143 return Mesh{};
146 144 }
147 145
148 146 // Converts y-axis and its resolution to logarithmic values
149 147 if (yResolution.m_Logarithmic) {
150 148 std::for_each(yData.begin(), yData.end(), [](auto &val) { val = std::log10(val); });
151 149 }
152 150
153 151 // Computes mesh properties
154 152 int nbX, nbY;
155 153 double xMin, xStep, yMin, yStep;
156 154 std::tie(nbX, xMin, xStep)
157 155 = meshProperties(begin, end, [](const auto &it) { return it->x(); }, xResolution.m_Val);
158 156 std::tie(nbY, yMin, yStep) = meshProperties(
159 157 yData.begin(), yData.end(), [](const auto &it) { return *it; }, yResolution.m_Val);
160 158
161 159 // Generates mesh according to the x-axis and y-axis steps
162 160 Mesh result{nbX, xMin, xStep, nbY, yMin, yStep};
163 161
164 162 for (auto meshXIndex = 0; meshXIndex < nbX; ++meshXIndex) {
165 163 auto meshX = xMin + meshXIndex * xStep;
166 164 // According to current x-axis of the mesh, finds in the data series the interval in which
167 165 // the data is or gets closer (without exceeding it).
168 166 // An interval is defined by a value and extends to +/- 50% of the resolution. For example,
169 167 // for a value of 3 and a resolution of 1, the associated interval is [2.5, 3.5].
170 168 auto xIt = std::lower_bound(begin, end, meshX,
171 169 [xResolution](const auto &it, const auto &val) {
172 170 return it.x() - xResolution.m_Val / 2. < val;
173 171 })
174 172 - 1;
175 173
176 174 // When the corresponding entry of the data series is found, generates the values of the
177 175 // mesh by retrieving the values of the entry, for each y-axis value of the mesh
178 176 auto values = xIt->values();
179 177
180 178 for (auto meshYIndex = 0; meshYIndex < nbY; ++meshYIndex) {
181 179 auto meshY = yMin + meshYIndex * yStep;
182 180
183 181 auto yBegin = yData.begin();
184 182 auto yIt = std::lower_bound(yBegin, yData.end(), meshY,
185 183 [yResolution](const auto &it, const auto &val) {
186 184 return it - yResolution.m_Val / 2. < val;
187 185 })
188 186 - 1;
189 187
190 188 auto valueIndex = std::distance(yBegin, yIt);
191 189 result.m_Data[result.m_NbX * meshYIndex + meshXIndex] = values.at(valueIndex);
192 190 }
193 191 }
194 192
195 193 return result;
196 194 }
General Comments 0
You need to be logged in to leave comments. Login now