QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
Loading...
Searching...
No Matches
qgspointcloudstatscalculator.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspointcloudstatscalculator.cpp
3 --------------------
4 begin : April 2022
5 copyright : (C) 2022 by Belgacem Nedjima
6 email : belgacem dot nedjima at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
20#include "qgsfeedback.h"
21#include "qgsmessagelog.h"
24#include "qgspointcloudindex.h"
28
29#include <QQueue>
30#include <QString>
31#include <QtConcurrentMap>
32
33#include "moc_qgspointcloudstatscalculator.cpp"
34
35using namespace Qt::StringLiterals;
36
38{
41
42 StatsProcessor( QgsPointCloudIndex index, QgsPointCloudRequest request, QgsFeedback *feedback, double progressValue )
43 : mIndex( std::move( index ) )
44 , mRequest( request )
45 , mFeedback( feedback )
46 , mProgressValue( progressValue )
47 {}
48
49 StatsProcessor( const StatsProcessor &processor )
50 : mIndex( processor.mIndex )
51 , mRequest( processor.mRequest )
52 , mFeedback( processor.mFeedback )
53 , mProgressValue( processor.mProgressValue )
54 {}
55
57 {
58 mIndex = rhs.mIndex;
59 mRequest = rhs.mRequest;
60 mFeedback = rhs.mFeedback;
61 mProgressValue = rhs.mProgressValue;
62 return *this;
63 }
64
66 {
67 QgsPointCloudNode node = mIndex.getNode( nodeId );
68 if ( node.pointCount() < 1 )
70
71 std::unique_ptr<QgsPointCloudBlock> block = nullptr;
72 if ( mIndex.accessType() == Qgis::PointCloudAccessType::Local )
73 {
74 block = mIndex.nodeData( nodeId, mRequest );
75 }
76 else
77 {
78 QgsPointCloudBlockRequest *request = mIndex.asyncNodeData( nodeId, mRequest );
79 if ( request == nullptr )
80 {
81 QgsDebugError( u"Unable to calculate statistics for node %1: Got nullptr async request"_s.arg( nodeId.toString() ) );
83 }
84 QEventLoop loop;
85 QObject::connect( request, &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit );
86 QObject::connect( mFeedback, &QgsFeedback::canceled, &loop, &QEventLoop::quit );
87 loop.exec();
88 if ( !mFeedback->isCanceled() )
89 {
90 block = request->takeBlock();
91 if ( !block )
92 {
93 QgsMessageLog::logMessage( QObject::tr( "Unable to calculate statistics for node %1, error: \"%2\"" ).arg( nodeId.toString(), request->errorStr() ) );
94 }
95 }
96 }
97
98 if ( !block )
99 {
100 updateFeedback();
102 }
103
104 const QgsPointCloudAttributeCollection attributesCollection = block->attributes();
105 const QVector<QgsPointCloudAttribute> attributes = attributesCollection.attributes();
106 const char *ptr = block->data();
107 int count = block->pointCount();
108 int recordSize = attributesCollection.pointRecordSize();
109
110 QMap<QString, QgsPointCloudAttributeStatistics> statsMap;
111 for ( const QgsPointCloudAttribute &attribute : attributes )
112 {
114 summary.minimum = std::numeric_limits<double>::max();
115 summary.maximum = std::numeric_limits<double>::lowest();
116 summary.count = 0;
117 summary.mean = 0;
118 summary.stDev = std::numeric_limits<double>::quiet_NaN();
119 summary.classCount.clear();
120 statsMap[attribute.name()] = std::move( summary );
121 }
122
123 QVector<int> attributeOffsetVector;
124 QSet<int> classifiableAttributesOffsetSet;
125 for ( const QgsPointCloudAttribute &attribute : attributes )
126 {
127 int attributeOffset = 0;
128 attributesCollection.find( attribute.name(), attributeOffset );
129 attributeOffsetVector.push_back( attributeOffset );
130 if ( attribute.name() == "ScannerChannel"_L1
131 || attribute.name() == "ReturnNumber"_L1
132 || attribute.name() == "NumberOfReturns"_L1
133 || attribute.name() == "ScanDirectionFlag"_L1
134 || attribute.name() == "Classification"_L1
135 || attribute.name() == "EdgeOfFlightLine"_L1
136 || attribute.name() == "PointSourceId"_L1
137 || attribute.name() == "Synthetic"_L1
138 || attribute.name() == "KeyPoint"_L1
139 || attribute.name() == "Withheld"_L1
140 || attribute.name() == "Overlap"_L1 )
141 {
142 classifiableAttributesOffsetSet.insert( attributeOffset );
143 }
144 }
145
146 QVector<double> sumOfSquares( attributes.size(), 0 );
147 QVector<double> partialMean( attributes.size(), 0 );
148 for ( int i = 0; i < count; ++i )
149 {
150 for ( int j = 0; j < attributes.size(); ++j )
151 {
152 if ( mFeedback->isCanceled() )
153 {
155 }
156 QString attributeName = attributes.at( j ).name();
157 QgsPointCloudAttribute::DataType attributeType = attributes.at( j ).type();
158
159 double attributeValue = 0;
160 int attributeOffset = attributeOffsetVector[j];
161
162 QgsPointCloudAttributeStatistics &stats = statsMap[attributeName];
163 QgsPointCloudRenderContext::getAttribute( ptr, i * recordSize + attributeOffset, attributeType, attributeValue );
164 stats.minimum = std::min( stats.minimum, attributeValue );
165 stats.maximum = std::max( stats.maximum, attributeValue );
166 stats.mean += attributeValue / count;
167 stats.count++;
168
169 // Single pass stdev
170 const double delta = attributeValue - partialMean[j];
171 partialMean[j] += delta / stats.count;
172 sumOfSquares[j] += delta * ( attributeValue - partialMean[j] );
173
174 if ( classifiableAttributesOffsetSet.contains( attributeOffset ) )
175 {
176 stats.classCount[( int ) attributeValue]++;
177 }
178 }
179 }
180
181 for ( int j = 0; j < attributes.size(); ++j )
182 {
183 const QString attributeName = attributes.at( j ).name();
184 QgsPointCloudAttributeStatistics &stats = statsMap[attributeName];
185 stats.stDev = std::sqrt( sumOfSquares[j] / ( stats.count - 1.0 ) );
186 }
187
188 updateFeedback();
189 return QgsPointCloudStatistics( count, statsMap );
190 }
191
192 private:
193 QgsPointCloudIndex mIndex;
194 QgsPointCloudRequest mRequest;
195 QgsFeedback *mFeedback = nullptr;
196 double mProgressValue = 0.0;
197
198 void updateFeedback()
199 {
200 QMutexLocker locker( &sStatsProcessorFeedbackMutex );
201 mFeedback->setProgress( mFeedback->progress() + mProgressValue );
202 }
203};
204
206
210
211bool QgsPointCloudStatsCalculator::calculateStats( QgsFeedback *feedback, const QVector<QgsPointCloudAttribute> &attributes, qint64 pointsLimit )
212{
213 if ( !mIndex.isValid() )
214 {
215 QgsMessageLog::logMessage( QObject::tr( "Unable to calculate statistics of an invalid index" ) );
216 return false;
217 }
218 mRequest.setAttributes( attributes );
219
220 qint64 pointCount = 0;
221 QVector<QgsPointCloudNodeId> nodes;
222 QQueue<QgsPointCloudNodeId> queue;
223 queue.push_back( mIndex.root() );
224 while ( !queue.empty() )
225 {
226 QgsPointCloudNode node = mIndex.getNode( queue.front() );
227 queue.pop_front();
228 if ( !mProcessedNodes.contains( node.id() ) )
229 pointCount += node.pointCount();
230 if ( pointsLimit != -1 && pointCount > pointsLimit )
231 break;
232 if ( !mProcessedNodes.contains( node.id() ) )
233 {
234 nodes.push_back( node.id() );
235 mProcessedNodes.insert( node.id() );
236 }
237 for ( const QgsPointCloudNodeId &child : node.children() )
238 {
239 queue.push_back( child );
240 }
241 }
242
243 feedback->setProgress( 0 );
244
245 QVector<QgsPointCloudStatistics> list = QtConcurrent::blockingMapped( nodes, StatsProcessor( mIndex, mRequest, feedback, 100.0 / ( double ) nodes.size() ) );
246
247 for ( QgsPointCloudStatistics &s : list )
248 {
249 mStats.combineWith( s );
250 }
251 return !feedback->isCanceled() && mStats.sampledPointsCount() != 0;
252}
@ Local
Local means the source is a local file on the machine.
Definition qgis.h:6433
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void canceled()
Internal routines can connect to this signal if they use event loop.
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
double progress() const
Returns the current progress reported by the feedback object.
Definition qgsfeedback.h:81
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
A collection of point cloud attributes.
int pointRecordSize() const
Returns total size of record.
const QgsPointCloudAttribute * find(const QString &attributeName, int &offset) const
Finds the attribute with the name.
QVector< QgsPointCloudAttribute > attributes() const
Returns all attributes.
Attribute for point cloud data pair of name and size in bytes.
DataType
Systems of unit measurement.
Base class for handling loading QgsPointCloudBlock asynchronously.
QString errorStr() const
Returns the error message string of the request.
void finished()
Emitted when the request processing has finished.
std::unique_ptr< QgsPointCloudBlock > takeBlock()
Returns the requested block.
Smart pointer for QgsAbstractPointCloudIndex.
Represents an indexed point cloud node's position in octree.
QString toString() const
Encode node to string.
Keeps metadata for an indexed point cloud node.
QList< QgsPointCloudNodeId > children() const
Returns IDs of child nodes.
qint64 pointCount() const
Returns number of points contained in node data.
QgsPointCloudNodeId id() const
Returns node's ID (unique in index).
static void getAttribute(const char *data, std::size_t offset, QgsPointCloudAttribute::DataType type, T &value)
Retrieves the attribute value from data at the specified offset, where type indicates the original da...
Point cloud data request.
Used to store statistics of a point cloud dataset.
QgsPointCloudStatsCalculator(QgsPointCloudIndex index)
Constructor.
bool calculateStats(QgsFeedback *feedback, const QVector< QgsPointCloudAttribute > &attributes, qint64 pointsLimit=-1)
Calculates the statistics of given attributes attributes up to new pointsLimit points Note: the alrea...
#define QgsDebugError(str)
Definition qgslogger.h:59
Stores statistics of one attribute of a point cloud dataset.
static QMutex sStatsProcessorFeedbackMutex
StatsProcessor(const StatsProcessor &processor)
QgsPointCloudStatistics result_type
QgsPointCloudStatistics operator()(QgsPointCloudNodeId nodeId)
StatsProcessor & operator=(const StatsProcessor &rhs)
StatsProcessor(QgsPointCloudIndex index, QgsPointCloudRequest request, QgsFeedback *feedback, double progressValue)