Photographer's Data
Building a portrait of a photographer through metadata.
category
date
technologies used
Python, Chart.js
course
This is a collaborative project between myself and Soonho Kwon, a photographer and design major from Carnegie Mellon University. The theme of the assignment was portraits. Due to the experimental nature of the course, and the photography interests we both have, we designed my interpretation of the assignment to be a data visualization, a "portrait of a photographer," using our photograph metadata.
All of the following data portraits were created by first converting our photography to JPGs (Soonho had over 90k RAW images which took over a week to convert). We then ran a custom software I wrote which analyzed each image's metadata for a multitude of datapoints, such as aperture, focal length, ISO rating, etc.
You can view the original project documentation on the class website, and Soonho's interpretation of this portrait assignment here.
Focal Length
This chart shows that I tended to use my 50mm prime and zoom lenses the most. I often used the 50mm for weddings, and did 4 years of sports photography with a telescopic lens. Soonho's favorite lens is in the 35mm range.
The only outlier in this data would be a point-and-shoot that I used to use, which measured in a 9mm but looked more like a standard 18mm photo.
Aperture
Aperture is, among other uses, great for controlling how out of focus the foreground and background are compared to the subject. The smaller the aperture, the thinner the plane of focus is. Click here to learn more about aperture.
I started a majority my photography with beginner's set of lenses, which has a lower quality, higher aperture. Later on I had portrait lenses at 1.8mm and such. However at the time of this data, Soonho had much nicer, lower-aperture lenses in his arsenal.
Ambience (Hızal)
This chart shows that I tended to use my 50mm prime and zoom lenses the most. I often used the 50mm for weddings, and did 4 years of sports photography with a telescopic lens. Soonho's favorite lens is in the 35mm range.
The only outlier in this data would be a point-and-shoot that I used to use, which measured in a 9mm but looked more like a standard 18mm photo.
Ambience (Soonho)
This chart shows that I tended to use my 50mm prime and zoom lenses the most. I often used the 50mm for weddings, and did 4 years of sports photography with a telescopic lens. Soonho's favorite lens is in the 35mm range.
The only outlier in this data would be a point-and-shoot that I used to use, which measured in a 9mm but looked more like a standard 18mm photo.
# Copyright Hizal Celik, 2017 # Cannot use or modify this code without Hizal's permission from collections import OrderedDict from os.path import exists, join from datetime import datetime from os import makedirs, walk import logging, traceback import exifread import json debug = False default_folder = "imgs" extentions = ('.jpg','.jpeg','.png','.tif','.tiff','.gif') files = [] metadata = {} days = {} data = [] def load(folder = None): global files if not folder: folder = default_folder for r, dir, f in walk(folder): for file in f: if join(r,file).lower().endswith(extentions): files.append(join(r, file)) perc = 0 count = 0 for file in files: if debug: print file image = None while not image: try: image = open(file, 'rb') except: print "ERROR: File not found: " + file raw_input("Press enter to continue when reconnected "); tags = exifread.process_file(image, details=False) try: # timestamp ts = datetime.strptime(str(tags['EXIF DateTimeOriginal']), '%Y:%m:%d %H:%M:%S') # aperture fstop = str(tags['EXIF FNumber']).split('/') if len(fstop) > 1: f = float(fstop[0])/float(fstop[1]) else: f = float(fstop[0]) # shutter speed speed = str(tags['EXIF ExposureTime']).split('/') if len(speed) > 1: ss = float(speed[0])/float(speed[1]) else: ss = float(speed[0]) # iso iso = int(str(tags['EXIF ISOSpeedRatings'])) # focal length mm = str(tags['EXIF FocalLength']).split('/') if len(mm) > 1: fl = float(mm[0])/float(mm[1]) else: fl = float(mm[0]) if debug: print "\tTimestamp: " + str(ts) print "\tAperture: f" + str(f) print "\tShutter: " + str(tags['EXIF ExposureTime']) + " (" + str(ss) + ")" print "\tISO: " + str(iso) print "\tFocal length: " + str(fl) + "mm" metadata[file] = {'f':f, 'ss':ss, 'iso':iso, 'fl':fl, 'ts':ts} except Exception as e: if debug: print file logging.error(traceback.format_exc()) pass # print progress if count == 0: print " 0% ", count += 1 new_perc = int(round(((count * 1.0) / len(files)) * 100)) if new_perc > perc and new_perc%10==0: print "\n" + str(new_perc) + "% ", elif new_perc > perc and new_perc%1==0: print ".", perc = new_perc print "" print str(len(files)) + " files found.\n" def write(): filename = "data.js" if debug: filename = "debug.txt" print "Writing " + filename + "... ", with open(filename, 'w') as f: f.write("window.chartdata = [\n") for day in data: f.write("[") for i in xrange(len(day)): f.write(str(day[i])) if i != len(day)-1: f.write(',') else: f.write('],\n') f.write("];") f.close() print "\t\tdone." def map(value, srcMin, srcMax, tgtMin, tgtMax): return tgtMin + (tgtMax - tgtMin) * ((float(value) - srcMin) / (srcMax - srcMin)) def constrain(value, min, max): if value < min: return min if value > max: return max return value def getRating(meta): iso = constrain(map(meta['iso'], 100, 6400, 0, 100), 0, 100) f = constrain(map(meta['f'], 22, 1.4, 0, 100), 0, 100) ss = constrain(map(meta['ss'], float(1.0/8000), 1, 0, 100), 0, 100) if debug: print "\tISO: " + str(meta['iso']) + "/" + str(iso) print "\tF: " + str(meta['f']) + "/" + str(f) print "\tSS: " + str(meta['ss']) + "/" + str(ss) return int(iso + f + ss) def analyze(index = None): global metadata, data, days count = 0 perc = 0 for img in metadata: meta = metadata[img] rating = getRating(meta) if debug: print "" print img print rating if rating >= 250: print img if str(meta['ts'].date()) in days: days[str(meta['ts'].date())].append(rating) else: days[str(meta['ts'].date())] = [rating] # print progress count += 1 new_perc = int(round(((count * 1.0) / len(metadata)) * 100)) if new_perc > perc and new_perc%10==0: print str(new_perc) + "% " perc = new_perc # save as ordered days ordered = OrderedDict(sorted(days.items(), key=lambda t: t[0])) for day in ordered: data.append(ordered[day]) if debug: print days print ordered print data print str(len(metadata)) + " files processed." def test(): pass while True: print "0: Exit (without saving)" print "1: Auto" print "2: Load" print "3: Analyze" print "4: Save data" choice = (int)(raw_input("> ")) if choice == 0: break if choice == 1: load() analyze() write() elif choice == 2: folder = raw_input("Folder section: ") load(folder) elif choice == 3: analyze() elif choice == 4: write() elif choice == 626: test() else: print "" print ""
Analysis
Python script to analyze ambience. Aperture and focal length analysis was a modified version of this file.