Skip to content

Commit 433d04b

Browse files
authored
QgsRasterBlock::data() for efficient access to pixel data also in Python (#4009)
Also adds some unit tests and API cleanups
1 parent c80e5d1 commit 433d04b

11 files changed

Lines changed: 229 additions & 110 deletions

File tree

‎doc/api_break.dox‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,6 +1469,12 @@ QgsRaster {#qgis_api_break_3_0_QgsRaster}
14691469
- QgsRaster::contrastEnhancementLimitsFromString() has been removed, use QgsRasterMinMaxOrigin::limitsFromString()
14701470

14711471

1472+
QgsRasterBlock {#qgis_api_break_3_0_QgsRasterBlock}
1473+
--------------
1474+
1475+
- The constructor and reset() do not take no data value as the fourth argument anymore. There is new call setNoDataValue() to set it.
1476+
1477+
14721478
QgsRasterCalcNode {#qgis_api_break_3_0_QgsRasterCalcNode}
14731479
-----------------
14741480

‎python/core/raster/qgsrasterblock.sip‎

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,8 @@ class QgsRasterBlock
1212
* @param theDataType raster data type
1313
* @param theWidth width of data matrix
1414
* @param theHeight height of data matrix
15-
* @note not available in python bindings (use variant with theNoDataValue)
1615
*/
17-
// QgsRasterBlock( Qgis::DataType theDataType, int theWidth, int theHeight );
18-
19-
/** \brief Constructor which allocates data block in memory
20-
* @param theDataType raster data type
21-
* @param theWidth width of data matrix
22-
* @param theHeight height of data matrix
23-
* @param theNoDataValue the value representing no data (NULL)
24-
*/
25-
QgsRasterBlock( Qgis::DataType theDataType, int theWidth, int theHeight, double theNoDataValue );
16+
QgsRasterBlock( Qgis::DataType theDataType, int theWidth, int theHeight );
2617

2718
virtual ~QgsRasterBlock();
2819

@@ -31,18 +22,8 @@ class QgsRasterBlock
3122
* @param theWidth width of data matrix
3223
* @param theHeight height of data matrix
3324
* @return true on success
34-
* @note not available in python bindings (use variant with theNoDataValue)
3525
*/
36-
// bool reset( Qgis::DataType theDataType, int theWidth, int theHeight );
37-
38-
/** \brief Reset block
39-
* @param theDataType raster data type
40-
* @param theWidth width of data matrix
41-
* @param theHeight height of data matrix
42-
* @param theNoDataValue the value representing no data (NULL)
43-
* @return true on success
44-
*/
45-
bool reset( Qgis::DataType theDataType, int theWidth, int theHeight, double theNoDataValue );
26+
bool reset( Qgis::DataType theDataType, int theWidth, int theHeight );
4627

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

8263
/** True if the block has no data value.
83-
* @return true if the block has no data value */
64+
* @return true if the block has no data value
65+
* @see noDataValue(), setNoDataValue(), resetNoDataValue()
66+
*/
8467
bool hasNoDataValue() const;
8568

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

75+
/** Sets cell value that will be considered as "no data".
76+
* @note added in QGIS 3.0
77+
* @see noDataValue(), hasNoDataValue(), resetNoDataValue()
78+
*/
79+
void setNoDataValue( double noDataValue );
80+
81+
/** Reset no data value: if there was a no data value previously set,
82+
* it will be discarded.
83+
* @note added in QGIS 3.0
84+
* @see noDataValue(), hasNoDataValue(), setNoDataValue()
85+
*/
86+
void resetNoDataValue();
87+
9288
/** Return no data value. If the block does not have a no data value the
9389
* returned value is undefined.
94-
* @return No data value */
90+
* @return No data value
91+
* @see hasNoDataValue(), setNoDataValue(), resetNoDataValue()
92+
*/
9593
double noDataValue() const;
9694

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

198+
/** Get access to raw data.
199+
* The returned QByteArray instance is not a copy of the data: it only refers to the array
200+
* owned by the QgsRasterBlock, therefore it is only valid while the QgsRasterBlock object
201+
* still exists. Writing to the returned QByteArray will not affect the original data:
202+
* a deep copy of the data will be made and only the local copy will be modified.
203+
* @note in Python the method returns ordinary bytes object as the
204+
* @note added in QGIS 3.0
205+
*/
206+
QByteArray data() const;
207+
200208
/** \brief Get pointer to data
201209
* @param row row index
202210
* @param column column index

‎src/core/raster/qgsrasterblock.cpp‎

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -60,23 +60,6 @@ QgsRasterBlock::QgsRasterBlock( Qgis::DataType theDataType, int theWidth, int th
6060
( void )reset( mDataType, mWidth, mHeight );
6161
}
6262

63-
QgsRasterBlock::QgsRasterBlock( Qgis::DataType theDataType, int theWidth, int theHeight, double theNoDataValue )
64-
: mValid( true )
65-
, mDataType( theDataType )
66-
, mTypeSize( 0 )
67-
, mWidth( theWidth )
68-
, mHeight( theHeight )
69-
, mHasNoDataValue( true )
70-
, mNoDataValue( theNoDataValue )
71-
, mData( nullptr )
72-
, mImage( nullptr )
73-
, mNoDataBitmap( nullptr )
74-
, mNoDataBitmapWidth( 0 )
75-
, mNoDataBitmapSize( 0 )
76-
{
77-
( void )reset( mDataType, mWidth, mHeight, mNoDataValue );
78-
}
79-
8063
QgsRasterBlock::~QgsRasterBlock()
8164
{
8265
QgsDebugMsgLevel( QString( "mData = %1" ).arg( reinterpret_cast< ulong >( mData ) ), 4 );
@@ -88,18 +71,6 @@ QgsRasterBlock::~QgsRasterBlock()
8871
bool QgsRasterBlock::reset( Qgis::DataType theDataType, int theWidth, int theHeight )
8972
{
9073
QgsDebugMsgLevel( QString( "theWidth= %1 theHeight = %2 theDataType = %3" ).arg( theWidth ).arg( theHeight ).arg( theDataType ), 4 );
91-
if ( !reset( theDataType, theWidth, theHeight, std::numeric_limits<double>::quiet_NaN() ) )
92-
{
93-
return false;
94-
}
95-
mHasNoDataValue = false;
96-
// the mNoDataBitmap is created only if necessary (usually, it is not) in setIsNoData()
97-
return true;
98-
}
99-
100-
bool QgsRasterBlock::reset( Qgis::DataType theDataType, int theWidth, int theHeight, double theNoDataValue )
101-
{
102-
QgsDebugMsgLevel( QString( "theWidth= %1 theHeight = %2 theDataType = %3 theNoDataValue = %4" ).arg( theWidth ).arg( theHeight ).arg( theDataType ).arg( theNoDataValue ), 4 );
10374

10475
qgsFree( mData );
10576
mData = nullptr;
@@ -144,8 +115,6 @@ bool QgsRasterBlock::reset( Qgis::DataType theDataType, int theWidth, int theHei
144115
mTypeSize = QgsRasterBlock::typeSize( mDataType );
145116
mWidth = theWidth;
146117
mHeight = theHeight;
147-
mHasNoDataValue = true;
148-
mNoDataValue = theNoDataValue;
149118
QgsDebugMsgLevel( QString( "mWidth= %1 mHeight = %2 mDataType = %3 mData = %4 mImage = %5" ).arg( mWidth ).arg( mHeight ).arg( mDataType )
150119
.arg( reinterpret_cast< ulong >( mData ) ).arg( reinterpret_cast< ulong >( mImage ) ), 4 );
151120
return true;
@@ -278,6 +247,18 @@ bool QgsRasterBlock::hasNoData() const
278247
return mHasNoDataValue || mNoDataBitmap;
279248
}
280249

250+
void QgsRasterBlock::setNoDataValue( double noDataValue )
251+
{
252+
mHasNoDataValue = true;
253+
mNoDataValue = noDataValue;
254+
}
255+
256+
void QgsRasterBlock::resetNoDataValue()
257+
{
258+
mHasNoDataValue = false;
259+
mNoDataValue = std::numeric_limits<double>::quiet_NaN();
260+
}
261+
281262
bool QgsRasterBlock::isNoDataValue( double value, double noDataValue )
282263
{
283264
// TODO: optimize no data value test by memcmp()
@@ -662,6 +643,16 @@ void QgsRasterBlock::setIsData( qgssize index )
662643
mNoDataBitmap[byte] = mNoDataBitmap[byte] & ~nodata;
663644
}
664645

646+
QByteArray QgsRasterBlock::data() const
647+
{
648+
if ( mData )
649+
return QByteArray::fromRawData( static_cast<const char*>( mData ), typeSize( mDataType ) * mWidth * mHeight );
650+
else if ( mImage && mImage->constBits() )
651+
return QByteArray::fromRawData( reinterpret_cast<const char*>( mImage->constBits() ), mImage->byteCount() );
652+
else
653+
return QByteArray();
654+
}
655+
665656
char * QgsRasterBlock::bits( qgssize index )
666657
{
667658
// Not testing type to avoid too much overhead because this method is called per pixel

‎src/core/raster/qgsrasterblock.h‎

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,38 +40,19 @@ class CORE_EXPORT QgsRasterBlock
4040
* @param theDataType raster data type
4141
* @param theWidth width of data matrix
4242
* @param theHeight height of data matrix
43-
* @note not available in python bindings (use variant with theNoDataValue)
4443
*/
4544
QgsRasterBlock( Qgis::DataType theDataType, int theWidth, int theHeight );
4645

47-
/** \brief Constructor which allocates data block in memory
48-
* @param theDataType raster data type
49-
* @param theWidth width of data matrix
50-
* @param theHeight height of data matrix
51-
* @param theNoDataValue the value representing no data (NULL)
52-
*/
53-
QgsRasterBlock( Qgis::DataType theDataType, int theWidth, int theHeight, double theNoDataValue );
54-
5546
virtual ~QgsRasterBlock();
5647

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

66-
/** \brief Reset block
67-
* @param theDataType raster data type
68-
* @param theWidth width of data matrix
69-
* @param theHeight height of data matrix
70-
* @param theNoDataValue the value representing no data (NULL)
71-
* @return true on success
72-
*/
73-
bool reset( Qgis::DataType theDataType, int theWidth, int theHeight, double theNoDataValue );
74-
7556
// TODO: consider if use isValid() at all, isEmpty() should be sufficient
7657
// and works also if block is valid but empty - difference between valid and empty?
7758

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

147128
/** True if the block has no data value.
148-
* @return true if the block has no data value */
129+
* @return true if the block has no data value
130+
* @see noDataValue(), setNoDataValue(), resetNoDataValue()
131+
*/
149132
bool hasNoDataValue() const { return mHasNoDataValue; }
150133

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

140+
/** Sets cell value that will be considered as "no data".
141+
* @note added in QGIS 3.0
142+
* @see noDataValue(), hasNoDataValue(), resetNoDataValue()
143+
*/
144+
void setNoDataValue( double noDataValue );
145+
146+
/** Reset no data value: if there was a no data value previously set,
147+
* it will be discarded.
148+
* @note added in QGIS 3.0
149+
* @see noDataValue(), hasNoDataValue(), setNoDataValue()
150+
*/
151+
void resetNoDataValue();
152+
157153
/** Return no data value. If the block does not have a no data value the
158154
* returned value is undefined.
159-
* @return No data value */
155+
* @return No data value
156+
* @see hasNoDataValue(), setNoDataValue(), resetNoDataValue()
157+
*/
160158
double noDataValue() const { return mNoDataValue; }
161159

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

263+
/** Get access to raw data.
264+
* The returned QByteArray instance is not a copy of the data: it only refers to the array
265+
* owned by the QgsRasterBlock, therefore it is only valid while the QgsRasterBlock object
266+
* still exists. Writing to the returned QByteArray will not affect the original data:
267+
* a deep copy of the data will be made and only the local copy will be modified.
268+
* @note in Python the method returns ordinary bytes object as the
269+
* @note added in QGIS 3.0
270+
*/
271+
QByteArray data() const;
272+
265273
/** \brief Get pointer to data
266274
* @param row row index
267275
* @param column column index

‎src/core/raster/qgsrasterdataprovider.cpp‎

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,10 @@ QgsRasterBlock * QgsRasterDataProvider::block( int theBandNo, QgsRectangle cons
4848
QgsDebugMsgLevel( QString( "theBandNo = %1 theWidth = %2 theHeight = %3" ).arg( theBandNo ).arg( theWidth ).arg( theHeight ), 4 );
4949
QgsDebugMsgLevel( QString( "theExtent = %1" ).arg( theExtent.toString() ), 4 );
5050

51-
QgsRasterBlock *block;
51+
QgsRasterBlock *block = new QgsRasterBlock( dataType( theBandNo ), theWidth, theHeight );
5252
if ( sourceHasNoDataValue( theBandNo ) && useSourceNoDataValue( theBandNo ) )
5353
{
54-
block = new QgsRasterBlock( dataType( theBandNo ), theWidth, theHeight, sourceNoDataValue( theBandNo ) );
55-
}
56-
else
57-
{
58-
block = new QgsRasterBlock( dataType( theBandNo ), theWidth, theHeight );
54+
block->setNoDataValue( sourceNoDataValue( theBandNo ) );
5955
}
6056

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

148-
QgsRasterBlock *tmpBlock;
144+
QgsRasterBlock *tmpBlock = new QgsRasterBlock( dataType( theBandNo ), tmpWidth, tmpHeight );
149145
if ( sourceHasNoDataValue( theBandNo ) && useSourceNoDataValue( theBandNo ) )
150146
{
151-
tmpBlock = new QgsRasterBlock( dataType( theBandNo ), tmpWidth, tmpHeight, sourceNoDataValue( theBandNo ) );
152-
}
153-
else
154-
{
155-
tmpBlock = new QgsRasterBlock( dataType( theBandNo ), tmpWidth, tmpHeight );
147+
tmpBlock->setNoDataValue( sourceNoDataValue( theBandNo ) );
156148
}
157149

158150
readBlock( theBandNo, tmpExtent, tmpWidth, tmpHeight, tmpBlock->bits(), feedback );

‎src/core/raster/qgsrasternuller.cpp‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ QgsRasterBlock * QgsRasterNuller::block( int bandNo, QgsRectangle const & exten
8585
return inputBlock;
8686
}
8787

88-
QgsRasterBlock *outputBlock = nullptr;
89-
88+
QgsRasterBlock *outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height );
9089
if ( mHasOutputNoData.value( bandNo - 1 ) || inputBlock->hasNoDataValue() )
9190
{
9291
double noDataValue;
@@ -98,11 +97,7 @@ QgsRasterBlock * QgsRasterNuller::block( int bandNo, QgsRectangle const & exten
9897
{
9998
noDataValue = inputBlock->noDataValue();
10099
}
101-
outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height, noDataValue );
102-
}
103-
else
104-
{
105-
outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height );
100+
outputBlock->setNoDataValue( noDataValue );
106101
}
107102

108103
for ( int i = 0; i < height; i++ )

‎src/core/raster/qgsrasterprojector.cpp‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -781,14 +781,10 @@ QgsRasterBlock * QgsRasterProjector::block( int bandNo, QgsRectangle const & ex
781781

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

784-
QgsRasterBlock *outputBlock;
784+
QgsRasterBlock *outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height );
785785
if ( inputBlock->hasNoDataValue() )
786786
{
787-
outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height, inputBlock->noDataValue() );
788-
}
789-
else
790-
{
791-
outputBlock = new QgsRasterBlock( inputBlock->dataType(), width, height );
787+
outputBlock->setNoDataValue( inputBlock->noDataValue() );
792788
}
793789
if ( !outputBlock->isValid() )
794790
{

‎src/providers/gdal/qgsgdalprovider.cpp‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -389,15 +389,10 @@ QImage* QgsGdalProvider::draw( QgsRectangle const & viewExtent, int pixelWidth,
389389

390390
QgsRasterBlock* QgsGdalProvider::block( int theBandNo, const QgsRectangle &theExtent, int theWidth, int theHeight, QgsRasterBlockFeedback* feedback )
391391
{
392-
//QgsRasterBlock *block = new QgsRasterBlock( dataType( theBandNo ), theWidth, theHeight, noDataValue( theBandNo ) );
393-
QgsRasterBlock *block;
392+
QgsRasterBlock *block = new QgsRasterBlock( dataType( theBandNo ), theWidth, theHeight );
394393
if ( sourceHasNoDataValue( theBandNo ) && useSourceNoDataValue( theBandNo ) )
395394
{
396-
block = new QgsRasterBlock( dataType( theBandNo ), theWidth, theHeight, sourceNoDataValue( theBandNo ) );
397-
}
398-
else
399-
{
400-
block = new QgsRasterBlock( dataType( theBandNo ), theWidth, theHeight );
395+
block->setNoDataValue( sourceNoDataValue( theBandNo ) );
401396
}
402397

403398
if ( block->isEmpty() )

0 commit comments

Comments
 (0)