Cannot get tiff image resolution - python

I'm trying to read 16 bit .tif microscope images from
https://data.broadinstitute.org/bbbc/BBBC006/
and analyze them using
https://github.com/sakoho81/pyimagequalityranking/tree/master/pyimq
however I got an error in the part of the code that loads the tif image.
It uses the PIL tiffimageplugin:
https://pillow.readthedocs.io/en/3.0.0/_modules/PIL/TiffImagePlugin.html
and when it tries to get the resolution tag, it gives me a keyerror
Any ideas why? Advice? Fixes?
Thanks!
import os
import numpy
import scipy.ndimage.interpolation as itp
import argparse
from PIL import Image
from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
from matplotlib import pyplot as plt
from math import log10, ceil, floor
def get_image_from_imagej_tiff(cls, path):
"""
A class method for opening a ImageJ tiff file. Using this method
will enable the use of correct pixel size during analysis.
:param path: Path to an image
:return: An object of the MyImage class
"""
assert os.path.isfile(path)
assert path.endswith(('.tif', '.tiff'))
print(path) #my own little debug thingamajig
image = Image.open(path)
xresolution = image.tag.tags[X_RESOLUTION][0][0] #line that errors out
yresolution = image.tag.tags[Y_RESOLUTION][0][0]
#data = utils.rescale_to_min_max(numpy.array(image), 0, 255)
if data.shape[0] == 1:
data = data[0]
return cls(images=data, spacing=[1.0/xresolution, 1.0/yresolution])
terminal input:
pyimq.main --mode=directory --mode=analyze --mode=plot --working-directory=/home/myufa/predxion/BBBC/a_1_s1 --normalize-power --result=fstd --imagej
output:
Mode option is ['directory', 'analyze', 'plot']
/home/myufa/predxion/BBBC/a_1_s1/z0_a_1_s1_w1.tif
Traceback (most recent call last):
File "/home/myufa/.local/bin/pyimq.main", line 11, in <module>
load_entry_point('PyImageQualityRanking==0.1', 'console_scripts', 'pyimq.main')()
File "/home/myufa/anaconda3/lib/python3.7/site-packages/PyImageQualityRanking-0.1-py3.7.egg/pyimq/bin/main.py", line 148, in main
File "/home/myufa/anaconda3/lib/python3.7/site-packages/PyImageQualityRanking-0.1-py3.7.egg/pyimq/myimage.py", line 81, in get_image_from_imagej_tiff
KeyError: 282
Edit: Here's what I got when I tried some suggestions/indexed the tag, which makes even less sense

I guess the tiff in question isn't following the normal image conventions. The [XY]Resolution tags, number 282 and 283, are mandatory or required in a whole bunch of specifications, but none the less may not be present in all applications. I have some TIFFs (DNG format) that wouldn't load with PIL (Pillow) at all; that prompted me to write a script to dump the primary tag structure:
# TIFF structure program
import struct
import PIL.TiffTags
class DE:
def __init__(self, tiff):
self.tiff = tiff
(self.tag, self.type, self.count, self.valueoroffset) = struct.unpack(
tiff.byteorder+b'HHI4s', self.tiff.file.read(12))
# TODO: support reading the value
def getstring(self):
offset = struct.unpack(self.tiff.byteorder+b'I', self.valueoroffset)[0]
self.tiff.file.seek(offset)
return self.tiff.file.read(self.count)
class IFD:
def __init__(self, tiff):
self.tiff = tiff
self.offset = tiff.file.tell()
(self.len,) = struct.unpack(self.tiff.byteorder+b'H', self.tiff.file.read(2))
def __len__(self):
return self.len
def __getitem__(self, index):
if index>=self.len or index<0:
raise IndexError()
self.tiff.file.seek(self.offset+2+12*index)
return DE(self.tiff)
def nextoffset(self):
self.tiff.file.seek(self.offset+2+12*self.len)
(offset,) = struct.unpack(self.tiff.byteorder+b'I', self.tiff.file.read(4))
return (offset if offset!=0 else None)
class TIFF:
def __init__(self, file):
self.file = file
header = self.file.read(8)
self.byteorder = {b'II': b'<', b'MM': b'>'}[header[:2]]
(magic, self.ifdoffset) = struct.unpack(self.byteorder+b'HI', header[2:])
assert magic == 42
def __iter__(self):
offset = self.ifdoffset
while offset:
self.file.seek(offset)
ifd = IFD(self)
yield ifd
offset = ifd.nextoffset()
def main():
tifffile = open('c:/users/yann/pictures/img.tiff', 'rb')
tiff = TIFF(tifffile)
for ifd in tiff:
print(f'IFD at {ifd.offset}, {ifd.len} entries')
for entry in ifd:
print(f' tag={entry.tag} {PIL.TiffTags.lookup(entry.tag).name}')
if __name__=='__main__':
main()
A quicker way, since you at least have the image object, might be:
import pprint, PIL.TiffTags
pprint.pprint(list(map(PIL.TiffTags.lookup, img.tag)))
One of these might give you a clue what the actual contents of the TIFF are. Since PIL could load it, it probably has pixel counts but not physical resolution.

Figured out a quick fix, writing
image.tag[X_RESOLUTION]
before
xresolution = image.tag.tags[X_RESOLUTION][0][0]
made the info available in the tag.tags dictionary for some reason. Can anyone chime in and explain why this might be? Would love to learn/make sure I didn't mess it up

Related

use python to get the size of an image (NEF and jpeg)

When taking pictures with my NIKON D5100 I shoot both in raw (.NEF) and jpeg. Most of the time I don't use that raw image, but from time to time I do.
To ease the tagging and selection of the images I want to automatically move the raw images to a different location. As a check in the python script to move the raw images I want to compare the image resolution of the jpeg to the raw resolution. So far I have not found a method that returns the same size for the raw and jpeg image.
This is a summary of the methods I tried using Python:
from PIL import Image as PIL_Image
from wand.image import Image as wand_Image
import imageio
from PIL.ExifTags import TAGS
import exifread
from collections import namedtuple
def get_exif(fname):
# http://www.blog.pythonlibrary.org/2010/03/28/getting-photo-metadata-exif-using-python/
ret = {}
i = PIL_Image.open(fname)
info = i._getexif()
for tag, value in info.items():
decoded = TAGS.get(tag, tag)
ret[decoded] = value
return ret
def get_imagesize_PIL(fname):
with PIL_Image.open(fname) as im:
return im.size
def get_imagesize_PIL2(fname):
exif = get_exif(fname)
return exif['ExifImageHeight'], exif['ExifImageWidth']
def get_imagesize_PIL3(fname):
with PIL_Image.open(fname) as im:
return im.info
def get_imagesize_imageioraw(fname):
return imageio.imread(fname, "raw").shape[:-1]
def get_imagesize_imageio(fname):
return imageio.imread(fname).shape[:-1]
def get_imagesize_exifread(fname): # http://stackoverflow.com/a/18027454/1562285
exif = exifread.process_file(open(fname, 'rb'), strict=True)
return exif['Image XResolution'], exif['Image YResolution']
def get_imagesize_exifread2(fname):
exif = exifread.process_file(open(fname, 'rb'), strict=True)
return exif['Image ImageLength'], exif['Image ImageWidth']
def get_imagesize_exifread3(fname):
exif = exifread.process_file(open(fname, 'rb'), strict=True)
return exif['MakerNote CropHiSpeed']
def get_imagesize_exifread4(fname):
exif = exifread.process_file(open(fname, 'rb'), strict=True)
return exif['EXIF ExifImageLength'], exif['EXIF ExifImageWidth']
def get_imagesize_wand(fname):
with wand_Image(filename=fname) as img:
return img.size
def get_imagesize_wand2(fname):
with wand_Image(filename=fname) as img:
return img.page
# def get_imagesize_wand3(fname):
# with wand_Image(filename=fname) as img:
# return img.info
def get_imagesize_libraw(fname):
with rawkit_Raw(filename=fname) as raw:
print(raw.Metadata.height, raw.Metadata.width)
def create_eval(fmethod, fname):
try:
eval_str = "get_imagesize_%s('%s')" % (fmethod, fname)
# print(eval_str)
return eval(eval_str)
except BaseException as e:
return str(e)
if __name__ == '__main__':
file_nt = namedtuple("image_file", "filename tag")
filetypes = list()
filetypes.append(file_nt("20120917_131155 DSC_0159.JPG", "jpeg"))
filetypes.append(file_nt("20120917_131155 DSC_0159.NEF", "nef"))
# filetypes.append(file_nt("20120917_131155 DSC_0159.xmp", "xmp"))
# #TODO: add method to check into xmp?
methods = [
"PIL",
"PIL2",
"PIL3",
"imageioraw",
"imageio",
"exifread",
"exifread2",
"exifread3",
"exifread4",
"wand",
"wand2",
"libraw",
]
for method in methods:
for filetype in filetypes:
print("%s %s: %s" % (filetype.tag, method, repr(create_eval(method, filetype.filename))))
# #TODO: add timers to check the fastest method
with this result
jpeg PIL: (4928, 3264)
nef PIL: (160, 120)
jpeg PIL2: (3264, 4928)
nef PIL2: "'TiffImageFile' object has no attribute '_getexif'"
jpeg PIL3: {'exif': b'Exif\x00\x00MM\x00*\x00\x00\x00\x08\x00\x0b\x01\x0f\x00\x02\x00\x00\x00\x12\x00\x00\x00\x94\x01\x10\x00\x02\x00\x00\x00\x0c\x00\x00\x00\xa8\x01\x12\x00...'}
nef PIL3: {'compression': 'raw', 'dpi': (300.0, 300.0)}
jpeg imageioraw: 'Could not load bitmap "C:\\Users\\maarten\\PycharmProjects\\fotosize\\20120917_131155 DSC_0159.JPG": LibRaw : failed to open input stream (unknown format)'
nef imageioraw: (3280, 4948)
jpeg imageio: (3264, 4928)
nef imageio: (120, 160)
jpeg exifread: ((0x011A) Ratio=300 # 180, (0x011B) Ratio=300 # 188)
nef exifread: ((0x011A) Ratio=300 # 356, (0x011B) Ratio=300 # 364)
jpeg exifread2: "'Image ImageLength'"
nef exifread2: ((0x0101) Long=120 # 42, (0x0100) Long=160 # 30)
jpeg exifread3: (0x001B) Short=[0, 4992, 3280, 4992, 3280, 0, 0] # 1676
nef exifread3: (0x001B) Short=[0, 4992, 3280, 4992, 3280, 0, 0] # 1852
jpeg exifread4: ((0xA003) Short=3264 # 526, (0xA002) Short=4928 # 514)
nef exifread4: "'EXIF ExifImageLength'"
jpeg wand: (4928, 3264)
nef wand: (4948, 3280)
jpeg wand2: (4928, 3264, 0, 0)
nef wand2: (4948, 3280, 0, 0)
jpeg libraw: 'Cannot find LibRaw on your system!'
nef libraw: 'Cannot find LibRaw on your system!'
The ones returning 160x120 is probably for the embedded preview image
Windows explorer does seem to find the correct dimensions
screenshot
I've tried rawkit too, but it doesn't seem to find the libraw. I found no clear instructions on how to 'install' libraw, but I tried copying it named as libraw.dll or Raw.dll into "C:\Anaconda3\Library\bin", and when I try
In[3]: ctypes.util.find_library("libraw")
Out[3]:
'C:\\Anaconda3\\Library\\bin\\libraw.dll'
In[4]: ctypes.util.find_library("Raw")
Out[4]:
'C:\\Anaconda3\\Library\\bin\\Raw.dll'
it does seem to find it.
Does anyone know what method I can use? It doesn't have to be the same method for the jpeg and the NEF-image, but that'd be welcome.
At this moment performance isn't a key issue, but the faster the better
The code and a sample image can be found in my github repo
ImageMagick identifies them as follows:
identify sign.jpg
Output
sign.jpg JPEG 4928x3264 4928x3264+0+0 8-bit sRGB 4.401MB 0.000u 0:00.009
and for the NEF:
identify ~/Desktop/*NEF
Output
/Users/... NEF 4948x3280 4948x3280+0+0 16-bit sRGB 75.99MB 0.010u 0:00.009
I've found a different solution employing win32com thanks to this postP
from pprint import pprint
import win32com.client
import os
import re
from pathlib import Path
# https://bytes.com/topic/python/answers/802917-finding-file-details
def _get_dimensions_subdir(folder_name):
sh = win32com.client.Dispatch('Shell.Application')
ns = sh.NameSpace(folder_name)
results = {}
for c in range(0, 255):
colname = ns.GetDetailsOf(None, c)
if colname == 'Dimensions':
for i in ns.Items():
dimensions = ns.GetDetailsOf(i, c)
if dimensions:
results[ns.GetDetailsOf(i, 0)] = _parse_dimensions(dimensions)
return results
dim_pat = re.compile("(\d+) x (\d+)")
def _parse_dimensions(dim):
x, y = dim_pat.findall(dim)[0]
return int(x), int(y)
def get_combined_results(subdirs, src_path):
for subdir in subdirs:
dim = _get_dimensions_subdir(str(subdir))
if dim:
yield str(subdir.relative_to(src_path)), dim
if __name__ == '__main__':
DATA_DIR = Path(".")
subdirs = DATA_DIR.glob("**")
results = get_combined_results(subdirs, DATA_DIR)
pprint(dict(results))

How to update HTML block in IPython

This is related to another question I posted, but is more specific (and hopefully gets more specific answers).
I am trying to display png images on an IPython notebook, and update the display as the png files are updated.
One possible solution is below. Unfortunately, my implementation does not update the file, and creates a new HTML block at the end of the old one. What I want instead, is to replace the old HTML block with the new one -i.e. replace the content only.
As example code, I have two notebooks. One notebook generates pngs and saves them in a figures directory.
import os
import glob
import time
import matplotlib.pyplot as plt
import numpy as np
for ix in range(20):
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
colors = np.random.rand(N)
area = np.pi * (15 * np.random.rand(N))**2 # 0 to 15 point radiuses
fig = plt.figure(figsize=(8,8))
plt.scatter(x, y, s=area, c=colors, alpha=0.5)
fig.savefig(os.path.join("figures", "fig1.png"), bbox_inches="tight")
plt.close(fig)
time.sleep(3)
A second notebook shows the pngs It is supposed top show a single png which updates. Instead it stacks the same png on itself, every time the png is updated.
import os
import time
from IPython.html.widgets import interact, interactive, fixed
from IPython.html import widgets
from IPython.display import clear_output, display, HTML
def get_latest_file_ts(directory="figures", file_name="fig1.png", strip_directory=True):
"""
Continuously check for modifications to the file file_name in directory. If file has been
modified after touched_on, return the Unix timestamp of the modification time.
:param directory: string / the directory where the file is
:param file_name: string / the file name
:param strip_directory: boolean / if True, strip the directory part of the file name
:return:
"""
if strip_directory:
fname = os.path.join(directory, file_name)
else:
fname = file_name
try:
return os.stat(fname).st_mtime
except:
print "FileNotFoundException: Could not find file %s" % fname
return None
def check_if_modified_file(directory="figures", file_name="fig1.png",
touched_on=1420070400, sleep_time=1, strip_directory=True):
"""
Continuously check for modifications to the file file_name in directory. If file has been
modified after touched_on, return the Unix timestamp of the modification time.
:param directory: string / the directory where the file is
:param file_name: string / the file name
:param touched_on: float / the Unix timestamp on which the file was last modified
:param sleep_time: float / wait time between interactions
:param strip_directory: boolean / if True, strip the directory part of the file name
:return:
"""
if strip_directory:
fname = os.path.join(directory, file_name)
else:
fname = file_name
while True:
try:
latest_touch = os.stat(fname).st_mtime
if latest_touch == touched_on:
time.sleep(sleep_time)
else:
return latest_touch
except:
print "FileNotFoundException: Could not find %s" % fname
return None
def show_figs(directory="figures", file_name="fig1.png"):
s = """<figure>\n\t<img src="%s" alt="The figure" width="304" height="228">\n</figure>""" % os.path.join(directory, file_name)
display(HTML(s))
timestamp = get_latest_file_ts(directory="figures", file_name="fig1.png", strip_directory=True)
show_figs(directory="figures", file_name="fig1.png")
cnt = 1
while True and cnt < 4:
timestamp = check_if_modified_file(directory="figures", file_name="fig1.png", touched_on=timestamp, sleep_time=1, strip_directory=True)
display(HTML(""))
show_figs(directory="figures", file_name="fig1.png")
time.sleep(1)
cnt += 1
(as you can see I have added upper limits on the executions of both the generator and consumer loops)
Any help on how to make widgets update HTML content would be awesome.
The problem that you only see the first image is very likely related to caching of the browser. To overcome this issue a simple solution is to add a varying query string to the image src as shown e.g. here.
Thus your show_figs method could look like:
import time
def show_figs(directory="figures", file_name="fig1.png"):
s = """<figure>\n\t<img src="{0}?{1}" alt="The figure" width="304" height="228">\n</figure>""".format(os.path.join(directory, file_name),time.time())
display(HTML(s))
In combination with the clear_output function you should be able to get your updated image.

Django PIL : IOError Cannot identify image file

I'm learning Python and Django.
An image is provided by the user using forms.ImageField(). Then I have to process it in order to create two different sized images.
When I submit the form, Django returns the following error:
IOError at /add_event/
cannot identify image file
I call the resize function:
def create_event(owner_id, name, image):
image_thumb = image_resizer(image, name, '_t', 'events', 180, 120)
image_medium = image_resizer(image, name, '_m', 'events', 300, 200)
I get en error when image_resizer is called for the second time:
def image_resizer(image, name, size, app_name, length, height):
im = Image.open(image)
if im.mode != "RGB":
im = im.convert("RGB")
im = create_thumb(im, length, height)
posit = str(MEDIA_ROOT)+'/'+app_name+'/'
image_2 = im
image_name = name + size +'.jpg'
imageurl = posit + image_name
image_2.save(imageurl,'JPEG',quality=80)
url_image='/'+app_name+'/'+image_name
return url_image
Versions:
Django 1.3.1
Python 2.7.1
PIL 1.1.7
I'm trying to find the problem, but i don't know what to do. Thank you in advanced!
EDIT
I solved rewriting the function; now it creates the different images in batch:
I call the resize function:
url_array = image_resizer.resize_batch(image, image_name, [[180,120,'_t'], [300,200,'_m']], '/events/')
so:
image_thumb = url_array[0]
image_medium = url_array[1]
and the resize function:
def resize_batch(image, name, size_array, position):
im = Image.open(image)
if im.mode != "RGB":
im = im.convert("RGB")
url_array = []
for size in size_array:
new_im = create_thumb(im, size[0], size[1])
posit = str(MEDIA_ROOT) + position
image_name = name + size[2] +'.jpg'
imageurl = posit + image_name
new_im.save(imageurl,'JPEG',quality=90)
new_url_array = position + image_name
url_array.append(new_url_array)
return url_array
Thanks to all!
As ilvar asks in the comments, what kind of object is image? I'm going to assume for the purposes of this answer that it's the file property of a Django ImageField that comes from a file uploaded by a remote user.
After a file upload, the object you get in the ImageField.file property is a TemporaryUploadedFile object that might represent a file on disk or in memory, depending on how large the upload was. This object behaves much like a normal Python file object, so after you have read it once (to make the first thumbnail), you have reached the end of the file, so that when you try to read it again (to make the second thumbnail), there's nothing there, hence the IOError. To make a second thumbnail, you need to seek back to the beginning of the file. So you could add the line
image.seek(0)
to the start of your image_resizer function.
But this is unnecessary! You have this problem because you are asking the Python Imaging Library to re-read the image for each new thumbnail you want to create. This is a waste of time: better to read the image just once and then create all the thumbnails you want.
I'm guessing that is a TemporaryUploadedFile ... find this with type(image).
import cStringIO
if isinstance(image, TemporaryUploadedFile):
temp_file = open(image.temporary_file_path(), 'rb+')
content = cStringIO.StringIO(temp_file.read())
image = Image.open(content)
temp_file.close()
I'm not 100% sure of the code above ... comes from 2 classes I've got for image manipulation ... but give it a try.
If is a InMemoryUploadedFile your code should work!

In Python, how do I read the exif data for an image?

I'm using PIL. How do I turn the EXIF data of a picture into a dictionary?
You can use the _getexif() protected method of a PIL Image.
import PIL.Image
img = PIL.Image.open('img.jpg')
exif_data = img._getexif()
This should give you a dictionary indexed by EXIF numeric tags. If you want the dictionary indexed by the actual EXIF tag name strings, try something like:
import PIL.ExifTags
exif = {
PIL.ExifTags.TAGS[k]: v
for k, v in img._getexif().items()
if k in PIL.ExifTags.TAGS
}
For Python3.x and starting Pillow==6.0.0, Image objects now provide a "public"/official getexif() method that returns a <class 'PIL.Image.Exif'> instance or None if the image has no EXIF data.
From Pillow 6.0.0 release notes:
getexif() has been added, which returns an Exif instance. Values can
be retrieved and set like a dictionary. When saving JPEG, PNG or WEBP,
the instance can be passed as an exif argument to include any changes
in the output image.
As stated, you can iterate over the key-value pairs of the Exif instance like a regular dictionary. The keys are 16-bit integers that can be mapped to their string names using the ExifTags.TAGS module.
from PIL import Image, ExifTags
img = Image.open("sample.jpg")
img_exif = img.getexif()
print(type(img_exif))
# <class 'PIL.Image.Exif'>
if img_exif is None:
print('Sorry, image has no exif data.')
else:
for key, val in img_exif.items():
if key in ExifTags.TAGS:
print(f'{ExifTags.TAGS[key]}:{val}')
# ExifVersion:b'0230'
# ...
# FocalLength:(2300, 100)
# ColorSpace:1
# ...
# Model:'X-T2'
# Make:'FUJIFILM'
# LensSpecification:(18.0, 55.0, 2.8, 4.0)
# ...
# DateTime:'2019:12:01 21:30:07'
# ...
Tested with Python 3.8.8 and Pillow==8.1.0.
You can also use the ExifRead module:
import exifread
# Open image file for reading (binary mode)
f = open(path_name, 'rb')
# Return Exif tags
tags = exifread.process_file(f)
I use this:
import os,sys
from PIL import Image
from PIL.ExifTags import TAGS
for (k,v) in Image.open(sys.argv[1])._getexif().items():
print('%s = %s' % (TAGS.get(k), v))
or to get a specific field:
def get_field (exif,field) :
for (k,v) in exif.items():
if TAGS.get(k) == field:
return v
exif = image._getexif()
print get_field(exif,'ExposureTime')
import sys
import PIL
import PIL.Image as PILimage
from PIL import ImageDraw, ImageFont, ImageEnhance
from PIL.ExifTags import TAGS, GPSTAGS
class Worker(object):
def __init__(self, img):
self.img = img
self.exif_data = self.get_exif_data()
self.lat = self.get_lat()
self.lon = self.get_lon()
self.date =self.get_date_time()
super(Worker, self).__init__()
#staticmethod
def get_if_exist(data, key):
if key in data:
return data[key]
return None
#staticmethod
def convert_to_degress(value):
"""Helper function to convert the GPS coordinates
stored in the EXIF to degress in float format"""
d0 = value[0][0]
d1 = value[0][1]
d = float(d0) / float(d1)
m0 = value[1][0]
m1 = value[1][1]
m = float(m0) / float(m1)
s0 = value[2][0]
s1 = value[2][1]
s = float(s0) / float(s1)
return d + (m / 60.0) + (s / 3600.0)
def get_exif_data(self):
"""Returns a dictionary from the exif data of an PIL Image item. Also
converts the GPS Tags"""
exif_data = {}
info = self.img._getexif()
if info:
for tag, value in info.items():
decoded = TAGS.get(tag, tag)
if decoded == "GPSInfo":
gps_data = {}
for t in value:
sub_decoded = GPSTAGS.get(t, t)
gps_data[sub_decoded] = value[t]
exif_data[decoded] = gps_data
else:
exif_data[decoded] = value
return exif_data
def get_lat(self):
"""Returns the latitude and longitude, if available, from the
provided exif_data (obtained through get_exif_data above)"""
# print(exif_data)
if 'GPSInfo' in self.exif_data:
gps_info = self.exif_data["GPSInfo"]
gps_latitude = self.get_if_exist(gps_info, "GPSLatitude")
gps_latitude_ref = self.get_if_exist(gps_info, 'GPSLatitudeRef')
if gps_latitude and gps_latitude_ref:
lat = self.convert_to_degress(gps_latitude)
if gps_latitude_ref != "N":
lat = 0 - lat
lat = str(f"{lat:.{5}f}")
return lat
else:
return None
def get_lon(self):
"""Returns the latitude and longitude, if available, from the
provided exif_data (obtained through get_exif_data above)"""
# print(exif_data)
if 'GPSInfo' in self.exif_data:
gps_info = self.exif_data["GPSInfo"]
gps_longitude = self.get_if_exist(gps_info, 'GPSLongitude')
gps_longitude_ref = self.get_if_exist(gps_info, 'GPSLongitudeRef')
if gps_longitude and gps_longitude_ref:
lon = self.convert_to_degress(gps_longitude)
if gps_longitude_ref != "E":
lon = 0 - lon
lon = str(f"{lon:.{5}f}")
return lon
else:
return None
def get_date_time(self):
if 'DateTime' in self.exif_data:
date_and_time = self.exif_data['DateTime']
return date_and_time
if __name__ == '__main__':
try:
img = PILimage.open(sys.argv[1])
image = Worker(img)
lat = image.lat
lon = image.lon
date = image.date
print(date, lat, lon)
except Exception as e:
print(e)
I have found that using ._getexif doesn't work in higher python versions, moreover, it is a protected class and one should avoid using it if possible.
After digging around the debugger this is what I found to be the best way to get the EXIF data for an image:
from PIL import Image
def get_exif(path):
return Image.open(path).info['parsed_exif']
This returns a dictionary of all the EXIF data of an image.
Note: For Python3.x use Pillow instead of PIL
Here's the one that may be little easier to read. Hope this is helpful.
from PIL import Image
from PIL import ExifTags
exifData = {}
img = Image.open(picture.jpg)
exifDataRaw = img._getexif()
for tag, value in exifDataRaw.items():
decodedTag = ExifTags.TAGS.get(tag, tag)
exifData[decodedTag] = value
To read image url and get tags
from PIL import Image
from urllib.request import urlopen
from PIL.ExifTags import TAGS
def get_exif(filename):
image = Image.open(filename)
image.verify()
return image._getexif()
def get_labeled_exif(exif):
labeled = {}
for (key, val) in exif.items():
labeled[TAGS.get(key)] = val
return labeled
my_image= urlopen(url)
exif = get_exif(my_image)
labeled = get_labeled_exif(exif)
print(labeled)
and to get GPS coordinate, Jayson DeLancey has excellent blog post.
Feb 2023 Pillow information
Starting version 8.2.0 API of PIL changed slightly, hiding most of tags a bit deeper into methods of Exif. All other answers became slightly outdated, showing only few tags (around 14).
The modern way of doing it:
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS, IFD
from pillow_heif import register_heif_opener # HEIF support
register_heif_opener() # HEIF support
def print_exif(fname: str):
img = Image.open(fname)
exif = img.getexif()
print('>>>>>>>>>>>>>>>>>>', 'Base tags', '<<<<<<<<<<<<<<<<<<<<')
for k, v in exif.items():
tag = TAGS.get(k, k)
print(tag, v)
for ifd_id in IFD:
print('>>>>>>>>>', ifd_id.name, '<<<<<<<<<<')
try:
ifd = exif.get_ifd(ifd_id)
if ifd_id == IFD.GPSInfo:
resolve = GPSTAGS
else:
resolve = TAGS
for k, v in ifd.items():
tag = resolve.get(k, k)
print(tag, v)
except KeyError:
pass
Only some of useful tags are available on the root level of Exif now (e.g. Make, Model, DateTime, Orientation, Software. In order to access other useful tags, such as ShutterSpeedValue, ApertureValue, ISOSpeedRatings, WhiteBalance, DateTimeOriginal, DateTimeDigitized, ExposureBiasValue, FocalLength, ExifImageWidth, ExifImageHeight, etc, you need to get an IFD called Exif. For GPS information, use IFD GPSInfo. Also note that GPS tags have another tag-to-int encoding dictionary.
These two lines
from pillow_heif import register_heif_opener
register_heif_opener()
are required only if you want to have support of HEIF format, that is configured by default on modern Apple devices (.HEIC file extension). If you don't need to work with HEIF, you can omit them, the code will work for the rest of image formats supported by PIL.
Package references:
Pillow, the way to work with images in Python.
pillow-heif or it's lighter version pi-heif (no save() support).
I usually use pyexiv2 to set exif information in JPG files, but when I import the library in a script QGIS script crash.
I found a solution using the library exif:
https://pypi.org/project/exif/
It's so easy to use, and with Qgis I don,'t have any problem.
In this code I insert GPS coordinates to a snapshot of screen:
from exif import Image
with open(file_name, 'rb') as image_file:
my_image = Image(image_file)
my_image.make = "Python"
my_image.gps_latitude_ref=exif_lat_ref
my_image.gps_latitude=exif_lat
my_image.gps_longitude_ref= exif_lon_ref
my_image.gps_longitude= exif_lon
with open(file_name, 'wb') as new_image_file:
new_image_file.write(my_image.get_file())

How do you convert a PIL `Image` to a Django `File`?

I'm trying to convert an UploadedFile to a PIL Image object to thumbnail it, and then convert the PIL Image object that my thumbnail function returns back into a File object. How can I do this?
The way to do this without having to write back to the filesystem, and then bring the file back into memory via an open call, is to make use of StringIO and Django InMemoryUploadedFile. Here is a quick sample on how you might do this. This assumes that you already have a thumbnailed image named 'thumb':
import StringIO
from django.core.files.uploadedfile import InMemoryUploadedFile
# Create a file-like object to write thumb data (thumb data previously created
# using PIL, and stored in variable 'thumb')
thumb_io = StringIO.StringIO()
thumb.save(thumb_io, format='JPEG')
# Create a new Django file-like object to be used in models as ImageField using
# InMemoryUploadedFile. If you look at the source in Django, a
# SimpleUploadedFile is essentially instantiated similarly to what is shown here
thumb_file = InMemoryUploadedFile(thumb_io, None, 'foo.jpg', 'image/jpeg',
thumb_io.len, None)
# Once you have a Django file-like object, you may assign it to your ImageField
# and save.
...
Let me know if you need more clarification. I have this working in my project right now, uploading to S3 using django-storages. This took me the better part of a day to properly find the solution here.
I've had to do this in a few steps, imagejpeg() in php requires a similar process. Not to say theres no way to keep things in memory, but this method gives you a file reference to both the original image and thumb (usually a good idea in case you have to go back and change your thumb size).
save the file
open it from filesystem with PIL,
save to a temp directory with PIL,
then open as a Django file for this to work.
Model:
class YourModel(Model):
img = models.ImageField(upload_to='photos')
thumb = models.ImageField(upload_to='thumbs')
Usage:
#in upload code
uploaded = request.FILES['photo']
from django.core.files.base import ContentFile
file_content = ContentFile(uploaded.read())
new_file = YourModel()
#1 - get it into the DB and file system so we know the real path
new_file.img.save(str(new_file.id) + '.jpg', file_content)
new_file.save()
from PIL import Image
import os.path
#2, open it from the location django stuck it
thumb = Image.open(new_file.img.path)
thumb.thumbnail(100, 100)
#make tmp filename based on id of the model
filename = str(new_file.id)
#3. save the thumbnail to a temp dir
temp_image = open(os.path.join('/tmp',filename), 'w')
thumb.save(temp_image, 'JPEG')
#4. read the temp file back into a File
from django.core.files import File
thumb_data = open(os.path.join('/tmp',filename), 'r')
thumb_file = File(thumb_data)
new_file.thumb.save(str(new_file.id) + '.jpg', thumb_file)
This is actual working example for python 3.5 and django 1.10
in views.py:
from io import BytesIO
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import InMemoryUploadedFile
def pill(image_io):
im = Image.open(image_io)
ltrb_border = (0, 0, 0, 10)
im_with_border = ImageOps.expand(im, border=ltrb_border, fill='white')
buffer = BytesIO()
im_with_border.save(fp=buffer, format='JPEG')
buff_val = buffer.getvalue()
return ContentFile(buff_val)
def save_img(request)
if request.POST:
new_record = AddNewRecordForm(request.POST, request.FILES)
pillow_image = pill(request.FILES['image'])
image_file = InMemoryUploadedFile(pillow_image, None, 'foo.jpg', 'image/jpeg', pillow_image.tell, None)
request.FILES['image'] = image_file # really need rewrite img in POST for success form validation
new_record.image = request.FILES['image']
new_record.save()
return redirect(...)
Putting together comments and updates for Python 3+
from io import BytesIO
from django.core.files.base import ContentFile
import requests
# Read a file in
r = request.get(image_url)
image = r.content
scr = Image.open(BytesIO(image))
# Perform an image operation like resize:
width, height = scr.size
new_width = 320
new_height = int(new_width * height / width)
img = scr.resize((new_width, new_height))
# Get the Django file object
thumb_io = BytesIO()
img.save(thumb_io, format='JPEG')
photo_smaller = ContentFile(thumb_io.getvalue())
To complete for those who, like me, want to couple it with Django's FileSystemStorage:
(What I do here is upload an image, resize it to 2 dimensions and save both files.
utils.py
def resize_and_save(file):
size = 1024, 1024
thumbnail_size = 300, 300
uploaded_file_url = getURLforFile(file, size, MEDIA_ROOT)
uploaded_thumbnail_url = getURLforFile(file, thumbnail_size, THUMBNAIL_ROOT)
return [uploaded_file_url, uploaded_thumbnail_url]
def getURLforFile(file, size, location):
img = Image.open(file)
img.thumbnail(size, Image.ANTIALIAS)
thumb_io = BytesIO()
img.save(thumb_io, format='JPEG')
thumb_file = InMemoryUploadedFile(thumb_io, None, file.name, 'image/jpeg', thumb_io.tell, None)
fs = FileSystemStorage(location=location)
filename = fs.save(file.name, thumb_file)
return fs.url(filename)
In views.py
if request.FILES:
fl, thumbnail = resize_and_save(request.FILES['avatar'])
#delete old profile picture before saving new one
try:
os.remove(BASE_DIR + user.userprofile.avatarURL)
except Exception as e:
pass
user.userprofile.avatarURL = fl
user.userprofile.thumbnailURL = thumbnail
user.userprofile.save()
Here is an app that can do that: django-smartfields
from django.db import models
from smartfields import fields
from smartfields.dependencies import FileDependency
from smartfields.processors import ImageProcessor
class ImageModel(models.Model):
image = fields.ImageField(dependencies=[
FileDependency(processor=ImageProcessor(
scale={'max_width': 150, 'max_height': 150}))
])
Make sure to pass keep_orphans=True to the field, if you want to keep old files, otherwise they are cleaned up upon replacement.
For those using django-storages/-redux to store the image file on S3, here's the path I took (the example below creates a thumbnail of an existing image):
from PIL import Image
import StringIO
from django.core.files.storage import default_storage
try:
# example 1: use a local file
image = Image.open('my_image.jpg')
# example 2: use a model's ImageField
image = Image.open(my_model_instance.image_field)
image.thumbnail((300, 200))
except IOError:
pass # handle exception
thumb_buffer = StringIO.StringIO()
image.save(thumb_buffer, format=image.format)
s3_thumb = default_storage.open('my_new_300x200_image.jpg', 'w')
s3_thumb.write(thumb_buffer.getvalue())
s3_thumb.close()

Categories

Resources