QGIS API Documentation 4.3.0-Master (d3b565c628d)
Loading...
Searching...
No Matches
qgsalgorithmrastergaussianblur.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmrastergaussianblur.cpp
3 ---------------------
4 begin : December 2025
5 copyright : (C) 2025 by Nyall Dawson
6 email : nyall dot dawson 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 "qgsrasterfilewriter.h"
21
22#include <QString>
23
24using namespace Qt::StringLiterals;
25
27
28QString QgsRasterGaussianBlurAlgorithm::name() const
29{
30 return u"rastergaussianblur"_s;
31}
32
33QString QgsRasterGaussianBlurAlgorithm::displayName() const
34{
35 return QObject::tr( "Gaussian blur" );
36}
37
38QStringList QgsRasterGaussianBlurAlgorithm::tags() const
39{
40 return QObject::tr( "smooth,filter,denoise" ).split( ',' );
41}
42
43QString QgsRasterGaussianBlurAlgorithm::group() const
44{
45 return QObject::tr( "Raster analysis" );
46}
47
48QString QgsRasterGaussianBlurAlgorithm::groupId() const
49{
50 return u"rasteranalysis"_s;
51}
52
53QString QgsRasterGaussianBlurAlgorithm::shortHelpString() const
54{
55 return QObject::tr(
56 "This algorithm applies a Gaussian blur filter to an input raster layer.\n\n"
57 "The radius parameter controls the strength of the blur. "
58 "A larger radius results in a smoother output, at the cost of execution time."
59 );
60}
61
62QString QgsRasterGaussianBlurAlgorithm::shortDescription() const
63{
64 return QObject::tr( "Applies a Gaussian blur filter to a raster layer." );
65}
66
67void QgsRasterGaussianBlurAlgorithm::initAlgorithm( const QVariantMap & )
68{
69 addParameter( new QgsProcessingParameterRasterLayer( u"INPUT"_s, QObject::tr( "Input layer" ) ) );
70
71 addParameter( new QgsProcessingParameterBand( u"BAND"_s, QObject::tr( "Band number" ), 1, u"INPUT"_s ) );
72
73 auto sigmaParam = std::make_unique<QgsProcessingParameterNumber>( u"RADIUS"_s, QObject::tr( "Blur radius (pixels)" ), Qgis::ProcessingNumberParameterType::Integer, 2.0, false, 1, 512 );
74 addParameter( sigmaParam.release() );
75
76 auto creationOptsParam = std::make_unique<QgsProcessingParameterString>( u"CREATION_OPTIONS"_s, QObject::tr( "Creation options" ), QVariant(), false, true );
77 creationOptsParam->setMetadata( QVariantMap( { { u"widget_wrapper"_s, QVariantMap( { { u"widget_type"_s, u"rasteroptions"_s } } ) } } ) );
78 creationOptsParam->setFlags( creationOptsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
79 addParameter( creationOptsParam.release() );
80
81 auto outputLayerParam = std::make_unique<QgsProcessingParameterRasterDestination>( u"OUTPUT"_s, QObject::tr( "Output layer" ) );
82 addParameter( outputLayerParam.release() );
83}
84
85QgsRasterGaussianBlurAlgorithm *QgsRasterGaussianBlurAlgorithm::createInstance() const
86{
87 return new QgsRasterGaussianBlurAlgorithm();
88}
89
90bool QgsRasterGaussianBlurAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
91{
92 QgsRasterLayer *layer = parameterAsRasterLayer( parameters, u"INPUT"_s, context );
93 if ( !layer )
94 throw QgsProcessingException( invalidRasterError( parameters, u"INPUT"_s ) );
95
96 const int band = parameterAsInt( parameters, u"BAND"_s, context );
97
98 mBand = parameterAsInt( parameters, u"BAND"_s, context );
99 if ( mBand < 1 || mBand > layer->bandCount() )
100 throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( layer->bandCount() ) );
101
102 mInterface.reset( layer->dataProvider()->clone() );
103 mHasNoDataValue = layer->dataProvider()->sourceHasNoDataValue( band );
104 mLayerWidth = layer->width();
105 mLayerHeight = layer->height();
106 mExtent = layer->extent();
107 mCrs = layer->crs();
108 mRasterUnitsPerPixelX = layer->rasterUnitsPerPixelX();
109 mRasterUnitsPerPixelY = layer->rasterUnitsPerPixelY();
110 mDataType = layer->dataProvider()->dataType( mBand );
111 mNoData = layer->dataProvider()->sourceNoDataValue( mBand );
112 return true;
113}
114
115QVariantMap QgsRasterGaussianBlurAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
116{
117 QGS_MARK_ALGORITHM_SOURCE
118
119 const int radius = parameterAsInt( parameters, u"RADIUS"_s, context );
120
121 const QString creationOptions = parameterAsString( parameters, u"CREATION_OPTIONS"_s, context ).trimmed();
122
123 const QString outputFile = parameterAsOutputLayer( parameters, u"OUTPUT"_s, context );
124 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT"_s, context );
125
126 auto outputWriter = std::make_unique<QgsRasterFileWriter>( outputFile );
127 outputWriter->setOutputProviderKey( u"gdal"_s );
128 if ( !creationOptions.isEmpty() )
129 {
130 outputWriter->setCreationOptions( creationOptions.split( '|' ) );
131 }
132 outputWriter->setOutputFormat( outputFormat );
133
134 std::unique_ptr<QgsRasterDataProvider> destProvider( outputWriter->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
135 if ( !destProvider )
136 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
137 if ( !destProvider->isValid() )
138 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, destProvider->error().message( QgsErrorMessage::Text ) ) );
139
140 destProvider->setNoDataValue( 1, mNoData );
141 destProvider->setEditable( true );
142
143 // create 1D Gaussian kernel
144 const int kernelSize = 2 * radius + 1;
145 std::vector<double> kernel( kernelSize );
146
147 double sum = 0.0;
148 const double sigma = radius / 3.0;
149 const double expCoefficient = -1.0 / ( 2.0 * sigma * sigma );
150
151 for ( int i = -radius; i <= radius; ++i )
152 {
153 double result = std::exp( i * i * expCoefficient );
154 kernel[i + radius] = result;
155 sum += result;
156 }
157 // normalize kernel
158 for ( double &w : kernel )
159 {
160 w /= sum;
161 }
162
163 QgsRasterIterator iter( mInterface.get(), radius );
164 iter.startRasterRead( mBand, mLayerWidth, mLayerHeight, mExtent );
165 int iterLeft = 0;
166 int iterTop = 0;
167 int iterCols = 0;
168 int iterRows = 0;
169 int tileLeft = 0;
170 int tileTop = 0;
171 int tileCols = 0;
172 int tileRows = 0;
173
174 QgsRectangle blockExtent;
175
176 std::vector<double> horizBuffer;
177 std::vector<qint8> horizNoData;
178 // reserve max potential size to avoid reallocations
179 const std::size_t maxBufferSize = static_cast< std::size_t >( QgsRasterIterator::DEFAULT_MAXIMUM_TILE_WIDTH + 2 * radius )
180 * static_cast< std::size_t >( QgsRasterIterator::DEFAULT_MAXIMUM_TILE_HEIGHT + 2 * radius );
181 horizBuffer.reserve( maxBufferSize );
182 horizNoData.reserve( maxBufferSize );
183
184 const bool hasReportsDuringClose = destProvider->hasReportsDuringClose();
185 const double maxProgressDuringBlockWriting = hasReportsDuringClose ? 50.0 : 100.0;
186
187 std::unique_ptr<QgsRasterBlock> inputBlock;
188 while ( iter.readNextRasterPart( mBand, iterCols, iterRows, inputBlock, iterLeft, iterTop, &blockExtent, &tileCols, &tileRows, &tileLeft, &tileTop ) )
189 {
190 feedback->setProgress( maxProgressDuringBlockWriting * iter.progress( mBand, 0 ) );
191
192 if ( feedback->isCanceled() )
193 break;
194
195 auto outputBlock = std::make_unique<QgsRasterBlock>( mDataType, tileCols, tileRows );
196
197 const int tileBoundaryLeft = tileLeft - iterLeft;
198 const int tileBoundaryTop = tileTop - iterTop;
199
200 // first pass -- horizontally blurred buffer. Take CAREFUL note of the different sizes used here -- for the
201 // first pass we calculate the blur over the whole vertical height of the tile (i.e. every row), but EXCLUDE the horizontal padding
202 const int horizWidth = tileCols;
203
204 const std::size_t bufferSize = static_cast< std::size_t>( horizWidth ) * iterRows;
205 horizBuffer.resize( bufferSize );
206 horizNoData.assign( bufferSize, 0 ); // reset to 0 (false)
207
208 bool isNoData = false;
209 double *horizPtr = horizBuffer.data();
210 qint8 *noDataPtr = horizNoData.data();
211
212 for ( int r = 0; r < iterRows; ++r )
213 {
214 feedback->setProgress( maxProgressDuringBlockWriting * iter.progress( mBand, r / static_cast< double >( iterRows ) * 0.5 ) );
215
216 if ( feedback->isCanceled() )
217 break;
218
219 for ( int c = 0; c < tileCols; ++c )
220 {
221 const int inputColumn = c + tileBoundaryLeft;
222 inputBlock->valueAndNoData( r, inputColumn, isNoData );
223
224 if ( isNoData )
225 {
226 *noDataPtr++ = 1; // mark as no-data
227 horizPtr++;
228 continue;
229 }
230
231 double sumValues = 0.0;
232 double sumWeight = 0.0;
233
234 for ( int k = -radius; k <= radius; ++k )
235 {
236 const int neighborColumn = inputColumn + k;
237 if ( neighborColumn < 0 || neighborColumn >= iterCols )
238 continue;
239
240 const double val = inputBlock->valueAndNoData( r, neighborColumn, isNoData );
241 if ( !isNoData )
242 {
243 double w = kernel[k + radius];
244 sumValues += val * w;
245 sumWeight += w;
246 }
247 }
248
249 if ( sumWeight > 0.0 )
250 {
251 // must re-normalize summed values -- we may have hit edges or nodata pixels
252 *horizPtr++ = sumValues / sumWeight;
253 // leave no-data flag as zero (not no-data)
254 noDataPtr++;
255 }
256 else
257 {
258 // should theoretically not happen if center is valid, unless all weights are 0
259 *noDataPtr++ = 1; // mark as no-data
260 horizPtr++;
261 }
262 }
263 }
264
265 // second pass -- vertical blur
266 // unlike the first pass, here we ONLY need to calculate the blur for pixels which aren't block padding
267 for ( int r = 0; r < tileRows; ++r )
268 {
269 feedback->setProgress( maxProgressDuringBlockWriting * iter.progress( mBand, 0.5 + r / static_cast< double >( tileRows ) * 0.5 ) );
270
271 if ( feedback->isCanceled() )
272 break;
273
274 const int inputRow = r + tileBoundaryTop;
275 for ( int c = 0; c < tileCols; ++c )
276 {
277 const std::size_t targetPixelBufferIndex = inputRow * static_cast< std::size_t>( horizWidth ) + c;
278 if ( horizNoData[targetPixelBufferIndex] )
279 {
280 outputBlock->setValue( r, c, mNoData );
281 continue;
282 }
283
284 double sumValues = 0.0;
285 double sumWeight = 0.0;
286
287 for ( int k = -radius; k <= radius; ++k )
288 {
289 const int neighborRow = inputRow + k;
290 if ( neighborRow < 0 || neighborRow >= iterRows )
291 continue;
292
293 const std::size_t neighborIndex = static_cast< std::size_t>( neighborRow ) * horizWidth + c;
294 if ( !horizNoData[neighborIndex] )
295 {
296 double w = kernel[k + radius];
297 sumValues += horizBuffer[neighborIndex] * w;
298 sumWeight += w;
299 }
300 }
301
302 if ( sumWeight > 0.0 )
303 {
304 outputBlock->setValue( r, c, sumValues / sumWeight );
305 }
306 else
307 {
308 outputBlock->setValue( r, c, mNoData );
309 }
310 }
311 }
312
313 if ( !destProvider->writeBlock( outputBlock.get(), 1, tileLeft, tileTop ) )
314 {
315 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( destProvider->error().summary() ) );
316 }
317 }
318 destProvider->setEditable( false );
319
320 if ( feedback && hasReportsDuringClose )
321 {
322 std::unique_ptr<QgsFeedback> scaledFeedback( QgsFeedback::createScaledFeedback( feedback, maxProgressDuringBlockWriting, 100.0 ) );
323 if ( !destProvider->closeWithProgress( scaledFeedback.get() ) )
324 {
325 if ( feedback->isCanceled() )
326 return {};
327 throw QgsProcessingException( QObject::tr( "Could not write raster dataset" ) );
328 }
329 }
330
331 QVariantMap outputs;
332 outputs.insert( u"OUTPUT"_s, outputFile );
333 return outputs;
334}
335
336
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3982
@ Text
Plain text format.
Definition qgserror.h:40
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:56
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:65
static std::unique_ptr< QgsFeedback > createScaledFeedback(QgsFeedback *parentFeedback, double startPercentage, double endPercentage)
Returns a feedback object whose [0, 100] progression range will be mapped to parentFeedback [startPer...
virtual Q_INVOKABLE QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
Contains information about the context in which a processing algorithm is executed.
Custom exception class for processing related exceptions.
Base class for providing feedback from a processing algorithm.
A raster band parameter for Processing algorithms.
A raster layer parameter for processing algorithms.
QgsRasterDataProvider * clone() const override=0
Clone itself, create deep copy.
virtual bool sourceHasNoDataValue(int bandNo) const
Returns true if source band has no data value.
virtual double sourceNoDataValue(int bandNo) const
Value representing no data value.
Qgis::DataType dataType(int bandNo) const override=0
Returns data type for the band specified by number.
Iterator for sequentially processing raster cells.
static const int DEFAULT_MAXIMUM_TILE_WIDTH
Default maximum tile width.
static const int DEFAULT_MAXIMUM_TILE_HEIGHT
Default maximum tile height.
Represents a raster layer.
int height() const
Returns the height of the (unclipped) raster.
int bandCount() const
Returns the number of bands in this layer.
double rasterUnitsPerPixelX() const
Returns the number of raster units per each raster pixel in X axis.
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
double rasterUnitsPerPixelY() const
Returns the number of raster units per each raster pixel in Y axis.
int width() const
Returns the width of the (unclipped) raster.
A rectangle specified with double values.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c