String formatting with dictionary-like objects

In the past, whenever I needed to format a datetime.date string, I would do something like this:

date = datetime.date.today()

#Chinese year, month and day.
u'{}年 {}月 {}日'.format(*str(date).split('-'))

#Output: u'2014年 05月 02日'

It worked but I always had a nagging feeling that it’s not clean or pretty enough. So I finally took a moment to rethink it and remembered that you can also access attributes of an object when formatting:

u'{0.year}年 {0.month}月 {0.day}日'.format(date)

#Output: u'2014年 5月 2日'

(Zero is the index of the argument passed to the format method.)

In this case, the two ways are about the same length but the second one is more readable. These are not exactly the same though. Using split in the first method will give you two-digit strings for month and day (i.e. “05”), whereas accessing the attribute only retrieves a number. To get the two-digit output from attributes, add a bit more formatting code:

u'{0.year}年 {0.month:0>2}月 {0.day:0>2}日'.format(date)

#Output: u'2014年 05月 02日'

“0>2” orders up a 2 character long string with the main value served on the right with a side of zeros (if there’s room).

Chinese in matplotlib

I recently needed to display Chinese characters in a plot legend. I’ve only ever changed font size in the past so this was new to me.

Before adding a legend, you need to create a font properties object.

from matplotlib import font_manager
...
fontP = font_manager.FontProperties()
fontP.set_family('SimHei')
fontP.set_size(14)
plt.legend(prop=fontP)
plt.show()

Working with PIL and EXIF

PIL has a way to look at a simple set of EXIF tags but cannot read the myriad of file types out there. I needed something to get everything I could out of a NIKON *.jpg file.

The solution I found was running exiftool.exe with the subprocess module.

Exiftool.exe supports many file types. Download at http://www.sno.phy.queensu.ca/~phil/exiftool/

Loading EXIF from a file into Python
To load EXIF data from an image file, use the check_output method in the subprocess module. This method will return the exiftool.exe output as a string. Then splitlines and add each line/tag to a dictionary. I prefer using OrderedDict from the collections module, but I use a regular dictionary in the code below.

import subprocess

# filename is the path to your target image file.
exifdata = subprocess.check_output(['exiftool.exe',
                                    filename], shell=True)
exifdata = exifdata.splitlines()
exif = dict()
for i, each in enumerate(exifdata):
     # tags and values are separated by a colon
     tag,val = each.split(': ', 1) # '1' only allows one split
     exif[tag.strip()] = val.strip()

You can review all the available tags from a file with exif.keys().

Getting a preview image or thumbnail from EXIF into Python
You can get a preview image from a NEF or JPG file also. Not all files will have a preview so you’ll need to handle the error. You can also retrieve the full-size jpeg that is embedded in the file.

import subprocess
import StringIO
from PIL import Image

def get_preview( filename ):
    try:
        im_binary = subprocess.check_output(['exiftool.exe',
                                             filename,
                                             '-previewimage',
                                             '-b'], shell=True)
        image = Image.open( StringIO.StringIO(im_binary) )
        return image
    except:
        return None

def get_thumbnail( filename ):
    try:
        im_binary = subprocess.check_output(['exiftool.exe',
                                              filename,
                                              '-thumbnailimage',
                                              '-b'], shell=True)
        image = Image.open( StringIO.StringIO(im_binary) )
        return image
    except:
        return None

def get_jpeg( filename ):
    try:
        im_binary = subprocess.check_output(['exiftool.exe',
                                              filename,
                                              '-JpgFromRaw',
                                              '-b'], shell=True)
        image = Image.open( StringIO.StringIO(im_binary) )
        return image
    except:
        return None

Preserving the EXIF after PIL manipulation and save
PIL does not automatically save EXIF and PIL’s exif methods do not grab everything, particularly NIKON MakerNote data.
My solution is to save a manipulated image separate from original (do not overwrite original JPG) and copy the EXIF from old to new with exiftool.

# After editing/manipulating image with PIL
savename = filename[:-4]+'_new.jpg'
image.save( savename, quality=95 ) # Default quality is 75
subprocess.call(['exiftool.exe',
                 savename,
                 '-tagsFromFile',
                 filename], shell=True)

After copying the EXIF data, delete the old image or change its name to be a backup. Use os.rename or os.remove.

Cython workflow

1)
cython_file.pyx
Create a *.pyx file containing Python or Cython code. The *.pyx file contains the code that will be compiled to C.

2)
cython_setup.py
The setup *.py file contains the code that runs the compile process.

The setup *.py file:

import numpy
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

pyx_filename = "cython_file.pyx"
new_module_name = "cython_module"

ext_modules = [Extension(new_module_name,
                         [pyx_filename],
                         include_dirs=[numpy.get_include()]
                        )]
setup(
  cmdclass = {'build_ext': build_ext},
  ext_modules = ext_modules
)

Lines 1 and 11 are necessary if you are using NumPy in the pyx file, otherwise they can be commented out.

3)
C:\...>cython_setup.py build_ext --inplace
In the command prompt from the setup file directory execute the code above. This will create a cython_file.c, a build directory, and a cython_module.pyd file.

3b)
error: Unable to find vcvarsall.bat
If you get this error. If may be because you have Visual C++ 2010 installed instead of the 2008 that it looks for. In the command prompt enter: SET VS90COMNTOOLS=%VS100COMNTOOLS%
This will redirect the search to the 2010 version.

3c)
The build directory and *.c file are not necessary and can be deleted. The *.pyd file is what you keep and import into projects.

4)
import cython_module
In a new project the C code module can be imported like any other module. The methods can be individually imported or accessed by cython_module.method()

PIL Image to CV2 image

Bands need to be reversed for CV2. PIL is RGB. CV2 is BGR.
Also, if you are getting errors with CV2, it may be because it doesn’t like transformed ‘views’ of arrays. Use the copy method.

# PIL RGB 'im' to CV2 BGR 'imcv'
imcv = np.asarray(im)[:,:,::-1].copy()
# Or
imcv = cv2.cvtColor(np.asarray(im), cv2.COLOR_RGB2BGR)
# To gray image
imcv = np.asarray(im.convert('L'))
# Or
imcv = cv2.cvtColor(np.asarray(im), cv2.COLOR_RGB2GRAY)
view raw gistfile1.py hosted with ❤ by GitHub

PIL and CV2 use the same percentages of R,G, and B to make a greyscale image but the results may have a difference of 1 in many places due to rounding off. PIL uses integer division and CV2 uses floating point percentages.
PIL: GRAY = R * 299/1000 + G * 587/1000 + B * 114/1000
CV2: GRAY = R * 0.299 + G * 0.587 + B * 0.114

If the PIL image has four bands, only reverse the first three.

cv2.correctMatches

This function requires two 1xN array of points, how does that work? Maybe 1xNx2 would be more clear. You need to convert the point array to the shape (1,N,2). If your point array looks like mine:

a3xN = \
array([[35, 59, 32, 92, 71, 12,  4,  8, 28, 19],
       [ 9,  4, 36, 80, 85, 47, 60, 83, 38, 68],
       [ 1,  1,  1,  1,  1,  1,  1,  1,  1,  1]])

You need to change it to look like this:

a1xNx2 = \
array([[[35,  9],
        [59,  4],
        [32, 36],
        [92, 80],
        [71, 85],
        [12, 47],
        [ 4, 60],
        [ 8, 83],
        [28, 38],
        [19, 68]]])

Try this:

a1xNx2 = r_[[a3xN[:2].T]]
#or
a1xNx2 = array([a3xN[:2].T])

The output is in the same form. Use output[0].T to convert to a 2xN array. If you don’t need the original a3xN array, then copy the output 2xN into the first two rows of a3xN (a3xN[:2] = output[0].T). Or convert to homogeneous coordinates like this:

outa3xN = append(outa[0].T,ones((1,len(outa[0]))),0)
#or
outa3xN = vstack((outa[0].T,ones(len(outa[0]))))

Complete example:

# A fundamental or essential matrix
F3x3 = array([[ 0.0345, -0.2073, -0.0673],
              [-0.1403, -0.0003,  0.6889],
              [ 0.0604, -0.6725,  0.0349]])
# Homogeneous 2D point arrays
a3xN = array([[35, 59, 32, 92, 71, 12,  4,  8],
              [ 9,  4, 36, 80, 85, 47, 60, 83],
              [ 1,  1,  1,  1,  1,  1,  1,  1]], float)
# Pass an integer array and get an integer array back
b3xN = array([[95, 24, 86, 26,  4, 85, 68, 66],
              [35, 44, 91, 10, 14, 41, 93, 34],
              [ 1,  1,  1,  1,  1,  1,  1,  1]], int)
# The cv2 method
outa, outb = cv2.correctMatches( F3x3, r_[[a3xN[:2].T]], r_[[b3xN[:2].T]] )
# Two ways of making the output homogeneous
outa3xN = append(outa[0].T,ones((1,len(outa[0]))),0)
outb3xN = vstack((outb[0].T,ones(len(outb[0]),int)))
print outa3xN
print outb3xN
[[ 34.37  52.71  12.63  94.56  70.1    5.   -24.92 -14.35]
 [ -1.31 -15.19  -3.31  77.    85.69   0.48  35.01  77.4 ]
 [  1.     1.     1.     1.     1.     1.     1.     1.  ]]
[[ 96  36  88   6  -8  85  49   4]
 [ 33  28  89 -10   7  41 103  44]
 [  1   1   1   1   1   1   1   1]]

Note that if you pass an integer array, then the function returns an int array, so you may need to convert to float first.

Epipole and Epipolar Lines

Epipole from essential matrix:
e1c2 is the position of camera 2 in image 1 coordinates. (Plot it in image 1)

def epipoleSVD(M):
    V = cv2.SVDecomp(M)[2]
    return V[-1]/V[-1,-1]
e1c2 = epipoleSVD(E)
e2c1 = epipoleSVD(E.T)

Note: This cv2 method returns three values, [S, U, V], but only V is needed to find the epipole. The epipole is in form [x,y,1].

Epipolar lines from essential matrix:
elines1 (a 3xN array) are drawn on image 1 and will converge at e1c2 (from above). x1 and x2 are normalized 3xN arrays of corresponding points in image 1 and image 2. (A line vector contains the values [a,b,c] in ax+by+c=0)

elines2 = dot(E, x1)
elines1 = dot(E.T, x2) #or dot(x2.T, E).T

To plot these lines, try the following:

def lines2points(lines, epipole):
    xmax = ones(lines.shape[1])*epipole[0]
    a,c = lines[0]/lines[1], lines[2]/lines[1]
    x = array([-xmax,xmax])
    y = array([r_[-a*Lx[0] - c], r_[-a*Lx[1] - c]])
    return x, y
Lx,Ly = lines2points( dot(E, x1), epipoleSVD(E.T) )
plt.plot(Lx, Ly, 'r')

Note: This will overlay epipolar lines derived from image 1 on corresponding points in image 2 that are between +/- epipole x-coordinate.

Essential Matrix

From point correspondences:
x1 and x2 are 3xN array of normalized points from images 1 and 2.

E = cv2.findFundamentalMat(x1[:2].T, x2[:2].T)[0]

Note: This cv2 method requires points to be in Nx2 format.
The ending [0] returns the matrix. The 2nd returned value [1] is a mask array of inliers (=1) and outliers (=0)

From transformation/projection matrix:
P is a 3×4 (or 4×4) camera projection matrix for image 2.
(World origin at image 1 camera)

R,t = cv2.decomposeProjectionMatrix(P[:3])[1:3]
skewt = roll(roll(diag(t.flat[:3]/t[3]), 1, 1), -1, 0)
skewt -= skewt.T
E = dot(R, skewt)

Note: This method requires a 3×4 array and returns seven items, [1] and [2] are the rotation matrix and translation vector. Also, the t-vec has length 4 and must be normalized by the last value (t[:3]/t[3])
roll, diag, .flat, dot are all NumPy functions.

PIL projective transform

im.transform(size, PERSPECTIVE, data) image ⇒ image

size = output image size
PERSPECTIVE = Image.PERSPECTIVE (int)
data = 8-tuple (a, b, c, d, e, f, g, h) of coefficients

Questions you may have:
1) How do you know the size of the image after the transform to set the size before doing the transform?
2) How is the transformed image placed within that pre-sized output window?
3) Where does the data 8-tuple come from?
4) How can I use the 8-tuple coefficients to project a point back and forth between original and transformed image.

Answers:
1&2) It depends on how much of the image you want to include. The destination points determine the placement within the new image. You can first calculate the coefficients (forward form, see Ans. 4), then calculate where the image corners will end up, then shift the destination points (or corners) to the desired placement and adjust the image size. Finally, calculate the coefficients a second time (backward form) for the Image.transform function.

3) The coefficients are calculated with some matrix math. You can find an explanation elsewhere. I’ll just show some code:

def get_transform_data(pts8, backward=True ):
    '''This method returns a perspective transform 8-tuple (a,b,c,d,e,f,g,h).

    Use to transform an image:
    X = (a x + b y + c)/(g x + h y + 1)
    Y = (d x + e y + f)/(g x + h y + 1)
    
    Image.transform: Use 4 source coordinates, followed by 4 corresponding 
        destination coordinates. Use backward=True (the default)
        
    To calculate the destination coordinate of a single pixel, either reverse
        the pts (4 dest, followed by 4 source, backward=True) or use the same
        pts but set backward to False.
    
    @arg pts8: four source and four corresponding destination coordinates
    @kwarg backward: True to return coefficients for calculating an originating
        position. False to return coefficients for calculating a destination
        coordinate. (Image.transform calculates originating position.)
    '''
    assert len(pts8) == 8, 'Requires a tuple of eight coordinate tuples (x,y)'
    
    b0,b1,b2,b3,a0,a1,a2,a3 = pts8 if backward else pts8[::-1]
    
    # CALCULATE THE COEFFICIENTS
    A = array([[a0[0], a0[1], 1,     0,     0, 0, -a0[0]*b0[0], -a0[1]*b0[0]],
               [    0,     0, 0, a0[0], a0[1], 1, -a0[0]*b0[1], -a0[1]*b0[1]],
               [a1[0], a1[1], 1,     0,     0, 0, -a1[0]*b1[0], -a1[1]*b1[0]],
               [    0,     0, 0, a1[0], a1[1], 1, -a1[0]*b1[1], -a1[1]*b1[1]],
               [a2[0], a2[1], 1,     0,     0, 0, -a2[0]*b2[0], -a2[1]*b2[0]],
               [    0,     0, 0, a2[0], a2[1], 1, -a2[0]*b2[1], -a2[1]*b2[1]],
               [a3[0], a3[1], 1,     0,     0, 0, -a3[0]*b3[0], -a3[1]*b3[0]],
               [    0,     0, 0, a3[0], a3[1], 1, -a3[0]*b3[1], -a3[1]*b3[1]]] )
    B = array([b0[0], b0[1], b1[0], b1[1], b2[0], b2[1], b3[0], b3[1]])
    
    return linalg.solve(A,B)

If your image has black borders that you want to make transparent, this code could help:

    transparency = 150
    I = asarray(transimage)  # (y, x, band)
    maskmat = (I[:,:,0] | I[:,:,1] | I[:,:,2] == 0)
    mask = ones(size, int).transpose() * transparency
    mask[maskmat] = 0
    mask = Image.fromarray(uint8(mask))
    transimage.putalpha(mask)

This code applies an alpha mask to a transformed image (transimage). Transparency is how transparent the image part will be and the maskmat region is for all the black (non-image) border area. This area of the mask is set to completely transparent (mask[maskmat] = 0). This works with Tkinter where an alpha of zero is transparent and 255 is opaque.

4) When I first saw the equation for calculating the point transform I believed I must have read it wrong. “Why would the equations move a point from the transformed image back to the original? I must be reading this wrong,” I thought. But the data set passed to the transform method must be working in the following way: It first creates a blank image of the specified size and then uses the ‘backwards’ equation to calculate from which pixel in the original image to copy color values into the destination location. Well, it seems counter-intuitive to me so I still call it ‘backwards’ (where ‘backward’ means mapping from destination to the origin, and ‘forward’ is origin to destination). Here is the code to transform a point:

def transform_pt(pt , coeffs ):
    T = coeffs
    x = (T[0]*pt[0] + T[1]*pt[1] + T[2])/(T[6]*pt[0] + T[7]*pt[1] + 1)
    y = (T[3]*pt[0] + T[4]*pt[1] + T[5])/(T[6]*pt[0] + T[7]*pt[1] + 1)
    return x,y

Two ways to use this. Pass a destination coord with the default (backward) transformation coefficients, or pass an origin coord with the forward (backward=False) coefficients.

C enums revisited

Just found an easy way to make enums.

(LADYBUG_DISABLE,
   LADYBUG_EDGE_SENSING,
   LADYBUG_NEAREST_NEIGHBOR,
   LADYBUG_NEAREST_NEIGHBOR_FAST,
   LADYBUG_RIGOROUS,
   LADYBUG_DOWNSAMPLE4,
   LADYBUG_MONO,
   LADYBUG_HQLINEAR) = xrange(8)

This is a simple way to make enums. Overriden values need to be added separately.
Unless a method to retrieve the enum name from the integer is needed, in that case you make another list (or dict for unordered values) to retrieve the enum word. The namedtuple is the best way if you don’t want to put quotes around each word in a long list.
For a ctypes version, you can use map()

(...) = map(c_int, xrange(8))

I guess there really isn’t a need to go the reverse direction unless you want to read the value for debugging. In my novice understanding when first learning ctypes I thought it would be necessary to have this option. Here how to make a list and then enumerate from that list:

LadybugColorProcessingMethod = '''
   LADYBUG_DISABLE
   LADYBUG_EDGE_SENSING
   LADYBUG_NEAREST_NEIGHBOR
   LADYBUG_NEAREST_NEIGHBOR_FAST
   LADYBUG_RIGOROUS
   LADYBUG_DOWNSAMPLE4
   LADYBUG_MONO
   LADYBUG_HQLINEAR
   '''.split()
for n, v in zip( LadybugColorProcessingMethod, range(8) ):
    exec( n + ' = ' + str(v) )

A namedtuple implementation might be preferred if the same name might be used for different values in different enums.