Open Source workflow for macro focus stacking

We're Getting Mutants in the MCU - The Loop



This is a description of my set of BASH scripts and C programs utilizing open source software (dcraw, Hugin, and ImageMagick) for macro photography post-processing (every step of the way - from converting raw files to focus stacking to multi-scale sharpening). It works under Linux (natively) and Windows (using Cygwin). It might work under MacOS, but I can't help with that as I'm not using MacOS.

This open source workflow has some advantages and disadvantages when compared to existing commercial solutions.

The advantages:

  • In some respects it produces better quality stacked photographs. In particular, the stacked photos have better sharpness (when compared to Zerene Stacker). See e.g. here: . Also, this package handles properly hot pixels, unlike the commercial Adobe Camera Raw product (part of Adobe Lightroom and Photoshop). This removes all hot-pixels related artifacts from stacked photos.
  • It is free.
  • It is fully scriptable (as it is based on command line tools), which is great for full automatization of macro photography post-processing.

The disadvantages:

  • It is slower.
  • It is more memory hungry.
  • It can produce artifacts of its own (e.g., halos around bright features are more pronounced).
  • It is not for everyone. (You need to have some minimum bash and command line experience to be able to use it.)


The package can be downloaded here:

The up-to-date installation instructions can be found in README.txt file inside the package.


This script identifies all dead and hot pixels in raw dark frame image(s). This should be done infrequently, for each camera you use, as appearance of new dead and hot pixels is a very slow process. The dark frame(s) should be produced with the same exposure and ISO as your typical focus stack image. If you provide more than one dark frame, they will be averaged, and converted to dark.tiff, which will later be used by (where dark.tiff will be subtracted from all images - very important when using long exposures and/or high ISO).

Syntax (attention: first letter is capitalized - in Linux it does matter!): dark_frame_raw_image(s)

This script utilizes my C++ program deadpixels.c. As an input it takes a raw (DNG, CR2 etc.) dark frame image. The code works with raw pixel values (before debayering), which ensures its accuracy. First the code identifies and removes from the following analysis all dead pixels. Next, it applies iteratively the "three-sigma rule" to all non dead pixels. Specifically, at each iteration it identifies and removes from the following analysis all pixels hotter than the current 3σ estimate (σ being the current estimate of the standard deviation for raw pixel brightness). This is to get rid of all outliers (hot pixels), to eventually converge to the true standard deviation (and the true median, p) of the Gaussian noise present in the data. This process converges very quickly (typically after 4 iterations). Once the accurate σ value is known, the program identifies all the pixels whose brightness is above p+Nσ, where N=10 by default. These pixels are considered to be hot.

At the end, a file deadpixels.txt, containing a list of all dead and hot pixels in the dark frame, is created in the current folder, and copied to the standard location (home directory). If that file already exists in the home directory, you'll get a warning. If present, this file will be used by scripts and

If the program finds too many hot pixels your dark frame might not actually be totally dark (make sure you completely cover the lens and the viewfinder when making the dark shot). You can always reduce the number of hot pixels found by the program by increasing the second argument for deadpixels program inside script (it is "10" by default). Alternatively, if the program misses some obvious hot pixels, decrease that argument to values < 10. (But don't go below 5.5, as you will start picking purely random noise instead of real hot pixels.)

This script uses a photo of a grey card (filling the whole frame) to compute the rgb scaling coefficients to be used in for proper white balancing of the focus stack. This should be ideally done for every focus stack you make, but it should be okay if you create a separate gray card image for every lighting setup you use. For example, I always use the same lighting setup when doing 10:1 macro stacking with my Nikon M Plan objective (see here): a diffusive cone snapped on the objective, with an extra diffusion layer on top (a piece of paper) - to eliminate a hot spot - illuminated by YN560III flash with a built-in diffuser at 1/16 power. As long as I am using this setup without major modifications, it is sufficient to create the corresponding grey card image only once.

Syntax:  grey_card_raw_image

File deadpixels.txt will be used if present (in the home directory). The output is four numbers which should be used with -r argument when executing script.

By default, the script will use the central 0.3 of the frame (for both x and y dimensions). Change the line frac=0.3 in the script to modify this behavior.

This scripts utilizes the prior information (from and to convert one or more images to 16-bit per color (48 bit total) RGB TIFF files, using either linear or non-linear (sRGB) color space.

Syntax: [-l] [-r <r g b g>] image1 [image2 image3 ...]

-l: use linear color space
-r <r g b g>: use custom white balance coefficients r, g, b, g

One can use wildcards for image names, e.g.  -r 2.35 1.15 1.01 1.00  IMG_9*.CR2

will process all images in the current directory starting with IMG_9 and having the CR2 extension.

One can be even more specific, and specify a particular range of continuously numbered images (this is very convenient if, as I like to do, the first image in the stack is always a dark frame, and the last one is a gray card photo, for white balance computations), like in this example: IMG_{1945..2037}.CR2

If you ran the prior command with more than one dark frame, an average dark frame dark.tiff was created in the current directory. If that is the case, will automatically use it during processing - it will subtract dark.tiff from all images, before debayering, which is very important for long exposure (a few seconds or more) and/or high ISO images.

This script utilizes align_image_stack program (part of Hugin package) to align all images in a stack.

Syntax: [-l] image1 image2 [image3 ...]

-l: assume that the images have linear color space

By itself align_image_stack is fairly slow (as it only uses a single cpu core - apparently only the Windows version). In addition, align_image_stack tries to shift/rotate/rescale all images in the stack to match the geometry of the very first image. Most of the time this is a very bad idea, as the first image in the stack usually has few or no sharp features. As a result, very often all the images in the stack get shifted/rotated/rescaled much more than needed, wasting a large fraction of the frame.

I realized that both deficiencies can be fixed if instead of a single align_image_stack run per stack one would launch two align_image_stack processes simultaneously, both starting form the middle image, but moving in the opposite directions. The middle image is usually well framed (much better than either the first or the last image in a stack), so this approach minimizes the frame waste due to misalignments of the stack images. In addition, the whole alignment procedure runs faster, because there are two align_image_stack processes running in parallel. This should result in 50-100% faster runtime on multi-cpu core processors.

The script utilizes the above approach. As input it takes the list of TIFF images (output of script; one can use wild cards for image names). It creates the same number of aligned TIFF images with names OUT0000.tif, OUT00001.tif and so on. These TIFF files are ~30% larger than the original TIFF files as they have an extra alpha layer (this is the feature of align_image_stack which cannot be disabled). One can always edit the alpha channel of individual stack images if needed, to remove certain areas from the following (focus stacking) stage. One can use open source image editor Gimp for that. (Google for "gimp 2.9" or "gimp goat invasion" to find the 16-bit per color version of gimp; the official Gimp distribution only supports 8-bit colors. The Windows 2.9 builds can be found here.).

A warning: if something goes wrong and you interrupt by pressing Ctrl-C, you will also need to manually kill the two background align_image_stack processes (using pkill or kill programs).

This script applies focus stacking operation to all files with the names OUT*.tif (output of in the current directory. The script takes no command line arguments. It utilizes program Enfuse (part of Hugin package). Simply execute:

The biggest issue with Enfuse is that it loads all the images in the stack to RAM. This severely limits the size of the stack. E.g., on my 16 GB Windows PC I can realistically process only up to 70 images per stack. For extreme macro photography one usually works with much larger stacks (300 or more).

The workaround is to do stacking in two stages - and that's what my script does. The first stage processes multiple slabs in sequence, each slabs having Nslab=sqrt(Ntotal) images, plus the specified overlap factor (by default the overlap between adjacent slabs is 0.3*Nslab; the overlap cannot be smaller than 2 images; both of these parameters can be modified inside script). The second stage applies the same focus stacking algorithm to the slabs, to produce the final stacked image. Using the Nslab=sqrt(Ntotal) assumption minimizes RAM utilization across the whole two-stage process, and also seems to be the optimal choice in terms of the contrast and noise of the final image. With this two-stage approach, on my 16 GB PC, I can do focus stacking for up to 70*70 ~ 5000 48-bit TIFF images.

A side note: the two stage (slabs) focus stacking is also used for completely different reasons - to improve quality of focus stacking (see e.g., though in my tests I didn't see noticeable quality improvements when using slabbing.

The output is a single file - output.tiff - which is the final focus stacked image. The script also creates intermediate (slab) images, with the names 0000.tif, 0001.tif and so on.

Finally, the focus stacked image can be optionally sharpened on up to three different spacial scales using my script, which internally utilizes program convert (part of ImageMagick package).

Syntax:  [strength]  in_file  out_file

If 'strength' is skipped, it is assumed to be 1. If 'strength' is a single number, all spatial scales will be sharpened by the same amount (given by that number). If 'strength' has the following form - x:y:z - the largest scale will be sharpened using 'x' strength, the middle scale will use 'y', and the smallest scale will use 'z'. Set any scale to '0' to disable sharpening on that scale. For example, 0:0:1 will only do the smallest scale sharpening.

It is not perfect - sometimes I get better results than with Smart Sharpening filter of Photoshop, sometimes worse. Ideally, focus stacking sharpening should be done before focus stacking (using 3D blind deconvolution), but this is a very large project which I will be working on in the future.

Community content is available under CC-BY-SA unless otherwise noted.