QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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 const int radius = parameterAsInt( parameters, u"RADIUS"_s, context );
118
119 const QString creationOptions = parameterAsString( parameters, u"CREATION_OPTIONS"_s, context ).trimmed();
120
121 const QString outputFile = parameterAsOutputLayer( parameters, u"OUTPUT"_s, context );
122 const QString outputFormat = parameterAsOutputRasterFormat( parameters, u"OUTPUT"_s, context );
123
124 auto outputWriter = std::make_unique<QgsRasterFileWriter>( outputFile );
125 outputWriter->setOutputProviderKey( u"gdal"_s );
126 if ( !creationOptions.isEmpty() )
127 {
128 outputWriter->setCreationOptions( creationOptions.split( '|' ) );
129 }
130 outputWriter->setOutputFormat( outputFormat );
131
132 std::unique_ptr<QgsRasterDataProvider> destProvider( outputWriter->createOneBandRaster( mDataType, mLayerWidth, mLayerHeight, mExtent, mCrs ) );
133 if ( !destProvider )
134 throw QgsProcessingException( QObject::tr( "Could not create raster output: %1" ).arg( outputFile ) );
135 if ( !destProvider->isValid() )
136 throw QgsProcessingException( QObject::tr( "Could not create raster output %1: %2" ).arg( outputFile, destProvider->error().message( QgsErrorMessage::Text ) ) );
137
138 destProvider->setNoDataValue( 1, mNoData );
139 destProvider->setEditable( true );
140
141 // create 1D Gaussian kernel
142 const int kernelSize = 2 * radius + 1;
143 std::vector<double> kernel( kernelSize );
144
145 double sum = 0.0;
146 const double sigma = radius / 3.0;
147 const double expCoefficient = -1.0 / ( 2.0 * sigma * sigma );
148
149 for ( int i = -radius; i <= radius; ++i )
150 {
151 double result = std::exp( i * i * expCoefficient );
152 kernel[i + radius] = result;
153 sum += result;
154 }
155 // normalize kernel
156 for ( double &w : kernel )
157 {
158 w /= sum;
159 }
160
161 QgsRasterIterator iter( mInterface.get(), radius );
162 iter.startRasterRead( mBand, mLayerWidth, mLayerHeight, mExtent );
163 int iterLeft = 0;
164 int iterTop = 0;
165 int iterCols = 0;
166 int iterRows = 0;
167 int tileLeft = 0;
168 int tileTop = 0;
169 int tileCols = 0;
170 int tileRows = 0;
171
172 QgsRectangle blockExtent;
173
174 std::vector<double> horizBuffer;
175 std::vector<qint8> horizNoData;
176 // reserve max potential size to avoid reallocations
177 const std::size_t maxBufferSize = static_cast< std::size_t >( QgsRasterIterator::DEFAULT_MAXIMUM_TILE_WIDTH + 2 * radius )
178 * static_cast< std::size_t >( QgsRasterIterator::DEFAULT_MAXIMUM_TILE_HEIGHT + 2 * radius );
179 horizBuffer.reserve( maxBufferSize );
180 horizNoData.reserve( maxBufferSize );
181
182 const bool hasReportsDuringClose = destProvider->hasReportsDuringClose();
183 const double maxProgressDuringBlockWriting = hasReportsDuringClose ? 50.0 : 100.0;
184
185 std::unique_ptr<QgsRasterBlock> inputBlock;
186 while ( iter.readNextRasterPart( mBand, iterCols, iterRows, inputBlock, iterLeft, iterTop, &blockExtent, &tileCols, &tileRows, &tileLeft, &tileTop ) )
187 {
188 feedback->setProgress( maxProgressDuringBlockWriting * iter.progress( mBand, 0 ) );
189
190 if ( feedback->isCanceled() )
191 break;
192
193 auto outputBlock = std::make_unique<QgsRasterBlock>( mDataType, tileCols, tileRows );
194
195 const int tileBoundaryLeft = tileLeft - iterLeft;
196 const int tileBoundaryTop = tileTop - iterTop;
197
198 // first pass -- horizontally blurred buffer. Take CAREFUL note of the different sizes used here -- for the
199 // first pass we calculate the blur over the whole vertical height of the tile (i.e. every row), but EXCLUDE the horizontal padding
200 const int horizWidth = tileCols;
201
202 const std::size_t bufferSize = static_cast< std::size_t>( horizWidth ) * iterRows;
203 horizBuffer.resize( bufferSize );
204 horizNoData.assign( bufferSize, 0 ); // reset to 0 (false)
205
206 bool isNoData = false;
207 double *horizPtr = horizBuffer.data();
208 qint8 *noDataPtr = horizNoData.data();
209
210 for ( int r = 0; r < iterRows; ++r )
211 {
212 feedback->setProgress( maxProgressDuringBlockWriting * iter.progress( mBand, r / static_cast< double >( iterRows ) * 0.5 ) );
213
214 if ( feedback->isCanceled() )
215 break;
216
217 for ( int c = 0; c < tileCols; ++c )
218 {
219 const int inputColumn = c + tileBoundaryLeft;
220 inputBlock->valueAndNoData( r, inputColumn, isNoData );
221
222 if ( isNoData )
223 {
224 *noDataPtr++ = 1; // mark as no-data
225 horizPtr++;
226 continue;
227 }
228
229 double sumValues = 0.0;
230 double sumWeight = 0.0;
231
232 for ( int k = -radius; k <= radius; ++k )
233 {
234 const int neighborColumn = inputColumn + k;
235 if ( neighborColumn < 0 || neighborColumn >= iterCols )
236 continue;
237
238 const double val = inputBlock->valueAndNoData( r, neighborColumn, isNoData );
239 if ( !isNoData )
240 {
241 double w = kernel[k + radius];
242 sumValues += val * w;
243 sumWeight += w;
244 }
245 }
246
247 if ( sumWeight > 0.0 )
248 {
249 // must re-normalize summed values -- we may have hit edges or nodata pixels
250 *horizPtr++ = sumValues / sumWeight;
251 // leave no-data flag as zero (not no-data)
252 noDataPtr++;
253 }
254 else
255 {
256 // should theoretically not happen if center is valid, unless all weights are 0
257 *noDataPtr++ = 1; // mark as no-data
258 horizPtr++;
259 }
260 }
261 }
262
263 // second pass -- vertical blur
264 // unlike the first pass, here we ONLY need to calculate the blur for pixels which aren't block padding
265 for ( int r = 0; r < tileRows; ++r )
266 {
267 feedback->setProgress( maxProgressDuringBlockWriting * iter.progress( mBand, 0.5 + r / static_cast< double >( tileRows ) * 0.5 ) );
268
269 if ( feedback->isCanceled() )
270 break;
271
272 const int inputRow = r + tileBoundaryTop;
273 for ( int c = 0; c < tileCols; ++c )
274 {
275 const std::size_t targetPixelBufferIndex = inputRow * static_cast< std::size_t>( horizWidth ) + c;
276 if ( horizNoData[targetPixelBufferIndex] )
277 {
278 outputBlock->setValue( r, c, mNoData );
279 continue;
280 }
281
282 double sumValues = 0.0;
283 double sumWeight = 0.0;
284
285 for ( int k = -radius; k <= radius; ++k )
286 {
287 const int neighborRow = inputRow + k;
288 if ( neighborRow < 0 || neighborRow >= iterRows )
289 continue;
290
291 const std::size_t neighborIndex = static_cast< std::size_t>( neighborRow ) * horizWidth + c;
292 if ( !horizNoData[neighborIndex] )
293 {
294 double w = kernel[k + radius];
295 sumValues += horizBuffer[neighborIndex] * w;
296 sumWeight += w;
297 }
298 }
299
300 if ( sumWeight > 0.0 )
301 {
302 outputBlock->setValue( r, c, sumValues / sumWeight );
303 }
304 else
305 {
306 outputBlock->setValue( r, c, mNoData );
307 }
308 }
309 }
310
311 if ( !destProvider->writeBlock( outputBlock.get(), 1, tileLeft, tileTop ) )
312 {
313 throw QgsProcessingException( QObject::tr( "Could not write raster block: %1" ).arg( destProvider->error().summary() ) );
314 }
315 }
316 destProvider->setEditable( false );
317
318 if ( feedback && hasReportsDuringClose )
319 {
320 std::unique_ptr<QgsFeedback> scaledFeedback( QgsFeedback::createScaledFeedback( feedback, maxProgressDuringBlockWriting, 100.0 ) );
321 if ( !destProvider->closeWithProgress( scaledFeedback.get() ) )
322 {
323 if ( feedback->isCanceled() )
324 return {};
325 throw QgsProcessingException( QObject::tr( "Could not write raster dataset" ) );
326 }
327 }
328
329 QVariantMap outputs;
330 outputs.insert( u"OUTPUT"_s, outputFile );
331 return outputs;
332}
333
334
@ Advanced
Parameter is an advanced parameter which should be hidden from users by default.
Definition qgis.h:3880
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 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