*/
CV_EXPORTS_W bool haveImageWriter( const String& filename );
+/** @brief To read Multi Page images on demand
+
+The ImageCollection class provides iterator API to read multi page images on demand. Create iterator
+to the collection of the images and iterate over the collection. Decode the necessary page with operator*.
+
+The performance of page decoding is O(1) if collection is increment sequentially. If the user wants to access random page,
+then the time Complexity is O(n) because the collection has to be reinitialized every time in order to go to the correct page.
+However, the intermediate pages are not decoded during the process, so typically it's quite fast.
+This is required because multipage codecs does not support going backwards.
+After decoding the one page, it is stored inside the collection cache. Hence, trying to get Mat object from already decoded page is O(1).
+If you need memory, you can use .releaseCache() method to release cached index.
+The space complexity is O(n) if all pages are decoded into memory. The user is able to decode and release images on demand.
+*/
+class CV_EXPORTS ImageCollection {
+public:
+ struct CV_EXPORTS iterator {
+ iterator(ImageCollection* col);
+ iterator(ImageCollection* col, int end);
+ Mat& operator*();
+ Mat* operator->();
+ iterator& operator++();
+ iterator operator++(int);
+ friend bool operator== (const iterator& a, const iterator& b) { return a.m_curr == b.m_curr; };
+ friend bool operator!= (const iterator& a, const iterator& b) { return a.m_curr != b.m_curr; };
+
+ private:
+ ImageCollection* m_pCollection;
+ int m_curr;
+ };
+
+ ImageCollection();
+ ImageCollection(const String& filename, int flags);
+ void init(const String& img, int flags);
+ size_t size() const;
+ const Mat& at(int index);
+ const Mat& operator[](int index);
+ void releaseCache(int index);
+ iterator begin();
+ iterator end();
+
+ class Impl;
+ Ptr<Impl> getImpl();
+protected:
+ Ptr<Impl> pImpl;
+};
//! @} imgcodecs
#include <cerrno>
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/core/utils/configuration.private.hpp>
+#include <opencv2/imgcodecs.hpp>
+
/****************************************************************************************\
static
size_t imcount_(const String& filename, int flags)
{
- /// Search for the relevant decoder to handle the imagery
- ImageDecoder decoder;
-
-#ifdef HAVE_GDAL
- if (flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) {
- decoder = GdalDecoder().newDecoder();
- }
- else {
-#else
- CV_UNUSED(flags);
-#endif
- decoder = findDecoder(filename);
-#ifdef HAVE_GDAL
- }
-#endif
-
- /// if no decoder was found, return nothing.
- if (!decoder) {
- return 0;
- }
-
- /// set the filename in the driver
- decoder->setSource(filename);
-
- // read the header to make sure it succeeds
- try
- {
- // read the header to make sure it succeeds
- if (!decoder->readHeader())
- return 0;
- }
- catch (const cv::Exception& e)
- {
- std::cerr << "imcount_('" << filename << "'): can't read header: " << e.what() << std::endl << std::flush;
- return 0;
- }
- catch (...)
- {
- std::cerr << "imcount_('" << filename << "'): can't read header: unknown exception" << std::endl << std::flush;
+ try{
+ ImageCollection collection(filename, flags);
+ return collection.size();
+ } catch(cv::Exception const& e) {
+ // Reading header or finding decoder for the filename is failed
return 0;
}
-
- size_t result = 1;
-
-
- while (decoder->nextPage())
- {
- ++result;
- }
-
- return result;
+ return 0;
}
size_t imcount(const String& filename, int flags)
return !encoder.empty();
}
+class ImageCollection::Impl {
+public:
+ Impl() = default;
+ Impl(const std::string& filename, int flags);
+ void init(String const& filename, int flags);
+ size_t size() const;
+ Mat& at(int index);
+ Mat& operator[](int index);
+ void releaseCache(int index);
+ ImageCollection::iterator begin(ImageCollection* ptr);
+ ImageCollection::iterator end(ImageCollection* ptr);
+ Mat read();
+ int width() const;
+ int height() const;
+ bool readHeader();
+ Mat readData();
+ bool advance();
+ int currentIndex() const;
+ void reset();
+
+private:
+ String m_filename;
+ int m_flags{};
+ std::size_t m_size{};
+ int m_width{};
+ int m_height{};
+ int m_current{};
+ std::vector<cv::Mat> m_pages;
+ ImageDecoder m_decoder;
+};
+
+ImageCollection::Impl::Impl(std::string const& filename, int flags) {
+ this->init(filename, flags);
+}
+
+void ImageCollection::Impl::init(String const& filename, int flags) {
+ m_filename = filename;
+ m_flags = flags;
+
+#ifdef HAVE_GDAL
+ if (m_flags != IMREAD_UNCHANGED && (m_flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) {
+ m_decoder = GdalDecoder().newDecoder();
+ }
+ else {
+#endif
+ m_decoder = findDecoder(filename);
+#ifdef HAVE_GDAL
+ }
+#endif
+
+
+ CV_Assert(m_decoder);
+ m_decoder->setSource(filename);
+ CV_Assert(m_decoder->readHeader());
+
+ // count the pages of the image collection
+ size_t count = 1;
+ while(m_decoder->nextPage()) count++;
+
+ m_size = count;
+ m_pages.resize(m_size);
+ // Reinitialize the decoder because we advanced to the last page while counting the pages of the image
+#ifdef HAVE_GDAL
+ if (m_flags != IMREAD_UNCHANGED && (m_flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) {
+ m_decoder = GdalDecoder().newDecoder();
+ }
+ else {
+#endif
+ m_decoder = findDecoder(m_filename);
+#ifdef HAVE_GDAL
+ }
+#endif
+
+ m_decoder->setSource(m_filename);
+ m_decoder->readHeader();
+}
+
+size_t ImageCollection::Impl::size() const { return m_size; }
+
+Mat ImageCollection::Impl::read() {
+ auto result = this->readHeader();
+ if(!result) {
+ return {};
+ }
+ return this->readData();
+}
+
+int ImageCollection::Impl::width() const {
+ return m_width;
+}
+
+int ImageCollection::Impl::height() const {
+ return m_height;
+}
+
+bool ImageCollection::Impl::readHeader() {
+ bool status = m_decoder->readHeader();
+ m_width = m_decoder->width();
+ m_height = m_decoder->height();
+ return status;
+}
+
+// readHeader must be called before calling this method
+Mat ImageCollection::Impl::readData() {
+ int type = m_decoder->type();
+ if ((m_flags & IMREAD_LOAD_GDAL) != IMREAD_LOAD_GDAL && m_flags != IMREAD_UNCHANGED) {
+ if ((m_flags & IMREAD_ANYDEPTH) == 0)
+ type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type));
+
+ if ((m_flags & IMREAD_COLOR) != 0 ||
+ ((m_flags & IMREAD_ANYCOLOR) != 0 && CV_MAT_CN(type) > 1))
+ type = CV_MAKETYPE(CV_MAT_DEPTH(type), 3);
+ else
+ type = CV_MAKETYPE(CV_MAT_DEPTH(type), 1);
+ }
+
+ // established the required input image size
+ Size size = validateInputImageSize(Size(m_width, m_height));
+
+ Mat mat(size.height, size.width, type);
+ bool success = false;
+ try {
+ if (m_decoder->readData(mat))
+ success = true;
+ }
+ catch (const cv::Exception &e) {
+ std::cerr << "ImageCollection class: can't read data: " << e.what() << std::endl << std::flush;
+ }
+ catch (...) {
+ std::cerr << "ImageCollection class:: can't read data: unknown exception" << std::endl << std::flush;
+ }
+ if (!success)
+ return cv::Mat();
+
+ if ((m_flags & IMREAD_IGNORE_ORIENTATION) == 0 && m_flags != IMREAD_UNCHANGED) {
+ ApplyExifOrientation(m_decoder->getExifTag(ORIENTATION), mat);
+ }
+
+ return mat;
+}
+
+bool ImageCollection::Impl::advance() { ++m_current; return m_decoder->nextPage(); }
+
+int ImageCollection::Impl::currentIndex() const { return m_current; }
+
+ImageCollection::iterator ImageCollection::Impl::begin(ImageCollection* ptr) { return ImageCollection::iterator(ptr); }
+
+ImageCollection::iterator ImageCollection::Impl::end(ImageCollection* ptr) { return ImageCollection::iterator(ptr, this->size()); }
+
+void ImageCollection::Impl::reset() {
+ m_current = 0;
+#ifdef HAVE_GDAL
+ if (m_flags != IMREAD_UNCHANGED && (m_flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) {
+ m_decoder = GdalDecoder().newDecoder();
+ }
+ else {
+#endif
+ m_decoder = findDecoder(m_filename);
+#ifdef HAVE_GDAL
+ }
+#endif
+
+ m_decoder->setSource(m_filename);
+ m_decoder->readHeader();
+}
+
+Mat& ImageCollection::Impl::at(int index) {
+ CV_Assert(index >= 0 && size_t(index) < m_size);
+ return operator[](index);
+}
+
+Mat& ImageCollection::Impl::operator[](int index) {
+ if(m_pages.at(index).empty()) {
+ // We can't go backward in multi images. If the page is not in vector yet,
+ // go back to first page and advance until the desired page and read it into memory
+ if(m_current != index) {
+ reset();
+ for(int i = 0; i != index && advance(); ++i) {}
+ }
+ m_pages[index] = read();
+ }
+ return m_pages[index];
+}
+
+void ImageCollection::Impl::releaseCache(int index) {
+ CV_Assert(index >= 0 && size_t(index) < m_size);
+ m_pages[index].release();
+}
+
+/* ImageCollection API*/
+
+ImageCollection::ImageCollection() : pImpl(new Impl()) {}
+
+ImageCollection::ImageCollection(const std::string& filename, int flags) : pImpl(new Impl(filename, flags)) {}
+
+void ImageCollection::init(const String& img, int flags) { pImpl->init(img, flags); }
+
+size_t ImageCollection::size() const { return pImpl->size(); }
+
+const Mat& ImageCollection::at(int index) { return pImpl->at(index); }
+
+const Mat& ImageCollection::operator[](int index) { return pImpl->operator[](index); }
+
+void ImageCollection::releaseCache(int index) { pImpl->releaseCache(index); }
+
+Ptr<ImageCollection::Impl> ImageCollection::getImpl() { return pImpl; }
+
+/* Iterator API */
+
+ImageCollection::iterator ImageCollection::begin() { return pImpl->begin(this); }
+
+ImageCollection::iterator ImageCollection::end() { return pImpl->end(this); }
+
+ImageCollection::iterator::iterator(ImageCollection* col) : m_pCollection(col), m_curr(0) {}
+
+ImageCollection::iterator::iterator(ImageCollection* col, int end) : m_pCollection(col), m_curr(end) {}
+
+Mat& ImageCollection::iterator::operator*() {
+ CV_Assert(m_pCollection);
+ return m_pCollection->getImpl()->operator[](m_curr);
+}
+
+Mat* ImageCollection::iterator::operator->() {
+ CV_Assert(m_pCollection);
+ return &m_pCollection->getImpl()->operator[](m_curr);
+}
+
+ImageCollection::iterator& ImageCollection::iterator::operator++() {
+ if(m_pCollection->pImpl->currentIndex() == m_curr) {
+ m_pCollection->pImpl->advance();
+ }
+ m_curr++;
+ return *this;
+}
+
+ImageCollection::iterator ImageCollection::iterator::operator++(int) {
+ iterator tmp = *this;
+ ++(*this);
+ return tmp;
+}
+
}
/* End of file. */
EXPECT_EQ(0, remove(dst_name.c_str()));
}
+TEST(Imgcodecs_Image, multipage_collection_size)
+{
+ const string root = cvtest::TS::ptr()->get_data_path();
+ const string filename = root + "readwrite/multipage.tif";
+
+ ImageCollection collection(filename, IMREAD_ANYCOLOR);
+ EXPECT_EQ((std::size_t)6, collection.size());
+}
+
+TEST(Imgcodecs_Image, multipage_collection_read_pages_iterator)
+{
+ const string root = cvtest::TS::ptr()->get_data_path();
+ const string filename = root + "readwrite/multipage.tif";
+ const string page_files[] = {
+ root + "readwrite/multipage_p1.tif",
+ root + "readwrite/multipage_p2.tif",
+ root + "readwrite/multipage_p3.tif",
+ root + "readwrite/multipage_p4.tif",
+ root + "readwrite/multipage_p5.tif",
+ root + "readwrite/multipage_p6.tif"
+ };
+
+ ImageCollection collection(filename, IMREAD_ANYCOLOR);
+
+ auto collectionBegin = collection.begin();
+ for(size_t i = 0; i < collection.size(); ++i, ++collectionBegin)
+ {
+ double diff = cv::norm(collectionBegin.operator*(), imread(page_files[i]), NORM_INF);
+ EXPECT_EQ(0., diff);
+ }
+}
+
+TEST(Imgcodecs_Image, multipage_collection_two_iterator)
+{
+ const string root = cvtest::TS::ptr()->get_data_path();
+ const string filename = root + "readwrite/multipage.tif";
+ const string page_files[] = {
+ root + "readwrite/multipage_p1.tif",
+ root + "readwrite/multipage_p2.tif",
+ root + "readwrite/multipage_p3.tif",
+ root + "readwrite/multipage_p4.tif",
+ root + "readwrite/multipage_p5.tif",
+ root + "readwrite/multipage_p6.tif"
+ };
+
+ ImageCollection collection(filename, IMREAD_ANYCOLOR);
+ auto firstIter = collection.begin();
+ auto secondIter = collection.begin();
+
+ // Decode all odd pages then decode even pages -> 1, 0, 3, 2 ...
+ firstIter++;
+ for(size_t i = 1; i < collection.size(); i += 2, ++firstIter, ++firstIter, ++secondIter, ++secondIter) {
+ Mat mat = *firstIter;
+ double diff = cv::norm(mat, imread(page_files[i]), NORM_INF);
+ EXPECT_EQ(0., diff);
+ Mat evenMat = *secondIter;
+ diff = cv::norm(evenMat, imread(page_files[i-1]), NORM_INF);
+ EXPECT_EQ(0., diff);
+ }
+}
+
+TEST(Imgcodecs_Image, multipage_collection_operator_plusplus)
+{
+ const string root = cvtest::TS::ptr()->get_data_path();
+ const string filename = root + "readwrite/multipage.tif";
+
+ // operator++ test
+ ImageCollection collection(filename, IMREAD_ANYCOLOR);
+ auto firstIter = collection.begin();
+ auto secondIter = firstIter++;
+
+ // firstIter points to second page, secondIter points to first page
+ double diff = cv::norm(*firstIter, *secondIter, NORM_INF);
+ EXPECT_NE(diff, 0.);
+}
+
+TEST(Imgcodecs_Image, multipage_collection_backward_decoding)
+{
+ const string root = cvtest::TS::ptr()->get_data_path();
+ const string filename = root + "readwrite/multipage.tif";
+ const string page_files[] = {
+ root + "readwrite/multipage_p1.tif",
+ root + "readwrite/multipage_p2.tif",
+ root + "readwrite/multipage_p3.tif",
+ root + "readwrite/multipage_p4.tif",
+ root + "readwrite/multipage_p5.tif",
+ root + "readwrite/multipage_p6.tif"
+ };
+
+ ImageCollection collection(filename, IMREAD_ANYCOLOR);
+ EXPECT_EQ((size_t)6, collection.size());
+
+ // backward decoding -> 5,4,3,2,1,0
+ for(int i = (int)collection.size() - 1; i >= 0; --i)
+ {
+ cv::Mat ithPage = imread(page_files[i]);
+ EXPECT_FALSE(ithPage.empty());
+ double diff = cv::norm(collection[i], ithPage, NORM_INF);
+ EXPECT_EQ(diff, 0.);
+ }
+
+ for(int i = 0; i < (int)collection.size(); ++i)
+ {
+ collection.releaseCache(i);
+ }
+
+ double diff = cv::norm(collection[2], imread(page_files[2]), NORM_INF);
+ EXPECT_EQ(diff, 0.);
+}
+
+TEST(ImgCodecs, multipage_collection_decoding_range_based_for_loop_test)
+{
+ const string root = cvtest::TS::ptr()->get_data_path();
+ const string filename = root + "readwrite/multipage.tif";
+ const string page_files[] = {
+ root + "readwrite/multipage_p1.tif",
+ root + "readwrite/multipage_p2.tif",
+ root + "readwrite/multipage_p3.tif",
+ root + "readwrite/multipage_p4.tif",
+ root + "readwrite/multipage_p5.tif",
+ root + "readwrite/multipage_p6.tif"
+ };
+
+ ImageCollection collection(filename, IMREAD_ANYCOLOR);
+
+ size_t index = 0;
+ for(auto &i: collection)
+ {
+ cv::Mat ithPage = imread(page_files[index]);
+ EXPECT_FALSE(ithPage.empty());
+ double diff = cv::norm(i, ithPage, NORM_INF);
+ EXPECT_EQ(0., diff);
+ ++index;
+ }
+ EXPECT_EQ(index, collection.size());
+
+ index = 0;
+ for(auto &&i: collection)
+ {
+ cv::Mat ithPage = imread(page_files[index]);
+ EXPECT_FALSE(ithPage.empty());
+ double diff = cv::norm(i, ithPage, NORM_INF);
+ EXPECT_EQ(0., diff);
+ ++index;
+ }
+ EXPECT_EQ(index, collection.size());
+}
+
+TEST(ImgCodecs, multipage_collection_two_iterator_operatorpp)
+{
+ const string root = cvtest::TS::ptr()->get_data_path();
+ const string filename = root + "readwrite/multipage.tif";
+
+ ImageCollection imcol(filename, IMREAD_ANYCOLOR);
+
+ auto it0 = imcol.begin(), it1 = it0, it2 = it0;
+ vector<Mat> img(6);
+ for (int i = 0; i < 6; i++) {
+ img[i] = *it0;
+ it0->release();
+ ++it0;
+ }
+
+ for (int i = 0; i < 3; i++) {
+ ++it2;
+ }
+
+ for (int i = 0; i < 3; i++) {
+ auto img2 = *it2;
+ auto img1 = *it1;
+ ++it2;
+ ++it1;
+ EXPECT_TRUE(cv::norm(img2, img[i+3], NORM_INF) == 0);
+ EXPECT_TRUE(cv::norm(img1, img[i], NORM_INF) == 0);
+ }
+}
+
}} // namespace