Skip to content

Commit

Permalink
Merge pull request opencv#19780 from HarryDC:feature/index-multiimage…
Browse files Browse the repository at this point in the history
…-tiff

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
  • Loading branch information
HarryDC authored and Sajjad Ali committed Mar 27, 2023
1 parent 3ebe33e commit 17e82ab
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 17 deletions.
20 changes: 20 additions & 0 deletions modules/imgcodecs/include/opencv2/imgcodecs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,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
Expand Down
116 changes: 99 additions & 17 deletions modules/imgcodecs/src/loadsave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -498,44 +498,42 @@ 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
}
#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);

// read the header to make sure it succeeds
try
{
// read the header to make sure it succeeds
if( !decoder->readHeader() )
if (!decoder->readHeader())
return 0;
}
catch (const cv::Exception& e)
Expand All @@ -549,11 +547,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));
Expand Down Expand Up @@ -588,7 +597,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);
}
Expand All @@ -598,6 +607,7 @@ imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats)
{
break;
}
++current;
}

return !mats.empty();
Expand Down Expand Up @@ -639,9 +649,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 )
{
Expand Down
88 changes: 88 additions & 0 deletions modules/imgcodecs/test/test_tiff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,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

0 comments on commit 17e82ab

Please sign in to comment.