Skip to content

Commit

Permalink
Add new GCI_ constants in particular for InfraRed, and 'standardize' …
Browse files Browse the repository at this point in the history
…a band-level IMAGERY metadata domain

Added values:
    * GCI_PanBand: Panchromatic band [0.40 - 1.00 um]
    * GCI_CoastalBand: Coastal band [0.40 - 0.45 um]
    * GCI_RedEdgeBand: Red-edge band [0.69 - 0.79 um]
    * GCI_NIRBand: Near-InfraRed (NIR) band [0.75 - 1.40 um]
    * GCI_SWIRBand: Short-Wavelength InfraRed (SWIR) band [1.40 - 3.00 um]
    * GCI_MWIRBand: Mid-Wavelength InfraRed (MWIR) band [3.00 - 8.00 um]
    * GCI_LWIRBand: Long-Wavelength InfraRed (LWIR) band [8.00 - 15 um]
    * GCI_TIRBand: Thermal InfraRed (TIR) band (MWIR or LWIR) [3 - 15 um]
    * GCI_OtherIRBand: Other infrared band [0.75 - 1000 um]

For spectral bands, the wavelength ranges are indicative only, and may vary
depending on sensors.

STACIT driver enhanced to map STAC common_name to GCI_ constants

gdalinfo -json output enhanced to output STAC common_name from GCI_
constants

Items in the IMAGERY band-level metadata domain:
- CENTRAL_WAVELENGTH_UM: Central Wavelength in micrometers.
- FWHM_UM: Full-width half-maximum (FWHM) in micrometers.

Filled by the SENTINEL2 and ENVI drivers (if corresponding metadata items are found in
the ENVI header)
  • Loading branch information
rouault committed Sep 3, 2024
1 parent 031a209 commit 58451a9
Show file tree
Hide file tree
Showing 21 changed files with 464 additions and 100 deletions.
3 changes: 3 additions & 0 deletions MIGRATION_GUIDE.TXT
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ MIGRATION GUIDE FROM GDAL 3.9 to GDAL 3.10
- Python bindings: Band.GetStatistics() and Band.ComputeStatistics() now
return a None value in case of error (when exceptions are not enabled)

- New color interpretation (GCI_xxxx) items have been added to the GDALColorInterp
enumeration. Code testing color interpretation may need to be adapted.

MIGRATION GUIDE FROM GDAL 3.8 to GDAL 3.9
-----------------------------------------

Expand Down
19 changes: 7 additions & 12 deletions apps/gdal_translate_lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2737,18 +2737,11 @@ static void CopyBandInfo(GDALRasterBand *poSrcBand, GDALRasterBand *poDstBand,

static int GetColorInterp(const char *pszStr)
{
if (EQUAL(pszStr, "red"))
return GCI_RedBand;
if (EQUAL(pszStr, "green"))
return GCI_GreenBand;
if (EQUAL(pszStr, "blue"))
return GCI_BlueBand;
if (EQUAL(pszStr, "alpha"))
return GCI_AlphaBand;
if (EQUAL(pszStr, "gray") || EQUAL(pszStr, "grey"))
return GCI_GrayIndex;
if (EQUAL(pszStr, "undefined"))
return GCI_Undefined;
const int eInterp = GDALGetColorInterpretationByName(pszStr);
if (eInterp != GCI_Undefined)
return eInterp;
CPLError(CE_Warning, CPLE_NotSupported,
"Unsupported color interpretation: %s", pszStr);
return -1;
Expand Down Expand Up @@ -3078,7 +3071,8 @@ GDALTranslateOptionsGetParser(GDALTranslateOptions *psOptions,
_("Add the indicated ground control point to the output dataset."));

argParser->add_argument("-colorinterp")
.metavar("{red|green|blue|alpha|gray|undefined},...")
.metavar("{red|green|blue|alpha|gray|undefined|pan|coastal|rededge|nir|"
"swir|mwir|lwir|...},...")
.action(
[psOptions](const std::string &s)
{
Expand All @@ -3093,7 +3087,8 @@ GDALTranslateOptionsGetParser(GDALTranslateOptions *psOptions,

argParser->add_argument("-colorinterp_X")
.append()
.metavar("{red|green|blue|alpha|gray|undefined}")
.metavar("{red|green|blue|alpha|gray|undefined|pan|coastal|rededge|nir|"
"swir|mwir|lwir|...}")
.help(_("Override the color interpretation of band X."));

{
Expand Down
28 changes: 19 additions & 9 deletions apps/gdalinfo_lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1128,25 +1128,21 @@ char *GDALInfo(GDALDatasetH hDataset, const GDALInfoOptions *psOptions)
json_object_object_add(poStacEOBand, "name", poBandName);
}

if (GDALGetDescription(hBand) != nullptr &&
strlen(GDALGetDescription(hBand)) > 0)
const char *pszBandDesc = GDALGetDescription(hBand);
if (pszBandDesc != nullptr && strlen(pszBandDesc) > 0)
{
if (bJson)
{
json_object *poBandDescription =
json_object_new_string(GDALGetDescription(hBand));
json_object_object_add(poBand, "description",
poBandDescription);
json_object_new_string(pszBandDesc));

json_object *poStacBandDescription =
json_object_new_string(GDALGetDescription(hBand));
json_object_object_add(poStacEOBand, "description",
poStacBandDescription);
json_object_new_string(pszBandDesc));
}
else
{
Concat(osStr, psOptions->bStdoutOutput, " Description = %s\n",
GDALGetDescription(hBand));
pszBandDesc);
}
}
else
Expand All @@ -1161,6 +1157,17 @@ char *GDALInfo(GDALDatasetH hDataset, const GDALInfoOptions *psOptions)
}
}

if (bJson)
{
const char *pszCommonName = GDALGetSTACCommonNameFromColorInterp(
GDALGetRasterColorInterpretation(hBand));
if (pszCommonName)
{
json_object_object_add(poStacEOBand, "common_name",
json_object_new_string(pszCommonName));
}
}

{
int bGotMin = FALSE;
int bGotMax = FALSE;
Expand Down Expand Up @@ -2269,6 +2276,9 @@ static void GDALInfoReportMetadata(const GDALInfoOptions *psOptions,
GDALInfoPrintMetadata(psOptions, hObject, "RPC", "RPC Metadata",
pszIndent, bJson, poMetadata, osStr);
}

GDALInfoPrintMetadata(psOptions, hObject, "IMAGERY", "Imagery", pszIndent,
bJson, poMetadata, osStr);
}

/************************************************************************/
Expand Down
117 changes: 117 additions & 0 deletions autotest/gdrivers/envi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1056,3 +1056,120 @@ def test_envi_read_metadata_with_leading_space():
assert ds.GetRasterBand(1).GetMetadataItem("wavelength") == "3"
ds = None
gdal.GetDriverByName("ENVI").Delete("/vsimem/test.bin")


###############################################################################
# Test wavelength / fwhm


def test_envi_read_wavelength_fwhm_um():

gdal.FileFromMemBuffer(
"/vsimem/test.hdr",
"""ENVI
samples = 1
lines = 1
bands = 3
header offset = 0
file type = ENVI Standard
data type = 1
interleave = bip
sensor type = Unknown
byte order = 0
wavelength units = um
wavelength = {3, 2, 1}
fwhm = {.3, .2, .1}""",
)
gdal.FileFromMemBuffer("/vsimem/test.bin", "xyz")

ds = gdal.Open("/vsimem/test.bin")
assert (
ds.GetRasterBand(1).GetMetadataItem("CENTRAL_WAVELENGTH_UM", "IMAGERY")
== "3.000"
)
assert ds.GetRasterBand(1).GetMetadataItem("FWHM_UM", "IMAGERY") == "0.300"
assert (
ds.GetRasterBand(2).GetMetadataItem("CENTRAL_WAVELENGTH_UM", "IMAGERY")
== "2.000"
)
assert ds.GetRasterBand(2).GetMetadataItem("FWHM_UM", "IMAGERY") == "0.200"
ds = None
gdal.GetDriverByName("ENVI").Delete("/vsimem/test.bin")


###############################################################################
# Test wavelength / fwhm


def test_envi_read_wavelength_fwhm_nm():

gdal.FileFromMemBuffer(
"/vsimem/test.hdr",
"""ENVI
samples = 1
lines = 1
bands = 3
header offset = 0
file type = ENVI Standard
data type = 1
interleave = bip
sensor type = Unknown
byte order = 0
wavelength units = nm
wavelength = {3000, 2000, 1000}
fwhm = {300, 200, 100}""",
)
gdal.FileFromMemBuffer("/vsimem/test.bin", "xyz")

ds = gdal.Open("/vsimem/test.bin")
assert (
ds.GetRasterBand(1).GetMetadataItem("CENTRAL_WAVELENGTH_UM", "IMAGERY")
== "3.000"
)
assert ds.GetRasterBand(1).GetMetadataItem("FWHM_UM", "IMAGERY") == "0.300"
assert (
ds.GetRasterBand(2).GetMetadataItem("CENTRAL_WAVELENGTH_UM", "IMAGERY")
== "2.000"
)
assert ds.GetRasterBand(2).GetMetadataItem("FWHM_UM", "IMAGERY") == "0.200"
ds = None
gdal.GetDriverByName("ENVI").Delete("/vsimem/test.bin")


###############################################################################
# Test wavelength / fwhm


def test_envi_read_wavelength_fwhm_mm():

gdal.FileFromMemBuffer(
"/vsimem/test.hdr",
"""ENVI
samples = 1
lines = 1
bands = 3
header offset = 0
file type = ENVI Standard
data type = 1
interleave = bip
sensor type = Unknown
byte order = 0
wavelength units = mm
wavelength = {0.003, 0.002, 0.001}
fwhm = {0.0003, 0.0002, 0.0001}""",
)
gdal.FileFromMemBuffer("/vsimem/test.bin", "xyz")

ds = gdal.Open("/vsimem/test.bin")
assert (
ds.GetRasterBand(1).GetMetadataItem("CENTRAL_WAVELENGTH_UM", "IMAGERY")
== "3.000"
)
assert ds.GetRasterBand(1).GetMetadataItem("FWHM_UM", "IMAGERY") == "0.300"
assert (
ds.GetRasterBand(2).GetMetadataItem("CENTRAL_WAVELENGTH_UM", "IMAGERY")
== "2.000"
)
assert ds.GetRasterBand(2).GetMetadataItem("FWHM_UM", "IMAGERY") == "0.200"
ds = None
gdal.GetDriverByName("ENVI").Delete("/vsimem/test.bin")
21 changes: 12 additions & 9 deletions autotest/gdrivers/sentinel2.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ def test_sentinel2_l1c_2():
pprint.pprint(got_md)
pytest.fail()

assert band.GetMetadataItem("CENTRAL_WAVELENGTH_UM", "IMAGERY") == "0.665"
assert band.GetMetadataItem("FWHM_UM", "IMAGERY") == "0.030"

assert band.GetColorInterpretation() == gdal.GCI_RedBand

assert band.DataType == gdal.GDT_UInt16
Expand All @@ -252,7 +255,7 @@ def test_sentinel2_l1c_2():

band = ds.GetRasterBand(4)

assert band.GetColorInterpretation() == gdal.GCI_Undefined
assert band.GetColorInterpretation() == gdal.GCI_NIRBand

got_md = band.GetMetadata()
expected_md = {
Expand Down Expand Up @@ -843,7 +846,7 @@ def test_sentinel2_l1c_tile_3():

band = ds.GetRasterBand(4)

assert band.GetColorInterpretation() == gdal.GCI_Undefined
assert band.GetColorInterpretation() == gdal.GCI_NIRBand

got_md = band.GetMetadata()
expected_md = {
Expand Down Expand Up @@ -2618,7 +2621,7 @@ def test_sentinel2_l1c_safe_compact_2():

band = ds.GetRasterBand(4)

assert band.GetColorInterpretation() == gdal.GCI_Undefined
assert band.GetColorInterpretation() == gdal.GCI_NIRBand

got_md = band.GetMetadata()
expected_md = {
Expand Down Expand Up @@ -2804,7 +2807,7 @@ def test_sentinel2_l1c_processing_baseline_5_09__1():

band = ds.GetRasterBand(4)

assert band.GetColorInterpretation() == gdal.GCI_Undefined
assert band.GetColorInterpretation() == gdal.GCI_NIRBand

got_md = band.GetMetadata()
expected_md = {
Expand Down Expand Up @@ -2900,13 +2903,13 @@ def test_sentinel2_l1c_processing_baseline_5_09__2():
pprint.pprint(got_md)
pytest.fail()

assert band.GetColorInterpretation() == gdal.GCI_Undefined
assert band.GetColorInterpretation() == gdal.GCI_RedEdgeBand

assert band.DataType == gdal.GDT_UInt16

band = ds.GetRasterBand(4)

assert band.GetColorInterpretation() == gdal.GCI_Undefined
assert band.GetColorInterpretation() == gdal.GCI_NIRBand

got_md = band.GetMetadata()
expected_md = {
Expand Down Expand Up @@ -3035,7 +3038,7 @@ def test_sentinel2_l2a_processing_baseline_5_09__1():

band = ds.GetRasterBand(4)

assert band.GetColorInterpretation() == gdal.GCI_Undefined
assert band.GetColorInterpretation() == gdal.GCI_NIRBand

got_md = band.GetMetadata()
expected_md = {
Expand Down Expand Up @@ -3158,13 +3161,13 @@ def test_sentinel2_l2a_processing_baseline_5_09__2():
pprint.pprint(got_md)
pytest.fail()

assert band.GetColorInterpretation() == gdal.GCI_Undefined
assert band.GetColorInterpretation() == gdal.GCI_RedEdgeBand

assert band.DataType == gdal.GDT_UInt16

band = ds.GetRasterBand(4)

assert band.GetColorInterpretation() == gdal.GCI_Undefined
assert band.GetColorInterpretation() == gdal.GCI_NIRBand

got_md = band.GetMetadata()
expected_md = {
Expand Down
2 changes: 1 addition & 1 deletion autotest/gdrivers/stacit.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def test_stacit_overlapping_sources():
# Check that the source covered by another one is not listed
vrt = ds.GetMetadata("xml:VRT")[0]
only_one_simple_source = """
<ColorInterp>Gray</ColorInterp>
<ColorInterp>Coastal</ColorInterp>
<SimpleSource>
<SourceFilename relativeToVRT="0">data/byte.tif</SourceFilename>
<SourceBand>1</SourceBand>
Expand Down
11 changes: 11 additions & 0 deletions autotest/utilities/test_gdalinfo_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,14 @@ def test_gdalinfo_lib_nomask(tmp_path):

ret = gdal.Info(ds, format="json", showMask=False)
assert "mask" not in ret["bands"][0]


###############################################################################


def test_gdalinfo_lib_json_stac_common_name():

ds = gdal.GetDriverByName("MEM").Create("", 1, 1)
ds.GetRasterBand(1).SetColorInterpretation(gdal.GCI_PanBand)
ret = gdal.Info(ds, options="-json")
assert ret["stac"]["eo:bands"][0]["common_name"] == "pan"
6 changes: 3 additions & 3 deletions doc/source/programs/gdal_edit.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ Synopsis

.. code-block::
gdal_edit [--help] [--help-general] [-ro] [-a_srs <srs_def>]
gdal_edit [--help] [--help-general] [-ro] [-a_srs <srs_def>]
[-a_ullr <ulx> <uly> <lrx> <lry>] [-a_ulurll <ulx> <uly> <urx> <ury> <llx> <lly>]
[-tr <xres> <yres>] [-unsetgt] [-unsetrpc] [-a_nodata <value>] [-unsetnodata]
[-a_coord_epoch <epoch>] [-unsetepoch]
[-unsetstats] [-stats] [-approx_stats]
[-setstats <min> <max> <mean> <stddev>]
[-scale <value>] [-offset <value>] [-units <value>]
[-colorinterp_<X> {red|green|blue|alpha|gray|undefined}]...
[-colorinterp_<X> {red|green|blue|alpha|gray|undefined|pan|coastal|rededge|nir|swir|mwir|lwir|...}]...
[-gcp <pixel> <line> <easting> <northing> [<elevation>]]...
[-unsetmd] [-oo <NAME>=<VALUE>]... [-mo <META-TAG>=<VALUE>]...
<datasetname>
Expand Down Expand Up @@ -172,7 +172,7 @@ It works only with raster formats that support update access to existing dataset

.. versionadded:: 3.1

.. option:: -colorinterp_<X> {red|green|blue|alpha|gray|undefined}
.. option:: -colorinterp_<X> {red|green|blue|alpha|gray|undefined|pan|coastal|rededge|nir|swir|mwir|lwir|...}

Change the color interpretation of band X (where X is a valid band
number, starting at 1).
Expand Down
8 changes: 4 additions & 4 deletions doc/source/programs/gdal_translate.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ Synopsis
[-a_gt <gt0> <gt1> <gt2> <gt3> <gt4> <gt5>]
[-a_scale <value>] [-a_offset <value>]
[-nogcp] [-gcp <pixel> <line> <easting> <northing> [<elevation>]]...
|-colorinterp{_bn} {red|green|blue|alpha|gray|undefined}]
|-colorinterp {red|green|blue|alpha|gray|undefined},...]
|-colorinterp{_bn} {red|green|blue|alpha|gray|undefined|pan|coastal|rededge|nir|swir|mwir|lwir|...}]
|-colorinterp {red|green|blue|alpha|gray|undefined|pan|coastal|rededge|nir|swir|mwir|lwir|...},...]
[-mo <META-TAG>=<VALUE>]... [-dmo "DOMAIN:META-TAG=VALUE"]... [-q] [-sds]
[-co <NAME>=<VALUE>]... [-stats] [-norat] [-noxmp]
[-oo <NAME>=<VALUE>]...
Expand Down Expand Up @@ -289,14 +289,14 @@ resampling, and rescaling pixels in the process.
nodata value, this does not cause pixel values that are equal to that nodata
value to be changed to the value specified with this option.

.. option:: -colorinterp_X <red|green|blue|alpha|gray|undefined>
.. option:: -colorinterp_X <red|green|blue|alpha|gray|undefined|pan|coastal|rededge|nir|swir|mwir|lwir|...>

Override the color interpretation of band X (where X is a valid band number,
starting at 1)

.. versionadded:: 2.3

.. option:: -colorinterp {red|green|blue|alpha|gray|undefined},...
.. option:: -colorinterp {red|green|blue|alpha|gray|undefined|pan|coastal|rededge|nir|swir|mwir|lwir|...},...

Override the color interpretation of all specified bands. For
example -colorinterp red,green,blue,alpha for a 4 band output dataset.
Expand Down
Loading

0 comments on commit 58451a9

Please sign in to comment.