Skip to main content

High Dynaic Range (HDR)

These are some modifications I made to the HDR tonemapping operations in PfsTmo and the related Graphical User Interface QtPfsGui. The modifications are primarily to the Mantiuk tonemapping, and I am indebted to Rafal Mantiuk for coming up with this amazing tonemapping in the first place, and for the original code.

These modifications have been incorporated into PfsTmo from version 1.2 onwards, and will probably be incorporated into QtPfsGui after its next release. I recommend you use these official and maintained versions, rather than the ones listed below. The details below are liable to go even more out of date!

Here is an example of the output from the modified tonemapping:

Normal photograph
Normal photograph
Tonemapped using modified Mantiuk
Same photograph, tonemapped using the modified Mantiuk operator with contrast equalization and a contrast scaling of 2.0

About the modifications

These modifications get round some problems with the Mantiuk operator not converging (it didn't tell you previously when this happened), and, when the original version did converged, the new version gets there significantly quicker. It also adds dual-core (and quad-core, etc) support to the Mantiuk operator, and gives added flexibility to the contrast equalization option within the Mantiuk operator.

The Mantiuk operator also used to give diagonal lines across images. This was fixed in v1.9.0 of qtpfsgui, but (as of December 2007) hasn't been fixed in pfstmo v1.1. This new version includes the fix. It also fixes another major bug that hadn't previously been fixed that, at least for me, was giving a yellow cast to the final results.

There are also small changes to the Fattal operator that may or may not speed things up.

These changes are summarized below, below which are some technical details. Finally, there's another pfs tool, pfsequalize, that I've found useful. See details of that at the bottom.

Modified contrast equalization

The new Mantiuk operator requires a contrast scaling, even in contrast equalization mode. In qtpfsgui this is the same contrast scaling slider as in contrast scaling mode, just with the contrast equalization tick box checked. In pfstmo_mantiuk06, it's the -e <num> option. The original behaviour had a scaling of 1, but there's no special reason it should be this. I find larger values give more contrasty and fantasy-looking images, such as the image above. Sometimes, large values of this number need alterations to the convergence parameters, as noted below.


The Mantiuk central engine (for want of a better word) keeps going until it gets an answer close enough to the correct one that it doesn't make much difference, or until it's tried for long enough. The original version, in easy cases, kept going for too long, and in hard cases, gave up too easily. The new one (I think) does better, and there are several options in pfstmo_mantiuk06 to tell it what you want it to do. qtpfsgui just does something sensible.

When using pfstmo_mantiuk06 with the -v option (which I'd recommend), or when looking at the command line when running qtpfsgui, there's a percentage complete reported. This starts at 0%, and when it gets to 100% it's converged as much as it wanted to. Sometimes it gets stuck for a while, and then keeps moving on. If it stops before it gets to 100% it'll tell you; in this case, have a look at the resulting image, as it may look ok. If it doesn't, try again with a higher itmax using the --itmax <num> option to pfstmo_mantiuk06.

One change is that the new version of pfstmo_mantiuk06 uses a new engine, Conjugate Gradients, which is twice as fast as the old one, BiConjugate Gradients. CG is the default; you can get the old BCG method by using the --bcg command line option to pfstmo_mantiuk06. This is sometimes needed; if the percentage complete gets stuck for ages when using CG, try using BCG to see if that'll help.

How close is good enough? By default, the new version uses a tolerance of 0.001, (10-3, or 1e-3 in computer speak). This is different to how it used to do it, which I think was unpredictable. Sometimes this tolerance isn't low enough, and if it were allowed to carry on it would get a significantly different image. In this case, try using a smaller tolerance (0.00001, or 1e-5) and keep using a smaller and smaller value until the final image doesn't visibly change any more.

One final thing on convergence; the new pfstmo_mantiuk06 has three different upsampling and downsampling operations: -i1 is the original, -i2 is faster and seems to give the same quality, and -i3 is slower and experimental. -i3 may also need a significantly smaller tolerance than the others to converge. -i2 is the recommended option, and is what qtpfsgui uses.

Detailed list of changes

Changes to Fattal tone mapping

  • Changed longs to ints for faster code on 64bit architectures where these aren't the same (this would only be an issue for images with more than 2048 megapixels, which seems unlikely).
  • Changed all maths functions to their float versions, rather than the double versions that were used before. For example, we use powf rather than pow, logf, exp10f, etc. Whether this has a speed advantage, I don't know, but if it's vectorized correctly it could speed things up by a factor of two.

Changes to Mantiuk tone mapping

  • Major bug fix (in both qtpfsgui and pfstmo_mantiuk06): After the tone mapping was done, the input brightness data (which had subsequently been overwritten) was reused for the green channel, rather than using the newly tonemapped green channel. This typically gave my photos a yellowish cast, although the behaviour may be very different for other people.
  • Major bug fix (courtesy of Rafal Mantiuk) that eliminates the diagonal lines across images. This was already fixed in qtpfsgui v1.9.0, but not in pfstmo v1.1.
  • Major internal changes, to the way the pyramid linked list is handled, what data is passed between functions, and what data is overwritten, so that the new version should run more quickly and use significantly less memory.
  • Added a contrast scaling variable to the contrast equalization method, as there's no reason that the contrast histogram should be equalized to 1. This can be used qtpfsgui by moving the contrast scaling slider with contrast equalization mode on, or from pfstmo_mantiuk06 by using the -e <num> option. A value of 1.0 gives the previous behaviour. Larger values seem to be more contrasty and more fantasy looking (the image above used -e2).
  • Modified the contrast scaling algorithm to be accurate, rather than using the 2048 bin histogram it was previously using. It now uses qsort().
  • Added new upsampling and downsampling algorithms, which seem to be significantly faster than the originals, and have the same quality. These are active by default in qtpfsgui, and are activated by using the -i2 option in pfstmo_mantiuk06. This setting is recommended.
  • Added another new upsampling and downsampling routine that I believe is actually what is described in the Mantiuk et al (2006) paper; although this takes far longer to converge and doesn't produce dramatically different results than the other upsamplings. This setting is experimental.
  • Added a new engine (Conjugate Gradients) to the original (BiConjugate Gradients) that is twice as fast. The new one is the default. The old one can be used in pfstmo_mantiuk06 by using the --bcg option. Sometimes BCG converges when CG doesn't; it depends on the image being processed. CG is the default; use BCG when CG doesn't work.
  • Changed dramatically how the engine (CG or BCG) detects when it's working and when it's not. We now converge to within a given tolerance (defaults to 0.001). Sometimes -i3 needs a much higher tolerance, which can be set using pfstmo_mantiuk06 using the --tol <num> option. Try the default, and if it looks weird, try something smaller (say 0.00001). Keep trying smaller values until you get the same result twice.
  • Also changed how the engine detects when it's converging or not by whether it goes backwards, and if so, resets the engine and tries again. This carries on until itmax iterations have happened. This defaults to 50 (pfstmo_mantiuk06) or 200 (qtpfsgui), and can be changed in pfstmo_mantiuk06 by using the --itmax <num> option. Sometimes 50 is ok, but for -e larger than 1 or for -i3 it may need to be as large as 2000.Use the default, look at the command line, and it'll tell you if it needs longer or not. Even if it hasn't fully converged, the resulting image may look fine, so check it first before spending more computer cycles.
  • If this point is confusing to you (and it's quite technical and mathematical), please ignore it. Changed the matrix A being solved using the CG or BCG engine. It's the same as before, except we also try to have the sum of all the x[i] being zero. This is in an effort to make the A matrix positive definite, rather than just positive semi-definite (I don't know if it works though), and to stop the CG or BCG algorithms getting confused. Before, adding any constant to all x[i] would give the same value for A times x. I've found this modification to help convergence in at least one case.
  • Changed the way the percentage complete is reported (on the command line, not in the GUI in qtpfsgui) so that it actually goes steadily (unless it finds the going difficult) from 0% to 100% as it converges. Note that it sometimes gets stuck for a bit and then gets going again, and occasionally it goes backwards, although we hope not for too long!
  • Added dual-core (and quad-core, etc) support for compilers that handle OpenMP.
  • Possibly other changes that I've forgotten: let me know if you find any!

PFS Histogram Equalization

This short program performs a histogram equalization on the image it's given (download the source code below). It's designed to be used on a PFS stream after tonemapping. A saturation of 1 (the default) equalizes the histogram completely, which usually looks far too harsh (try it). A saturation of 0 does nothing. Typically, I quite like something in the range of 0.4 to 0.6, although it varies with the image. The tonemapped image on the top of the page was equalized with a saturation of 0.5, something like:

pfsin input.pfs | pfstmo_mantiuk06 -e2 | pfsequalize -s0.5 | pfsout output.ppm

... although, in reality, I think the options to pfstmo_mantiuk06 were -e2 --itmax 2000 -i2 --bcg.