Java tutorial
/* * #%L * Ridge Detection plugin for ImageJ * %% * Copyright (C) 2014 - 2015 Thorsten Wagner (ImageJ java plugin), 1996-1998 Carsten Steger (original C code), 1999 R. Balasubramanian (detect lines code to incorporate within GRASP) * %% * 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 2 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-2.0.html>. * #L% */ package de.biomedical_imaging.ij.steger; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.apache.commons.lang3.mutable.MutableInt; import ij.IJ; import ij.process.FloatProcessor; import ij.process.ImageProcessor; public class LineDetector { boolean isDarkLine = false; boolean doCorrectPosition = false; boolean doEstimateWidth = false; boolean doExtendLine = false; private Options opts = null; private Junctions junctions; private Lines lines; Set<Integer> alreadyProcessedJunctionPoints; boolean bechatty = false; /** * * @param ip * The image where the lines should be detected * @param sigma * A value which depends on the line width: sigma >= * width/(2*sqrt(3)) * @param upperThresh * Upper hysteresis thresholds used in the linking algorithm * (Depends on the maximum contour brightness (greyvalue)) * @param lowerThresh * Lower hysteresis thresholds used in the linking algorithm * (Depends on the minimum contour brightness (greyvalue)). * @param isDarkLine * True if the line darker than the background * @param doCorrectPosition * Determines whether the line width and position correction * should be applied * @param doEstimateWidth * Determines whether the line width should be extracted * @param doExtendLine * Extends the detect lines to find more junction points * @return An arraylist with line objects */ public Lines detectLines(ImageProcessor ip, double sigma, double upperThresh, double lowerThresh, boolean isDarkLine, boolean doCorrectPosition, boolean doEstimateWidth, boolean doExtendLine) { return detectLines(ip, sigma, upperThresh, lowerThresh, isDarkLine, doCorrectPosition, doEstimateWidth, doExtendLine, OverlapOption.NONE); } public Lines detectLines(ImageProcessor ip, double sigma, double upperThresh, double lowerThresh, boolean isDarkLine, boolean doCorrectPosition, boolean doEstimateWidth, boolean doExtendLine, OverlapOption overlapOption) { this.isDarkLine = isDarkLine; this.doCorrectPosition = doCorrectPosition; this.doEstimateWidth = doEstimateWidth; this.doExtendLine = doExtendLine; junctions = new Junctions(ip.getSliceNumber()); lines = get_lines(sigma, upperThresh, lowerThresh, ip.getHeight(), ip.getWidth(), ip, junctions, overlapOption); return lines; } private void assignLinesToJunctions(Lines lines, Junctions junctions) { for (Junction j : junctions) { j.lineCont1 = lines.get(j.cont1); j.lineCont2 = lines.get(j.cont2); } } public Options getUsedParamters() { return opts; } public Junctions getJunctions() { return junctions; } private void addAdditionalJunctionPointsAndLines(Lines lines, Junctions junctions) { for (int i = 0; i < junctions.size(); i++) { Junction splitPoint = junctions.get(i); //Split point! log("Process Splitpoint " + splitPoint.getLine1().getID() + "-" + splitPoint.getLine2().getID() + " Pos: " + splitPoint.pos); //splitPoint.pos!=0&&splitPoint.pos!=(splitPoint.getLine1().num-1) if (!alreadyProcessedJunctionPoints.contains(i)) { /* * Find Junctions with the same position as the split point */ Junctions junctionsWithTheSamePosition = new Junctions(junctions.getFrame()); alreadyProcessedJunctionPoints.add(i); junctionsWithTheSamePosition.add(splitPoint); for (int j = i + 1; j < junctions.size(); j++) { if (!alreadyProcessedJunctionPoints.contains(j)) { Junction junc2 = junctions.get(j); if (Math.abs(junc2.x - splitPoint.x) < 0.01 && Math.abs(junc2.y - splitPoint.y) < 0.01) { alreadyProcessedJunctionPoints.add(j); junctionsWithTheSamePosition.add(junc2); } } } /* * Connect all lines which are connected with the processed line also with each other (new junctions point) */ ArrayList<Line> connectedWithProcessedLine = new ArrayList<Line>(); ArrayList<Integer> connectedWithProcessedIndex = new ArrayList<Integer>(); for (Junction junc : junctionsWithTheSamePosition) { connectedWithProcessedLine.add(junc.getLine2()); connectedWithProcessedIndex.add(junc.cont2); } for (int j = 0; j < connectedWithProcessedLine.size(); j++) { for (int k = j + 1; k < connectedWithProcessedLine.size(); k++) { Line l1 = connectedWithProcessedLine.get(j); Line l2 = connectedWithProcessedLine.get(k); Junction junc = new Junction(); junc.lineCont1 = l1; junc.lineCont2 = l2; junc.x = splitPoint.x; junc.y = splitPoint.y; junc.cont1 = connectedWithProcessedIndex.get(j); junc.cont2 = connectedWithProcessedIndex.get(k); junc.pos = l1.getStartOrdEndPosition(junc.x, junc.y); junctions.add(junc); log("Connect " + junc.getLine1().getID() + "-" + junc.getLine2().getID() + " Pos: " + junc.pos); //l1.setContourClass(reconstructContourClass(l1, l1.getStartOrdEndPosition(junc.x, junc.y))); // l2.setContourClass(reconstructContourClass(l2, l2.getStartOrdEndPosition(junc.x, junc.y))); alreadyProcessedJunctionPoints.add(junctions.size() - 1); } } /* * Split the line in two line at the split point if it is not at the end or beginning of the line */ Line l1 = splitPoint.getLine1(); int pos = splitPoint.pos; boolean isClosedContour = l1.col[0] == l1.col[l1.num - 1] && l1.row[0] == l1.row[l1.num - 1]; if (isClosedContour) { l1.setContourClass(LinesUtil.contour_class.cont_closed); l1.setContourClass( reconstructContourClass(l1, l1.getStartOrdEndPosition(splitPoint.x, splitPoint.y))); } log("Pos: " + pos + " num: " + l1.num); if (pos != 0 && pos != (l1.num - 1) && !isClosedContour) { //All data up to pos (included) int keepLength = pos + 1; float[] keepAsymmetry = new float[keepLength]; float[] keepIntensity = new float[keepLength]; float[] keepAngle = new float[keepLength]; float[] keepWidth_l = new float[keepLength]; float[] keepWidth_r = new float[keepLength]; float[] keepCol = new float[keepLength]; float[] keepRow = new float[keepLength]; float[] keepResponse = new float[keepLength]; //All data from pos (included) int splitSize = (int) (l1.num - pos); float[] splitAsymmetry = new float[splitSize]; float[] splitIntensity = new float[splitSize]; float[] splitAngle = new float[splitSize]; float[] splitWidth_l = new float[splitSize]; float[] splitWidth_r = new float[splitSize]; float[] splitCol = new float[splitSize]; float[] splitRow = new float[splitSize]; float[] splitResponse = new float[splitSize]; //Copy data if (doEstimateWidth) { if (doCorrectPosition) { System.arraycopy(l1.asymmetry, 0, keepAsymmetry, 0, keepLength); System.arraycopy(l1.asymmetry, pos, splitAsymmetry, 0, splitSize); System.arraycopy(l1.intensity, 0, keepIntensity, 0, keepLength); System.arraycopy(l1.intensity, pos, splitIntensity, 0, splitSize); } System.arraycopy(l1.angle, 0, keepAngle, 0, keepLength); System.arraycopy(l1.angle, pos, splitAngle, 0, splitSize); System.arraycopy(l1.width_l, 0, keepWidth_l, 0, keepLength); System.arraycopy(l1.width_l, pos, splitWidth_l, 0, splitSize); System.arraycopy(l1.width_r, 0, keepWidth_r, 0, keepLength); System.arraycopy(l1.width_r, pos, splitWidth_r, 0, splitSize); } System.arraycopy(l1.col, 0, keepCol, 0, keepLength); System.arraycopy(l1.col, pos, splitCol, 0, splitSize); System.arraycopy(l1.row, 0, keepRow, 0, keepLength); System.arraycopy(l1.row, pos, splitRow, 0, splitSize); System.arraycopy(l1.response, 0, keepResponse, 0, keepLength); System.arraycopy(l1.response, pos, splitResponse, 0, splitSize); //Generate new line Line lNew = new Line(); lNew.angle = splitAngle; lNew.asymmetry = splitAsymmetry; lNew.col = splitCol; lNew.row = splitRow; lNew.response = splitResponse; lNew.intensity = splitIntensity; lNew.width_l = splitWidth_l; lNew.width_r = splitWidth_r; lNew.num = splitSize; lNew.setContourClass(l1.getContourClass()); lNew.setFrame(l1.getFrame()); lines.add(lNew); int newID = lNew.getID(); //Update junctions //Add additional junction points for the split point Set<Integer> lineIds = new HashSet<Integer>(); //All IDs which are connected at this junction point for (Junction junc : junctionsWithTheSamePosition) { lineIds.add(junc.getLine1().getID()); lineIds.add(junc.getLine2().getID()); } Iterator<Integer> idIt = lineIds.iterator(); while (idIt.hasNext()) { int id = idIt.next(); int connectWithLineID = lines.getIndexByID(id); Line connectWith = lines.get(connectWithLineID); Junction j = new Junction(); j.cont1 = lines.size() - 1; j.cont2 = connectWithLineID; j.lineCont1 = lNew; j.lineCont2 = connectWith; j.x = splitPoint.x; j.y = splitPoint.y; j.pos = lNew.getStartOrdEndPosition(splitPoint.x, splitPoint.y); //lNew.setContourClass(reconstructContourClass(lNew, j.pos)); //connectWith.setContourClass(reconstructContourClass(connectWith, connectWith.getStartOrdEndPosition(splitPoint.x, splitPoint.y))); junctions.add(j); log("Connect " + j.getLine1().getID() + "-" + j.getLine2().getID() + " Pos: " + j.pos); alreadyProcessedJunctionPoints.add(junctions.size() - 1); } //Update following junctions point for (int j = 0; j < junctions.size(); j++) { Junction junc2 = junctions.get(j); if (junc2.cont1 == splitPoint.cont1 && junc2.pos > splitPoint.pos) { log("Update From " + junc2.getLine1().getID() + "-" + junc2.getLine2().getID() + " Pos: " + junc2.pos); junc2.cont1 = lines.getIndexByID(newID); junc2.lineCont1 = lNew; junc2.pos = junc2.pos - splitPoint.pos; log("Update To " + junc2.getLine1().getID() + "-" + junc2.getLine2().getID() + " Pos: " + junc2.pos); } double[] min = minDistance(junc2.getLine2(), junc2.x, junc2.y); if (junc2.cont2 == splitPoint.cont1 && ((int) min[1]) > splitPoint.pos) { junc2.cont2 = lines.getIndexByID(newID); junc2.lineCont2 = lNew; } } //Update Line 1 //Overwrite line data l1.angle = keepAngle; l1.asymmetry = keepAsymmetry; l1.col = keepCol; l1.row = keepRow; l1.response = keepResponse; l1.intensity = keepIntensity; l1.width_l = keepWidth_l; l1.width_r = keepWidth_r; l1.num = keepLength; //Update position of splitpoint log("Set Splitpoint Position from " + splitPoint.pos); splitPoint.pos = l1.getStartOrdEndPosition(splitPoint.x, splitPoint.y); log("Set Splitpoint Position to " + splitPoint.pos); lines.set(splitPoint.cont1, l1); } } } } private Junctions fixJunctions(Lines lines, Junctions junctions) { /* * For some reason, the x and y coordinates are permuted */ for (Junction junction : junctions) { float help = junction.x; junction.x = junction.y; junction.y = help; } Junctions newJunctions = new Junctions(junctions.getFrame()); ArrayList<Point2D.Float> processedJunctions = new ArrayList<Point2D.Float>(); for (int i = 0; i < junctions.size(); i++) { Junction junc = junctions.get(i); Line mainLine = null; int mainLineIndex = -1; int mainLinePos = -1; ArrayList<Line> secondaryLines = new ArrayList<Line>(); ArrayList<Integer> secondaryLineIndex = new ArrayList<Integer>(); ArrayList<Integer> secondaryLinePos = new ArrayList<Integer>(); //Verarbeite jede Junction-Position nur einmal. if (!processedJunctions.contains(new Point2D.Float(junc.x, junc.y))) { //processed[(int)junc.x][(int)junc.y]==0 processedJunctions.add(new Point2D.Float(junc.x, junc.y)); /* * Finde die Sekundrlinien und Hauptlinien */ for (int j = 0; j < lines.size(); j++) { Line l = lines.get(j); double[] mindist = minDistance(l, junc.x, junc.y); if (mindist[0] < 0.1) { //Wenn der Punkt auf der Linie liegt, analysiere genauer if (mindist[1] == 0 || mindist[1] == (l.num - 1)) { //Wenn der Junction-Point am Ende oder am Anfang liegt, ist es sekundre Linie. secondaryLines.add(l); secondaryLineIndex.add(j); secondaryLinePos.add((int) mindist[1]); } else { // Wenn er innerhalb der Linie liegt, ist dies die Hauptlinie. if (mainLine != null) { if (mainLine.getID() == l.getID()) { continue; } log("h, zwei Hauptlininen geht nich..." + mainLine.getID() + " x " + junc.x + " y " + junc.y); log("h, zwei Hauptlininen geht nich..." + l.getID() + " x " + junc.x + " y " + junc.y); } mainLine = l; mainLineIndex = j; mainLinePos = (int) mindist[1]; } } } if (mainLine != null) { for (int j = 0; j < secondaryLines.size(); j++) { Junction newJunc = new Junction(); newJunc.cont1 = mainLineIndex; newJunc.cont2 = secondaryLineIndex.get(j); newJunc.x = junc.x; newJunc.y = junc.y; newJunc.pos = mainLinePos; //lines.get(newJunc.cont1).setContourClass(reconstructContourClass(lines.get(newJunc.cont1), mainLinePos)); //lines.get(newJunc.cont2).setContourClass(reconstructContourClass(lines.get(newJunc.cont2), secondaryLinePos.get(j))); newJunctions.add(newJunc); log("NewJunc Mainline: " + lines.get(newJunc.cont1).getID() + "-" + lines.get(newJunc.cont2).getID() + " pos " + newJunc.pos + " num " + lines.get(newJunc.cont1).num); } } else { //In manchen Fllen gibt es keine Hauptlinie... (bug im Algorithmus, ich bin aber nicht fhig ihn zu finden=. HashSet<Integer> uniqueIDs = new HashSet<Integer>(); ArrayList<Line> uniqueLines = new ArrayList<Line>(); ArrayList<Integer> uniqueLineIndex = new ArrayList<Integer>(); ArrayList<Integer> uniqueLinePos = new ArrayList<Integer>(); for (int j = 0; j < secondaryLines.size(); j++) { if (!uniqueIDs.contains(secondaryLines.get(j).getID())) { uniqueIDs.add(secondaryLines.get(j).getID()); uniqueLines.add(secondaryLines.get(j)); uniqueLineIndex.add(secondaryLineIndex.get(j)); uniqueLinePos.add(secondaryLinePos.get(j)); } } for (int j = 0; j < uniqueLines.size(); j++) { for (int k = j + 1; k < uniqueLines.size(); k++) { Junction newJunc = new Junction(); newJunc.cont1 = uniqueLineIndex.get(j); newJunc.cont2 = uniqueLineIndex.get(k); newJunc.x = junc.x; newJunc.y = junc.y; newJunc.pos = uniqueLinePos.get(j); newJunctions.add(newJunc); log("NewJunc Second: " + lines.get(newJunc.cont1).getID() + "-" + lines.get(newJunc.cont2).getID() + " pos " + newJunc.pos + " num " + lines.get(newJunc.cont1).num); //lines.get(newJunc.cont1).setContourClass(reconstructContourClass(lines.get(newJunc.cont1), uniqueLinePos.get(j))); //lines.get(newJunc.cont2).setContourClass(reconstructContourClass(lines.get(newJunc.cont2), uniqueLinePos.get(k))); alreadyProcessedJunctionPoints.add(newJunctions.size() - 1); } } } } } return newJunctions; } private LinesUtil.contour_class reconstructContourClass(Line l, int pos) { LinesUtil.contour_class currentClass = l.getLineClass(); boolean hasJunctionAtStartpoint = pos == 0 ? true : false; boolean hasJunctionAtEndpoint = pos == (l.num - 1) ? true : false; if (currentClass == LinesUtil.contour_class.cont_no_junc && hasJunctionAtStartpoint) { return LinesUtil.contour_class.cont_start_junc; } if (currentClass == LinesUtil.contour_class.cont_no_junc && hasJunctionAtEndpoint) { return LinesUtil.contour_class.cont_end_junc; } if (currentClass == LinesUtil.contour_class.cont_start_junc && hasJunctionAtEndpoint) { return LinesUtil.contour_class.cont_both_junc; } if (currentClass == LinesUtil.contour_class.cont_end_junc && hasJunctionAtStartpoint) { return LinesUtil.contour_class.cont_both_junc; } if (currentClass == LinesUtil.contour_class.cont_closed && (hasJunctionAtEndpoint || hasJunctionAtStartpoint)) { return LinesUtil.contour_class.cont_both_junc; } return currentClass; } /** * * @param l Line * @param x x-Position * @param y y-Position * @return Double Array [0] minimal distance [1] position of minimal distance */ private double[] minDistance(Line l, float x, float y) { double min = Double.MAX_VALUE; double index = -1; for (int i = 0; i < l.num; i++) { double d = Math.sqrt(Math.pow(l.col[i] - x, 2) + Math.pow(l.row[i] - y, 2)); if (d < min) { min = d; index = i; } } double[] ret = { min, index }; return ret; } private void deleteContour(Lines contours, Junctions junctions, Line c) { ArrayList<Junction> remove = new ArrayList<Junction>(); for (Junction junction : junctions) { if (contours.get((int) junction.cont1).getID() == c.getID() || contours.get((int) junction.cont2).getID() == c.getID()) { remove.add(junction); } } for (Junction junction : remove) { junctions.remove(junction); } contours.remove(c); } private void fixContours(Lines contours, Junctions junctions) { // Contours with only a single position cant be valid. for (Line contour : contours) { if (contour.num == 1) { deleteContour(contours, junctions, contour); continue; } //If the results are corrupted, this informationen has to be reconstructed in fixJunctions contour.setContourClass(LinesUtil.contour_class.cont_no_junc); } // For some reason the first and the last element are the same. Delete // it! if (contours.size() >= 2) { if (contours.get(0).getID() == contours.get(contours.size() - 1).getID()) { contours.remove(contours.size() - 1); } } } private Lines get_lines(double sigma, double high, double low, int rows, int cols, ImageProcessor in_img, Junctions resultJunction, OverlapOption overlapOption) { FloatProcessor image; Lines contours = new Lines(in_img.getSliceNumber()); int num_cont = 0; opts = new Options(-1.0, -1.0, -1.0, isDarkLine ? LinesUtil.MODE_DARK : LinesUtil.MODE_LIGHT, doCorrectPosition, doEstimateWidth, doExtendLine, false, false, false, overlapOption); opts.sigma = sigma; opts.high = high; opts.low = low; check_sigma(opts.sigma, cols, rows); OverlapResolver resolver = null; switch (overlapOption) { default: case NONE: break; case SLOPE: resolver = new SlopeOverlapResolver(); break; } int i2, j2; // //(float *) malloc(rows*cols*sizeof(float)); float[] imgpxls = new float[cols * rows]; for (i2 = 0; i2 < rows; i2++) for (j2 = 0; j2 < cols; j2++) imgpxls[i2 * cols + j2] = in_img.getf(j2, i2); image = new FloatProcessor(cols, rows, imgpxls); MutableInt hnum_cont = new MutableInt(num_cont); float[] imgpxls2 = (float[]) image.getPixels(); Position p = new Position(); p.detect_lines(imgpxls2, cols, rows, contours, hnum_cont, opts.sigma, opts.low, opts.high, opts.mode, opts.width, opts.correct, opts.extend, resultJunction); num_cont = hnum_cont.getValue(); // lines = contours; fixContours(contours, resultJunction); alreadyProcessedJunctionPoints = new HashSet<Integer>(); //Reconstruct solution from junction points. This have to be done, because in raw cases //the algorithm corrupts the results. However, I was not able to find that bug so I decided //to reconstruct the solution from the information which were not be corrupted. resultJunction = fixJunctions(contours, resultJunction); assignLinesToJunctions(contours, resultJunction); addAdditionalJunctionPointsAndLines(contours, resultJunction); Collections.sort(resultJunction); junctions = resultJunction; /* * RECONSRUCTION OF CONTOUR CLASS */ //Reset contour class for (int i = 0; i < contours.size(); i++) { contours.get(i).setContourClass(LinesUtil.contour_class.cont_no_junc); } //Find closed lines for (int i = 0; i < contours.size(); i++) { boolean isClosedContour = contours.get(i).col[0] == contours.get(i).col[contours.get(i).num - 1] && contours.get(i).row[0] == contours.get(i).row[contours.get(i).num - 1]; if (isClosedContour) { contours.get(i).setContourClass(LinesUtil.contour_class.cont_closed); IJ.log(""); } } //Reconstruction contour class for (int i = 0; i < junctions.size(); i++) { Junction j = junctions.get(i); j.getLine1().setContourClass(reconstructContourClass(j.getLine1(), j.pos)); float x = j.getLine1().getXCoordinates()[j.pos]; float y = j.getLine1().getYCoordinates()[j.pos]; j.getLine2().setContourClass( reconstructContourClass(j.getLine2(), j.getLine2().getStartOrdEndPosition(x, y))); } if (resolver != null) contours = resolver.resolve(contours, junctions, bechatty); return contours; } private void log(String s) { if (bechatty) { IJ.log(s); } } private void check_sigma(double sigma, int width, int height) { int min_dim; min_dim = width < height ? width : height; if (sigma < 0.4) IJ.error(LinesUtil.ERR_SOR, "< 0.4"); if (LinesUtil.MASK_SIZE(LinesUtil.MAX_SIZE_MASK_2, sigma) >= min_dim) IJ.error(LinesUtil.ERR_SOR, "too large for image size"); } }