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