FocalApp v0.2

I changed my FocalApp java application a little bit. First, there was sometimes a memory exception as the old version did a “new File()” in each loop iteration and apparently the garbage collector couldn’t catch up. I moved the file-open operation into an own class method and now the GC seems to get its job done. Second, I added some more command line parameters for specifying the camera model, the file extensions and the file name pattern – which all filter the resulting image file list used for the focal length count. Hence one can now say “Only count the images whose EXIF data’s camera model is a Canon 300D, but only the CRW files and only the files starting with CRW_50”. In addition I added a little help screen:

Build:
javac -Xlint:unchecked -classpath .;metadata-extractor-2.4.0-beta-1.jar FocalApp.java
jar cfm FocalApp.jar Manifest.txt *.class

Usage:
java -jar FocalApp.jar [Options]

Description:
For all images in the given directory and its subdirectories read the
lenses' focal length from the exif data and print a summary of how
often the particular focal lengths are used.

Currently this program works with the following image types:
* JPEG
* CRW - Canon Raw
* CR2 - Canon Raw v2
* NEF - Nikon Raw
* ARW - Sony Raw
* TIF/TIFF

Options:
-imagepath          path for the images to work on                          (required)
-camera             string with the name of the camera, the program will    (optional)
                    only count the images containing that camera name
-fileext            only count images with that file extension(s)           (optional)
-filename           only count images matching this regular expression      (optional)

Examples:
java -jar FocalApp.jar -imagedir "D:testimages"
java -jar FocalApp.jar -imagedir "D:testimages" -camera "Canon EOS 30D"
-fileext "jpg,crw" -filename "^CRW(.*)"

Feel free to get the new version here. You can find the original blog post to the FocalApp here.

Advertisement

Most widely used camera focus length

Recently a collegue of mine argued that he more often needs a small camera focus length (wide-angled) than a zoom focus length (tele). To proof – or confute – this I wrote a small java program which extracts the exif data from a given directory of images and counts the focal length parameters used when taking the pictures. I used the beta version of Drew Noakes’ metadata extractor. As I haven’t coded in java for some time, the code might be a little bit dirty.

import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.exif.ExifReader;
import com.drew.metadata.iptc.IptcReader;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGDecodeParam;
import com.sun.image.codec.jpeg.JPEGImageDecoder;
import com.drew.metadata.*;
import com.drew.metadata.exif.ExifDirectory;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.FilenameFilter;
import java.util.*;
import java.util.regex.*;

public class FocalApp {
    public FocalApp(String dirname) {
 // list of files
        File folder = new File(dirname);
        List filelist = new ArrayList();
        this.getFiles(folder, filelist);

 // key - value pairs: focallength - count
        SortedMap focalmap = new TreeMap();

 // for all found files
        for (Iterator it=filelist.iterator(); it.hasNext(); ) {
            String filename = it.next().toString();
 //System.out.println(" --- working on file: "+filename);

            File imageFile = new File(filename);
            try {
                Metadata metadata = ImageMetadataReader.readMetadata(imageFile);
                Directory exifDirectory = metadata.getDirectory(ExifDirectory.class);

                Double cameraFocus = 0.0;
                try {
                    String cameraFocusString = exifDirectory.getString(ExifDirectory.TAG_FOCAL_LENGTH);
                    if (cameraFocusString != null) {
 // value is present
                        cameraFocus = Double.parseDouble(cameraFocusString);
                    }
 //System.out.println("Focus lenght = " + cameraFocus);
                } catch(NumberFormatException nFE) {
 //System.out.println("Not an Integer");
                }

                Integer currentimagecount = 0;
                if(focalmap.containsKey(cameraFocus)) {
 //System.out.println("key already tgere");
                    currentimagecount = focalmap.get(cameraFocus);
                }
                currentimagecount++;
 //System.out.println("putting in map: "+currentvalue);
                focalmap.put(cameraFocus, currentimagecount);
            } catch (ImageProcessingException e) {
                System.err.println("skipping file "+filename+"due to error:"+e);
            }
        }

        Integer totalimagecount = 0;

        TreeSet set = new TreeSet(new Comparator() {
            public int compare(Object obj, Object obj1) {
                int vcomp = ((Comparable) ((Map.Entry) obj1).getValue()).compareTo(((Map.Entry)
                    obj).getValue());
                if (vcomp != 0) return vcomp;
                else return ((Comparable) ((Map.Entry) obj1).getKey()).compareTo(((Map.Entry)
                    obj).getKey());

            }
    });

        set.addAll(focalmap.entrySet());
        System.out.println("focal length in mm;number of images;");
        for (Iterator i = set.iterator(); i.hasNext();) {
            Map.Entry entry = (Map.Entry) i.next();
            Double key = (Double) entry.getKey();
            String keyname;
            if(key == 0) {
                keyname = "?";
            } else {
                keyname = key.toString();
            }
            Integer numimages = (Integer) entry.getValue();
            System.out.println(keyname + ";" + numimages+";");
            totalimagecount+=numimages;
        }

 //System.out.println("total: "+totalimagecount+" images.");
    }

    private void getFiles(File folder, List list) {
        folder.setReadOnly();

        File[] files = folder.listFiles(new ImageFileFilter());
        for(int j = 0; j < files.length; j++) {
            list.add(files[j]);
        }

        File[] subfolders = folder.listFiles();
        for(int j = 0; j  0 && args[0].length() > 0) {
            String imagepath = args[0];
            if(new File(imagepath).exists()) {
                new FocalApp(imagepath);
            } else {
                System.err.println("given image directory not found!");
                System.exit(2);
            }
        } else {
            System.err.println("please give a directory containing the images!");
            System.exit(1);
        }
    }
}

class ImageFileFilter implements FilenameFilter
{
    public boolean accept( File f, String s )
    {
        Pattern pattern = Pattern.compile("([^\s]+(?=\.(jpg|crw|cr2|nef|arw|tiff|tif))\.\2)", Pattern.CASE_INSENSITIVE);
        return pattern.matcher(s).matches();
    }
}

I used JDK 1.6. To build, you’ll need the “metadata-extractor-2.4.0-beta-1.jar” from the metadata extractor page. Then use:

javac -Xlint:unchecked -classpath .;metadata-extractor-2.4.0-beta-1.jar FocalApp.java
jar cfm FocalApp.jar Manifest.txt *.class

to build it. The Manifest.txt is just for entry method:

Manifest-Version: 1.0
Created-By: 1.6.0 (Sun Microsystems Inc.)
Class-Path: metadata-extractor-2.4.0-beta-1.jar
Main-Class: FocalApp

The run the jar, i.e. using:

java -jar FocalApp.jar "/path/to/your/images"

A tricky construct I had to google for after all the java-free months was how to sort a TreeMap by its values and not by the keys. In PERL this is simple (see Perlfaq4 for example):

my @keys = sort { $hash{$a} <=> $hash{$b} } keys %hash;

You get an array containig the hash’s keys, but sorted in the order of the hash’s values.
Now you can just iterate over the keys, displaying the values which could be something like that:

foreach my $Key (@keys) {
print "Key $Key has value $hash{$Key}";
}

In java I couldn’t find such an easy solution. First, I use a TreeMap to store the key-value pairs. This can already sort by keys using a SortedMap. But to turn the sorting around I needed put the entire content of that SortedMap into a TreeSet collection (code line 78), which uses a custom Comperator instance. I got this snippet from this forum page.

You can download the java code in a zip from here.