Soon there will be a new generation of Phil Davis' BTZS ExpoDev available for iOS devices. This is a completely new and redesigned version of ExpoDev but it will use the same exported Plotter film test files as previous versions. It promises to be easier to use and will have many new features in its final form. While there are many things still left to do, a lot of the UI is in place and it has gotten to the point where I am ready to share some initial screen previews. What you're seeing below is the actual ExpoDev program as it runs on an iPhone. BTZS ExpoDev will support both the iPhone and iPod Touch devices (it will also run on an iPad but not as an iPad sized app).

Disclaimer: This is an early preview as much work remains to be done. I do hope to have this available by spring but there are no guarantees as Apple's App Store approval process and timeline is largely out of my hands.

When you start ExpoDev for iOS, it starts with the list of exposure records that you have previously created (note: all data in these screen shots is simulated and/or placeholders so don't expect it to make much sense even if you are familiar with ExpoDev).

IMG_0520

From here you can browse and manage previously created exposure records, view their details,  etc… You can also access app settings and defaults and of course, create new exposure records.

Here is the first screen of a new exposure record. Here you can set the title, film holder, select your film and developer combination from your list of imported film tests, select the lens in use and of course tune the Flare Factor and Paper ES values.

IMG_0514

 

Along the bottom is a set of tabs. ExpoDev exposure records have several sections for each of the logical parts of an exposure: Camera setup, Metering, Exposure Factors, DOF, and the final Exposure. You can freely moving between these areas by selecting the active tab at the bottom of the app at any time. 

Here's two examples of the Metering tab, showing both Incident and Zone metering. You can set which metering mode is your default in the app settings as well as change it on the fly on the metering tab. These two screens also show that you can work with meters that use decimal EV units as well as meters that display EV in 1/3rds.

IMG_0519

IMG_0515

You can adjustment these and other similar values with simple taps without ever having to bring up the iOS keyboard. You'll notice that these values are separated into segments. To change a value, you tap either the top or bottom of a segment to change the value up or down. You do this independently for each segment, which will allow you to quickly enter or modify values.

The next exposure screen is the Factors screen which allows you to set filter factors, bellow extension or close-up factor and a general exposure adjustment in 1/3 stop increments.

IMG_0516

ExpoDev will come set up with common filters and their factors but this list is fully editable in the app settings. You can add and remove filters, change their factors, etc… to match your equipment and needs.

Next up is the DOF or Depth of Field tab. It supports several modes from simply checking DOF to calculating and enforcing DOF based on several factors. What is shown below is calculating DOF based on near and far subject planes. Again you'll notice the multi-segmented value selectors for fast data entry. This allows you to quickly enter both small and large numbers efficiently with a minimum number of finger taps. The example below shows working in Metric measurement units but you can also work in Imperial units (i.e. feet and inches).

IMG_0517

Once you've set all of your parameters ExpoDev then calculates a starting exposure for you (again don't read too much in the values below, they are placeholders):

IMG_0518

Here you can choose to set either the Aperture or Shutter Speed or time. If you've used the DOF feature, ExpoDev will select exposure settings that preserve your selected DOF. Using Plotter's exported film data it will also correct the exposure for reciprocity failure, yielding very accurate exposures that are based on your personal film test data. It will also offer you a timer to use as necessary for longer exposures.

When you're done, it will add the exposure record to the list of exposure records where you can views the details or start again with a new exposure.

ExpoDev is still a work in progress, including the UI you see above. Recently at the Phil Davis Memorial Photo Retreat in Death Valley national Park I ran some field trials with users and collected feedback. In the next round of app development I'll work in that feedback. I am hoping that a beta test version will make it out to testers in January and that hopefully by this spring, it will be available for purchase in the Apple App Store.

In the meantime you can watch BTZS.org for announcements.


There are quite a few discussions and examples on using DynamicObject to create wrappers for XML documents already out here. However I found most of them lacking. Here’s my take on this.

This is largely based on this example: Dynamic in C# 4.0: Creating Wrappers with DynamicObject (you’ll probably need to read this first to understand what I’m talking about here as mine is an extended version of this).

I made some important additions like handling attributes and dealing with collections of elements. My goal here was to keep the fluency of working with XML this way, not to cover every base. It’s not a perfect abstraction by any means nor is it a complete one. But it works for me.

For accessing XML attributes, I went with an indexer syntax like a dictionary class:

dynamicXMLObject[“AttributeName”] = “AttributeValue”

Note that is you set a value to null it removes the attribute. Setting a value for an existing attribute replaced it. This all happens in the TrySetIndex() and TryGetIndex() methods of this wrapper class and it pretty self-explanatory.

 

For dealing with collections of elements, you just access the underlying XElement collection method of choice:

dynamic elements = dynamicXMLObject.Elements()

This was possible in the orignal example but I added the ability to return wrapped collections of DynamicXMLNode. When you call any underlying method that returns an IEnumerable<XElement> collection, I instead return an IEnumberable<DynamicXMLNode> collection . This bit of magic is probably the most interesting part as I detect the return type for any underlying method called and substitute in the wrapped collection as necessary. This allow all the members of XElement that return collections of XElement to return collections of DynamicXMLNode instead, transparently. Note that in your dynamic code, you’ll need to cast this collection to IEnumerable<dynamic> to access all the handy Linq extension methods, etc…

Along those same lines, I also detect and convert underlying method parameters so that you can call XElement.Add(), etc… and pass in DynamicXMLNode. When you do I instead substitute the inner wrapped XElement as necessary. This allows for transparent use of DynamicXMLNode in most cases.

Both of these detections and substitution happen in TryInvokeMember(). The combination of these two substitutions allow any of the underlying methods of the wrapped XElement to transparently work with the DynamicXMLNode wrapper class.

I also cloned some of the useful static XElement methods, like Load() and Parse(). I would have liked to handle this dynamically like other method calls but it seems you can’t trap static method calls with DynamicObject. Oh well.

The only hitch is that for XElement methods that expect an XName object like Elements() or Descendants() you’ll have to cast any strings you use to an XName in your dynamic code. Normally with static typing this is not necessary as it happens behind the scene via an implicit cast done for you. However with dynamic code the compiler can’t do this for you.

I suppose that I could snoop the parameters to all the methods of XElement and do this cast automatically, but I’ll leave that for the next person to figure out.

public class DynamicXMLNode : DynamicObject
{
    private XElement node;
    
    public DynamicXMLNode(XElement node)
    {
        this.node = node;
    }

    public DynamicXMLNode(String name)
    {
        node = new XElement(name);
    }

    public static DynamicXMLNode Load(string uri)
    {
        return new DynamicXMLNode(XElement.Load(uri));
    }

    public static DynamicXMLNode Load(Stream stream)
    {
        return new DynamicXMLNode(XElement.Load(stream));
    }

    public static DynamicXMLNode Load(TextReader textReader)
    {
        return new DynamicXMLNode(XElement.Load(textReader));
    }

    public static DynamicXMLNode Load(XmlReader reader)
    {
        return new DynamicXMLNode(XElement.Load(reader));
    }

    public static DynamicXMLNode Load(Stream stream, LoadOptions options)
    {
        return new DynamicXMLNode(XElement.Load(stream, options));
    }

    public static DynamicXMLNode Load(string uri, LoadOptions options)
    {
        return new DynamicXMLNode(XElement.Load(uri, options));
    }

    public static DynamicXMLNode Load(TextReader textReader, LoadOptions options)
    {
        return new DynamicXMLNode(XElement.Load(textReader, options));
    }

    public static DynamicXMLNode Load(XmlReader reader, LoadOptions options)
    {
        return new DynamicXMLNode(XElement.Load(reader, options));
    }

    public static DynamicXMLNode Parse(string text)
    {
        return new DynamicXMLNode(XElement.Parse(text));
    }

    public static DynamicXMLNode Parse(string text, LoadOptions options)
    {
        return new DynamicXMLNode(XElement.Parse(text, options));
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        XElement setNode = node.Element(binder.Name);
        if (setNode != null)
        {
            setNode.SetValue(value);
        }
        else
        {
            if (value.GetType() == typeof(DynamicXMLNode))
            {
                node.Add(new XElement(binder.Name));
            }
            else
            {
                node.Add(new XElement(binder.Name, value));
            }
        }
        return true;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        XElement getNode = node.Element(binder.Name);
        if (getNode != null)
        {
            result = new DynamicXMLNode(getNode);
            return true;
        }
        else
        {
            result = null;
            return false;
        }
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        Type xmlType = typeof(XElement);
        try
        {
            // unwrap parameters: if the parameters are DynamicXMLNode then pass in the inner XElement node
            List<object> newargs = null;
            var argtypes = args.Select(x => x.GetType()); // because GetTypeArray is not supported in Silverlight
            if (argtypes.Contains(typeof(DynamicXMLNode)) || argtypes.Contains(typeof(DynamicXMLNode[])))
            {
                newargs = new List<object>();
                foreach (var arg in args)
                {
                    if (arg.GetType() == typeof(DynamicXMLNode))
                    {
                        newargs.Add(((DynamicXMLNode)arg).node);
                    }
                    else if (arg.GetType() == typeof(DynamicXMLNode[]))
                    {
                        // unwrap array of DynamicXMLNode
                        newargs.Add(((DynamicXMLNode[])arg).Select(x => ((DynamicXMLNode)x).node));
                    }
                    else
                    {
                        newargs.Add(arg);
                    }
                }
            }

            result = xmlType.InvokeMember(
                      binder.Name,
                      System.Reflection.BindingFlags.InvokeMethod |
                      System.Reflection.BindingFlags.Public |
                      System.Reflection.BindingFlags.Instance,
                      null, node, newargs == null ? args : newargs.ToArray());


            // wrap return value: if the results are an IEnumerable<XElement>, then return a wrapped collection of DynamicXMLNode
            if (result != null && typeof(IEnumerable<XElement>).IsAssignableFrom(result.GetType()))
            {
                result = ((IEnumerable<XElement>)result).Select(x => new DynamicXMLNode(x));
            }
            // Note: we could wrap single XElement too but nothing returns that

            return true;
        }
        catch
        {
            result = null;
            return false;
        }
    }

    public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
    {
        if (indexes[0].GetType() == typeof(String))
        {
            node.SetAttributeValue((string)indexes[0], value);
            return true;
        }
        else
        {
            return false;
        }
    }

    public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
    {
        if (indexes[0].GetType() == typeof(String))
        {
            XAttribute attr = node.Attribute((string)indexes[0]);
            if (attr != null)
            {
                result = attr.Value;
                return true;
            }
            else
            {
                result = null;
                return false;
            }
        }
        else
        {
            result = null;
            return false;
        }
    }


    public override bool TryConvert(ConvertBinder binder, out object result)
    {
        if(binder.Type == typeof(XElement))
        {
                result = node;
        }
        else if(binder.Type == typeof(String))
        {
                result = node.Value;
        }
        else
        {
            result = false;
            return false;
        }

        return true;
    }
}
 

Scratch that.

I’ve been collaborating with someone on a new and better way to works with subrepos in Mercurial. We wrote a Mercurial extension that adds commands for directly working on subrepos. It is much more capable than my original script.

You can find the Mercurial extension here: http://bitbucket.org/kirbysayshi/hgsubrepo

You install it like any other Mercurial extension, just place the file somewhere and register the extension in your settings file. You can find details about that process here.

Once installed, you can get help on using it by executing “hg help subrepo”.

 

 

I’ve recently switched to Mercurial for source control. It’s a good system but it’s a little rough around the edges sometimes. One area is subepo support. It’s quite usable but there’s one thing missing that really bugs me: recursive status when using nested repos (i.e. combined status for all subrepos). Right now Mercurial’s status command knows nothing about nested repos. It’s a pain since Mercurial’s commit command will commit all nested repos and without proper status support you can’t tell what will be committed.

However, it’s pretty simple to automate getting this status with a little batch file:

@echo off
for /d /r %%d in (*.hg) do (
    pushd %%d
    cd ..
    cd
    hg status %*
    popd
)

This is a very basic version but it works for getting basic status for both the current and all nested repos. It searches for all repos and executes a hg status in each working directory, passing any arguments that you specify to hg status .

I’m sure someone could also patch this directly into Mercurial, perhaps via an extension but this is simple and it works. Hopefully this is something that won’t be needed for too much longer.

 


I love my iPhone but I really don’t care for iTunes. I also use Exchange for email, contacts, and calendar so I a spoiled when it comes to online automatic syncing of my data. I have to use iTunes for getting my .MP3 files onto my iPhone and I have to use it for backing up my iPhone, but other than that I try to use it as little as possible. One thing that really bothers me however is that in order to get my pictures off on my iPhone I either have to plug in a USB cable and use iTunes or I have to pay $99/year for mobile me. For a phone that is so well connected to the Internet this seems really silly to me. Sure I could email them to myself or put them on Flckr but what I really want is to sync the full resolution .jpegs to my computer.

If you look in the iTunes App Store you’ll likely find a bunch of picture syncing apps. A lot of these will let you sync your pictures to your computer but most of them all have a very specific sequence that you have to go through each time, which usually goes something like this:

  1. Start the app on your iPhone
  2. The app will display a random HTTP address
  3. Then you go to your computer and type in the address in your web browser, which will then show your pictures
  4. Then you click and save each picture

There are a few other apps but they all seems to suffer from similar complications. Virtually none of them just push the pictures from your iPhone to your computer with a single click. Many of them also suffer in that they don’t send the full resolution pictures but instead send resized versions. However I’ve figured out something that can do this. It’s not quite automatic but it’s all done by using free apps and services.

First you’ll need an account on Pixelpipe.com. Pixelpipe is an interesting app/service. It lets you set up “pipes” that you can use to send pictures to online services such as blogs, file sharing, and picture sharing sites. To use Pixelpipe you set up “pipes” which tell Pixelpipe where to route the pictures that you send to it. You can send your pictures to Pixelpipe via email, SMS, or the free iPhone app called Pixelpipe. They also have plug-ins for many applications such as Lightroom and Picasa which allow you to directly send your pictures from within these applications.

The next step is that you’ll need an account at box.net. Box.net is one of many file sharing sites on the Internet. You can get a free account with box.net that will allow you to store up to 1GB of data. For transferring pictures, that should be plenty. If you plan to store your pictures online in your box.net account (which might be a good idea as a simple to use backup), then you can upgrade to any of their reasonably priced premium accounts.

Once you have your box.net account, you need to set up a “pipe” in Pixelpipe to route all pictures sent to it to your new box.net account. To do this, start up the Pixelpipe app and go to settings and add a new destination, using your box.net account information.

Now whenever you want to sync your pictures from your iPhone you simply start the Pixelpipe app, select the pictures, and upload them. You can optionally add a title, caption and keywords for each picture at this time as well.

Now this gets us almost there. Your pictures are now on your box.net account but still not on your computer. You could go to the box.net web site and view them but to download them, box.net will make you select each picture one at a time for download (at least with the free account). There’s a trick though that will allow you to drag/drop all of your pictures straight from box.net to any folder on your computer.

The trick is to open your box.net account as a WebDAV or Web Folder. This is something that Windows has supported since at least XP. Box.net however doesn’t officially support this but it seems to work for me, YMMV. (I’m also told that Macs support this as well, just search for WebDAV folders to learn how to set them up).

On Windows Vista, to open your box.net account as a Web Folder you first need to open your “Computer” folder. Then you want to right-click and choose “Add a Network Location.” Next you want to choose “Add a Custom Location” and type in https://box.net/dav and click to finish. When you open this new network location, simply use your box.net account information to log it. This Web Folder can then be opened in Explorer just like any other folder except that it is a remote folder.

Once the Web Folder is open, you can simple drag/drop your uploaded pictures to any folder on your computer. You could even set up automatic syncing via one of the many file syncing tools available since many of them support WebDAV folders as well.

While it’s not the most straightforward thing to set up, once it is set up I find it reliable and easy to use and best of all, free for something that really should have been free all along.


Here’s a quick tip that I recently ran across. Maybe it’s old news but I haven’t seen it before.

Sometimes you want to pause or sleep a few seconds in a command/batch script. By default Windows doesn’t have any form of a “sleep” command installed that you can use in script files. Sure there’s versions of sleep.exe or similar programs that you can install but there’s another utility that’s already installed by default that you can use*. It’s called “choice” and it’s normally used to prompt a user to make a choice between several options. The trick is, it has a default choice timeout that can be set in seconds.

So here’s how to use choice.exe to pause or sleep for 10 seconds in a command script or batch file:

choice /T:10 /D N /N > Nul

Here we’re telling choice.exe to select the default choice, set by /D, in 10 seconds as set by the /T option. The /N and redirection to Nul just keeps the console output clean. I’ve tried with with fairly large timeouts and it seems to work for me. The upper limit appears to be 9999 seconds, which is roughly 2.75 hours.

btw, another common way that I’ve seen used to pause or sleep in a batch file is to use “ping” to repeatedly ping an IP with a timeout value but this method seems much cleaner to me.

* Note: Unfortunately, choice.exe isn’t available by default on Windows XP but it is on Windows Server 2003, Vista, and presumably all later versions of Windows.


Flux and Mutability

The mutable notebook of David Jade