QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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 <QtConcurrent/QtConcurrentMap>
31
32#include "moc_qgspointcloudstatscalculator.cpp"
33
35{
38
39 StatsProcessor( QgsPointCloudIndex index, QgsPointCloudRequest request, QgsFeedback *feedback, double progressValue )
40 : mIndex( std::move( index ) ), mRequest( request ), mFeedback( feedback ), mProgressValue( progressValue )
41 {
42 }
43
44 StatsProcessor( const StatsProcessor &processor )
45 : mIndex( processor.mIndex ), mRequest( processor.mRequest ), mFeedback( processor.mFeedback ), mProgressValue( processor.mProgressValue )
46 {
47 }
48
50 {
51 mIndex = rhs.mIndex;
52 mRequest = rhs.mRequest;
53 mFeedback = rhs.mFeedback;
54 mProgressValue = rhs.mProgressValue;
55 return *this;
56 }
57
59 {
60 QgsPointCloudNode node = mIndex.getNode( nodeId );
61 if ( node.pointCount() < 1 )
63
64 std::unique_ptr<QgsPointCloudBlock> block = nullptr;
65 if ( mIndex.accessType() == Qgis::PointCloudAccessType::Local )
66 {
67 block = mIndex.nodeData( nodeId, mRequest );
68 }
69 else
70 {
71 QgsPointCloudBlockRequest *request = mIndex.asyncNodeData( nodeId, mRequest );
72 if ( request == nullptr )
73 {
74 QgsDebugError( QStringLiteral( "Unable to calculate statistics for node %1: Got nullptr async request" ).arg( nodeId.toString() ) );
76 }
77 QEventLoop loop;
78 QObject::connect( request, &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit );
79 QObject::connect( mFeedback, &QgsFeedback::canceled, &loop, &QEventLoop::quit );
80 loop.exec();
81 if ( !mFeedback->isCanceled() )
82 {
83 block = request->takeBlock();
84 if ( !block )
85 {
86 QgsMessageLog::logMessage( QObject::tr( "Unable to calculate statistics for node %1, error: \"%2\"" ).arg( nodeId.toString(), request->errorStr() ) );
87 }
88 }
89 }
90
91 if ( !block )
92 {
93 updateFeedback();
95 }
96
97 const QgsPointCloudAttributeCollection attributesCollection = block->attributes();
98 const QVector<QgsPointCloudAttribute> attributes = attributesCollection.attributes();
99 const char *ptr = block->data();
100 int count = block->pointCount();
101 int recordSize = attributesCollection.pointRecordSize();
102
103 QMap<QString, QgsPointCloudAttributeStatistics> statsMap;
104 for ( const QgsPointCloudAttribute &attribute : attributes )
105 {
107 summary.minimum = std::numeric_limits<double>::max();
108 summary.maximum = std::numeric_limits<double>::lowest();
109 summary.count = 0;
110 summary.mean = 0;
111 summary.stDev = std::numeric_limits<double>::quiet_NaN();
112 summary.classCount.clear();
113 statsMap[ attribute.name() ] = std::move( summary );
114 }
115
116 QVector<int> attributeOffsetVector;
117 QSet<int> classifiableAttributesOffsetSet;
118 for ( const QgsPointCloudAttribute &attribute : attributes )
119 {
120 int attributeOffset = 0;
121 attributesCollection.find( attribute.name(), attributeOffset );
122 attributeOffsetVector.push_back( attributeOffset );
123 if ( attribute.name() == QLatin1String( "ScannerChannel" ) ||
124 attribute.name() == QLatin1String( "ReturnNumber" ) ||
125 attribute.name() == QLatin1String( "NumberOfReturns" ) ||
126 attribute.name() == QLatin1String( "ScanDirectionFlag" ) ||
127 attribute.name() == QLatin1String( "Classification" ) ||
128 attribute.name() == QLatin1String( "EdgeOfFlightLine" ) ||
129 attribute.name() == QLatin1String( "PointSourceId" ) ||
130 attribute.name() == QLatin1String( "Synthetic" ) ||
131 attribute.name() == QLatin1String( "KeyPoint" ) ||
132 attribute.name() == QLatin1String( "Withheld" ) ||
133 attribute.name() == QLatin1String( "Overlap" ) )
134 {
135 classifiableAttributesOffsetSet.insert( attributeOffset );
136 }
137 }
138
139 QVector<double> sumOfSquares( attributes.size(), 0 );
140 QVector<double> partialMean( attributes.size(), 0 );
141 for ( int i = 0; i < count; ++i )
142 {
143 for ( int j = 0; j < attributes.size(); ++j )
144 {
145 if ( mFeedback->isCanceled() )
146 {
148 }
149 QString attributeName = attributes.at( j ).name();
150 QgsPointCloudAttribute::DataType attributeType = attributes.at( j ).type();
151
152 double attributeValue = 0;
153 int attributeOffset = attributeOffsetVector[ j ];
154
155 QgsPointCloudAttributeStatistics &stats = statsMap[ attributeName ];
156 QgsPointCloudRenderContext::getAttribute( ptr, i * recordSize + attributeOffset, attributeType, attributeValue );
157 stats.minimum = std::min( stats.minimum, attributeValue );
158 stats.maximum = std::max( stats.maximum, attributeValue );
159 stats.mean += attributeValue / count;
160 stats.count++;
161
162 // Single pass stdev
163 const double delta = attributeValue - partialMean[j];
164 partialMean[j] += delta / stats.count;
165 sumOfSquares[j] += delta * ( attributeValue - partialMean[j] );
166
167 if ( classifiableAttributesOffsetSet.contains( attributeOffset ) )
168 {
169 stats.classCount[( int )attributeValue ]++;
170 }
171 }
172 }
173
174 for ( int j = 0; j < attributes.size(); ++j )
175 {
176 const QString attributeName = attributes.at( j ).name();
177 QgsPointCloudAttributeStatistics &stats = statsMap[ attributeName ];
178 stats.stDev = std::sqrt( sumOfSquares[j] / ( stats.count - 1.0 ) );
179 }
180
181 updateFeedback();
182 return QgsPointCloudStatistics( count, statsMap );
183 }
184 private:
185 QgsPointCloudIndex mIndex;
186 QgsPointCloudRequest mRequest;
187 QgsFeedback *mFeedback = nullptr;
188 double mProgressValue = 0.0;
189
190 void updateFeedback()
191 {
192 QMutexLocker locker( &sStatsProcessorFeedbackMutex );
193 mFeedback->setProgress( mFeedback->progress() + mProgressValue );
194 }
195};
196
198
200 : mIndex( std::move( index ) )
201{
202
203}
204
205bool QgsPointCloudStatsCalculator::calculateStats( QgsFeedback *feedback, const QVector<QgsPointCloudAttribute> &attributes, qint64 pointsLimit )
206{
207 if ( !mIndex.isValid() )
208 {
209 QgsMessageLog::logMessage( QObject::tr( "Unable to calculate statistics of an invalid index" ) );
210 return false;
211 }
212 mRequest.setAttributes( attributes );
213
214 qint64 pointCount = 0;
215 QVector<QgsPointCloudNodeId> nodes;
216 QQueue<QgsPointCloudNodeId> queue;
217 queue.push_back( mIndex.root() );
218 while ( !queue.empty() )
219 {
220 QgsPointCloudNode node = mIndex.getNode( queue.front() );
221 queue.pop_front();
222 if ( !mProcessedNodes.contains( node.id() ) )
223 pointCount += node.pointCount();
224 if ( pointsLimit != -1 && pointCount > pointsLimit )
225 break;
226 if ( !mProcessedNodes.contains( node.id() ) )
227 {
228 nodes.push_back( node.id() );
229 mProcessedNodes.insert( node.id() );
230 }
231 for ( const QgsPointCloudNodeId &child : node.children() )
232 {
233 queue.push_back( child );
234 }
235 }
236
237 feedback->setProgress( 0 );
238
239 QVector<QgsPointCloudStatistics> list = QtConcurrent::blockingMapped( nodes, StatsProcessor( mIndex, mRequest, feedback, 100.0 / ( double )nodes.size() ) );
240
241 for ( QgsPointCloudStatistics &s : list )
242 {
243 mStats.combineWith( s );
244 }
245 return !feedback->isCanceled() && mStats.sampledPointsCount() != 0;
246}
@ Local
Local means the source is a local file on the machine.
Definition qgis.h:6075
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:53
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:61
double progress() const
Returns the current progress reported by the feedback object.
Definition qgsfeedback.h:77
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())
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:57
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)