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.

Advertisements

Portable virtual boxes

I’ve been looking for an alternative to VMWare and came across Sun’s VirtualBox. I also found the portable version, which is very cool if you want to setup a bunch of  virtual machines and share it with your colleagues. I installed CentOS5, which is a free linux distribution very similar to RedHat. To get access to the “Guest Additions” coming with VirtualBox, you need a little note how to setup the kernel headers.

Furthermore, if you want to make a copy of a virtual machine drive you created with virtualbox, you can use CloneVDI. Also very nice is the not-so-obvious-possibility to map network ports from the virtual machine to your local computer using the VBoxManage tool. For instance I rerouted the VM’s ports 22 (SSH), 80 (WWW) and 443 (SSL) to my local computer’s ports 2222, 2280 and 22443:

VBoxManage setextradata "CentOS5" "VBoxInternal/Devices/pcnet/0/LUN#0/Config/guest_ssh/Protocol" TCP
VBoxManage setextradata "CentOS5" "VBoxInternal/Devices/pcnet/0/LUN#0/Config/guest_ssh/GuestPort" 22
VBoxManage setextradata "CentOS5" "VBoxInternal/Devices/pcnet/0/LUN#0/Config/guest_ssh/HostPort" 2222

VBoxManage setextradata "CentOS5" "VBoxInternal/Devices/pcnet/0/LUN#0/Config/guest_www/Protocol" TCP
VBoxManage setextradata "CentOS5" "VBoxInternal/Devices/pcnet/0/LUN#0/Config/guest_www/GuestPort" 80
VBoxManage setextradata "CentOS5" "VBoxInternal/Devices/pcnet/0/LUN#0/Config/guest_www/HostPort" 2280

VBoxManage setextradata "CentOS5" "VBoxInternal/Devices/pcnet/0/LUN#0/Config/guest_ssl/Protocol" TCP
VBoxManage setextradata "CentOS5" "VBoxInternal/Devices/pcnet/0/LUN#0/Config/guest_ssl/GuestPort" 443
VBoxManage setextradata "CentOS5" "VBoxInternal/Devices/pcnet/0/LUN#0/Config/guest_ssl/HostPort" 22443

If you’re walking your first steps in CentOS – like I did, you might find it helpful that the basic configuration screen which appears right after the installation can be re-run using just the setup command. And if you are coming from Debian (apt-get ..) or SuSE (yast2) be aware that the installer for repository packets in RedHat and CentOS ist called yum.

Default editor for midnight commander

Mcedit isn’t the choice of everyone. It’s quite hard to copy & paste text with mcedit, so I wanted to use nano for the F4 key’s job. You can easily change the editor seperately for each user by setting the EDITOR variable:

export EDITOR="nano"

In addition you have to disable the internal mcedit editor by editing the midnight commander’s ini file in the user’s home directory, say the: ~/.mc/ini file:

use_internal_edit=0

OpenGL 3.0 context

“Great” news : OpenGL 3.0 is out! Well, after skimming over the new specs there’s really no new stuff – besindes of some manufacturer specific extensions going EXT or ARB and the deprecated markings sig. NVIDIA announced full GL3.0 support on G80 series with driver version 181.00+ so I tried to play around with the new context type – here’s some code 🙂

#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
#define WGL_CONTEXT_FLAGS_ARB 0x2094

typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC) (HDC hDC, HGLRC hShareContext, const int* attribList);

void GLWindow::CreateGL() {
    if(m_hWnd == NULL) {
        throw std::exception("Window handle cannot be NULL");
    }    // If we have no window, we cannot continue

    // If any of the following steps fail, we shutdown any OpenGL elements that have been started and exit
    if (!(m_hDC=GetDC(m_hWnd))) {                        // Retrieve the Device Context for this window
        ShutdownGL();
        throw std::exception("Could not get window Device Context");
    }

    //Create the PixelFormatDescriptor, which describes to OpenGL the pixel properties such as depth, color, and alpha channels
    PIXELFORMATDESCRIPTOR pfd;
    ZeroMemory( &pfd, sizeof(PIXELFORMATDESCRIPTOR) );
    pfd.nVersion = 1;                                                                // The PFD version, always 1
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;        // The properties of the PFD, in this case OpenGL support and double buffering
    pfd.iPixelType = PFD_TYPE_RGBA;                                                    // The type of Pixels we're dealing with
    pfd.cColorBits = m_displaySettings.colorBits;                                    // The color depth of the window
    pfd.cAlphaBits = m_displaySettings.alphaBits;                                    // The alpha depth of the window
    pfd.cDepthBits = m_displaySettings.depthBits;                                    // The number of bits to use for the depth buffer
    pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);                                        // The size of this structure

    int pixelFormat;
    if (!(pixelFormat = ChoosePixelFormat(m_hDC,&pfd))) {
        ShutdownGL();
        throw std::exception("Could not find a suitable Pixel Format");
    }

    if(!SetPixelFormat(m_hDC,pixelFormat,&pfd)) {            // Set the format of the Device Context
        ShutdownGL();
        throw std::exception("Could not set the Pixel Format");
    }

    // NV GL 3.0
    HGLRC tempContext = NULL;
    if(!(tempContext = wglCreateContext(m_hDC))) {                    // Bind the render context for drawing
        ShutdownGL();
        throw std::exception("Could not create temp. Render Context");
    }

    if(!wglMakeCurrent(m_hDC, tempContext)) {                    // Bind the render context for drawing
        wglDeleteContext(tempContext);
        ShutdownGL();
        throw std::exception("Could not make temp. Render Context current");
    }

    int attribs[] = {
        WGL_CONTEXT_MAJOR_VERSION_ARB, 3,//we want a 3.0 context
        WGL_CONTEXT_MINOR_VERSION_ARB, 0,//and it shall be forward compatible so that we can only use up to date functionality
        WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
        0 //zero indicates the end of the array
    };

    PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; //pointer to the method
    wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC) wglGetProcAddress("wglCreateContextAttribsARB");

    if(wglCreateContextAttribsARB == NULL) { //OpenGL 3.0 is not supported
        // Bind the render context for drawing
        wglDeleteContext(tempContext);
        ShutdownGL();
        throw std::exception("Cannot get Proc Adress for GL 3.0 CreateContextAttribs");
    }

    if (!(m_hRC=wglCreateContextAttribsARB(m_hDC,0, attribs))) {
        wglDeleteContext(tempContext);
        ShutdownGL();
        throw std::exception("Can't Create A GL 3.0 Rendering Context.");
    }
    wglDeleteContext(tempContext);

    if(!wglMakeCurrent(m_hDC,m_hRC)) { // Try To Activate The Rendering Context
        ShutdownGL();
        throw std::exception("Could not make Render Context current");
    }
}

The key is the PFNWGLCREATECONTEXTATTRIBSARBPROC function pointer that is supplied by the opengl dll (see its declation on line 5). Using this layout we can fetch the function using a casted wglGetProcAddress() to the new function “wglCreateContextAttribsARB”. So the difference with GL3.0 here is to use a different method when creating a GL-context. The former method would still create an GL2 context. Alternatively one may use a new version of a utility/ext wrapper (glut, glew, glext, etc.) which supplies the new function directly.

Here’s the complete test project: gl30context_sample_code.

DICOM viewer with .NET

After an application interview at Zeiss Meditec I was given a homework with two excercise. The first is about parsing some patient data from xml files with DTD which is not really new stuff for me. The second excercise is an implementation of a DICOM file viewer for a given example VL dicom file. The task tells to use whether C++, Java or C# and because I didn’t do C# for a while I started looking for a good .NET dicom library.

Looking at some OpenSource projects: they’re horrible! Most of the libs are in a pre- alpha- testing- doNotUseInProductiveEnvironments phase and the example viewers of some libs could not read meditec’s VL dicom file at all. Quite promising appeared GrassRoots as it’s a C++ based lib that is wrapped to C#, Python, etc. using swig. But I encountered some issues and as the documentation is close to zero I continued my lib-search.

Finally with mDCM I found a quite easy-to-use .NET C# implementation which was very fast to integrate into my little viewer app. I used a Utility class to access the library methods using the Dicom.Data namespace. To open a dicom file I used:

bool success = false;
m_fileformat = new DicomFileFormat();
try {
m_fileformat.Load(filename, DicomReadOptions.Default |
DicomReadOptions.KeepGroupLengths |
DicomReadOptions.DeferLoadingLargeElements |
DicomReadOptions.DeferLoadingPixelData);

success = true;
}
catch (Exception e)
{
m_lasterror = "Error parsing file! : " + e.Message + "n";
return false;
}

return success;

Where m_fileformat is a Dicom.Data.DicomFileFormat data member. After that I read the pixel data to obtain the enclosed image:

DcmPixelData pixeldata = new DcmPixelData(m_fileformat.Dataset);
if (pixeldata.NumberOfFrames == 0)
{
m_lasterror = "No image data found" + "n";
return false;
}
else if (pixeldata.NumberOfFrames == 1) // currently only first img
{
MemoryStream strm = null;
try
{
strm = new MemoryStream(pixeldata.GetFrameDataU8(0));
img = System.Drawing.Image.FromStream(strm);
}
catch (Exception ex)
{
m_lasterror = "Error reading image from dicom file (" + ex.ToString() + ")" + "n";
return false;
}
return true;
}

As one can see, most likely only 8bit-per-channel images will be supported here (GetFrameDataU8() returns a plain byte[]). Moreover when testing with other sample dicom files this threw exceptions in many cases, so I guess I should look for another .NET lib or alternatively switch to Java or C++ 😦

=> zdicomviewer sourcecode and documentation

Direct3D9 Depth Buffer access

If you have to deal with Depth Buffer reading in Direct3D9, you’ll propably encounter the documentation saying that you cannot read the GPU depth buffer directly. But there’s a workaround with special buffer formats on NVIDIA Cards (RAWZ on older cards and INTZ on G80+ series) and on ATI (DF16/24, I think). There’s also a paragraph in the G80 documentation by NVIDIA (see http://developer.nvidia.com/object/gpu_programming_guide.html). I think GTA4 is a game that uses the formats. Here’s some code I have been playing around with using Microsofts DirectX SDK of November 2008:

// decls

IDirect3DTexture9* m_pSMZTexture;   // rawz for using dss as input for next shader
LPDIRECT3DSURFACE9 m_pBackBuffer;   // default

// creates NVIDIA INTZ Format DSS. Use as DSS and render scene. Then use as input texture for another pass.
BOOL vmD3D::SetupDepthBufferAccess()
{
if(!m_pd3dDevice)
return FALSE;

if(FAILED(m_pd3dDevice->CreateTexture(TEXDEPTH_WIDTH, TEXDEPTH_HEIGHT, 1,
D3DUSAGE_DEPTHSTENCIL, (D3DFORMAT)MAKEFOURCC('I','N','T','Z'),
D3DPOOL_DEFAULT, &m_pSMZTexture, NULL)))
{
return FALSE;
}

return TRUE;
}

This actually creates an INTZ-Format DSS. Be sure to use the same dimensions of the render target you’ll bind on the same drawcall. Next, before your dracall bind the dss:

// setdss (our custom zsurf) -> intz!
// first fetch surface from tex at level 0
IDirect3DSurface9 *pSMZSurf = NULL;
m_pSMZTexture->GetSurfaceLevel(0, &pSMZSurf);
// set dss:
if(FAILED(m_pd3dDevice->SetDepthStencilSurface(pSMZSurf)))
return FALSE;
pSMZSurf->Release();

When drawing, there’s no need for any shader stuff if you simply want to output depth-only values (just write black color for example). But obviosly your vertex shader should do the needed transformations (multiply your WorldProjectionMatrix for example). After drawing with the intz-dss bound, you can use the texture as shader input for a next render pass. Here you can simply render a Full-Screen Quad to visualize the depth values:

if(FAILED(pFxDisplayDepth->SetTexture("DepthMap", m_pSMZTexture)))
return FALSE;

//set render target back to normal back buffer / depth buffer (or disable Z - as it's a dumb quad!)
if(FAILED(m_pd3dDevice->SetRenderTarget(0, m_pBackBuffer)))
return FALSE;

Then just draw the quad, i.e. with lazy UP RHW drawing:

UINT uPasses;
if (SUCCEEDED(pFxDisplayDepth->Begin(&uPasses, 0)))
{
for (UINT uPass = 0; uPass BeginPass(uPass);
m_pd3dDevice->SetFVF( m_pQuad->FVF );
m_pd3dDevice->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, m_pQuad->m_Vertex, sizeof( FullScreenQuad::Vertex ) );
pFxDisplayDepth->EndPass();
}
pFxDisplayDepth->End();
}

Be aware that Microsoft PIX cannot handle the INTZ resource, so you won’t be able to see a preview or the metadata of the INTZ-Surface.

Startup

Omg, I’m blogging! Well, first maybe some stuff I’ve been doing at present. This will mainly be GPU rendering and Direct3D/OpenGL topics. There’re many interesting blogs caring about such issues:

BrainDump by flo
Flo is lead dev at RadonLabs in Berlin (Nebula2+3 engines, Drakensang game for example)
http://flohofwoe.blogspot.com/

Diary of a Graphics Programmer
http://diaryofagraphicsprogrammer.blogspot.com
Blog of Wolgang Engel (ShaderX books, Rockstar games dev)

http://realtimecollisiondetection.net
Christer Ericson’s blog (Sony dev)

http://drkappa.blogspot.com
Dr. Kappa’s Korner.  Graphics blog.

http://www.realtimerendering.com/blog
Real-Time Rendering blog, by sackboy from LittleBIGPlanet project, There’s also a book with that name ^^

http://levelofdetail.wordpress.com
LOD stuff, but also posts about shadows, Radiosity, etc.