Commit 1cf07545 authored by Manuel Gunkel's avatar Manuel Gunkel
Browse files

added template matching utility

parent 8b826a93
......@@ -4,14 +4,8 @@
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>
<!--
<parent>
<groupId>net.imagej</groupId>
<artifactId>pom-imagej</artifactId>
<version>5.0</version>
<relativePath />
</parent>
-->
<!-- <parent> <groupId>net.imagej</groupId> <artifactId>pom-imagej</artifactId>
<version>5.0</version> <relativePath /> </parent> -->
<groupId>embl.almf</groupId>
<artifactId>AutoMicTools_</artifactId>
<version>1.1.18-SNAPSHOT</version>
......@@ -29,14 +23,8 @@
<distribution>repo</distribution>
</license>
</licenses>
<!--
<parent>
<groupId>org.scijava</groupId>
<artifactId>pom-scijava</artifactId>
<version>12.0.0</version>
<relativePath />
</parent>
-->
<!-- <parent> <groupId>org.scijava</groupId> <artifactId>pom-scijava</artifactId>
<version>12.0.0</version> <relativePath /> </parent> -->
<dependencies>
<dependency>
......@@ -83,7 +71,7 @@
<groupId>com.github.ssgpers</groupId>
<artifactId>CommMicroscope</artifactId>
</dependency>
<dependency>
<groupId>de.embl.cba</groupId>
<artifactId>java-utils-embl</artifactId>
......@@ -125,14 +113,20 @@
<artifactId>xmlrpc-client</artifactId>
</dependency>
<dependency>
<groupId>sc.fiji</groupId>
<groupId>sc.fiji</groupId>
<artifactId>TransformJ_</artifactId>
</dependency>
<dependency>
<groupId>org.ilastik</groupId>
<artifactId>ilastik4ij</artifactId>
<artifactId>ilastik4ij</artifactId>
</dependency>
<!-- OPEN-CV dependency for template matching: -->
<dependency>
<groupId>io.github.joheras</groupId>
<artifactId>IJ-OpenCV</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies>
<developers>
......@@ -155,7 +149,7 @@
<timezone>+1</timezone>
</developer>
</developers>
<repositories>
<!-- for Tischi dependencies -->
<repository>
......@@ -164,28 +158,28 @@
<url>https://embl-cba.bintray.com/snapshots</url>
</repository>
<repository>
<id>imagej.public</id>
<url>http://maven.imagej.net/content/groups/public</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
<id>imagej.public</id>
<url>http://maven.imagej.net/content/groups/public</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repository>
<repository>
<id>imagej.releases</id>
<url>http://maven.imagej.net/content/repositories/releases</url>
</repository>
<repository>
<repository>
<id>imagej.snapshots</id>
<url>http://maven.imagej.net/content/repositories/snapshots</url>
</repository>
</repositories>
<dependencyManagement>
<dependencies>
<dependency>
......@@ -193,7 +187,7 @@
<artifactId>ij</artifactId>
<version>1.52s</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
......@@ -306,14 +300,14 @@
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>sc.fiji</groupId>
<groupId>sc.fiji</groupId>
<artifactId>TransformJ_</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.ilastik</groupId>
<artifactId>ilastik4ij</artifactId>
<version>1.7.2</version>
<groupId>org.ilastik</groupId>
<artifactId>ilastik4ij</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
......@@ -356,55 +350,28 @@
<configuration>
<mainClass>console.ConsoleApplication</mainClass>
</configuration>
</plugin>
<!--
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/automic-dependencies</outputDirectory>
<outputDirectory>C:\automic-dependencies</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
-->
<!--
</plugin>
<!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId>
<executions> <execution> <id>copy-dependencies</id> <phase>prepare-package</phase>
<goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/automic-dependencies</outputDirectory>
<outputDirectory>C:\automic-dependencies</outputDirectory> <overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots> <overWriteIfNewer>true</overWriteIfNewer>
</configuration> </execution> </executions> </plugin> -->
<!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId>
<configuration> <archive> <manifest> <mainClass>automic.postaq.protocols.ConsoleApp</mainClass>
<addClasspath>true</addClasspath> <classpathPrefix>automic-dependencies/</classpathPrefix>
</manifest> </archive> </configuration> </plugin> -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<archive>
<manifest>
<mainClass>automic.postaq.protocols.ConsoleApp</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>automic-dependencies/</classpathPrefix>
</manifest>
</archive>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
......
package automic.utils.matchTemplate;
//Java imports:
import java.awt.Polygon;
import java.io.File;
import java.util.ArrayList;
//AutoMic Tools imports:
import automic.utils.imagefiles.ImageOpenerWithBioformats;
//ImageJ imports:
import ij.IJ;
import ij.ImagePlus;
import ij.gui.RotatedRectRoi;
import ij.plugin.filter.MaximumFinder;
import ij.plugin.frame.RoiManager;
import ij.process.ImageConverter;
import ij.process.ImageProcessor;
//OpenCV imports:
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacpp.opencv_imgproc;
import ijopencv.ij.ImagePlusMatConverter;
import ijopencv.opencv.MatImagePlusConverter;
/**
* Implements the template matching routine from OpenCV.
* Additionally, a template can be rotated and flipped horizontally or vertically. The resulting matching coordinates can afterwards be filtered.
* @author Manuel Gunkel
*
*/
public class MatchTemplate {
//prominence and strict values for the maximum finder:
private double prominence =10;
private boolean strict = false;
/*** Method for the template matching. The following methods are available:
* 0: TM_SQDIFF
* 1: TM_SQDIFF_NORMED
* 2: TM_CCORR
* 3: TM_CCORR_NORMED
* 4: TM_CCOEFF
* 5: TM_CCOEFF_NORMED
*/
private int method = 5;
public ArrayList<Integer> positions_x = new ArrayList<>();
public ArrayList<Integer> positions_y = new ArrayList<>();
public ArrayList<Double> intensity_match = new ArrayList<>();
public ArrayList<Boolean> is_fipped_hor = new ArrayList<>();
public ArrayList<Boolean> is_flipped_ver = new ArrayList<>();
public ArrayList<Double> angle_rot = new ArrayList<>();
public ArrayList<Boolean> isvalidxy = new ArrayList<>();
public ArrayList<Boolean> isvalidint = new ArrayList<>();
public ArrayList<String> ROIs = new ArrayList<>();
private RoiManager rm = new RoiManager();
/*** Todo:
*save ROIs as ArrayList
*/
/***
* Implementation of the OpenCV template matching routine. Images are converted, 'match' should be larger than 'template'.
* @param match Image in which the temlate should be identified
* @param template Image which should be identified in the bigger 'match' image.
* @param method Method used for the template matching (0-5)
* 0: TM_SQDIFF
* 1: TM_SQDIFF_NORMED
* 2: TM_CCORR
* 3: TM_CCORR_NORMED
* 4: TM_CCOEFF
* 5: TM_CCOEFF_NORMED
* @return result Correlation image between 'match' and 'template'. If W x H are the dimensions of 'match' and
* w x h the dimensions of 'template, 'result has the dimensions (W - w + 1) x (H - h + 1).
* @throws Exception
*/
public ImagePlus matchTemplate(ImagePlus match, ImagePlus template, int method ) throws Exception {
//get bit depth of the images and convert:
int matchbd = match.getBitDepth();
int templatebd = template.getBitDepth();
// OpenCV can only handle 8bit or 32 bit. check if both images are 8bit, otherwise convert:
if (!(matchbd == 8 && templatebd == 8)) {
match = MatchTemplate.convertTo32bit(match);
template = MatchTemplate.convertTo32bit(template);;
}
// Convert the ImageJ images to OpenCV images
Mat ocv_match = MatchTemplate.convertToMat(match);
Mat ocv_template = MatchTemplate.convertToMat(template);
Mat ocv_result = new Mat();
// do the matching
opencv_imgproc.matchTemplate(ocv_match, ocv_template, ocv_result, method);
//convert the result to ImagePlus:
ImagePlus result = MatImagePlusConverter.toImagePlus(ocv_result);
return result;
}
/**
* Implements the ImageJ Find Maxima command on the resulting correlation image in order to get the positions of the template
* @param TMimage Correlation image resulting from the matchTemplate call.
* @return maxima Polygon containing all identified coordinates
* @throws Exception
*/
public Polygon getPositions(ImagePlus TMimage) throws Exception {
MaximumFinder mf = new MaximumFinder();
Polygon maxima = mf.getMaxima(TMimage.getProcessor(), this.prominence, this.strict);
return maxima;
}
/**
* Shift the positions of the identified coordinates back to the 'match' image. For this, the dimensions of the 'template'
* image need to be known, since the correlation image is reduced by this size.
* @param template The 'template' image.
* @param pg Polygon with the identified coordinates.
* @return pg Polygon with the coordinates shifted to the original 'match' image.
* @throws Exception
*/
public Polygon transferPositions(ImagePlus template, Polygon pg) throws Exception {
double tw = template.getWidth();
int dx = (int) Math.ceil(tw/2);
double th = template.getHeight();
int dy = (int) Math.ceil(th/2);
pg.translate(dx,dy);
return pg;
}
/** Rotates the template by the given angles and performs a template match each time. Is repeated with flipped images if the flags are set.
* @param template_ The 'template' image.
* @param match_ The 'match' image.
* @param flip_hor Flag for flipping the template horizontally.
* @param flip_ver Flag for flipping the template vertically.
* @param alpha_start First angle by which the template should be rotated.
* @param alpha_step Rotation step size.
* @param alpha_stop Last angle by which the template is rotated.
* @throws Exception
*/
public void multiTemplateMatch(ImagePlus template_, ImagePlus match_, boolean flip_hor, boolean flip_ver, double alpha_start, double alpha_step, double alpha_stop) throws Exception {
//match the template unflipped:
templateMatchRotations(template_, match_, false, false, alpha_start, alpha_step, alpha_stop);
//match her template flipped vertically if flag is set:
if (flip_ver) {
ImagePlus template_fv = template_.duplicate();
ImageProcessor fv = template_fv.getProcessor();
fv.flipVertical();
templateMatchRotations(template_fv, match_, false, flip_ver, alpha_start, alpha_step, alpha_stop);
}
//match the template flipped horizontally if flag is set:
if (flip_hor) {
ImagePlus template_fh = template_.duplicate();
ImageProcessor fh = template_fh.getProcessor();
fh.flipHorizontal();
templateMatchRotations(template_fh, match_, flip_hor, false, alpha_start, alpha_step, alpha_stop);
}
// match_.show();
}
/** Rotates the template by the given angles and performs a template match each time.
* @param template_ The 'template' image.
* @param match_ The 'match' image.
* @param flip_hor Flag for flipping the template horizontally.
* @param flip_ver Flag for flipping the template vertically.
* @param alpha_start First angle by which the template should be rotated.
* @param alpha_step Rotation step size.
* @param alpha_stop Last angle by which the template is rotated.
* @throws Exception
*/
public void templateMatchRotations(ImagePlus template_, ImagePlus match_, boolean flip_hor, boolean flip_ver, double alpha_start, double alpha_step, double alpha_stop) throws Exception {
ImagePlus res;
ImagePlus testimage;
//iterate over all angles
for (double aha = alpha_start; aha<=alpha_stop; aha = aha+alpha_step) {
testimage = rotateImage(template_, aha);
res = matchTemplate(match_, testimage, this.method);
Polygon pg = getPositions(res);
int xpos = 0;
int ypos = 0;
// for each point identified, write the parameters to the instances respective ArrayList
for (int i = 0; i<pg.npoints; i++) {
xpos = pg.xpoints[i];
ypos = pg.ypoints[i];
// get correlation value from the correlation image:
double intensity = res.getProcessor().getValue(xpos, ypos);
//shift coordinates to the original 'match' image:
double tw = testimage.getWidth();
int dx_pos = (int) Math.ceil(tw/2);
double th = testimage.getHeight();
int dy_pos = (int) Math.ceil(th/2);
xpos = xpos+dx_pos;
ypos = ypos +dy_pos;
//get coordinates for the rotated ROI and set the rotated ROI:
double alpharoi = aha*Math.PI/180;
int a = template_.getWidth()/2;
int b = template_.getHeight();
double dx = Math.cos(alpharoi)*a;
double dy = -Math.sin(alpharoi)*a;
RotatedRectRoi nuroirot = new RotatedRectRoi(xpos-dx, ypos+dy, xpos+dx, ypos-dy, b);
//add all parameters to the respective ArrayList:
this.rm.add(match_, nuroirot, i);
this.positions_x.add(xpos);
this.positions_y.add(ypos);
this.intensity_match.add(intensity);
this.is_fipped_hor.add(flip_hor);
this.is_flipped_ver.add(flip_ver);
this.angle_rot.add(aha);
}
}
}
/** Rotates an image (creates a new rotated image). Calls the ImageJ 'Rotate ...' command.
* @param img_ Image to be rotated
* @param angle Angle by which the image should be rotated.
* @return The rotated image.
* @throws Exception
*/
public ImagePlus rotateImage(ImagePlus img_, double angle) throws Exception {
ImagePlus tmpimg = img_.duplicate();
IJ.run(tmpimg, "Rotate... ", "angle="+angle+" grid=-2 interpolation=Bilinear enlarge");
return tmpimg;
}
/** Filters the results. Locally, only the best matching (highest value in the correlation image) position is kept.
* A global threshold for the value of the found maxima can be set.
* @param radius local radius within which only the best matching template is regarded.
* @param corrthreshold threshold for the value of the maxima in the correlation image
* @throws Exception
*/
public void filterResults(double radius, double corrthreshold) throws Exception {
this.isvalidint.clear();
this.isvalidxy.clear();
//temporary Array lists for the filtered results:
ArrayList<Integer> positions_x_tmp = new ArrayList<>();
ArrayList<Integer> positions_y_tmp = new ArrayList<>();
ArrayList<Double> intensity_match_tmp = new ArrayList<>();
ArrayList<Boolean> is_fipped_hor_tmp = new ArrayList<>();
ArrayList<Boolean> is_flipped_ver_tmp = new ArrayList<>();
ArrayList<Double> angle_rot_tmp = new ArrayList<>();
// names of the ROIs to be kept
ArrayList<String> ROIs_tmp = new ArrayList<>();
// integer index of the ROIs to be kept
ArrayList<Integer> ROI_ind = new ArrayList<>();
//initialize variables;
double r = 0;
double dx = 0;
double dy = 0;
boolean local_max = true;
// Iterate over all coordinates
for (int i = 0; i<positions_x.size(); i++) {
this.ROIs.add(this.rm.getName(i));
// Check if the correlation value is above the threshold:
if (this.intensity_match.get(i) >= corrthreshold) {
isvalidint.add(true);
}
else isvalidint.add(false);
// Check if the coordinate is a local maximum of the correlation value compare with all other coordinates:
// (somewhat brute force, could'nt think of anything clever)
for (int j = 0; j<positions_x.size(); j++) {
dx = positions_x.get(i)-positions_x.get(j);
dy = positions_y.get(i)-positions_y.get(j);
r=Math.sqrt((dx*dx+dy*dy));
if (r<=radius) {
if (intensity_match.get(j)>intensity_match.get(i)) {
local_max = false;
}
}
}
isvalidxy.add(local_max);
local_max=true;
// add to temp lists if both criteria are fulfilled:
if (isvalidint.get(i) && isvalidxy.get(i)) {
positions_x_tmp.add(positions_x.get(i));
positions_y_tmp.add(positions_y.get(i));
intensity_match_tmp.add(intensity_match.get(i));
is_fipped_hor_tmp.add(is_fipped_hor.get(i));
is_flipped_ver_tmp.add(is_flipped_ver.get(i));
angle_rot_tmp.add(angle_rot.get(i));
ROIs_tmp.add(ROIs.get(i));
}
else ROI_ind.add(i);
}
// replace the original instance lists with the temp lists:
positions_x = positions_x_tmp;
positions_y = positions_y_tmp;
intensity_match = intensity_match_tmp;
is_fipped_hor = is_fipped_hor_tmp;
is_flipped_ver = is_flipped_ver_tmp;
angle_rot = angle_rot_tmp;
ROIs = ROIs_tmp;
int[] ROI_selected = MatchTemplate.convertIntegers(ROI_ind);
rm.setSelectedIndexes(ROI_selected);
rm.runCommand("Delete");
}
/***************************************************
* HELPER METHODS
* converters, setters and getters are defined here.
**************************************************/
private static int[] convertIntegers(ArrayList<Integer> integers) throws Exception {
int[] ret = new int[integers.size()];
for (int i=0; i < ret.length; i++)
{
ret[i] = integers.get(i).intValue();
}
return ret;
}
// // conversion to 8bit should not be needed. otherwise reactivate:
// private static ImagePlus convertTo8bit(ImagePlus _img) {
// ImageConverter ic = new ImageConverter(_img);
// ic.convertToGray8();
// return _img;
// }
private static ImagePlus convertTo32bit(ImagePlus _img) throws Exception {
ImageConverter ic = new ImageConverter(_img);
ic.convertToGray32();
return _img;
}
private static Mat convertToMat(ImagePlus _img) throws Exception {
ImagePlusMatConverter ic = new ImagePlusMatConverter();
Mat cImg = ic.convert(_img, Mat.class);
return cImg;
}
private static ImagePlus setImage(String imgPath) throws Exception {
File fd = new File(imgPath);
ImagePlus img = ImageOpenerWithBioformats.openImage(fd);
return img;
}
public double getProminence() {
return prominence;
}
public void setProminence(double prominence) {
this.prominence = prominence;
}
public int getMethod() {
return method;
}
public void setMethod(int method) {
this.method = method;
}
public boolean isStrict() {
return strict;
}
public void setStrict(boolean strict) {
this.strict = strict;
}
/*****************************************************************************
* Main method
*****************************************************************************/
public static void main(String[] args)throws Exception{
// match image: right filetype (8bit)
final String templatePath="C:\\Users\\Manu\\Desktop\\ArgoLight\\Templates\\Argolight_Letters_8bit.tif";
//final String templatePath="C:\\Users\\Manu\\Desktop\\ArgoLight\\Templates\\FoR.tif";
// template image: wrong filetype (16 bit), needs conversion
final String matchPath ="C:\\Users\\Manu\\tmp\\Watch_TemplateMatch\\MatchMe--WholeArgo_flat.czi";
//final String templatePath="C:\\Users\\Manu\\Desktop\\ArgoLight\\Templates\\MatchMe--8bit.tif";
//start imageJ:
new ij.ImageJ();
IJ.log("---Template matchin start---");