Merge pull request #19780 from HarryDC:feature/index-multiimage-tiff
authorHarald Scheirich <hscheirich@yahoo.com>
Fri, 23 Apr 2021 20:48:32 +0000 (16:48 -0400)
committerGitHub <noreply@github.com>
Fri, 23 Apr 2021 20:48:32 +0000 (20:48 +0000)
Add reading of specific images from multipage tiff

* Add reading of specific images from multipage tiff

* Fix build issues

* Add missing flag for gdal

* Fix unused param warning

* Remove duplicated code

* change public parameter type to int

* Fix warnings

* Fix parameter check

modules/imgcodecs/include/opencv2/imgcodecs.hpp
modules/imgcodecs/src/loadsave.cpp
modules/imgcodecs/test/test_tiff.cpp

index 6a389fd47179510f6d3e75305b919b67ad2b031c..42227f3788e5f96508c1e64bf41f0935bb443025 100644 (file)
@@ -215,6 +215,26 @@ The function imreadmulti loads a multi-page image from the specified file into a
 */
 CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector<Mat>& mats, int flags = IMREAD_ANYCOLOR);
 
+/** @brief Loads a of images of a multi-page image from a file.
+
+The function imreadmulti loads a specified range from a multi-page image from the specified file into a vector of Mat objects.
+@param filename Name of file to be loaded.
+@param start Start index of the image to load
+@param count Count number of images to load
+@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR.
+@param mats A vector of Mat objects holding each page, if more than one.
+@sa cv::imread
+*/
+CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector<Mat>& mats, int start, int count, int flags = IMREAD_ANYCOLOR);
+
+/** @brief Returns the number of images inside the give file
+
+The function imcount will return the number of pages in a multi-page image, or 1 for single-page images
+@param filename Name of file to be loaded.
+@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR.
+*/
+CV_EXPORTS_W size_t imcount(const String& filename, int flags = IMREAD_ANYCOLOR);
+
 /** @brief Saves an image to a specified file.
 
 The function imwrite saves the image to the specified file. The image format is chosen based on the
index 350042cd7d304261a578fd22f9d3284254e62c1e..28d8ff285bd45d76365560fa4acb647c9abcd028 100644 (file)
@@ -495,25 +495,19 @@ imread_( const String& filename, int flags, Mat& mat )
 }
 
 
-/**
-* Read an image into memory and return the information
-*
-* @param[in] filename File to load
-* @param[in] flags Flags
-* @param[in] mats Reference to C++ vector<Mat> object to hold the images
-*
-*/
 static bool
-imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats)
+imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats, int start, int count)
 {
     /// Search for the relevant decoder to handle the imagery
     ImageDecoder decoder;
 
+    CV_CheckGE(start, 0, "Start index cannont be < 0");
+
 #ifdef HAVE_GDAL
-    if (flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL){
+    if (flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) {
         decoder = GdalDecoder().newDecoder();
     }
-    else{
+    else {
 #endif
         decoder = findDecoder(filename);
 #ifdef HAVE_GDAL
@@ -521,10 +515,14 @@ imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats)
 #endif
 
     /// if no decoder was found, return nothing.
-    if (!decoder){
+    if (!decoder) {
         return 0;
     }
 
+    if (count < 0) {
+        count = std::numeric_limits<int>::max();
+    }
+
     /// set the filename in the driver
     decoder->setSource(filename);
 
@@ -532,7 +530,7 @@ imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats)
     try
     {
         // read the header to make sure it succeeds
-        if( !decoder->readHeader() )
+        if (!decoder->readHeader())
             return 0;
     }
     catch (const cv::Exception& e)
@@ -546,11 +544,22 @@ imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats)
         return 0;
     }
 
-    for (;;)
+    int current = start;
+
+    while (current > 0)
+    {
+        if (!decoder->nextPage())
+        {
+            return false;
+        }
+        --current;
+    }
+
+    while (current < count)
     {
         // grab the decoded type
         int type = decoder->type();
-        if( (flags & IMREAD_LOAD_GDAL) != IMREAD_LOAD_GDAL && flags != IMREAD_UNCHANGED )
+        if ((flags & IMREAD_LOAD_GDAL) != IMREAD_LOAD_GDAL && flags != IMREAD_UNCHANGED)
         {
             if ((flags & IMREAD_ANYDEPTH) == 0)
                 type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type));
@@ -585,7 +594,7 @@ imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats)
             break;
 
         // optionally rotate the data if EXIF' orientation flag says so
-        if( (flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED )
+        if ((flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED)
         {
             ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat);
         }
@@ -595,6 +604,7 @@ imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats)
         {
             break;
         }
+        ++current;
     }
 
     return !mats.empty();
@@ -636,9 +646,81 @@ bool imreadmulti(const String& filename, std::vector<Mat>& mats, int flags)
 {
     CV_TRACE_FUNCTION();
 
-    return imreadmulti_(filename, flags, mats);
+    return imreadmulti_(filename, flags, mats, 0, -1);
 }
 
+
+bool imreadmulti(const String& filename, std::vector<Mat>& mats, int start, int count, int flags)
+{
+    CV_TRACE_FUNCTION();
+
+    return imreadmulti_(filename, flags, mats, start, count);
+}
+
+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;
+        return 0;
+    }
+
+    size_t result = 1;
+
+
+    while (decoder->nextPage())
+    {
+        ++result;
+    }
+
+    return result;
+}
+
+size_t imcount(const String& filename, int flags)
+{
+    CV_TRACE_FUNCTION();
+
+    return imcount_(filename, flags);
+}
+
+
 static bool imwrite_( const String& filename, const std::vector<Mat>& img_vec,
                       const std::vector<int>& params, bool flipv )
 {
index 2c6fb6249b7eb017299d384612246734aff4ac3b..dec38014aa2882081f08a3ab4434cf316384d91f 100644 (file)
@@ -358,6 +358,94 @@ TEST(Imgcodecs_Tiff, decode_black_and_write_image_pr17275_default)
     EXPECT_EQ(CV_8UC3, img.type()) << cv::typeToString(img.type());
 }
 
+TEST(Imgcodecs_Tiff, count_multipage)
+{
+    const string root = cvtest::TS::ptr()->get_data_path();
+    {
+        const string filename = root + "readwrite/multipage.tif";
+        ASSERT_EQ((size_t)6, imcount(filename));
+    }
+    {
+        const string filename = root + "readwrite/test32FC3_raw.tiff";
+        ASSERT_EQ((size_t)1, imcount(filename));
+    }
+}
+
+TEST(Imgcodecs_Tiff, read_multipage_indexed)
+{
+    const string root = cvtest::TS::ptr()->get_data_path();
+    const string filename = root + "readwrite/multipage.tif";
+    const string page_files[] = {
+        "readwrite/multipage_p1.tif",
+        "readwrite/multipage_p2.tif",
+        "readwrite/multipage_p3.tif",
+        "readwrite/multipage_p4.tif",
+        "readwrite/multipage_p5.tif",
+        "readwrite/multipage_p6.tif"
+    };
+    const int page_count = sizeof(page_files) / sizeof(page_files[0]);
+    vector<Mat> single_pages;
+    for (int i = 0; i < page_count; i++)
+    {
+        // imread and imreadmulti have different default values for the flag
+        const Mat page = imread(root + page_files[i], IMREAD_ANYCOLOR);
+        single_pages.push_back(page);
+    }
+    ASSERT_EQ((size_t)page_count, single_pages.size());
+
+    {
+        SCOPED_TRACE("Edge Cases");
+        vector<Mat> multi_pages;
+        bool res = imreadmulti(filename, multi_pages, 0, 0);
+        // If we asked for 0 images and we successfully read 0 images should this be false ?
+        ASSERT_TRUE(res == false);
+        ASSERT_EQ((size_t)0, multi_pages.size());
+        res = imreadmulti(filename, multi_pages, 0, 123123);
+        ASSERT_TRUE(res == true);
+        ASSERT_EQ((size_t)6, multi_pages.size());
+    }
+
+    {
+        SCOPED_TRACE("Read all with indices");
+        vector<Mat> multi_pages;
+        bool res = imreadmulti(filename, multi_pages, 0, 6);
+        ASSERT_TRUE(res == true);
+        ASSERT_EQ((size_t)page_count, multi_pages.size());
+        for (int i = 0; i < page_count; i++)
+        {
+            EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), multi_pages[i], single_pages[i]);
+        }
+    }
+
+    {
+        SCOPED_TRACE("Read one by one");
+        vector<Mat> multi_pages;
+        for (int i = 0; i < page_count; i++)
+        {
+            bool res = imreadmulti(filename, multi_pages, i, 1);
+            ASSERT_TRUE(res == true);
+            ASSERT_EQ((size_t)1, multi_pages.size());
+            EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), multi_pages[0], single_pages[i]);
+            multi_pages.clear();
+        }
+    }
+
+    {
+        SCOPED_TRACE("Read multiple at a time");
+        vector<Mat> multi_pages;
+        for (int i = 0; i < page_count/2; i++)
+        {
+            bool res = imreadmulti(filename, multi_pages, i*2, 2);
+            ASSERT_TRUE(res == true);
+            ASSERT_EQ((size_t)2, multi_pages.size());
+            EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), multi_pages[0], single_pages[i * 2]) << i;
+            EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), multi_pages[1], single_pages[i * 2 + 1]);
+            multi_pages.clear();
+        }
+    }
+}
+
+
 #endif
 
 }} // namespace