diff --git a/pom.xml b/pom.xml index 941ce00..e08f936 100644 --- a/pom.xml +++ b/pom.xml @@ -1,149 +1,149 @@ 4.0.0 org.scijava pom-scijava 19.2.0 ch.epfl.biop 1.0.0 PTS_Analyzer.jar imagej.public http://maven.imagej.net/content/groups/public oburri Olivier Burri olivier.burri@epfl.ch http://biop.epfl.ch EPFL BioImaging And Optics Platform BIOP http://epfl.ch lead developer debugger reviewer support maintainer +1 https://c4science.ch/diffusion/767/ijp-pts.git HEAD https://c4science.ch/diffusion/767/ijp-pts.git ch.epfl.biop ch.epfl.biop.PTS_Analyzer N/A N/A - C:/Fiji/ + F:/Fiji.app/ BIOP http://biop.epfl.ch Simplified BSD License repo Olivier Burri http://biop.epfl.ch/INFO_Facility.html developer debugger reviewer support maintainer oburri N/A N/A fiji fiji-plugins sc.fiji imagescience net.imagej ij maven-jar-plugin ${main-class} PTS_Analyzer PTS Analyzer plugin fiji fiji-plugins 20101208 sc.fiji imagescience 3.0.0 2015 https://c4science.ch/source/ijp-pts/ None https://biop.epfl.ch None N/A diff --git a/src/ch/epfl/biop/pts/HerbieCenterDetector.java b/src/ch/epfl/biop/pts/HerbieCenterDetector.java index 36d6e6f..c42c668 100644 --- a/src/ch/epfl/biop/pts/HerbieCenterDetector.java +++ b/src/ch/epfl/biop/pts/HerbieCenterDetector.java @@ -1,91 +1,91 @@ package ch.epfl.biop.pts; import java.awt.geom.Point2D; import ij.IJ; import ij.plugin.filter.MaximumFinder; import ij.process.Blitter; import ij.process.ImageProcessor; /** * @author Olivier Burri, with the idea from Dr. Herbie Gluender * This detection method relies on the sum projection of the image's pixels * vertically and horizontaly. By contruction the position of the minimum on the * two 1D pixel arrays corresponds to the center of the pattern! */ public class HerbieCenterDetector extends PTSCenterDetector { public HerbieCenterDetector() { super("Herbie Center Detector"); } @Override public Point2D computeCenter(ImageProcessor ip) { // Remove low frequencies ip = ip.convertToFloat(); ip.invert(); ImageProcessor ip_blur = ip.duplicate(); - ip_blur.blurGaussian(50); + ip_blur.blurGaussian(2.0); ip.copyBits(ip_blur, 0, 0, Blitter.DIVIDE); // Project double ignore_frac = 0.05; // Ignore 5% of image to the left and to the right int ignore_px = (int) Math.round(ignore_frac*ip.getWidth()); int ignore_py = (int) Math.round(ignore_frac*ip.getHeight()); double[] xSum = new double[ ip.getWidth() - (2*ignore_px)]; double[] ySum = new double[ip.getHeight() - (2*ignore_py)]; for(int i=0; i 0.98 && Math.abs(px[1]-px[0]) < 20) { posX = (px[0]+px[1]) /2; } if (ry > 0.98 && Math.abs(py[1]-py[0]) < 20) { posY = (py[0]+py[1]) /2; } //IJ.log("Ratio X: "+(rx)+ "X1("+(px[0]+ignore_px)+")="+xSum[px[0]]+ "X2("+(px[1]+ignore_px)+")="+xSum[px[1]]); //IJ.log("Ratio Y: "+(ry)+ "Y1("+(py[0]+ignore_py)+")="+ySum[py[0]]+ "Y2("+(py[1]+ignore_py)+")="+ySum[py[1]]); Point2D chosen = new Point2D.Double(posX+ignore_px, posY + ignore_py); return chosen; } } diff --git a/src/ch/epfl/biop/pts/PTSAnalyzer.java b/src/ch/epfl/biop/pts/PTSAnalyzer.java index 2a66b4e..1c0d4c4 100644 --- a/src/ch/epfl/biop/pts/PTSAnalyzer.java +++ b/src/ch/epfl/biop/pts/PTSAnalyzer.java @@ -1,276 +1,281 @@ package ch.epfl.biop.pts; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Arrays; import ij.IJ; import ij.ImagePlus; import ij.gui.PolygonRoi; import ij.gui.Roi; import ij.measure.Calibration; import ij.plugin.RoiScaler; import ij.plugin.Straightener; import ij.plugin.filter.MaximumFinder; import ij.process.FloatPolygon; import ij.process.ImageProcessor; public class PTSAnalyzer { private static double scale = 2; /** * Main method for analyzing BIOP Pizza Target Slide Images. * @param ip ImageProcessor of the PTS image * @param settings PTSSettings object with all relevant settings * @param criteria PTSCriteria object with convergence conditions * @return a ResultTable with a single Row, use {@link ij.measure.ResultsTable#addResults()} to append to the Results ResultsTable */ public static PTSResult run(ImagePlus imp, PTSSettings settings, PTSCriteria criteria) { + Roi roi = null; + if(imp.getRoi() != null) { + roi = imp.getRoi(); + imp.killRoi(); + } ImagePlus imp2 = imp.duplicate(); // If needs scaling do it here if (settings.isOversample()) { //IJ.log("Calibration before: "+imp.getCalibration().pixelWidth); imp2.setProcessor(imp2.getProcessor().resize((int) (imp2.getWidth()*scale))); Calibration cal = imp2.getCalibration().copy(); cal.pixelHeight /= scale; cal.pixelWidth /= scale; imp2.setCalibration(cal); //IJ.log("Calibration after: "+imp2.getCalibration().pixelWidth); } // Find center Point2D center = new Point2D.Double(); - if(imp.getRoi() != null) { - center = new Point2D.Double(imp.getRoi().getFloatPolygon().xpoints[0],imp.getRoi().getFloatPolygon().ypoints[0]); + if(roi != null) { + center = new Point2D.Double(roi.getBounds().getCenterX(),roi.getBounds().getCenterY()); //center = settings.getCenterDetector().optimizeCenter(imp.getProcessor(), center); if(settings.isOversample()) { center.setLocation(center.getX()*scale, center.getY()*scale); } } else { center = settings.getCenterDetector().computeCenter(imp2.getProcessor()); } double rad; boolean is_done = false; // Do some smart assumption about where to start and stop looking for values; double min_distance = getMinLookupDist(); // Go to the edge of the image, as defined by the center double max_distance = getMaxLookupDist(imp2.getProcessor(), center); rad = (max_distance+min_distance)/2; PTSResult res = new PTSResult(); double new_rad = Double.MAX_VALUE; int n_cycles=0; while(!is_done) { n_cycles++; if(n_cycles >100) break; // Iterate to find the thing // Define minimum and maximum search diameters // Make a circle Roi circ = cleanCircle(center, rad); imp2.setRoi(circ); //IJ.log(""+max_distance+" "+min_distance); // Straighten, until here nothing is calibrated ImageProcessor band = new Straightener().straighten(imp2, circ, (int) settings.getLineWidth()); res = analyzePeaks(band, settings, rad, imp2.getCalibration()); // Check criteria and continue if( criteria.checkCriteria(res) ) { max_distance = rad; new_rad = ( rad + min_distance ) / 2; if (Math.abs(rad - new_rad) < criteria.getTolerance()) { is_done=true; } else { rad = new_rad; } } else { min_distance = rad; rad = ( rad + max_distance ) / 2; } } Roi circ = cleanCircle(center, rad); if( settings.isOversample()) { circ = RoiScaler.scale(circ, 1/scale, 1/scale, false); center.setLocation(center.getX()/scale, center.getY()/scale); } res.setCenter(center); res.setConverged(is_done); imp.setRoi(circ ); imp2.close(); return res; } private static PTSResult analyzePeaks(ImageProcessor band, PTSSettings settings, double current_radius, Calibration cal) { double[] values = divideArray(sumPixelRows(band), settings.getLineWidth()); // Find Maxima and Minima: These are the POSITIONS int[] maxima = MaximumFinder.findMaxima(values, 2, true); // These are SORTED by amplitude, we must sort them again Arrays.sort(maxima); int[] minima = MaximumFinder.findMinima(values, 2, true); Arrays.sort(minima); // Apply criteria for good and bad detections double goodDist = current_radius*Math.sin(2*Math.PI/360)*cal.pixelWidth; double minDist = goodDist*(1-settings.getP2pVarPct()/100); double maxDist = goodDist*(1+settings.getP2pVarPct()/100); int min_good_cnt = 0; int max_good_cnt = 0; double min_good_avg= 0; double max_good_avg= 0; ArrayList min_d = new ArrayList(); ArrayList max_d = new ArrayList(); // Check peak to Peak Distance Maxima for(int i=1; i= minDist && d <= maxDist) { max_good_cnt++; max_good_avg+=values[maxima[i-1]]; max_d.add(d); } } // Check peak to Peak Distance Minima for(int i=1; i= minDist && d <= maxDist) { min_good_cnt++; min_good_avg+=values[minima[i-1]]; min_d.add(d); } } max_good_avg /= maxima.length; min_good_avg /= minima.length; // Check Michelson Contrast double michelson = (max_good_avg-min_good_avg)/(max_good_avg+min_good_avg); // Peak to Peak Statistics // Need mean and standard deviation max_d.addAll(min_d); // Use all distances from minima and maxima double p2p_mean = getMean(toDoubleArray(max_d)); double p2p_stdev = getStdDev(toDoubleArray(max_d)); // Expected Peak 2 Peak double ep_p2p = current_radius*Math.sin(2*Math.PI/360)*cal.pixelWidth; // Expected FWHM based on distance double ep_fwhm = 2*ep_p2p/3; // Calculated FWHM double fwhm = 2*p2p_mean/3; // Total Peaks double tot_peaks = (double) (maxima.length+minima.length)/2.0; // Good Peaks double good_peaks = (double) (min_good_cnt+max_good_cnt)/2.0; PTSResult res = new PTSResult(ep_p2p, p2p_mean, p2p_stdev, ep_fwhm, fwhm, tot_peaks, good_peaks, good_peaks/tot_peaks * 100, michelson, cal.pixelWidth, current_radius*cal.pixelWidth, new Point2D.Double()); return res; } private static double[] divideArray(double[] values, double factor) { for(int i=0; i al) { double[] target = new double[al.size()]; for (int i = 0; i < target.length; i++) { target[i] = al.get(i); // java 1.5+ style (outboxing) } return target; } } \ No newline at end of file diff --git a/src/ch/epfl/biop/pts/PTSResult.java b/src/ch/epfl/biop/pts/PTSResult.java index a79dfbe..47cb2d5 100644 --- a/src/ch/epfl/biop/pts/PTSResult.java +++ b/src/ch/epfl/biop/pts/PTSResult.java @@ -1,203 +1,210 @@ package ch.epfl.biop.pts; import java.awt.geom.Point2D; import java.util.regex.Matcher; import java.util.regex.Pattern; import ij.measure.ResultsTable; public class PTSResult { double ep_p2p, p2p_mean, p2p_stdev, ep_fwhm, fwhm, tot_peaks, good_peaks, good_peaks_pct, michelson; double px_size; + double line_width; double radius; Point2D center; /** * @param center the center to set */ public void setCenter(Point2D center) { this.center = center; } String converged; public PTSResult() { } /** * @param ep_p2p * @param p2p_mean * @param p2p_stdev * @param ep_fwhm * @param fwhm * @param tot_peaks * @param good_peaks * @param good_peaks_pct * @param michelson * @param px_size * @param radius * @param center * @param converged */ public PTSResult(double ep_p2p, double p2p_mean, double p2p_stdev, double ep_fwhm, double fwhm, double tot_peaks, double good_peaks, double good_peaks_pct, double michelson, double px_size, double radius, Point2D center) { super(); this.ep_p2p = ep_p2p; this.p2p_mean = p2p_mean; this.p2p_stdev = p2p_stdev; this.ep_fwhm = ep_fwhm; this.fwhm = fwhm; this.tot_peaks = tot_peaks; this.good_peaks = good_peaks; this.good_peaks_pct = good_peaks_pct; this.michelson = michelson; this.px_size = px_size; this.radius = radius; this.center = center; + this.line_width = -1; } + + public void setLineWidth(double line_width) { + this.line_width = line_width; + } + /** * @return the px_size */ public double getPx_size() { return px_size; } /** * @return the radius */ public double getRadius() { return radius; } /** * @param px_size the px_size to set */ public void setPx_size(double px_size) { this.px_size = px_size; } /** * @param radius the radius to set */ public void setRadius(double radius) { this.radius = radius; } public void setConverged(boolean has_converged) { this.converged = (has_converged) ? "True" : "False"; } public ResultsTable getResultsTable(String image_name) { ResultsTable rt = new ResultsTable(); return appendToResultsTable(rt,image_name); } public ResultsTable appendToResultsTable(ResultsTable rt, String image_name ) { rt.incrementCounter(); rt.addValue("Label" , image_name ); rt.addValue("Pixel Size", px_size); rt.addValue("Center X", center.getX()); rt.addValue("Center Y", center.getY()); rt.addValue("Radius [cal]", radius); rt.addValue("Expected P2P [cal]" , ep_p2p ); rt.addValue("Average P2P [cal]" , p2p_mean ); rt.addValue("StDev P2P [cal]" , p2p_stdev); - rt.addValue("Expected FWHM [cal]", ep_fwhm ); - rt.addValue("FWHM [cal]" , fwhm ); + rt.addValue("Expected OQP [cal]", ep_fwhm ); + rt.addValue("OQP [cal]" , fwhm ); rt.addValue("Total Peaks" , tot_peaks); rt.addValue("Good Peaks" , good_peaks); rt.addValue("Good Peaks[%]", good_peaks_pct); - rt.addValue("Michelson Contrast", michelson); rt.addValue("Converged", converged); + if(line_width != -1) rt.addValue("Line Width", line_width); // Try to parse image name and if parsed add info to the table String pattern = "(\\d{8})_(.*)_(\\d*x)([\\d\\.]*)_([^_.]*)_(.*?)( .*)*\\."; // Create a Pattern object Pattern r = Pattern.compile(pattern); // Now create matcher object. Matcher m = r.matcher(image_name); if (m.find( )) { rt.addValue("Date", Double.parseDouble(m.group(1))); rt.addValue("Machine", m.group(2)); rt.addValue("Objective", m.group(3)); rt.addValue("NA", Double.parseDouble(m.group(4))); rt.addValue("Wavelength", m.group(5)); rt.addValue("Note", m.group(6)); } return rt; } /** * @return the ep_p2p */ public double getEpP2p() { return ep_p2p; } /** * @return the p2p_mean */ public double getP2pMean() { return p2p_mean; } /** * @return the p2p_stdev */ public double getP2pStdev() { return p2p_stdev; } /** * @return the ep_fwhm */ public double getEpFwhm() { return ep_fwhm; } /** * @return the fwhm */ public double getFwhm() { return fwhm; } /** * @return the tot_peaks */ public double getTotPeaks() { return tot_peaks; } /** * @return the good_peaks */ public double getGoodPeaks() { return good_peaks; } /** * @return the good_peaks_pct */ public double getGoodPeaksPct() { return good_peaks_pct; } /** * @return the michelson */ public double getMichelson() { return michelson; } } diff --git a/src/ch/epfl/biop/pts/PTS_Analyzer.java b/src/ch/epfl/biop/pts/PTS_Analyzer.java index dc3bab0..3b0a146 100644 --- a/src/ch/epfl/biop/pts/PTS_Analyzer.java +++ b/src/ch/epfl/biop/pts/PTS_Analyzer.java @@ -1,128 +1,131 @@ -package ch.epfl.biop.pts; - -import java.io.File; -import java.io.FilenameFilter; - -import ch.epfl.biop.pts.HerbieCenterDetector; -import ch.epfl.biop.pts.PTSAnalyzer; -import ch.epfl.biop.pts.PTSCenterDetector; -import ch.epfl.biop.pts.PTSCriteria; -import ch.epfl.biop.pts.PTSResult; -import ch.epfl.biop.pts.PTSSettings; -import ij.IJ; -import ij.ImageJ; -import ij.ImagePlus; -import ij.Prefs; -import ij.gui.GenericDialog; -import ij.measure.ResultsTable; -import ij.plugin.PlugIn; - -/** - * PlugIn to find the largest circle or circles inside a mask. - * Macro recordable - * @author Olivier Burri - * @version 1.0 - */ -public class PTS_Analyzer implements PlugIn { - - @Override - public void run(String arg) { - ImagePlus imp = IJ.getImage(); - - // Pick up all the preferences for the dialog - boolean is_over_sample = Prefs.get("biop.pts.is_over_sample", true); - - double p2p_var_pct = Prefs.get("biop.pts.p2p_var_pct", 50); - - double convergence_limit = Prefs.get("biop.pts.convergence", 0.01); - double min_peaks = Prefs.get("biop.pts.min_peaks", 350); - double min_good_pct = Prefs.get("biop.pts.min_good_pct", 95); - - - GenericDialog gd = new GenericDialog("PTS Analysis Settings"); - gd.addCheckbox("Oversample Image", is_over_sample); - gd.addNumericField("Allowed Peak to Peak Variation [%]", p2p_var_pct, 3); - gd.addNumericField("Minimum_Number of Total Peaks", min_peaks, 3); - gd.addNumericField("Minimum_Percent of Good Peaks [%]", min_good_pct, 3); - gd.addNumericField("Convergence Limit", convergence_limit, 3); - - gd.showDialog(); - - if(gd.wasCanceled()) { - return; - } - - is_over_sample = gd.getNextBoolean(); - p2p_var_pct = gd.getNextNumber(); - min_peaks = gd.getNextNumber(); - min_good_pct = gd.getNextNumber(); - convergence_limit = gd.getNextNumber(); - - Prefs.set("biop.pts.is_over_sample", is_over_sample); - Prefs.set("biop.pts.p2p_var_pct", p2p_var_pct); - - Prefs.set("biop.pts.convergence", convergence_limit); - Prefs.set("biop.pts.min_good_peaks", min_peaks); - Prefs.set("biop.pts.min_good_pct", min_good_pct); - - // Whew, now we can begin - PTSCenterDetector cd = new HerbieCenterDetector(); - PTSSettings set = new PTSSettings(is_over_sample, 20, p2p_var_pct, cd); - PTSCriteria crit = new PTSCriteria(convergence_limit, min_peaks, min_good_pct); - - - - PTSResult r = PTSAnalyzer.run(imp, set, crit); - - ResultsTable rt = ResultsTable.getResultsTable(); - rt = r.appendToResultsTable(rt, imp.getTitle()); - rt.show("Results"); - - } - - /** - * Main method for debugging. - * @param args unused - */ - public static void main(String[] args) { - // set the plugins.dir property to make the plugin appear in the Plugins menu - Class clazz = PTS_Analyzer.class; - String url = clazz.getResource("/" + clazz.getName().replace('.', '/') + ".class").toString(); - String pluginsDir = url.substring(5, url.length() - clazz.getName().length() - 6); - System.setProperty("plugins.dir", pluginsDir); - - // start ImageJ - new ImageJ(); - - // Get Image List from folder - File main = new File("E:\\PTS Tests\\"); - - ResultsTable rt = ResultsTable.getResultsTable(); - - for(String f : main.list(new FilenameFilter() { - - @Override - public boolean accept(File dir, String name) { - if (name.endsWith("tif")) return true; - return false; - } - })) { - IJ.log(main.getAbsolutePath()+File.separator+f); - ImagePlus imp1 = IJ.openImage(main.getAbsolutePath()+File.separator+f); - imp1.show(); - - PTSCenterDetector cd = new HerbieCenterDetector(); - PTSSettings set = new PTSSettings(false, 20, 50, cd); - - PTSCriteria crit = new PTSCriteria(0.01, 350, 95); - PTSResult r = PTSAnalyzer.run(imp1, set, crit); - - rt = r.appendToResultsTable(rt, imp1.getTitle()); - - } - rt.show("Results"); - - - } - -} +package ch.epfl.biop.pts; + +import java.io.File; +import java.io.FilenameFilter; + +import ch.epfl.biop.pts.HerbieCenterDetector; +import ch.epfl.biop.pts.PTSAnalyzer; +import ch.epfl.biop.pts.PTSCenterDetector; +import ch.epfl.biop.pts.PTSCriteria; +import ch.epfl.biop.pts.PTSResult; +import ch.epfl.biop.pts.PTSSettings; +import ij.IJ; +import ij.ImageJ; +import ij.ImagePlus; +import ij.Prefs; +import ij.gui.GenericDialog; +import ij.measure.ResultsTable; +import ij.plugin.PlugIn; + +/** + * PlugIn to find the largest circle or circles inside a mask. + * Macro recordable + * @author Olivier Burri + * @version 1.0 + */ +public class PTS_Analyzer implements PlugIn { + + @Override + public void run(String arg) { + ImagePlus imp = IJ.getImage(); + + // Pick up all the preferences for the dialog + boolean is_over_sample = Prefs.get("biop.pts.is_over_sample", true); + + double p2p_var_pct = Prefs.get("biop.pts.p2p_var_pct", 50); + + double convergence_limit = Prefs.get("biop.pts.convergence", 0.01); + double min_peaks = Prefs.get("biop.pts.min_peaks", 350); + double min_good_pct = Prefs.get("biop.pts.min_good_pct", 95); + + + + GenericDialog gd = new GenericDialog("PTS Analysis Settings"); + gd.addCheckbox("Oversample Image", is_over_sample); + gd.addNumericField("Allowed Peak to Peak Variation [%]", p2p_var_pct, 3); + gd.addNumericField("Minimum_Number of Total Peaks", min_peaks, 3); + gd.addNumericField("Minimum_Percent of Good Peaks [%]", min_good_pct, 3); + gd.addNumericField("Convergence Limit", convergence_limit, 3); + + + gd.showDialog(); + + if(gd.wasCanceled()) { + return; + } + + is_over_sample = gd.getNextBoolean(); + p2p_var_pct = gd.getNextNumber(); + min_peaks = gd.getNextNumber(); + min_good_pct = gd.getNextNumber(); + convergence_limit = gd.getNextNumber(); + + + Prefs.set("biop.pts.is_over_sample", is_over_sample); + Prefs.set("biop.pts.p2p_var_pct", p2p_var_pct); + + Prefs.set("biop.pts.convergence", convergence_limit); + Prefs.set("biop.pts.min_good_peaks", min_peaks); + Prefs.set("biop.pts.min_good_pct", min_good_pct); + + + // Whew, now we can begin + PTSCenterDetector cd = new HerbieCenterDetector(); + PTSSettings set = new PTSSettings(is_over_sample, 20, p2p_var_pct, cd); + PTSCriteria crit = new PTSCriteria(convergence_limit, min_peaks, min_good_pct); + + + + PTSResult r = PTSAnalyzer.run(imp, set, crit); + ResultsTable rt = ResultsTable.getResultsTable(); + rt = r.appendToResultsTable(rt, imp.getTitle()); + rt.show("Results"); + + } + + /** + * Main method for debugging. + * @param args unused + */ + public static void main(String[] args) { + // set the plugins.dir property to make the plugin appear in the Plugins menu + Class clazz = PTS_Analyzer.class; + String url = clazz.getResource("/" + clazz.getName().replace('.', '/') + ".class").toString(); + String pluginsDir = url.substring(5, url.length() - clazz.getName().length() - 6); + System.setProperty("plugins.dir", pluginsDir); + + // start ImageJ + new ImageJ(); + + // Get Image List from folder + File main = new File("E:\\PTS Tests\\"); + + ResultsTable rt = ResultsTable.getResultsTable(); + + for(String f : main.list(new FilenameFilter() { + + @Override + public boolean accept(File dir, String name) { + if (name.endsWith("tif")) return true; + return false; + } + })) { + IJ.log(main.getAbsolutePath()+File.separator+f); + ImagePlus imp1 = IJ.openImage(main.getAbsolutePath()+File.separator+f); + imp1.show(); + + PTSCenterDetector cd = new HerbieCenterDetector(); + PTSSettings set = new PTSSettings(false, 20, 50, cd); + + PTSCriteria crit = new PTSCriteria(0.01, 350, 95); + PTSResult r = PTSAnalyzer.run(imp1, set, crit); + + rt = r.appendToResultsTable(rt, imp1.getTitle()); + + } + rt.show("Results"); + + + } + +}