diff --git a/dali/operators/imgcodec/image_decoder.h b/dali/operators/imgcodec/image_decoder.h index 795422ede6e..30da68aec7f 100644 --- a/dali/operators/imgcodec/image_decoder.h +++ b/dali/operators/imgcodec/image_decoder.h @@ -12,11 +12,18 @@ // limitations under the License. #include +#include +#include +#include #include #include +#include #include #include #include +#if LIBTIFF_ENABLED +#include +#endif #include "dali/core/call_at_exit.h" #include "dali/core/mm/memory.h" #include "dali/operators.h" @@ -46,6 +53,51 @@ nvimgcodecStatus_t get_nvjpeg2k_extension_desc(nvimgcodecExtensionDesc_t *ext_de namespace dali { namespace imgcodec { +#if LIBTIFF_ENABLED + +// GeoTIFF-specific TIFF tag IDs that libtiff does not recognize natively. +// When decoding GeoTIFF files, libtiff emits "Unknown field with tag X" warnings for these tags. +// The image data is decoded correctly; the tags only carry geographic metadata. +constexpr uint32_t kGeoTIFFTags[] = { + 33550, // ModelPixelScaleTag + 33922, // ModelTiepointTag + 34264, // ModelTransformationTag + 34735, // GeoKeyDirectoryTag + 34736, // GeoDoubleParamsTag + 34737, // GeoAsciiParamsTag + 42112, // GDAL_METADATA + 42113, // GDAL_NODATA +}; + +inline void SuppressGeoTIFFTagWarnings(const char *module, const char *fmt, va_list ap) { + if (strstr(fmt, "Unknown field with tag") != nullptr) { + va_list ap_copy; + va_copy(ap_copy, ap); + unsigned int tag = va_arg(ap_copy, unsigned int); + va_end(ap_copy); + for (auto geotiff_tag : kGeoTIFFTags) { + if (tag == geotiff_tag) + return; + } + } + char buf[1024]; + vsnprintf(buf, sizeof(buf), fmt, ap); + // libtiff permits a null module name in some code paths + if (module) + std::cerr << module << ": " << buf << "\n"; + else + std::cerr << buf << "\n"; +} + +// Declared inline so the static once_flag is shared across all translation units, +// guaranteeing TIFFSetWarningHandler is called exactly once per process. +inline void InstallGeoTIFFWarningFilter() { + static std::once_flag flag; + std::call_once(flag, [] { TIFFSetWarningHandler(SuppressGeoTIFFTagWarnings); }); +} + +#endif // LIBTIFF_ENABLED + template struct OutBackend { using type = GPUBackend; @@ -219,6 +271,9 @@ class ImageDecoder : public StatelessOperator { explicit ImageDecoder(const OpSpec &spec) : StatelessOperator(spec) { +#if LIBTIFF_ENABLED + InstallGeoTIFFWarningFilter(); +#endif device_id_ = std::is_same::value ? CPU_ONLY_DEVICE_ID : spec.GetArgument("device_id"); format_ = spec.GetArgument("output_type"); diff --git a/dali/test/python/decoder/test_image.py b/dali/test/python/decoder/test_image.py index 8c4398e1957..69c124f6bb5 100644 --- a/dali/test/python/decoder/test_image.py +++ b/dali/test/python/decoder/test_image.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright (c) 2019-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import nvidia.dali.types as types import os import random +import struct import tempfile from nvidia.dali import pipeline_def @@ -26,8 +27,8 @@ from nose_utils import assert_raises, SkipTest from test_utils import compare_pipelines from test_utils import get_dali_extra_path -from test_utils import to_array from test_utils import get_nvjpeg_ver +from test_utils import to_array def get_img_files(data_path, subdir="*", ext=None): @@ -491,6 +492,108 @@ def pipe(): assert np.quantile(delta, 0.9) < 0.05, "Original and palette TIFF differ significantly" +def _create_geotiff(path, width=4, height=4): + """Build a minimal GeoTIFF file from scratch with the five standard GeoTIFF metadata tags. + + Returns the expected pixel array (HxW uint8) so callers can compare against decoded output. + The file is constructed with struct so no third-party TIFF library is required. + """ + # Image: row-major, values 0..(H*W-1), clipped to uint8 + image_data = bytes(i % 256 for i in range(width * height)) + + # Extra tag payloads (stored after the IFD) + model_pixel_scale = struct.pack("<3d", 1.0, 1.0, 0.0) # ModelPixelScaleTag (3 doubles) + model_tiepoint = struct.pack("<6d", *([0.0] * 6)) # ModelTiepointTag (6 doubles) + geo_key_dir = struct.pack("<4H", 1, 1, 0, 0) # GeoKeyDirectoryTag (4 shorts) + geo_double = struct.pack("