Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] add native Round raster algorithm #35694

Merged
merged 10 commits into from Apr 12, 2020

Conversation

root676
Copy link
Contributor

@root676 root676 commented Apr 9, 2020

Description

This PR proposes a new processing algorithm to round Raster values (thanks @nyalldawson, your algorithm list triggered me ;) ). The algorithm is a reimplementation of the FME counterpart RasterCellValueRounder.

The main functionality of the algorithm is either standard up/nearest/down-rounding of floating point cell values or rounding to powers of a user specified base n. By exposing the base as parameter to the user, the algorithm now is even a bit more versatile than the FME counterpart and allows for some interesting raster generalization (see example). The algorithm can be used with rasters of all types (even byte/integer rasters when rounding to powers of n) and outputs the same raster data type, a standard floating point rounding of an integer raster will copy it and raise a warning. A detailed description of the parameters interplay can be found in the algorithm helpstring.
The PR also contains a test of the algorithm.

Example:
input
Input Raster file
generalized
Output raster rounded to multiples of 10

Please feel free to review/comment!

@roya0045
Copy link
Contributor

roya0045 commented Apr 9, 2020

Isn't that smooth?

And also looking at the code, this made me think.
Isn't there a way to convert the images to an array and then reproject them? Having a complete raster as an array would be far better to control like in numpy. Though this might not be easy to do with the type of data we are dealing with.

@github-actions github-actions bot added this to the 3.14.0 milestone Apr 9, 2020
Copy link
Collaborator

@nyalldawson nyalldawson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition! You've nailed the implementation too (like usual 😉 )

@@ -116,6 +116,7 @@
<file>themes/default/algorithms/mAlgorithmRandomPointsWithinPolygon.svg</file>
<file>themes/default/algorithms/mAlgorithmRandomPointsWithinExtent.svg</file>
<file>themes/default/algorithms/mAlgorithmRegularPoints.svg</file>
<file>themes/default/algorithms/mAlgorithmRoundRastervalues.svg</file>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


QString QgsRoundRasterValuesAlgorithm::group() const
{
return QObject::tr( "Raster tools" );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure Raster Tools is the right group for this. I think it fits better in the current Raster Analysis group, but ideally we'd split that group into purely analysis tools vs "manipulation" tools.

Copy link
Contributor Author

@root676 root676 Apr 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. One question on adding new processing algorithm groups between major releases: Is this considered as an API break? I plan to add more raster creation algorithms and would put them in one new group. Is it problematic to regroup algorithms/add new groups between major releases?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not an API break, I think it's perfectly acceptable for a minor release!

addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT" ), QStringLiteral( "Input raster" ) ) );
addParameter( new QgsProcessingParameterBand( QStringLiteral( "BAND" ), QObject::tr( "Band number" ), 1, QStringLiteral( "INPUT" ) ) );
addParameter( new QgsProcessingParameterEnum( QStringLiteral( "ROUNDING_DIRECTION" ), QObject::tr( "Rounding direction" ), QStringList() << QObject::tr( "Round up" ) << QObject::tr( "Round to nearest" ) << QObject::tr( "Round down" ), false, 1 ) );
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "DECIMAL_PLACES" ), QObject::tr( "Number of decimals places (use negative values to round cell values to a multiple of a base n, see advanced parameters)" ), QgsProcessingParameterNumber::Integer, 2 ) );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This description is very long compared with the usual parameter description. I'd shorten to just "number of decimal places" and leave the full explanation for the help string.

I keep thinking we need api to support extended descriptions for parameters, which we could show in a tooltip...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - this would be very nice!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


QString QgsRoundRasterValuesAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm rounds the cell values of a rasterdataset according to the specified number of decimals.\n "
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return QObject::tr( "This algorithm rounds the cell values of a rasterdataset according to the specified number of decimals.\n "
return QObject::tr( "This algorithm rounds the cell values of a raster dataset according to the specified number of decimals.\n "

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

"Alternatively, a negative number of decimal places may be used to round values to powers of a base n "
"(specified in the advanced parameter Base n). For example, with a Base value n of 10 and Decimal places of -1 "
"the algorithm rounds cell values to multiples of 10, -2 rounds to multiples of 100, and so on. Arbitrary base values "
"may be chosen, the algorithm applies the same multiplicative principle.Rounding cell values to multiples of "
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"may be chosen, the algorithm applies the same multiplicative principle.Rounding cell values to multiples of "
"may be chosen, the algorithm applies the same multiplicative principle. Rounding cell values to multiples of "

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

double roundUpBaseN( double &value, int &multipleOfBaseN );
double roundDownBaseN( double &value, int &multipleOfBaseN );

QgsRasterLayer *mInputRaster;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better not to store this as a member -- it potentially risks someone using it from processAlgorithm later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean the QgsRasterLayer member variable? If so, it is fixed.

break;
for ( int column = 0; column < iterCols; column++ )
{
if ( analysisRasterBlock->isNoData( row, column ) )
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling valueAndNoData once here instead of separately calling isNoData and the value is more efficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

double QgsRoundRasterValuesAlgorithm::roundNearest( double &value, int &decimals )
{
double m = ( value < 0.0 ) ? -1.0 : 1.0;
double scaleFactor = std::pow( 10.0, decimals );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This scalefactor is getting calculated once per pixel -- it'd be preferable to calculate it once in processAlgorithm and pass it to this function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I think you misunderstood me -- it's "scale factor" which is a constant value and should only be calculated once

@nyalldawson
Copy link
Collaborator

@roya0045

Isn't there a way to convert the images to an array

That's exactly what QgsRasterBlock is

@nyalldawson nyalldawson added Feature Processing Relating to QGIS Processing framework or individual Processing algorithms labels Apr 10, 2020
@root676
Copy link
Contributor Author

root676 commented Apr 10, 2020

@nyalldawson thank you for the fast review :)

@roya0045
Copy link
Contributor

@nyalldawson I just find it strange that values must be handled and set one at the time instead of using broadcasting. Though it still might me more efficient in c++ than numpy.

Copy link
Collaborator

@nyalldawson nyalldawson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nearly there!

addParameter( new QgsProcessingParameterEnum( QStringLiteral( "ROUNDING_DIRECTION" ), QObject::tr( "Rounding direction" ), QStringList() << QObject::tr( "Round up" ) << QObject::tr( "Round to nearest" ) << QObject::tr( "Round down" ), false, 1 ) );
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "DECIMAL_PLACES" ), QObject::tr( "Number of decimals places" ), QgsProcessingParameterNumber::Integer, 2 ) );
addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Output raster" ) ) );
std::unique_ptr< QgsProcessingParameterDefinition > baseParameter = qgis::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "BASE_N" ), QObject::tr( "Base n (provides the base rounding raster values near/up/down to multiples of n when Decimal parameter is negative)" ), QgsProcessingParameterNumber::Integer, 10, true, 1 );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better shorten this one too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I condensed the parameter description a little.

double QgsRoundRasterValuesAlgorithm::roundNearest( double &value, int &decimals )
{
double m = ( value < 0.0 ) ? -1.0 : 1.0;
double scaleFactor = std::pow( 10.0, decimals );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I think you misunderstood me -- it's "scale factor" which is a constant value and should only be calculated once

return ( std::round( value * m * scaleFactor ) / scaleFactor ) * m;
}

double QgsRoundRasterValuesAlgorithm::roundUp( double &value, double &m, int &decimals )
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These arguments should be passed by value, not reference (by reference is slower then by value for small trivially copied types like int and double)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@nyalldawson
Copy link
Collaborator

Perfect!

@nyalldawson nyalldawson merged commit bea2834 into qgis:master Apr 12, 2020
@root676 root676 deleted the processing_roundRaster branch April 13, 2020 08:39
@timlinux timlinux added Changelog Items that are queued to appear in the visual changelog - remove after harvesting and removed Changelog Items that are queued to appear in the visual changelog - remove after harvesting labels May 22, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature Processing Relating to QGIS Processing framework or individual Processing algorithms
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants