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