[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky]
[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Get an image's EXIF data including orientation in Python

[An image in eight different orientations]

Almost all modern cameras (including the one in your phone) add EXIF (exchangeable image file format) metadata to picture files to give you a whole bunch of potentially useful information such as the image's size, camera information, lens settings, location, and orientation. For example, if you hold your camera sideways, the image will include that information.

This example shows how you can get EXIF orientation information from an image.

Orientations

The orientation names are pretty confusing. Each includes two words that indicate where the first row and first column of the image data should be in the corrected picture.

For example, look at the first image labeled TopLeft in the upper left of the picture at the top of the post. The Top part of the label means the first row of pixels should be at the top of the correct image. The Left part means the first column of pixels should be along the left side of the image. In other words, this image is already in its correct orientation.

Now consider the second image labeled TopRight. The Top means the first row of pixels belongs at the top as before. The Right means the first column of pixels (the leftmost column in the image) really belongs on the right side of the image. To get this picture into its correct orientation, you need to flip it horizontally.

For another example, look at the second row's second picture labeled RightTop. The Right means the first row of pixels should be along the right side of the corrected image. The Top means the first column of pixels should be along the top of the corrected image. To transform the image into its correct orientation, you need to rotate it 90 degrees clockwise.

I find this all pretty confusing. The best way I've found to understand the orientations is to look at the examples and then decide what rotations and flips you need to make to properly orient the image.

EXIF Tags

Unfortunately, cameras are annoyingly inconsistent about which EXIF data is included. Sometimes you need to adjust the camera's settings to make it include certain information and sometimes a camera may just not include a particular piece of information. Usually the orientation information should be present, though.

To get EXIF information out of a PIL image, you can call the image's getexif method. The result is a dictionary of tags and values. Unfortunately the tags are numeric codes not names, so you can then use the PIL library's ExifTags.TAGS.get method to convert the tag IDs into names.

Some of the EXIF tag values are also codes. For example, ResolutionUnit is 1 (none), 2 (centimeters), or 3 (inches). If you want those values to be easier to understand, you can convert them from their numeric values into names.

The following code defines values for the Orientation, ResolutionUnit, and YCbCrPositioning tags.

'''Define EXIF values.''' exif_orientations = { 0: 'Unknown', 1: 'TopLeft', # This is "normal" 2: 'TopRight', 3: 'BottomRight', 4: 'BottomLeft', 5: 'LeftTop', 6: 'RightTop', 7: 'RightBottom', 8: 'LeftBottom', } exif_resolution_units = { 1: 'None', 2: 'Inches', 3: 'Centimeters', } exit_y_cb_cr_positioning_values = { 1: 'Centered', 2: 'Co-Sited', }

This code just defines three lookup dictionaries that map numeric values to named versions. For example, the EXIF orientation code 7 means RightBottom. Here's my get_exif_data function.

def get_exif_data(image): '''Return a dictionary of EXIF tags and values.''' data = image.getexif() if data is None: return None # Create the EXIF data dictionary. exif_data = { ExifTags.TAGS.get(tag_id, f'Unknown_{tag_id}'): value for tag_id, value in data.items() } # Give the orientation and resolution units their names. if 'Orientation' in exif_data: exif_data['Orientation'] = \ exif_orientations[exif_data['Orientation']] if 'ResolutionUnit' in exif_data: exif_data['ResolutionUnit'] = \ exif_resolution_units[exif_data['ResolutionUnit']] if 'YCbCrPositioning' in exif_data: exif_data['YCbCrPositioning'] = \ exit_y_cb_cr_positioning_values[exif_data['YCbCrPositioning']] return exif_data

This code first calls getexif to get the image's EXIF data. Here's an example of what that data looks like.

{ 296: 2, 282: 72.0, 256: 4128, 257: 3096, 34665: 226, 271: 'samsung', 272: 'SM-A205U', 305: 'A205USQSDBWC2', 274: 1, 306: '2025:07:31 13:44:04', 531: 1, 283: 72.0 }

Next, the code uses a dictionary comprehension to convert the numeric tag IDs to tag names. For each ID and value in the data dictionary, it uses ExifTags.TAGS.get to get the ID's name and adds that plus its value to the new exif_data dictionary.

The function then checks for the three specific tags Orientation, ResolutionUnit, and YCbCrPositioning. If it finds those values in the exif_data dictionary, it replaces their numeric codes with the strings defined in the earlier lookup dictionaries. Here's an example of what this result looks like.

{ 'ImageWidth': 4128, 'ImageLength': 3096, 'ResolutionUnit': 'Inches', 'ExifOffset': 226, 'Make': 'samsung', 'Model': 'SM-A205U', 'Software': 'A205USQSDBWC2', 'Orientation': 'TopLeft', 'DateTime': '2025:07:31 13:44:04', 'YCbCrPositioning': 'Centered', 'XResolution': 72.0, 'YResolution': 72.0 }

ExifDemoApp

Here's the example's main app class.

class ExifDemoApp: def __init__(self): # Make the main interface. self.window = tk.Tk() self.window.title('exif_show_orientations') self.window.protocol('WM_DELETE_WINDOW', self.kill_callback) # Canvases. self.tk_images = [] for i in range(1, 9): if i == 1 or i == 5: row_frame = tk.Frame(self.window) row_frame.pack(side=tk.TOP) filename = f'loki{i}.jpg' with Image.open(filename) as image: frame = tk.Frame(row_frame) frame.pack(side=tk.LEFT, padx=5, pady=5) canvas = tk.Canvas(frame, width=image.width, height=image.height) canvas.pack(side=tk.TOP) tk_image = ImageTk.PhotoImage(image) self.tk_images.append(tk_image) canvas.create_image(0, 0, image=tk_image, anchor=tk.NW) exif_data = get_exif_data(image) label = tk.Label(frame, text=exif_data['Orientation']) print(exif_data['Orientation']) label.pack(side=tk.TOP) # Display the EXIF information for the original image. with Image.open('loki.jpg') as image: exif_data = get_exif_data(image) print(exif_data) # Display the window. self.window.focus_force() self.window.mainloop() def kill_callback(self): self.window.destroy()

The code initializes tkinter and then enters a loop to display eight images. Those images are copies of an original that I rotated to demonstrate the different EXIF orientations. (I'll describe the program that did that in my next post.)

Inside the loop, the code loads the next image (for example, loki3.jpg) and displays it in a Canvas widget. It calls get_exif_data to get the image's EXIF data and displays the Orientation value in a Label.

The code finishes by displaying all of the EXIF data for the original image loki.jpg so you can see all of the details.

Conclusion

To get an image's EXIF data, you can simply load it and then call the get_exif_data function. Then you can learn things about the image like its orientation, resolution, and the date and time it was taken.

In my next post, I'll show you how you can change an image's orientation and update its EXIF information. In the post after that, I'll explain how to perform the most useful EXIF operation for most programs: transforming an image to give it its normal orientation. PIL does not do this for you by default when you load a rotated image, but it will do it pretty easily.

Download the example to experiment with it and to see additional details.

© 2025 Rocky Mountain Computer Consulting, Inc. All rights reserved.