Skip to content

Commit

Permalink
Add GDALRasterComputeMinMaxLocation / GDALRasterBand::ComputeMinMaxLo…
Browse files Browse the repository at this point in the history
…cation, and map it to SWIG

and add a gdal_minmax_location.py script:

```
$ python ../swig/python/gdal-utils/osgeo_utils/samples/gdal_minmax_location.py byte.tif
Minimum=74.0 at (col,line)=(9,17), (X,Y)_georef=(441290.0,3750270.0), (long,lat)_WGS84=(-117.6358076,33.8929309)
Maximum=255.0 at (col,line)=(2,18), (X,Y)_georef=(440870.0,3750210.0), (long,lat)_WGS84=(-117.6403456,33.8923662)
```
  • Loading branch information
rouault committed Nov 3, 2024
1 parent f23b059 commit 0df47a0
Show file tree
Hide file tree
Showing 9 changed files with 533 additions and 0 deletions.
76 changes: 76 additions & 0 deletions autotest/cpp/test_gdal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4777,4 +4777,80 @@ TEST_F(test_gdal, ReadRaster)
}
}

// Test GDALComputeRasterMinMaxLocation
TEST_F(test_gdal, GDALComputeRasterMinMaxLocation)
{
GDALDatasetH hDS = GDALOpen(GCORE_DATA_DIR "byte.tif", GA_ReadOnly);
ASSERT_NE(hDS, nullptr);
GDALRasterBandH hBand = GDALGetRasterBand(hDS, 1);
double dfMin = 0;
double dfMax = 0;
int nMinX = -1;
int nMinY = -1;
int nMaxX = -1;
int nMaxY = -1;
EXPECT_EQ(GDALComputeRasterMinMaxLocation(hBand, &dfMin, &dfMax, &nMinX,
&nMinY, &nMaxX, &nMaxY),
CE_None);
EXPECT_EQ(dfMin, 74.0);
EXPECT_EQ(dfMax, 255.0);
EXPECT_EQ(nMinX, 9);
EXPECT_EQ(nMinY, 17);
EXPECT_EQ(nMaxX, 2);
EXPECT_EQ(nMaxY, 18);
GByte val = 0;
EXPECT_EQ(GDALRasterIO(hBand, GF_Read, nMinX, nMinY, 1, 1, &val, 1, 1,
GDT_Byte, 0, 0),
CE_None);
EXPECT_EQ(val, 74);
EXPECT_EQ(GDALRasterIO(hBand, GF_Read, nMaxX, nMaxY, 1, 1, &val, 1, 1,
GDT_Byte, 0, 0),
CE_None);
EXPECT_EQ(val, 255);
GDALClose(hDS);
}

// Test GDALComputeRasterMinMaxLocation
TEST_F(test_gdal, GDALComputeRasterMinMaxLocation_with_mask)
{
GDALDatasetUniquePtr poDS(GDALDriver::FromHandle(GDALGetDriverByName("MEM"))
->Create("", 2, 2, 1, GDT_Byte, nullptr));
std::array<uint8_t, 6> buffer = {
2, 10, //////////////////////////////////////////////////////////
4, 20, //////////////////////////////////////////////////////////
};
GDALRasterIOExtraArg sExtraArg;
INIT_RASTERIO_EXTRA_ARG(sExtraArg);
EXPECT_EQ(poDS->GetRasterBand(1)->RasterIO(
GF_Write, 0, 0, 2, 2, buffer.data(), 2, 2, GDT_Byte,
sizeof(uint8_t), 2 * sizeof(uint8_t), &sExtraArg),
CE_None);

poDS->GetRasterBand(1)->CreateMaskBand(0);
std::array<uint8_t, 6> buffer_mask = {
0, 255, //////////////////////////////////////////////////////////
255, 0, //////////////////////////////////////////////////////////
};
EXPECT_EQ(poDS->GetRasterBand(1)->GetMaskBand()->RasterIO(
GF_Write, 0, 0, 2, 2, buffer_mask.data(), 2, 2, GDT_Byte,
sizeof(uint8_t), 2 * sizeof(uint8_t), &sExtraArg),
CE_None);

double dfMin = 0;
double dfMax = 0;
int nMinX = -1;
int nMinY = -1;
int nMaxX = -1;
int nMaxY = -1;
EXPECT_EQ(poDS->GetRasterBand(1)->ComputeRasterMinMaxLocation(
&dfMin, &dfMax, &nMinX, &nMinY, &nMaxX, &nMaxY),
CE_None);
EXPECT_EQ(dfMin, 4);
EXPECT_EQ(dfMax, 10);
EXPECT_EQ(nMinX, 0);
EXPECT_EQ(nMinY, 1);
EXPECT_EQ(nMaxX, 1);
EXPECT_EQ(nMaxY, 0);
}

} // namespace
19 changes: 19 additions & 0 deletions autotest/gcore/basic_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -987,3 +987,22 @@ def test_colorinterp():
assert name not in d
d[name] = c
assert gdal.GetColorInterpretationByName(name) == c


def test_ComputeMinMaxLocation():

ds = gdal.Open("data/byte.tif")
ret = ds.GetRasterBand(1).ComputeMinMaxLocation()
assert (
ret.min == 74
and ret.max == 255
and ret.minX == 9
and ret.minY == 17
and ret.maxX == 2
and ret.maxY == 18
)

ds = gdal.GetDriverByName("MEM").Create("", 1, 1, 1, gdal.GDT_Float64)
ds.GetRasterBand(1).Fill(float("nan"))
ret = ds.GetRasterBand(1).ComputeMinMaxLocation()
assert ret is None
1 change: 1 addition & 0 deletions doc/source/api/python_samples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Python Raster Sample scripts
- hsv_merge: Merge greyscale image into RGB image as intensity in HSV space.
- gdal_ls: Display the list of files in a virtual directory, like /vsicurl or /vsizip
- gdal_cp: Copy a virtual file
- gdal_minmax_location: returns the location where min/max values of a raster are hit.

Python Vector Sample scripts
------------------------------
Expand Down
4 changes: 4 additions & 0 deletions gcore/gdal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1670,6 +1670,10 @@ CPLErr CPL_DLL CPL_STDCALL GDALSetRasterScale(GDALRasterBandH hBand,
CPLErr CPL_DLL CPL_STDCALL GDALComputeRasterMinMax(GDALRasterBandH hBand,
int bApproxOK,
double adfMinMax[2]);
CPLErr CPL_DLL GDALComputeRasterMinMaxLocation(GDALRasterBandH hBand,
double *pdfMin, double *pdfMax,
int *pnMinX, int *pnMinY,
int *pnMaxX, int *pnMaxY);
CPLErr CPL_DLL CPL_STDCALL GDALFlushRasterCache(GDALRasterBandH hBand);
CPLErr CPL_DLL CPL_STDCALL GDALDropRasterCache(GDALRasterBandH hBand);
CPLErr CPL_DLL CPL_STDCALL GDALGetRasterHistogram(
Expand Down
3 changes: 3 additions & 0 deletions gcore/gdal_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -1810,6 +1810,9 @@ class CPL_DLL GDALRasterBand : public GDALMajorObject
virtual CPLErr SetStatistics(double dfMin, double dfMax, double dfMean,
double dfStdDev);
virtual CPLErr ComputeRasterMinMax(int bApproxOK, double *adfMinMax);
virtual CPLErr ComputeRasterMinMaxLocation(double *pdfMin, double *pdfMax,
int *pnMinX, int *pnMinY,
int *pnMaxX, int *pnMaxY);

// Only defined when Doxygen enabled
#ifdef DOXYGEN_SKIP
Expand Down
244 changes: 244 additions & 0 deletions gcore/gdalrasterband.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "gdal_rat.h"
#include "gdal_priv_templates.hpp"
#include "gdal_interpolateatpoint.h"
#include "gdal_minmax_element.hpp"

/************************************************************************/
/* GDALRasterBand() */
Expand Down Expand Up @@ -7415,6 +7416,249 @@ CPLErr CPL_STDCALL GDALComputeRasterMinMax(GDALRasterBandH hBand, int bApproxOK,
return poBand->ComputeRasterMinMax(bApproxOK, adfMinMax);
}

/************************************************************************/
/* ComputeRasterMinMaxLocation() */
/************************************************************************/

/**
* \brief Compute the min/max values for a band, and their location.
*
* If they are hit in several locations, it is not specified which one will be
* returned.
*
* @param[out] pdfMin Pointer to the minimum value.
* @param[out] pdfMax Pointer to the maximum value.
* @param[out] pnMinX Pointer to the column where the minimum value is hit.
* @param[out] pnMinY Pointer to the line where the minimum value is hit.
* @param[out] pnMaxX Pointer to the column where the maximum value is hit.
* @param[out] pnMaxY Pointer to the line where the maximum value is hit.
*
* @return CE_None in case of success, CE_Warning if there are no valid values,
* CE_Failure in case of error.
*
* @since GDAL 3.11
*/

CPLErr GDALRasterBand::ComputeRasterMinMaxLocation(double *pdfMin,
double *pdfMax, int *pnMinX,
int *pnMinY, int *pnMaxX,
int *pnMaxY)
{
int nMinX = -1;
int nMinY = -1;
int nMaxX = -1;
int nMaxY = -1;
double dfMin = std::numeric_limits<double>::infinity();
double dfMax = -std::numeric_limits<double>::infinity();
if (pdfMin)
*pdfMin = dfMin;
if (pdfMax)
*pdfMax = dfMax;
if (pnMinX)
*pnMinX = nMinX;
if (pnMinY)
*pnMinY = nMinY;
if (pnMaxX)
*pnMaxX = nMaxX;
if (pnMaxY)
*pnMaxY = nMaxY;

if (GDALDataTypeIsComplex(eDataType))
{
CPLError(CE_Failure, CPLE_NotSupported,
"Complex data type not supported");
return CE_Failure;
}

int bGotNoDataValue = FALSE;
const double dfNoDataValue = GetNoDataValue(&bGotNoDataValue);
bGotNoDataValue = bGotNoDataValue && !std::isnan(dfNoDataValue);
bool bGotFloatNoDataValue = false;
float fNoDataValue = 0.0f;
ComputeFloatNoDataValue(eDataType, dfNoDataValue, bGotNoDataValue,
fNoDataValue, bGotFloatNoDataValue);

GDALRasterBand *poMaskBand = nullptr;
if (!bGotNoDataValue)
{
const int l_nMaskFlags = GetMaskFlags();
if (l_nMaskFlags != GMF_ALL_VALID && l_nMaskFlags != GMF_NODATA &&
GetColorInterpretation() != GCI_AlphaBand)
{
poMaskBand = GetMaskBand();
}
}

bool bSignedByte = false;
if (eDataType == GDT_Byte)
{
EnablePixelTypeSignedByteWarning(false);
const char *pszPixelType =
GetMetadataItem("PIXELTYPE", "IMAGE_STRUCTURE");
EnablePixelTypeSignedByteWarning(true);
bSignedByte =
pszPixelType != nullptr && EQUAL(pszPixelType, "SIGNEDBYTE");
}

GByte *pabyMaskData = nullptr;
if (poMaskBand)
{
pabyMaskData =
static_cast<GByte *>(VSI_MALLOC2_VERBOSE(nBlockXSize, nBlockYSize));
if (!pabyMaskData)
{
return CE_Failure;
}
}

if (!InitBlockInfo())
return CE_Failure;

const GIntBig nTotalBlocks =
static_cast<GIntBig>(nBlocksPerRow) * nBlocksPerColumn;
for (GIntBig iBlock = 0; iBlock < nTotalBlocks; ++iBlock)
{
const int iYBlock = static_cast<int>(iBlock / nBlocksPerRow);
const int iXBlock = static_cast<int>(iBlock % nBlocksPerRow);

GDALRasterBlock *poBlock = GetLockedBlockRef(iXBlock, iYBlock);
if (poBlock == nullptr)
{
CPLFree(pabyMaskData);
return CE_Failure;
}

void *const pData = poBlock->GetDataRef();

int nXCheck = 0, nYCheck = 0;
GetActualBlockSize(iXBlock, iYBlock, &nXCheck, &nYCheck);

if (poMaskBand &&
poMaskBand->RasterIO(GF_Read, iXBlock * nBlockXSize,
iYBlock * nBlockYSize, nXCheck, nYCheck,
pabyMaskData, nXCheck, nYCheck, GDT_Byte, 0,
nBlockXSize, nullptr) != CE_None)
{
poBlock->DropLock();
CPLFree(pabyMaskData);
return CE_Failure;
}

if (poMaskBand || nYCheck < nBlockYSize || nXCheck < nBlockXSize)
{
for (int iY = 0; iY < nYCheck; ++iY)
{
for (int iX = 0; iX < nXCheck; ++iX)
{
const GPtrDiff_t iOffset =
iX + static_cast<GPtrDiff_t>(iY) * nBlockXSize;
if (pabyMaskData && pabyMaskData[iOffset] == 0)
continue;
bool bValid = true;
double dfValue = GetPixelValue(
eDataType, bSignedByte, pData, iOffset, bGotNoDataValue,
dfNoDataValue, bGotFloatNoDataValue, fNoDataValue,
bValid);
if (!bValid)
continue;
if (dfValue < dfMin)
{
dfMin = dfValue;
nMinX = iXBlock * nBlockXSize + iX;
nMinY = iYBlock * nBlockYSize + iY;
}
if (dfValue > dfMax)
{
dfMax = dfValue;
nMaxX = iXBlock * nBlockXSize + iX;
nMaxY = iYBlock * nBlockYSize + iY;
}
}
}
}
else
{
const auto [pos_min, pos_max] = gdal::minmax_element(
pData, static_cast<size_t>(nBlockXSize) * nBlockYSize,
bSignedByte ? GDT_Int8 : eDataType, bGotNoDataValue,
dfNoDataValue);
const int nMinXBlock = static_cast<int>(pos_min % nBlockXSize);
const int nMinYBlock = static_cast<int>(pos_min / nBlockXSize);
const int nMaxXBlock = static_cast<int>(pos_max % nBlockXSize);
const int nMaxYBlock = static_cast<int>(pos_max / nBlockXSize);
bool bValid = true;
const double dfMinValueBlock = GetPixelValue(
eDataType, bSignedByte, pData, pos_min, bGotNoDataValue,
dfNoDataValue, bGotFloatNoDataValue, fNoDataValue, bValid);
if (!bValid)
continue;
const double dfMaxValueBlock = GetPixelValue(
eDataType, bSignedByte, pData, pos_max, bGotNoDataValue,
dfNoDataValue, bGotFloatNoDataValue, fNoDataValue, bValid);
// No need to check for bValid here since if we got a valid min,
// we'll also get a valid max
if (dfMinValueBlock < dfMin)
{
dfMin = dfMinValueBlock;
nMinX = nMinXBlock;
nMinY = nMinYBlock;
}
if (dfMaxValueBlock > dfMax)
{
dfMax = dfMaxValueBlock;
nMaxX = nMaxXBlock;
nMaxY = nMaxYBlock;
}
}

poBlock->DropLock();

if (eDataType == GDT_Byte && dfMin == 0 && dfMax == 255)
{
break;
}
}

CPLFree(pabyMaskData);

if (pdfMin)
*pdfMin = dfMin;
if (pdfMax)
*pdfMax = dfMax;
if (pnMinX)
*pnMinX = nMinX;
if (pnMinY)
*pnMinY = nMinY;
if (pnMaxX)
*pnMaxX = nMaxX;
if (pnMaxY)
*pnMaxY = nMaxY;
return nMinX < 0 ? CE_Warning : CE_None;
}

/************************************************************************/
/* GDALComputeRasterMinMaxLocation() */
/************************************************************************/

/**
* \brief Compute the min/max values for a band, and their location.
*
* @see GDALRasterBand::ComputeRasterMinMax()
* @since GDAL 3.11
*/

CPLErr GDALComputeRasterMinMaxLocation(GDALRasterBandH hBand, double *pdfMin,
double *pdfMax, int *pnMinX, int *pnMinY,
int *pnMaxX, int *pnMaxY)

{
VALIDATE_POINTER1(hBand, "GDALComputeRasterMinMaxLocation", CE_Failure);

GDALRasterBand *poBand = GDALRasterBand::FromHandle(hBand);
return poBand->ComputeRasterMinMaxLocation(pdfMin, pdfMax, pnMinX, pnMinY,
pnMaxX, pnMaxY);
}

/************************************************************************/
/* SetDefaultHistogram() */
/************************************************************************/
Expand Down
Loading

0 comments on commit 0df47a0

Please sign in to comment.