diff --git a/src/aliceVision/image/io.cpp b/src/aliceVision/image/io.cpp index 61d47b342a..0cc9c63ccf 100644 --- a/src/aliceVision/image/io.cpp +++ b/src/aliceVision/image/io.cpp @@ -47,7 +47,7 @@ EImageFileType EImageFileType_stringToEnum(const std::string& imageFileType) if(type == "tif" || type == "tiff") return EImageFileType::TIFF; if(type == "exr") return EImageFileType::EXR; - throw std::out_of_range("Invalid image file type : " + imageFileType); + throw std::out_of_range("Invalid image file type: " + imageFileType); } std::string EImageFileType_enumToString(const EImageFileType imageFileType) @@ -101,6 +101,53 @@ bool isSupported(const std::string& ext) return (std::find(start, end, boost::to_lower_copy(ext)) != end); } + +std::string EStorageDataType_informations() +{ + return EStorageDataType_enumToString(EStorageDataType::Float) + ", " + + EStorageDataType_enumToString(EStorageDataType::Half) + ", " + + EStorageDataType_enumToString(EStorageDataType::HalfFinite) + ", " + + EStorageDataType_enumToString(EStorageDataType::Auto); +} + +EStorageDataType EStorageDataType_stringToEnum(const std::string& dataType) +{ + std::string type = dataType; + std::transform(type.begin(), type.end(), type.begin(), ::tolower); //tolower + + if (type == "Float") return EStorageDataType::Float; + if (type == "Half") return EStorageDataType::Half; + if (type == "HalfFinite") return EStorageDataType::HalfFinite; + if (type == "Auto") return EStorageDataType::Auto; + + throw std::out_of_range("Invalid EStorageDataType: " + dataType); +} + +std::string EStorageDataType_enumToString(const EStorageDataType dataType) +{ + switch (dataType) + { + case EStorageDataType::Float: return "Float"; + case EStorageDataType::Half: return "Half"; + case EStorageDataType::HalfFinite: return "HalfFinite"; + case EStorageDataType::Auto: return "Auto"; + } + throw std::out_of_range("Invalid EStorageDataType enum"); +} + +std::ostream& operator<<(std::ostream& os, EStorageDataType dataType) +{ + return os << EStorageDataType_enumToString(dataType); +} + +std::istream& operator>>(std::istream& in, EStorageDataType& dataType) +{ + std::string token; + in >> token; + dataType = EStorageDataType_stringToEnum(token); + return in; +} + // Warning: type conversion problems from string to param value, we may lose some metadata with string maps oiio::ParamValueList getMetadataFromMap(const std::map& metadataMap) { @@ -280,7 +327,7 @@ void readImage(const std::string& path, // compute luminance via a weighted sum of R,G,B // (assuming Rec709 primaries and a linear scale) - const float weights[3] = {.2126, .7152, .0722}; + const float weights[3] = {.2126f, .7152f, .0722f}; oiio::ImageBuf grayscaleBuf; oiio::ImageBufAlgo::channel_sum(grayscaleBuf, inBuf, weights, convertionROI); inBuf.copy(grayscaleBuf); @@ -324,6 +371,19 @@ void readImage(const std::string& path, } } +bool containsHalfFloatOverflow(const oiio::ImageBuf& image) +{ + oiio::ImageBufAlgo::PixelStats stats; + oiio::ImageBufAlgo::computePixelStats(stats, image); + + for(auto maxValue: stats.max) + { + if(maxValue > HALF_MAX) + return true; + } + return false; +} + template void writeImage(const std::string& path, oiio::TypeDesc typeDesc, @@ -368,11 +428,31 @@ void writeImage(const std::string& path, oiio::ImageBuf formatBuf; // buffer for image format modification if(isEXR) { - int useFullFloat = imageSpec.get_int_attribute("AliceVision:useFullFloat", 0); + EStorageDataType storageDataType = EStorageDataType_stringToEnum(imageSpec.get_string_attribute("AliceVision:storageDataType", EStorageDataType_enumToString(EStorageDataType::HalfFinite))); + + if (storageDataType == EStorageDataType::Auto) + { + if (containsHalfFloatOverflow(*outBuf)) + { + storageDataType = EStorageDataType::Float; + } + else + { + storageDataType = EStorageDataType::Half; + } + } - if (!useFullFloat) { - formatBuf.copy(*outBuf, oiio::TypeDesc::HALF); // override format, use half instead of float - outBuf = &formatBuf; + if (storageDataType == EStorageDataType::HalfFinite) + { + oiio::ImageBufAlgo::clamp(colorspaceBuf, *outBuf, -HALF_MAX, HALF_MAX); + outBuf = &colorspaceBuf; + } + + if (storageDataType == EStorageDataType::Half || + storageDataType == EStorageDataType::HalfFinite) + { + formatBuf.copy(*outBuf, oiio::TypeDesc::HALF); // override format, use half instead of float + outBuf = &formatBuf; } } diff --git a/src/aliceVision/image/io.hpp b/src/aliceVision/image/io.hpp index 1bcc37b5dc..87fc04f7f0 100644 --- a/src/aliceVision/image/io.hpp +++ b/src/aliceVision/image/io.hpp @@ -42,6 +42,7 @@ enum class EImageFileType EXR }; + /** * @brief get informations about each image file type * @return String @@ -91,6 +92,25 @@ std::vector getSupportedExtensions(); */ bool isSupported(const std::string& ext); + +/** +* @brief Data type use to write the image +*/ +enum class EStorageDataType +{ + Float, //< Use full floating point precision to store + Half, //< Use half (values our of range could become inf or nan) + HalfFinite, //< Use half, but ensures out-of-range pixels are clamps to keep finite pixel values + Auto //< Use half if all pixels can be stored in half without clamp, else use full float +}; + +std::string EStorageDataType_informations(); +EStorageDataType EStorageDataType_stringToEnum(const std::string& dataType); +std::string EStorageDataType_enumToString(const EStorageDataType dataType); +std::ostream& operator<<(std::ostream& os, EStorageDataType dataType); +std::istream& operator>>(std::istream& in, EStorageDataType& dataType); + + /** * @brief convert a metadata string map into an oiio::ParamValueList * @param[in] metadataMap string map diff --git a/src/software/pipeline/main_LdrToHdrMerge.cpp b/src/software/pipeline/main_LdrToHdrMerge.cpp index 885ab16316..248896e5a5 100644 --- a/src/software/pipeline/main_LdrToHdrMerge.cpp +++ b/src/software/pipeline/main_LdrToHdrMerge.cpp @@ -44,10 +44,6 @@ std::string getHdrImagePath(const std::string& outputPath, std::size_t g) return hdrImagePath; } -bool isOverflow(const image::Image & input) { - const float maxHalfFloat = 65504.0f; - return (input.maxCoeff() > maxHalfFloat); -} int aliceVision_main(int argc, char** argv) { @@ -64,6 +60,8 @@ int aliceVision_main(int argc, char** argv) float highlightCorrectionFactor = 1.0f; float highlightTargetLux = 120000.0f; + image::EStorageDataType storageDataType = image::EStorageDataType::Float; + int rangeStart = -1; int rangeSize = 1; @@ -97,6 +95,8 @@ int aliceVision_main(int argc, char** argv) ("highlightCorrectionFactor", po::value(&highlightCorrectionFactor)->default_value(highlightCorrectionFactor), "float value between 0 and 1 to correct clamped highlights in dynamic range: use 0 for no correction, 1 for " "full correction to maxLuminance.") + ("storageDataType", po::value(&storageDataType)->default_value(storageDataType), + ("Storage data type: " + image::EStorageDataType_informations()).c_str()) ("rangeStart", po::value(&rangeStart)->default_value(rangeStart), "Range image index start.") ("rangeSize", po::value(&rangeSize)->default_value(rangeSize), @@ -292,11 +292,7 @@ int aliceVision_main(int argc, char** argv) // Write an image with parameters from the target view oiio::ParamValueList targetMetadata = image::readImageMetadata(targetView->getImagePath()); - - if (isOverflow(HDRimage)) - { - targetMetadata.push_back(oiio::ParamValue("AliceVision:useFullFloat", int(1))); - } + targetMetadata.push_back(oiio::ParamValue("AliceVision:storageDataType", image::EStorageDataType_enumToString(storageDataType))); image::writeImage(hdrImagePath, HDRimage, image::EImageColorSpace::AUTO, targetMetadata); } diff --git a/src/software/pipeline/main_panoramaCompositing.cpp b/src/software/pipeline/main_panoramaCompositing.cpp index b7a2f179b5..a6363bf025 100644 --- a/src/software/pipeline/main_panoramaCompositing.cpp +++ b/src/software/pipeline/main_panoramaCompositing.cpp @@ -49,10 +49,6 @@ typedef struct { std::string weights_path; } ConfigView; -bool isOverflow(const image::Image & input) { - const float maxHalfFloat = 65504.0f; - return (input.maxCoeff() > maxHalfFloat); -} /** * @brief Maxflow computation based on a standard Adjacency List graph reprensentation. @@ -2155,6 +2151,8 @@ int aliceVision_main(int argc, char **argv) bool showBorders = false; bool showSeams = false; + image::EStorageDataType storageDataType = image::EStorageDataType::Float; + system::EVerboseLevel verboseLevel = system::Logger::getDefaultVerboseLevel(); // Program description @@ -2176,7 +2174,9 @@ int aliceVision_main(int argc, char **argv) optionalParams.add_options() ("compositerType,c", po::value(&compositerType)->required(), "Compositer Type [replace, alpha, multiband].") ("overlayType,c", po::value(&overlayType)->required(), "Overlay Type [none, borders, seams, all].") - ("useGraphCut,c", po::value(&useGraphCut)->default_value(useGraphCut), "Do we use graphcut for ghost removal ?"); + ("useGraphCut,c", po::value(&useGraphCut)->default_value(useGraphCut), "Do we use graphcut for ghost removal ?") + ("storageDataType", po::value(&storageDataType)->default_value(storageDataType), + ("Storage data type: " + image::EStorageDataType_informations()).c_str()); allParams.add(optionalParams); // Setup log level given command line @@ -2490,11 +2490,8 @@ int aliceVision_main(int argc, char **argv) ALICEVISION_LOG_INFO("Write output panorama to file " << outputPanorama); const aliceVision::image::Image & panorama = compositer->getPanorama(); - oiio::ParamValueList view_metadata = outputMetadata; - if (isOverflow(panorama)) - { - outputMetadata.push_back(oiio::ParamValue("AliceVision:useFullFloat", int(1))); - } + // Select storage data type + outputMetadata.push_back(oiio::ParamValue("AliceVision:storageDataType", image::EStorageDataType_enumToString(storageDataType))); image::writeImage(outputPanorama, panorama, image::EImageColorSpace::AUTO, outputMetadata); diff --git a/src/software/pipeline/main_panoramaWarping.cpp b/src/software/pipeline/main_panoramaWarping.cpp index bb4332f9c5..9d5558dac0 100644 --- a/src/software/pipeline/main_panoramaWarping.cpp +++ b/src/software/pipeline/main_panoramaWarping.cpp @@ -38,11 +38,6 @@ namespace po = boost::program_options; namespace bpt = boost::property_tree; namespace fs = boost::filesystem; -bool isOverflow(const image::Image & input) { - const float maxHalfFloat = 65504.0f; - return (input.maxCoeff() > maxHalfFloat); -} - namespace SphericalMapping { /** @@ -1008,9 +1003,12 @@ int aliceVision_main(int argc, char **argv) std::string outputDirectory; std::pair panoramaSize = {0, 0}; + int percentUpscale = 50; + + image::EStorageDataType storageDataType = image::EStorageDataType::Float; + int rangeStart = -1; int rangeSize = 1; - int percentUpscale = 50; // Program description po::options_description allParams ( @@ -1032,6 +1030,8 @@ int aliceVision_main(int argc, char **argv) "Panorama Width in pixels.") ("percentUpscale", po::value(&percentUpscale)->default_value(percentUpscale), "Percentage of upscaled pixels.") + ("storageDataType", po::value(&storageDataType)->default_value(storageDataType), + ("Storage data type: " + image::EStorageDataType_informations()).c_str()) ("rangeStart", po::value(&rangeStart)->default_value(rangeStart), "Range image index start.") ("rangeSize", po::value(&rangeSize)->default_value(rangeSize), @@ -1202,15 +1202,12 @@ int aliceVision_main(int argc, char **argv) { const aliceVision::image::Image & cam = warper.getColor(); - oiio::ParamValueList view_metadata = metadata; - if (isOverflow(cam)) - { - view_metadata.push_back(oiio::ParamValue("AliceVision:useFullFloat", int(1))); - } + oiio::ParamValueList viewMetadata = metadata; + viewMetadata.push_back(oiio::ParamValue("AliceVision:storageDataType", image::EStorageDataType_enumToString(storageDataType))); const std::string viewFilepath = (fs::path(outputDirectory) / (viewIdStr + ".exr")).string(); ALICEVISION_LOG_INFO("Store view " << i << " with path " << viewFilepath); - image::writeImage(viewFilepath, cam, image::EImageColorSpace::AUTO, view_metadata); + image::writeImage(viewFilepath, cam, image::EImageColorSpace::AUTO, viewMetadata); } { const aliceVision::image::Image & mask = warper.getMask();