Python - add arbitrary EXIF data to image (UserComment field)? - python

I need to add arbitrary data to a JPEG image. Specifically, I need to store two integers. From reading about EXIF data, I'm under the impression that it is not possible to make your own custom fields, but rather the EXIF standard fields must be used.
This post Custom Exif Tags however mentions a UserComment field which I gather it is possible to write a string to. If this is the only option it's fine since I can store two integers in a comma-delimited string, ex '2,5' to store the integers 2 and 5, so if I only have one string of storage to work with it's still sufficient.
I downloaded a few random images from a Google image search and found they don't seem to have EXIF data, perhaps it's stripped off purposefully by Google? Also I took a few images with my cell phone and found that as expected they have a significant amount of EXIF data (image size, GPS location, etc.)
Upon some Googleing I found this example on how to read/dump EXIF data:
from PIL import Image
image = Image.open('image.jpg')
exifData = image._getexif()
print('exifData = ' + str(exifData))
This works great, if I run this on an image with no EXIF data I get:
exifData = None
and if I run this on an image with EXIF data I get a dictionary showing the EXIF fields as expected.
Now my question is, how can I add to the EXIF data? Using the UserComment 37510 field mentioned in the above linked post, and also here https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/usercomment.html, and using piexif this is my best attempt so far:
from PIL import Image
import piexif
image = Image.open('image.jpg')
exifData = image._getexif()
if exifData is None:
exifData = {}
# end if
exifData[37510] = 'my message'
exifDataBytes = piexif.dump(exifData)
image.save('image_mod.jpg', format='jpeg', exif=exifDataBytes)
If I then run the 1st code above on image_mod.jpg I get:
exifData = {}
So clearly the 37510 message was not properly written. I get this same empty dictionary result whether I'm using an image that has EXIF data or an image without EXIF data to begin with.
Before somebody marks this as a duplicate, I also tried what this post How can I insert EXIF/other metadata into a JPEG stored in a memory buffer? mentions in the highest-rated answer and got the same result when attempting to read the EXIF data (empty dictionary).
What am I doing wrong? How can I properly add custom EXIF data to an image using 37510, or any other means?

You're missing a step in handling the data passed to piexif.dump:
exif_ifd = {piexif.ExifIFD.UserComment: 'my message'.encode()}
exif_dict = {"0th": {}, "Exif": exif_ifd, "1st": {},
"thumbnail": None, "GPS": {}}
exif_dat = piexif.dump(exif_dict)
img.save('image_mod.jpg', exif=exif_dat)
You should be able to read it back out after this. See also this answer for dealing with custom metadata.

Rasterio tags are the easiest way to add metadata of any kind to an image. Easy and practical. example:
import rasterio
old_file=rasterio.open('old_image.tif')
profile=old_file.profile
data=old_file.read()
with rasterio.open('new_image.tif','w',**profile) as dst:
dst.update_tags(a='1', b='2')
dst.write(data)
dst.close()
#now access the tags like below:
im=rasterio.open('new_image.tif')
print(im.tags())

Related

Convert mp3 song image from png to jpg

I have a set of many songs, some of which have png images in metadata, and I need to convert these to jpg.
I know how to convert png images to jpg in general, but I am currently accessing metadata using eyed3, which returns ImageFrame objects, and I don't know how to manipulate these. I can, for instance, access the image type with
print(img.mime_type)
which returns
image/png
but I don't know how to progress from here. Very naively I tried loading the image with OpenCV, but it is either not a compatible format or I didn't do it properly. And anyway I wouldn't know how to update the old image with the new one either!
Note: While I am currently working with eyed3, it is perfectly fine if I can solve this any other way.
I was finally able to solve this, although in a not very elegant way.
The first step is to load the image. For some reason I could not make this work with eyed3, but TinyTag does the job:
from PIL import Image
from tinytag import TinyTag
tag = TinyTag.get(mp3_path, image=True)
image_data = tag.get_image()
img_bites = io.BytesIO(image_data)
photo = Image.open(im)
Then I manipulate it. For example we may resize it and save it as jpg. Because we are using Pillow (PIL) for these operations, we actually need to save the image and finally load it back to get the binary data (this detail is probably what should be improved in the process).
photo = photo.resize((500, 500)) # suppose we want 500 x 500 pixels
rgb_photo = photo.convert("RGB")
rgb_photo.save(temp_file_path, format="JPEG")
The last step is thus to load the image and set it as metadata. You have more details about this step in this answer.:
audio_file = eyed3.load(mp3_path) # this has been loaded before
audio_file.tag.images.set(
3, open(temp_file_path, "rb").read(), "image/jpeg"
)
audio_file.tag.save()

python image taken date and time

I'm trying to create an array that contains the filenames of all images in a folder in the first column and the "time taken" of the image in the second column. This time should be in hh:mm:ss:msmsms (or hhmmssmsmsms), where "ms" is milliseconds.
I found a piece of code that uses the Pillow library with the to pull the EXIFTAG data of the image. I realize that I would need the DateTimeOriginal and SubsecTimeOriginal tags to get the data I want.
Now the problem is that I just don't understand how the code bellow pulls the data from the image and how I would be able to create the desired array. If anyone knows how the .ExifTags and ._getexif() modules work, some explanation would be appreciated.
code:
from PIL import Image
from PIL.ExifTags import TAGS
file_path = 'IMG_20200528_125319.jpg'
results = {}
i = Image.open(file_path)
info = i._getexif()
for tag, value in info.items():
decoded = TAGS.get(tag, tag)
results[decoded] = value
print results
Sadly the info I was looking for is not in the exif tags of the picture. See Mark Setchell's comments.

Python-Retain both geocoding and orientation while image save/overwite

I am using PIL to enhance my images. While saving I need both the geographic coordinates and the orientation angles written to the header of the enhanced image. So far I have failed find a way to write the orientation angles.
I could write the coordinates using piexif after reading Preserve exif data of image with PIL when resize(create thumbnail). But this seems not enough to write the orientation also, or maybe I am missing something.
im = Image.open(direc + '\\' + filename)
exif_dict = piexif.load(im.info["exif"])
exif_bytes = piexif.dump(exif_dict)
enhancer = ImageEnhance.Brightness(im)
enhanced_im = enhancer.enhance(1.8)
enhanced_im.save(s + 'enhanced\\' + directory + "\e_" + filename, "JPEG", exif=exif_bytes)
When I print my exif_dict I see two main keys 0th and Exif (with reasonable key-value pairs under each of them and a lot of \x00\x00\x00q\x00\x00\x00g\x00\x00\x00r\x00\x00\x00l\x00\x00\x0... such characters which continues even after the parenthesis of the dictionary has ended. Please advise.
You could write a world file for every image: https://en.wikipedia.org/wiki/World_file
Create a text file, calculate the values, write them in the text file and add the corresponding extension to the file name.
EDIT: If you need to change the exif values I would recommend looking at the tags which already are in the exif data and change/add the orientation tag (How to modify EXIF data in python).
If you search for exif orientation tag on google, you can find the explanation of the values. They are also explained on this page https://sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html.
This page also explains how to change the orientation https://magnushoff.com/jpeg-orientation.html.
Hopefully it helps.

Setting meta data tags with piexif

I try to set specific meta data for jpegs with the piexif module. I get the respective dicts out of the piexif.load().
data = piexif.load().
They return {'GPS': {}, 'Exif': {}, 'Interop': {}, 'thumbnail': None, '1st': {}, '0th': {}}. (Maybe the answer is very obviouse but I am a little confused to the dicts)
However, I would like to know where and what to write to set my focus length, the camera maker and model.
The reason for that, I want to use the Regard3D reconstruction GUI from http://www.regard3d.org/index.php/documentation/details/picture-set.
Therefore, I need to add the meta to the jpegs and the data of the camera into the camera db. This is needed for the triangulation step.
Thank you very much in advance
The Tags are different in the standard than in the tutorial. They supposed to be Make, Model, Focal Length in the EXIF format.
I believe that there is a limit to what can be done with piexif. Although, I was unable to find camera make/model.
However, you should be able to access the focal length like this.
(Of course there are multiple ways to achieve this. This was just what I came up with)
import piexif
from PIL import Image
img = Image.open('input filename or path')
# Defining dictionary
exif_dict = piexif.FocalLength(img.info["exif"])
if piexif.ExifIFD.FocalLength in exif_dict["Exif"]:
print("Current Focal Length is ", exif_dict["Exif"][piexif.ExifIFD.FocalLength])
else:
print("No Focal Length is set")
# Getting User Input
fl = input("Enter a Focal Length: ")
# Applying user variable
exif_dict["Exif"][piexif.ExifIFD.LensMake] = fl
# Converting to bytes
exif_bytes = piexif.dump(exif_dict)
#Saving Image
img.save("output filename or path", exif=exif_bytes)
There does look to be a lens make and lens model though.
piexif.ExifIFD.LensMake
piexif.ExifIFD.LensModel
Also, it might be worth noting that this value may not be changeable on some images and sometimes updating the value for an image does not seem to apply to the same field that would be displayed in windows 10 ui. I'm unsure if this is a bug or a compatibility issue with the image I was testing.
You also may want to check the piexif docs as there might be more information on it as well.

What is the best way to save image metadata alongside a tif?

In my work as a grad student, I capture microscope images and use python to save them as raw tif's. I would like to add metadata such as the name of the microscope I am using, the magnification level, and the imaging laser wavelength. These details are all important for how I post-process the images.
I should be able to do this with a tif, right? Since it has a header?
I was able to add to the info in a PIL image:
im.info['microscope'] = 'george'
but when I save and load that image, the info I added is gone.
I'm open to all suggestions. If I have too, I'll just save a separate .txt file with the metadata, but it would be really nice to have it embedded in the image.
Tifffile is one option for saving microscopy images with lots of metadata in python.
It doesn't have a lot of external documentation, but the docstings are great so you can get a lot of info just by typing help(tifffile) in python, or go look at the source code.
You can look at the TiffWriter.save function in the source code (line 750) for the different keyword arguments you can use to write metadata.
One is to use description, which accepts a string. It will show up as the tag "ImageDescription" when you read your image.
Another is to use the extratags argument, which accepts a list of tuples. That allows you to write any tag name that exist in TIFF.TAGS(). One of the easiest way is to write them as strings because then you don't have to specify length.
You can also write ImageJ metadata with ijmetadata, for which the acceptable types are listed in the source code here.
As an example, if you write the following:
import json
import numpy as np
import tifffile
im = np.random.randint(0, 255, size=(150, 100), dtype=np.uint8)
# Description
description = "This is my description"
# Extratags
metadata_tag = json.dumps({"ChannelIndex": 1, "Slice": 5})
extra_tags = [("MicroManagerMetadata", 's', 0, metadata_tag, True),
("ProcessingSoftware", 's', 0, "my_spaghetti_code", True)]
# ImageJ metadata. 'Info' tag needs to be a string
ijinfo = {"InitialPositionList": [{"Label": "Pos1"}, {"Label": "Pos3"}]}
ijmetadata = {"Info": json.dumps(ijinfo)}
# Write file
tifffile.imsave(
save_name,
im,
ijmetadata=ijmetadata,
description=description,
extratags=extra_tags,
)
You can see the following tags when you read the image:
frames = tifffile.TiffFile(save_name)
page = frames.pages[0]
print(page.tags["ImageDescription"].value)
Out: 'this is my description'
print(page.tags["MicroManagerMetadata"].value)
Out: {'ChannelIndex': 1, 'Slice': 5}
print(page.tags["ProcessingSoftware"].value)
Out: my_spaghetti_code
For internal use, try saving the metadata as JSON in the TIFF ImageDescription tag, e.g.
from __future__ import print_function, unicode_literals
import json
import numpy
import tifffile # http://www.lfd.uci.edu/~gohlke/code/tifffile.py.html
data = numpy.arange(256).reshape((16, 16)).astype('u1')
metadata = dict(microscope='george', shape=data.shape, dtype=data.dtype.str)
print(data.shape, data.dtype, metadata['microscope'])
metadata = json.dumps(metadata)
tifffile.imsave('microscope.tif', data, description=metadata)
with tifffile.TiffFile('microscope.tif') as tif:
data = tif.asarray()
metadata = tif[0].image_description
metadata = json.loads(metadata.decode('utf-8'))
print(data.shape, data.dtype, metadata['microscope'])
Note that JSON uses unicode strings.
To be compatible with other microscopy software, consider saving OME-TIFF files, which store defined metadata as XML in the ImageDescription tag.
I should be able to do this with a tif, right? Since it has a header?
No.
First, your premise is wrong, but that's a red herring. TIFF does have a header, but it doesn't allow you to store arbitrary metadata in it.
But TIFF is a tagged file format, a series of chunks of different types, so the header isn't important here. And you can always create your own private chunk (any ID > 32767) and store anything you want there.
The problem is, nothing but your own code will have any idea what you stored there. So, what you probably want is to store EXIF or XMP or some other standardized format for extending TIFF with metadata. But even there, EXIF or whatever you choose isn't going to have a tag for "microscope", so ultimately you're going to end up having to store something like "microscope=george\nspam=eggs\n" in some string field, and then parse it back yourself.
But the real problem is that PIL/Pillow doesn't give you an easy way to store EXIF or XMP or anything else like that.
First, Image.info isn't for arbitrary extra data. At save time, it's generally ignored.
If you look at the PIL docs for TIFF, you'll see that it reads additional data into a special attribute, Image.tag, and can save data by passing a tiffinfo keyword argument to the Image.save method. But that additional data is a mapping from TIFF tag IDs to binary hunks of data. You can get the Exif tag IDs from the undocumented PIL.ExifTags.TAGS dict (or by looking them up online yourself), but that's as much support as PIL is going to give you.
Also, note that accessing tag and using tiffinfo in the first place requires a reasonably up-to-date version of Pillow; older versions, and classic PIL, didn't support it. (Ironically, they did have partial EXIF support for JPG files, which was never finished and has been stripped out…) Also, although it doesn't seem to be documented, if you built Pillow without libtiff it seems to ignore tiffinfo.
So ultimately, what you're probably going to want to do is:
Pick a metadata format you want.
Use a different library than PIL/Pillow to read and write that metadata. (For example, you can use GExiv2 or pyexif for EXIF.)
You could try setting tags in the tag property of a TIFF image. This is an ImageFileDirectory object. See TiffImagePlugin.py.
Or, if you have libtiff installed, you can use the subprocess module to call the tiffset command to set a field in the header after you have saved the file. There are online references of available tags.
According to this page:
If one needs more than 10 private tags or so, the TIFF specification suggests that, rather then using a large amount of private tags, one should instead allocate a single private tag, define it as datatype IFD, and use it to point to a socalled 'private IFD'. In that private IFD, one can next use whatever tags one wants. These private IFD tags do not need to be properly registered with Adobe, they live in a namespace of their own, private to the particular type of IFD.
Not sure if PIL supports this, though.

Categories

Resources