2

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

Maarten Fabré
  • 6,938
  • 1
  • 17
  • 36

2 Answers2

0

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
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
0

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))
Maarten Fabré
  • 6,938
  • 1
  • 17
  • 36