Skip to content

Reading and converting 16-bit pixels #452

Description

@igg
>>> 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

Activity

  1. wiredfool commented on Dec 19, 2013

    @wiredfool
    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
    
  2. wiredfool commented on Dec 19, 2013

    @wiredfool
    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.

  3. igg commented on Dec 19, 2013

    @igg
    Author

    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.

  4. igg commented on Dec 19, 2013

    @igg
    Author

    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.

  5. igg commented on Dec 19, 2013

    @igg
    Author

    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?

  6. wiredfool commented on Dec 19, 2013

    @wiredfool
    Member

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

  7. wiredfool commented on Dec 20, 2013

    @wiredfool
    Member

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

  8. changed the title [-]reading and converting 16-bit pixels[/-] [+]Reading and converting 16-bit pixels[/+] on Nov 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions