Skip to content

Commit

Permalink
QgsRasterBlock::data() for efficient access to pixel data also in Pyt…
Browse files Browse the repository at this point in the history
…hon (#4009)

Also adds some unit tests and API cleanups
  • Loading branch information
wonder-sk committed Jan 18, 2017
1 parent c80e5d1 commit 433d04b
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 110 deletions.
6 changes: 6 additions & 0 deletions doc/api_break.dox
Expand Up @@ -1469,6 +1469,12 @@ QgsRaster {#qgis_api_break_3_0_QgsRaster}
- QgsRaster::contrastEnhancementLimitsFromString() has been removed, use QgsRasterMinMaxOrigin::limitsFromString()


QgsRasterBlock {#qgis_api_break_3_0_QgsRasterBlock}
--------------

- The constructor and reset() do not take no data value as the fourth argument anymore. There is new call setNoDataValue() to set it.


QgsRasterCalcNode {#qgis_api_break_3_0_QgsRasterCalcNode}
-----------------

Expand Down
54 changes: 31 additions & 23 deletions python/core/raster/qgsrasterblock.sip
Expand Up @@ -12,17 +12,8 @@ class QgsRasterBlock
* @param theDataType raster data type
* @param theWidth width of data matrix
* @param theHeight height of data matrix
* @note not available in python bindings (use variant with theNoDataValue)
*/
// QgsRasterBlock( Qgis::DataType theDataType, int theWidth, int theHeight );

/** \brief Constructor which allocates data block in memory
* @param theDataType raster data type
* @param theWidth width of data matrix
* @param theHeight height of data matrix
* @param theNoDataValue the value representing no data (NULL)
*/
QgsRasterBlock( Qgis::DataType theDataType, int theWidth, int theHeight, double theNoDataValue );
QgsRasterBlock( Qgis::DataType theDataType, int theWidth, int theHeight );

virtual ~QgsRasterBlock();

Expand All @@ -31,18 +22,8 @@ class QgsRasterBlock
* @param theWidth width of data matrix
* @param theHeight height of data matrix
* @return true on success
* @note not available in python bindings (use variant with theNoDataValue)
*/
// bool reset( Qgis::DataType theDataType, int theWidth, int theHeight );

/** \brief Reset block
* @param theDataType raster data type
* @param theWidth width of data matrix
* @param theHeight height of data matrix
* @param theNoDataValue the value representing no data (NULL)
* @return true on success
*/
bool reset( Qgis::DataType theDataType, int theWidth, int theHeight, double theNoDataValue );
bool reset( Qgis::DataType theDataType, int theWidth, int theHeight );

// TODO: consider if use isValid() at all, isEmpty() should be sufficient
// and works also if block is valid but empty - difference between valid and empty?
Expand Down Expand Up @@ -80,7 +61,9 @@ class QgsRasterBlock
static Qgis::DataType typeWithNoDataValue( Qgis::DataType dataType, double *noDataValue );

/** True if the block has no data value.
* @return true if the block has no data value */
* @return true if the block has no data value
* @see noDataValue(), setNoDataValue(), resetNoDataValue()
*/
bool hasNoDataValue() const;

/** Returns true if the block may contain no data. It does not guarantee
Expand All @@ -89,9 +72,24 @@ class QgsRasterBlock
* @return true if the block may contain no data */
bool hasNoData() const;

/** Sets cell value that will be considered as "no data".
* @note added in QGIS 3.0
* @see noDataValue(), hasNoDataValue(), resetNoDataValue()
*/
void setNoDataValue( double noDataValue );

/** Reset no data value: if there was a no data value previously set,
* it will be discarded.
* @note added in QGIS 3.0
* @see noDataValue(), hasNoDataValue(), setNoDataValue()
*/
void resetNoDataValue();

/** Return no data value. If the block does not have a no data value the
* returned value is undefined.
* @return No data value */
* @return No data value
* @see hasNoDataValue(), setNoDataValue(), resetNoDataValue()
*/
double noDataValue() const;

/** Get byte array representing a value.
Expand Down Expand Up @@ -197,6 +195,16 @@ class QgsRasterBlock
* @note added in QGIS 2.10 */
void setIsData( qgssize index );

/** Get access to raw data.
* The returned QByteArray instance is not a copy of the data: it only refers to the array
* owned by the QgsRasterBlock, therefore it is only valid while the QgsRasterBlock object
* still exists. Writing to the returned QByteArray will not affect the original data:
* a deep copy of the data will be made and only the local copy will be modified.
* @note in Python the method returns ordinary bytes object as the
* @note added in QGIS 3.0
*/
QByteArray data() const;

/** \brief Get pointer to data
* @param row row index
* @param column column index
Expand Down
53 changes: 22 additions & 31 deletions src/core/raster/qgsrasterblock.cpp
Expand Up @@ -60,23 +60,6 @@ QgsRasterBlock::QgsRasterBlock( Qgis::DataType theDataType, int theWidth, int th
( void )reset( mDataType, mWidth, mHeight );
}

QgsRasterBlock::QgsRasterBlock( Qgis::DataType theDataType, int theWidth, int theHeight, double theNoDataValue )
: mValid( true )
, mDataType( theDataType )
, mTypeSize( 0 )
, mWidth( theWidth )
, mHeight( theHeight )
, mHasNoDataValue( true )
, mNoDataValue( theNoDataValue )
, mData( nullptr )
, mImage( nullptr )
, mNoDataBitmap( nullptr )
, mNoDataBitmapWidth( 0 )
, mNoDataBitmapSize( 0 )
{
( void )reset( mDataType, mWidth, mHeight, mNoDataValue );
}

QgsRasterBlock::~QgsRasterBlock()
{
QgsDebugMsgLevel( QString( "mData = %1" ).arg( reinterpret_cast< ulong >( mData ) ), 4 );
Expand All @@ -88,18 +71,6 @@ QgsRasterBlock::~QgsRasterBlock()
bool QgsRasterBlock::reset( Qgis::DataType theDataType, int theWidth, int theHeight )
{
QgsDebugMsgLevel( QString( "theWidth= %1 theHeight = %2 theDataType = %3" ).arg( theWidth ).arg( theHeight ).arg( theDataType ), 4 );
if ( !reset( theDataType, theWidth, theHeight, std::numeric_limits<double>::quiet_NaN() ) )
{
return false;
}
mHasNoDataValue = false;
// the mNoDataBitmap is created only if necessary (usually, it is not) in setIsNoData()
return true;
}

bool QgsRasterBlock::reset( Qgis::DataType theDataType, int theWidth, int theHeight, double theNoDataValue )
{
QgsDebugMsgLevel( QString( "theWidth= %1 theHeight = %2 theDataType = %3 theNoDataValue = %4" ).arg( theWidth ).arg( theHeight ).arg( theDataType ).arg( theNoDataValue ), 4 );

qgsFree( mData );
mData = nullptr;
Expand Down Expand Up @@ -144,8 +115,6 @@ bool QgsRasterBlock::reset( Qgis::DataType theDataType, int theWidth, int theHei
mTypeSize = QgsRasterBlock::typeSize( mDataType );
mWidth = theWidth;
mHeight = theHeight;
mHasNoDataValue = true;
mNoDataValue = theNoDataValue;
QgsDebugMsgLevel( QString( "mWidth= %1 mHeight = %2 mDataType = %3 mData = %4 mImage = %5" ).arg( mWidth ).arg( mHeight ).arg( mDataType )
.arg( reinterpret_cast< ulong >( mData ) ).arg( reinterpret_cast< ulong >( mImage ) ), 4 );
return true;
Expand Down Expand Up @@ -278,6 +247,18 @@ bool QgsRasterBlock::hasNoData() const
return mHasNoDataValue || mNoDataBitmap;
}

void QgsRasterBlock::setNoDataValue( double noDataValue )
{
mHasNoDataValue = true;
mNoDataValue = noDataValue;
}

void QgsRasterBlock::resetNoDataValue()
{
mHasNoDataValue = false;
mNoDataValue = std::numeric_limits<double>::quiet_NaN();
}

bool QgsRasterBlock::isNoDataValue( double value, double noDataValue )
{
// TODO: optimize no data value test by memcmp()
Expand Down Expand Up @@ -662,6 +643,16 @@ void QgsRasterBlock::setIsData( qgssize index )
mNoDataBitmap[byte] = mNoDataBitmap[byte] & ~nodata;
}

QByteArray QgsRasterBlock::data() const
{
if ( mData )
return QByteArray::fromRawData( static_cast<const char*>( mData ), typeSize( mDataType ) * mWidth * mHeight );
else if ( mImage && mImage->constBits() )
return QByteArray::fromRawData( reinterpret_cast<const char*>( mImage->constBits() ), mImage->byteCount() );
else
return QByteArray();
}

char * QgsRasterBlock::bits( qgssize index )
{
// Not testing type to avoid too much overhead because this method is called per pixel
Expand Down
50 changes: 29 additions & 21 deletions src/core/raster/qgsrasterblock.h
Expand Up @@ -40,38 +40,19 @@ class CORE_EXPORT QgsRasterBlock
* @param theDataType raster data type
* @param theWidth width of data matrix
* @param theHeight height of data matrix
* @note not available in python bindings (use variant with theNoDataValue)
*/
QgsRasterBlock( Qgis::DataType theDataType, int theWidth, int theHeight );

/** \brief Constructor which allocates data block in memory
* @param theDataType raster data type
* @param theWidth width of data matrix
* @param theHeight height of data matrix
* @param theNoDataValue the value representing no data (NULL)
*/
QgsRasterBlock( Qgis::DataType theDataType, int theWidth, int theHeight, double theNoDataValue );

virtual ~QgsRasterBlock();

/** \brief Reset block
* @param theDataType raster data type
* @param theWidth width of data matrix
* @param theHeight height of data matrix
* @return true on success
* @note not available in python bindings (use variant with theNoDataValue)
*/
bool reset( Qgis::DataType theDataType, int theWidth, int theHeight );

/** \brief Reset block
* @param theDataType raster data type
* @param theWidth width of data matrix
* @param theHeight height of data matrix
* @param theNoDataValue the value representing no data (NULL)
* @return true on success
*/
bool reset( Qgis::DataType theDataType, int theWidth, int theHeight, double theNoDataValue );

// TODO: consider if use isValid() at all, isEmpty() should be sufficient
// and works also if block is valid but empty - difference between valid and empty?

Expand Down Expand Up @@ -145,7 +126,9 @@ class CORE_EXPORT QgsRasterBlock
static Qgis::DataType typeWithNoDataValue( Qgis::DataType dataType, double *noDataValue );

/** True if the block has no data value.
* @return true if the block has no data value */
* @return true if the block has no data value
* @see noDataValue(), setNoDataValue(), resetNoDataValue()
*/
bool hasNoDataValue() const { return mHasNoDataValue; }

/** Returns true if the block may contain no data. It does not guarantee
Expand All @@ -154,9 +137,24 @@ class CORE_EXPORT QgsRasterBlock
* @return true if the block may contain no data */
bool hasNoData() const;

/** Sets cell value that will be considered as "no data".
* @note added in QGIS 3.0
* @see noDataValue(), hasNoDataValue(), resetNoDataValue()
*/
void setNoDataValue( double noDataValue );

/** Reset no data value: if there was a no data value previously set,
* it will be discarded.
* @note added in QGIS 3.0
* @see noDataValue(), hasNoDataValue(), setNoDataValue()
*/
void resetNoDataValue();

/** Return no data value. If the block does not have a no data value the
* returned value is undefined.
* @return No data value */
* @return No data value
* @see hasNoDataValue(), setNoDataValue(), resetNoDataValue()
*/
double noDataValue() const { return mNoDataValue; }

/** Get byte array representing a value.
Expand Down Expand Up @@ -262,6 +260,16 @@ class CORE_EXPORT QgsRasterBlock
* @note added in QGIS 2.10 */
void setIsData( qgssize index );

/** Get access to raw data.
* The returned QByteArray instance is not a copy of the data: it only refers to the array
* owned by the QgsRasterBlock, therefore it is only valid while the QgsRasterBlock object
* still exists. Writing to the returned QByteArray will not affect the original data:
* a deep copy of the data will be made and only the local copy will be modified.
* @note in Python the method returns ordinary bytes object as the
* @note added in QGIS 3.0
*/
QByteArray data() const;

/** \brief Get pointer to data
* @param row row index
* @param column column index
Expand Down
16 changes: 4 additions & 12 deletions src/core/raster/qgsrasterdataprovider.cpp
Expand Up @@ -48,14 +48,10 @@ QgsRasterBlock * QgsRasterDataProvider::block( int theBandNo, QgsRectangle cons
QgsDebugMsgLevel( QString( "theBandNo = %1 theWidth = %2 theHeight = %3" ).arg( theBandNo ).arg( theWidth ).arg( theHeight ), 4 );
QgsDebugMsgLevel( QString( "theExtent = %1" ).arg( theExtent.toString() ), 4 );

QgsRasterBlock *block;
QgsRasterBlock *block = new QgsRasterBlock( dataType( theBandNo ), theWidth, theHeight );
if ( sourceHasNoDataValue( theBandNo ) && useSourceNoDataValue( theBandNo ) )
{
block = new QgsRasterBlock( dataType( theBandNo ), theWidth, theHeight, sourceNoDataValue( theBandNo ) );
}
else
{
block = new QgsRasterBlock( dataType( theBandNo ), theWidth, theHeight );
block->setNoDataValue( sourceNoDataValue( theBandNo ) );
}

if ( block->isEmpty() )
Expand Down Expand Up @@ -145,14 +141,10 @@ QgsRasterBlock * QgsRasterDataProvider::block( int theBandNo, QgsRectangle cons
QgsDebugMsgLevel( QString( "Reading smaller block tmpWidth = %1 theHeight = %2" ).arg( tmpWidth ).arg( tmpHeight ), 4 );
QgsDebugMsgLevel( QString( "tmpExtent = %1" ).arg( tmpExtent.toString() ), 4 );

QgsRasterBlock *tmpBlock;
QgsRasterBlock *tmpBlock = new QgsRasterBlock( dataType( theBandNo ), tmpWidth, tmpHeight );
if ( sourceHasNoDataValue( theBandNo ) && useSourceNoDataValue( theBandNo ) )
{
tmpBlock = new QgsRasterBlock( dataType( theBandNo ), tmpWidth, tmpHeight, sourceNoDataValue( theBandNo ) );
}
else
{
tmpBlock = new QgsRasterBlock( dataType( theBandNo ), tmpWidth, tmpHeight );
tmpBlock->setNoDataValue( sourceNoDataValue( theBandNo ) );
}

readBlock( theBandNo, tmpExtent, tmpWidth, tmpHeight, tmpBlock->bits(), feedback );
Expand Down
9 changes: 2 additions & 7 deletions src/core/raster/qgsrasternuller.cpp
Expand Up @@ -85,8 +85,7 @@ QgsRasterBlock * QgsRasterNuller::block( int bandNo, QgsRectangle const & exten
return inputBlock;
}

QgsRasterBlock *outputBlock = nullptr;

QgsRasterBlock *outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height );
if ( mHasOutputNoData.value( bandNo - 1 ) || inputBlock->hasNoDataValue() )
{
double noDataValue;
Expand All @@ -98,11 +97,7 @@ QgsRasterBlock * QgsRasterNuller::block( int bandNo, QgsRectangle const & exten
{
noDataValue = inputBlock->noDataValue();
}
outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height, noDataValue );
}
else
{
outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height );
outputBlock->setNoDataValue( noDataValue );
}

for ( int i = 0; i < height; i++ )
Expand Down
8 changes: 2 additions & 6 deletions src/core/raster/qgsrasterprojector.cpp
Expand Up @@ -781,14 +781,10 @@ QgsRasterBlock * QgsRasterProjector::block( int bandNo, QgsRectangle const & ex

qgssize pixelSize = QgsRasterBlock::typeSize( mInput->dataType( bandNo ) );

QgsRasterBlock *outputBlock;
QgsRasterBlock *outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height );
if ( inputBlock->hasNoDataValue() )
{
outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height, inputBlock->noDataValue() );
}
else
{
outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height );
outputBlock->setNoDataValue( inputBlock->noDataValue() );
}
if ( !outputBlock->isValid() )
{
Expand Down
9 changes: 2 additions & 7 deletions src/providers/gdal/qgsgdalprovider.cpp
Expand Up @@ -389,15 +389,10 @@ QImage* QgsGdalProvider::draw( QgsRectangle const & viewExtent, int pixelWidth,

QgsRasterBlock* QgsGdalProvider::block( int theBandNo, const QgsRectangle &theExtent, int theWidth, int theHeight, QgsRasterBlockFeedback* feedback )
{
//QgsRasterBlock *block = new QgsRasterBlock( dataType( theBandNo ), theWidth, theHeight, noDataValue( theBandNo ) );
QgsRasterBlock *block;
QgsRasterBlock *block = new QgsRasterBlock( dataType( theBandNo ), theWidth, theHeight );
if ( sourceHasNoDataValue( theBandNo ) && useSourceNoDataValue( theBandNo ) )
{
block = new QgsRasterBlock( dataType( theBandNo ), theWidth, theHeight, sourceNoDataValue( theBandNo ) );
}
else
{
block = new QgsRasterBlock( dataType( theBandNo ), theWidth, theHeight );
block->setNoDataValue( sourceNoDataValue( theBandNo ) );
}

if ( block->isEmpty() )
Expand Down

0 comments on commit 433d04b

Please sign in to comment.