Friday, June 5, 2009

Deep Zoom Batch Processing using DeepZoomTools.dll

The following code will open a folder browser dialog and allow you to recursively batch process multiple images using the DeepZoomTools.dll that is included when you download Deep Zoom Composer. I also added additional methods for creating a CSV of your directory structure so you can filter out your dzi files and export into a data source.

Note: This is part of the April 2009 version of Deep Zoom Composer.

Steps:

1. Create a Window Form Application in Visual Studio.

2. Make a reference to the DeepZoomTools.dll found in C:\Program Files\Microsoft Expression\Deep Zoom Composer.

3. Add the following code and call the GenerateDeepZoomImages method.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using Microsoft.DeepZoomTools;
public void GenerateDeepZoomImages()
{
FolderBrowserDialog folderBrowser = new FolderBrowserDialog();
DialogResult result = folderBrowser.ShowDialog();
if (result == DialogResult.OK)
{
List<string> extensionFilter = new List<string> { ".tiff", ".tif", ".jpg", ".bmp", ".png" }; //DeepZoom (April 2009) only supports these extensions
List<FileSystemInfo> fileList = GetFileSystemInfo(folderBrowser.SelectedPath, false, true, true, extensionFilter);
CollectionCreator collectionCreator = new CollectionCreator();
ImageCreator imageCreator = new ImageCreator();
List<Microsoft.DeepZoomTools.Image> images = new List<Microsoft.DeepZoomTools.Image>();
foreach (FileInfo fsInfo in fileList)
{
string imageFileName = fsInfo.FullName;
string imageFolderName = Path.GetDirectoryName(fsInfo.FullName);
string destinationFolder = imageFolderName + "\\" + fsInfo.Name + "_dzdata";
imageCreator.Create(fsInfo.FullName, destinationFolder);
Microsoft.DeepZoomTools.Image img = new Microsoft.DeepZoomTools.Image(imageFileName);
//You can manipulate this Image object before adding
images.Add(img);
collectionCreator.Create(images, destinationFolder);
}
}
}

public List<FileSystemInfo> GetFileSystemInfo(string directoryPath, bool includeFolders, bool includeFiles, bool recursive, List<string> extensionFilter)
{
extensionFilter = (from e in extensionFilter select e.ToLower()).ToList<string>();
List<FileSystemInfo> fileSystemList = new List<FileSystemInfo>();
DirectoryInfo directory = new DirectoryInfo(directoryPath);
if (recursive)
{
foreach (DirectoryInfo childDirectory in directory.GetDirectories())
{
fileSystemList.AddRange(GetFileSystemInfo(childDirectory.FullName, includeFolders, includeFiles, recursive, extensionFilter));
}
}
if (includeFolders)
{
fileSystemList.Add(directory);
}
FileInfo[] files = directory.GetFiles("*.*");
foreach (FileInfo file in files)
{
if (extensionFilter.Count != 0)
{
if (extensionFilter.Contains(file.Extension.ToLower()))
{
fileSystemList.Add(file);
}
}
else
{
fileSystemList.Add(file);
}
}
return fileSystemList;
}
public static string GetFileSystemCsv(List<FileSystemInfo> fileSystemList)
{
StringBuilder sb = new StringBuilder();
sb.Append("Name,Full Name,Parent Folder,Creation Time,Last Access Time,Last Write Time,Length, Extension,Attributes\r\n");
foreach (FileSystemInfo fileSystemItem in fileSystemList)
{
if (fileSystemItem.GetType() == typeof(FileInfo))
{
sb.Append(string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8}\r\n", fileSystemItem.Name, fileSystemItem.FullName, fileSystemItem.FullName.Replace(fileSystemItem.Name, ""), fileSystemItem.CreationTime, fileSystemItem.LastAccessTime, fileSystemItem.LastWriteTime, ((FileInfo)fileSystemItem).Length.ToString(), fileSystemItem.Extension, fileSystemItem.Attributes.ToString()));
}
else
{
sb.Append(string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8}\r\n", fileSystemItem.Name, fileSystemItem.FullName, fileSystemItem.FullName.Replace(fileSystemItem.Name, ""), fileSystemItem.CreationTime, fileSystemItem.LastAccessTime, fileSystemItem.LastWriteTime, "", "", fileSystemItem.Attributes.ToString()));
}
}
return sb.ToString();
}

private void SaveCsvFile(string fileText)
{
Stream myStream;
SaveFileDialog saveFileDialog1 = new SaveFileDialog();
saveFileDialog1.Filter = "CSV Files (*.csv)*.csv";
if (saveFileDialog1.ShowDialog() == DialogResult.OK)
{
if ((myStream = saveFileDialog1.OpenFile()) != null)
{
StreamWriter wText = new StreamWriter(myStream);
wText.Write(fileText);
wText.Close();
}
}
}

11 comments:

  1. Any extension supported by WIC should work. See list at http://msdn.microsoft.com/en-us/library/ms737408.aspx#wpfc_codecs

    ReplyDelete
  2. Thanks anonymous. I couldn't find a way to retrieve all codes supported by WIC. I imagine it must be somewhere in the System.Windows.Media namespace that is part of PresentationCore.dll. Here is a way to retrieve the ones supported by the System.Drawing.Imaging namespace.

    .bmp
    .dib
    .rle
    .jpg
    .jpeg
    .jpe
    .jfif
    .gif
    .tif
    .tiff
    .png

    public static List<string>> GetImageCodecs()
    {
    return (from img in ImageCodecInfo.GetImageEncoders()
    select img.FilenameExtension.Split(';') into extensions from extension in extensions
    select extension.Replace("*", "")).ToList<string>();
    }

    ReplyDelete
  3. Hi John,

    I just started learning silverlight and deep zoom. I setup a folder of images and processed them using deepzoomtools.dll using your c# code and it worked perfect. If I want to show these images in a silverlight application (vs 2008), what additional steps do I need to do? Any guidance would be greatly appreciated.

    Best,

    Steve Ellis
    Steven1774@gmail.com

    ReplyDelete
  4. My response to Steve:

    Hi Steve,
    I'm glad to here you found something useful on my blog. I have been pretty busy the last week, but can give you a few pointers. I think the trick is to get this into some sort of data source (like SQL) so that you can make appropriate queries. In my case I had a relational database that included information on motorcycles. One table was called ModelImages that had a one-to-many relationship between motorcycle models and correlated images. I also had a bit column called Default which told me if the image was the default.

    Unfortunately I don't use Silverlight at work, so it's been one of those part-time geek hobbies and I am still learning. Here are two links I found extremely helpful.

    Scott Gu's excellent eight part Silverlight tutorial. My motorcycle app followed this basic pattern which included a grid and modal popup with details.
    http://weblogs.asp.net/scottgu/pages/silverlight-2-end-to-end-tutorial-building-a-digg-search-client.aspx

    Brad Abrams recorded this before 3.0 came out, so some of the steps may be slightly different, but I think it will give you a push in the right direction.
    http://videos.visitmix.com/MIX09/T40F

    Cheers,
    John

    ReplyDelete
  5. Small correction to generating the CSV file. The filter should use the following:

    saveFileDialog1.Filter = "CSV Files (*.csv)*|.csv";

    ReplyDelete
  6. Hi I get an error here
    collectionCreator.Create(images, destinationFolder);

    Do you have any ideas how to fix this?

    System.IO.InvalidDataException was unhandled
    Message="File is not in an image format 'file:///C:/CD/XYZ/TWC/Website/tiles/png/radar1_images/radar1.XML'. Exception message: No imaging component suitable to complete this operation was found."
    Source="DeepZoomTools"
    StackTrace:
    at Microsoft.DeepZoomTools.BitmapTransformer.CreateBitmapDecoder(StreamEventArgs streamEventArgs, BitmapCreateOptions createOptions, BitmapCacheOption cacheOption)
    at Microsoft.DeepZoomTools.BitmapTransformer.CreateInputFrame(InputNode inputNode, Int32Rect rectNeeded)
    at Microsoft.DeepZoomTools.BitmapTransformer.GetPixelSize(String fileName)
    at Microsoft.DeepZoomTools.SparseImageCreator.Create(ICollection`1 images, String destination)
    at Microsoft.DeepZoomTools.ImageCreator.Create(String source, String destination)
    at Microsoft.DeepZoomTools.CollectionCreator.Create(ICollection`1 images, String destination)
    at DeepZoomTester.Form1.GenerateDeepZoomImages() in C:\Users\Administrator\Documents\Visual Studio 2008\Projects\TempProj\DeepZoomTester\DeepZoomTester\Form1.cs:line 49
    at DeepZoomTester.Form1.btnTest_Click(Object sender, EventArgs e) in C:\Users\Administrator\Documents\Visual Studio 2008\Projects\TempProj\DeepZoomTester\DeepZoomTester\Form1.cs:line 23
    at System.Windows.Forms.Control.OnClick(EventArgs e)
    at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
    at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
    at System.Windows.Forms.Control.WndProc(Message& m)
    at System.Windows.Forms.ButtonBase.WndProc(Message& m)
    at System.Windows.Forms.Button.WndProc(Message& m)
    at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
    at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
    at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
    at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
    at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
    at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
    at DeepZoomTester.Program.Main() in C:\Users\Administrator\Documents\Visual Studio 2008\Projects\TempProj\DeepZoomTester\DeepZoomTester\Program.cs:line 18
    at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
    at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
    at System.Threading.ThreadHelper.ThreadStart()
    InnerException:

    ReplyDelete
  7. It sounds like one of this images cannot be processed. If you can find out the image in question, you can try saving it as a different format to see if it helps. You can also collect all the unprocessed images by doing something like this:

    Replace this line...

    collectionCreator.Create(images, destinationFolder);

    With this...(Put a breakpoint in the Catch clause)

    List&ltMicrosoft.DeepZoomTools.Image> invalidImages = new List&ltMicrosoft.DeepZoomTools.Image>();
    foreach(Microsoft.DeepZoomTools.Image image in images)
    {
    try
    {
    collectionCreator.Create(image, destinationFolder);
    }
    catch(Exception ex)
    {
    invalidImages.Add(image);
    }
    }

    ReplyDelete
  8. Grrr...blogspot is jacking up my comments. &lt is supposed to be less than < (less than).

    ReplyDelete
  9. Thanks for sharing this.

    I am trying the same API but for the image grids, let's say 3 rows and 4 columns. In my understanding ImageCreator does what "import" does in DeepZoom Composer, then CollectionCreator works like "Export".

    Q:How to stitch or "Compose" images in a grid placing them side by side in rows and columns (based on file name convention) programmatically?

    As well, how to export them into "Single image" format?

    Thanks,

    Val

    ReplyDelete
  10. pls post form design for this

    ReplyDelete
  11. This really doesn't have much of a UI. You could even just create a Console Application assuming you have the image directory hard coded.

    ReplyDelete