Skip to content
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

TIFFReadScanline: scanline oriented access is not supported for downsampled JPEG compressed images, consider enabling TIFF_JPEGCOLORMODE as JPEGCOLORMODE_RGB #2926

Closed
jwilk opened this issue Jan 3, 2018 · 35 comments
Labels
Bug Any unexpected behavior, until confirmed feature.

Comments

@jwilk
Copy link
Contributor

jwilk commented Jan 3, 2018

Pillow 5.0.0 can no longer read JPEG-compressed TIFFs:

$ pip freeze | grep -i pillow
Pillow==5.0.0
pillow-scripts==5.0.0
$ wget -q https://github.com/jwilk/didjvu/raw/0.8/tests/data/ycbcr-jpeg.tiff
$ pilconvert.py -r ycbcr-jpeg.tiff tmp.png
TIFFReadScanline: scanline oriented access is not supported for downsampled JPEG compressed images, consider enabling TIFF_JPEGCOLORMODE as JPEGCOLORMODE_RGB..
cannot convert image (<type 'exceptions.IOError'>:-2)

(I presume TIFF_JPEGCOLORMODE is a typo for TIFFTAG_JPEGCOLORMODE.)

This used to work until Pillow 4.3.0.

jwilk added a commit to jwilk/didjvu that referenced this issue Jan 3, 2018
Fixes:

    TIFFReadScanline: scanline oriented access is not supported for downsampled JPEG compressed images, consider enabling TIFF_JPEGCOLORMODE as JPEGCOLORMODE_RGB..
    Traceback (most recent call last):
      ...
      File ".../lib/didjvu.py", line 277, in encode_one
        image = gamera.load_image(image_filename)
      File ".../lib/gamera_support.py", line 93, in load_image
        pil_image = pil_image.convert('RGB')
      File ".../site-packages/PIL/Image.py", line 877, in convert
        self.load()
      File ".../site-packages/PIL/TiffImagePlugin.py", line 1039, in load
        return self._load_libtiff()
      File ".../site-packages/PIL/TiffImagePlugin.py", line 1131, in _load_libtiff
        raise IOError(err)
    IOError: -2

Work-around for: python-pillow/Pillow#2926
@radarhere
Copy link
Member

'This used to work until Pillow 4.3.0' - Just to be clear, while this sentence would indicate that 4.3.0 is also problematic, this is not the case, and you are just finding this issue with 5.0.0.

@jwilk
Copy link
Contributor Author

jwilk commented Jan 3, 2018

Indeed, Pillow 4.3.0 was happy with this file.
Sorry for bad wording.

@wiredfool
Copy link
Member

There was a change in 5.0 from using the internal JPEG decoder for JPEG encoded TIFFs to the libtiff decoder because the internal decoder was returning incorrect images. (Actually, it's more that we shifted the remaining three internally provided compression modes -- jpeg, packbits, and lzw, because 2 of the 3 were incorrect).

Your test file does appear to work on my machine though, which is using libtiff 4.0.8 from homebrew.

@jwilk
Copy link
Contributor Author

jwilk commented Jan 4, 2018

I tested this with Pillow-5.0.0-cp27-cp27mu-manylinux1_i686.whl installed from PyPI, which has its own embedded libtiff AFAICS.

@hugovk
Copy link
Member

hugovk commented Jan 4, 2018

pillow-wheels was recently updated from libtiff 4.0.8 to 4.0.9: python-pillow/pillow-wheels#70

@cgohlke
Copy link
Contributor

cgohlke commented Jan 7, 2018

This is reproducible with Pillow-5.0.0-cp36-cp36m-win_amd64, which is also using libtiff 4.0.9.

@minusdavid
Copy link

I am having this problem in the Loris application when using Pillow 5.0.0 via pip. For now, I think we'll fall back to using Pillow 4.3.0, since I can confirm that my TIFs are being handled correctly in 4.3.0, but it sounds like that might not be the best long-term solution.

Does anyone know what the actual technical problem is? A bug in libtiff or something else?

@minusdavid
Copy link

@minusdavid
Copy link

So the error says "TIFFReadScanline: scanline oriented access is not supported for downsampled JPEG compressed images, consider enabling TIFF_JPEGCOLORMODE as JPEGCOLORMODE_RGB"...

I wondered how one might enable TIFF_JPEGCOLORMODE as JPEGCOLORMODE_RGB... and I saw an example of it at https://github.com/ImageMagick/tiff/blob/master/tools/ppm2tiff.c#L169. This line is also relevant: https://github.com/ImageMagick/tiff/blob/master/tools/ppm2tiff.c#L223

I'm not that familiar with image processing in general or Pillow, but that looks like what we want to be doing in Pillow.

So I cloned the ImageMagick/tiff repo and searched for "TIFFTAG_JPEGCOLORMODE" and found it to correspond to the integer 65538.

I used https://pillow.readthedocs.io/en/latest/reference/TiffTags.html to dump all the Tiff Tags that Pillow knows about... and didn't find the integer 65538.

I don't know if that means that Pillow can't use it... or if it's just not in that dictionary. But this all seems relevant...

@minusdavid
Copy link

Looking again at the ImageMagick code... I can see that the constant JPEGCOLORMODE_RGB is 0x0001.

So I'm going to see if I can just go ahead and set that with the "tag" method...

@minusdavid
Copy link

Hmm I can't even use the "save" method for my TIFF anyway...

@minusdavid
Copy link

According to http://maptools-org.996276.n3.nabble.com/JPEG-compressed-RGB-tiled-TIFF-with-chroma-subsampling-td14062.html and the ImageMagick code, I might need to set TIFFTAG_PHOTOMETRIC to PHOTOMETRIC_YCBCR which means...

TIFFTAG_PHOTOMETRIC is 262 (otherwise known as PhotometricInterpretation in Pillow)
PHOTOMETRIC_YCBCR is 6

Hmm... It looks like tag 262 is already set to 6... damn.

@minusdavid
Copy link

I'm guessing @wiredfool might be able to take it from here based on the work on #344 and #291

It looks like libtiff is aware of the 65538 tag... https://github.com/vadz/libtiff/blob/master/libtiff/tiff.h

@minusdavid
Copy link

Ah this is interesting... it's not a tag but a pseudo tag... https://github.com/vadz/libtiff/blob/master/libtiff/tiff.h#L538

@minusdavid
Copy link

So I'm able to use Image.open() to open my TIFF but then I try to save it and I get the following:

TIFFReadScanline: scanline oriented access is not supported for downsampled JPEG compressed images, consider enabling TIFF_JPEGCOLORMODE as JPEGCOLORMODE_RGB..
Traceback (most recent call last):
File "test.py", line 11, in
im.save("new.tiff", tiffinfo=im.tag)
File "/usr/local/lib/python2.7/dist-packages/PIL/Image.py", line 1897, in save
self.load()
File "/usr/local/lib/python2.7/dist-packages/PIL/TiffImagePlugin.py", line 1039, in load
return self._load_libtiff()
File "/usr/local/lib/python2.7/dist-packages/PIL/TiffImagePlugin.py", line 1131, in _load_libtiff
raise IOError(err)
IOError: -2

It looks like the error is being generated by a decoder... which is using some IO object of some kind...

I don't know why we didn't get this error during Image.open() but whatever. Ignoring that for now.

I see the decoder here:

decoder = Image._getdecoder(self.mode, 'libtiff', tuple(args),
self.decoderconfig)

Not really seeing anything about decoderconfig in the code... but I reckon that might be useful. Because me setting those pseudo-tags where I am is probably doing nothing I imagine. In the case of ImageMagick, they're setting those for their new TIFF that they're going to write out to... whereas we have an existing TIFF that we need to read... but we need to set these pseudocodes before it can properly read it I think?

@minusdavid
Copy link

Ahh reading through Image.py and I'm seeing that Image.open is a lazy method, which is probably why I don't get an error...

Ah yup. When I do a im.rotate(45), now I get the error as it's actually trying to decode the TIFF...

@minusdavid
Copy link

I'm guessing that we might need to update "src/libImaging/TiffDecode.c"?

I'd say probably using ImagingLibTiffSetField to set the tags mentioned above around https://github.com/python-pillow/Pillow/blob/master/src/libImaging/TiffDecode.c#L220?

I think the error is being generated at https://github.com/python-pillow/Pillow/blob/master/src/libImaging/TiffDecode.c#L238, so I think that's a reasonable suggestion.

@minusdavid
Copy link

Everything I'm seeing in the ImageMagick code seems to suggest this, so I'm thinking that this is probably the right direction.

I'm tempted to write a patch myself. I was going to leave it for you folk in the project, but I think I have a VM that I probably could use to test this on...

@minusdavid
Copy link

Ok harder than anticipated finding the right spot... but I know I'm calling the decoder here at least:

   elif hasattr(self.fp, "fileno"):
        # we've got a actual file on disk, pass in the fp.
        if DEBUG:
            print("have fileno, calling fileno version of the decoder.")
        self.fp.seek(0)
        # 4 bytes, otherwise the trace might error out
        n, err = decoder.decode(b"fpfp")

@minusdavid
Copy link

Wow... I think I may have done it. I need to add some more if statements but... I was able to load the TIFF without getting that error.

@minusdavid
Copy link

Hmm so I could use the load() method but using the save() method generated an error:

Encoding line bt lineTIFFScanlineSize64: Invalid YCbCr subsampling.
Encode Error, row 0
Traceback (most recent call last):
File "test.py", line 17, in
im.save("new.tiff")
File "/usr/local/lib/python2.7/dist-packages/PIL/Image.py", line 1930, in save
save_handler(self, fp, filename)
File "/usr/local/lib/python2.7/dist-packages/PIL/TiffImagePlugin.py", line 1540, in _save
raise IOError("encoder error %d when writing image file" % s)
IOError: encoder error -2 when writing image file

Might just need the same bit of code for the encoder as the decoder though...

@minusdavid
Copy link

So enabling TIFF_JPEGCOLORMODE as JPEGCOLORMODE_RGB did prevent the fatal error from occuring.

However, when I run "pilconvert.py -r old.tif tmp.png", I wind up with a PNG file which has some horrifically bad colours.

It went from a black background with a brown/white foreground to a green background with a fuchsia foreground.

Same goes when outputting to a jpeg instead of a png.

@minusdavid
Copy link

By setting the TIFF_JPEGCOLORMODE to JPEGCOLORMODE_RGB, I think we go down this branch:

https://github.com/leapmotion/FreeImage/blob/master/Source/LibTIFF4/tif_jpeg.c#L1131

"pilconvert.py old.tif tmp.png" gives this error:
cannot convert image (<type 'exceptions.IOError'>:cannot write mode YCbCr as PNG)

"pilconvert.py -g old.tif tmp.jpg" looks pretty good...

"pilconvert.py -p old.tif tmp.png" looks bad.. the same as -r

Hoping that it was just pilconvert.py... I just tried it in Loris and it looks bad there too.

@minusdavid
Copy link

For what it's worth, I'm on Ubuntu 16.04.03 and using libtiff5 version 4.0.6-1ubuntu0.2...

@minusdavid
Copy link

So comparing Pillow 5.1.0.dev0 against Pillow 4.3.0...

Both are handling grayscale fine for transforming TIFF to other formats. But converting the TIFF to color JPEG/PNG isn't working for Pillow 5.1.0.dev0 (with my modifications) but it's working perfectly for Pillow 4.3.0...

I'm thinking there is something I must be missing in the conversion process somewhere... possibly on the JPEG/PNG end since the grayscale is fine so it looks like the TIFF is being read correctly?

@minusdavid
Copy link

Ok this is interesting... look at https://en.wikipedia.org/wiki/YCbCr

"A color image and its Y, CB and CR components. The Y image is essentially a greyscale copy of the main image."

The Y image is a perfect greyscale version. The CR version looks like green in the background and fuchsia in the foreground... pretty much exactly what I'm seeing when converting to JPEG/PNG.

The original error message is coming from https://github.com/leapmotion/FreeImage/blob/master/Source/LibTIFF4/tif_jpeg.c#L1299, so maybe the issue is in tif_jpeg.c?

@minusdavid
Copy link

I've commented on http://bugzilla.maptools.org/show_bug.cgi?id=2140.

I should get my code sample up so that others can review it...

@minusdavid
Copy link

Ok there you go:

minusdavid@df71a32

I followed these instructions for the most part:
https://pillow.readthedocs.io/en/latest/installation.html#external-libraries

Basically I installed all the external libraries, uninstalled Pillow with "pip uninstall Pillow" and then installed my Git version using "pip install ./Pillow". It was easy.

@wiredfool
Copy link
Member

wiredfool commented Mar 3, 2018

That maptools bug is 8 years old, and very likely not directly related to this.

There were several cases where YCbCr images were being returned as RGB from Pillow's internal jpeg decoder in 4.3.0, and switching to the libtiff decoder fixed them, but broke a different subset.

I think what's happening here is that you're telling libjpeg to use RGB mode for JPEGs, regardless of the actual encoding of the image.

@minusdavid
Copy link

Looking back, I realise that I forgot to say that I thought the libtiff/maptools bug wasn't directly related, but interesting that the same error message has been around so long.

Mmm, that sounds like a plausible explanation, but according to https://github.com/leapmotion/FreeImage/blob/master/Source/LibTIFF4/tif_jpeg.c#L1132 it should be converting YCbCr to RGB. I suppose I haven't looked to make sure it is following that branch though...

@minusdavid
Copy link

It looks like the TIFFs where I've encountered errors are test data and our project shouldn't involve any TIFFs containing JPEGs, so I might not end up having to worry about this in practice... but I admit that this bug really has me curious and has got me intrigued in image processing...

@CMCDragonkai
Copy link

Is there a solution to this problem? I'm just trying to load the TIF via skimage.io.imread.

@wiredfool wiredfool reopened this Apr 1, 2018
@wiredfool wiredfool added Bug Any unexpected behavior, until confirmed feature. and removed Invalid labels Apr 1, 2018
@wiredfool
Copy link
Member

This is a real issue, but is going to take work in the tiff decoder to fix, or we have to revert the changed that fixed color issues on jpeg encoded tiffs.

@doko42
Copy link

doko42 commented Jul 29, 2018

the proposed patch apparently fixes the issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Any unexpected behavior, until confirmed feature.
Projects
None yet
Development

No branches or pull requests

9 participants