Skip to content

implementation closer to TIFF-spec, less boilerplate-code #27412

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: 4.x
Choose a base branch
from

Conversation

janstarzy
Copy link
Contributor

Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

  • I agree to contribute to the project under Apache 2 License.
  • To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
  • [ X] The PR is proposed to the proper branch
  • [X ] There is a reference to the original bug report and related work: none existing
  • [ X] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
    Patch to opencv_extra has the same branch name: covered by existing tests
  • [X ] The feature is well documented and sample code can be built with the project CMake

From the TIFF-Spec:

SamplesPerPixel
...
SamplesPerPixel is usually 1 for bilevel, grayscale, and palette-color images.
SamplesPerPixel is usually 3 for RGB images.
Default = 1. See also BitsPerSample

So the default is 1 unless RGB (not 3 as before).

BitsPerSample
...
Default = 1. See also SamplesPerPixel.

The original code does this with more lines of code. I wondered, why it was different to the retrieval of SamplesPerPixel.

@asmorkalov
Copy link
Contributor

@janstarzy Could you describe you issue and what problem do you solve? Problematic code / image are useful too.

@janstarzy
Copy link
Contributor Author

This does not fix a visible bug, but it gets the implementation closer to the TIFF-spec for SamplesPerPixel (as described at the bottom of the description. The change concerning BitsPerSample is a matter of clean code, achieving the same effect with less lines of code. I wondered, why it was handled differently to SamplesPerPixel.

@janstarzy janstarzy force-pushed the tif_bitsperpixel_boilerplate branch from 4785ea4 to 3f1eb2a Compare June 6, 2025 20:55
@Kumataro
Copy link
Contributor

Kumataro commented Jun 7, 2025

Hello, this fix may changes ncn(number of channels) and bpp(bit per pixel) behaviours.
And this change contains to revert #17275 .

The change concerning BitsPerSample is a matter of clean code, achieving the same effect with less lines of code.

ncn ( number of channnels)

TIFFTAG_PHOTOMETRIC can be not only RGB but also YCBCR, LAB for color images.

To simplify, ncn = isGrayScale ? 1 : 3 ; is better for non-RGB color spaces.

#define TIFFTAG_PHOTOMETRIC 262 /* photometric interpretation */
#define PHOTOMETRIC_MINISWHITE 0 /* min value is white */
#define PHOTOMETRIC_MINISBLACK 1 /* min value is black */
#define PHOTOMETRIC_RGB 2 /* RGB color model */
#define PHOTOMETRIC_PALETTE 3 /* color map indexed */
#define PHOTOMETRIC_MASK 4 /* $holdout mask */
#define PHOTOMETRIC_SEPARATED 5 /* !color separations */
#define PHOTOMETRIC_YCBCR 6 /* !CCIR 601 */
#define PHOTOMETRIC_CIELAB 8 /* !1976 CIE L*a*b* */
#define PHOTOMETRIC_ICCLAB 9 /* ICC L*a*b* [Adobe TIFF Technote 4] */
#define PHOTOMETRIC_ITULAB 10 /* ITU L*a*b* */
#define PHOTOMETRIC_CFA 32803 /* color filter array */
#define PHOTOMETRIC_LOGL 32844 /* CIE Log2(L) */
#define PHOTOMETRIC_LOGLUV 32845 /* CIE Log2(L) (u',v') */

CV_TIFF_CHECK_CALL_DEBUG()

uint16_t bpp = 8, ncn = isGrayScale ? 1 : 3;
if (0 == TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bpp))
{
// TIFF bi-level images don't require TIFFTAG_BITSPERSAMPLE tag
bpp = 1;
}
CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &ncn));

If TIFFGetField() retrurns non-zero, bpp is updated with new value. It is OK.
If not, I couldn't find what the bpp value should be in the libtiff documentation. ( https://libtiff.gitlab.io/libtiff/functions/TIFFGetField.html#c.TIFFGetField ).
With strongly strictly, it will be undefined value.

Currently implemtation write over to default value, so bpp should not be undefined value.
I felt that bpp implemantation is more preferable than ncn.

@janstarzy janstarzy force-pushed the tif_bitsperpixel_boilerplate branch 3 times, most recently from d9a0726 to 091d8f2 Compare June 8, 2025 22:14
@janstarzy
Copy link
Contributor Author

janstarzy commented Jun 10, 2025

Hi, I was not quoting libtiff, but the TIFF 6.0 Spec. There the default values are defined.
TIFF6.pdf

@janstarzy
Copy link
Contributor Author

ncn = isGrayScale ? 1 : 3 fails to load PHOTOMETRIC_PALETTE (ncn=1).

@janstarzy
Copy link
Contributor Author

The longer I think about the matter, the more I come to the conclusion ncn = 1 is the correct implementation. The Spec states "SamplesPerPixel is usually 3 for RGB images". It does not say "always", so we should stick to 1.

@Kumataro
Copy link
Contributor

I'm very sorry, but I also find it difficult to understand what you were trying to solve with this pull request.

ncn = isGrayScale ? 1 : 3 fails to load PHOTOMETRIC_PALETTE (ncn=1).

CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric));
{
bool isGrayScale = photometric == PHOTOMETRIC_MINISWHITE || photometric == PHOTOMETRIC_MINISBLACK;
uint16_t bpp = 8, ncn = isGrayScale ? 1 : 3;
if (0 == TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bpp))
{
// TIFF bi-level images don't require TIFFTAG_BITSPERSAMPLE tag
bpp = 1;
}
CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &ncn));
m_width = wdth;
m_height = hght;
m_frame_count = TIFFNumberOfDirectories(tif);
if (ncn == 3 && photometric == PHOTOMETRIC_LOGLUV)
{
m_type = CV_32FC3;
m_hdr = true;
return true;
}
m_hdr = false;
if( bpp > 8 &&
((photometric > 2) ||
(ncn != 1 && ncn != 3 && ncn != 4)))
bpp = 8;
uint16_t sample_format = SAMPLEFORMAT_UINT;
TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sample_format);
int wanted_channels = normalizeChannelsNumber(ncn);

My understanding is ...

  • isGrayScale shall be false if photometric == PHOTOMETRIC_PALETTE.
  • ncn shall be 3 as default.
  • wanted_channels shall be 3 with normalizeChannelsNumber()
  • destination Mat shall created with wanted_channels if needed.

Index number of palette is 1-dim, but palette has R,G,B elements. So destination Mat for palette-color image shall have 3 colorant.

@Kumataro
Copy link
Contributor

Kumataro commented Jun 10, 2025

This is another suggestion. SamplesPerPixel tag is required for RGB Images only, but it does not for other colormetric.

-           CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &ncn));
+           switch(photometric)
+           {
+               case PHOTOMETRIC_RGB:
+                   // SamplesPerPixel tag is required for RGB Images(From TIFF6.0 Section 6).
+                   CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &ncn));
+                   CV_CheckEQ(ncn, 3);
+                   break;
+               case PHOTOMETRIC_MINISWHITE:
+               case PHOTOMETRIC_MINISBLACK:
+                   ncn = 1; // GRAY output
+                   break;
+               default:
+                   ncn = 3; // Color output(RGB).
+                   break;
+           }

@janstarzy
Copy link
Contributor Author

image

I mean to follow the TIFF-Spec strictly. Only this way we can ensure that spec-complying tiff-files will be processed correctly.

So, my understanding is:

  • isGrayScale shall be false if photometric == PHOTOMETRIC_PALETTE, yes.
  • ncn shall be 1 as default according to the TIFF-spec. Any other value requires TIFFTAG_SAMPLESPERPIXEL. No guessing.
  • wanted_channels is the same as ncn. normalizeChannelsNumber() only checks, it normalizes nothing.
  • ncn is the number of channels in the TIFF-file (TIFFTAG_SAMPLESPERPIXEL). It is not the number of channels in Mat.

The proper number of channels on Mat is set to 3 for PHOTOMETRIC_PALETTE in the code that handles palette-images (Lines 319 and 332 in my branch).

For the other PHOTOMETRIC_*-values it is set to !isGrayScale ? wanted_channels : 1.

My initial intention was just cleaner code, but in the course of work this changed to complying to the specs (both TIFF and libtiff).

@Kumataro
Copy link
Contributor

I mean to follow the TIFF-Spec strictly. Only this way we can ensure that spec-complying tiff-files will be processed correctly.

Strictly your fix seems not be according to TIFF 6.0. Default value of SamplesPerPixels shall be 1. It is not good to change to consider Photometric values. Please see your quoted image. There are no description about condition.

If you disagree it, ncn is a different concept from SamplesPerPixels.
I think It is working variable to determine wanted_channels, not same as it.

@janstarzy janstarzy force-pushed the tif_bitsperpixel_boilerplate branch from 091d8f2 to 32d590a Compare June 10, 2025 14:46
@janstarzy
Copy link
Contributor Author

Yes, the default value for SamplesPerPixels shall be 1 unconditionally. I mentioned it in an earlier comment, but it was not in my code until recently. I wanted to see your opinion before any changes.

@Kumataro
Copy link
Contributor

Thank you very much. OK, I know ncn = SamplesPerPixel after fixing.

See https://libtiff.gitlab.io/libtiff/specification/coverage.html

If it complies with TIFF 6.0 and/or libtiff, we have to valid SamplesPerPixel.

Current implementation Tiff image which has PHOTOMETRIC_RGB and no SAMPLESPERPIXEL will not be blocked to decode.
I belive SamplesPerPixel validation is required.

Photometrics Expected SamplesPerPixel
Bilevel 1
Gray 1
Palette RGB 1
RGB 3
Separate(CMYK) 4
: :

How about this suggestion ?

@janstarzy
Copy link
Contributor Author

I'll have a look into the code to see what validation is necessary.

@janstarzy janstarzy force-pushed the tif_bitsperpixel_boilerplate branch from 32d590a to 954cfcf Compare June 12, 2025 11:25
@janstarzy
Copy link
Contributor Author

Nothing exiting new code until now, only a little cleanup. More to come, you do not need to look yet.

@janstarzy
Copy link
Contributor Author

I agree with your suggestion. Let's play it save here, I'll implement the validation. May be there is a more sophisticated solution, but I don't see it yet. And the code is complicated enough already.

@janstarzy janstarzy force-pushed the tif_bitsperpixel_boilerplate branch from 954cfcf to f15c8af Compare June 13, 2025 14:56
@janstarzy
Copy link
Contributor Author

Unfortunately "coverage.html" covers only baseline-tiff: with these rules the tests fail. I coded something I found reasonable...

@janstarzy janstarzy force-pushed the tif_bitsperpixel_boilerplate branch from f15c8af to 4e13855 Compare June 13, 2025 15:14
@janstarzy
Copy link
Contributor Author

But the work is not done yet.

@asmorkalov asmorkalov requested a review from vrabaud June 17, 2025 09:41
@vpisarev vpisarev self-requested a review June 27, 2025 06:59
@asmorkalov asmorkalov marked this pull request as draft June 27, 2025 08:13
@asmorkalov
Copy link
Contributor

I converted the PR to draft. Please change its status, when it's ready for review.

@vpisarev
Copy link
Contributor

vpisarev commented Jun 27, 2025

@janstarzy, thanks for the contribution!
I'd like to clarify the goal(s) of this PR:

  1. if we want to reduce the amount of code, we should admit that the goal is not achieved. On the opposite, 108 lines of code have been added.
  2. if we want to fix/extend OpenCV to handle some TIFFs, then extra tests and extra test images should be added (preferably, those should be tiny files, we don't need big resolution here, I believe) to demonstrate the problem and effectiveness of the solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy