# Tone mapping a two dimensional distribution

I'd like to display some two dimensional distributions, but I'm not sure how to properly tone map them. My distributions have some narrow spikes, so linearly mapping the probabilities to color hides most of the information.

I've tried two approaches, doing a gamma adjustment or cutting off high values at some threshold, in both cases I try to target a particular gini distribution for the displayed colors.

The results aren't very good though. I feel that the gamma transforms don't really approximate the inverse CDF of my color distribution. I feel reluctant to use a purely local method, because I don't want to lose all absolute contrast difference. Are there other parametric transforms I could try?

I suggest you try tone mappings which are either logarithmic or evenly quantiled with respect to the probability densities, as opposed to linear, in order to make the non-spiky portions of the pdf surface stand out more. I've included examples of all three styles (linear, logarithmic, and quantiled) below for side-by-side comparison. Example R code to produce them is appended below. Furthermore, you may wish to plot the probability density itself on log scale as well (i.e., make the vertical dimension on a 3D surface plot log scale, while keeping x and y linear) although I haven't bothered to illustrate that here.

library(matlab) # Defines jet.colors()

# Common viewing angle for 3D surface plots
el = 7
az = -35
# Define 2D grid
par(bg = "white")
x <- seq(-20, 20, length = 51)
y <- seq(-20, 20, length = 51)

# Example function to plot, containing large central spike and small
# peripheral ripples
z <- outer(x, y, function(a, b) sin(sqrt(a**2+b**2))**2/(a**2+b**2))
nrz <- nrow(z)
ncz <- ncol(z)
# Generate the desired number of colors from this palette
ncol <- 100
color <- jet.colors(ncol)
# Compute the z-value at the facet centres
zfacet <- (z[-1, -1] + z[-1, -ncz] + z[-nrz, -1] + z[-nrz, -ncz]) / 4

# Recode facet z-values into linear color indices
linfacetcol <- cut(zfacet, ncol)
persp(x, y, z, col = color[linfacetcol], phi = el, theta = az,
main="Linear Tone Map")

dev.new()
par(bg = "white")
# Recode facet z-values into logarithmic color indices
logfacetcol <- cut(log(zfacet), ncol)
persp(x, y, z, col = color[logfacetcol], phi = el, theta = az,
main="Logarithmic Tone Map")

dev.new()
par(bg = "white")
# Recode facet z-values into quantiled color indices
qntfacetcol <- cut(zfacet, quantile(zfacet, prob=seq(0, 1, 0.01), na.rm=TRUE))
persp(x, y, z, col = color[qntfacetcol], phi = el, theta = az,
main="Quantiled Tone Map")

• There are areas of zero or very low probability in my density. Log doesn't really help with that. Commented Dec 20, 2013 at 13:34
• That's why I also included the quantiled version; it doesn't suffer from the same problem. As an alternative, for the areas where probability is precisely zero (and thus a log transform would be ill-defined) you can always reassign the zeros to some very small value (e.g., 1e-9 or something like that). Alternatively, depending on what analysis/display package you are using, you may also have the option to just leave those points as NaN ("Not a Number", which is a code that some packages use to signify undefined values), and the surface renderer may choose not to draw a facet at that point. Commented Dec 20, 2013 at 13:42
• Even if I assign a very small value, I might have a very large range. As for the quantiled version, it would completely erase any sense of relative contrast, a bit too strong for my taste as indicated in the question. Commented Dec 20, 2013 at 17:17
• O.K., then I guess I'm not really sure I understand your objection. It might help if you edited your question to post your own example image, as I've done, so that others can get a better sense of where exactly you are having trouble. Commented Dec 20, 2013 at 17:23