ExifTags Module ############### .. note:: The source images loaded from the demos in this page is in the real-world, but I can't redistribute those. I downloaded those to local by `google searching with words "Images for jpeg contained gpsinfo sample" `_, so most of all of those are not free liscenced. Keeping exif data when saving ***************************** :doc: https://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html#jpeg, http://effbot.org/imagingbook/format-jpeg.htm Before you use ExifTags, note that save method doesn't preserve the exif in case of default. .. code-block:: pycon >>> from io import BytesIO >>> from PIL import Image >>> >>> src = Image.open("01.jpg") >>> src.info.get('exif', b'')[:20] 'Exif\x00\x00II*\x00\x08\x00\x00\x00\x0c\x00\x0f\x01\x02\x00' >>> >>> # >>> tmp = BytesIO() >>> src.save(tmp, "JPEG") # drop exif data >>> dst = Image.open(tmp) >>> dst.info.get('exif', b'')[:20] '' >>> >>> # >>> tmp = BytesIO() >>> src.save(tmp, "JPEG", exif=b"") # override exif data >>> dst = Image.open(tmp) >>> dst.info.get('exif', b'')[:20] '' >>> >>> # >>> tmp = BytesIO() >>> src.save(tmp, "JPEG", exif=src.info["exif"]) # keep exif data >>> dst = Image.open(tmp) >>> dst.info.get('exif', b'')[:20] 'Exif\x00\x00II*\x00\x08\x00\x00\x00\x0c\x00\x0f\x01\x02\x00' >>> Exploring Exif Items As Human Readable ************************************** .. code-block:: pycon :emphasize-lines: 23 Python 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:54:25) [MSC v.1900 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import PIL >>> PIL.VERSION, PIL.PILLOW_VERSION ('1.1.7', '4.1.1') >>> >>> from PIL import Image >>> from PIL.ExifTags import TAGS, GPSTAGS >>> >>> # build reverse dicts >>> _TAGS_r = dict(((v, k) for k, v in TAGS.items())) >>> _GPSTAGS_r = dict(((v, k) for k, v in GPSTAGS.items())) >>> >>> # >>> img = Image.open("01.jpg") >>> img.info.keys() dict_keys(['dpi', 'exif']) >>> len(img.info['exif']) 13798 >>> img.info['exif'][:30] # raw exif data b'Exif\x00\x00II*\x00\x08\x00\x00\x00\x0c\x00\x0f\x01\x02\x00\x06\x00\x00\x00\x9e\x00\x00\x00\x10\x01' >>> # >>> exifd = img._getexif() # this merges gpsinfo as data rather than an offset pointer >>> type(exifd) >>> exifd.keys() # numeric keys dict_keys([36864, 37377, 37378, 36867, 36868, 40965, 37510, 37383, 37385, 37386, 40962, 41486, 271, 272, 37521, 274, 531, 33432, 37380, 282, 283, 33434, 42032, 34850, 40961, 34853, 34855, 296, 41987, 37121, 34866, 33437, 34864, 42033, 306, 42036, 42037, 42034, 315, 41985, 40960, 41990, 41487, 40963, 37520, 41986, 34665, 37522, 41488, 37500]) >>> # >>> # 1. ignore "MakerNote" and "UserComment" because these can be too long >>> # 2. ignore unknwon tag >>> keys = list(exifd.keys()) >>> keys.remove(_TAGS_r["MakerNote"]) >>> keys.remove(_TAGS_r["UserComment"]) >>> keys = [k for k in keys if k in TAGS] >>> # symbolic name of keys >>> print("\n".join([TAGS[k] for k in keys])) ExifVersion ShutterSpeedValue --- snip --- ColorSpace GPSInfo ISOSpeedRatings --- snip --- FocalPlaneResolutionUnit >>> # each values >>> print("\n".join([str((TAGS[k], exifd[k])) for k in keys])) ('ExifVersion', b'0230') ('ShutterSpeedValue', (417792, 65536)) --- snip --- ('ColorSpace', 1) ('GPSInfo', {0: b'\x02\x03\x00\x00', 1: 'S', 2: ((20, 1), (150146, 10000), (0, 1)), 3: 'E', 4: ((44, 1), (251558, 10000), (0, 1)), 5: b'\x00', 6: (235, 10), 7: ((14, 1), (31, 1), (59000, 1000)), 8: '12', 9: 'A', 10: '3', 11: (14, 10), 29: '2012:08:13'}) ('ISOSpeedRatings', 100) --- snip --- >>> gpsinfo = exifd[_TAGS_r["GPSInfo"]] >>> gpsinfo {0: b'\x02\x03\x00\x00', 1: 'S', 2: ((20, 1), (150146, 10000), (0, 1)), 3: 'E', 4: ((44, 1), (251558, 10000), (0, 1)), 5: b'\x00', 6: (235, 10), 7: ((14, 1), (31, 1), (59000, 1000)), 8: '12', 9: 'A', 10: '3', 11: (14, 10), 29: '2012:08:13'} >>> print("\n".join([GPSTAGS[k] for k in gpsinfo.keys()])) GPSVersionID GPSLatitudeRef GPSLatitude GPSLongitudeRef GPSLongitude GPSAltitudeRef GPSAltitude GPSTimeStamp GPSSatellites GPSStatus GPSMeasureMode GPSDOP GPSDateStamp >>> print("\n".join([str((GPSTAGS[k], gpsinfo[k])) for k in gpsinfo.keys()])) ('GPSVersionID', b'\x02\x03\x00\x00') ('GPSLatitudeRef', 'S') ('GPSLatitude', ((20, 1), (150146, 10000), (0, 1))) ('GPSLongitudeRef', 'E') ('GPSLongitude', ((44, 1), (251558, 10000), (0, 1))) ('GPSAltitudeRef', b'\x00') ('GPSAltitude', (235, 10)) ('GPSTimeStamp', ((14, 1), (31, 1), (59000, 1000))) ('GPSSatellites', '12') ('GPSStatus', 'A') ('GPSMeasureMode', '3') ('GPSDOP', (14, 10)) ('GPSDateStamp', '2012:08:13') .. code-block:: pycon :emphasize-lines: 22 Python 3.9.2 (tags/v3.9.2:1a79785, Feb 19 2021, 13:44:55) [MSC v.1928 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import PIL >>> PIL.__version__ '8.2.0' >>> from PIL import Image >>> from PIL.ExifTags import TAGS, GPSTAGS >>> >>> # build reverse dicts >>> _TAGS_r = dict(((v, k) for k, v in TAGS.items())) >>> _GPSTAGS_r = dict(((v, k) for k, v in GPSTAGS.items())) >>> >>> # >>> img = Image.open("02.jpg") >>> img.info.keys() dict_keys(['jfif', 'jfif_version', 'dpi', 'jfif_unit', 'jfif_density', 'exif', 'photoshop', 'adobe', 'adobe_transform', 'progressive', 'progression', 'icc_profile']) >>> len(img.info['exif']) 4997 >>> img.info['exif'][:30] # raw exif data b'Exif\x00\x00II*\x00\x08\x00\x00\x00\x11\x00\x00\x01\x03\x00\x01\x00\x00\x00 \x0f\x00\x00\x01\x01' >>> # >>> exifd = img.getexif()._get_merged_dict() # this merges gpsinfo as data rather than an offset pointer >>> type(exifd) >>> exifd.keys() # numeric keys dict_keys([256, 257, 258, 259, 34853, 262, 296, 34665, 271, 272, 305, 274, 306, 277, 282, 283, 284, 36864, 37377, 37378, 36867, 36868, 37380, 37381, 37383, 37384, 37385, 37386, 40961, 40962, 41992, 41993, 41994, 40963, 41996, 41495, 41728, 33434, 33437, 41729, 42016, 34850, 34855, 41986, 41987]) >>> keys = list(exifd.keys()) >>> keys = [k for k in keys if k in TAGS] >>> # symbolic name of keys >>> print("\n".join([TAGS[k] for k in keys])) ImageWidth ImageLength BitsPerSample Compression GPSInfo PhotometricInterpretation --- snip --- WhiteBalance >>> # each values >>> print("\n".join([str((TAGS[k], exifd[k])) for k in keys])) ('ImageWidth', 3872) ('ImageLength', 2592) ('BitsPerSample', (8, 8, 8)) ('Compression', 1) ('GPSInfo', {0: b'\x00\x00\x02\x02', 1: 'N', 2: (53.0, 20.0, 58.86), 3: 'W', 4: (6.0, 15.0, 36.29), 5: b'\x00', 6: 0.0}) ('PhotometricInterpretation', 2) --- snip --- ('WhiteBalance', 1) >>> gpsinfo = exifd[_TAGS_r["GPSInfo"]] >>> gpsinfo {0: b'\x00\x00\x02\x02', 1: 'N', 2: (53.0, 20.0, 58.86), 3: 'W', 4: (6.0, 15.0, 36.29), 5: b'\x00', 6: 0.0} >>> print("\n".join([GPSTAGS[k] for k in gpsinfo.keys()])) GPSVersionID GPSLatitudeRef GPSLatitude GPSLongitudeRef GPSLongitude GPSAltitudeRef GPSAltitude >>> print("\n".join([str((GPSTAGS[k], gpsinfo[k])) for k in gpsinfo.keys()])) ('GPSVersionID', b'\x00\x00\x02\x02') ('GPSLatitudeRef', 'N') ('GPSLatitude', (53.0, 20.0, 58.86)) ('GPSLongitudeRef', 'W') ('GPSLongitude', (6.0, 15.0, 36.29)) ('GPSAltitudeRef', b'\x00') ('GPSAltitude', 0.0) Building the exif tags by hand, and saving it ********************************************* It is too heavy to build exif data by hand with only pillow, because exif data is a binary (of course!), its specification might be larger than you think, and pillow's supports for it are not rich enough. Actually, `the Exif tag structure is borrowed from TIFF files `_, so you might think :code:`TiffImagePlugin.ImageFileDirectory*` are useful, but those are insufficient for Exif (in JPEG). Several reasons: 1. :code:`TiffImagePlugin.ImageFileDirectory*` doesn't know about value type information for non-TIFF tags. 2. :code:`TiffImagePlugin.ImageFileDirectory*` doesn't know about a whole Exif structure in JPEG. 3. :code:`TiffImagePlugin.ImageFileDirectory*` doesn't know about substructure needed by GPSInfo. If your needs are very simple, indeed you can: .. code-block:: pycon >>> from io import BytesIO >>> from PIL import Image, TiffImagePlugin, TiffTags >>> from PIL.ExifTags import TAGS >>> from PIL.TiffImagePlugin import ImageFileDirectory_v2 >>> >>> _TAGS_r = dict(((v, k) for k, v in TAGS.items())) >>> >>> # >>> jpgimg1 = Image.new("RGB", (64, 64)) >>> >>> # Image File Directory >>> ifd = ImageFileDirectory_v2() >>> >>> # TiffTags knows "Artist" (0x013b) >>> TiffTags.lookup(_TAGS_r["Artist"]) TagInfo(value=315, name='Artist', type=2, length=1, enum={}) >>> ifd[_TAGS_r["Artist"]] = u'somebody' >>> #ifd.tagtype[_TAGS_r['Artist']] = 2 # string, but you don't have to set explicitly. >>> >>> # TiffTags doesn't know "LightSource" (0x9208) >>> TiffTags.lookup(_TAGS_r["LightSource"]) TagInfo(value=37384, name='LightSource', type=None, length=0, enum={}) >>> ifd[_TAGS_r['LightSource']] = 1 # DayLight >>> ifd.tagtype[_TAGS_r['LightSource']] = 3 # short, you must set. >>> >>> ## >>> out = BytesIO() >>> ifd.save(out) 48 >>> ## you must add magic number of exif structure >>> exif = b"Exif\x00\x00" + out.getvalue() >>> >>> jpgimg1.save("out.jpg", exif=exif) >>> jpgimg2 = Image.open("out.jpg") >>> # >>> jpgimg2._getexif() {37384: 1, 315: 'somebody'} But your needs should be more complex, and your code will lose maintainability soon. You can use other third party library, for example `piexif `_: >>> from PIL import Image >>> import piexif >>> zeroth_ifd = { ... piexif.ImageIFD.Artist: u"someone", ... piexif.ImageIFD.XResolution: (96, 1), ... piexif.ImageIFD.YResolution: (96, 1), ... piexif.ImageIFD.Software: u"piexif" ... } >>> exif_ifd = { ... piexif.ExifIFD.DateTimeOriginal: u"2099:09:29 10:10:10", ... piexif.ExifIFD.LensMake: u"LensMake", ... piexif.ExifIFD.Sharpness: 65535, ... piexif.ExifIFD.LensSpecification: ((1, 1), (1, 1), (1, 1), (1, 1)), ... } >>> exif_dict = {"0th": zeroth_ifd, "Exif": exif_ifd} >>> exif_bytes = piexif.dump(exif_dict) >>> jpgimg1 = Image.new("RGB", (64, 64)) >>> jpgimg1.save("out.jpg", exif=exif_bytes) >>> jpgimg2 = Image.open("out.jpg") >>> # >>> jpgimg2._getexif() {36867: '2099:09:29 10:10:10', 315: 'someone', 34665: 105, 41994: 65535, 305: 'piexif', 42034: ((1, 1), (1, 1), (1, 1), (1, 1)), 42035: 'LensMake', 282: (96, 1), 283: (96, 1)} Accessing to GeoTIFF tags by Pillow *********************************** A typical library that can handle GeoTIFF is related to GDAL, but GDAL tends to be difficult to introduce due to its many dependencies, and some people say "I don't want to think about using it if possible". I'm not the only one thinking about this, but `noGDAL `__ is probably the forefront. Depending on how much GeoTIFF functionality you use for image processing, Pillow alone is quite possible if you just want to decipher GeoTIFF-specific tags: .. code-block:: python :caption: getgeotifftags.py # -*- coding: utf-8 -*- import struct from PIL import Image _tags = { # https://www.awaresystems.be/imaging/tiff/tifftags/modelpixelscaletag.html 33550: "ModelPixelScale", # (ScaleX, ScaleY, ScaleZ) # https://www.awaresystems.be/imaging/tiff/tifftags/modeltiepointtag.html 33922: "ModelTiepoint", # (...,I,J,K, X,Y,Z...) # https://www.awaresystems.be/imaging/tiff/tifftags/modeltransformationtag.html 34264: "ModelTransformation", # double*16 # https://www.awaresystems.be/imaging/tiff/tifftags/geokeydirectorytag.html 34735: "GeoKeyDirectory", # https://www.awaresystems.be/imaging/tiff/tifftags/geodoubleparamstag.html 34736: "GeoDoubleParams", # https://www.awaresystems.be/imaging/tiff/tifftags/geoasciiparamstag.html 34737: "GeoAsciiParams", } _keys = { 1024: 'GTModelTypeGeoKey', 1025: 'GTRasterTypeGeoKey', 1026: 'GTCitationGeoKey', 2048: 'GeographicTypeGeoKey', 2049: 'GeogCitationGeoKey', 2050: 'GeogGeodeticDatumGeoKey', 2051: 'GeogPrimeMeridianGeoKey', 2052: 'GeogLinearUnitsGeoKey', 2053: 'GeogLinearUnitSizeGeoKey', 2054: 'GeogAngularUnitsGeoKey', 2055: 'GeogAngularUnitsSizeGeoKey', 2056: 'GeogEllipsoidGeoKey', 2057: 'GeogSemiMajorAxisGeoKey', 2058: 'GeogSemiMinorAxisGeoKey', 2059: 'GeogInvFlatteningGeoKey', 2060: 'GeogAzimuthUnitsGeoKey', 2061: 'GeogPrimeMeridianLongGeoKey', 2062: 'GeogTOWGS84GeoKey', 3059: 'ProjLinearUnitsInterpCorrectGeoKey', # GDAL 3072: 'ProjectedCSTypeGeoKey', 3073: 'PCSCitationGeoKey', 3074: 'ProjectionGeoKey', 3075: 'ProjCoordTransGeoKey', 3076: 'ProjLinearUnitsGeoKey', 3077: 'ProjLinearUnitSizeGeoKey', 3078: 'ProjStdParallel1GeoKey', 3079: 'ProjStdParallel2GeoKey', 3080: 'ProjNatOriginLongGeoKey', 3081: 'ProjNatOriginLatGeoKey', 3082: 'ProjFalseEastingGeoKey', 3083: 'ProjFalseNorthingGeoKey', 3084: 'ProjFalseOriginLongGeoKey', 3085: 'ProjFalseOriginLatGeoKey', 3086: 'ProjFalseOriginEastingGeoKey', 3087: 'ProjFalseOriginNorthingGeoKey', 3088: 'ProjCenterLongGeoKey', 3089: 'ProjCenterLatGeoKey', 3090: 'ProjCenterEastingGeoKey', 3091: 'ProjFalseOriginNorthingGeoKey', 3092: 'ProjScaleAtNatOriginGeoKey', 3093: 'ProjScaleAtCenterGeoKey', 3094: 'ProjAzimuthAngleGeoKey', 3095: 'ProjStraightVertPoleLongGeoKey', 3096: 'ProjRectifiedGridAngleGeoKey', 4096: 'VerticalCSTypeGeoKey', 4097: 'VerticalCitationGeoKey', 4098: 'VerticalDatumGeoKey', 4099: 'VerticalUnitsGeoKey', } def getgeotiffdata(img): # FIXME: this can't handle correctly if its endian is big-endian. result = {} if not hasattr(img, "tag"): img = Image.open(img) tagdata = img.tag.tagdata # ModelPixelScale if 33550 in tagdata: result[_tags[33550]] = struct.unpack( "<3d", tagdata[33550]) # ModelTiepoint if 33922 in tagdata: result[_tags[33922]] = struct.unpack( "<{}d".format(len(tagdata[33922]) // 8), tagdata[33922]) # ModelTransformation if 34264 in tagdata: result[_tags[34264]] = struct.unpack( "<{}d".format(len(tagdata[34264]) // 8), tagdata[34264]) # GeoKeyDirectory # GeoDoubleParams # GeoAsciiParams if 34735 in tagdata: inner = result[_tags[34735]] = [{}, {}] # gkd = struct.unpack("<{}H".format(len(tagdata[34735]) // 2), tagdata[34735]) gkd = [gkd[i:i + 4] for i in range(0, len(gkd), 4)] KeyDirectoryVersion, KeyRevision, KeyRevisionMinor = gkd.pop(0)[:-1] inner[0]["KeyDirectoryVersion"] = KeyDirectoryVersion inner[0]["KeyRevision"] = KeyRevision inner[0]["KeyRevisionMinor"] = KeyRevisionMinor # for keyid, tagid, count, offset in gkd: if tagid == 0: value = offset else: if tagid == 34736: value = tagdata[tagid] value = struct.unpack("<{}d".format(len(value) // 8), value) elif tagid == 34737: value = tagdata[tagid][offset:offset + count] value = value.decode() if value[-1] == "|": value = value[:-1] else: raise NotImplementedError("sorry") inner[1][_keys.get(keyid, keyid)] = value return result, img.size if __name__ == '__main__': import sys import json geotiffdata, (width, height) = getgeotiffdata(sys.argv[1]) print(json.dumps(geotiffdata, indent=2)) .. code-block:: console [me@host: ~]$ python getgeotifftags.py data/manhattan.tif { "ModelTiepoint": [ 0.0, 0.0, 0.0, 583057.357, 4516255.36, 0.0 ], "ModelPixelScale": [ 0.999948245999997, 0.999948245999997, 0.0 ], "GeoKeyDirectory": [ { "KeyRevision": 1, "KeyDirectoryVersion": 1, "KeyRevisionMinor": 0 }, { "PCSCitationGeoKey": "IMAGINE GeoTIFF Support\nCopyright 1991 - 2005 by Leica Geosystems Geospatial Imaging, LLC. All Rights Reserved\n@(#)$RCSfile: egtf.c $ IMAGINE 9.0 $Revision: 10.0 $ $Date: 2005/07/26 15:10:00 EST $\nUTM Zone 18N\nEllipsoid = GRS 1980\nDatum = NAD83", "ProjLinearUnitsGeoKey": 9001, "ProjectedCSTypeGeoKey": 26918, "GTModelTypeGeoKey": 1, "GTRasterTypeGeoKey": 1, "GTCitationGeoKey": "IMAGINE GeoTIFF Support\nCopyright 1991 - 2005 by Leica Geosystems Geospatial Imaging, LLC. All Rights Reserved\n@(#)$RCSfile: egtf.c $ IMAGINE 9.0 $Revision: 10.0 $ $Date: 2005/07/26 15:10:00 EST $\nProjection Name = UTM\nUnits = meters\nGeoTIFF Units = meters" } ] } [me@host: ~]$ python getgeotifftags.py data/landsat.tif { "ModelTiepoint": [ 0.0, 0.0, 0.0, -13051837.419021819, 6193028.747207512, 0.0 ], "ModelPixelScale": [ 45.19374321600039, 45.14032535950664, 0.0 ], "GeoKeyDirectory": [ { "KeyRevision": 1, "KeyDirectoryVersion": 1, "KeyRevisionMinor": 0 }, { "ProjLinearUnitsGeoKey": 9001, "ProjectedCSTypeGeoKey": 3857, "GTModelTypeGeoKey": 1, "GeogAngularUnitsGeoKey": 9102, "GeogCitationGeoKey": "WGS 84", "GTRasterTypeGeoKey": 1, "GTCitationGeoKey": "WGS 84 / Pseudo-Mercator" } ] } [me@host: ~]$ python getgeotifftags.py NE1_LR_LC/NE1_LR_LC.tif { "ModelTiepoint": [ 0.0, 0.0, 0.0, -180.0, 90.0, 0.0 ], "ModelPixelScale": [ 0.02222222222222, 0.02222222222222, 0.0 ], "GeoKeyDirectory": [ { "KeyRevision": 1, "KeyDirectoryVersion": 1, "KeyRevisionMinor": 0 }, { "GTModelTypeGeoKey": 2, "GeogAngularUnitsGeoKey": 9102, "GeogInvFlatteningGeoKey": [ 298.257223563, 6378137.0 ], "GeogCitationGeoKey": "WGS 84", "GTRasterTypeGeoKey": 1, "GeographicTypeGeoKey": 4326, "GeogSemiMajorAxisGeoKey": [ 298.257223563, 6378137.0 ] } ] } [me@host: ~]$ python getgeotifftags.py FAA_UTM18N_NAD83.tif { "ModelTiepoint": [ 0.5, 0.5, 0.0, 223598.2000551123, 4217895.013917857, 0.0 ], "ModelPixelScale": [ 23.927070933333344, 23.927070933333344, 0.0 ], "GeoKeyDirectory": [ { "KeyRevision": 1, "KeyDirectoryVersion": 1, "KeyRevisionMinor": 0 }, { "ProjLinearUnitsGeoKey": 9001, "GTCitationGeoKey": "#MAP_PROJECTION\n\"NAD83 / UTM zone 18N\"\nNAD83,6378137,0.0818191910428158,0\n\"Transverse Mercator\",0,-75,0.9996,500000,0\n#UNITS_LENGTH\nm,1\n#MAP_DATUM_TRANSFORM\n\"NAD83 to WGS 84 (1)\",0,0,0,0,0,0,0\n", "ProjectedCSTypeGeoKey": 26918, "GTRasterTypeGeoKey": 1, "GTModelTypeGeoKey": 1 } ] } You may also want to consider `tifffile `__. If you use it, you can like this: .. code-block:: python :caption: getgeotifftags2.py # -*- coding: utf-8 -*- import tifffile def getgeotiffdata(img): return tifffile.TiffFile(img).pages[0].geotiff_tags if __name__ == '__main__': import sys import json print(json.dumps(getgeotiffdata(sys.argv[1]), indent=2)) .. code-block:: console [me@host: ~]$ python getgeotifftags2.py NE1_LR_LC/NE1_LR_LC.tif { "KeyDirectoryVersion": 1, "KeyRevision": 1, "KeyRevisionMinor": 0, "GTModelTypeGeoKey": 2, "GTRasterTypeGeoKey": 1, "GeographicTypeGeoKey": 4326, "GeogCitationGeoKey": "WGS 84", "GeogAngularUnitsGeoKey": 9102, "GeogSemiMajorAxisGeoKey": 6378137.0, "GeogInvFlatteningGeoKey": 298.257223563, "ModelPixelScale": [ 0.02222222222222, 0.02222222222222, 0.0 ], "ModelTiepoint": [ 0.0, 0.0, 0.0, -180.0, 90.0, 0.0 ] } You can get "modeltransformation" from "modelpixelscale", and "modeltiepoint": .. code-block:: python :caption: getgeotifftags.py # -*- coding: utf-8 -*- # # ...(snip)... # def gettransforms(geotiffdata): """ build modeltransformation from modelpixelscale, and modeltiepoint """ transforms = [] if "ModelPixelScale" in geotiffdata and "ModelTiepoint" in geotiffdata: # see "B.6. GeoTIFF Tags for Coordinate Transformations" # in https://earthdata.nasa.gov/files/19-008r4.pdf. sx, sy, sz = geotiffdata["ModelPixelScale"] tiepoints = geotiffdata["ModelTiepoint"] for tp in range(0, len(tiepoints), 6): i, j, k, x, y, z = tiepoints[tp:tp + 6] transforms.append([ [sx, 0.0, 0.0, x - i * sx], [0.0, -sy, 0.0, y + j * sy], [0.0, 0.0, sz, z - k * sz], [0.0, 0.0, 0.0, 1.0]]) return transforms if __name__ == '__main__': import sys import json import numpy as np geotiffdata, (width, height) = getgeotiffdata(sys.argv[1]) print(json.dumps(geotiffdata, indent=2)) transforms = gettransforms(geotiffdata) print(np.dot(transforms, [0, 0, 0, 1])[0,:2]) print(np.dot(transforms, [width, height, 0, 1])[0,:2]) .. code-block:: console [me@host: ~]$ python getgeotifftags.py gm-jpn-ve_u_1_1/jpn/ve.tif { "ModelTiepoint": [ 0.0, 0.0, 0.0, 120.00833129882812, 49.99166809767485, 0.0 ], "ModelPixelScale": [ 0.008333333767950535, 0.008333333767950535, 0.0 ], "GeoKeyDirectory": [ { "KeyRevision": 1, "KeyDirectoryVersion": 1, "KeyRevisionMinor": 0 }, { "GTRasterTypeGeoKey": 2 } ] } [120.0083313 49.9916681] [155.00833312 19.99166653]