From 584772119f79108e06e059d7348216c9881ce362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Vital?= Date: Thu, 2 Mar 2023 17:15:25 +0100 Subject: [PATCH] [imageCaching] support all downscale levels instead of just powers of two --- src/aliceVision/image/imageCaching.cpp | 3 +- src/aliceVision/image/imageCaching.hpp | 66 +++++-------------- src/aliceVision/image/imageCaching_test.cpp | 13 ---- .../imageCaching/main_imageCaching.cpp | 14 ++-- 4 files changed, 25 insertions(+), 71 deletions(-) diff --git a/src/aliceVision/image/imageCaching.cpp b/src/aliceVision/image/imageCaching.cpp index b67984fee3..c5550caaf3 100644 --- a/src/aliceVision/image/imageCaching.cpp +++ b/src/aliceVision/image/imageCaching.cpp @@ -135,7 +135,7 @@ std::string ImageCache::toString() const std::string keyDesc = key.filename + ", nbChannels: " + std::to_string(key.nbChannels) + ", typeDesc: " + std::to_string(key.typeDesc) + - ", halfSampleLevel: " + std::to_string(key.halfSampleLevel) + + ", downscaleLevel: " + std::to_string(key.downscaleLevel) + ", usages: " + std::to_string(_imagePtrs.at(key).useCount()) + ", size: " + std::to_string(_imagePtrs.at(key).memorySize()); description += "\n * " + keyDesc; @@ -151,7 +151,6 @@ std::string ImageCache::toString() const std::string statsDesc = "\nUsage statistics: " "\n * nb load from disk: " + std::to_string(_info.nbLoadFromDisk) + "\n * nb load from cache: " + std::to_string(_info.nbLoadFromCache) + - "\n * nb load from higher scale: " + std::to_string(_info.nbLoadFromHigherScale) + "\n * nb remove unused: " + std::to_string(_info.nbRemoveUnused); description += statsDesc; diff --git a/src/aliceVision/image/imageCaching.hpp b/src/aliceVision/image/imageCaching.hpp index ad41b4f048..2d72703d63 100644 --- a/src/aliceVision/image/imageCaching.hpp +++ b/src/aliceVision/image/imageCaching.hpp @@ -28,21 +28,21 @@ namespace aliceVision { namespace image { /** - * @brief A struct used to identify a cached image using its file description, color type info and half-sampling level. + * @brief A struct used to identify a cached image using its file description, color type info and downscale level. */ struct CacheKey { std::string filename; int nbChannels; oiio::TypeDesc::BASETYPE typeDesc; - int halfSampleLevel; + int downscaleLevel; std::time_t lastWriteTime; CacheKey(const std::string& path, int nchannels, oiio::TypeDesc::BASETYPE baseType, int level, std::time_t time) : filename(path), nbChannels(nchannels), typeDesc(baseType), - halfSampleLevel(level), + downscaleLevel(level), lastWriteTime(time) { } @@ -52,7 +52,7 @@ struct CacheKey return (filename == other.filename && nbChannels == other.nbChannels && typeDesc == other.typeDesc && - halfSampleLevel == other.halfSampleLevel && + downscaleLevel == other.downscaleLevel && lastWriteTime == other.lastWriteTime); } }; @@ -65,7 +65,7 @@ struct CacheKeyHasher boost::hash_combine(seed, key.filename); boost::hash_combine(seed, key.nbChannels); boost::hash_combine(seed, key.typeDesc); - boost::hash_combine(seed, key.halfSampleLevel); + boost::hash_combine(seed, key.downscaleLevel); boost::hash_combine(seed, key.lastWriteTime); return seed; } @@ -88,7 +88,6 @@ struct CacheInfo /// usage statistics int nbLoadFromDisk = 0; int nbLoadFromCache = 0; - int nbLoadFromHigherScale = 0; int nbRemoveUnused = 0; CacheInfo(float capacity_MiB, float maxSize_MiB) : @@ -193,11 +192,6 @@ inline std::shared_ptr> CacheValue::get() { return * or until there is nothing to remove * 5. if the image fits in the maximal size, load it, store it and return it * 6. the image is too big for the cache, throw an error. - * - * In the process described above, we also take advantage of the cache content when loading an image: - * if the same image with a lower half-sampling level (i.e. higher resolution) exists in the cache, - * we take the high-resolution version of the image from the cache and create and new downscaled version of it - * instead of loading the image from disk. */ class ImageCache { @@ -220,15 +214,15 @@ class ImageCache ImageCache& operator=(const ImageCache&) = delete; /** - * @brief Retrieve a cached image at a given half-sampling level. + * @brief Retrieve a cached image at a given downscale level. * @note This method is thread-safe. * @param[in] filename the image's filename on disk - * @param[in] halfSampleLevel the half-sampling level + * @param[in] downscaleLevel the downscale level * @return a shared pointer to the cached image * @throws std::runtime_error if the image does not fit in the maximal size of the cache */ template - std::shared_ptr> get(const std::string& filename, int halfSampleLevel); + std::shared_ptr> get(const std::string& filename, int downscaleLevel); /** * @return information on the current cache state and usage @@ -274,18 +268,18 @@ class ImageCache // their definition must be given in this header file template -std::shared_ptr> ImageCache::get(const std::string& filename, int halfSampleLevel) +std::shared_ptr> ImageCache::get(const std::string& filename, int downscaleLevel) { const std::lock_guard lock(_mutex); ALICEVISION_LOG_TRACE("[image] ImageCache: reading " << filename - << " with half-sampling level " << halfSampleLevel + << " with downscale level " << downscaleLevel << " from thread " << std::this_thread::get_id()); using TInfo = ColorTypeInfo; auto lastWriteTime = boost::filesystem::last_write_time(filename); - CacheKey keyReq(filename, TInfo::size, TInfo::typeDesc, halfSampleLevel, lastWriteTime); + CacheKey keyReq(filename, TInfo::size, TInfo::typeDesc, downscaleLevel, lastWriteTime); // find the requested image in the cached images { @@ -306,8 +300,7 @@ std::shared_ptr> ImageCache::get(const std::string& filename, int ha // retrieve image size int width, height; readImageSize(filename, width, height); - int downscale = 1 << halfSampleLevel; - unsigned long long int memSize = (width / downscale) * (height / downscale) * sizeof(TPix); + unsigned long long int memSize = (width / downscaleLevel) * (height / downscaleLevel) * sizeof(TPix); // add image to cache if it fits in capacity if (memSize + _info.contentSize <= _info.capacity) @@ -391,38 +384,13 @@ void ImageCache::load(const CacheKey& key) { auto img = std::make_shared>(); - // find the same image with a higher scale - auto it = std::find_if(_keys.begin(), _keys.end(), [&key](const CacheKey& k){ - return k.filename == key.filename && - k.nbChannels == key.nbChannels && - k.typeDesc == key.typeDesc && - k.halfSampleLevel < key.halfSampleLevel && - k.lastWriteTime == key.lastWriteTime; - }); - - if (it != _keys.end()) - { - // retrieve high-scale image from cache - const CacheKey& keyHighScale = *it; - CacheValue& valueHighScale = _imagePtrs.at(keyHighScale); - - // apply downscale - int downscale = 1 << (key.halfSampleLevel - keyHighScale.halfSampleLevel); - imageAlgo::resizeImage(downscale, *(valueHighScale.get()), *img); + // load image from disk + readImage(key.filename, *img, _options); - _info.nbLoadFromHigherScale++; - } - else - { - // load image from disk - readImage(key.filename, *img, _options); + // apply downscale + imageAlgo::resizeImage(key.downscaleLevel, *img); - // apply downscale - int downscale = 1 << key.halfSampleLevel; - imageAlgo::resizeImage(downscale, *img); - - _info.nbLoadFromDisk++; - } + _info.nbLoadFromDisk++; // create wrapper around shared pointer CacheValue value = CacheValue::wrap(img); diff --git a/src/aliceVision/image/imageCaching_test.cpp b/src/aliceVision/image/imageCaching_test.cpp index 064425c381..895f3b1212 100644 --- a/src/aliceVision/image/imageCaching_test.cpp +++ b/src/aliceVision/image/imageCaching_test.cpp @@ -40,19 +40,6 @@ BOOST_AUTO_TEST_CASE(load_image_twice) { BOOST_CHECK_EQUAL(cache.info().nbLoadFromCache, 1); } -BOOST_AUTO_TEST_CASE(reload_lower_scale) { - ImageCache cache(256, 1024, EImageColorSpace::LINEAR); - const std::string filename = std::string(THIS_SOURCE_DIR) + "/image_test/lena.png"; - auto img1 = cache.get(filename, 0); - auto img2 = cache.get(filename, 1); - BOOST_CHECK_EQUAL(img1->Width() / 2, img2->Width()); - BOOST_CHECK_EQUAL(img1->Height() / 2, img2->Height()); - BOOST_CHECK_EQUAL(cache.info().nbImages, 2); - BOOST_CHECK_EQUAL(cache.info().contentSize, img1->MemorySize() + img2->MemorySize()); - BOOST_CHECK_EQUAL(cache.info().nbLoadFromDisk, 1); - BOOST_CHECK_EQUAL(cache.info().nbLoadFromHigherScale, 1); -} - BOOST_AUTO_TEST_CASE(load_all_pixel_types) { ImageCache cache(256, 1024, EImageColorSpace::LINEAR); const std::string filename = std::string(THIS_SOURCE_DIR) + "/image_test/lena.png"; diff --git a/src/samples/imageCaching/main_imageCaching.cpp b/src/samples/imageCaching/main_imageCaching.cpp index a730be0dc9..a8ee6dfcc3 100644 --- a/src/samples/imageCaching/main_imageCaching.cpp +++ b/src/samples/imageCaching/main_imageCaching.cpp @@ -32,8 +32,8 @@ int aliceVision_main(int argc, char **argv) float capacity = 256.f; float maxSize = 1024.f; std::vector filenames; - std::vector halfSampleLevels; - int defaultHalfSampleLevel = 0; + std::vector downscaleLevels; + int defaultDownscaleLevel = 1; std::vector pixelTypes; int defaultPixelType = 5; int nbThreads = 1; @@ -50,10 +50,10 @@ int aliceVision_main(int argc, char **argv) "Cache capacity") ("maxSize", po::value(&maxSize)->default_value(maxSize), "Cache max size") - ("halfSampleLevels", po::value>(&halfSampleLevels)->multitoken()->default_value(halfSampleLevels), - "Half-sampling levels") - ("defaultHalfSampleLevel", po::value(&defaultHalfSampleLevel)->default_value(defaultHalfSampleLevel), - "Default half-sampling level") + ("downscaleLevels", po::value>(&downscaleLevels)->multitoken()->default_value(downscaleLevels), + "Downscale levels") + ("defaultDownscaleLevel", po::value(&defaultDownscaleLevel)->default_value(defaultDownscaleLevel), + "Default downscale level") ("pixelTypes", po::value>(&pixelTypes)->multitoken()->default_value(pixelTypes), "Pixel types:" "\n * 0: unsigned char" @@ -90,7 +90,7 @@ int aliceVision_main(int argc, char **argv) for (int j = 0; j < filenames.size(); j++) { const std::string& filename = filenames[j]; - const int level = (j < halfSampleLevels.size()) ? halfSampleLevels[j] : defaultHalfSampleLevel; + const int level = (j < downscaleLevels.size()) ? downscaleLevels[j] : defaultDownscaleLevel; const int pixelType = (j < pixelTypes.size()) ? pixelTypes[j] : defaultPixelType; switch (pixelType) {