QGIS API Documentation 3.99.0-Master (357b655ed83)
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( "This algorithm applies a Gaussian blur filter to an input raster layer.\n\n"
56 "The radius parameter controls the strength of the blur. "
57 "A larger radius results in a smoother output, at the cost of execution time." );
58}
59
60QString QgsRasterGaussianBlurAlgorithm::shortDescription() const
61{
62 return QObject::tr( "Applies a Gaussian blur filter to a raster layer." );
63}
64
65void QgsRasterGaussianBlurAlgorithm::initAlgorithm( const QVariantMap & )
66{
67 addParameter( new QgsProcessingParameterRasterLayer( u"INPUT"_s, QObject::tr( "Input layer" ) ) );
68
69 addParameter( new QgsProcessingParameterBand( u"BAND"_s, QObject::tr( "Band number" ), 1, u"INPUT"_s ) );
70
71 auto sigmaParam = std::make_unique<QgsProcessingParameterNumber>( u"RADIUS"_s, QObject::tr( "Blur radius (pixels)" ), Qgis::ProcessingNumberParameterType::Integer, 2.0, false, 1, 512 );
72 addParameter( sigmaParam.release() );
73
74 auto creationOptsParam = std::make_unique<QgsProcessingParameterString>( u"CREATION_OPTIONS"_s, QObject::tr( "Creation options" ), QVariant(), false, true );
75 creationOptsParam->setMetadata( QVariantMap( { { u"widget_wrapper"_s, QVariantMap( { { u"widget_type"_s, u"rasteroptions"_s } } ) } } ) );
76 creationOptsParam->setFlags( creationOptsParam->flags() | Qgis::ProcessingParameterFlag::Advanced );
77 addParameter( creationOptsParam.release() );
78
79 auto outputLayerParam = std::make_unique<QgsProcessingParameterRasterDestination>( u"OUTPUT"_s, QObject::tr( "Output layer" ) );
80 addParameter( outputLayerParam.release() );
81}
82
83QgsRasterGaussianBlurAlgorithm *QgsRasterGaussianBlurAlgorithm::createInstance() const
84{
85 return new QgsRasterGaussianBlurAlgorithm();
86}
87
88bool QgsRasterGaussianBlurAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
89{
90 QgsRasterLayer *layer = parameterAsRasterLayer( parameters, u"INPUT"_s, context );
91 if ( !layer )
92 throw QgsProcessingException( invalidRasterError( parameters, u"INPUT"_s ) );
93
94 const int band = parameterAsInt( parameters, u"BAND"_s, context );
95
96 mBand = parameterAsInt( parameters, u"BAND"_s, context );
97 if ( mBand < 1 || mBand > layer->bandCount() )
98 throw QgsProcessingException( QObject::tr( "Invalid band number for BAND (%1): Valid values for input raster are 1 to %2" ).arg( mBand ).arg( layer->bandCount() ) );
99
100 mInterface.reset( layer->dataProvider()->clone() );
101 mHasNoDataValue = layer->dataProvider()->sourceHasNoDataValue( band );
102 mLayerWidth = layer->width();
103 mLayerHeight = layer->height();
104 mExtent = layer->extent();
105 mCrs = layer->crs();
106 mRasterUnitsPerPixelX = layer->rasterUnitsPerPixelX();
107 mRasterUnitsPerPixelY = layer->rasterUnitsPerPixelY();
108 mDataType = layer->dataProvider()->dataType( mBand );
109 mNoData = layer->dataProvider()->sourceNoDataValue( mBand );
110 return true;
111}
112
113QVariantMap QgsRasterGaussianBlurAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
114{
115 const int radius = parameterAsInt( parameters, u"RADIUS"_s, context );
116
117 const QString creationOptions = parameterAsString( parameters, u"CREATION_OPTIONS"_s, context ).trimmed();
118
119 const QString outputFile = parameterAsOutputLayer( parameters, u"OUTPUT"_s, context );
120 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT"_s, context );
121
122 auto outputWriter = std::make_unique<QgsRasterFileWriter>( outputFile );
123 outputWriter->setOutputProviderKey( u"gdal"_s );
124 if ( !creationOptions.isEmpty() )
125 {
126 outputWriter->setCreationOptions( creationOptions.split( '|' ) );
127 }
128 outputWriter->setOutputFormat( outputFormat );
129
130 std::unique_ptr<QgsRasterDataProvider> destProvider( outputWriter->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
131 if ( !destProvider )
132 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
133 if ( !destProvider->isValid() )
134 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, destProvider->error().message( QgsErrorMessage::Text ) ) );
135
136 destProvider->setNoDataValue( 1, mNoData );
137 destProvider->setEditable( true );
138
139 // create 1D Gaussian kernel
140 const int kernelSize = 2 * radius + 1;
141 std::vector<double> kernel( kernelSize );
142
143 double sum = 0.0;
144 const double sigma = radius / 3.0;
145 const double expCoefficient = -1.0 / ( 2.0 * sigma * sigma );
146
147 for ( int i = -radius; i <= radius; ++i )
148 {
149 double result = std::exp( i * i * expCoefficient );
150 kernel[i + radius] = result;
151 sum += result;
152 }
153 // normalize kernel
154 for ( double &w : kernel )
155 {
156 w /= sum;
157 }
158
159 QgsRasterIterator iter( mInterface.get(), radius );
160 iter.startRasterRead( mBand, mLayerWidth, mLayerHeight, mExtent );
161 int iterLeft = 0;
162 int iterTop = 0;
163 int iterCols = 0;
164 int iterRows = 0;
165 int tileLeft = 0;
166 int tileTop = 0;
167 int tileCols = 0;
168 int tileRows = 0;
169
170 QgsRectangle blockExtent;
171
172 std::vector<double> horizBuffer;
173 std::vector<qint8> horizNoData;
174 // reserve max potential size to avoid reallocations
175 const std::size_t maxBufferSize = static_cast< std::size_t >( QgsRasterIterator::DEFAULT_MAXIMUM_TILE_WIDTH + 2 * radius ) * static_cast< std::size_t >( QgsRasterIterator::DEFAULT_MAXIMUM_TILE_HEIGHT + 2 * radius );
176 horizBuffer.reserve( maxBufferSize );
177 horizNoData.reserve( maxBufferSize );
178
179 const bool hasReportsDuringClose = destProvider->hasReportsDuringClose();
180 const double maxProgressDuringBlockWriting = hasReportsDuringClose ? 50.0 : 100.0;
181
182 std::unique_ptr<QgsRasterBlock> inputBlock;
183 while ( iter.readNextRasterPart( mBand, iterCols, iterRows, inputBlock, iterLeft, iterTop, &blockExtent, &tileCols, &tileRows, &tileLeft, &tileTop ) )
184 {
185 feedback->setProgress( maxProgressDuringBlockWriting * iter.progress( mBand, 0 ) );
186
187 if ( feedback->isCanceled() )
188 break;
189
190 auto outputBlock = std::make_unique<QgsRasterBlock>( mDataType, tileCols, tileRows );
191
192 const int tileBoundaryLeft = tileLeft - iterLeft;
193 const int tileBoundaryTop = tileTop - iterTop;
194
195 // first pass -- horizontally blurred buffer. Take CAREFUL note of the different sizes used here -- for the
196 // first pass we calculate the blur over the whole vertical height of the tile (i.e. every row), but EXCLUDE the horizontal padding
197 const int horizWidth = tileCols;
198
199 const std::size_t bufferSize = static_cast< std::size_t>( horizWidth ) * iterRows;
200 horizBuffer.resize( bufferSize );
201 horizNoData.assign( bufferSize, 0 ); // reset to 0 (false)
202
203 bool isNoData = false;
204 double *horizPtr = horizBuffer.data();
205 qint8 *noDataPtr = horizNoData.data();
206
207 for ( int r = 0; r < iterRows; ++r )
208 {
209 feedback->setProgress( maxProgressDuringBlockWriting * iter.progress( mBand, r / static_cast< double >( iterRows ) * 0.5 ) );
210
211 if ( feedback->isCanceled() )
212 break;
213
214 for ( int c = 0; c < tileCols; ++c )
215 {
216 const int inputColumn = c + tileBoundaryLeft;
217 inputBlock->valueAndNoData( r, inputColumn, isNoData );
218
219 if ( isNoData )
220 {
221 *noDataPtr++ = 1; // mark as no-data
222 horizPtr++;
223 continue;
224 }
225
226 double sumValues = 0.0;
227 double sumWeight = 0.0;
228
229 for ( int k = -radius; k <= radius; ++k )
230 {
231 const int neighborColumn = inputColumn + k;
232 if ( neighborColumn < 0 || neighborColumn >= iterCols )
233 continue;
234
235 const double val = inputBlock->valueAndNoData( r, neighborColumn, isNoData );
236 if ( !isNoData )
237 {
238 double w = kernel[k + radius];
239 sumValues += val * w;
240 sumWeight += w;
241 }
242 }
243
244 if ( sumWeight > 0.0 )
245 {
246 // must re-normalize summed values -- we may have hit edges or nodata pixels
247 *horizPtr++ = sumValues / sumWeight;
248 // leave no-data flag as zero (not no-data)
249 noDataPtr++;
250 }
251 else
252 {
253 // should theoretically not happen if center is valid, unless all weights are 0
254 *noDataPtr++ = 1; // mark as no-data
255 horizPtr++;
256 }
257 }
258 }
259
260 // second pass -- vertical blur
261 // unlike the first pass, here we ONLY need to calculate the blur for pixels which aren't block padding
262 for ( int r = 0; r < tileRows; ++r )
263 {
264 feedback->setProgress( maxProgressDuringBlockWriting * iter.progress( mBand, 0.5 + r / static_cast< double >( tileRows ) * 0.5 ) );
265
266 if ( feedback->isCanceled() )
267 break;
268
269 const int inputRow = r + tileBoundaryTop;
270 for ( int c = 0; c < tileCols; ++c )
271 {
272 const std::size_t targetPixelBufferIndex = inputRow * static_cast< std::size_t>( horizWidth ) + c;
273 if ( horizNoData[targetPixelBufferIndex] )
274 {
275 outputBlock->setValue( r, c, mNoData );
276 continue;
277 }
278
279 double sumValues = 0.0;
280 double sumWeight = 0.0;
281
282 for ( int k = -radius; k <= radius; ++k )
283 {
284 const int neighborRow = inputRow + k;
285 if ( neighborRow < 0 || neighborRow >= iterRows )
286 continue;
287
288 const std::size_t neighborIndex = static_cast< std::size_t>( neighborRow ) * horizWidth + c;
289 if ( !horizNoData[neighborIndex] )
290 {
291 double w = kernel[k + radius];
292 sumValues += horizBuffer[neighborIndex] * w;
293 sumWeight += w;
294 }
295 }
296
297 if ( sumWeight > 0.0 )
298 {
299 outputBlock->setValue( r, c, sumValues / sumWeight );
300 }
301 else
302 {
303 outputBlock->setValue( r, c, mNoData );
304 }
305 }
306 }
307
308 if ( !destProvider->writeBlock( outputBlock.get(), 1, tileLeft, tileTop ) )
309 {
310 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( destProvider->error().summary() ) );
311 }
312 }
313 destProvider->setEditable( false );
314
315 if ( feedback && hasReportsDuringClose )
316 {
317 std::unique_ptr<QgsFeedback> scaledFeedback( QgsFeedback::createScaledFeedback( feedback, maxProgressDuringBlockWriting, 100.0 ) );
318 if ( !destProvider->closeWithProgress( scaledFeedback.get() ) )
319 {
320 if ( feedback->isCanceled() )
321 return {};
322 throw QgsProcessingException( QObject::tr( "Could not write raster dataset" ) );
323 }
324 }
325
326 QVariantMap outputs;
327 outputs.insert( u"OUTPUT"_s, outputFile );
328 return outputs;
329}
330
331
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3834
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:55
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition qgsfeedback.h:63
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 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