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

Reading and converting 16-bit pixels #452

Closed
igg opened this issue Dec 19, 2013 · 7 comments · Fixed by #455
Closed

Reading and converting 16-bit pixels #452

igg opened this issue Dec 19, 2013 · 7 comments · Fixed by #455
Milestone

Comments

@igg
Copy link

igg commented Dec 19, 2013

>>> import PIL
>>> PIL.PILLOW_VERSION
'2.2.2'
>>> from PIL import Image
>>> im = Image.open('pil_pillow_16_bit_bug.tif')
>>> im
<PIL.TiffImagePlugin.TiffImageFile image mode=I;16B size=512x512 at 0x103125E60>

Note no 'S' in mode.
Then,

>>> im.getpixel((0,0))
-32768

Two problems: Other than using the extended SampleFormat tag (339, 0x0153), there is no way to store a signed value in a tiff file. The value stored in this image at 0,0 is 32768, and this file has no SampleFormat tag, so only unsigned integers are possible. Instead the value reported is the two's complement of the actual value.
Second problem unrelated to tiff peculiarities, you have an unsigned image mode with a signed pixel value.
Moving on,

>>> im2 = im.convert('I')
>>> im2.getpixel((0,0))
32768

This is the correct value.
Even more surprising is when we convert this 32-bit, presumably unsigned image back to unsigned 16 bit:

>>> im3 = im2.convert('I;16')
>>> im3.getpixel((0,0))
32767

???. Even better, a pixel at 32,16 has a value of 32769:

>>> im2.getpixel((32,16))
32769

Correct in the 32-bit unsigned image (is it really unsigned, BTW? didn't check), but in the 16-bit image converted from it, this pixel is still 32767!

>>> im3.getpixel((32,16))
32767

In fact, all of the pixels in the converted image are clipped to 32767. In the image as-read, this value is -32767 (two's complement of 32769):

>>>> >>> im.getpixel((32,16))
-32767

Does the problem originate from the fact that we went over the signed 16-bit max even though we were supposed to be dealing with unsigned values? Is the convert problem related to the tiff reader problem?

Sample file for the above: pil_pillow_16_bit_bug.tif

@wiredfool
Copy link
Member

That image isn't coming up as a signed tiff for me. I'm seeing a SampleFormat of 1, not 2. If you look at the tiff spec, http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf page 80, the SampleFormat tag can take one of 4 values:

1 = unsigned integer data
2 = two’s complement signed integer data
3 = IEEE floating point data [IEEE]
4 = undefined data format

We do read the sample format tag and act on it -- the values are in PIL/TiffImagePlugin.py around line 140. If this had a sample format of 2, we'd be getting this line: (MM, 1, 2, 1, (16,), ()): ("I;16BS", "I;16BS"), where currently we're getting: (MM, 1, 1, 1, (16,), ()): ("I;16B", "I;16B"),

*** Summary ***
- compression: raw
- photometric_interpretation: 1
- planar_configuration: 1
- fill_order: 1
- size: (64, 64)
format key: ('MM', 1, 1, 1, (16,), ())
- raw mode: I;16B
- pil mode: I;16B
tiles:  [('raw', (0, 0, 64, 64), 3089, ('I;16B', 0, 1))]

Imagemagick agrees:

(vpy27)erics:~/16bit$ identify -verbose pil_pillow_16_bit_bug.tif 
Image: pil_pillow_16_bit_bug.tif
  Format: TIFF (Tagged Image File Format)
  Class: DirectClass
  Geometry: 64x64+0+0
  Resolution: 72x72
  Print size: 0.888889x0.888889
  Units: PixelsPerInch
  Type: Grayscale
  Base type: Grayscale
  Endianess: MSB
  Colorspace: RGB
  Depth: 16-bit
  Channel depth:
    gray: 16-bit
  Channel statistics:
    Gray:
      min: 32768 (0.500008)
      max: 32771 (0.500053)
      mean: 32769.5 (0.500031)
      standard deviation: 0.949615 (1.44902e-05)
      kurtosis: 389103
      skewness: -3.10189
  Histogram:
       846: (32768,32768,32768) #800080008000 rgb(50.0008%,50.0008%,50.0008%)
       807: (32769,32769,32769) #800180018001 rgb(50.0023%,50.0023%,50.0023%)
      1954: (32770,32770,32770) #800280028002 rgb(50.0038%,50.0038%,50.0038%)
       489: (32771,32771,32771) #800380038003 rgb(50.0053%,50.0053%,50.0053%)
  Rendering intent: Undefined
  Interlace: None
  Background color: white
  Border color: rgb(223,223,223)
  Matte color: grey74
  Transparent color: black
  Compose: Over
  Page geometry: 64x64+0+0
  Dispose: Undefined
  Iterations: 0
  Compression: None
  Orientation: TopLeft
  Properties:
    comment: ImageJ=1.48k
cf=0
c0=-32768.0
c1=1.0
vunit=Gray Value
min=32768.0
max=32771.0

    date:create: 2013-12-19T08:36:22-08:00
    date:modify: 2013-12-19T08:36:22-08:00
    signature: b4b86b8ead068a453a9d05af84c2c460e918bade85b48b6b8bf930141ce6815b
    tiff:endian: msb
    tiff:photometric: min-is-black
    tiff:rows-per-strip: 64
  Artifacts:
    verbose: true
  Tainted: False
  Filesize: 11.3KBB
  Number pixels: 4.1KB
  Pixels per second: 410KB
  User time: 0.000u
  Elapsed time: 0:01.010

@wiredfool
Copy link
Member

As for the rest of it, try checking after including PR #416 and #417. There were some fixes for I;16 images and endianness issues there.

@igg
Copy link
Author

igg commented Dec 19, 2013

Of course its not coming up as a signed tiff. Its not a signed tiff - your ImageMagick dump, my tiffdump, tiffinfo, and ImageJ are all in complete agreement about that. Its only a signed tiff when read by Image.open(), which is the whole problem and basis for this bug report. The mode is reported correctly as unsigned, but the pixel values are negative!

As far as conversion goes, I would expect the same numerical values as I convert from unsigned 16-bit to unsigned 32-bit, and back to unsigned 16-bit (I;16B -> I -> I;16). This should be true regardless of the numerical range because the first conversion is an up-cast, and the second conversion is a down-cast to the original numeric type. The output above clearly shows that this is not the case.

So PR #416 and #417 are not part of the 2.2.2 release which looked to me happened after their merge into master? From my looking at them (which I did before submitting this report, but maybe not in sufficient detail), they deal with 12-bit tiffs stored as 16-bit, where you wouldn't encounter numerical values high enough to run into signedness bugs. If these were not merged into the 2.2.2 release, and as they are your PRs and you already have the test file in hand, can you quickly check what pixel values get reported for this file using your codebase?

Lastly, tiffdump does not report a SampleFormat tag for this file, so a SampleFormat tag value of 1 (unsigned) is only implied by its absence. Or, did you read the SampleFormat tag from the file? I always considered tiffdump the last word on what tags are present in a tiff file, but at this point I'm ready to consider any possibility.

@igg
Copy link
Author

igg commented Dec 19, 2013

For reference, I just tried with current master (presumably PR #416 and #417 are in there?), and the output is exactly as originally reported: negative pixel values with an unsigned image mode, clipping at the signed-16-bit-max (32767) when converting from I;16B->I->I;16.

@igg
Copy link
Author

igg commented Dec 19, 2013

Finally, in case your branch isn't merged with master at this repo, I installed pillow from your 12-bit-tiff branch like so:

pip uninstall pillow
pip install https://github.com/wiredfool/Pillow/archive/12-bit-tiff.zip

And the output is still exactly the same as above. Are we there yet?

@wiredfool
Copy link
Member

Sorry, I misread your report as the image not showing up as signed. I'll take a closer look.

wiredfool added a commit to wiredfool/Pillow that referenced this issue Dec 20, 2013
@wiredfool
Copy link
Member

It looks like the pixel access functions are using int16 and int32 instead of uint*s. Check out #455.

@radarhere radarhere changed the title reading and converting 16-bit pixels Reading and converting 16-bit pixels Nov 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants