python-pillow / Pillow

Python Imaging Library (Fork)
https://python-pillow.org
Other
11.91k stars 2.2k forks source link

TIFF Image tags are not available #4940

Closed mol3earth closed 2 years ago

mol3earth commented 3 years ago

What did you do?

Tried to access TIFF tags with the Pillow library but get nothing.
I can upload my .tiff file to https://exifinfo.org/ and see many tags.

What did you expect to happen?

To be able to access exif tags of my tiff files.

What actually happened?

An empty dict is returned.

What are your OS, Python and Pillow versions?

from PIL import Image

img = Image.open(file_name)
exif_data = img.getexif()
dict(exif_data)
# returns {}
radarhere commented 3 years ago

Please provide a copy of your image so that we can replicate the problem.

mol3earth commented 3 years ago

Thanks for the reply. Here is a zip file of the .tiff file.

20191002_151243_137.zip

radarhere commented 3 years ago

Depending on what data you're after, you may appreciate this.

from PIL import Image
img = Image.open('20191002_151243_137.TIFF')
print(img.tag_v2)

gives

{256: 640, 257: 512, 258: (16,), 259: 1, 262: 1, 271: 'FLIR', 272: 'Duo Pro R', 273: (8,), 339: (1,), 277: 1, 278: 512, 279: (655360,), 282: 1.0, 283: 1.0, 284: 1, 296: 1, 297: (0, 1), 50735: '278290', 305: 'V01.02.05', 700: b'<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>\r\n<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"\r\nxmlns:Camera="http://pix4d.com/camera/1.0/"\r\nxmlns:FLIR="http://ns.flir.com/xmp/1.0/">\r\n<rdf:Description rdf:about="">\r\n<Camera:BandName>\r\n<rdf:Seq>\r\n<rdf:li>LWIR</rdf:li>\r\n</rdf:Seq>\r\n</Camera:BandName>\r\n<Camera:CentralWavelength>\r\n<rdf:Seq>\r\n<rdf:li>10000</rdf:li>\r\n</rdf:Seq>\r\n</Camera:CentralWavelength>\r\n<Camera:WavelengthFWHM>\r\n<rdf:Seq>\r\n<rdf:li>4500</rdf:li>\r\n</rdf:Seq>\r\n</Camera:WavelengthFWHM>\r\n<Camera:TlinearGain>0.04</Camera:TlinearGain>\r\n<Camera:Yaw>-11954/100</Camera:Yaw>\r\n<Camera:Pitch>-8497/100</Camera:Pitch>\r\n<Camera:Roll>9660/100</Camera:Roll>\r\n<Camera:GPSXYAccuracy>1.92</Camera:GPSXYAccuracy>\r\n<Camera:GPSZAccuracy>2.79</Camera:GPSZAccuracy>\r\n<Camera:GyroRate>2.55</Camera:GyroRate>\r\n<Camera:DetectorBitDepth>16</Camera:DetectorBitDepth>\r\n<Camera:IsNormalized>1</Camera:IsNormalized>\r\n<FLIR:ImageOffsetX>0</FLIR:ImageOffsetX>\r\n<FLIR:ImageOffsetY>0</FLIR:ImageOffsetY>\r\n<FLIR:ImageValidStartX>0</FLIR:ImageValidStartX>\r\n<FLIR:ImageValidEndX>639</FLIR:ImageValidEndX>\r\n<FLIR:ImageValidStartY>0</FLIR:ImageValidStartY>\r\n<FLIR:ImageValidEndY>511</FLIR:ImageValidEndY>\r\n<FLIR:ImageUpsampleMode>2</FLIR:ImageUpsampleMode>\r\n<FLIR:MAVVersionID>0.3.0.0</FLIR:MAVVersionID>\r\n<FLIR:MAVComponentID>100</FLIR:MAVComponentID>\r\n<FLIR:MAVRelativeAltitude>0/1000</FLIR:MAVRelativeAltitude>\r\n<FLIR:MAVRateOfClimbRef>M</FLIR:MAVRateOfClimbRef>\r\n<FLIR:MAVRateOfClimb>0/1000</FLIR:MAVRateOfClimb>\r\n<FLIR:MAVYaw>0/100</FLIR:MAVYaw>\r\n<FLIR:MAVPitch>0/100</FLIR:MAVPitch>\r\n<FLIR:MAVRoll>0/100</FLIR:MAVRoll>\r\n<FLIR:MAVYawRate>0/100</FLIR:MAVYawRate>\r\n<FLIR:MAVPitchRate>0/100</FLIR:MAVPitchRate>\r\n<FLIR:MAVRollRate>0/100</FLIR:MAVRollRate>\r\n</rdf:Description>\r\n</rdf:RDF>\r\n<?xpacket end="w"?>\x00'}
mol3earth commented 3 years ago

thanks for your reply!

yes, I had found that already but i need the exif tags, i.e. GPS information.

on a related note, I can't add exif tags to a .tiff file either. I can do so with a .jpg file.

it seems these issues are related to the .tiff file format.

import piexif
import numpy as np
from PIL import Image
# make a dict of exif tags
exif_bytes = piexif.dump( exif_dict )
# make array of random data
arr = np.random.randint(0, 255, size=[100, 100, 3], dtype=np.uint8)
# use fromarray method to convert to img
img = Image.fromarray(arr)
# add exif bytes to image, 
img.info['exif'] = exif_bytes
# this save it 
img.save(
    'temp.tiff',
    format='tiff',
)
# Alternately, pass the exif_bytes to the save method.
img.save(
    'temp.tiff',
    format='tiff',
    exif=exif_bytes
)

Neither assigning the exif_bytes to info, nor passing it as an argument to save() work for me. Image.open() does not recognize the exif tags I added.

mol3earth commented 3 years ago

bump? any ideas here?

radarhere commented 3 years ago

When Pillow saves TIFF images, it does not use 'exif' from the info dictionary. Instead, 'tiffinfo' can be used - https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#saving-tiff-images

Looking for the GPS info in your image, Pillow reports that there is a GPS IFD, but that it has type 13. This is unexpected, because page 16 of the specification only lists up to 12. Whatever program has created your image appears to have instead followed TIFF Specification Supplement 1, which specifies type 13 as IFD.

So Pillow doesn't currently support reading the GPS data from your image, and it would need to be added. Would you be able to give permission to add your image to the our test suite, and distribute it under the Pillow license?

mol3earth commented 3 years ago

Hi! Thanks so much for the response and the info.

You can definitely use the image in your test suite and license it as such.

Do you have a timeline for when the package will be updated to work for our images?

radarhere commented 3 years ago

Since you're interested in an immediate solution, does this suit your needs?

from PIL import Image, TiffTags
from PIL.TiffImagePlugin import ImageFileDirectory_v2

# Add IFD as a tag type
ImageFileDirectory_v2._load_dispatch[13] = ImageFileDirectory_v2._load_dispatch[TiffTags.LONG]

im = Image.open('20191002_151243_137.TIFF')
gps_offset = im.tag_v2.get(0x8825)
if gps_offset:
    ifh = b"II\x2A\x00\x08\x00\x00\x00" if im.tag_v2._endian == "<" else "MM\x00\x2A\x00\x00\x00\x08"
    info = ImageFileDirectory_v2(ifh)
    im.fp.seek(gps_offset)
    info.load(im.fp)

    data = {}
    gps_keys = ['GPSVersionID','GPSLatitudeRef','GPSLatitude','GPSLongitudeRef','GPSLongitude','GPSAltitudeRef','GPSAltitude','GPSTimeStamp','GPSSatellites','GPSStatus','GPSMeasureMode','GPSDOP','GPSSpeedRef','GPSSpeed','GPSTrackRef','GPSTrack','GPSImgDirectionRef','GPSImgDirection','GPSMapDatum','GPSDestLatitudeRef','GPSDestLatitude','GPSDestLongitudeRef','GPSDestLongitude','GPSDestBearingRef','GPSDestBearing','GPSDestDistanceRef','GPSDestDistance','GPSProcessingMethod','GPSAreaInformation','GPSDateStamp','GPSDifferential']
    for k, v in info.items():
        print(gps_keys[k]+": "+str(v))
else:
    print("No GPS data present")

Running it over your image, I get

GPSVersionID: b'\x03\x02\x00\x00'
GPSLatitudeRef: N
GPSLatitude: (35.0, 35.0, 59.995)
GPSLongitudeRef: W
GPSLongitude: (111.0, 39.0, 42.578)
GPSAltitudeRef: b'\x00'
GPSAltitude: 1845.656
GPSMapDatum: WGS-84
radarhere commented 3 years ago

This is now marginally simpler with Pillow 8.0, thanks to #4979

from PIL import Image, TiffTags
from PIL.TiffImagePlugin import ImageFileDirectory_v2

im = Image.open('20191002_151243_137.TIFF')
gps_offset = im.tag_v2.get(0x8825)
if gps_offset:
    ifh = b"II\x2A\x00\x08\x00\x00\x00" if im.tag_v2._endian == "<" else "MM\x00\x2A\x00\x00\x00\x08"
    info = ImageFileDirectory_v2(ifh)
    im.fp.seek(gps_offset)
    info.load(im.fp)

    data = {}
    gps_keys = ['GPSVersionID','GPSLatitudeRef','GPSLatitude','GPSLongitudeRef','GPSLongitude','GPSAltitudeRef','GPSAltitude','GPSTimeStamp','GPSSatellites','GPSStatus','GPSMeasureMode','GPSDOP','GPSSpeedRef','GPSSpeed','GPSTrackRef','GPSTrack','GPSImgDirectionRef','GPSImgDirection','GPSMapDatum','GPSDestLatitudeRef','GPSDestLatitude','GPSDestLongitudeRef','GPSDestLongitude','GPSDestBearingRef','GPSDestBearing','GPSDestDistanceRef','GPSDestDistance','GPSProcessingMethod','GPSAreaInformation','GPSDateStamp','GPSDifferential']
    for k, v in info.items():
        print(gps_keys[k]+": "+str(v))
else:
    print("No GPS data present")
radarhere commented 3 years ago

With #5416, this is now as simple as

from PIL import Image
img = Image.open('20191002_151243_137.TIFF')
info = img.getexif().get_ifd(0x8825)
if info:
    gps_keys = ['GPSVersionID','GPSLatitudeRef','GPSLatitude','GPSLongitudeRef','GPSLongitude','GPSAltitudeRef','GPSAltitude','GPSTimeStamp','GPSSatellites','GPSStatus','GPSMeasureMode','GPSDOP','GPSSpeedRef','GPSSpeed','GPSTrackRef','GPSTrack','GPSImgDirectionRef','GPSImgDirection','GPSMapDatum','GPSDestLatitudeRef','GPSDestLatitude','GPSDestLongitudeRef','GPSDestLongitude','GPSDestBearingRef','GPSDestBearing','GPSDestDistanceRef','GPSDestDistance','GPSProcessingMethod','GPSAreaInformation','GPSDateStamp','GPSDifferential']
    for k, v in info.items():
        print(gps_keys[k]+": "+str(v))
else:
    print("No GPS data present")
radarhere commented 3 years ago

I've created #5575 to resolve this. With that PR, you should also be able to save a TIFF with the exif keyword argument.

from PIL import Image

def print_gps_data(exif):
    info = exif.get_ifd(0x8825)
    if info:
        gps_keys = ['GPSVersionID','GPSLatitudeRef','GPSLatitude','GPSLongitudeRef','GPSLongitude','GPSAltitudeRef','GPSAltitude','GPSTimeStamp','GPSSatellites','GPSStatus','GPSMeasureMode','GPSDOP','GPSSpeedRef','GPSSpeed','GPSTrackRef','GPSTrack','GPSImgDirectionRef','GPSImgDirection','GPSMapDatum','GPSDestLatitudeRef','GPSDestLatitude','GPSDestLongitudeRef','GPSDestLongitude','GPSDestBearingRef','GPSDestBearing','GPSDestDistanceRef','GPSDestDistance','GPSProcessingMethod','GPSAreaInformation','GPSDateStamp','GPSDifferential']
        for k, v in info.items():
            print(gps_keys[k]+": "+str(v))
    else:
        print("No GPS data present")

img = Image.open('20191002_151243_137.TIFF')
exif = img.getexif()
print_gps_data(exif)
img.save("out.TIFF", exif=exif)

print()
img = Image.open('out.TIFF')
exif = img.getexif()
print_gps_data(exif)