qupath.opencv.processing.OpenCVTools.java Source code

Java tutorial

Introduction

Here is the source code for qupath.opencv.processing.OpenCVTools.java

Source

/*-
 * #%L
 * This file is part of QuPath.
 * %%
 * Copyright (C) 2014 - 2016 The Queen's University of Belfast, Northern Ireland
 * Contact: IP Management (ipmanagement@qub.ac.uk)
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */

package qupath.opencv.processing;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;

/**
 * Collection of static methods to help with using OpenCV from Java.
 * 
 * @author Pete Bankhead
 *
 */
public class OpenCVTools {

    /**
     * Convert an RGB image to an OpenCV Mat.
     * 
     * @param img
     * @return
     */
    public static Mat imageToMat(BufferedImage img) {
        //      img.getRaster().getDataBuffer()
        //      img.getRaster().getDataBuffer().getDataType()
        //      byte[] data = ((DataBufferByte) img.getRaster().getDataBuffer()).getData();
        int[] data = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();

        ByteBuffer byteBuffer = ByteBuffer.allocate(data.length * 4);
        IntBuffer intBuffer = byteBuffer.asIntBuffer();
        intBuffer.put(data);
        byte[] dataBytes = byteBuffer.array();

        //      byte[] dataBytes = new byte[data.length * 4];
        //      int j = 0;
        //       for (int x : data) {
        //          dataBytes[j] = (byte) ((x >>> 0) & 0xff);           
        //          dataBytes[j] = (byte) ((x >>> 8) & 0xff);
        //          dataBytes[j] = (byte) ((x >>> 16) & 0xff);
        //          dataBytes[j] = (byte) ((x >>> 24) & 0xff);
        //           j++;
        //       }

        //      Mat mat = new Mat(img.getHeight(), img.getWidth(), CvType.CV_8UC3);
        Mat mat = new Mat(img.getHeight(), img.getWidth(), CvType.CV_8UC4);
        mat.put(0, 0, dataBytes);
        return mat;
    }

    public static void labelImage(Mat matBinary, Mat matLabels, int contourType) {
        List<MatOfPoint> contours = new ArrayList<>();
        Mat hierarchy = new Mat();
        Imgproc.findContours(matBinary, contours, hierarchy, contourType, Imgproc.CHAIN_APPROX_SIMPLE);
        // It's convoluted, but drawing contours this way is *much* faster than passing the full list (which is copied by the OpenCV 2.4.9 Java code)
        List<MatOfPoint> temp = new ArrayList<>(1);
        int i = 2;
        int ind = 0;
        for (MatOfPoint contour : contours) {
            temp.clear();
            temp.add(contour);
            Imgproc.drawContours(matLabels, temp, 0, new Scalar(i++), -1, 8, hierarchy.col(ind), 2,
                    new Point(0, 0));
            //         Imgproc.drawContours(matLabels, temp, 0, new Scalar(i++), -1);
            ind++;
        }
    }

    public static void watershedDistanceTransformSplit(Mat matBinary, int maxFilterRadius) {
        Mat matWatershedSeedsBinary;

        // Create a background mask
        Mat matBackground = new Mat();
        Core.compare(matBinary, new Scalar(255), matBackground, Core.CMP_NE);

        // Separate by shape using the watershed transform
        Mat matDistanceTransform = new Mat();
        Imgproc.distanceTransform(matBinary, matDistanceTransform, Imgproc.CV_DIST_L2,
                Imgproc.CV_DIST_MASK_PRECISE);
        // Find local maxima
        matWatershedSeedsBinary = new Mat();
        Imgproc.dilate(matDistanceTransform, matWatershedSeedsBinary,
                OpenCVTools.getCircularStructuringElement(maxFilterRadius));
        Core.compare(matDistanceTransform, matWatershedSeedsBinary, matWatershedSeedsBinary, Core.CMP_EQ);
        matWatershedSeedsBinary.setTo(new Scalar(0), matBackground);
        // Dilate slightly to merge nearby maxima
        Imgproc.dilate(matWatershedSeedsBinary, matWatershedSeedsBinary,
                OpenCVTools.getCircularStructuringElement(2));

        // Create labels for watershed
        Mat matLabels = new Mat(matDistanceTransform.size(), CvType.CV_32F, new Scalar(0));
        labelImage(matWatershedSeedsBinary, matLabels, Imgproc.RETR_CCOMP);

        // Remove everything outside the thresholded region
        matLabels.setTo(new Scalar(0), matBackground);

        // Do watershed
        // 8-connectivity is essential for the watershed lines to be preserved - otherwise OpenCV's findContours could not be used
        ProcessingCV.doWatershed(matDistanceTransform, matLabels, 0.1, true);

        // Update the binary image to remove the watershed lines
        Core.multiply(matBinary, matLabels, matBinary, 1, matBinary.type());
    }

    public static Mat getCircularStructuringElement(int radius) {
        Mat strel = new Mat(radius * 2 + 1, radius * 2 + 1, CvType.CV_8UC1, new Scalar(0));
        Imgproc.circle(strel, new Point(radius, radius), radius, new Scalar(1), -1);
        return strel;
    }

    /*
     * Invert a binary image.
     * Technically, set all zero pixels to 255 and all non-zero pixels to 0.
     */
    public static void invertBinary(Mat matBinary, Mat matDest) {
        Core.compare(matBinary, new Scalar(0), matDest, Core.CMP_EQ);
    }

    /**
     * Fill holes in a binary image (1-channel, 8-bit unsigned) with an area <= maxArea.
     * 
     * @param matBinary
     * @param maxArea
     */
    public static void fillSmallHoles(Mat matBinary, double maxArea) {
        Mat matHoles = new Mat();
        invertBinary(matBinary, matHoles);
        List<MatOfPoint> contours = new ArrayList<>();
        Mat hierarchy = new Mat();
        Imgproc.findContours(matHoles, contours, hierarchy, Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE);
        List<MatOfPoint> contoursTemp = new ArrayList<>(1);
        Scalar color = new Scalar(255);
        int ind = 0;
        for (MatOfPoint contour : contours) {
            // Only fill the small, inner contours
            if (hierarchy.get(0, ind)[3] >= 0 || Imgproc.contourArea(contour) > maxArea) {
                ind++;
                continue;
            }
            contoursTemp.clear();
            contoursTemp.add(contour);
            Imgproc.drawContours(matBinary, contoursTemp, 0, color, -1);
            ind++;
        }
    }

    /**
     * Apply a watershed transform to refine a binary image, guided either by a distance transform or a supplied intensity image.
     * 
     * @param matBinary - thresholded, 8-bit unsigned integer binary image
     * @param matIntensities - optional intensity image for applying watershed transform; if not set, distance transform of binary will be used
     * @param threshold
     */
    public static void watershedIntensitySplit(Mat matBinary, Mat matWatershedIntensities, double threshold,
            int maximaRadius) {

        // Separate by intensity using the watershed transform
        // Find local maxima
        Mat matTemp = new Mat();

        Mat strel = getCircularStructuringElement(maximaRadius);
        Imgproc.dilate(matWatershedIntensities, matTemp, strel);
        Core.compare(matWatershedIntensities, matTemp, matTemp, Core.CMP_EQ);
        Imgproc.dilate(matTemp, matTemp, getCircularStructuringElement(2));
        Mat matWatershedSeedsBinary = matTemp;

        // Remove everything outside the thresholded region
        Core.min(matWatershedSeedsBinary, matBinary, matWatershedSeedsBinary);

        // Create labels for watershed
        Mat matLabels = new Mat(matWatershedIntensities.size(), CvType.CV_32F, new Scalar(0));
        labelImage(matWatershedSeedsBinary, matLabels, Imgproc.RETR_CCOMP);

        // Do watershed
        // 8-connectivity is essential for the watershed lines to be preserved - otherwise OpenCV's findContours could not be used
        ProcessingCV.doWatershed(matWatershedIntensities, matLabels, threshold, true);

        // Update the binary image to remove the watershed lines
        Core.multiply(matBinary, matLabels, matBinary, 1, matBinary.type());
    }

}