Cris’ Image Analysis Blog

theory, methods, algorithms, applications

DIPlib 3.2.0 released

We made a new release for DIPlib this week, version 3.2.0 (see the change log). There is some added functionality to the C++ library, such as the ability to render text in an image, but otherwise it’s mostly smaller usability tweaks and bug fixes. The biggest changes were to the Python bindings, PyDIP. Of these, the changes to dip.Histogram() and dip.LookupTable() are the most important ones, because they break backwards compatibility. We aim at keeping old code running with newer versions of DIPlib, but this was a necessary change.

In previous versions, dip.Histogram() and dip.LookupTable() were functions that used the dip::Histogram and the dip::LookupTable classes from the C++ library. This seemed convenient at the time, but turned out to be very limiting. The C++ classes are now mapped to Python, opening up a whole host of functionality that wasn’t available from Python previously. An additional benefit is that the documentation for the C++ library can now be used by Python users to understand how to use the library functionality from within Python.

What this means for old code is that dip.Histogram() no longer returns a tuple with an array of bin values and an array of bin centers, but returns a dip.Histogram object. Likewise, dip.LookupTable() no longer is a function that applies a lookup table to an image, returning an image, but is a constructor that returns a dip.LookupTable object.

To keep old code working there are two options:

  1. After importing the diplib package, assign dip.Histogram = dip.Histogram_old and/or dip.LookupTable = dip.LookupTable_old, to recover the behavior of previous versions.

  2. Rewrite your code to use the new classes. Below I’ll demonstrate how these classes can be used, and what the advantages are.

dip.Histogram

The dip.Histogram class has constructors similar to those in the C++ library, using an object of class dip.Histogram.Configuration, or an array of them for multi-dimensional histograms, to define the histogram parameters. In C++, we can construct a histogram as follows:

dip::Histogram hist1( img, {}, { 0.0, 255.0, 100 } );
dip::Histogram hist2( img, {}, { 0.0, 255.0, 1.7 } );

resulting in a histogram hist1 with bounds [0.0, 255.0] and 100 bins, and a histogram hist2 with the same bounds, and a bin width of 1.7 (it is the distinction between an integer and a float that decides how the third number is interpreted). In Python the equivalent constructors could be called as follows:

hist1 = dip.Histogram(img, None, dip.Histogram.Configuration(lowerBound=0, upperBound=255, nBins=100))
hist2 = dip.Histogram(img, None, dip.Histogram.Configuration(lowerBound=0, upperBound=255, binSize=1.7))

This is obviously much more verbose. This is why we added a constructor that takes the parameters as individual values:

hist1 = dip.Histogram(img, bounds=(0, 255), nBins=100)

This constructor cannot be used when one wants to set the bin size explicitly. Also when other configuration options are needed, an explicit dip.Histogram.Configuration object should be constructed. On the other hand, the dip.Histogram.Configuration object is useful when constructing multiple histograms that must be comparable.

Old code that looked like this:

hist, bins = dip.Histogram(img, nBins=100)  # old syntax
plt.plot(bins, hist)  # import matplotlib.pyplot as plt

can now be written as:

hist = dip.Histogram(img, nBins=100)
plt.plot(hist.BinCenters(), hist.GetImage())

or simply as:

dip.Histogram(img, nBins=100).Show()

Besides methods to inspect the histogram, there are methods like Cumulative(), and Smooth() that transform the histogram, or GetMarginal() that returns the marginal histogram for any one of its dimensions. There is also a long list of free functions to compute statistics from the histogram, to compute thresholds, and to do clustering.

For example, this is the way to apply k-means clustering to the intensities of an image:

img = dip.ImageRead('DIP.tif')  # from here: https://github.com/DIPlib/diplib/blob/master/examples/DIP.tif
img.Show()
hist = dip.Histogram(img, nBins=100)
hist = dip.KMeansClustering(hist, 3)  # 3 clusters
lab = hist.ReverseLookup(img)
lab.Show('labels')

(note that the function dip.KMeansClustering, when applied to an image, clusters the image spatially, not its intensities as we did here.)

Input to code above Output of code above

There are also two functions, dip.EqualizationLookupTable() and dip.MatchingLookupTable(), that return a dip.LookupTable object from respectively one and two input histograms.

dip.LookupTable

The dip.LookupTable class is much simpler than the dip.Histogram class. There is one constructor, which takes an image or NumPy array as input, and optionally also corresponding indices. The image must be 1D, but can have multiple samples per pixel. There are some methods that change the lookup table’s behavior with respect to out of bounds lookups, but the most important method is Apply.

Here we use the dip.LookupTable class to paint each label in the lab image we created above with the average color of each

msr = dip.MeasurementTool.Measure(lab, img, ['Mean'])
colors = dip.Image(msr['Mean'], tensor_axis=1)
colors.SetColorSpace('srgb')
lut = dip.LookupTable(colors, [1,2,3])  # indices in lab start at 1
lut.Convert('UINT8')
out = lut.Apply(lab)
out.Show()

Output of code above

Although the above could have been done just as easily with the dip.LookupTable() function that existed previously, it is convenient to have the lookup table be an object because it can be applied repeatedly, to many images. Also, we now can make functions such as dip.EqualizationLookupTable available in Python. This function can be used to create a lookup table that will equalize (flatten) the histogram of an image. This would be interesting over simply applying the function dip.HistogramEqualization() because it makes it possible to examine the mapping applied, and apply the same mapping to other images. For example,

img = dip.ImageRead('examples/trui')   # from here: https://github.com/DIPlib/diplib/blob/master/examples/trui.ics
hist = dip.Histogram(img)
lut = dip.EqualizationLookupTable(hist)
imgeq = lut.Apply(img1)  # histogram equalized
lut.Apply(np.arange(0, 256)).Show()

Mapping visualized

img2 = dip.ImageRead('examples/cermet')  # from here: https://github.com/DIPlib/diplib/blob/master/examples/cermet.ics
img2eq = lut.Apply(img2)  # histogram mapped in the same way, not equalized

Questions or comments on this topic?
Join the discussion on LinkedIn