26 August 2014

OpenCV, Java and CLAHE

As it seems that the OpenCV guys have missed porting the creation of the Contrast Limited Adaptive Histogram Equalization algorithm to Java in OpenCV 2.4.9, so my colleage developer Michael Niephaus and myself have implemented a version in Java using the objects from the OpenCV  project.

import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Range;
import org.opencv.core.Rect;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

public class CLAHE {

private final static int BUFFER_SIZE = 256;
private final double clipLimit_;
private final int tilesX_;
private final int tilesY_;
Mat lut_ = new Mat();

public CLAHE() {
this(40, 8, 8);
}

public static int saturateCast(int x) {
return x > BUFFER_SIZE - 1 ? BUFFER_SIZE - 1 : (x < 0 ? 0 : x);
}

public static int saturateCast(float x) {
return (int) (x > BUFFER_SIZE - 1 ? BUFFER_SIZE - 1 : (x < 0 ? 0 : x));
}

public int GCD(int a, int b) {
return b == 0 ? a : GCD(b, a % b);
}

public CLAHE(double clipLimit, int tilesX, int tilesY) {
this.clipLimit_ = clipLimit;
this.tilesX_ = tilesX;
this.tilesY_ = tilesY;
}

public Mat apply(Mat src) {
if (src.type() != CvType.CV_8UC1) {
throw new IllegalArgumentException("Mat not of type CV_8UC1!");
}
Mat dst = new Mat(src.size(), src.type());
lut_.create(tilesX_ * tilesY_, BUFFER_SIZE, CvType.CV_8UC1);

Size tileSize;
Mat srcForLut;

if (src.cols() % tilesX_ == 0 && src.rows() % tilesY_ == 0) {
tileSize = new Size(src.cols() / tilesX_, src.rows() / tilesY_);
srcForLut = src;
} else {
Mat srcExt_ = new Mat();
Imgproc.copyMakeBorder(src, srcExt_, 0, tilesY_ - (src.rows() % tilesY_), 0, tilesX_ - (src.cols() % tilesX_), Imgproc.BORDER_REFLECT_101);
tileSize = new Size(srcExt_.cols() / tilesX_, srcExt_.rows() / tilesY_);
srcForLut = srcExt_;
}

double tileSizeTotal = tileSize.area(); // int ?
float lutScale = (float) ((BUFFER_SIZE - 1) / tileSizeTotal); // why BUFFER_SIZE - 1 ?
int clipLimit = 0;
if (clipLimit_ > 0.0) {
clipLimit = (int) (clipLimit_ * tileSizeTotal / BUFFER_SIZE);
if (clipLimit < 1) {
clipLimit = 1;
}
}
CLAHE_CalcLut_Body calcLutBody = new CLAHE_CalcLut_Body(srcForLut, lut_, tileSize, tilesX_, clipLimit, lutScale);
calcLutBody.execute(new Range(0, tilesX_ * tilesY_));

CLAHE_Interpolation_Body interpolationBody = new CLAHE_Interpolation_Body(src, dst, lut_, tileSize, tilesX_, tilesY_);
interpolationBody.execute(new Range(0, src.rows()));

return dst;
}

private class CLAHE_Interpolation_Body {

Mat src_;
Mat dst_;
Mat lut_;
Size tileSize_;
int tilesX_;
int tilesY_;

CLAHE_Interpolation_Body(Mat src, Mat dst, Mat lut_, Size tileSize, int tilesX_, int tilesY_) {
this.src_ = src;
this.dst_ = dst;
this.lut_ = lut_;
this.tileSize_ = tileSize;
this.tilesX_ = tilesX_;
this.tilesY_ = tilesY_;
}

void execute(Range range) {
int lut_step = (int) lut_.step1();
int lut_break = tilesX_ * lut_step;

for (int y = range.start; y < range.end; ++y) {

float tyf = (y / (float) tileSize_.height) - 0.5f;
int ty1 = (int) Math.floor(tyf);
int ty2 = ty1 + 1;
float ya = tyf - ty1;
// keep largest
if (ty1 < 0) {
ty1 = 0;
}
// keep smallest
if (ty2 > tilesY_ - 1) {
ty2 = tilesY_ - 1;
}

int lutPlane1 = ty1 * tilesX_;
int lutPlane2 = ty2 * tilesX_;

for (int x = 0; x < src_.cols(); x++) {

float txf = (x / (float) tileSize_.width) - 0.5f;
int tx1 = (int) Math.floor(txf);
int tx2 = tx1 + 1;
float xa = txf - tx1;
// keep largest
if (tx1 < 0) {
tx1 = 0;
}
// keep smallest
if (tx2 > tilesX_ - 1) {
tx2 = tilesX_ - 1;
}
// original pixel value
double[] ptr = src_.get(y, x);
int srcVal = (int) ptr[0];

int ind1 = tx1 * lut_step + srcVal;
int ind2 = tx2 * lut_step + srcVal;

int column1 = (ind1 + (ty1 * lut_break)) % lut_step;
int row1 = (ind1 + (ty1 * lut_break)) / lut_step;

int column2 = (ind2 + (ty1 * lut_break)) % lut_step;
int row2 = (ind2 + (ty1 * lut_break)) / lut_step;

int column3 = (ind1 + (ty2 * lut_break)) % lut_step;
int row3 = (ind1 + (ty2 * lut_break)) / lut_step;

int column4 = (ind2 + (ty2 * lut_break)) % lut_step;
int row4 = (ind2 + (ty2 * lut_break)) / lut_step;

float res = 0;

double[] lut_ptr1 = lut_.get(row1, column1);
res += ((byte) lut_ptr1[0] & 0xFF) * ((1.0f - xa) * (1.0f - ya));

double[] lut_ptr2 = lut_.get(row2, column2);
res += ((byte) lut_ptr2[0] & 0xFF) * ((xa) * (1.0f - ya));

double[] lut_ptr3 = lut_.get(row3, column3);
res += ((byte) lut_ptr3[0] & 0xFF) * ((1.0f - xa) * (ya));

double[] lut_ptr4 = lut_.get(row4, column4);
res += ((byte) lut_ptr4[0] & 0xFF) * ((xa) * (ya));

dst_.put(y, x, saturateCast(res));
}
}
}
}

private class CLAHE_CalcLut_Body {

Mat src_;
Mat lut_;
Size tileSize_;
int tilesX_;
int clipLimit_;
float lutScale_;

CLAHE_CalcLut_Body(Mat srcForLut, Mat lut_, Size tileSize, int tilesX_, int clipLimit, float lutScale) {
this.src_ = srcForLut;
this.lut_ = lut_;
this.tileSize_ = tileSize;
this.tilesX_ = tilesX_;
this.clipLimit_ = clipLimit;
this.lutScale_ = lutScale;
}

void execute(Range range) {
int[] tileHist;
int[] lutBytes = new int[lut_.height() * lut_.width()];
for (int k = range.start; k < range.end; ++k) {
int ty = k / tilesX_;
int tx = k % tilesX_;
// retrieve tile submatrix
Rect tileROI = new Rect();
tileROI.x = (int) (tx * tileSize_.width);
tileROI.y = (int) (ty * tileSize_.height);
tileROI.width = (int) tileSize_.width;
tileROI.height = (int) tileSize_.height;
Mat tile = src_.submat(tileROI);
// calc histogram
tileHist = new int[BUFFER_SIZE];
int height = tileROI.height;

for (int h = height; h > 0; h--) {
int x;
double[] ptr;
for (int w = 0; w < tileROI.width; w++) {
ptr = tile.get(h - 1, w);
tileHist[(int) ptr[0]]++;
}
}
// clip histogram
if (clipLimit_ > 0) {
// how many pixels were clipped
int clipped = 0;
for (int i = 0; i < BUFFER_SIZE; ++i) {
if (tileHist[i] > clipLimit_) {
clipped += tileHist[i] - clipLimit_;
tileHist[i] = clipLimit_;
}
}
// redistribute clipped pixels
int redistBatch = clipped / BUFFER_SIZE;
int residual = clipped - redistBatch * BUFFER_SIZE;
for (int i = 0; i < BUFFER_SIZE; ++i) {
tileHist[i] += redistBatch;
}
for (int i = 0; i < residual; ++i) {
tileHist[i]++;
}
}
// calc Lut
int sum = 0;
for (int i = 0; i < BUFFER_SIZE; ++i) {
sum += tileHist[i];
lut_.put(k, i, saturateCast(Math.round(sum * lutScale_)));
}
}
}
}
}

//  DISCLAIMER :
// This software is provided "as is".
// Any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the blog writer be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.

4 January 2014

OpenCV CMake Java Maven OSGi toolchain

OpenCV is a cross platform computer vision library written in c / c++ with a Java interface. The first thing to solve is the generation and compilation of the sources using CMake and Maven. The Maven plugin  cmake-maven-project does great things here. Now we can add this as a dependency to our OpenCV Maven project and use it for cross platform compilation.
Download the OpenCV sources from github and add them to your OpenCV Maven project. The OpenCV-CMake make-files produce, depending on platform compiling, a .dll or .so file and the Java source files. These are copied into src/main/resources and src/main/java respectively. Now we are ready for the Maven Bundle plugin.
OSGi has a easy way to resolve dependencies on native code called the bundle native code tag. Point this to the .dll / .so file in resources. Add a bundle activate class to the package, calling System.loadLibrary, and your of deploying OpenCV to OSGi.

2 October 2013

Automate p2 Eclipse plugin transfer to Maven

In my last post i discribed a way to transfer Eclsipe p2 plugins to a Maven repository. To automate this in a Maven project one can setup a pom like this:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>your-group-id</groupId>
    <artifactId>your-artifact-id</artifactId>
    <version>1.0.0</version>
    <name>your-name</name>
    <packaging>pom</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

     <build>
        <plugins>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <inherited>false</inherited>
                <executions>
                    <execution>
                        <id>copy-eclipse-resources-to-maven</id>
                        <phase>process-resources</phase>
                        <goals>
                            <goal>to-maven</goal>
                        </goals>
                        <configuration>
                            <eclipseDir>${basedir}/src/main/config/eclipse</eclipseDir>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>2.4.0</version>
                <inherited>false</inherited>
                <executions>
                    <execution>
                        <id>obr-indexing</id>
                        <phase>install</phase>
                        <configuration>
                            <urlTemplate>maven</urlTemplate>
                        </configuration>
                        <goals>
                            <goal>clean</goal>
                            <goal>index</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

After the transfer the maven bundle plugin will do an indexing so all plugins are also available in the Object Bundle Repository.

29 September 2013

Using Eclipse plugins in a Equinox - Karaf setup.

Using a modular standard like OSGi promises manny goodies, even the ability the swap out the core implementing the OSGi model and deploying bundles on different OSGi implementations ... but not quite. Every implementation brings in its own background like provisioning, restricting things even more. But now i am getting ahead of myself, back to the beginning.

My main OSGi (development) platform so far is Felix wrapped in a Karaf container backed by Maven for provisioning. Now i had to implement the Open Health Tools Model-Driven Health Tools (MDHT) which, as i was about to find out, is Eclipse based and thus uses the provisioning system called p2.

My first thought was to retrieve all the MDHT sources and convert them to Maven projects, there was even a standalone setup provided which proved functional. Looking into the source i discovered MDHT makes heavy use of the Eclipse Modeling Framework (EMF) and the Eclipse Object Constraint Language (OCL), powerful and non trivial frameworks with a lot of dependencies of their own that are mostly not even Mavenized. The deployment on my favorite OSGi combo was bound to fail, i needed an environment matching that of Eclipse.

Then i remembered that Karaf could swap out Felix, and guess for what: Equinox! Simply edit the etc/config.properties file and modify the karaf.framework property to equinox and finished. This leaves me with the provisioning problem since Karaf has a preference for Maven. Converting the sources myself for such big frameworks is not an option for obvious reasons and after some digging i found myself looking at the Maven website: http://maven.apache.org/plugins/maven-eclipse-plugin/.
MDHT is also a modeling tool for Clinical Document Architecture (CDA) templates based on Eclipse, and thus Equinox, meaning it exists out of Eclipse plugins provisioned by p2.
Using the Maven Eclipse Plugin goal eclipse:to-maven i was able to copy the MDHT Eclipse artifacts out of p2 to my local Maven repository and after a re-indexing by the OSGi Bundle Repository (OBR) i finally could install EMF and OCL on my Equinox - Karaf combo. And of course one can now use the Eclipse artifacts in any Maven project.

More practical experiences are bound to follow.

10 July 2013

Monitoring OSGi services and bundles

Keeping track of OSGi services and bundles can be a tedious task. Declerative Services provide a good degree of control here, but for fine grain control the OSGi api presents the org.osgi.util.tracker package.
With the ServiceTracker and BundleTracker classes it is really easy to monitor your enviroment for add, modify or remove events. It provides filtering capabillities trough the customizer interfaces and keeps a record of all registred objects being tracked.
This is also the perfect location to hook into these events, like for updating any logging to be done on the bundles or services in your framework.
The bruise to be avoided here is the addingService / Bundle methodes. These normally return the object that raised the event for the add methode. However, this can be overridden in a subclass to customize the object to be tracked for the one just being added!

26 June 2013

Fuse IDE JMX Explorer not enlisting running jvm's

This is a nice bruise that took me some time to solve. After installing a new jdk and switching Fuse IDE 6.0  to the new installed JRE under Preferences / Java / Installed JRE's, one has to remember that there is a setting under Fuse Plugins for Eclipse / JMX Explorer / Tools that has to be set aswell so the processes to be monitored are running on the same VM as the IDE.
Otherwise no JMX servers get enlisted.