@ -1,6 +1,7 @@
import re
import os
import logging
import math
from itertools import groupby
log = logging . getLogger ( __name__ )
@ -12,11 +13,11 @@ LENS_META_DEFAULT_RE = re.compile(
(
# anything at the start
" .*? "
# maybe min focal length and hy h pen, surely max focal length e.g.: 24-70mm
# maybe min focal length and hy ph en, surely max focal length e.g.: 24-70mm
" (?:(?P<focal_length_min>[0-9]+)-)?(?P<focal_length_max>[0-9]+)mm "
# anything in between
# anything in - between
" .*? "
# maybe short focal length max aperture and hy h pen, surely at least single max aperture e.g.: f/4.5-5.6
# maybe short focal length max aperture and hy ph en, surely at least single max aperture e.g.: f/4.5-5.6
# short and tele indicate apertures at the short (focal_length_min) and tele (focal_length_max) position of the lens
" (?:(?:f \ /)|T)(?:(?P<aperture_max_short>[0-9]+(?: \ .[0-9]+)?)-)?(?P<aperture_max_tele>[0-9]+(?: \ .[0-9])?) "
# check if there is a teleconverter pattern e.g. + 1.4x
@ -25,9 +26,58 @@ LENS_META_DEFAULT_RE = re.compile(
)
def aperture_to_raw_exif ( aperture ) :
# see https://github.com/exiftool/exiftool/blob/master/lib/Image/ExifTool/Canon.pm#L9678
""" Transform aperture value to Canon maker note style hex format. """
# for apertures < 1 the below is negative
num = math . log ( aperture ) * 2 / math . log ( 2 )
# temporarily make the number positive
if num < 0 :
num = - num
sign = - 1
else :
sign = 1
val = int ( num )
frac = num - val
if abs ( frac - 0.33 ) < 0.05 :
frac = 0x0C
elif abs ( frac - 0.67 ) < 0.05 :
frac = 0x14
else :
frac = int ( frac * 0x20 + 0.5 )
return sign * ( val * 0x20 + frac )
def raw_exif_to_aperture ( raw ) :
""" The inverse operation of aperture_to_raw_exif """
val = raw
if val < 0 :
val = - val
sign = - 1
else :
sign = 1
frac = val & 0x1F
val - = frac
# Convert 1/3 and 2/3 codes
if frac == 0x0C :
frac = 0x20 / 3
elif frac == 0x14 :
frac = 0x40 / 3
ev = sign * ( val + frac ) / 0x20
return math . exp ( ev * math . log ( 2 ) / 2 )
def parse_lens_entry ( text , pattern = LENS_ENTRY_DEFAULT_RE ) :
""" get the ID, and description from a lens entry field
Expexted input format :
"""
get the ID , and description from a lens entry field
Expected input format :
{ 748 , " Canon EF 100-400mm f/4.5-5.6L IS II USM + 1.4x " }
We return a dict of :
lens_id = 748
@ -40,6 +90,7 @@ def parse_lens_entry(text, pattern=LENS_ENTRY_DEFAULT_RE):
def extract_meta ( text , pattern = LENS_META_DEFAULT_RE ) :
"""
Extract metadata from lens description .
Input expected in the form of e . g . " Canon EF 100-400mm f/4.5-5.6L IS II USM + 1.4x "
We return a dict of :
focal_length_min = 100
@ -64,16 +115,27 @@ def extract_meta(text, pattern=LENS_META_DEFAULT_RE):
return ret
# FIXME explain somwhere that lens_is_match(l1,l2) does not imply lens_is_match(l2,l1)
# becuse we don't have short and tele aperture values in exif
def lens_is_match ( l1 , l2 ) :
"""
Test if lens l2 is compatible with lens l1 ,
assuming we write l1 ' s metadata and apeture_max_short into exif
Test if lens l2 is compatible with lens l1
This assumes we write l1 ' s metadata and pick its ' aperture_max_short ' value
as the maximum aperture value to write into exif .
Normally the canon maker note holds the max aperture of the lens at the focal length
the picture was taken at . Thus for a f / 4 - 6.3 lens , this value could be anywhere in that range .
"""
return (
all ( [ l1 [ k ] == l2 [ k ] for k in [ " tc " , " focal_length_min " , " focal_length_max " ] ] )
and l2 [ " aperture_max_short " ] < = l1 [ " aperture_max_short " ] < = l2 [ " aperture_max_tele " ]
# the problem is that the round trip transformation isn't exact
# so we need to account for this here as well to not define a target
# which isn't achievable for exiv2
reconstructed_aperture = raw_exif_to_aperture ( aperture_to_raw_exif ( l1 [ " aperture_max_short " ] * l1 [ " tc " ] ) )
return all (
[
l1 [ " focal_length_min " ] * l1 [ " tc " ] == l2 [ " focal_length_min " ] * l2 [ " tc " ] ,
l1 [ " focal_length_max " ] * l1 [ " tc " ] == l2 [ " focal_length_max " ] * l2 [ " tc " ] ,
( l2 [ " aperture_max_short " ] * l2 [ " tc " ] ) - 0.1
< = reconstructed_aperture
< = ( l2 [ " aperture_max_tele " ] * l2 [ " tc " ] ) + 0.1 ,
]
)
@ -100,7 +162,7 @@ def make_test_cases(lenses):
def extract_lenses_from_cpp ( filename , start_pattern ) :
"""
Extract lens information from the lens descri t pions array in a maker note cpp file
Extract lens information from the lens descri pt ions array in a maker note cpp file
filename : path to cpp file
start_pattern : start_pattern == line . strip ( ) should return True for
the starting line of the array containing the lenses .
@ -134,7 +196,7 @@ def extract_lenses_from_cpp(filename, start_pattern):
meta = extract_meta ( lens_entry [ 1 ] )
if not meta :
log . error ( f " Failure extrac ing metadata from lens description: { lens_entry [ 0 ] } : { lens_entry [ 1 ] } . " )
log . error ( f " Failure extrac t ing metadata from lens description: { lens_entry [ 0 ] } : { lens_entry [ 1 ] } . " )
continue
lenses . append ( { " id " : lens_entry [ 0 ] , " desc " : lens_entry [ 1 ] , " meta " : meta } )