diff --git a/src/main/java/ColocImgLibGadgets.java b/src/main/java/sc/fiji/coloc/ColocImgLibGadgets.java similarity index 99% rename from src/main/java/ColocImgLibGadgets.java rename to src/main/java/sc/fiji/coloc/ColocImgLibGadgets.java index 5f069fe..e412a19 100644 --- a/src/main/java/ColocImgLibGadgets.java +++ b/src/main/java/sc/fiji/coloc/ColocImgLibGadgets.java @@ -1,154 +1,155 @@ /* * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ +package sc.fiji.coloc; import ij.IJ; import ij.ImagePlus; import ij.plugin.PlugIn; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; import net.imglib2.Cursor; import net.imglib2.algorithm.math.ImageStatistics; import net.imglib2.img.ImagePlusAdapter; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; import net.imglib2.img.array.ArrayImgFactory; import net.imglib2.type.NativeType; import net.imglib2.type.numeric.RealType; public class ColocImgLibGadgets & NativeType> implements PlugIn { protected Img img1, img2; @Override public void run(String arg) { ImagePlus imp1 = IJ.openImage("/Users/dan/Documents/Dresden/ipf/colocPluginDesign/red.tif"); img1 = ImagePlusAdapter.wrap(imp1); ImagePlus imp2 = IJ.openImage("/Users/dan/Documents/Dresden/ipf/colocPluginDesign/green.tif"); img2 = ImagePlusAdapter.wrap(imp2); double pearson = calculatePearson(); Img ranImg = generateRandomImageStack(img1, new int[] {2,2,1}); } /** * To randomize blockwise we enumerate the blocks, shuffle that list and * write the data to their new position based on the shuffled list. */ protected Img generateRandomImageStack(Img img, int[] blockDimensions) { int numberOfDimensions = Math.min(img.numDimensions(), blockDimensions.length); int numberOfBlocks = 0; long[] numberOfBlocksPerDimension = new long[numberOfDimensions]; for (int i = 0 ; i allTheBlocks = new ArrayList(numberOfBlocks); for (int i = 0; i cursor = img.cursor(); // create factories for new image stack //ContainerFactory containerFactory = new ImagePlusContainerFactory(); ImgFactory imgFactory = new ArrayImgFactory(); //new ImageFactory(cursor.getType(), containerFactory); // create a new stack for the random images final long[] dim = new long[ img.numDimensions() ]; img.dimensions(dim); Img randomStack = imgFactory.create(dim, img.firstElement().createVariable()); // iterate over image data while (cursor.hasNext()) { cursor.fwd(); T type = cursor.get(); // type.getRealDouble(); } return randomStack; } protected double calculatePearson() { Cursor cursor1 = img1.cursor(); Cursor cursor2 = img2.cursor(); double mean1 = getImageMean(img1); double mean2 = getImageMean(img2); // Do some rather simple performance testing long startTime = System.currentTimeMillis(); double pearson = calculatePearson(cursor1, mean1, cursor2, mean2); // End performance testing long finishTime = System.currentTimeMillis(); long elapsed = finishTime - startTime; // print some output to IJ log IJ.log("mean of ch1: " + mean1 + " " + "mean of ch2: " + mean2); IJ.log("Pearson's Coefficient " + pearson); IJ.log("That took: " + elapsed + " ms"); return pearson; } protected double calculatePearson(Cursor cursor1, double mean1, Cursor cursor2, double mean2) { double pearsonDenominator = 0; double ch1diffSquaredSum = 0; double ch2diffSquaredSum = 0; while (cursor1.hasNext() && cursor2.hasNext()) { cursor1.fwd(); cursor2.fwd(); T type1 = cursor1.get(); double ch1diff = type1.getRealDouble() - mean1; T type2 = cursor2.get(); double ch2diff = type2.getRealDouble() - mean2; pearsonDenominator += ch1diff*ch2diff; ch1diffSquaredSum += (ch1diff*ch1diff); ch2diffSquaredSum += (ch2diff*ch2diff); } double pearsonNumerator = Math.sqrt(ch1diffSquaredSum * ch2diffSquaredSum); return pearsonDenominator / pearsonNumerator; } protected double getImageMean(Img img) { double sum = 0; Cursor cursor = img.cursor(); while (cursor.hasNext()) { cursor.fwd(); T type = cursor.get(); sum += type.getRealDouble(); } return sum / ImageStatistics.getNumPixels(img); } } diff --git a/src/main/java/Coloc_2.java b/src/main/java/sc/fiji/coloc/Coloc_2.java similarity index 96% rename from src/main/java/Coloc_2.java rename to src/main/java/sc/fiji/coloc/Coloc_2.java index 979a1bc..db72f5a 100644 --- a/src/main/java/Coloc_2.java +++ b/src/main/java/sc/fiji/coloc/Coloc_2.java @@ -1,857 +1,858 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ +package sc.fiji.coloc; import ij.IJ; import ij.ImagePlus; import ij.Prefs; import ij.WindowManager; import ij.gui.GenericDialog; import ij.gui.Roi; import ij.gui.ShapeRoi; import ij.plugin.PlugIn; import ij.plugin.frame.RoiManager; import ij.process.Blitter; import ij.process.ImageProcessor; import java.awt.Checkbox; import java.awt.Rectangle; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import net.imglib2.Cursor; import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; import net.imglib2.TwinCursor; import net.imglib2.img.ImagePlusAdapter; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; import net.imglib2.img.array.ArrayImgFactory; import net.imglib2.type.NativeType; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; -import algorithms.Algorithm; -import algorithms.AutoThresholdRegression; -import algorithms.AutoThresholdRegression.Implementation; -import algorithms.CostesSignificanceTest; -import algorithms.Histogram2D; -import algorithms.InputCheck; -import algorithms.KendallTauRankCorrelation; -import algorithms.LiHistogram2D; -import algorithms.LiICQ; -import algorithms.MandersColocalization; -import algorithms.MissingPreconditionException; -import algorithms.PearsonsCorrelation; -import algorithms.SpearmanRankCorrelation; -import gadgets.DataContainer; -import results.AnalysisResults; -import results.PDFWriter; -import results.ResultHandler; -import results.SingleWindowDisplay; -import results.Warning; +import sc.fiji.coloc.algorithms.Algorithm; +import sc.fiji.coloc.algorithms.AutoThresholdRegression; +import sc.fiji.coloc.algorithms.AutoThresholdRegression.Implementation; +import sc.fiji.coloc.algorithms.CostesSignificanceTest; +import sc.fiji.coloc.algorithms.Histogram2D; +import sc.fiji.coloc.algorithms.InputCheck; +import sc.fiji.coloc.algorithms.KendallTauRankCorrelation; +import sc.fiji.coloc.algorithms.LiHistogram2D; +import sc.fiji.coloc.algorithms.LiICQ; +import sc.fiji.coloc.algorithms.MandersColocalization; +import sc.fiji.coloc.algorithms.MissingPreconditionException; +import sc.fiji.coloc.algorithms.PearsonsCorrelation; +import sc.fiji.coloc.algorithms.SpearmanRankCorrelation; +import sc.fiji.coloc.gadgets.DataContainer; +import sc.fiji.coloc.results.AnalysisResults; +import sc.fiji.coloc.results.PDFWriter; +import sc.fiji.coloc.results.ResultHandler; +import sc.fiji.coloc.results.SingleWindowDisplay; +import sc.fiji.coloc.results.Warning; /** * An ImageJ plugin which does pixel intensity correlation based colocalisation * analysis on a pair of images, with optional Mask or ROI. * * @param * @author Daniel J. White * @author Tom Kazimiers * @author Johannes Schindelin */ public class Coloc_2 & NativeType> implements PlugIn { // a small bounding box container protected class BoundingBox { public long[] offset; public long[] size; public BoundingBox(final long[] offset, final long[] size) { this.offset = offset.clone(); this.size = size.clone(); } } // a storage class for ROI information protected class MaskInfo { BoundingBox roi; public Img mask; // constructors public MaskInfo(final BoundingBox roi, final Img mask) { this.roi = roi; this.mask = mask; } public MaskInfo() {} } // the storage key for Fiji preferences protected final static String PREF_KEY = "Coloc_2."; // Allowed types of ROI configuration protected enum RoiConfiguration { None, Img1, Img2, Mask, RoiManager } // the ROI configuration to use protected RoiConfiguration roiConfig = RoiConfiguration.Img1; // A list of all ROIs/masks found protected ArrayList masks = new ArrayList<>(); // A list of auto threshold implementations protected String[] regressions = new String[AutoThresholdRegression.Implementation.values().length]; // default indices of image, mask, ROI and regression choices protected static int index1 = 0; protected static int index2 = 1; protected static int indexMask = 0; protected static int indexRoi = 0; protected static int indexRegr = 0; // the images to work on protected Img img1, img2; // names of the images working on protected String Ch1Name = ""; protected String Ch2Name = ""; // the channels of the images to use protected int img1Channel = 1, img2Channel = 1; /* The different algorithms this plug-in provides. * If a reference is null it will not get run. */ protected PearsonsCorrelation pearsonsCorrelation; protected LiHistogram2D liHistogramCh1; protected LiHistogram2D liHistogramCh2; protected LiICQ liICQ; protected SpearmanRankCorrelation SpearmanRankCorrelation; protected MandersColocalization mandersCorrelation; protected KendallTauRankCorrelation kendallTau; protected Histogram2D histogram2D; protected CostesSignificanceTest costesSignificance; // indicates if images should be printed in result protected boolean displayImages; // indicates if a PDF should be saved automatically protected boolean autoSavePdf; @Override public void run(final String arg0) { if (showDialog()) { try { for (final MaskInfo mi : masks) { colocalise(img1, img2, mi.roi, mi.mask); } } catch (final MissingPreconditionException e) { IJ.handleException(e); IJ.showMessage("An error occured, could not colocalize!"); return; } } } public boolean showDialog() { // get IDs of open windows final int[] windowList = WindowManager.getIDList(); // if there are less than 2 windows open, cancel if (windowList == null || windowList.length < 2) { IJ.showMessage("At least 2 images must be open!"); return false; } /* create a new generic dialog for the * display of various options. */ final GenericDialog gd = new GenericDialog("Coloc 2"); final String[] titles = new String[windowList.length]; /* the masks and ROIs array needs three more entries than * windows to contain "none", "ROI ch 1" and "ROI ch 2" */ final String[] roisAndMasks = new String[windowList.length + 4]; roisAndMasks[0] = ""; roisAndMasks[1] = "ROI(s) in channel 1"; roisAndMasks[2] = "ROI(s) in channel 2"; roisAndMasks[3] = "ROI Manager"; // go through all open images and add them to GUI for (int i = 0; i < windowList.length; i++) { final ImagePlus imp = WindowManager.getImage(windowList[i]); if (imp != null) { titles[i] = imp.getTitle(); roisAndMasks[i + 4] = imp.getTitle(); } else { titles[i] = ""; } } // find all available regression strategies final Implementation[] regressionImplementations = AutoThresholdRegression.Implementation.values(); for (int i = 0; i < regressionImplementations.length; ++i) { regressions[i] = regressionImplementations[i].toString(); } // set up the users preferences displayImages = Prefs.get(PREF_KEY + "displayImages", false); autoSavePdf = Prefs.get(PREF_KEY + "autoSavePdf", true); boolean displayShuffledCostes = Prefs.get(PREF_KEY + "displayShuffledCostes", false); boolean useLiCh1 = Prefs.get(PREF_KEY + "useLiCh1", true); boolean useLiCh2 = Prefs.get(PREF_KEY + "useLiCh2", true); boolean useLiICQ = Prefs.get(PREF_KEY + "useLiICQ", true); boolean useSpearmanRank = Prefs.get(PREF_KEY + "useSpearmanRank", true); boolean useManders = Prefs.get(PREF_KEY + "useManders", true); boolean useKendallTau = Prefs.get(PREF_KEY + "useKendallTau", true); boolean useScatterplot = Prefs.get(PREF_KEY + "useScatterplot", true); boolean useCostes = Prefs.get(PREF_KEY + "useCostes", true); int psf = (int) Prefs.get(PREF_KEY + "psf", 3); int nrCostesRandomisations = (int) Prefs.get(PREF_KEY + "nrCostesRandomisations", 10); indexRegr = (int) Prefs.get(PREF_KEY + "regressionImplementation", 0); /* make sure the default indices are no bigger * than the amount of images we have */ index1 = clip(index1, 0, titles.length); index2 = clip(index2, 0, titles.length); indexMask = clip(indexMask, 0, roisAndMasks.length - 1); gd.addChoice("Channel_1", titles, titles[index1]); gd.addChoice("Channel_2", titles, titles[index2]); gd.addChoice("ROI_or_mask", roisAndMasks, roisAndMasks[indexMask]); // gd.addChoice("Use ROI", roiLabels, roiLabels[indexRoi]); gd.addChoice("Threshold_regression", regressions, regressions[indexRegr]); gd.addCheckbox("Show_Save_PDF_Dialog", autoSavePdf); gd.addCheckbox("Display_Images_in_Result", displayImages); gd.addCheckbox("Display_Shuffled_Images", displayShuffledCostes); final Checkbox shuffleCb = (Checkbox) gd.getCheckboxes().lastElement(); // Add algorithm options gd.addMessage("Algorithms:"); gd.addCheckbox("Li_Histogram_Channel_1", useLiCh1); gd.addCheckbox("Li_Histogram_Channel_2", useLiCh2); gd.addCheckbox("Li_ICQ", useLiICQ); gd.addCheckbox("Spearman's_Rank_Correlation", useSpearmanRank); gd.addCheckbox("Manders'_Correlation", useManders); gd.addCheckbox("Kendall's_Tau_Rank_Correlation", useKendallTau); gd.addCheckbox("2D_Instensity_Histogram", useScatterplot); gd.addCheckbox("Costes'_Significance_Test", useCostes); final Checkbox costesCb = (Checkbox) gd.getCheckboxes().lastElement(); gd.addNumericField("PSF", psf, 1); gd.addNumericField("Costes_randomisations", nrCostesRandomisations, 0); // disable shuffle checkbox if costes checkbox is set to "off" shuffleCb.setEnabled(useCostes); costesCb.addItemListener(new ItemListener() { @Override public void itemStateChanged(final ItemEvent e) { shuffleCb.setEnabled(costesCb.getState()); } }); // show the dialog, finally gd.showDialog(); // do nothing if dialog has been canceled if (gd.wasCanceled()) return false; final ImagePlus gdImp1 = WindowManager.getImage(gd.getNextChoiceIndex() + 1); final ImagePlus gdImp2 = WindowManager.getImage(gd.getNextChoiceIndex() + 1); int gdIndexMask = gd.getNextChoiceIndex(); int gdIndexRegr = gd.getNextChoiceIndex(); boolean gdAutoSavePdf = gd.getNextBoolean(); boolean gdDisplayImages = gd.getNextBoolean(); boolean gdDisplayShuffledCostes = gd.getNextBoolean(); boolean gdUseLiCh1 = gd.getNextBoolean(); boolean gdUseLiCh2 = gd.getNextBoolean(); boolean gdUseLiICQ = gd.getNextBoolean(); boolean gdUseSpearmanRank = gd.getNextBoolean(); boolean gdUseManders = gd.getNextBoolean(); boolean gdUseKendallTau = gd.getNextBoolean(); boolean gdUseScatterplot = gd.getNextBoolean(); boolean gdUseCostes = gd.getNextBoolean(); int gdPsf = (int) gd.getNextNumber(); int gdNrCostesRandomisations = (int) gd.getNextNumber(); // save user preferences Prefs.set(PREF_KEY + "regressionImplementation", gdIndexRegr); Prefs.set(PREF_KEY + "autoSavePdf", gdAutoSavePdf); Prefs.set(PREF_KEY + "displayImages", gdDisplayImages); Prefs.set(PREF_KEY + "displayShuffledCostes", gdDisplayShuffledCostes); Prefs.set(PREF_KEY + "useLiCh1", gdUseLiCh1); Prefs.set(PREF_KEY + "useLiCh2", gdUseLiCh2); Prefs.set(PREF_KEY + "useLiICQ", gdUseLiICQ); Prefs.set(PREF_KEY + "useSpearmanRank", gdUseSpearmanRank); Prefs.set(PREF_KEY + "useManders", gdUseManders); Prefs.set(PREF_KEY + "useKendallTau", gdUseKendallTau); Prefs.set(PREF_KEY + "useScatterplot", gdUseScatterplot); Prefs.set(PREF_KEY + "useCostes", gdUseCostes); Prefs.set(PREF_KEY + "psf", gdPsf); Prefs.set(PREF_KEY + "nrCostesRandomisations", gdNrCostesRandomisations); return initializeSettings(gdImp1, gdImp2, gdIndexMask, gdIndexRegr, gdAutoSavePdf, gdDisplayImages, gdDisplayShuffledCostes, gdUseLiCh1, gdUseLiCh2, gdUseLiICQ, gdUseSpearmanRank, gdUseManders, gdUseKendallTau, gdUseScatterplot, gdUseCostes, gdPsf, gdNrCostesRandomisations); } /** Programmatically initializes the colocalisation settings to match the given values. */ public boolean initializeSettings(ImagePlus imp1, ImagePlus imp2, int gdIndexMask, int gdIndexRegr, boolean gdAutoSavePdf, boolean gdDisplayImages, boolean gdDisplayShuffledCostes, boolean gdUseLiCh1, boolean gdUseLiCh2, boolean gdUseLiICQ, boolean gdUseSpearmanRank, boolean gdUseManders, boolean gdUseKendallTau, boolean gdUseScatterplot, boolean gdUseCostes, int gdPsf, int gdNrCostesRandomisations) { // get image names for output Ch1Name = imp1.getTitle(); Ch2Name = imp2.getTitle(); // make sure both images have the same bit-depth if (imp1.getBitDepth() != imp2.getBitDepth()) { IJ.showMessage("Both images must have the same bit-depth."); return false; } // get information about the mask/ROI to use indexMask = gdIndexMask; if (indexMask == 0) roiConfig = RoiConfiguration.None; else if (indexMask == 1) roiConfig = RoiConfiguration.Img1; else if (indexMask == 2) roiConfig = RoiConfiguration.Img2; else if (indexMask == 3) roiConfig = RoiConfiguration.RoiManager; else { roiConfig = RoiConfiguration.Mask; /* Make indexMask the reference to the mask image to use. * To do this we reduce it by three for the first three * entries in the combo box. */ indexMask = indexMask - 4; } // save the ImgLib wrapped images as members img1 = ImagePlusAdapter.wrap(imp1); img2 = ImagePlusAdapter.wrap(imp2); /* check if we have a valid ROI for the selected configuration * and if so, get the ROI's bounds. Alternatively, a mask can * be selected (that is basically all, but a rectangle). */ if (roiConfig == RoiConfiguration.Img1 && hasValidRoi(imp1)) { createMasksFromImage(imp1); } else if (roiConfig == RoiConfiguration.Img2 && hasValidRoi(imp2)) { createMasksFromImage(imp2); } else if (roiConfig == RoiConfiguration.RoiManager) { if (!createMasksFromRoiManager(imp1.getWidth(), imp1.getHeight())) return false; } else if (roiConfig == RoiConfiguration.Mask) { // get the image to be used as mask final int[] windowList = WindowManager.getIDList(); final ImagePlus maskImp = WindowManager.getImage(windowList[indexMask]); final Img maskImg = ImagePlusAdapter. wrap(maskImp); // get a valid mask info for the image final MaskInfo mi = getBoundingBoxOfMask(maskImg); masks.add(mi); } else { /* if no ROI/mask is selected, just add an empty MaskInfo * to colocalise both images without constraints. */ masks.add(new MaskInfo(null, null)); } // get information about the mask/ROI to use indexRegr = gdIndexRegr; // read out GUI data autoSavePdf = gdAutoSavePdf; displayImages = gdDisplayImages; // Parse algorithm options pearsonsCorrelation = new PearsonsCorrelation<>( PearsonsCorrelation.Implementation.Fast); if (gdUseLiCh1) liHistogramCh1 = new LiHistogram2D<>("Li - Ch1", true); if (gdUseLiCh2) liHistogramCh2 = new LiHistogram2D<>("Li - Ch2", false); if (gdUseLiICQ) liICQ = new LiICQ<>(); if (gdUseSpearmanRank) { SpearmanRankCorrelation = new SpearmanRankCorrelation<>(); } if (gdUseManders) mandersCorrelation = new MandersColocalization<>(); if (gdUseKendallTau) kendallTau = new KendallTauRankCorrelation<>(); if (gdUseScatterplot) histogram2D = new Histogram2D<>( "2D intensity histogram"); if (gdUseCostes) { costesSignificance = new CostesSignificanceTest<>(pearsonsCorrelation, gdPsf, gdNrCostesRandomisations, gdDisplayShuffledCostes); } return true; } /** * Call this method to run a whole colocalisation configuration, all selected * algorithms get run on the supplied images. You can specify the data further * by supplying appropriate information in the mask structure. *

* NB: This method returns {@code void} for binary backwards compatibility * with old code which might invoke it. If you want access to the * {@link AnalysisResults} directly, call * {@link #colocalise(Img, Img, BoundingBox, Img, List)} with {@code null} for * {@code extraHandlers}. *

* * @param image1 * @param image2 * @param roi * @param mask * @throws MissingPreconditionException */ public void colocalise(final Img image1, final Img image2, final BoundingBox roi, final Img mask) throws MissingPreconditionException { colocalise(image1, image2, roi, mask, null); } /** * Call this method to run a whole colocalisation configuration, all selected * algorithms get run on the supplied images. You can specify the data further * by supplying appropriate information in the mask structure. * * @param image1 First image. * @param image2 Second image. * @param roi Region of interest to which analysis is confined. * @param mask Mask to which analysis is confined. * @param extraHandlers additional objects to be notified of analysis results. * @return Data structure housing the results. * @throws MissingPreconditionException */ public AnalysisResults colocalise(final Img image1, final Img image2, final BoundingBox roi, final Img mask, final List> extraHandlers) throws MissingPreconditionException { // create a new container for the selected images and channels DataContainer container; if (mask != null) { container = new DataContainer<>(image1, image2, img1Channel, img2Channel, Ch1Name, Ch2Name, mask, roi.offset, roi.size); } else if (roi != null) { // we have no mask, but a regular ROI in use container = new DataContainer<>(image1, image2, img1Channel, img2Channel, Ch1Name, Ch2Name, roi.offset, roi.size); } else { // no mask and no ROI is present container = new DataContainer<>(image1, image2, img1Channel, img2Channel, Ch1Name, Ch2Name); } // create a results handler final List> listOfResultHandlers = new ArrayList<>(); final AnalysisResults analysisResults = new AnalysisResults<>(); listOfResultHandlers.add(analysisResults); final PDFWriter pdfWriter = new PDFWriter<>(container); final boolean headless = Boolean.getBoolean("java.awt.headless"); final SingleWindowDisplay swDisplay; if (headless) swDisplay = null; else { swDisplay = new SingleWindowDisplay<>(container, pdfWriter); listOfResultHandlers.add(swDisplay); } listOfResultHandlers.add(pdfWriter); if (extraHandlers != null) listOfResultHandlers.addAll(extraHandlers); // ResultHandler resultHandler = new EasyDisplay(container); // this contains the algorithms that will be run when the user clicks ok final List> userSelectedJobs = new ArrayList<>(); // add some pre-processing jobs: userSelectedJobs.add(container.setInputCheck(new InputCheck())); userSelectedJobs.add(container.setAutoThreshold( new AutoThresholdRegression<>(pearsonsCorrelation, AutoThresholdRegression.Implementation.values()[indexRegr]))); // add user selected algorithms addIfValid(pearsonsCorrelation, userSelectedJobs); addIfValid(liHistogramCh1, userSelectedJobs); addIfValid(liHistogramCh2, userSelectedJobs); addIfValid(liICQ, userSelectedJobs); addIfValid(SpearmanRankCorrelation, userSelectedJobs); addIfValid(mandersCorrelation, userSelectedJobs); addIfValid(kendallTau, userSelectedJobs); addIfValid(histogram2D, userSelectedJobs); addIfValid(costesSignificance, userSelectedJobs); // execute all algorithms int count = 0; final int jobs = userSelectedJobs.size(); for (final Algorithm a : userSelectedJobs) { try { count++; IJ.showStatus(count + "/" + jobs + ": Running " + a.getName()); a.execute(container); } catch (final MissingPreconditionException e) { for (final ResultHandler r : listOfResultHandlers) { r.handleWarning(new Warning("Probem with input data", a.getName() + ": " + e.getMessage())); } } } // clear status IJ.showStatus(""); // let the algorithms feed their results to the handler for (final Algorithm a : userSelectedJobs) { for (final ResultHandler r : listOfResultHandlers) a.processResults(r); } // if we have ROIs/masks, add them to results if (displayImages) { RandomAccessibleInterval channel1, channel2; if (mask != null || roi != null) { final long[] offset = container.getMaskBBOffset(); final long[] size = container.getMaskBBSize(); channel1 = createMaskImage(container.getSourceImage1(), // container.getMask(), offset, size); channel2 = createMaskImage(container.getSourceImage2(), // container.getMask(), offset, size); } else { channel1 = container.getSourceImage1(); channel2 = container.getSourceImage2(); } channel1 = project(channel1); channel2 = project(channel2); for (final ResultHandler r : listOfResultHandlers) { r.handleImage(channel1, "Channel 1 (Max Projection)"); r.handleImage(channel2, "Channel 2 (Max Projection)"); } } if (swDisplay != null) { // do the actual results processing swDisplay.process(); // add window to the IJ window manager swDisplay.addWindowListener(new WindowAdapter() { @Override public void windowClosing(final WindowEvent e) { WindowManager.removeWindow(swDisplay); swDisplay.dispose(); // NB: For some reason, garbage collection of this bundle of objects // does not occur when this window listener reference remains in // place. As such, we explicitly unregister ourself here. swDisplay.removeWindowListener(this); } }); WindowManager.addWindow(swDisplay); } // show PDF saving dialog if requested if (autoSavePdf) pdfWriter.process(); return analysisResults; } private RandomAccessibleInterval project( final RandomAccessibleInterval image) { if (image.numDimensions() < 2) { throw new IllegalArgumentException("Dimensionality too small: " + // image.numDimensions()); } final IterableInterval input = Views.iterable(image); final T type = input.firstElement(); // e.g. unsigned 8-bit final long xLen = image.dimension(0); final long yLen = image.dimension(1); // initialize output image with minimum value of the pixel type final long[] outputDims = { xLen, yLen }; final Img output = new ArrayImgFactory().create(outputDims, type); for (final T sample : output) { sample.setReal(type.getMinValue()); } // loop over the input image, performing the max projection final Cursor inPos = input.localizingCursor(); final RandomAccess outPos = output.randomAccess(); while (inPos.hasNext()) { final T inPix = inPos.next(); final long xPos = inPos.getLongPosition(0); final long yPos = inPos.getLongPosition(1); outPos.setPosition(xPos, 0); outPos.setPosition(yPos, 1); final T outPix = outPos.get(); if (outPix.compareTo(inPix) < 0) { outPix.set(inPix); } } return output; } /** * A method to get the bounding box from the data in the given image that is * above zero. Those values are interpreted as a mask. It will return null if * no mask information was found. * * @param mask The image to look for "on" values in * @return a new MaskInfo object or null */ protected MaskInfo getBoundingBoxOfMask(final Img mask) { final Cursor cursor = mask.localizingCursor(); final int numMaskDims = mask.numDimensions(); // the "off type" of the mask final T offType = mask.firstElement().createVariable(); offType.setZero(); // the corners of the bounding box final long[][] minMax = new long[2][]; // indicates if mask data has been found boolean maskFound = false; // a container for temporary position information final long[] pos = new long[numMaskDims]; // walk over the mask while (cursor.hasNext()) { cursor.fwd(); final T data = cursor.get(); // test if the current mask data represents on or off if (data.compareTo(offType) > 0) { // get current position cursor.localize(pos); if (!maskFound) { // we found mask data, first time maskFound = true; // init min and max with the current position minMax[0] = Arrays.copyOf(pos, numMaskDims); minMax[1] = Arrays.copyOf(pos, numMaskDims); } else { /* Is is at least second hit, compare if it * has new "extreme" positions, i.e. does * is make the BB bigger? */ for (int d = 0; d < numMaskDims; d++) { if (pos[d] < minMax[0][d]) { // is it smaller than min minMax[0][d] = pos[d]; } else if (pos[d] > minMax[1][d]) { // is it larger than max minMax[1][d] = pos[d]; } } } } } if (!maskFound) return null; // calculate size final long[] size = new long[numMaskDims]; for (int d = 0; d < numMaskDims; d++) size[d] = minMax[1][d] - minMax[0][d] + 1; // create and add bounding box final BoundingBox bb = new BoundingBox(minMax[0], size); return new MaskInfo(bb, mask); } /** * Adds the provided Algorithm to the list if it is not null. */ protected void addIfValid(final Algorithm a, final List> list) { if (a != null) list.add(a); } /** * Returns true if a custom ROI has been selected, i.e if the current ROI does * not have the extent of the whole image. * * @return true if custom ROI selected, false otherwise */ protected boolean hasValidRoi(final ImagePlus imp) { final Roi roi = imp.getRoi(); if (roi == null) return false; final Rectangle theROI = roi.getBounds(); // if the ROI is the same size as the image (default ROI), return false return (theROI.height != imp.getHeight() || theROI.width != imp.getWidth()); } /** * Clips a value to the specified bounds. */ protected static int clip(final int val, final int min, final int max) { return Math.max(Math.min(val, max), min); } /** * This method checks if the given ImagePlus contains any masks or ROIs. If * so, the appropriate date structures are created and filled. */ protected void createMasksFromImage(final ImagePlus imp) { // get ROIs from current image in Fiji final Roi[] impRois = split(imp.getRoi()); // create the ROIs createMasksAndRois(impRois, imp.getWidth(), imp.getHeight()); } /** * A method to fill the masks array with data based on the ROI manager. */ protected boolean createMasksFromRoiManager(final int width, final int height) { final RoiManager roiManager = RoiManager.getInstance(); if (roiManager == null) { IJ.error("Could not get ROI Manager instance."); return false; } final Roi[] selectedRois = roiManager.getSelectedRoisAsArray(); // create the ROIs createMasksAndRois(selectedRois, width, height); return true; } /** * Creates appropriate data structures from the ROI information * passed. If an irregular ROI is found, it will be put into a * frame of its bounding box size and put into an {@code Image}. * * In the end the members ROIs, masks and maskBBs will be * filled if ROIs or masks were found. They will be null * otherwise. */ protected void createMasksAndRois(final Roi[] rois, final int width, final int height) { // create empty list masks.clear(); for (final Roi r : rois) { final MaskInfo mi = new MaskInfo(); // add it to the list of masks/ROIs masks.add(mi); // get the ROIs/masks bounding box final Rectangle rect = r.getBounds(); mi.roi = new BoundingBox(new long[] { rect.x, rect.y }, new long[] { rect.width, rect.height }); final ImageProcessor ipMask = r.getMask(); // check if we got a regular ROI and return if so if (ipMask == null) { continue; } // create a mask processor of the same size as a slice final ImageProcessor ipSlice = ipMask.createProcessor(width, height); // fill the new slice with black ipSlice.setValue(0.0); ipSlice.fill(); // position the mask on the new mask processor ipSlice.copyBits(ipMask, (int) mi.roi.offset[0], (int) mi.roi.offset[1], Blitter.COPY); // create an Image out of it final ImagePlus maskImp = new ImagePlus("Mask", ipSlice); // and remember it and the masks bounding box mi.mask = ImagePlusAdapter. wrap(maskImp); } } /** * This method duplicates the given images, but respects ROIs if present. * Meaning, a sub-picture will be created when source images are * ROI/MaskImages. * * @throws MissingPreconditionException */ protected RandomAccessibleInterval createMaskImage( final RandomAccessibleInterval image, final RandomAccessibleInterval mask, final long[] offset, final long[] size) throws MissingPreconditionException { final long[] pos = new long[image.numDimensions()]; // sanity check if (pos.length != offset.length || pos.length != size.length) { throw new MissingPreconditionException( "Mask offset and size must be of same dimensionality like image."); } // use twin cursor for only one image final TwinCursor cursor = new TwinCursor<>(image.randomAccess(), // image.randomAccess(), Views.iterable(mask).localizingCursor()); // prepare output image final ImgFactory maskFactory = new ArrayImgFactory<>(); // Img maskImage = maskFactory.create( size, name ); final RandomAccessibleInterval maskImage = maskFactory.create(size, // image.randomAccess().get().createVariable()); final RandomAccess maskCursor = maskImage.randomAccess(); // go through the visible data and copy it to the output while (cursor.hasNext()) { cursor.fwd(); cursor.localize(pos); // shift coordinates by offset for (int i = 0; i < pos.length; ++i) { pos[i] = pos[i] - offset[i]; } // write out to correct position maskCursor.setPosition(pos); maskCursor.get().set(cursor.getFirst()); } return maskImage; } /** * Splits a non overlapping composite ROI into its sub ROIs. * * @param roi The ROI to split * @return A list of one or more ROIs */ public static Roi[] split(final Roi roi) { if (roi instanceof ShapeRoi) return ((ShapeRoi) roi).getRois(); return new Roi[] { roi }; } } diff --git a/src/main/java/Colocalisation_Test.java b/src/main/java/sc/fiji/coloc/Colocalisation_Test.java similarity index 99% rename from src/main/java/Colocalisation_Test.java rename to src/main/java/sc/fiji/coloc/Colocalisation_Test.java index 0ed491f..7b46ed6 100644 --- a/src/main/java/Colocalisation_Test.java +++ b/src/main/java/sc/fiji/coloc/Colocalisation_Test.java @@ -1,763 +1,765 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ +package sc.fiji.coloc; + //version 21 4 05 //added van Steensel CCF analysis //JCellSci v109.p787 //version 29/4/05 //Costes randomisation uses pixels in ch2 only once per random image import ij.IJ; import ij.ImagePlus; import ij.ImageStack; import ij.Prefs; import ij.WindowManager; import ij.gui.GenericDialog; import ij.gui.PlotWindow; import ij.gui.Roi; import ij.measure.Calibration; import ij.plugin.PlugIn; import ij.plugin.filter.GaussianBlur; import ij.process.ByteProcessor; import ij.process.ImageProcessor; import ij.process.ImageStatistics; import ij.process.ShortProcessor; import ij.text.TextWindow; import java.awt.Rectangle; import java.text.DecimalFormat; public class Colocalisation_Test implements PlugIn {static boolean headingsSet2; private static int index1=0; private static int index2=1; private ImagePlus imp1, imp2,impmask, imp3; private ImageProcessor ipmask; private int indexRoi= (int)Prefs.get("Rand_indexRoi.int",0); private int indexRand= (int)Prefs.get("Rand_indexRand.int",0); private boolean useROI, useMask ; private Roi roi, roi1, roi2; private static boolean randZ= Prefs.get("Rand_randZ.boolean", false); private static boolean ignoreZeroZero= Prefs.get("Rand_ignore.boolean", true); private static boolean smooth= Prefs.get("Rand_smooth.boolean", true); private static boolean keep = Prefs.get("Rand_keep.boolean", false); private static boolean currentSlice= Prefs.get("Rand_currentSlice.boolean", true); private static boolean useManPSF= Prefs.get("Rand_useManPSF.boolean", false); private static boolean showR= Prefs.get("Rand_showR.boolean", false); private static double psf =0; private static int manPSF= (int)Prefs.get("Rand_manPSF.int",10); private static int iterations= (int)Prefs.get("Rand_iterations.int",0); private static double ch2Lambda = Prefs.get("Rand_ch2L.double",520); private static double NA = Prefs.get("Rand_NA.double",1.4); private static double pixelSize = Prefs.get("Rand_pixelSize.double",0.10); DecimalFormat df3 = new DecimalFormat("##0.000"); DecimalFormat df2 = new DecimalFormat("##0.00"); DecimalFormat df1 = new DecimalFormat("##0.0"); DecimalFormat df0 = new DecimalFormat("##0"); String[] chooseRand= { "Fay (x,y,z translation)","Costes approximation (smoothed noise)", "van Steensel (x translation)"}; private int width, height, rwidth, rheight, xOffset, yOffset, mask; String randMethod = "Fay"; private long startTime; private long endTime; StringBuffer rVals = new StringBuffer(); boolean Costes = false; boolean Fay = false; boolean vanS = false; boolean rBlocks= false; protected static TextWindow textWindow; @Override public void run(String arg) {startTime = System.currentTimeMillis(); if (showDialog()) correlate(imp1, imp2, imp3); } public boolean showDialog() { int[] wList = WindowManager.getIDList(); if (wList==null) { IJ.noImage(); return false; } String[] titles = new String[wList.length]; String[] chooseROI= new String[wList.length+3]; chooseROI[0] = "None"; chooseROI[1] = "ROI in channel 1 "; chooseROI[2] = "ROI in channel 2"; if (indexRoi>wList.length+3) indexRoi=0; for (int i=0; i=titles.length)index1 = 0; if (index2>=titles.length)index2 = 0; GenericDialog gd = new GenericDialog("Colocalisation Test"); gd.addChoice("Channel_1", titles, titles[index1]); gd.addChoice("Channel_2", titles, titles[index2]); gd.addChoice("ROI or Mask", chooseROI, chooseROI[indexRoi]); gd.addChoice("Randomization method", chooseRand,chooseRand[indexRand]); //gd.addCheckbox("Ignore zero-zero pixels", ignoreZeroZero); gd.addCheckbox("Current_slice only (Ch1)", currentSlice); gd.addCheckbox("Keep_example randomized image", keep); gd.addCheckbox("Show_all_R_values from Ch1 vs Ch2(rand)", showR); gd.addMessage("See: http://uhnresearch.ca/wcif/imagej"); gd.showDialog(); if (gd.wasCanceled()) return false; index1 = gd.getNextChoiceIndex(); index2 = gd.getNextChoiceIndex(); indexRoi = gd.getNextChoiceIndex(); indexRand=gd.getNextChoiceIndex(); ignoreZeroZero = true; currentSlice= gd.getNextBoolean(); keep = gd.getNextBoolean(); showR=gd.getNextBoolean(); String title1 = titles[index1]; String title2 = titles[index2]; imp1 = WindowManager.getImage(wList[index1]); imp2 = WindowManager.getImage(wList[index2]); if (imp1.getType()==imp1.COLOR_RGB || imp2.getType()==imp1.COLOR_RGB) { IJ.showMessage("Colocalisation Test", "Both images must be grayscale."); return false; } useMask=false; if (indexRoi >=3) { imp3 = WindowManager.getImage(indexRoi-2); useMask=true; } else imp3 = WindowManager.getImage(wList[index2]); Calibration cal = imp2.getCalibration(); pixelSize = cal.pixelWidth; if(indexRand==0) {Fay=true; randMethod = "Fay"; } if(indexRand==1) {Costes = true; randMethod = "Costes X, Y"; } if(indexRand==2) {vanS=true; randMethod = "van Steensel"; } //test to ensure all images match up. boolean matchWidth=false; boolean matchHeight=false; boolean matchSlice = false; if (imp1.getWidth()==imp2.getWidth()&&imp1.getWidth()==imp3.getWidth()) matchWidth = true; if (imp1.getHeight()==imp2.getHeight()&&imp1.getHeight()==imp3.getHeight()) matchHeight = true; if (imp1.getStackSize()==imp2.getStackSize()&&imp1.getStackSize()==imp3.getStackSize()) matchSlice = true; if (!(matchWidth&&matchHeight&&matchSlice)) {IJ.showMessage("Image mismatch","Images do not match. Exiting"); return false; } if (Costes||rBlocks) { GenericDialog gd2 = new GenericDialog("PSF details"); gd2.addCheckbox("Randomize pixels in z-axis", randZ); gd2.addNumericField("Pixel Size (µm)", pixelSize,3); gd2.addNumericField("Channel_2_wavelength (nm)", ch2Lambda,0); gd2.addNumericField("NA of objective", NA,2); gd2.addNumericField("Iterations",iterations,0); gd2.addMessage(""); gd2.addCheckbox("Use_manual_PSF", useManPSF); gd2.addNumericField("PSF_radius in pixels", manPSF, 0); gd2.showDialog(); if (gd2.wasCanceled()) return false; randZ = gd2.getNextBoolean(); if (randZ) randMethod+=", Z"; pixelSize =gd2.getNextNumber(); ch2Lambda = gd2.getNextNumber(); NA = gd2.getNextNumber(); iterations = (int)gd2.getNextNumber(); useManPSF = gd2.getNextBoolean(); manPSF = (int)gd2.getNextNumber(); psf = (0.61*ch2Lambda)/NA; psf = (psf)/(pixelSize*1000); if (useManPSF) psf = manPSF; //IJ.showMessage("PSF radius = "+df3.format(psf)); } return true; } public void correlate(ImagePlus imp1, ImagePlus imp2, ImagePlus imp3) { String Ch1fileName = imp1.getTitle(); String Ch2fileName = imp2.getTitle(); ImageStack img1 = imp1.getStack(); ImageStack img2 = imp2.getStack(); ImageStack img3=imp3.getStack(); int width = imp1.getWidth(); int height = imp1.getHeight(); String fileName = Ch1fileName + " and " + Ch2fileName; ImageProcessor ip1 = imp1.getProcessor(); ImageProcessor ip2 = imp2.getProcessor(); ImageProcessor ip3= null; if (useMask) ip3 = imp3.getProcessor(); ImageStack stackRand = new ImageStack(rwidth,rheight); ImageProcessor ipRand= img2.getProcessor(1); int currentSliceNo = imp1.getCurrentSlice(); if(currentSlice) fileName = fileName+ ". slice: "+currentSliceNo ; double pearsons1 = 0; double pearsons2 = 0; double pearsons3 = 0; double r2= 1; double r=1; double ch1Max=0; double ch1Min = ip1.getMax(); double ch2Max=0; double ch2Min = ip1.getMax(); int nslices = imp1.getStackSize(); int ch1, ch2, count; double sumX = 0; double sumXY = 0; double sumXX = 0; double sumYY = 0; double sumY = 0; double sumXtotal=0; double sumYtotal=0; double colocX=0; double colocY=0; int N = 0; int N2 = 0; double r2min=1; double r2max=-1; sumX = 0; sumXY = 0; sumXX = 0; sumYY = 0; sumY = 0; int i=1; double coloc2M1 = 0; double coloc2M2 = 0; int colocCount=0; int colocCount1=0; int colocCount2=0; double r2sd=0; double sumr2sqrd=0; double sumr2=0; double ICQ2mean=0; double sumICQ2sqrd=0; double sumICQ2=0; double ICQobs=0; int countICQ=0; int Nr=0; int Ng=0; //get stack2 histogram ImageStatistics stats = imp2.getStatistics(); if (imp2.getType() == imp2.GRAY16) stats.nBins = 1<<16; int [] histogram = new int[stats.nBins]; //roi code if (indexRoi==1||indexRoi==2) useROI = true; else useROI=false; ip1 = imp1.getProcessor(); ip2 = imp2.getProcessor(); ip3 = imp2.getProcessor(); if (useMask) ip3 = imp3.getProcessor(); roi1 = imp1.getRoi(); roi2 = imp2.getRoi(); Rectangle rect =ip1.getRoi(); if (indexRoi==1) {if(roi1==null) useROI=false; else { ipmask = imp1.getMask(); rect = ip1.getRoi(); } } if (indexRoi==2) {if(roi2==null) useROI=false; else{ipmask = imp2.getMask(); rect = ip2.getRoi();} } if (useROI==false) {xOffset = 0;yOffset = 0; rwidth=width; rheight =height;} else {xOffset = rect.x; yOffset = rect.y; rwidth=rect.width; rheight =rect.height;} int g1=0;int g2=0; int histCount=0; //calulate pearsons for existing image; for (int s=1; s<=nslices;s++) {if (currentSlice) {s=currentSliceNo; nslices=s; } ip1 = img1.getProcessor(s); ip2 = img2.getProcessor(s); ip3 = img3.getProcessor(s); for (int y=0; ych1) ch1Min = ch1; if (ch2Maxch2) ch2Min = ch2; N++; if(ch1+ch2!=0) N2++; sumXtotal += ch1; sumYtotal += ch2; if(ch2>0) colocX += ch1; if(ch1>0) colocY += ch2; sumX +=ch1; sumXY += (ch1 * ch2); sumXX += (ch1 * ch1); sumYY += (ch2 *ch2); sumY += ch2; if(ch1>0) Nr++; if(ch2>0)Ng++; //add ch2 value to histogram histCount = histogram[ch2]; histCount++; histogram[ch2]=histCount; } } } } if (ignoreZeroZero) N = N2; //N = N2; //double ch1Mean = sumX/Nr; //double ch2Mean = sumY/Ng; double ch1Mean = sumX/N; double ch2Mean = sumY/N; // IJ.showMessage("Ch1: "+ch1Mean +" Ch2: "+ch2Mean +" count nonzerozero: "+N); pearsons1 = sumXY - (sumX*sumY/N); pearsons2 = sumXX - (sumX*sumX/N); pearsons3 = sumYY - (sumY*sumY/N); //IJ.showMessage("p1: "+pearsons1+" p2: "+pearsons2+" p3: "+pearsons3); r= pearsons1/(Math.sqrt(pearsons2*pearsons3)); double colocM1 = (double)(colocX/sumXtotal); double colocM2 = (double)(colocY/sumYtotal); //calucalte ICQ int countAll=0; int countPos=0; double PDMobs=0; double PDM=0; double ICQ2; int countAll2=0; for (int s=1; s<=nslices;s++) {if (currentSlice) {s=currentSliceNo; nslices=s; } ip1 = img1.getProcessor(s); ip2 = img2.getProcessor(s); ip3 = img3.getProcessor(s); for (int y=0; y<=rheight; y++) {IJ.showStatus("Calculating r for original images. Press 'Esc' to abort"); if (IJ.escapePressed()) {IJ.beep(); return;} for (int x=0; x<=rwidth; x++) {mask = (int)ip3.getPixelValue(x,y); if (indexRoi==0) mask=1; if((useROI)&&(ipmask!=null)) mask = (int)ipmask.getPixelValue(x,y); if (mask!=0) { ch1 = (int)ip1.getPixel(x+xOffset,y+yOffset); ch2 = (int)ip2.getPixel(x+xOffset,y+yOffset); if (ch1+ch2!=0) { PDMobs = ((double)ch1-(double)ch1Mean)*((double)ch2-(double)ch2Mean); if (PDMobs>0) countPos++; countAll2++; } } } } } //IJ.showMessage("count+ = "+countPos +" CountNonZeroPair= "+countAll2); ICQobs = ((double)countPos/(double)countAll2)-0.5; boolean ch3found= false; //do random localisations int rx=0; int ry = 0; int rz=0; int ch3; GaussianBlur gb = new GaussianBlur(); double r2mean=0; int slicesDone = 0; int xCount=0; int xOffset2 = -15; int yOffset2 = -10; int zOffset2=0; int startSlice=1; if(Costes) { xOffset2=0; yOffset2=0; } if (Fay) iterations = 25; if (nslices>=2&&Fay) zOffset2=-1; if (Fay&&nslices>=2) {startSlice=2; nslices-=2; iterations=75; } if (vanS) {xOffset2=-21; startSlice=1; iterations=41; } int blockNumberX = (int)(width/(psf*2)); int blockNumberY = (int)(height/(psf*2)); ImageProcessor blockIndex = new ByteProcessor(blockNumberX,blockNumberY); double [] vSx = new double [41]; double [] vSr = new double [41] ; int ch4=0; int [] zUsed = new int [nslices]; //stackRand = new ImageStack(rwidth,rheight); int blockCount=0; boolean rBlock=true; int vacant; //start randomisations and calculation or Rrands for (int c=1; c<=iterations; c++) { stackRand = new ImageStack(rwidth,rheight); if(Fay) {if (c==26||c==51) {zOffset2 += 1; xOffset2=-15; yOffset2=-10; } if (xOffset2<10) xOffset2+=5; else {xOffset2 = -15; yOffset2+=5;} } if(vanS) { //IJ.showMessage("xOffset: "+xOffset2); xOffset2+=1; } for (int s=startSlice; s<=nslices; s++) { ipRand = new ShortProcessor(rwidth,rheight); slicesDone++; if (currentSlice) {s=currentSliceNo; nslices=s; } IJ.showStatus("Iteration "+c+ "/"+iterations+" Slice: "+s +"/" +nslices+" 'Esc' to abort"); if (IJ.escapePressed()) {IJ.beep(); return;} ip1= img1.getProcessor(s); ip2 = img2.getProcessor(s+zOffset2); ip3 = img3.getProcessor(s); for (int y=0; y1)&&ch2!=0) { ch3 = (int)((Math.random()*(ch2Max-ch2Min))+ch2Min) ; ipRand.putPixel(x,y,ch3); } } if (IJ.escapePressed()) {IJ.beep(); return;} //add to random image } } } if (Costes) gb.blur(ipRand, psf); stackRand.addSlice("Correlation Plot", ipRand); } //random image created now calculate r //reset values for r sumXX=0; sumX=0; sumXY = 0; sumYY = 0; sumY = 0; N = 0; N2=0; int s2=0; sumXtotal = 0; sumYtotal = 0; colocX = 0; colocY = 0; double ICQrand=0; int countPos2=0; countAll=0; //xOffset2=-21; if (IJ.escapePressed()) {IJ.beep(); return;} for (int s=startSlice; s<=nslices;s++) { s2=s; if (Fay&&nslices>=2) s2-=1; if (currentSlice) {s=currentSliceNo; nslices=s; s2=1; } ip1= img1.getProcessor(s); ip2 = stackRand.getProcessor(s2); for (int y=0; ych1) ch1Min = ch1; if (ch2Maxch2) ch2Min = ch2; N++; //Mander calc sumXtotal = sumXtotal+ch1; sumYtotal = sumYtotal+ch2; if(ch2>0) colocX = colocX + ch1; if(ch1>0) colocY = colocY + ch2; if((ch1+ch2!=0)) N2++; sumX = sumX+ch1; sumXY = sumXY + (ch1 * ch2); sumXX = sumXX + (ch1 * ch1); sumYY = sumYY + (ch2 *ch2); sumY = sumY + ch2; if (ch1+ch2!=0) {PDM = ((double)ch1-(double)ch1Mean)*((double)ch2-(double)ch2Mean); if (PDM>0) countPos2++; countAll++; } } } } } if (ignoreZeroZero) N = N2; ICQ2 = ((double)countPos2/(double)countAll)-0.5; ICQ2mean+=ICQ2; if (ICQobs>ICQ2) countICQ++; pearsons1 = sumXY - (sumX*sumY/N); pearsons2 = sumXX - (sumX*sumX/N); pearsons3 = sumYY - (sumY*sumY/N); r2= pearsons1/(Math.sqrt(pearsons2*pearsons3)); if(vanS) { vSx[c-1] = (double)xOffset2; vSr[c-1] = (double)r2 ; } if (r2r2max) r2max = r2; //IJ.write("Random "+ c + "\t"+df3.format(r2)+ "\t"+ df3.format(coloc2M1) + "\t"+df3.format(coloc2M2)); //IJ.write(df3.format(r2)); rVals.append(r2+"\n"); r2mean = r2mean+r2; if (r>r2) colocCount++; sumr2sqrd =sumr2sqrd +(r2*r2); sumr2 = sumr2+r2; sumICQ2sqrd +=(ICQ2*ICQ2); sumICQ2 +=ICQ2; //IJ.write(IJ.d2s(ICQ2,3)); } //done randomisations //calcualte mean Rrand r2mean = r2mean/iterations; r2sd = Math.sqrt(((iterations*(sumr2sqrd))-(sumr2*sumr2))/(iterations*(iterations-1))); ICQ2mean=ICQ2mean/iterations; double ICQ2sd =Math.sqrt(((iterations*(sumICQ2sqrd))-(sumICQ2*sumICQ2))/(iterations*(iterations-1))); double Zscore =(r-r2mean)/r2sd; double ZscoreICQ = (ICQobs-ICQ2mean)/ICQ2sd; String icqPercentile= "<50%"; String Percentile = ""+(iterations-colocCount)+"/"+iterations; //calculate percentage of Rrand that is less than Robs //code from: //http://www.cs.princeton.edu/introcs/26function/MyMath.java.html //Thanks to Bob Dougherty //50*{1 + erf[(V -mean)/(sqrt(2)*sdev)] double fx = 0.5*(1+erf(r-r2mean)/(Math.sqrt(2)*r2sd)); if (fx>=1) fx=1; if (fx<=0) fx=0; String Percentile2 = IJ.d2s(fx,3)+""; if(keep) new ImagePlus("Example random image", stackRand).show(); double percColoc = ((double)colocCount/(double)iterations)*100; double percICQ = ((double)countICQ/(double)iterations)*100; String Headings2 = "Image" +"\tR(obs)" +"\tR(rand) mean±sd" +"\tP-value" +"\tR(rand)>R(obs)" +"\tIterations" +" \tRandomisation" +"\tPSF width\n"; String strPSF = "na"; if (Costes||rBlocks) strPSF = df3.format(psf*pixelSize*2)+ " µm ("+df0.format(psf*2)+" pix.)" ; String str = fileName + "\t"+df3.format(r)+ "\t" +df3.format(r2mean) + "±"+ df3.format(r2sd)+ "\t"+Percentile2+ "\t" + (Percentile )+ "\t" +df0.format(iterations)+ "\t" + randMethod+ "\t" +strPSF; if (textWindow == null) textWindow = new TextWindow("Results", Headings2, str, 400, 250); else { textWindow.getTextPanel().setColumnHeadings(Headings2); textWindow.getTextPanel().appendLine(str); } IJ.selectWindow("Results"); if (showR) new TextWindow( "Random R values", "R(rand)", rVals.toString(),300, 400); if(vanS) {PlotWindow plot = new PlotWindow("CCF","x-translation","Pearsons",vSx,vSr); //r2min = (1.05*r2min); //r2max= (r2max*1.05); //plot.setLimits(-20, 20, r2min, r2max); plot.draw(); } Prefs.set("Rand_ignore.boolean", ignoreZeroZero); Prefs.set("Rand_keep.boolean", keep); Prefs.set("Rand_manPSF.int", manPSF); Prefs.set("Rand_smooth.boolean", smooth); if (Costes) Prefs.set("Rand_iterations.int", (int)iterations); Prefs.set("Rand_ch2L.double",ch2Lambda); Prefs.set("Rand_NA.double",NA); Prefs.set("Rand_pixelSize.double",pixelSize); Prefs.set("Rand_currentSlice.boolean", currentSlice); Prefs.set("Rand_useManPSF.boolean", useManPSF); Prefs.set("Rand_showR.boolean", showR); Prefs.set("Rand_indexRoi.int",indexRoi); Prefs.set("Rand_indexRand.int",indexRand); Prefs.set("Rand_randZ.boolean",randZ); long elapsedTime = (System.currentTimeMillis()-startTime)/1000; String units = "secs"; if (elapsedTime>90) {elapsedTime /= 60; units = "mins";} IJ.showStatus("Done. "+ elapsedTime+ " "+ units); } //code from: //http://www.cs.princeton.edu/introcs/26function/MyMath.java.html public static double erf(double z) { double t = 1.0 / (1.0 + 0.5 * Math.abs(z)); // use Horner's method double ans = 1 - t * Math.exp( -z*z - 1.26551223 + t * ( 1.00002368 + t * ( 0.37409196 + t * ( 0.09678418 + t * (-0.18628806 + t * ( 0.27886807 + t * (-1.13520398 + t * ( 1.48851587 + t * (-0.82215223 + t * ( 0.17087277)))))))))); if (z >= 0) return ans; else return -ans; } } diff --git a/src/main/java/Colocalisation_Threshold.java b/src/main/java/sc/fiji/coloc/Colocalisation_Threshold.java similarity index 99% rename from src/main/java/Colocalisation_Threshold.java rename to src/main/java/sc/fiji/coloc/Colocalisation_Threshold.java index 95ff21c..1e44ca4 100644 --- a/src/main/java/Colocalisation_Threshold.java +++ b/src/main/java/sc/fiji/coloc/Colocalisation_Threshold.java @@ -1,967 +1,969 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ +package sc.fiji.coloc; + //22/4/5 import ij.IJ; import ij.ImagePlus; import ij.ImageStack; import ij.Prefs; import ij.WindowManager; import ij.gui.GenericDialog; import ij.gui.Roi; import ij.measure.Calibration; import ij.plugin.PlugIn; import ij.process.ColorProcessor; import ij.process.FloatProcessor; import ij.process.ImageConverter; import ij.process.ImageProcessor; import ij.process.ImageStatistics; import ij.process.ShortProcessor; import ij.text.TextWindow; import java.awt.Rectangle; import java.text.DecimalFormat; public class Colocalisation_Threshold implements PlugIn { boolean headingsSetCTC; private static int index1=0; private static int index2=1; private static int indexMask; private static boolean displayCounts; private static boolean useMask; private static boolean useRoi; private ImagePlus imp1, imp2; private static boolean threshold; private int ch1, ch2, ch3, nslices, width, height; private int indexRoi= (int)Prefs.get("CTC_indexRoi.int",0); private DecimalFormat df4 = new DecimalFormat("##0.0000"); private DecimalFormat df3 = new DecimalFormat("##0.000"); private DecimalFormat df2 = new DecimalFormat("##0.00"); private DecimalFormat df1 = new DecimalFormat("##0.0"); private DecimalFormat df0 = new DecimalFormat("##0"); private static boolean colocValConst= Prefs.get("CTC_colocConst.boolean", false); private int dualChannelIndex = (int)Prefs.get("CTC_channels.int",0); private static boolean bScatter= Prefs.get("CTC_bScatter.boolean", false); private static boolean bShowLocalisation=Prefs.get("CTC_show.boolean", false); boolean opt0 = Prefs.get("CTC_opt0.boolean", true); boolean opt1 = Prefs.get("CTC_opt1.boolean", true); boolean opt1a = Prefs.get("CTC_opt1a.boolean", true); boolean opt2 = Prefs.get("CTC_opt2.boolean", true); boolean opt3a = Prefs.get("CTC_opt3a.boolean", true); boolean opt3b = Prefs.get("CTC_opt3b.boolean", true); boolean opt4 = Prefs.get("CTC_opt4.boolean", true); boolean opt5 = Prefs.get("CTC_opt5.boolean", true); boolean opt6 = Prefs.get("CTC_opt6.boolean", true); boolean opt7 = Prefs.get("CTC_opt7.boolean", true); boolean opt8 = Prefs.get("CTC_opt8.boolean", true); boolean opt9 = Prefs.get("CTC_opt9.boolean", true); boolean opt10 = Prefs.get("CTC_opt10.boolean", true); String[] dualChannels= { "Red : Green","Red : Blue", "Green : Blue",}; private int colIndex1 = 0; private int colIndex2 = 1; private int colIndex3 = 2; ImageProcessor ip1, ip2, ipmask; ColorProcessor ipColoc; ImagePlus colocPix; private int rwidth, rheight, xOffset, yOffset; String[] chooseROI= { "None","Channel 1", "Channel 2",}; protected static TextWindow textWindow; @Override public void run(String arg) { if (showDialog()) correlate(imp1, imp2); } public boolean showDialog() { int[] wList = WindowManager.getIDList(); if (wList==null) { IJ.noImage(); return false; } String[] titles = new String[wList.length]; String[] chooseMask= new String[wList.length+1]; chooseMask[0]=""; for (int i=0; i=titles.length)index1 = 0; if (index2>=titles.length)index2 = 0; if (indexMask>=titles.length)indexMask = 0; displayCounts = false; threshold = false; GenericDialog gd = new GenericDialog("Colocalisation Thresholds"); gd.addChoice("Channel_1", titles, titles[index1]); gd.addChoice("Channel_2", titles, titles[index2]); gd.addChoice("Use ROI", chooseROI, chooseROI[indexRoi]); // gd.addChoice("Mask channel", chooseMask, chooseMask[indexMask]); gd.addChoice("Channel Combination", dualChannels, dualChannels[dualChannelIndex]); gd.addCheckbox("Show Colocalized Pixel Map",bShowLocalisation); gd.addCheckbox("Use constant intensity for colocalized pixels",colocValConst); gd.addCheckbox("Show Scatter plot",bScatter); gd.addCheckbox("Include zero-zero pixels in threshold calculation",opt0); gd.addCheckbox("Set options",false); gd.addMessage("See: http://uhnresearch.ca/wcif/imagej"); gd.showDialog(); if (gd.wasCanceled()) return false; index1 = gd.getNextChoiceIndex(); index2 = gd.getNextChoiceIndex(); indexRoi = gd.getNextChoiceIndex(); // indexMask = gd.getNextChoiceIndex(); //IJ.showMessage(""+indexMask); dualChannelIndex = gd.getNextChoiceIndex(); bShowLocalisation = gd.getNextBoolean(); colocValConst = gd.getNextBoolean(); bScatter= gd.getNextBoolean(); opt0 =gd.getNextBoolean(); boolean options=gd.getNextBoolean(); imp1 = WindowManager.getImage(wList[index1]); imp2 = WindowManager.getImage(wList[index2]); useMask=false; //IJ.showMessage(""+indexMask); imp1 = WindowManager.getImage(wList[index1]); imp2 = WindowManager.getImage(wList[index2]); if (imp1.getType()!=ImagePlus.GRAY8&&imp1.getType()!=ImagePlus.GRAY16&&imp2.getType()!=ImagePlus.GRAY16 &&imp2.getType()!=ImagePlus.GRAY8) { IJ.showMessage("Image Correlator", "Both images must be 8-bit or 16-bit grayscale."); return false; } ip1 = imp1.getProcessor(); ip2 = imp2.getProcessor(); Roi roi1 = imp1.getRoi(); Roi roi2= imp2.getRoi(); width = imp1.getWidth(); height = imp1.getHeight(); useRoi=true; if (indexRoi== 0) useRoi = false; Rectangle rect =ip1.getRoi(); //IJ.showMessage("index"+rect.width); if ((indexRoi==1)) { if (roi1==null) { useRoi=false; } else { if (roi1.getType()==Roi.RECTANGLE) { IJ.showMessage("Does not work with rectangular ROIs"); return false; } ipmask = imp1.getMask(); //if (keepROIimage) new ImagePlus("Mask",ipmask).show(); rect = ip1.getRoi(); } } if ((indexRoi==2)) { if (roi2==null) { useRoi=false; } else { if (roi2.getType()==Roi.RECTANGLE) { IJ.showMessage("Does not work with rectangular ROIs"); return false; } ipmask = imp2.getMask(); //if (keepROIimage) new ImagePlus("Mask",ipmask).show(); rect = ip2.getRoi(); } } if (indexRoi==0) { xOffset = 0; yOffset = 0; rwidth=width; rheight =height; } else { xOffset = rect.x; yOffset = rect.y; rwidth=rect.width; rheight =rect.height; } //if red-blue if (dualChannelIndex==1) { colIndex2 = 2; colIndex3=1; }; //if blue-green if (dualChannelIndex==2) { colIndex1 = 1; colIndex2 =2 ; colIndex3=0; } if (options) { GenericDialog gd2 = new GenericDialog("Set Results Options"); gd2.addMessage("See online manual for detailed description of these values"); gd2.addCheckbox("Show linear regression solution",opt1a); gd2.addCheckbox("Show thresholds",opt1); gd2.addCheckbox("Pearson's for whole image",opt2); gd2.addCheckbox("Pearson's for image above thresholds",opt3a); gd2.addCheckbox("Pearson's for image below thresholds (should be ~0)",opt3b); gd2.addCheckbox("Mander's original coefficients (threshold = 0)",opt4); gd2.addCheckbox("Mander's using thresholds",opt5); gd2.addCheckbox("Number of colocalized voxels",opt6); gd2.addCheckbox("% Image volume colocalized",opt7); gd2.addCheckbox("% Voxels colocalized",opt8); gd2.addCheckbox("% Intensity colocalized",opt9); gd2.addCheckbox("% Intensity above threshold colocalized",opt10); gd2.showDialog(); if (gd2.wasCanceled()) return false; opt1=gd2.getNextBoolean(); opt1a=gd2.getNextBoolean(); opt2=gd2.getNextBoolean(); opt3a=gd2.getNextBoolean(); opt3b=gd2.getNextBoolean(); opt4=gd2.getNextBoolean(); opt5=gd2.getNextBoolean(); opt6=gd2.getNextBoolean(); opt7=gd2.getNextBoolean(); opt8=gd2.getNextBoolean(); opt9=gd2.getNextBoolean(); opt10=gd2.getNextBoolean(); headingsSetCTC = false; } //IJ.showMessage(""+indexMask); return true; } public void correlate(ImagePlus imp1, ImagePlus imp2) {//IJ.showMessage("mask? "+useMask); String ch1fileName = imp1.getTitle(); String ch2fileName = imp2.getTitle(); //String maskName = impMask.getTitle(); String fileName = ch1fileName + " & " + ch2fileName; ImageProcessor ip1 = imp1.getProcessor(); ImageProcessor ip2 = imp2.getProcessor(); Calibration spatialCalibration = imp1.getCalibration(); ImageProcessor ipMask = imp1.getMask(); if (indexRoi>1) ipMask = imp2.getMask(); // ImageStack imgMask = impMask.getStack(); ImageStack img1 = imp1.getStack(); ImageStack img2 = imp2.getStack(); if (indexRoi== 0) useRoi = false; Rectangle rect1 =ip1.getRoi(); Rectangle rect2 =ip2.getRoi(); Roi roi1 = imp1.getRoi(); Roi roi2= imp2.getRoi(); nslices = imp1.getStackSize(); width = imp1.getWidth(); height = imp1.getHeight(); ipColoc = new ColorProcessor(rwidth,rheight); ImageStack stackColoc = new ImageStack(rwidth,rheight); MinMaxContainer minMax1 = getMinMax(ip1); MinMaxContainer minMax2 = getMinMax(ip2); double ch1threshmin=0; double ch1threshmax=minMax1.max; double ch2threshmin=0; double ch2threshmax=minMax2.max; double pearsons1 = 0; double pearsons2 = 0; double pearsons3 = 0; double r = 1; pearsons1 =0; pearsons2 = 0; pearsons3 = 0; double r2= 1; boolean thresholdFound=false; boolean unsigned = true; int count =0; double sumX = 0; double sumXY = 0; double sumXX = 0; double sumYY = 0; double sumY = 0; double colocX = 0; double colocY = 0; double countX = 0; double countY = 0; double sumXYm=0; int Nch1=0,Nch2=0; double oldMax=0; int sumCh2gtT =0; int sumCh1gtT =0; int N = 0; int N2 = 0; int Nzero=0; int Nch1gtT=0; int Nch2gtT=0; double oldMax2=0; int ch1Max=(int)minMax1.max; int ch2Max=(int)minMax2.max; int ch1Min = (int)minMax1.min; int ch2Min = (int)minMax2.min; int ch1ROIMax=0; int ch2ROIMax=0; String Headings = "\t \t \t \t \t \t \t \n"; ImageProcessor plot32 = new FloatProcessor(256, 256); ImageProcessor plot16 = new ShortProcessor(256, 256); int scaledXvalue =0; int scaledYvalue=0; if (ch1Max<255) ch1Max=255; if (ch2Max<255) ch2Max=255; double ch1Scaling = (double)255/(double)ch1Max; double ch2Scaling = (double)255/(double)ch2Max; // scaling for both channels should be the same so the scatterplot is not squewed double chScaling = 1; if (ch1Scaling>ch2Scaling) chScaling = ch1Scaling; else chScaling = ch2Scaling; boolean divByZero=false; StringBuffer sb= new StringBuffer(); String str=""; int i = imp1.getCurrentSlice(); double bBest=0; double mBest = 0; double bestr2=1; double ch1BestThresh=0; double ch2BestThresh=0; String mString; //start regression IJ.showStatus("1/4: Performing regression. Press 'Esc' to abort"); int ch1Sum=0; int ch2Sum=0; int ch3Sum=0; double ch1mch1MeanSqSum=0; double ch2mch2MeanSqSum= 0; double ch3mch3MeanSqSum= 0; ImageProcessor ipPlot = new ShortProcessor (256,256); int mask=0; if (indexRoi== 0) useRoi = false; Rectangle rect =ip1.getRoi(); if ((indexRoi==1)) { if (roi1==null) { useRoi=false; } else { if (roi1.getType()==Roi.RECTANGLE) { IJ.showMessage("Does not work with rectangular ROIs"); return; } ipMask = imp1.getMask(); //if (keepROIimage) new ImagePlus("Mask",ipmask).show(); rect = ip1.getRoi(); } } if ((indexRoi==2)) { if (roi2==null) { useRoi=false; } else { if (roi2.getType()==Roi.RECTANGLE) { IJ.showMessage("Does not work with rectangular ROIs"); return ; } ipMask = imp2.getMask(); //if (keepROIimage) new ImagePlus("Mask",ipmask).show(); rect = ip2.getRoi(); } } if (useRoi==false) { xOffset = 0; yOffset = 0; rwidth=width; rheight =height; } else { xOffset = rect.x; yOffset = rect.y; rwidth=rect.width; rheight =rect.height; } //new ImagePlus("Mask",ipMask).show(); //get means for (int s=1; s<=nslices; s++) { if (IJ.escapePressed()) { IJ.beep(); return; } ip1 = img1.getProcessor(s); ip2 = img2.getProcessor(s); //ipMask = imgMask.getProcessor(s); for (int y=0; ych1ROIMax) ch1ROIMax=ch1; if (ch2>ch2ROIMax) ch2ROIMax=ch2; ch3 = ch1+ch2; ch1Sum+=ch1; ch2Sum+=ch2; ch3Sum+=ch3; if (ch1+ch2!=0) N++; } } } } double ch1Mean = ch1Sum/N; double ch2Mean = ch2Sum/N; double ch3Mean = ch3Sum/N; N=0; // Do some rather simple performance testing long startTime = System.currentTimeMillis(); //calulate variances for (int s=1; s<=nslices; s++) { if (IJ.escapePressed()) { IJ.beep(); return; } ip1 = img1.getProcessor(s); ip2 = img2.getProcessor(s); //ipMask = imgMask.getProcessor(s); for (int y=0; yr2*r2)) { ch1BestThresh=ch1threshmax; bestr2=r2; } //if our r is close to our level of tolerance then set threshold has been found if ((r2-tolerance) )thresholdFound = true; //if we've reached ch1 =1 then we've exhausted posibilities if (Math.round(ch1threshmax)==0) thresholdFound =true; oldMax= newMax; //change threshold max if (r2>=0) { if ((r2>=r2Prev)&&(!divByZero)) newMax = newMax/2; if ((r20) { Nch1++; mCh2coloc = mCh2coloc+ch2; } if (ch2>0) { Nch2++; mCh1coloc = mCh1coloc+ch1; } if ((double)ch2>=ch2threshmax) { Nch2gtT++; sumCh2gtT = sumCh2gtT+ch2; colocX=colocX+ch1; } if ((double)ch1>=ch1threshmax) { Nch1gtT++; sumCh1gtT = sumCh1gtT+ch1; colocY=colocY+ch2; } if (((double)ch1>ch1threshmax)&&((double)ch2>ch2threshmax)) { sumColocCh1 = sumColocCh1+ch1; sumColocCh2 = sumColocCh2+ch2; Ncoloc++; //calc pearsons sumX = sumX+ch1; sumXY = sumXY + (ch1 * ch2); sumXX = sumXX + (ch1 * ch1); sumYY = sumYY + (ch2 *ch2); sumY = sumY + ch2; } } } } } //IJ.showMessage("Totoal"+N+" N0:"+Nzero+" Nc :"+ Ncoloc); pearsons1 = sumXY - (sumX*sumY/Ncoloc); pearsons2 = sumXX - (sumX*sumX/Ncoloc); pearsons3 = sumYY - (sumY*sumY/Ncoloc); //Pearsons for coloclaised volume double Rcoloc= pearsons1/(Math.sqrt(pearsons2*pearsons3)); //Mander's original //[i.e. E(ch1if ch2>0) ÷ E(ch1total)] double M1 = mCh1coloc /sumCh1total; double M2 = mCh2coloc /sumCh2total; //Manders using threshold //[i.e. E(ch1 if ch2>ch2threshold) ÷ (Ech1total)] double colocM1 = (double) colocX/(double)sumCh1total; double colocM2 = (double) colocY/(double)sumCh2total; //as in Coste's paper //[i.e. E(ch1>ch1threshold) ÷ E(ch1total)] double colocC1 = (double)sumCh1gtT/ (double)sumCh1total; double colocC2 = (double)sumCh2gtT/(double)sumCh2total; //Imaris percentage volume double percVolCh1 = (double)Ncoloc/ (double)Nch1gtT; double percVolCh2 = (double)Ncoloc/(double)Nch2gtT; double percTotCh1 = (double) sumColocCh1/ (double)sumCh1total; double percTotCh2 = (double) sumColocCh2/ (double)sumCh2total; //Imaris percentage material double percMatCh1 = (double) sumColocCh1/(double)sumCh1gtT; double percMatCh2 = (double)sumColocCh2/(double)sumCh2gtT; //IJ.showMessage("Totoal"+N+" N0:"+Nzero+" Nc :"+ Ncoloc); sb.append(fileName+"\n"); //if (!useMask) maskName = ""; str = fileName +"\t"+"ROI" + indexRoi+"\t"; str += opt0 ? "incl.\t" : "excl.\t"; if (opt2) str+= df3.format(rTotal)+ "\t"; if (opt1a) str+= df3.format(m)+ "\t "+df1.format(b)+ "\t"; if (opt1) str+= IJ.d2s(ch1threshmax,0)+"\t"+IJ.d2s(ch2threshmax,0)+"\t"; if (opt3a) str+= df4.format(Rcoloc) +"\t"; if (opt3b) str+= df3.format(bestr2)+"\t"; if (opt4) str+= df4.format(M1)+ "\t "+df4.format(M2)+"\t"; if (opt5) str+= df4.format(colocM1)+ "\t"+df4.format(colocM2)+"\t"; if (opt6) str+= Ncoloc+ "\t"; if (opt7) str+= df2.format(((double)Ncoloc*(double)100)/((double)width*(double)height*(double)nslices))+"%\t"; if (opt8) str+= df2.format(percVolCh1*100 )+ "%\t"; if (opt8) str+= df2.format(percVolCh2*100 )+ "%\t"; if (opt9) str+= df2.format(percTotCh1*100 )+ "%\t"; if (opt9) str+= df2.format(percTotCh2*100 )+ "%\t"; if (opt10) str+= df2.format(percMatCh1*100 )+ "%\t"; if (opt10) str+= df2.format(percMatCh2*100 )+ "%\t"; String heading = "Images\tMask\tZeroZero\t"; if (opt2) heading += "Rtotal\t"; if (opt1a) heading += "m\tb\t"; if (opt1) heading += "Ch1 thresh\tCh2 thresh\t"; if (opt3a) heading += "Rcoloc\t"; if (opt3b) heading += "RcolocPixelsImageThresh1)&&((double)ch2>colocPixelsImageThresh2)) { colocInt=255; if (!colocValConst) { colocInt = (int)Math.sqrt(ch1*ch2); } color[colIndex1 ]=(int)colocInt; color[colIndex2 ]=(int)colocInt; color[colIndex3 ]=(int)colocInt; ipColoc.putPixel(x,y,color ); } } } } //IJ.showMessage(stackColoc.getWidth()+ " - " + ipColoc.getWidth()); stackColocPix.addSlice("Colocalized Pixel Map Image", ipColoc); } return stackColocPix; } private class MinMaxContainer { public double min; public double max; public MinMaxContainer(double min, double max){ this.min = min; this.max = max; } } } diff --git a/src/main/java/algorithms/Accumulator.java b/src/main/java/sc/fiji/coloc/algorithms/Accumulator.java similarity index 98% rename from src/main/java/algorithms/Accumulator.java rename to src/main/java/sc/fiji/coloc/algorithms/Accumulator.java index bf7923b..66eb70d 100644 --- a/src/main/java/algorithms/Accumulator.java +++ b/src/main/java/sc/fiji/coloc/algorithms/Accumulator.java @@ -1,109 +1,109 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; import net.imglib2.TwinCursor; import net.imglib2.type.numeric.RealType; /** * A class allowing an easy accumulation of values visited by a * TwinCursor. After instantiation the sum of channel one, * channel two, products with them self and a product of both of * them will be available. It additionally provides the possibility * to subtract values from the data before the adding them to the * sum. * * @author Johannes Schindelin and Tom Kazimiers */ public abstract class Accumulator> { protected double x, y, xx, xy, yy; protected int count; /** * The two values x and y from each cursor iteration to get * summed up as single values and their combinations. */ public Accumulator(final TwinCursor cursor) { this(cursor, false, 0.0d, 0.0d); } /** * The two values (x - xDiff) and (y - yDiff) from each cursor * iteration to get summed up as single values and their combinations. */ public Accumulator(final TwinCursor cursor, double xDiff, double yDiff) { this(cursor, true, xDiff, yDiff); } protected Accumulator(final TwinCursor cursor, boolean substract, double xDiff, double yDiff) { while (cursor.hasNext()) { cursor.fwd(); T type1 = cursor.getFirst(); T type2 = cursor.getSecond(); if (!accept(type1, type2)) continue; double value1 = type1.getRealDouble(); double value2 = type2.getRealDouble(); if (substract) { value1 -= xDiff; value2 -= yDiff; } x += value1; y += value2; xx += value1 * value1; xy += value1 * value2; yy += value2 * value2; count++; } } public abstract boolean accept(T type1, T type2); public double getX() { return x; } public double getY() { return y; } public double getXX() { return xx; } public double getXY() { return xy; } public double getYY() { return yy; } public int getCount() { return count; } } diff --git a/src/main/java/algorithms/Algorithm.java b/src/main/java/sc/fiji/coloc/algorithms/Algorithm.java similarity index 93% rename from src/main/java/algorithms/Algorithm.java rename to src/main/java/sc/fiji/coloc/algorithms/Algorithm.java index 70823b4..46ac322 100644 --- a/src/main/java/algorithms/Algorithm.java +++ b/src/main/java/sc/fiji/coloc/algorithms/Algorithm.java @@ -1,92 +1,92 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; import java.util.ArrayList; import java.util.List; import net.imglib2.type.numeric.RealType; -import gadgets.DataContainer; -import results.ResultHandler; -import results.Warning; +import sc.fiji.coloc.gadgets.DataContainer; +import sc.fiji.coloc.results.ResultHandler; +import sc.fiji.coloc.results.Warning; /** * An algorithm is an abstraction of techniques like the * calculation of the Persons coefficient or Li'S ICQ. It * allows to separate initialization and execution of * such an algorithm. */ public abstract class Algorithm> { // a name for the algorithm protected String name; /* a list of warnings that can be filled by the * execute method */ List warnings = new ArrayList(); public Algorithm(String name) { this.name = name; } /** * Executes the previously initialized {@link Algorithm}. */ public abstract void execute(DataContainer container) throws MissingPreconditionException; public String getName() { return name; } /** * A method to give the algorithm the opportunity to let * its results being processed by the passed handler. * By default this methods passes the collected warnings to * the handler and sub-classes should make use of this by * adding custom behavior and call the super class. * * @param handler The ResultHandler to process the results. */ public void processResults(ResultHandler handler) { for (Warning w : warnings) handler.handleWarning( w ); } /** * Gets a reference to the warnings. * * @return A reference to the warnings list */ public List getWarnings() { return warnings; } /** * Adds a warning to the list of warnings. * * @param shortMsg A short descriptive message * @param longMsg A long message */ protected void addWarning(String shortMsg, String longMsg) { warnings.add( new Warning(shortMsg, longMsg) ); } } diff --git a/src/main/java/algorithms/AutoThresholdRegression.java b/src/main/java/sc/fiji/coloc/algorithms/AutoThresholdRegression.java similarity index 98% rename from src/main/java/algorithms/AutoThresholdRegression.java rename to src/main/java/sc/fiji/coloc/algorithms/AutoThresholdRegression.java index 78e120f..b91b356 100644 --- a/src/main/java/algorithms/AutoThresholdRegression.java +++ b/src/main/java/sc/fiji/coloc/algorithms/AutoThresholdRegression.java @@ -1,349 +1,349 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; import net.imglib2.RandomAccessibleInterval; import net.imglib2.TwinCursor; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; -import gadgets.DataContainer; -import gadgets.ThresholdMode; -import results.ResultHandler; +import sc.fiji.coloc.gadgets.DataContainer; +import sc.fiji.coloc.gadgets.ThresholdMode; +import sc.fiji.coloc.results.ResultHandler; /** * A class implementing the automatic finding of a threshold * used for Pearson colocalisation calculation. */ public class AutoThresholdRegression> extends Algorithm { // Identifiers for choosing which implementation to use public enum Implementation {Costes, Bisection}; Implementation implementation = Implementation.Bisection; /* The threshold for ratio of y-intercept : y-mean to raise a warning about * it being to high or low, meaning far from zero. Don't use y-max as before, * since this could be a very high value outlier. Mean is probably more * reliable. */ final double warnYInterceptToYMeanRatioThreshold = 0.01; // the slope and and intercept of the regression line double autoThresholdSlope = 0.0, autoThresholdIntercept = 0.0; /* The thresholds for both image channels. Pixels below a lower * threshold do NOT include the threshold and pixels above an upper * one will NOT either. Pixels "in between (and including)" thresholds * do include the threshold values. */ T ch1MinThreshold, ch1MaxThreshold, ch2MinThreshold, ch2MaxThreshold; // additional information double bToYMeanRatio = 0.0; //This is the Pearson's correlation we will use for further calculations PearsonsCorrelation pearsonsCorrellation; public AutoThresholdRegression(PearsonsCorrelation pc) { this(pc, Implementation.Costes); } public AutoThresholdRegression(PearsonsCorrelation pc, Implementation impl) { super("auto threshold regression"); pearsonsCorrellation = pc; implementation = impl; } @Override public void execute(DataContainer container) throws MissingPreconditionException { // get the 2 images for the calculation of Pearson's final RandomAccessibleInterval img1 = container.getSourceImage1(); final RandomAccessibleInterval img2 = container.getSourceImage2(); final RandomAccessibleInterval mask = container.getMask(); double ch1Mean = container.getMeanCh1(); double ch2Mean = container.getMeanCh2(); double combinedMean = ch1Mean + ch2Mean; // get the cursors for iterating through pixels in images TwinCursor cursor = new TwinCursor( img1.randomAccess(), img2.randomAccess(), Views.iterable(mask).localizingCursor()); // variables for summing up the double ch1MeanDiffSum = 0.0, ch2MeanDiffSum = 0.0, combinedMeanDiffSum = 0.0; double combinedSum = 0.0; int N = 0, NZero = 0; // reference image data type final T type = cursor.getFirst(); while (cursor.hasNext()) { cursor.fwd(); double ch1 = cursor.getFirst().getRealDouble(); double ch2 = cursor.getSecond().getRealDouble(); combinedSum = ch1 + ch2; // TODO: Shouldn't the whole calculation take only pixels // into account that are combined above zero? And not just // the denominator (like it is done now)? // calculate the numerators for the variances ch1MeanDiffSum += (ch1 - ch1Mean) * (ch1 - ch1Mean); ch2MeanDiffSum += (ch2 - ch2Mean) * (ch2 - ch2Mean); combinedMeanDiffSum += (combinedSum - combinedMean) * (combinedSum - combinedMean); // count only pixels that are above zero if ( (ch1 + ch2) > 0.00001) NZero++; N++; } double ch1Variance = ch1MeanDiffSum / (N - 1); double ch2Variance = ch2MeanDiffSum / (N - 1); double combinedVariance = combinedMeanDiffSum / (N - 1.0); //http://mathworld.wolfram.com/Covariance.html //?2 = X2?(X)2 // = E[X2]?(E[X])2 //var (x+y) = var(x)+var(y)+2(covar(x,y)); //2(covar(x,y)) = var(x+y) - var(x)-var(y); double ch1ch2Covariance = 0.5*(combinedVariance - (ch1Variance + ch2Variance)); // calculate regression parameters double denom = 2*ch1ch2Covariance; double num = ch2Variance - ch1Variance + Math.sqrt( (ch2Variance - ch1Variance) * (ch2Variance - ch1Variance) + (4 * ch1ch2Covariance *ch1ch2Covariance) ); final double m = num/denom; final double b = ch2Mean - m*ch1Mean ; // A stepper that walks thresholds Stepper stepper; // to map working thresholds to channels ChannelMapper mapper; // let working threshold walk on channel one if the regression line // leans more towards the abscissa (which represents channel one) for // positive and negative correlation. if (m > -1 && m < 1.0) { // Map working threshold to channel one (because channel one has a // larger maximum value. mapper = new ChannelMapper() { @Override public double getCh1Threshold(double t) { return t; } @Override public double getCh2Threshold(double t) { return (t * m) + b; } }; // Select a stepper if (implementation == Implementation.Bisection) { // Start at the midpoint of channel one stepper = new BisectionStepper( Math.abs(container.getMaxCh1() + container.getMinCh1()) * 0.5, container.getMaxCh1()); } else { stepper = new SimpleStepper(container.getMaxCh1()); } } else { // Map working threshold to channel two (because channel two has a // larger maximum value. mapper = new ChannelMapper() { @Override public double getCh1Threshold(double t) { return (t - b) / m; } @Override public double getCh2Threshold(double t) { return t; } }; // Select a stepper if (implementation == Implementation.Bisection) { // Start at the midpoint of channel two stepper = new BisectionStepper( Math.abs(container.getMaxCh2() + container.getMinCh2()) * 0.5, container.getMaxCh2()); } else { stepper = new SimpleStepper(container.getMaxCh2()); } } // Min threshold not yet implemented double ch1ThreshMax = container.getMaxCh1(); double ch2ThreshMax = container.getMaxCh2(); // define some image type specific threshold variables T thresholdCh1 = type.createVariable(); T thresholdCh2 = type.createVariable(); // reset the previously created cursor cursor.reset(); /* Get min and max value of image data type. Since type of image * one and two are the same, we dont't need to distinguish them. */ T dummyT = type.createVariable(); final double minVal = dummyT.getMinValue(); final double maxVal = dummyT.getMaxValue(); // do regression while (!stepper.isFinished()) { // round ch1 threshold and compute ch2 threshold ch1ThreshMax = Math.round(mapper.getCh1Threshold(stepper.getValue())); ch2ThreshMax = Math.round(mapper.getCh2Threshold(stepper.getValue())); /* Make sure we don't get overflow the image type specific threshold variables * if the image data type doesn't support this value. */ thresholdCh1.setReal(clamp(ch1ThreshMax, minVal, maxVal)); thresholdCh2.setReal(clamp(ch2ThreshMax, minVal, maxVal)); try { // do persons calculation within the limits final double currentPersonsR = pearsonsCorrellation.calculatePearsons(cursor, ch1Mean, ch2Mean, thresholdCh1, thresholdCh2, ThresholdMode.Below); stepper.update(currentPersonsR); } catch (MissingPreconditionException e) { /* the exception that could occur is due to numerical * problems within the Pearsons calculation. */ stepper.update(Double.NaN); } // reset the cursor to reuse it cursor.reset(); } /* Store the new results. The lower thresholds are the types * min value for now. For the max threshold we do a clipping * to make it fit into the image type. */ ch1MinThreshold = type.createVariable(); ch1MinThreshold.setReal(minVal); ch1MaxThreshold = type.createVariable(); ch1MaxThreshold.setReal(clamp(ch1ThreshMax, minVal, maxVal)); ch2MinThreshold = type.createVariable(); ch2MinThreshold.setReal(minVal); ch2MaxThreshold = type.createVariable(); ch2MaxThreshold.setReal(clamp(ch2ThreshMax, minVal, maxVal)); autoThresholdSlope = m; autoThresholdIntercept = b; bToYMeanRatio = b / container.getMeanCh2(); // add warnings if values are not in tolerance range if ( Math.abs(bToYMeanRatio) > warnYInterceptToYMeanRatioThreshold ) { addWarning("y-intercept far from zero", "The ratio of the y-intercept of the auto threshold regression " + "line to the mean value of Channel 2 is high. This means the " + "y-intercept is far from zero, implying a significant positive " + "or negative zero offset in the image data intensities. Maybe " + "you should use a ROI. Maybe do a background subtraction in " + "both channels. Make sure you didn't clip off the low " + "intensities to zero. This might not affect Pearson's " + "correlation values very much, but might harm other results."); } // add warning if threshold is above the image mean if (ch1ThreshMax > ch1Mean) { addWarning("Threshold of ch. 1 too high", "Too few pixels are taken into account for above-threshold calculations. The threshold is above the channel's mean."); } if (ch2ThreshMax > ch2Mean) { addWarning("Threshold of ch. 2 too high", "Too few pixels are taken into account for above-threshold calculations. The threshold is above the channel's mean."); } // add warnings if values are below lowest pixel value of images if ( ch1ThreshMax < container.getMinCh1() || ch2ThreshMax < container.getMinCh2() ) { String msg = "The auto threshold method could not find a positive " + "threshold, so thresholded results are meaningless."; msg += implementation == Implementation.Costes ? "" : " Maybe you should try classic thresholding."; addWarning("thresholds too low", msg); } } /** * Clamp a value to a min or max value. If the value is below min, min is * returned. Accordingly, max is returned if the value is larger. If it is * neither, the value itself is returned. */ public static double clamp(double val, double min, double max) { return min > val ? min : max < val ? max : val; } @Override public void processResults(ResultHandler handler) { super.processResults(handler); handler.handleValue( "m (slope)", autoThresholdSlope , 2 ); handler.handleValue( "b (y-intercept)", autoThresholdIntercept, 2 ); handler.handleValue( "b to y-mean ratio", bToYMeanRatio, 2 ); handler.handleValue( "Ch1 Max Threshold", ch1MaxThreshold.getRealDouble(), 2); handler.handleValue( "Ch2 Max Threshold", ch2MaxThreshold.getRealDouble(), 2); handler.handleValue( "Threshold regression", implementation.toString()); } public double getBToYMeanRatio() { return bToYMeanRatio; } public double getWarnYInterceptToYMaxRatioThreshold() { return warnYInterceptToYMeanRatioThreshold; } public double getAutoThresholdSlope() { return autoThresholdSlope; } public double getAutoThresholdIntercept() { return autoThresholdIntercept; } public T getCh1MinThreshold() { return ch1MinThreshold; } public T getCh1MaxThreshold() { return ch1MaxThreshold; } public T getCh2MinThreshold() { return ch2MinThreshold; } public T getCh2MaxThreshold() { return ch2MaxThreshold; } } diff --git a/src/main/java/algorithms/BisectionStepper.java b/src/main/java/sc/fiji/coloc/algorithms/BisectionStepper.java similarity index 98% rename from src/main/java/algorithms/BisectionStepper.java rename to src/main/java/sc/fiji/coloc/algorithms/BisectionStepper.java index 8970c25..de98904 100644 --- a/src/main/java/algorithms/BisectionStepper.java +++ b/src/main/java/sc/fiji/coloc/algorithms/BisectionStepper.java @@ -1,91 +1,91 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; /** * Try to converge a threshold based on an update value condition. If the * update value is larger zero, the threshold is lowered by half the distance * between the last thresholds. If the update value falls below zero or is not * a number, the threshold is increased by such a half step. * * @author Tom Kazimiers * */ public class BisectionStepper extends Stepper { protected double threshold1; protected double threshold2; protected double thrDiff = Double.NaN; protected int iterations = 0; protected int maxIterations = 100; /** * Initialize the bisection stepper with a start threshold and its * last threshold * * @param threshold The current threshold * @param lastThreshold The last threshold */ public BisectionStepper(double threshold, double lastThreshold) { threshold1 = threshold; threshold2 = lastThreshold; thrDiff = Math.abs(threshold1 - threshold2); } /** * Update threshold by a bisection step. If {@code value} is below zero or * not a number, the step is made upwards. If it is above zero, the stoep is * downwards. */ @Override public void update(double value) { // update working thresholds for next iteration threshold2 = threshold1; if (Double.NaN == value || value < 0) { // we went too far, increase by the absolute half threshold1 = threshold1 + thrDiff * 0.5; } else if (value > 0) { // as long as r > 0 we go half the way down threshold1 = threshold1 - thrDiff * 0.5; } // Update difference to last threshold thrDiff = Math.abs(threshold1 - threshold2); // Update update counter iterations++; } /** * Get current threshold. */ @Override public double getValue() { return threshold1; } /** * If the difference between both thresholds is < 1, we consider * that as reasonable close to abort the regression. */ @Override public boolean isFinished() { return iterations > maxIterations || thrDiff < 1.0; } } diff --git a/src/main/java/algorithms/ChannelMapper.java b/src/main/java/sc/fiji/coloc/algorithms/ChannelMapper.java similarity index 96% rename from src/main/java/algorithms/ChannelMapper.java rename to src/main/java/sc/fiji/coloc/algorithms/ChannelMapper.java index f26c564..61d70af 100644 --- a/src/main/java/algorithms/ChannelMapper.java +++ b/src/main/java/sc/fiji/coloc/algorithms/ChannelMapper.java @@ -1,33 +1,33 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; /** * A channel mapper should map an input value to either channel one or * channel two. * * @author Tom Kazimiers */ public interface ChannelMapper { double getCh1Threshold(double t); double getCh2Threshold(double t); } diff --git a/src/main/java/algorithms/CostesSignificanceTest.java b/src/main/java/sc/fiji/coloc/algorithms/CostesSignificanceTest.java similarity index 98% rename from src/main/java/algorithms/CostesSignificanceTest.java rename to src/main/java/sc/fiji/coloc/algorithms/CostesSignificanceTest.java index f30db15..f092315 100644 --- a/src/main/java/algorithms/CostesSignificanceTest.java +++ b/src/main/java/sc/fiji/coloc/algorithms/CostesSignificanceTest.java @@ -1,421 +1,421 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import net.imglib2.Cursor; import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.algorithm.gauss.Gauss; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; import net.imglib2.img.array.ArrayImgFactory; import net.imglib2.roi.RectangleRegionOfInterest; import net.imglib2.type.NativeType; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; -import gadgets.DataContainer; -import gadgets.DataContainer.MaskType; -import gadgets.Statistics; -import results.ResultHandler; +import sc.fiji.coloc.gadgets.DataContainer; +import sc.fiji.coloc.gadgets.DataContainer.MaskType; +import sc.fiji.coloc.gadgets.Statistics; +import sc.fiji.coloc.results.ResultHandler; public class CostesSignificanceTest & NativeType> extends Algorithm { // radius of the PSF in pixels, its size *must* for now be three protected double[] psfRadius = new double[3]; // indicates if the shuffled images should be shown as a result boolean showShuffledImages = false; // the number of randomization tests int nrRandomizations; // the shuffled image last worked on Img smoothedShuffledImage; // the Pearson's algorithm (that should have been run before) PearsonsCorrelation pearsonsCorrelation; // a list of resulting Pearsons values from the randomized images List shuffledPearsonsResults; /* the amount of Pearson's values with shuffled data * that has the value of the original one or is larger. */ int shuffledPearsonsNotLessOriginal = 0; // The mean of the shuffled Pearson values double shuffledMean = 0.0; // The standard derivation of the shuffled Pearson values double shuffledStdDerivation = 0.0; /* The Costes P-Value which is the probability that * Pearsons r is different from the mean of the randomized * r values. */ double costesPValue; // the maximum retries in case of Pearson numerical errors protected final int maxErrorRetries = 3; /** * Creates a new Costes significance test object by using a * cube block with the given edge length. * * @param psfRadiusInPixels The edge width of the 3D cube block. */ public CostesSignificanceTest(PearsonsCorrelation pc, int psfRadiusInPixels, int nrRandomizations, boolean showShuffledImages) { super("Costes significance test"); this.pearsonsCorrelation = pc; Arrays.fill(psfRadius, psfRadiusInPixels); this.nrRandomizations = nrRandomizations; this.showShuffledImages = showShuffledImages; } /** * Builds a list of blocks that represent the images. To * do so we create a list image ROI cursors. If a block * does not fit into the image it will get a out-of-bounds * strategy. */ @Override public void execute(DataContainer container) throws MissingPreconditionException { final RandomAccessibleInterval img1 = container.getSourceImage1(); final RandomAccessibleInterval img2 = container.getSourceImage2(); final RandomAccessibleInterval mask = container.getMask(); /* To determine the number of needed blocks, we need * the effective dimensions of the image. Since the * mask is responsible for this, we ask for its size. */ long[] dimensions = container.getMaskBBSize(); int nrDimensions = dimensions.length; // calculate the needed number of blocks per image int nrBlocksPerImage = 1; long[] nrBlocksPerDimension = new long[3]; for (int i = 0; i < nrDimensions; i++) { // add the amount of full fitting blocks to the counter nrBlocksPerDimension[i] = (long) (dimensions[i] / psfRadius[i]); // if there is the need for a out-of-bounds block, increase count if ( dimensions[i] % psfRadius[i] != 0 ) nrBlocksPerDimension[i]++; // increase total count nrBlocksPerImage *= nrBlocksPerDimension[i]; } /* For creating the input and output blocks we need * offset and size as floating point array. */ double[] floatOffset = new double[ img1.numDimensions() ]; long[] longOffset = container.getMaskBBOffset(); for (int i=0; i< longOffset.length; ++i ) floatOffset[i] = longOffset[i]; double[] floatDimensions = new double[ nrDimensions ]; for (int i=0; i< nrDimensions; ++i ) floatDimensions[i] = dimensions[i]; /* Create the ROI blocks. The image dimensions might not be * divided cleanly by the block size. Therefore we need to * have an out of bounds strategy -- a mirror. */ List> blockIntervals; blockIntervals = new ArrayList>( nrBlocksPerImage ); RandomAccessible< T> infiniteImg = Views.extendMirrorSingle( img1 ); generateBlocks( infiniteImg, blockIntervals, floatOffset, floatDimensions); // create input and output cursors and store them along their offset List> inputBlocks = new ArrayList>(nrBlocksPerImage); List> outputBlocks = new ArrayList>(nrBlocksPerImage); for (IterableInterval roiIt : blockIntervals) { inputBlocks.add(roiIt.localizingCursor()); outputBlocks.add(roiIt.localizingCursor()); } // we will need a zero variable final T zero = img1.randomAccess().get().createVariable(); zero.setZero(); /* Create a new image to contain the shuffled data and with * same dimensions as the original data. */ final long[] dims = new long[img1.numDimensions()]; img1.dimensions(dims); ImgFactory factory = new ArrayImgFactory(); Img shuffledImage = factory.create( dims, img1.randomAccess().get().createVariable() ); RandomAccessible< T> infiniteShuffledImage = Views.extendValue(shuffledImage, zero ); // create a double version of the PSF for the smoothing double[] smoothingPsfRadius = new double[nrDimensions]; for (int i = 0; i < nrDimensions; i++) { smoothingPsfRadius[i] = (double) psfRadius[i]; } // the retry count for error cases int retries = 0; shuffledPearsonsResults = new ArrayList(); for (int i=0; i < nrRandomizations; i++) { // shuffle the list Collections.shuffle( inputBlocks ); // get an output random access RandomAccess output = infiniteShuffledImage.randomAccess(); // check if a mask is in use and further actions are needed if (container.getMaskType() == MaskType.Irregular) { Cursor siCursor = shuffledImage.cursor(); // black the whole intermediate image, just in case we have irr. masks while (siCursor.hasNext()) { siCursor.fwd(); output.setPosition(siCursor); output.get().setZero(); } } // write out the shuffled input blocks into the output blocks for (int j=0; j inputCursor = inputBlocks.get(j); Cursor outputCursor = outputBlocks.get(j); /* Iterate over both blocks. Theoretically the iteration * order could be different. Because we are dealing with * randomized data anyway, this is not a problem here. */ while (inputCursor.hasNext() && outputCursor.hasNext()) { inputCursor.fwd(); outputCursor.fwd(); output.setPosition(outputCursor); // write the data output.get().set( inputCursor.get() ); } /* Reset both cursors. If we wouldn't do that, the * image contents would not change on the next pass. */ inputCursor.reset(); outputCursor.reset(); } smoothedShuffledImage = Gauss.inFloat( smoothingPsfRadius, shuffledImage); try { // calculate correlation value... double pValue = pearsonsCorrelation.calculatePearsons( smoothedShuffledImage, img2, mask); // ...and add it to the results list shuffledPearsonsResults.add( pValue ); } catch (MissingPreconditionException e) { /* if the randomized input data does not suit due to numerical * problems, try it three times again and then fail. */ if (retries < maxErrorRetries) { // increase retry count and the number of randomizations retries++; nrRandomizations++; } else { throw new MissingPreconditionException("Maximum retries have been made (" + + retries + "), but errors keep on coming: " + e.getMessage(), e); } } } // calculate statistics on the randomized values and the original one double originalVal = pearsonsCorrelation.getPearsonsCorrelationValue(); calculateStatistics(shuffledPearsonsResults, originalVal); } /** * This method drives the creation of RegionOfInterest-Cursors on the given image. * It does not matter if those generated blocks are used for reading and/or * writing. The resulting blocks are put into the given list and are in the * responsibility of the caller, i.e. he or she must make sure the cursors get * closed on some point in time. * * @param img The image to create cursors on. * @param blockList The list to put newly created cursors into * @param offset * @param size */ protected void generateBlocks(RandomAccessible img, List> blockList, double[] offset, double[] size) throws MissingPreconditionException { // get the number of dimensions int nrDimensions = img.numDimensions(); if (nrDimensions == 2) { // for a 2D image... generateBlocksXY(img, blockList, offset, size); } else if (nrDimensions == 3) { // for a 3D image... final double depth = size[2]; double z; double originalZ = offset[2]; // go through the depth in steps of block depth for ( z = psfRadius[2]; z <= depth; z += psfRadius[2] ) { offset[2] = originalZ + z - psfRadius[2]; generateBlocksXY(img, blockList, offset, size); } // check is we need to add a out of bounds strategy cursor if (z > depth) { offset[2] = originalZ + z - psfRadius[2]; generateBlocksXY(img, blockList, offset, size); } offset[2] = originalZ; } else throw new MissingPreconditionException("Currently only 2D and 3D images are supported."); } /** * Goes stepwise through the y-dimensions of the image data and adds cursors * for each row to the given list. The method does not check if there is a * y-dimensions, so this should be made sure before. you can enforce to * create all cursors as out-of-bounds one. * * @param img The image to get the data and cursors from. * @param blockList The list to put the blocks into. * @param offset The current offset configuration. Only [0] and [1] will be changed. * @param size */ protected void generateBlocksXY(RandomAccessible img, List> blockList, double[] offset, double[] size) { // potentially masked image height double height = size[1]; final double originalY = offset[1]; // go through the height in steps of block width double y; for ( y = psfRadius[1]; y <= height; y += psfRadius[1] ) { offset[1] = originalY + y - psfRadius[1]; generateBlocksX(img, blockList, offset, size); } // check is we need to add a out of bounds strategy cursor if (y > height) { offset[1] = originalY + y - psfRadius[1]; generateBlocksX(img, blockList, offset, size); } offset[1] = originalY; } /** * Goes stepwise through a row of image data and adds cursors to the given list. * If there is not enough image data for a whole block, an out-of-bounds cursor * is generated. The creation of out-of-bound cursors could be enforced as well. * * @param img The image to get the data and cursors from. * @param blockList The list to put the blocks into. * @param offset The current offset configuration. Only [0] of it will be changed. * @param size */ protected void generateBlocksX(RandomAccessible img, List> blockList, double[] offset, double[] size) { // potentially masked image width double width = size[0]; final double originalX = offset[0]; // go through the width in steps of block width double x; for ( x = psfRadius[0]; x <= width; x += psfRadius[0] ) { offset[0] = originalX + x - psfRadius[0]; RectangleRegionOfInterest roi = new RectangleRegionOfInterest(offset.clone(), psfRadius.clone()); IterableInterval roiInterval = roi.getIterableIntervalOverROI(img); blockList.add(roiInterval); } // check is we need to add a out of bounds strategy cursor if (x > width) { offset[0] = originalX + x - psfRadius[0]; RectangleRegionOfInterest roi = new RectangleRegionOfInterest(offset.clone(), psfRadius.clone()); IterableInterval roiInterval = roi.getIterableIntervalOverROI(img); blockList.add(roiInterval); } offset[0] = originalX; } protected void calculateStatistics(List compareValues, double originalVal) { shuffledPearsonsNotLessOriginal = 0; int iterations = shuffledPearsonsResults.size(); double compareSum = 0.0; for( Double shuffledVal : shuffledPearsonsResults ) { double diff = shuffledVal - originalVal; /* check if the randomized Pearsons value is equal * or larger than the original one. */ if( diff > -0.00001 ) { shuffledPearsonsNotLessOriginal++; } compareSum += shuffledVal; } shuffledMean = compareSum / iterations; shuffledStdDerivation = Statistics.stdDeviation(compareValues); // get the quantile of the original value in the shuffle distribution costesPValue = Statistics.phi(originalVal, shuffledMean, shuffledStdDerivation); if (costesPValue > 1.0) costesPValue = 1.0; else if (costesPValue < 0.0) costesPValue = 0.0; } @Override public void processResults(ResultHandler handler) { super.processResults(handler); // if desired, show the last shuffled image available if ( showShuffledImages ) { handler.handleImage( smoothedShuffledImage, "Smoothed & shuffled channel 1" ); } handler.handleValue("Costes P-Value", costesPValue, 2); handler.handleValue("Costes Shuffled Mean", shuffledMean, 2); handler.handleValue("Costes Shuffled Std.D.", shuffledStdDerivation, 2); /* give the ratio of results at least as large as the * original value. */ double ratio = 0.0; if (shuffledPearsonsNotLessOriginal > 0) { ratio = (double)shuffledPearsonsResults.size() / (double)shuffledPearsonsNotLessOriginal; } handler.handleValue("Ratio of rand. Pearsons >= actual Pearsons value ", ratio, 2); } public double getCostesPValue() { return costesPValue; } public double getShuffledMean() { return shuffledMean; } public double getShuffledStdDerivation() { return shuffledStdDerivation; } public double getShuffledPearsonsNotLessOriginal() { return shuffledPearsonsNotLessOriginal; } } diff --git a/src/main/java/algorithms/Histogram2D.java b/src/main/java/sc/fiji/coloc/algorithms/Histogram2D.java similarity index 99% rename from src/main/java/algorithms/Histogram2D.java rename to src/main/java/sc/fiji/coloc/algorithms/Histogram2D.java index 2629a1a..79a8b0a 100644 --- a/src/main/java/algorithms/Histogram2D.java +++ b/src/main/java/sc/fiji/coloc/algorithms/Histogram2D.java @@ -1,415 +1,415 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; import ij.measure.ResultsTable; import java.util.EnumSet; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; import net.imglib2.TwinCursor; import net.imglib2.img.ImgFactory; import net.imglib2.img.array.ArrayImgFactory; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.LongType; import net.imglib2.view.Views; -import gadgets.DataContainer; -import results.ResultHandler; +import sc.fiji.coloc.gadgets.DataContainer; +import sc.fiji.coloc.results.ResultHandler; /** * Represents the creation of a 2D histogram between two images. * Channel 1 is set out in x direction, while channel 2 in y direction. * @param The source images value type */ public class Histogram2D> extends Algorithm { // An enumeration of possible drawings public enum DrawingFlags { Plot, RegressionLine, Axes } // the drawing configuration EnumSet drawingSettings; // The width of the scatter-plot protected int xBins = 256; // The height of the scatter-plot protected int yBins = 256; // The name of the result 2D histogram to pass elsewhere protected String title = ""; // Swap or not swap ch1 and ch2 protected boolean swapChannels = false; // member variables for labeling protected String ch1Label = "Channel 1"; protected String ch2Label = "Channel 2"; // Result keeping members // the generated plot image private RandomAccessibleInterval plotImage; // the bin widths for each channel private double xBinWidth = 0.0, yBinWidth = 0.0; // labels for the axes private String xLabel = "", yLabel = ""; // ranges for the axes private double xMin = 0.0, xMax = 0.0, yMin = 0.0, yMax = 0.0; public Histogram2D(){ this("2D Histogram"); } public Histogram2D(String title){ this(title, false); } public Histogram2D(String title, boolean swapChannels){ this(title, swapChannels, EnumSet.of( DrawingFlags.Plot, DrawingFlags.RegressionLine )); } public Histogram2D(String title, boolean swapChannels, EnumSet drawingSettings){ super(title); this.title = title; this.swapChannels = swapChannels; if (swapChannels) { int xBins = this.xBins; this.xBins = this.yBins; this.yBins = xBins; } this.drawingSettings = drawingSettings; } /** * Gets the minimum of channel one. Takes channel * swapping into consideration and will return min * of channel two if swapped. * * @return The minimum of what is seen as channel one. */ protected double getMinCh1(DataContainer container) { return swapChannels ? container.getMinCh2() : container.getMinCh1(); } /** * Gets the minimum of channel two. Takes channel * swapping into consideration and will return min * of channel one if swapped. * * @return The minimum of what is seen as channel two. */ protected double getMinCh2(DataContainer container) { return swapChannels ? container.getMinCh1() : container.getMinCh2(); } /** * Gets the maximum of channel one. Takes channel * swapping into consideration and will return max * of channel two if swapped. * * @return The maximum of what is seen as channel one. */ protected double getMaxCh1(DataContainer container) { return swapChannels ? container.getMaxCh2() : container.getMaxCh1(); } /** * Gets the maximum of channel two. Takes channel * swapping into consideration and will return max * of channel one if swapped. * * @return The maximum of what is seen as channel two. */ protected double getMaxCh2(DataContainer container) { return swapChannels ? container.getMaxCh1() : container.getMaxCh2(); } /** * Gets the image of channel one. Takes channel * swapping into consideration and will return image * of channel two if swapped. * * @return The image of what is seen as channel one. */ protected RandomAccessibleInterval getImageCh1(DataContainer container) { return swapChannels ? container.getSourceImage2() : container.getSourceImage1(); } /** * Gets the image of channel two. Takes channel * swapping into consideration and will return image * of channel one if swapped. * * @return The image of what is seen as channel two. */ protected RandomAccessibleInterval getImageCh2(DataContainer container) { return swapChannels ? container.getSourceImage1() : container.getSourceImage2(); } /** * Gets the label of channel one. Takes channel * swapping into consideration and will return label * of channel two if swapped. * * @return The label of what is seen as channel one. */ protected String getLabelCh1() { return swapChannels ? ch2Label : ch1Label; } /** * Gets the label of channel two. Takes channel * swapping into consideration and will return label * of channel one if swapped. * * @return The label of what is seen as channel two. */ protected String getLabelCh2() { return swapChannels ? ch1Label : ch2Label; } @Override public void execute(DataContainer container) throws MissingPreconditionException { generateHistogramData(container); } protected void generateHistogramData(DataContainer container) { double ch1BinWidth = getXBinWidth(container); double ch2BinWidth = getYBinWidth(container); // get the 2 images for the calculation of Pearson's final RandomAccessibleInterval img1 = getImageCh1(container); final RandomAccessibleInterval img2 = getImageCh2(container); final RandomAccessibleInterval mask = container.getMask(); // get the cursors for iterating through pixels in images TwinCursor cursor = new TwinCursor(img1.randomAccess(), img2.randomAccess(), Views.iterable(mask).localizingCursor()); // create new image to put the scatter-plot in final ImgFactory scatterFactory = new ArrayImgFactory< LongType >(); plotImage = scatterFactory.create(new int[] {xBins, yBins}, new LongType() ); // create access cursors final RandomAccess histogram2DCursor = plotImage.randomAccess(); long ignoredPixelCount = 0; // iterate over images long[] pos = new long[ plotImage.numDimensions() ]; while (cursor.hasNext()) { cursor.fwd(); double ch1 = cursor.getFirst().getRealDouble(); double ch2 = cursor.getSecond().getRealDouble(); /* Scale values for both channels to fit in the range. * Moreover mirror the y value on the x axis. */ pos[0] = getXValue(ch1, ch1BinWidth, ch2, ch2BinWidth); pos[1] = getYValue(ch1, ch1BinWidth, ch2, ch2BinWidth); if (pos[0] >= 0 && pos[1] >=0 && pos[0] < xBins && pos[1] < yBins) { // set position of input/output cursor histogram2DCursor.setPosition( pos ); // get current value at position and increment it long count = histogram2DCursor.get().getIntegerLong(); count++; histogram2DCursor.get().set(count); } else { ignoredPixelCount ++; } } if (ignoredPixelCount > 0) { addWarning("Ignored pixels while generating histogram.", "" + ignoredPixelCount + " pixels were ignored while generating the 2D histogram \"" + title + "\" because the grey values were out of range." + "This may happen, if an image contains negative pixel values."); } xBinWidth = ch1BinWidth; yBinWidth = ch2BinWidth; xLabel = getLabelCh1(); yLabel = getLabelCh2(); xMin = getXMin(container); xMax = getXMax(container); yMin = getYMin(container); yMax = getYMax(container); } /** * A table of x-values, y-values and the counts is generated and * returned as a string. The single fields in one row (X Y Count) * are separated by tabs. * * @return A String representation of the histogram data. */ public String getData() { StringBuffer sb = new StringBuffer(); double xBinWidth = 1.0 / getXBinWidth(); double yBinWidth = 1.0 / getYBinWidth(); double xMin = getXMin(); double yMin = getYMin(); // check if we have bins of size one or other ones boolean xBinWidthIsOne = Math.abs(xBinWidth - 1.0) < 0.00001; boolean yBinWidthIsOne = Math.abs(yBinWidth - 1.0) < 0.00001; // configure decimal places accordingly int xDecimalPlaces = xBinWidthIsOne ? 0 : 3; int yDecimalPlaces = yBinWidthIsOne ? 0 : 3; // create a cursor to access the histogram data RandomAccess cursor = plotImage.randomAccess(); // loop over 2D histogram for (int i=0; i < plotImage.dimension(0); ++i) { for (int j=0; j < plotImage.dimension(1); ++j) { cursor.setPosition(i, 0); cursor.setPosition(j, 1); sb.append( ResultsTable.d2s(xMin + (i * xBinWidth), xDecimalPlaces) + "\t" + ResultsTable.d2s(yMin + (j * yBinWidth), yDecimalPlaces) + "\t" + ResultsTable.d2s(cursor.get().getRealDouble(), 0) + "\n"); } } return sb.toString(); } @Override public void processResults(ResultHandler handler) { super.processResults(handler); handler.handleHistogram( this, title ); } /** * Calculates the bin width of one bin in x/ch1 direction. * @param container The container with images to work on * @return The width of one bin in x direction */ protected double getXBinWidth(DataContainer container) { double ch1Max = getMaxCh1(container); if (ch1Max < yBins) { // bin widths must not exceed 1 return 1; } // we need (ch1Max * width + 0.5) < yBins, but just so, i.e. // ch1Max * width + 0.5 == yBins - eps // width = (yBins - 0.5 - eps) / ch1Max return (yBins - 0.50001) / ch1Max; } /** * Calculates the bin width of one bin in y/ch2 direction. * @param container The container with images to work on * @return The width of one bin in y direction */ protected double getYBinWidth(DataContainer container) { double ch2Max = getMaxCh2(container); if (ch2Max < yBins) { // bin widths must not exceed 1 return 1; } return (yBins - 0.50001) / ch2Max; } /** * Calculates the locations x value. * @param ch1Val The intensity of channel one * @param ch1BinWidth The bin width for channel one * @return The x value of the data point location */ protected int getXValue(double ch1Val, double ch1BinWidth, double ch2Val, double ch2BinWidth) { return (int)(ch1Val * ch1BinWidth + 0.5); } /** * Calculates the locations y value. * @param ch2Val The intensity of channel one * @param ch2BinWidth The bin width for channel one * @return The x value of the data point location */ protected int getYValue(double ch1Val, double ch1BinWidth, double ch2Val, double ch2BinWidth) { return (yBins - 1) - (int)(ch2Val * ch2BinWidth + 0.5); } protected double getXMin(DataContainer container) { return 0; } protected double getXMax(DataContainer container) { return swapChannels ? getMaxCh2(container) : getMaxCh1(container); } protected double getYMin(DataContainer container) { return 0; } protected double getYMax(DataContainer container) { return swapChannels ? getMaxCh1(container) : getMaxCh2(container); } // Result access methods public RandomAccessibleInterval getPlotImage() { return plotImage; } public double getXBinWidth() { return xBinWidth; } public double getYBinWidth() { return yBinWidth; } public String getXLabel() { return xLabel; } public String getYLabel() { return yLabel; } public double getXMin() { return xMin; } public double getXMax() { return xMax; } public double getYMin() { return yMin; } public double getYMax() { return yMax; } public String getTitle() { return title; } public EnumSet getDrawingSettings() { return drawingSettings; } } diff --git a/src/main/java/algorithms/InputCheck.java b/src/main/java/sc/fiji/coloc/algorithms/InputCheck.java similarity index 97% rename from src/main/java/algorithms/InputCheck.java rename to src/main/java/sc/fiji/coloc/algorithms/InputCheck.java index b08f782..c090202 100644 --- a/src/main/java/algorithms/InputCheck.java +++ b/src/main/java/sc/fiji/coloc/algorithms/InputCheck.java @@ -1,207 +1,207 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; import ij.IJ; import net.imglib2.RandomAccessibleInterval; import net.imglib2.TwinCursor; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; -import gadgets.DataContainer; -import gadgets.DataContainer.MaskType; -import results.ResultHandler; +import sc.fiji.coloc.gadgets.DataContainer; +import sc.fiji.coloc.gadgets.DataContainer.MaskType; +import sc.fiji.coloc.results.ResultHandler; /** * This class implements some basic checks for the input image data. For * instance: Is the percentage of zero-zero or saturated pixels too high? Also, * we get basic image properties / stats from imglib2, and also the * colocalization job name from the DataContainer and allow implementations of * ResultHandler to report them. */ public class InputCheck> extends Algorithm { /* the maximum allowed ratio between zero-zero and * normal pixels */ protected final double maxZeroZeroRatio = 0.1f; /* the maximum allowed ratio between saturated and * normal pixels within a channel */ protected final double maxSaturatedRatio = 0.1f; // the zero-zero pixel ratio double zeroZeroPixelRatio; // the saturated pixel ratio of channel 1 double saturatedRatioCh1; // the saturated pixel ratio of channel 2 double saturatedRatioCh2; // the coloc job name String colocJobName; // general image stats/parameters/values double ch1Max; double ch2Max; double ch1Min; double ch2Min; double ch1Mean; double ch2Mean; double ch1Integral; double ch2Integral; // Mask infos MaskType maskType; double maskID; public InputCheck() { super("input data check"); } @Override public void execute(DataContainer container) throws MissingPreconditionException { // get the 2 images and the mask final RandomAccessibleInterval img1 = container.getSourceImage1(); final RandomAccessibleInterval img2 = container.getSourceImage2(); final RandomAccessibleInterval mask = container.getMask(); // get the cursors for iterating through pixels in images TwinCursor cursor = new TwinCursor(img1.randomAccess(), img2.randomAccess(), Views.iterable(mask).cursor()); // get various general image properties/stats/values from the DataContainer ch1Max = container.getMaxCh1(); ch2Max = container.getMaxCh2(); ch1Min = container.getMinCh1(); ch2Min = container.getMinCh2(); ch1Mean = container.getMeanCh1(); ch2Mean = container.getMeanCh2(); ch1Integral = container.getIntegralCh1(); ch2Integral = container.getIntegralCh2(); // get the info about the mask/ROI being used or not. maskType = container.getMaskType(); maskID = (double)container.getMaskID(); // the total amount of pixels that have been taken into consideration long N = 0; // the number of pixels that are zero in both channels long Nzero = 0; // the number of ch1 pixels with the maximum ch1 value; long NsaturatedCh1 = 0; // the number of ch2 pixels with the maximum ch2 value; long NsaturatedCh2 = 0; while (cursor.hasNext()) { cursor.fwd(); double ch1 = cursor.getFirst().getRealDouble(); double ch2 = cursor.getSecond().getRealDouble(); // is the current pixels combination a zero-zero pixel? if (Math.abs(ch1 + ch2) < 0.00001) Nzero++; // is the current pixel of channel one saturated? if (Math.abs(ch1Max - ch1) < 0.00001) NsaturatedCh1++; // is the current pixel of channel one saturated? if (Math.abs(ch2Max - ch2) < 0.00001) NsaturatedCh2++; N++; } // calculate results double zeroZeroRatio = (double)Nzero / (double)N; // for channel wise ratios we have to use half of the total pixel amount double ch1SaturatedRatio = (double)NsaturatedCh1 / ( (double)N *0.5); double ch2SaturatedRatio = (double)NsaturatedCh2 / ( (double)N * 0.5); /* save results * Percentage results need to be multiplied by 100 */ zeroZeroPixelRatio = zeroZeroRatio * 100.0; saturatedRatioCh1 = ch1SaturatedRatio * 100.0; saturatedRatioCh2 = ch2SaturatedRatio * 100.0; // get job name so the ResultsHandler implementation can have it. colocJobName = container.getJobName(); // add warnings if images contain negative values if (ch1Min < 0 || ch2Min < 0) { addWarning("Negative minimum pixel value found.", "The minimum pixel value in at least one of the channels is negative. Negative values might break the logic of some analysis methods by breaking a basic basic assumption: The pixel value is assumed to be proportional to the number of photons detected in a pixel. Negative photon counts make no physical sense. Set negative pixel values to zero, or shift pixel intensities higher so there are no negative pixel values."); } // add warnings if values are not in tolerance range if ( Math.abs(zeroZeroRatio) > maxZeroZeroRatio ) { addWarning("Zero-zero ratio too high", "The ratio between zero-zero pixels and other pixels is large: " + IJ.d2s(zeroZeroRatio, 2) + ". Maybe you should use a ROI."); } if ( Math.abs(ch1SaturatedRatio) > maxSaturatedRatio ) { addWarning("Saturated ch1 ratio too high", "The ratio between saturated pixels and other pixels in channel one is large: " + IJ.d2s(maxSaturatedRatio, 2) + ". Maybe you should use a ROI."); } if ( Math.abs(ch1SaturatedRatio) > maxSaturatedRatio ) { addWarning("Saturated ch2 ratio too high", "The ratio between saturated pixels and other pixels in channel two is large: " + IJ.d2s(maxSaturatedRatio, 2) + ". Maybe you should use a ROI."); } } @Override public void processResults(ResultHandler handler) { super.processResults(handler); // Let us have a ValueResult for the colocalisation analysis job name: // A ValueResult can be two Strings (or a string and a numerical value) // We want to keep the jobName close to all the value results // so they get shown together by whatever implementation of ResultsHandler. handler.handleValue("Coloc_Job_Name", colocJobName); // Make the ResultsHander implementation deal with the input check results. handler.handleValue("% zero-zero pixels", zeroZeroPixelRatio, 2); handler.handleValue("% saturated ch1 pixels", saturatedRatioCh1, 2); handler.handleValue("% saturated ch2 pixels", saturatedRatioCh2, 2); // Make the ResultsHander implementation deal with the images' // stats/parameters/values handler.handleValue("Channel 1 Max", ch1Max, 3); handler.handleValue("Channel 2 Max", ch2Max, 3); handler.handleValue("Channel 1 Min", ch1Min, 3); handler.handleValue("Channel 2 Min", ch2Min, 3); handler.handleValue("Channel 1 Mean", ch1Mean, 3); handler.handleValue("Channel 2 Mean", ch2Mean, 3); handler.handleValue("Channel 1 Integrated (Sum) Intensity", ch1Integral, 3); handler.handleValue("Channel 2 Integrated (Sum) Intensity", ch2Integral, 3); // Make the ResultsHandler implementation deal with the images' // ROI or mask or lack thereof, so the user knows what they used. handler.handleValue("Mask Type Used", maskType.label()); handler.handleValue("Mask ID Used", maskID, 0); } } diff --git a/src/main/java/algorithms/IntArraySorter.java b/src/main/java/sc/fiji/coloc/algorithms/IntArraySorter.java similarity index 99% rename from src/main/java/algorithms/IntArraySorter.java rename to src/main/java/sc/fiji/coloc/algorithms/IntArraySorter.java index 69edca8..9c8a4e9 100644 --- a/src/main/java/algorithms/IntArraySorter.java +++ b/src/main/java/sc/fiji/coloc/algorithms/IntArraySorter.java @@ -1,139 +1,139 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; /** * Sorts and int[] according to a custom comparator. *

* This is an implementation of introsort, i.e. it is stable because * it tries the quicksort algorithm first and falls back to the heap * sort when it detects an unfavorable execution path. *

* * @author Johannes Schindelin */ public class IntArraySorter { private final static int SORT_SIZE_THRESHOLD = 16; public static void sort(int[] array, IntComparator comparator) { introSort(array, comparator, 0, array.length, array.length); insertionSort(array, comparator); } private static void introSort(int[] array, IntComparator comparator, int begin, int end, int limit) { while (end - begin > SORT_SIZE_THRESHOLD) { if (limit == 0) { heapSort(array, comparator, begin, end); return; } limit >>= 1; // median of three int a = array[begin]; int b = array[begin + (end - begin) / 2 + 1]; int c = array[end - 1]; int median; if (comparator.compare(a, b) < 0) { median = comparator.compare(b, c) < 0 ? b : (comparator.compare(a, c) < 0 ? c : a); } else { median = comparator.compare(b, c) > 0 ? b : (comparator.compare(a, c) > 0 ? c : a); } // partition int pivot, i = begin, j = end; for (;;) { while (comparator.compare(array[i], median) < 0) { ++i; } --j; while (comparator.compare(median, array[j]) < 0) { --j; } if (i >= j) { pivot = i; break; } int swap = array[i]; array[i] = array[j]; array[j] = swap; ++i; } introSort(array, comparator, pivot, end, limit); end = pivot; } } private static void heapSort(int[] array, IntComparator comparator, int begin, int end) { int count = end - begin; for (int i = count / 2 - 1; i >= 0; --i) { siftDown(array, comparator, i, count, begin); } for (int i = count - 1; i > 0; --i) { // swap begin and begin + i int swap = array[begin + i]; array[begin + i] = array[begin]; array[begin] = swap; siftDown(array, comparator, 0, i, begin); } } private static void siftDown(int[] array, IntComparator comparator, int i, int count, int offset) { int value = array[offset + i]; while (i < count / 2) { int child = 2 * i + 1; if (child + 1 < count && comparator.compare(array[child], array[child + 1]) < 0) { ++child; } if (comparator.compare(value, array[child]) >= 0) { break; } array[offset + i] = array[offset + child]; i = child; } array[offset + i] = value; } private static void insertionSort(int[] array, IntComparator comparator) { for (int j = 1; j < array.length; ++j) { int t = array[j]; int i = j - 1; while (i >= 0 && comparator.compare(array[i], t) > 0) { array[i + 1] = array[i]; i = i - 1; } array[i + 1] = t; } } } diff --git a/src/main/java/algorithms/IntComparator.java b/src/main/java/sc/fiji/coloc/algorithms/IntComparator.java similarity index 96% rename from src/main/java/algorithms/IntComparator.java rename to src/main/java/sc/fiji/coloc/algorithms/IntComparator.java index 3ca3170..1d7a615 100644 --- a/src/main/java/algorithms/IntComparator.java +++ b/src/main/java/sc/fiji/coloc/algorithms/IntComparator.java @@ -1,26 +1,26 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; public interface IntComparator { int compare(int a, int b); } diff --git a/src/main/java/algorithms/KendallTauRankCorrelation.java b/src/main/java/sc/fiji/coloc/algorithms/KendallTauRankCorrelation.java similarity index 98% rename from src/main/java/algorithms/KendallTauRankCorrelation.java rename to src/main/java/sc/fiji/coloc/algorithms/KendallTauRankCorrelation.java index 65fe161..d9caa4e 100644 --- a/src/main/java/algorithms/KendallTauRankCorrelation.java +++ b/src/main/java/sc/fiji/coloc/algorithms/KendallTauRankCorrelation.java @@ -1,363 +1,363 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; import ij.IJ; import java.util.Arrays; import net.imglib2.PairIterator; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.TwinCursor; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; -import gadgets.DataContainer; -import results.ResultHandler; +import sc.fiji.coloc.gadgets.DataContainer; +import sc.fiji.coloc.results.ResultHandler; /** * This algorithm calculates Kendall's Tau-b rank correlation coefficient *

* According to this * article, Tau-b (appropriate if multiple pairs share the same first, or * second, value), the rank correlation of a set of pairs (x_1, y_1), ..., * (x_n, y_n): *

* *
  * Tau_B = (n_c - n_d) / sqrt( (n_0 - n_1) (n_0 - n_2) )
  * 
* * where * *
  * n_0 = n (n - 1) / 2
  * n_1 = sum_i t_i (t_i - 1) / 2
  * n_2 = sum_j u_j (u_j - 1) / 2
  * n_c = #{ i, j; i != j && (x_i - x_j) * (y_i - y_j) > 0 },
  *   i.e. the number of pairs of pairs agreeing on the order of x and y, respectively
  * n_d = #{ i, j: i != j && (x_i - x_j) * (y_i - y_j) < 0 },
  *   i.e. the number of pairs of pairs where x and y are ordered opposite of each other
  * t_i = number of tied values in the i-th group of ties for the first quantity
  * u_j = number of tied values in the j-th group of ties for the second quantity
  * 
* * @author Johannes Schindelin * @param */ public class KendallTauRankCorrelation> extends Algorithm { public KendallTauRankCorrelation() { super("Kendall's Tau-b Rank Correlation"); } private double tau; @Override public void execute(DataContainer container) throws MissingPreconditionException { RandomAccessible img1 = container.getSourceImage1(); RandomAccessible img2 = container.getSourceImage2(); RandomAccessibleInterval mask = container.getMask(); TwinCursor cursor = new TwinCursor(img1.randomAccess(), img2.randomAccess(), Views.iterable(mask).localizingCursor()); tau = calculateMergeSort(cursor); } public static> double calculateNaive(final PairIterator iterator) { if (!iterator.hasNext()) { return Double.NaN; } // See http://en.wikipedia.org/wiki/Kendall_tau_rank_correlation_coefficient int n = 0, max1 = 0, max2 = 0, max = 255; int[][] histogram = new int[max + 1][max + 1]; while (iterator.hasNext()) { iterator.fwd(); T type1 = iterator.getFirst(); T type2 = iterator.getSecond(); double ch1 = type1.getRealDouble(); double ch2 = type2.getRealDouble(); if (ch1 < 0 || ch2 < 0 || ch1 > max || ch2 > max) { IJ.log("Error: The current Kendall Tau implementation is limited to 8-bit data"); return Double.NaN; } n++; int ch1Int = (int)Math.round(ch1); int ch2Int = (int)Math.round(ch2); histogram[ch1Int][ch2Int]++; if (max1 < ch1Int) { max1 = ch1Int; } if (max2 < ch2Int) { max2 = ch2Int; } } long n0 = n * (long)(n - 1) / 2, n1 = 0, n2 = 0, nc = 0, nd = 0; for (int i1 = 0; i1 <= max1; i1++) { IJ.log("" + i1 + "/" + max1); int ch1 = 0; for (int i2 = 0; i2 <= max2; i2++) { ch1 += histogram[i1][i2]; int count = histogram[i1][i2]; for (int j1 = 0; j1 < i1; j1++) { for (int j2 = 0; j2 < i2; j2++) { nc += count * histogram[j1][j2]; } } for (int j1 = 0; j1 < i1; j1++) { for (int j2 = i2 + 1; j2 <= max2; j2++) { nd += count * histogram[j1][j2]; } } } n1 += ch1 * (long)(ch1 - 1) / 2; } for (int i2 = 0; i2 <= max2; i2++) { int ch2 = 0; for (int i1 = 0; i1 <= max1; i1++) { ch2 += histogram[i1][i2]; } n2 += ch2 * (long)(ch2 - 1) / 2; } return (nc - nd) / Math.sqrt((n0 - n1) * (double)(n0 - n2)); } private static> double[][] getPairs(final PairIterator iterator) { // TODO: it is ridiculous that this has to be counted all the time (i.e. in most if not all measurements!). // We only need an upper bound to begin with, so even the number of pixels in the first channel would be enough! int capacity = 0; while (iterator.hasNext()) { iterator.fwd(); capacity++; } double[] values1 = new double[capacity]; double[] values2 = new double[capacity]; iterator.reset(); int count = 0; while (iterator.hasNext()) { iterator.fwd(); values1[count] = iterator.getFirst().getRealDouble(); values2[count] = iterator.getSecond().getRealDouble(); count++; } if (count < capacity) { values1 = Arrays.copyOf(values1, count); values2 = Arrays.copyOf(values2, count); } return new double[][] { values1, values2 }; } /** * Calculate Tau-b efficiently. *

* This implementation is based on this * description of the merge sort based way to calculate Tau-b. This is * supposed to be the method described in: *

*
Knight, W. (1966). "A Computer Method for Calculating * Kendall's Tau with Ungrouped Data". Journal of the American Statistical * Association 61 (314): 436–439. doi:10.2307/2282833.
*

* but since that article is not available as Open Access, it is * unnecessarily hard to verify. *

* * @param iterator the iterator of the pairs * @return Tau-b */ public static> double calculateMergeSort(final PairIterator iterator) { final double[][] pairs = getPairs(iterator); final double[] x = pairs[0]; final double[] y = pairs[1]; final int n = x.length; int[] index = new int[n]; for (int i = 0; i < n; i++) { index[i] = i; } // First sort by x as primary key, y as secondary one. // We use IntroSort here because it is fast and in-place. IntArraySorter.sort(index, new IntComparator() { @Override public int compare(int a, int b) { double xa = x[a], ya = y[a]; double xb = x[b], yb = y[b]; int result = Double.compare(xa, xb); return result != 0 ? result : Double.compare(ya, yb); } }); // The trick is to count the ties of x (n1) and the joint ties of x and y (n3) now, while // index is sorted with regards to x. long n0 = n * (long)(n - 1) / 2; long n1 = 0, n3 = 0; for (int i = 1; i < n; i++) { double x0 = x[index[i - 1]]; if (x[index[i]] != x0) { continue; } double y0 = y[index[i - 1]]; int i1 = i; do { double y1 = y[index[i1++]]; if (y1 == y0) { int i2 = i1; while (i1 < n && x[index[i1]] == x0 && y[index[i1]] == y0) { i1++; } n3 += (i1 - i2 + 2) * (long)(i1 - i2 + 1) / 2; } y0 = y1; } while (i1 < n && x[index[i1]] == x0); n1 += (i1 - i + 1) * (long)(i1 - i) / 2; i = i1; } // Now, let's perform that merge sort that also counts S, the number of // swaps a Bubble Sort would require (and which therefore is half the number // by which we have to adjust n_0 - n_1 - n_2 + n_3 to obtain n_c - n_d) final MergeSort mergeSort = new MergeSort(index, new IntComparator() { @Override public int compare(int a, int b) { double ya = y[a]; double yb = y[b]; return Double.compare(ya, yb); } }); long S = mergeSort.sort(); index = mergeSort.getSorted(); long n2 = 0; for (int i = 1; i < n; i++) { double y0 = y[index[i - 1]]; if (y[index[i]] != y0) { continue; } int i1 = i + 1; while (i1 < n && y[index[i1]] == y0) { i1++; } n2 += (i1 - i + 1) * (long)(i1 - i) / 2; i = i1; } return (n0 - n1 - n2 + n3 - 2 * S) / Math.sqrt((n0 - n1) * (double)(n0 - n2)); } private final static class MergeSort { private int[] index; private final IntComparator comparator; public MergeSort(int[] index, IntComparator comparator) { this.index = index; this.comparator = comparator; } public int[] getSorted() { return index; } /** * Sorts the {@link #index} array. *

* This implements a non-recursive merge sort. *

* @param begin * @param end * @return the equivalent number of BubbleSort swaps */ public long sort() { long swaps = 0; int n = index.length; // There are merge sorts which perform in-place, but their runtime is worse than O(n log n) int[] index2 = new int[n]; for (int step = 1; step < n; step <<= 1) { int begin = 0, k = 0; for (;;) { int begin2 = begin + step, end = begin2 + step; if (end >= n) { if (begin2 >= n) { break; } end = n; } // calculate the equivalent number of BubbleSort swaps // and perform merge, too int i = begin, j = begin2; while (i < begin2 && j < end) { int compare = comparator.compare(index[i], index[j]); if (compare > 0) { swaps += (begin2 - i); index2[k++] = index[j++]; } else { index2[k++] = index[i++]; } } if (i < begin2) { do { index2[k++] = index[i++]; } while (i < begin2); } else { while (j < end) { index2[k++] = index[j++]; } } begin = end; } if (k < n) { System.arraycopy(index, k, index2, k, n - k); } int[] swapIndex = index2; index2 = index; index = swapIndex; } return swaps; } } @Override public void processResults(ResultHandler handler) { super.processResults(handler); handler.handleValue("Kendall's Tau-b rank correlation value", tau, 4); } } diff --git a/src/main/java/algorithms/LiHistogram2D.java b/src/main/java/sc/fiji/coloc/algorithms/LiHistogram2D.java similarity index 98% rename from src/main/java/algorithms/LiHistogram2D.java rename to src/main/java/sc/fiji/coloc/algorithms/LiHistogram2D.java index e70b009..6f50f9d 100644 --- a/src/main/java/algorithms/LiHistogram2D.java +++ b/src/main/java/sc/fiji/coloc/algorithms/LiHistogram2D.java @@ -1,173 +1,173 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; import java.util.EnumSet; import net.imglib2.RandomAccessibleInterval; import net.imglib2.TwinCursor; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; -import gadgets.DataContainer; +import sc.fiji.coloc.gadgets.DataContainer; /** * Represents the creation of a 2D histogram between two images. * Channel 1 is set out in x direction, while channel 2 in y direction. * The value calculation is done after Li. * @param */ public class LiHistogram2D> extends Histogram2D { // On execution these variables hold the images means double ch1Mean, ch2Mean; // On execution these variables hold the images min and max double ch1Min, ch1Max, ch2Min, ch2Max; // On execution these variables hold the Li's value min and max and their difference double liMin, liMax, liDiff; // On execution these variables hold the scaling factors double ch1Scaling, ch2Scaling; // boolean to test which channel we are using for eg. Li 2D histogram y axis boolean useCh1 = true; public LiHistogram2D(boolean useCh1) { this("Histogram 2D (Li)", useCh1); } public LiHistogram2D(String title, boolean useCh1) { this(title, false, useCh1); } public LiHistogram2D(String title, boolean swapChannels, boolean useCh1) { this(title, swapChannels, useCh1, EnumSet.of(DrawingFlags.Plot)); } public LiHistogram2D(String title, boolean swapChannels, boolean useCh1, EnumSet drawingSettings) { super(title, swapChannels, drawingSettings); this.useCh1 = useCh1; } @Override public void execute(DataContainer container) throws MissingPreconditionException { ch1Mean = swapChannels ? container.getMeanCh2() : container.getMeanCh1(); ch2Mean = swapChannels ? container.getMeanCh1() : container.getMeanCh2(); ch1Min = getMinCh1(container); ch1Max = getMaxCh1(container); ch2Min = getMinCh2(container); ch2Max = getMaxCh2(container); /* A scaling to the x bins has to be made: * For that to work we need the min and the * max value that could occur. */ // get the 2 images and the mask final RandomAccessibleInterval img1 = getImageCh1(container); final RandomAccessibleInterval img2 = getImageCh2(container); final RandomAccessibleInterval mask = container.getMask(); // get the cursors for iterating through pixels in images TwinCursor cursor = new TwinCursor(img1.randomAccess(), img2.randomAccess(), Views.iterable(mask).localizingCursor()); // give liMin and liMax appropriate starting values at the top and bottom of the range liMin = Double.MAX_VALUE; liMax = Double.MIN_VALUE; // iterate over images while (cursor.hasNext()) { cursor.fwd(); double ch1 = cursor.getFirst().getRealDouble(); double ch2 = cursor.getSecond().getRealDouble(); double productOfDifferenceOfMeans = (ch1Mean - ch1) * (ch2Mean - ch2); if (productOfDifferenceOfMeans < liMin) liMin = productOfDifferenceOfMeans; if (productOfDifferenceOfMeans > liMax) liMax = productOfDifferenceOfMeans; } liDiff = Math.abs(liMax - liMin); generateHistogramData(container); } @Override protected double getXBinWidth(DataContainer container) { return (double) xBins / (double)(liDiff + 1); } @Override protected double getYBinWidth(DataContainer container) { double max; if (useCh1) { max = getMaxCh1(container); } else { max = getMaxCh2(container); } return (double) yBins / (double)(max + 1); } @Override protected int getXValue(double ch1Val, double ch1BinWidth, double ch2Val, double ch2BinWidth) { /* We want the values to be scaled and shifted by and * offset in a way that the smallest (possibly negative) * value is in first bin and highest value in largest bin. */ return (int)( (( (ch1Mean - ch1Val) * (ch2Mean - ch2Val)) - liMin) * ch1BinWidth); } @Override protected int getYValue(double ch1Val, double ch1BinWidth, double ch2Val, double ch2BinWidth) { if (useCh1) return (yBins - 1) - (int)(ch1Val * ch2BinWidth); else return (yBins - 1) - (int)(ch2Val * ch2BinWidth); } @Override protected double getXMin(DataContainer container) { return swapChannels ? (useCh1 ? container.getMinCh1(): container.getMinCh2()) : liMin; } @Override protected double getXMax(DataContainer container) { return swapChannels ? (useCh1 ? container.getMaxCh1(): container.getMaxCh2()) : liMax; } @Override protected double getYMin(DataContainer container) { return swapChannels ? liMin : (useCh1 ? container.getMinCh1(): container.getMinCh2()); } @Override protected double getYMax(DataContainer container) { return swapChannels ? liMax : (useCh1 ? container.getMaxCh1(): container.getMaxCh2()); } } diff --git a/src/main/java/algorithms/LiICQ.java b/src/main/java/sc/fiji/coloc/algorithms/LiICQ.java similarity index 96% rename from src/main/java/algorithms/LiICQ.java rename to src/main/java/sc/fiji/coloc/algorithms/LiICQ.java index cc46e84..b57ff80 100644 --- a/src/main/java/algorithms/LiICQ.java +++ b/src/main/java/sc/fiji/coloc/algorithms/LiICQ.java @@ -1,108 +1,108 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.TwinCursor; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; -import gadgets.DataContainer; -import results.ResultHandler; +import sc.fiji.coloc.gadgets.DataContainer; +import sc.fiji.coloc.results.ResultHandler; /** * This algorithm calculates Li et al.'s ICQ (intensity * correlation quotient). * * @param */ public class LiICQ> extends Algorithm { // the resulting ICQ value double icqValue; public LiICQ() { super("Li ICQ calculation"); } @Override public void execute(DataContainer container) throws MissingPreconditionException { double mean1 = container.getMeanCh1(); double mean2 = container.getMeanCh2(); // get the 2 images for the calculation of Li's ICQ RandomAccessible img1 = container.getSourceImage1(); RandomAccessible img2 = container.getSourceImage2(); RandomAccessibleInterval mask = container.getMask(); TwinCursor cursor = new TwinCursor(img1.randomAccess(), img2.randomAccess(), Views.iterable(mask).localizingCursor()); // calculate ICQ value icqValue = calculateLisICQ(cursor, mean1, mean2); } /** * Calculates Li et al.'s intensity correlation quotient (ICQ) for * two images. * * @param cursor A TwinCursor that iterates over two images * @param mean1 The first images mean * @param mean2 The second images mean * @return Li et al.'s ICQ value */ public static > double calculateLisICQ(TwinCursor cursor, double mean1, double mean2) { /* variables to count the positive and negative results * of Li's product of the difference of means. */ long numPositiveProducts = 0; long numNegativeProducts = 0; // iterate over image while (cursor.hasNext()) { cursor.fwd(); T type1 = cursor.getFirst(); T type2 = cursor.getSecond(); double ch1 = type1.getRealDouble(); double ch2 = type2.getRealDouble(); double productOfDifferenceOfMeans = (mean1 - ch1) * (mean2 - ch2); // check for positive and negative values if (productOfDifferenceOfMeans < 0.0 ) ++numNegativeProducts; else ++numPositiveProducts; } /* calculate Li's ICQ value by dividing the amount of "positive pixels" to the * total number of pixels. Then shift it in the -0.5,0.5 range. */ return ( (double) numPositiveProducts / (double) (numNegativeProducts + numPositiveProducts) ) - 0.5; } @Override public void processResults(ResultHandler handler) { super.processResults(handler); handler.handleValue("Li's ICQ value", icqValue); } } diff --git a/src/main/java/algorithms/MandersColocalization.java b/src/main/java/sc/fiji/coloc/algorithms/MandersColocalization.java similarity index 98% rename from src/main/java/algorithms/MandersColocalization.java rename to src/main/java/sc/fiji/coloc/algorithms/MandersColocalization.java index 2eedbfa..1b01f8f 100644 --- a/src/main/java/algorithms/MandersColocalization.java +++ b/src/main/java/sc/fiji/coloc/algorithms/MandersColocalization.java @@ -1,289 +1,289 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.TwinCursor; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; -import gadgets.DataContainer; -import gadgets.ThresholdMode; -import results.ResultHandler; +import sc.fiji.coloc.gadgets.DataContainer; +import sc.fiji.coloc.gadgets.ThresholdMode; +import sc.fiji.coloc.results.ResultHandler; /** *

This algorithm calculates Manders et al.'s split colocalization * coefficients, M1 and M2. These are independent of signal intensities, * but are directly proportional to the amount of * fluorescence in the colocalized objects in each colour channel of the * image, relative to the total amount of fluorescence in that channel. * See "Manders, Verbeek, Aten - Measurement of colocalization * of objects in dual-colour confocal images. J. Microscopy, vol. 169 * pt 3, March 1993, pp 375-382".

* *

M1 = sum of Channel 1 intensity in pixels over the channel 2 threshold / total Channel 1 intensity. * M2 is vice versa. * The threshold may be everything > 0 in the other channel, which we call M1 and M2: without thresholds * or everything above some thresholds in the opposite channels 1 or 2, called tM1 and tM2: with thresholds * The result is a fraction (range 0-1, but often misrepresented as a %. We wont do that here.

* *

TODO: Further, it could/should/will calculate other split colocalization coefficients, * such as fraction of pixels (voxels) colocalized, * or fraction of intensity colocalized, as described at: * WCIF * copy pasted here - credits to Tony Collins.

* *

Number of colocalised voxels - Ncoloc * This is the number of voxels which have both channel 1 and channel 2 intensities above threshold * (i.e., the number of pixels in the yellow area of the scatterplot).

* *

%Image volume colocalised - %Volume * This is the percentage of voxels which have both channel 1 and channel 2 intensities above threshold, * expressed as a percentage of the total number of pixels in the image (including zero-zero pixels); * in other words, the number of pixels in the scatterplot's yellow area / total number of pixels in the scatter plot (the Red + Green + Blue + Yellow areas).

* *

%Voxels Colocalised - %Ch1 Vol; %Ch2 Vol * This generates a value for each channel. This is the number of voxels for each channel * which have both channel 1 and channel 2 intensities above threshold, * expressed as a percentage of the total number of voxels for each channel * above their respective thresholds; in other words, for channel 1 (along the x-axis), * this equals the (the number of pixels in the Yellow area) / (the number of pixels in the Blue + Yellow areas). * For channel 2 this is calculated as follows: * (the number of pixels in the Yellow area) / (the number of pixels in the Red + Yellow areas).

* *

%Intensity Colocalised - %Ch1 Int; %Ch2 Int * This generates a value for each channel. For channel 1, this value is equal to * the sum of the pixel intensities, with intensities above both channel 1 and channel 2 thresholds * expressed as a percentage of the sum of all channel 1 intensities; * in other words, it is calculated as follows: * (the sum of channel 1 pixel intensities in the Yellow area) / (the sum of channel 1 pixels intensities in the Red + Green + Blue + Yellow areas).

* *

%Intensities above threshold colocalised - %Ch1 Int > thresh; %Ch2 Int > thresh * This generates a value for each channel. For channel 1, * this value is equal to the sum of the pixel intensities * with intensities above both channel 1 and channel 2 thresholds * expressed as a percentage of the sum of all channel 1 intensities above the threshold for channel 1. * In other words, it is calculated as follows: * (the sum of channel 1 pixel intensities in the Yellow area) / (sum of channel 1 pixels intensities in the Blue + Yellow area)

* *

The results are often represented as % values, but to make them consistent with Manders' * split coefficients, we will also report them as fractions (range 0-1). * Perhaps this helps prevent the confusion in comparisons of results * caused by one person's %coloc being a totally * different measurement than another person's %coloc.

* * @param */ public class MandersColocalization> extends Algorithm { // Manders' split coefficients, M1 and M2: without thresholds // fraction of intensity of a channel, in pixels above zero in the other channel. private double mandersM1, mandersM2; // thresholded Manders M1 and M2 values, // Manders' split coefficients, tM1 and tM2: with thresholds // fraction of intensity of a channel, in pixels above threshold in the other channel. private double mandersThresholdedM1, mandersThresholdedM2; // Number of colocalized voxels (pixels) - Ncoloc private long numberOfPixelsAboveBothThresholds; // Fraction of Image volume colocalized - Fraction of Volume private double fractionOfPixelsAboveBothThresholds; // Fraction Voxels (pixels) Colocalized - Fraction of Ch1 Vol; Fraction of Ch2 Vol private double fractionOfColocCh1Pixels, fractionOfColocCh2Pixels; // Fraction Intensity Colocalized - Fraction of Ch1 Int; Fraction of Ch2 Int private double fractionOfColocCh1Intensity, fractionOfColocCh2Intensity; // Fraction of Intensities above threshold, colocalized - // Fraction of Ch1 Int > thresh; Fraction of Ch2 Int > thresh private double fractionOfColocCh1IntensityAboveCh1Thresh, fractionOfColocCh2IntensityAboveCh2Thresh; /** * A result container for Manders' calculations. */ public static class MandersResults { public double m1; public double m2; } public MandersColocalization() { super("Manders correlation"); } @Override public void execute(DataContainer container) throws MissingPreconditionException { // get the two images for the calculation of Manders' split coefficients RandomAccessible img1 = container.getSourceImage1(); RandomAccessible img2 = container.getSourceImage2(); RandomAccessibleInterval mask = container.getMask(); TwinCursor cursor = new TwinCursor(img1.randomAccess(), img2.randomAccess(), Views.iterable(mask).localizingCursor()); // calculate Manders' split coefficients without threshold, M1 and M2. MandersResults results = calculateMandersCorrelation(cursor, img1.randomAccess().get().createVariable()); // save the results mandersM1 = results.m1; mandersM2 = results.m2; // calculate the thresholded Manders' split coefficients, tM1 and tM2, if possible AutoThresholdRegression autoThreshold = container.getAutoThreshold(); if (autoThreshold != null ) { // thresholded Manders' split coefficients, tM1 and tM2 cursor.reset(); results = calculateMandersCorrelation(cursor, autoThreshold.getCh1MaxThreshold(), autoThreshold.getCh2MaxThreshold(), ThresholdMode.Above); // save the results mandersThresholdedM1 = results.m1; mandersThresholdedM2 = results.m2; } } /** * Calculates Manders' split coefficients, M1 and M2: without thresholds * * @param cursor A TwinCursor that walks over two images * @param type A type instance, its value is not relevant * @return Both Manders' split coefficients, M1 and M2. */ public MandersResults calculateMandersCorrelation(TwinCursor cursor, T type) { return calculateMandersCorrelation(cursor, type, type, ThresholdMode.None); } /** * Calculates Manders' split coefficients, tM1 and tM2: with thresholds * * @param cursor A TwinCursor that walks over two images * @param thresholdCh1 type T * @param thresholdCh2 type T * @param tMode A ThresholdMode the threshold mode * @return Both thresholded Manders' split coefficients, tM1 and tM2. */ public MandersResults calculateMandersCorrelation(TwinCursor cursor, final T thresholdCh1, final T thresholdCh2, ThresholdMode tMode) { SplitCoeffAccumulator mandersAccum; // create a zero-values variable to compare to later on final T zero = thresholdCh1.createVariable(); zero.setZero(); // iterate over images - set the boolean value for if a pixel is thresholded // without thresholds: M1 and M1 if (tMode == ThresholdMode.None) { mandersAccum = new SplitCoeffAccumulator(cursor) { @Override final boolean acceptMandersCh1(T type1, T type2) { return (type2.compareTo(zero) > 0); } @Override final boolean acceptMandersCh2(T type1, T type2) { return (type1.compareTo(zero) > 0); } }; // with thresholds - below thresholds } else if (tMode == ThresholdMode.Below) { mandersAccum = new SplitCoeffAccumulator(cursor) { @Override final boolean acceptMandersCh1(T type1, T type2) { return (type2.compareTo(zero) > 0) && (type2.compareTo(thresholdCh2) <= 0); } @Override final boolean acceptMandersCh2(T type1, T type2) { return (type1.compareTo(zero) > 0) && (type1.compareTo(thresholdCh1) <= 0); } }; // with thresholds - above thresholds: tM1 and tM2 } else if (tMode == ThresholdMode.Above) { mandersAccum = new SplitCoeffAccumulator(cursor) { @Override final boolean acceptMandersCh1(T type1, T type2) { return (type2.compareTo(zero) > 0) && (type2.compareTo(thresholdCh2) >= 0); } @Override final boolean acceptMandersCh2(T type1, T type2) { return (type1.compareTo(zero) > 0) && (type1.compareTo(thresholdCh1) >= 0); } }; } else { throw new UnsupportedOperationException(); } MandersResults results = new MandersResults(); // calculate the results, see description above, as a fraction. results.m1 = mandersAccum.mandersSumCh1 / mandersAccum.sumCh1; results.m2 = mandersAccum.mandersSumCh2 / mandersAccum.sumCh2; return results; } @Override public void processResults(ResultHandler handler) { super.processResults(handler); handler.handleValue( "Manders' M1 (Above zero intensity of Ch2)", mandersM1 ); handler.handleValue( "Manders' M2 (Above zero intensity of Ch1)", mandersM2 ); handler.handleValue( "Manders' tM1 (Above autothreshold of Ch2)", mandersThresholdedM1 ); handler.handleValue( "Manders' tM2 (Above autothreshold of Ch1)", mandersThresholdedM2 ); } /** * A class similar to the Accumulator class, but more specific * to the Manders' split and other split channel coefficient calculations. */ protected abstract class SplitCoeffAccumulator { double sumCh1, sumCh2, mandersSumCh1, mandersSumCh2; public SplitCoeffAccumulator(TwinCursor cursor) { while (cursor.hasNext()) { cursor.fwd(); T type1 = cursor.getFirst(); T type2 = cursor.getSecond(); double ch1 = type1.getRealDouble(); double ch2 = type2.getRealDouble(); // boolean logics for adding or not adding to the different value counters for a pixel. if (acceptMandersCh1(type1, type2)) mandersSumCh1 += ch1; if (acceptMandersCh2(type1, type2)) mandersSumCh2 += ch2; // add this pixel's two intensity values to the ch1 and ch2 sum counters sumCh1 += ch1; sumCh2 += ch2; } } abstract boolean acceptMandersCh1(T type1, T type2); abstract boolean acceptMandersCh2(T type1, T type2); } } diff --git a/src/main/java/algorithms/MissingPreconditionException.java b/src/main/java/sc/fiji/coloc/algorithms/MissingPreconditionException.java similarity index 97% rename from src/main/java/algorithms/MissingPreconditionException.java rename to src/main/java/sc/fiji/coloc/algorithms/MissingPreconditionException.java index 22ab94a..4e2c457 100644 --- a/src/main/java/algorithms/MissingPreconditionException.java +++ b/src/main/java/sc/fiji/coloc/algorithms/MissingPreconditionException.java @@ -1,46 +1,46 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; /** * An exception class for missing preconditions for algorithm execution. */ public class MissingPreconditionException extends Exception{ private static final long serialVersionUID = 1L; public MissingPreconditionException() { super(); } public MissingPreconditionException(String message, Throwable cause) { super(message, cause); } public MissingPreconditionException(String message) { super(message); } public MissingPreconditionException(Throwable cause) { super(cause); } } diff --git a/src/main/java/algorithms/PearsonsCorrelation.java b/src/main/java/sc/fiji/coloc/algorithms/PearsonsCorrelation.java similarity index 98% rename from src/main/java/algorithms/PearsonsCorrelation.java rename to src/main/java/sc/fiji/coloc/algorithms/PearsonsCorrelation.java index ce7123b..d6398bb 100644 --- a/src/main/java/algorithms/PearsonsCorrelation.java +++ b/src/main/java/sc/fiji/coloc/algorithms/PearsonsCorrelation.java @@ -1,409 +1,409 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; import net.imglib2.RandomAccessibleInterval; import net.imglib2.TwinCursor; import net.imglib2.algorithm.math.ImageStatistics; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; -import gadgets.DataContainer; -import gadgets.MaskFactory; -import gadgets.ThresholdMode; -import results.ResultHandler; +import sc.fiji.coloc.gadgets.DataContainer; +import sc.fiji.coloc.gadgets.MaskFactory; +import sc.fiji.coloc.gadgets.ThresholdMode; +import sc.fiji.coloc.results.ResultHandler; /** * A class that represents the mean calculation of the two source * images in the data container. * * @param */ public class PearsonsCorrelation> extends Algorithm { // Identifiers for choosing which implementation to use public enum Implementation {Classic, Fast}; // The member variable to store the implementation of the Pearson's Coefficient calculation used. Implementation theImplementation = Implementation.Fast; // resulting Pearsons value without thresholds double pearsonsCorrelationValue; // resulting Pearsons value below threshold double pearsonsCorrelationValueBelowThr; // resulting Pearsons value above threshold double pearsonsCorrelationValueAboveThr; /** * Creates a new Pearson's Correlation and allows us to define * which implementation of the calculation to use. * @param implementation The implementation of Pearson's Coefficient calculation to use. */ public PearsonsCorrelation(Implementation implementation) { super("Pearson correlation"); this.theImplementation = implementation; } /** * Creates a new Pearson's Correlation with default (fast) implementation parameter. */ public PearsonsCorrelation() { this(Implementation.Fast); } @Override public void execute(DataContainer container) throws MissingPreconditionException { // get the 2 images for the calculation of Pearson's RandomAccessibleInterval img1 = container.getSourceImage1(); RandomAccessibleInterval img2 = container.getSourceImage2(); RandomAccessibleInterval mask = container.getMask(); // get the thresholds of the images AutoThresholdRegression autoThreshold = container.getAutoThreshold(); if (autoThreshold == null ) { throw new MissingPreconditionException("Pearsons calculation need AutoThresholdRegression to be run before it."); } T threshold1 = autoThreshold.getCh1MaxThreshold(); T threshold2 = autoThreshold.getCh2MaxThreshold(); if (threshold1 == null || threshold2 == null ) { throw new MissingPreconditionException("Pearsons calculation needs valid (not null) thresholds."); } /* Create cursors to walk over the images. First go over the * images without a mask. */ TwinCursor cursor = new TwinCursor( img1.randomAccess(), img2.randomAccess(), Views.iterable(mask).localizingCursor()); MissingPreconditionException error = null; if (theImplementation == Implementation.Classic) { // get the means from the DataContainer double ch1Mean = container.getMeanCh1(); double ch2Mean = container.getMeanCh2(); try { cursor.reset(); pearsonsCorrelationValue = classicPearsons(cursor, ch1Mean, ch2Mean); } catch (MissingPreconditionException e) { // probably a numerical error occurred pearsonsCorrelationValue = Double.NaN; error = e; } try { cursor.reset(); pearsonsCorrelationValueBelowThr = classicPearsons(cursor, ch1Mean, ch2Mean, threshold1, threshold2, ThresholdMode.Below); } catch (MissingPreconditionException e) { // probably a numerical error occurred pearsonsCorrelationValueBelowThr = Double.NaN; error = e; } try { cursor.reset(); pearsonsCorrelationValueAboveThr = classicPearsons(cursor, ch1Mean, ch2Mean, threshold1, threshold2, ThresholdMode.Above); } catch (MissingPreconditionException e) { // probably a numerical error occurred pearsonsCorrelationValueAboveThr = Double.NaN; error = e; } } else if (theImplementation == Implementation.Fast) { try { cursor.reset(); pearsonsCorrelationValue = fastPearsons(cursor); } catch (MissingPreconditionException e) { // probably a numerical error occurred pearsonsCorrelationValue = Double.NaN; error = e; } try { cursor.reset(); pearsonsCorrelationValueBelowThr = fastPearsons(cursor, threshold1, threshold2, ThresholdMode.Below); } catch (MissingPreconditionException e) { // probably a numerical error occurred pearsonsCorrelationValueBelowThr = Double.NaN; error = e; } try { cursor.reset(); pearsonsCorrelationValueAboveThr = fastPearsons(cursor, threshold1, threshold2, ThresholdMode.Above); } catch (MissingPreconditionException e) { // probably a numerical error occurred pearsonsCorrelationValueAboveThr = Double.NaN; error = e; } } // if an error occurred, throw it one level up if (error != null) throw error; } /** * Calculates Pearson's R value without any constraint in values, thus it uses no thresholds. * If additional data like the images mean is needed, it is calculated. * * @param The images base type. * @param img1 The first image to walk over. * @param img2 The second image to walk over. * @return Pearson's R value. * @throws MissingPreconditionException */ public > double calculatePearsons( RandomAccessibleInterval img1, RandomAccessibleInterval img2) throws MissingPreconditionException { // create an "always true" mask to walk over the images final long[] dims = new long[img1.numDimensions()]; img1.dimensions(dims); RandomAccessibleInterval alwaysTrueMask = MaskFactory.createMask(dims, true); return calculatePearsons(img1, img2, alwaysTrueMask); } /** * Calculates Pearson's R value without any constraint in values, thus it uses no * thresholds. A mask is required to mark which data points should be visited. If * additional data like the images mean is needed, it is calculated. * * @param The images base type. * @param img1 The first image to walk over. * @param img2 The second image to walk over. * @param mask A mask for the images. * @return Pearson's R value. * @throws MissingPreconditionException */ public > double calculatePearsons( RandomAccessibleInterval img1, RandomAccessibleInterval img2, RandomAccessibleInterval mask) throws MissingPreconditionException { TwinCursor cursor = new TwinCursor( img1.randomAccess(), img2.randomAccess(), Views.iterable(mask).localizingCursor()); double r; if (theImplementation == Implementation.Classic) { /* since we need the means and apparently don't have them, * calculate them. */ double mean1 = ImageStatistics.getImageMean(img1); double mean2 = ImageStatistics.getImageMean(img2); // do the actual calculation r = classicPearsons(cursor, mean1, mean2); } else { r = fastPearsons(cursor); } return r; } /** * Calculates Pearson's R value with the possibility to constraint in values. * This could be useful of one wants to apply thresholds. You need to provide * the images means, albeit not used by all implementations. * * @param The images base type. * @param cursor The cursor to walk over both images. * @return Pearson's R value. * @throws MissingPreconditionException */ public > double calculatePearsons(TwinCursor cursor, double mean1, double mean2, S thresholdCh1, S thresholdCh2, ThresholdMode tMode) throws MissingPreconditionException { if (theImplementation == Implementation.Classic) { // do the actual calculation return classicPearsons(cursor, mean1, mean2, thresholdCh1, thresholdCh2, tMode); } else { return fastPearsons(cursor, thresholdCh1, thresholdCh2, tMode); } } /** * Calculates Person's R value by using a Classic implementation of the * algorithm. This method allows the specification of a TwinValueRangeCursor. * With such a cursor one for instance can combine different thresholding * conditions for each channel. The cursor is not closed in here. * * @param The image base type * @param cursor The cursor that defines the walk over both images. * @param meanCh1 Mean of channel 1. * @param meanCh2 Mean of channel 2. * @return Person's R value */ public static > double classicPearsons(TwinCursor cursor, double meanCh1, double meanCh2) throws MissingPreconditionException { return classicPearsons(cursor, meanCh1, meanCh2, null, null, ThresholdMode.None); } public static > double classicPearsons(TwinCursor cursor, double meanCh1, double meanCh2, final T thresholdCh1, final T thresholdCh2, ThresholdMode tMode) throws MissingPreconditionException { // the actual accumulation of the image values is done in a separate object Accumulator acc; if (tMode == ThresholdMode.None) { acc = new Accumulator(cursor, meanCh1, meanCh2) { @Override final public boolean accept(T type1, T type2) { return true; } }; } else if (tMode == ThresholdMode.Below) { acc = new Accumulator(cursor, meanCh1, meanCh2) { @Override final public boolean accept(T type1, T type2) { return type1.compareTo(thresholdCh1) < 0 || type2.compareTo(thresholdCh2) < 0; } }; } else if (tMode == ThresholdMode.Above) { acc = new Accumulator(cursor, meanCh1, meanCh2) { @Override final public boolean accept(T type1, T type2) { return type1.compareTo(thresholdCh1) > 0 || type2.compareTo(thresholdCh2) > 0; } }; } else { throw new UnsupportedOperationException(); } double pearsonsR = acc.xy / Math.sqrt(acc.xx * acc.yy); checkForSanity(pearsonsR, acc.count); return pearsonsR; } /** * Calculates Person's R value by using a fast implementation of the * algorithm. This method allows the specification of a TwinValueRangeCursor. * With such a cursor one for instance can combine different thresholding * conditions for each channel. The cursor is not closed in here. * * @param The image base type * @param cursor The cursor that defines the walk over both images. * @return Person's R value */ public static > double fastPearsons(TwinCursor cursor) throws MissingPreconditionException { return fastPearsons(cursor, null, null, ThresholdMode.None); } public static > double fastPearsons(TwinCursor cursor, final T thresholdCh1, final T thresholdCh2, ThresholdMode tMode) throws MissingPreconditionException { // the actual accumulation of the image values is done in a separate object Accumulator acc; if (tMode == ThresholdMode.None) { acc = new Accumulator(cursor) { @Override final public boolean accept(T type1, T type2) { return true; } }; } else if (tMode == ThresholdMode.Below) { acc = new Accumulator(cursor) { @Override final public boolean accept(T type1, T type2) { return type1.compareTo(thresholdCh1) < 0 || type2.compareTo(thresholdCh2) < 0; } }; } else if (tMode == ThresholdMode.Above) { acc = new Accumulator(cursor) { @Override final public boolean accept(T type1, T type2) { return type1.compareTo(thresholdCh1) > 0 || type2.compareTo(thresholdCh2) > 0; } }; } else { throw new UnsupportedOperationException(); } // for faster computation, have the inverse of N available double invCount = 1.0 / acc.count; double pearsons1 = acc.xy - (acc.x * acc.y * invCount); double pearsons2 = acc.xx - (acc.x * acc.x * invCount); double pearsons3 = acc.yy - (acc.y * acc.y * invCount); double pearsonsR = pearsons1 / (Math.sqrt(pearsons2 * pearsons3)); checkForSanity(pearsonsR, acc.count); return pearsonsR; } /** * Does a sanity check for calculated Pearsons values. Wrong * values can happen for fast and classic implementation. * * @param val The value to check. */ private static void checkForSanity(double value, int iterations) throws MissingPreconditionException { if ( Double.isNaN(value) || Double.isInfinite(value)) { /* For the _fast_ implementation this could happen: * Infinity could happen if only the numerator is 0, i.e.: * sum1squared == sum1 * sum1 * invN * and * sum2squared == sum2 * sum2 * invN * If the denominator is also zero, one will get NaN, i.e: * sumProduct1_2 == sum1 * sum2 * invN * * For the classic implementation it could happen, too: * Infinity happens if one channels sum of value-mean-differences * is zero. If it is negative for one image you will get NaN. * Additionally, if is zero for both channels at once you * could get NaN. NaN */ throw new MissingPreconditionException("A numerical problem occured: the input data is unsuitable for this algorithm. Possibly too few pixels (in range were: " + iterations + ")."); } } @Override public void processResults(ResultHandler handler) { super.processResults(handler); handler.handleValue("Pearson's R value (no threshold)", pearsonsCorrelationValue, 2); handler.handleValue("Pearson's R value (below threshold)", pearsonsCorrelationValueBelowThr, 2); handler.handleValue("Pearson's R value (above threshold)", pearsonsCorrelationValueAboveThr, 2); } public double getPearsonsCorrelationValue() { return pearsonsCorrelationValue; } public double getPearsonsCorrelationBelowThreshold() { return pearsonsCorrelationValueBelowThr; } public double getPearsonsCorrelationAboveThreshold() { return pearsonsCorrelationValueAboveThr; } } diff --git a/src/main/java/algorithms/SimpleStepper.java b/src/main/java/sc/fiji/coloc/algorithms/SimpleStepper.java similarity index 98% rename from src/main/java/algorithms/SimpleStepper.java rename to src/main/java/sc/fiji/coloc/algorithms/SimpleStepper.java index b647ace..7b40cdb 100644 --- a/src/main/java/algorithms/SimpleStepper.java +++ b/src/main/java/sc/fiji/coloc/algorithms/SimpleStepper.java @@ -1,87 +1,87 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; /** * The simple stepper decrements a start threshold with every update() call. It * is finished if the update value is not a number, below zero or larger than * the last value. It also stops if the decremented threshold falls below one. * * @author tom */ public class SimpleStepper extends Stepper { double threshold; double lastThreshold; double currentValue; double lastValue; boolean finished = false; /** * Initialize the simple sequential stepper with a starting threshold. * * @param threshold The starting threshold. */ public SimpleStepper(double threshold) { this.threshold = threshold; this.currentValue = 1.0; this.lastValue = Double.MAX_VALUE; } /** * Decrement the threshold if the stepper is not marked as finished. * Rendering a stepper finished happens if {@code value} is not a number, * below or equal zero or bigger than the last update value. The same * thing happens if the internal threshold falls below one. */ @Override public void update(double value) { if (!finished) { // Remember current value and store new value lastValue = this.currentValue; currentValue = value; // Decrement threshold threshold = this.threshold - 1.0; // Stop if the threshold was finished = Double.NaN == value || threshold < 1 || value < 0.0001 || value > lastValue; } } /** * Get the current threshold. */ @Override public double getValue() { return threshold; } /** * Indicates if the stepper is marked as finished. */ @Override public boolean isFinished() { return finished; } } diff --git a/src/main/java/algorithms/SpearmanRankCorrelation.java b/src/main/java/sc/fiji/coloc/algorithms/SpearmanRankCorrelation.java similarity index 98% rename from src/main/java/algorithms/SpearmanRankCorrelation.java rename to src/main/java/sc/fiji/coloc/algorithms/SpearmanRankCorrelation.java index 42843c7..7970a0c 100644 --- a/src/main/java/algorithms/SpearmanRankCorrelation.java +++ b/src/main/java/sc/fiji/coloc/algorithms/SpearmanRankCorrelation.java @@ -1,331 +1,331 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; import java.util.Arrays; import java.util.Comparator; import net.imglib2.RandomAccessibleInterval; import net.imglib2.TwinCursor; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; -import gadgets.DataContainer; -import results.ResultHandler; +import sc.fiji.coloc.gadgets.DataContainer; +import sc.fiji.coloc.results.ResultHandler; /* * This code has been heavily adapted from Numerical Recipces: The Art of Scientific Computing. * 3rd ed., 2007. Other formulations have been gathered from Wolfram's MathWorld: * http://mathworld.wolfram.com/SpearmanRankCorrelationCoefficient.html * * Adapted from code written by Dan White and Tom Kazimiers * * @author Leonardo Guizzetti */ /** * This algorithm calculates Spearman's rank correlation coefficient (Spearman's rho) * * @param */ public class SpearmanRankCorrelation> extends Algorithm { // the resulting Spearman rho value double rhoValue; double tStatisticSpearman; int dfSpearman; // create two paired arrays: one with raw pixel values and one for the corresponding ranks double[][] data; double[] ch1raw; double[] ch2raw; double[] ch1ranks; double[] ch2ranks; public SpearmanRankCorrelation() { super("Spearman's Rank Corelation calculation"); } @Override public void execute(DataContainer container) throws MissingPreconditionException { // get the 2 images for the calculation of Spearman's rho RandomAccessibleInterval img1 = container.getSourceImage1(); RandomAccessibleInterval img2 = container.getSourceImage2(); RandomAccessibleInterval mask = container.getMask(); TwinCursor cursor = new TwinCursor(img1.randomAccess(), img2.randomAccess(), Views.iterable(mask).localizingCursor()); // calculate Spearman's rho value rhoValue = calculateSpearmanRank(cursor); } /** * Calculates Spearman's Rank Correlation Coefficient (Spearman's rho) for * two images. * * @param cursor A TwinCursor that iterates over two images * @return Spearman's rank correlation coefficient (rho) value */ public > double calculateSpearmanRank(TwinCursor cursor) { // Step 0: Count the pixels first. int n = 0; while (cursor.hasNext()) { n++; cursor.fwd(); } cursor.reset(); data = new double[n][2]; for (int i = 0; i < n; i++) { cursor.fwd(); T type1 = cursor.getFirst(); T type2 = cursor.getSecond(); data[i][0] = type1.getRealDouble(); data[i][1] = type2.getRealDouble(); } return calculateSpearmanRank(data); } /** * Calculates Spearman's Rank Correlation Coefficient (Spearman's rho) for * two images. * * @param data A 2D array containing the data to be ranked * @return Spearman's rank correlation coefficient (rho) value */ public double calculateSpearmanRank(double[][] data) { final int n = data.length; ch1raw = new double[n]; ch2raw = new double[n]; ch1ranks = new double[n]; ch2ranks = new double[n]; /** * Here's the concept. Rank-transform the data, then run * the Pearson correlation on the transformed data. * * 1) We will sort the dataset by one column, extract the * column values and rank them, and replace the data by * the ranks. * 2) Repeat the process now with the remaining column. * 3) Calculate the coefficient from the individual rank * columns, the t-statistic and the df's of the test. */ // Step 1: Sort the raw data, by column #2 (arbitrary choice). Arrays.sort(data, new Comparator() { @Override public int compare(double[] row1, double[] row2) { return Double.compare(row1[1], row2[1]); } }); for (int i = 0; i < n; i++) { ch2raw[i] = data[i][1]; } // Rank the data then replace them into the dataset. ch2ranks = rankValues(ch2raw); for (int i = 0; i < n; i++) { data[i][1] = ch2ranks[i]; } // Step 2: Repeat step 1 with the other data column. Arrays.sort(data, new Comparator() { @Override public int compare(double[] row1, double[] row2) { return Double.compare(row1[0], row2[0]); } }); for (int i = 0; i < n; i++) { ch1raw[i] = data[i][0]; } ch1ranks = rankValues(ch1raw); for (int i = 0; i < n; i++) { data[i][0] = ch1ranks[i]; ch2ranks[i] = data[i][1]; } // Step 3: Compute statistics. rhoValue = calculateRho(ch1ranks, ch2ranks); tStatisticSpearman = getTStatistic(rhoValue, n); dfSpearman = getSpearmanDF(n); return rhoValue; } /** * Returns degrees of freedom for Spearman's rank correlation. * * @param n - N (number of data pairs) * @return Spearman's rank degrees of freedom. */ public int getSpearmanDF(int n) { return n - 2; } /** * Returns associated T-Statistic for Spearman's rank correlation. * * @param rho - Spearman's rho * @param n - N (number of data pairs) * @return Spearman's rank correlation t-statistic */ public double getTStatistic(double rho, int n) { double rho_squared = rho * rho; return rho * Math.sqrt( (n - 2) / (1 - rho_squared) ); } /** * Returns sorted rankings for a list of sorted values. * * @param sortedVals - The sorted absolute values * @return ranked sorted list of values */ public double[] rankValues(double[] sortedVals) { int len = sortedVals.length; int start = 0; int end = 0; double[] newranks = new double[len]; double avg = 0, ranksum = 0; boolean ties_found = false; // first assign ranks, ascending from 1 for (int i=0; i handler) { super.processResults(handler); handler.handleValue("Spearman's rank correlation value", rhoValue, 8); handler.handleValue("Spearman's correlation t-statistic", tStatisticSpearman, 4); handler.handleValue("t-statistic degrees of freedom", dfSpearman); } } diff --git a/src/main/java/algorithms/Stepper.java b/src/main/java/sc/fiji/coloc/algorithms/Stepper.java similarity index 96% rename from src/main/java/algorithms/Stepper.java rename to src/main/java/sc/fiji/coloc/algorithms/Stepper.java index b97fbed..16e5864 100644 --- a/src/main/java/algorithms/Stepper.java +++ b/src/main/java/sc/fiji/coloc/algorithms/Stepper.java @@ -1,34 +1,34 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package algorithms; +package sc.fiji.coloc.algorithms; /** * A stepper is used to change some value with every update() call. It should * finish at some point in time. * * @author Tom Kazimiers */ public abstract class Stepper { public abstract void update(double value); public abstract double getValue(); public abstract boolean isFinished(); } diff --git a/src/main/java/gadgets/DataContainer.java b/src/main/java/sc/fiji/coloc/gadgets/DataContainer.java similarity index 98% rename from src/main/java/gadgets/DataContainer.java rename to src/main/java/sc/fiji/coloc/gadgets/DataContainer.java index 9eac268..6fda1cf 100644 --- a/src/main/java/gadgets/DataContainer.java +++ b/src/main/java/sc/fiji/coloc/gadgets/DataContainer.java @@ -1,390 +1,390 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package gadgets; +package sc.fiji.coloc.gadgets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import net.imglib2.RandomAccessibleInterval; import net.imglib2.algorithm.math.ImageStatistics; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; -import algorithms.Algorithm; -import algorithms.AutoThresholdRegression; -import algorithms.InputCheck; -import algorithms.MissingPreconditionException; +import sc.fiji.coloc.algorithms.Algorithm; +import sc.fiji.coloc.algorithms.AutoThresholdRegression; +import sc.fiji.coloc.algorithms.InputCheck; +import sc.fiji.coloc.algorithms.MissingPreconditionException; /** * The DataContainer keeps all the source data, jobName, pre-processing results * and algorithm results that have been computed. It allows a * ResultsHandler implementation to get most of its content * and makes the source image and channel information available * to a client. * * @param */ public class DataContainer> { // enumeration of different mask types, include labels public enum MaskType { Regular("ROI"), Irregular("mask image"), None("none"); private final String label; MaskType(String label) { this.label = label; } public String label() { return label; } } // some general image statistics private double meanCh1, meanCh2, minCh1, maxCh1, minCh2, maxCh2, integralCh1, integralCh2; // The source images that the results are based on private RandomAccessibleInterval sourceImage1, sourceImage2; // The names of the two source images private String sourceImage1Name, sourceImage2Name; // The name of the colocalisation run job public String jobName; // The mask for the images private RandomAccessibleInterval mask; // Type of the used mask protected MaskType maskType; // the hash code integer of the mask object private int maskHash; // The channels of the source images that the result relate to private int ch1, ch2; // The mask's bounding box protected long[] maskBBSize = null; protected long[] maskBBOffset = null; InputCheck inputCheck = null; AutoThresholdRegression autoThreshold = null; // a list that contains all added algorithms List< Algorithm > algorithms = new ArrayList< Algorithm >(); /** * Creates a new {@link DataContainer} for a specific image channel * combination. We create default thresholds here that are the max and min of * the data type of the source image channels. * * @param src1 The channel one image source * @param src2 The channel two image source * @param ch1 The channel one image channel * @param ch2 The channel two image channel */ public DataContainer(RandomAccessibleInterval src1, RandomAccessibleInterval src2, int ch1, int ch2, String name1, String name2) { sourceImage1 = src1; sourceImage2 = src2; sourceImage1Name = name1; sourceImage2Name = name2; // create a mask that is true at all pixels. final long[] dims = new long[src1.numDimensions()]; src1.dimensions(dims); mask = MaskFactory.createMask(dims, true); this.ch1 = ch1; this.ch2 = ch2; // fill mask dimension information, here the whole image maskBBOffset = new long[mask.numDimensions()]; Arrays.fill(maskBBOffset, 0); maskBBSize = new long[mask.numDimensions()]; mask.dimensions(maskBBSize); // indicated that there is actually no mask maskType = MaskType.None; maskHash = mask.hashCode(); // create a jobName so ResultHandler instances can all use the same object // for the job name. jobName = "Colocalization_of_" + sourceImage1Name + "_versus_" + sourceImage2Name + "_" + maskHash; calculateStatistics(); } /** * Creates a new {@link DataContainer} for a specific set of image and * channel combination. It will give access to the image according to * the mask passed. It is expected that the mask is of the same size * as an image slice. Default thresholds, min, max and mean will be set * according to the mask as well. * * @param src1 The channel one image source * @param src2 The channel two image source * @param ch1 The channel one image channel * @param ch2 The channel two image channel * @param mask The mask to use * @param offset The offset of the ROI in each dimension * @param size The size of the ROI in each dimension * @throws MissingPreconditionException */ public DataContainer(RandomAccessibleInterval src1, RandomAccessibleInterval src2, int ch1, int ch2, String name1, String name2, final RandomAccessibleInterval mask, final long[] offset, final long[] size) throws MissingPreconditionException { sourceImage1 = src1; sourceImage2 = src2; this.ch1 = ch1; this.ch2 = ch2; sourceImage1Name = name1; sourceImage2Name = name2; final int numDims = src1.numDimensions(); maskBBOffset = new long[numDims]; maskBBSize = new long[numDims]; final long[] dim = new long[numDims]; src1.dimensions(dim); this.mask = MaskFactory.createMask(dim.clone(), mask); // this constructor supports irregular masks maskType = MaskType.Irregular; adjustRoiOffset(offset, maskBBOffset, dim); adjustRoiSize(size, maskBBSize, dim, maskBBOffset); maskHash = mask.hashCode(); // create a jobName so ResultHandler instances can all use the same // object for the job name. jobName = "Colocalization_of_" + sourceImage1Name + "_versus_" + sourceImage2Name + "_" + maskHash; calculateStatistics(); } /** * Creates a new {@link DataContainer} for a specific set of image and * channel combination. It will give access to the image according to * the region of interest (ROI) passed. Default thresholds, min, max and * mean will be set according to the ROI as well. * * @param src1 The channel one image source * @param src2 The channel two image source * @param ch1 The channel one image channel * @param ch2 The channel two image channel * @param offset The offset of the ROI in each dimension * @param size The size of the ROI in each dimension */ public DataContainer(RandomAccessibleInterval src1, RandomAccessibleInterval src2, int ch1, int ch2, String name1, String name2, final long[] offset, final long size[]) throws MissingPreconditionException { sourceImage1 = src1; sourceImage2 = src2; sourceImage1Name = name1; sourceImage2Name = name2; final int numDims = src1.numDimensions(); final long[] dim = new long[numDims]; src1.dimensions(dim); long[] roiOffset = new long[numDims]; long[] roiSize = new long[numDims]; adjustRoiOffset(offset, roiOffset, dim); adjustRoiSize(size, roiSize, dim, roiOffset); // create a mask that is valid everywhere mask = MaskFactory.createMask(dim, roiOffset, roiSize); maskBBOffset = roiOffset.clone(); maskBBSize = roiSize.clone(); // this constructor only supports regular masks maskType = MaskType.Regular; this.ch1 = ch1; this.ch2 = ch2; maskHash = mask.hashCode(); // create a jobName so ResultHandler instances can all use the same // object for the job name. jobName = "Colocalization_of_" + sourceImage1Name + "_versus_" + sourceImage2Name + "_" + maskHash; calculateStatistics(); } protected void calculateStatistics() { meanCh1 = ImageStatistics.getImageMean(sourceImage1, mask); meanCh2 = ImageStatistics.getImageMean(sourceImage2, mask); minCh1 = ImageStatistics.getImageMin(sourceImage1, mask).getRealDouble(); minCh2 = ImageStatistics.getImageMin(sourceImage2, mask).getRealDouble(); maxCh1 = ImageStatistics.getImageMax(sourceImage1, mask).getRealDouble(); maxCh2 = ImageStatistics.getImageMax(sourceImage2, mask).getRealDouble(); integralCh1 = ImageStatistics.getImageIntegral(sourceImage1, mask); integralCh2 = ImageStatistics.getImageIntegral(sourceImage2, mask); } /** * Make sure that the ROI offset has the same dimensionality * as the image. The method fills it up with zeros if needed. * * @param oldOffset The offset with the original dimensionality * @param newOffset The output array with the new dimensionality * @param dimensions An array of the dimensions * @throws MissingPreconditionException */ protected void adjustRoiOffset(long[] oldOffset, long[] newOffset, long[] dimensions) throws MissingPreconditionException { for (int i=0; i dimensions[i]) throw new MissingPreconditionException("Dimension " + i + " of ROI offset is larger than image dimension."); newOffset[i] = oldOffset[i]; } else { newOffset[i] = 0; } } } /** * Transforms a ROI size array to a dimensionality. The method * fill up with image (dimension - offset in that dimension) if * needed. * * @param oldSize Size array of old dimensionality * @param newSize Output size array of new dimensionality * @param dimensions Dimensions representing the new dimensionality * @param offset Offset of the new dimensionality * @throws MissingPreconditionException */ protected void adjustRoiSize(long[] oldSize, long[] newSize, long[] dimensions, long[] offset) throws MissingPreconditionException { for (int i=0; i (dimensions[i] - offset[i])) throw new MissingPreconditionException("Dimension " + i + " of ROI size is larger than what fits in."); newSize[i] = oldSize[i]; } else { newSize[i] = dimensions[i] - offset[i]; } } } public RandomAccessibleInterval getSourceImage1() { return sourceImage1; } public RandomAccessibleInterval getSourceImage2() { return sourceImage2; } public String getSourceImage1Name() { return "Ch1_" + sourceImage1Name; } public String getSourceImage2Name() { return "Ch2_" + sourceImage2Name; } public String getSourceCh1Name() { return sourceImage1Name; } public String getSourceCh2Name() { return sourceImage2Name; } public String getJobName() { return jobName; } public RandomAccessibleInterval getMask() { return mask; } public long[] getMaskBBOffset() { return maskBBOffset.clone(); } public long[] getMaskBBSize() { return maskBBSize.clone(); } public MaskType getMaskType(){ return maskType; } public int getMaskID(){ return maskHash; } public int getCh1() { return ch1; } public int getCh2() { return ch2; } public double getMeanCh1() { return meanCh1; } public double getMeanCh2() { return meanCh2; } public double getMinCh1() { return minCh1; } public double getMaxCh1() { return maxCh1; } public double getMinCh2() { return minCh2; } public double getMaxCh2() { return maxCh2; } public double getIntegralCh1() { return integralCh1; } public double getIntegralCh2() { return integralCh2; } public InputCheck getInputCheck() { return inputCheck; } public Algorithm setInputCheck(InputCheck inputCheck) { this.inputCheck = inputCheck; return inputCheck; } public AutoThresholdRegression getAutoThreshold() { return autoThreshold; } public Algorithm setAutoThreshold(AutoThresholdRegression autoThreshold) { this.autoThreshold = autoThreshold; return autoThreshold; } } diff --git a/src/main/java/gadgets/MaskFactory.java b/src/main/java/sc/fiji/coloc/gadgets/MaskFactory.java similarity index 98% rename from src/main/java/gadgets/MaskFactory.java rename to src/main/java/sc/fiji/coloc/gadgets/MaskFactory.java index 7b9c6e1..3d25032 100644 --- a/src/main/java/gadgets/MaskFactory.java +++ b/src/main/java/sc/fiji/coloc/gadgets/MaskFactory.java @@ -1,214 +1,214 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package gadgets; +package sc.fiji.coloc.gadgets; import java.util.Arrays; import net.imglib2.Cursor; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; import net.imglib2.img.ImgFactory; import net.imglib2.img.array.ArrayImgFactory; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; -import algorithms.MissingPreconditionException; +import sc.fiji.coloc.algorithms.MissingPreconditionException; public class MaskFactory { public enum CombinationMode { AND, OR, NONE } /** * Create a new mask image without any specific content, but with * a defined size. */ public static RandomAccessibleInterval createMask(long[] dim) { ImgFactory< BitType > imgFactory = new ArrayImgFactory< BitType >(); return imgFactory.create(dim, new BitType()); } /** * Create a new mask image with a defined size and preset content. */ public static RandomAccessibleInterval createMask(long[] dim, boolean val) { RandomAccessibleInterval mask = createMask(dim); for (BitType t : Views.iterable(mask)) t.set(val); return mask; } /** * Create a new mask image with a defined size and preset content. * @throws MissingPreconditionException */ public static RandomAccessibleInterval createMask(long[] dim, long[] roiOffset, long[] roiDim) throws MissingPreconditionException { if (dim.length != roiOffset.length || dim.length != roiDim.length) { throw new MissingPreconditionException("The dimensions of the mask as well as the ROIs and his offset must be the same."); } final RandomAccessibleInterval mask = createMask(dim); final int dims = mask.numDimensions(); final long[] pos = new long[dims]; // create an array with the max corner of the ROI final long[] roiOffsetMax = new long[dims]; for (int i=0; i cursor = Views.iterable(mask).localizingCursor(); while ( cursor.hasNext() ) { cursor.fwd(); cursor.localize(pos); boolean valid = true; // test if the current position is contained in the ROI for(int i=0; i= roiOffset[i] && pos[i] < roiOffsetMax[i]; cursor.get().set(valid); } return mask; } /** * Create a new mask based on a threshold condition for two images. */ public static> RandomAccessibleInterval createMask( RandomAccessibleInterval ch1, RandomAccessibleInterval ch2, T threshold1, T threshold2, ThresholdMode tMode, CombinationMode cMode) { final long[] dims = new long[ ch1.numDimensions() ]; ch1.dimensions(dims); RandomAccessibleInterval mask = createMask(dims); Cursor cursor1 = Views.iterable(ch1).cursor(); Cursor cursor2 = Views.iterable(ch2).cursor(); Cursor maskCursor = Views.iterable(mask).cursor(); while (cursor1.hasNext() && cursor2.hasNext() && maskCursor.hasNext()) { cursor1.fwd(); cursor2.fwd(); maskCursor.fwd(); boolean ch1Valid, ch2Valid; T data1 = cursor1.get(); T data2 = cursor2.get(); // get relation to threshold if (tMode == ThresholdMode.Above) { ch1Valid = data1.compareTo(threshold1) > 0; ch2Valid = data2.compareTo(threshold2) > 0; } else if (tMode == ThresholdMode.Below) { ch1Valid = data1.compareTo(threshold1) < 0; ch2Valid = data2.compareTo(threshold2) < 0; } else { throw new UnsupportedOperationException(); } BitType maskData = maskCursor.get(); // combine the results into mask if (cMode == CombinationMode.AND) { maskData.set( ch1Valid && ch2Valid ); } else if (cMode == CombinationMode.OR) { maskData.set( ch1Valid || ch2Valid ); } else if (cMode == CombinationMode.NONE) { maskData.set( !(ch1Valid || ch2Valid) ); } else { throw new UnsupportedOperationException(); } } return mask; } /** * Creates a new mask of the given dimensions, based on the image data * in the passed image. If the requested dimensionality is higher than * what is available in the data, the data gets repeated in the higher * dimensions. * * @param dim The dimensions of the new mask image * @param origMask The image from which the mask should be created from */ public static> RandomAccessibleInterval createMask( final long[] dim, final RandomAccessibleInterval origMask) { final RandomAccessibleInterval mask = createMask(dim); final long[] origDim = new long[ origMask.numDimensions() ]; origMask.dimensions(origDim); // test if original mask and new mask have same dimensions if (Arrays.equals(dim, origDim)) { // copy the input image to the mask output image Cursor origCursor = Views.iterable(origMask).localizingCursor(); RandomAccess maskCursor = mask.randomAccess(); while (origCursor.hasNext()) { origCursor.fwd(); maskCursor.setPosition(origCursor); boolean value = origCursor.get().getRealDouble() > 0.001; maskCursor.get().set(value); } } else if (dim.length > origDim.length) { // sanity check for (int i=0; i origCursor = Views.iterable(origMask).localizingCursor(); RandomAccess maskCursor = mask.randomAccess(); final long[] pos = new long[ origMask.numDimensions() ]; // iterate over the original mask while (origCursor.hasNext()) { origCursor.fwd(); origCursor.localize(pos); boolean value = origCursor.get().getRealDouble() > 0.001; // set available (lower dimensional) position information for (int i=0; i. * #L% */ -package gadgets; +package sc.fiji.coloc.gadgets; import java.util.List; /** * This class provides some basic statistics methods. * * @author Tom Kazimiers * */ public class Statistics { // have the inverted square root of two ready to use static final double invSqrtTwo = 1.0 / Math.sqrt(2); /** * Calculates an estimate of the upper tail cumulative normal distribution * (which is simply the complementary error function with linear scalings * of x and y axis). * * Fractional error in math formula less than 1.2 * 10 ^ -7. * although subject to catastrophic cancellation when z in very close to 0 * * Code from (thanks to Bob Dougherty): * w * * Original algorithm from Section 6.2 of Numerical Recipes */ public static double erf(double z) { double t = 1.0 / (1.0 + 0.5 * Math.abs(z)); // use Horner's method double ans = 1 - t * Math.exp( -z*z - 1.26551223 + t * ( 1.00002368 + t * ( 0.37409196 + t * ( 0.09678418 + t * (-0.18628806 + t * ( 0.27886807 + t * (-1.13520398 + t * ( 1.48851587 + t * (-0.82215223 + t * ( 0.17087277)))))))))); if (z >= 0) return ans; else return -ans; } /** * Calculates phi, which is the area of the Gaussian standard * distribution from minus infinity to the query value in units * of standard derivation. * The formula is: * * 1 + erf( z / sqrt(2) ) * Phi(z) = ---------------------- * 2 * @param z The point of interest * @return phi */ public static double phi(double z) { return 0.5 * (1.0 + erf( z * invSqrtTwo ) ); } /** * Calculates phi, but with a Gaussian distribution defined by * its mean and its standard derivation. This is a quantile. * * 1 + erf( (z - mean) / (sqrt(2) * stdDev) ) * Phi(z,mean,stdDev) = ------------------------------------------ * 2 * * @param z The point of interest * @param mean The mean of the distribution * @param sd The standard derivation of the distribution * @return phi */ public static double phi(double z, double mean, double sd) { return phi( (z - mean) / sd); } /** * Calculates the standard deviation of a list of values. * * @param values The list of values. * @return The standard deviation. */ public static double stdDeviation(List values) { int count = values.size(); // calculate mean double sum = 0; for( Double val : values ) { sum += val; } double mean = sum / count; // calculate deviates sum = 0; for( Double val : values ) { double diff = val - mean; double sqDiff = diff * diff; sum += sqDiff; } double stdDeviation = Math.sqrt( sum / (count - 1) ); return stdDeviation; } } diff --git a/src/main/java/gadgets/ThresholdMode.java b/src/main/java/sc/fiji/coloc/gadgets/ThresholdMode.java similarity index 96% rename from src/main/java/gadgets/ThresholdMode.java rename to src/main/java/sc/fiji/coloc/gadgets/ThresholdMode.java index 2f442e0..b2a923b 100644 --- a/src/main/java/gadgets/ThresholdMode.java +++ b/src/main/java/sc/fiji/coloc/gadgets/ThresholdMode.java @@ -1,29 +1,29 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package gadgets; +package sc.fiji.coloc.gadgets; /** * An enumerator for different modes of threshold handling. */ public enum ThresholdMode { Below, Above, None } diff --git a/src/main/java/results/AnalysisResults.java b/src/main/java/sc/fiji/coloc/results/AnalysisResults.java similarity index 97% rename from src/main/java/results/AnalysisResults.java rename to src/main/java/sc/fiji/coloc/results/AnalysisResults.java index 4d8b5d7..4baf757 100644 --- a/src/main/java/results/AnalysisResults.java +++ b/src/main/java/sc/fiji/coloc/results/AnalysisResults.java @@ -1,114 +1,114 @@ -package results; +package sc.fiji.coloc.results; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import net.imglib2.RandomAccessibleInterval; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.LongType; -import algorithms.Histogram2D; +import sc.fiji.coloc.algorithms.Histogram2D; /** * Data structure housing all colocalisation results. Intended for programmatic * access via API calls. * * @author Curtis Rueden */ public class AnalysisResults> implements ResultHandler { /** Result images, no matter what specific kinds. */ private final List>>> listOfImages = new ArrayList<>(); /** Histogram results. */ private final Map, Histogram2D> mapOf2DHistograms = new HashMap<>(); /** Warnings produced during analysis. */ private final List warnings = new ArrayList<>(); /** Named values, collected from algorithms. */ private final List valueResults = new ArrayList<>(); /** * Images and corresponding LUTs. When an image is not in there no LUT should * be applied. */ private final Map listOfLUTs = new HashMap<>(); // -- AllTheData methods -- public List>>> images() { return listOfImages; } public Map, Histogram2D> histograms() { return mapOf2DHistograms; } public List warnings() { return warnings; } public List values() { return valueResults; } // -- ResultHandler methods -- @Override public void handleImage(final RandomAccessibleInterval image, final String name) { listOfImages.add( new NamedContainer>>(image, name)); } @Override public void handleHistogram(final Histogram2D histogram, final String name) { listOfImages.add( new NamedContainer>>( histogram.getPlotImage(), name)); mapOf2DHistograms.put(histogram.getPlotImage(), histogram); // link the histogram to a LUT listOfLUTs.put(histogram.getPlotImage(), "Fire"); } @Override public void handleWarning(final Warning warning) { warnings.add(warning); } @Override public void handleValue(final String name, final String value) { valueResults.add(new ValueResult(name, value)); } @Override public void handleValue(final String name, final double value) { handleValue(name, value, 3); } @Override public void handleValue(final String name, final double value, final int decimals) { valueResults.add(new ValueResult(name, value, decimals)); } @Override public void process() { // NB: No action needed. } } diff --git a/src/main/java/results/EasyDisplay.java b/src/main/java/sc/fiji/coloc/results/EasyDisplay.java similarity index 96% rename from src/main/java/results/EasyDisplay.java rename to src/main/java/sc/fiji/coloc/results/EasyDisplay.java index bf25822..977ce60 100644 --- a/src/main/java/results/EasyDisplay.java +++ b/src/main/java/sc/fiji/coloc/results/EasyDisplay.java @@ -1,119 +1,119 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package results; +package sc.fiji.coloc.results; import ij.IJ; import ij.ImagePlus; import ij.text.TextWindow; import net.imglib2.RandomAccessibleInterval; import net.imglib2.algorithm.math.ImageStatistics; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.type.numeric.RealType; -import algorithms.Histogram2D; -import gadgets.DataContainer; +import sc.fiji.coloc.algorithms.Histogram2D; +import sc.fiji.coloc.gadgets.DataContainer; public class EasyDisplay> implements ResultHandler { // the text window to present value and text results protected static TextWindow textWindow; /* the data container with general information about the * source images that were processed by the algorithms. */ protected DataContainer container; public EasyDisplay(DataContainer container) { final int twWidth = 170; final int twHeight = 250; //test if the results windows is already there, if so use it. if (textWindow == null || !textWindow.isVisible()) textWindow = new TextWindow("Results", "Result\tValue\n", "", twWidth, twHeight); else { // set dimensions textWindow.setSize(twWidth, twHeight); } // deactivate the window for now textWindow.setVisible(false); // save a reference to the data container this.container = container; } @Override public void handleImage(RandomAccessibleInterval image, String name) { ImagePlus imp = ImageJFunctions.wrapFloat( image, name ); double max = ImageStatistics.getImageMax( image ).getRealDouble(); showImage( imp, max ); } @Override public void handleHistogram(Histogram2D histogram, String name) { ImagePlus imp = ImageJFunctions.wrapFloat( histogram.getPlotImage(), name ); double max = ImageStatistics.getImageMax( histogram.getPlotImage() ).getRealDouble(); showImage( imp, max ); } protected void showImage(ImagePlus imp, double max) { // set the display range imp.setDisplayRange(0.0, max); imp.show(); } @Override public void handleWarning(Warning warning) { // no warnings are shown in easy display } @Override public void handleValue(String name, String value) { textWindow.getTextPanel().appendLine(name + "\t" + value + "\n"); } @Override public void handleValue(String name, double value) { handleValue(name, value, 3); } @Override public void handleValue(String name, double value, int decimals) { handleValue(name, IJ.d2s(value, decimals)); } protected void printTextStatistics(DataContainer container){ textWindow.getTextPanel().appendLine("Ch1 Mean\t" + container.getMeanCh1() + "\n"); textWindow.getTextPanel().appendLine("Ch2 Mean\t" + container.getMeanCh2() + "\n"); textWindow.getTextPanel().appendLine("Ch1 Min\t" + container.getMinCh1() + "\n"); textWindow.getTextPanel().appendLine("Ch2 Min\t" + container.getMinCh2() + "\n"); textWindow.getTextPanel().appendLine("Ch1 Max\t" + container.getMaxCh1() + "\n"); textWindow.getTextPanel().appendLine("Ch2 Max\t" + container.getMaxCh2() + "\n"); } @Override public void process() { // print some general information about images printTextStatistics(container); // show the results textWindow.setVisible(true); IJ.selectWindow("Results"); } } diff --git a/src/main/java/results/NamedContainer.java b/src/main/java/sc/fiji/coloc/results/NamedContainer.java similarity index 97% rename from src/main/java/results/NamedContainer.java rename to src/main/java/sc/fiji/coloc/results/NamedContainer.java index af5c4e0..730d699 100644 --- a/src/main/java/results/NamedContainer.java +++ b/src/main/java/sc/fiji/coloc/results/NamedContainer.java @@ -1,46 +1,46 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package results; +package sc.fiji.coloc.results; /** * A small container to name objects by overriding * the toString method. * */ public class NamedContainer { T object; String name; public NamedContainer(T object, String name) { this.object = object; this.name = name; } public T getObject() { return object; } @Override public String toString() { return name; } } diff --git a/src/main/java/results/PDFWriter.java b/src/main/java/sc/fiji/coloc/results/PDFWriter.java similarity index 97% rename from src/main/java/results/PDFWriter.java rename to src/main/java/sc/fiji/coloc/results/PDFWriter.java index 4e0f940..a434b2f 100644 --- a/src/main/java/results/PDFWriter.java +++ b/src/main/java/sc/fiji/coloc/results/PDFWriter.java @@ -1,260 +1,260 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package results; +package sc.fiji.coloc.results; import com.itextpdf.text.BadElementException; import com.itextpdf.text.Document; import com.itextpdf.text.DocumentException; import com.itextpdf.text.PageSize; import com.itextpdf.text.Paragraph; import com.itextpdf.text.pdf.PdfContentByte; import com.itextpdf.text.pdf.PdfWriter; import ij.IJ; import ij.ImagePlus; import ij.io.SaveDialog; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import net.imglib2.RandomAccessibleInterval; import net.imglib2.algorithm.math.ImageStatistics; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.LongType; -import algorithms.Histogram2D; -import gadgets.DataContainer; -import gadgets.DataContainer.MaskType; +import sc.fiji.coloc.algorithms.Histogram2D; +import sc.fiji.coloc.gadgets.DataContainer; +import sc.fiji.coloc.gadgets.DataContainer.MaskType; public class PDFWriter> implements ResultHandler { // indicates if we want to produce US letter or A4 size boolean isLetter = false; // indicates if the content is the first item on the page boolean isFirst = true; // show the name of the image static boolean showName=true; // a static counter for this sessions created PDFs static int succeededPrints = 0; // show the size in pixels of the image static boolean showSize=true; // a reference to the data container DataContainer container; PdfWriter writer; Document document; // a list of the available result images, no matter what specific kinds protected List listOfPDFImages = new ArrayList(); protected List listOfPDFTexts = new ArrayList(); // a list of PDF warnings protected List PDFwarnings = new ArrayList(); /** * Creates a new PDFWriter that can access the container. * * @param container The data container for source image data */ public PDFWriter(DataContainer container) { this.container = container; } @Override public void handleImage(RandomAccessibleInterval image, String name) { ImagePlus imp = ImageJFunctions.wrapFloat( image, name ); // set the display range double max = ImageStatistics.getImageMax(image).getRealDouble(); imp.setDisplayRange(0.0, max); addImageToList(imp, name); } /** * Handles a histogram the following way: create snapshot, log data, reset the * display range, apply the Fire LUT and finally store it as an iText PDF image. * Afterwards the image is reset to its orignal state again */ @Override public void handleHistogram(Histogram2D histogram, String name) { RandomAccessibleInterval image = histogram.getPlotImage(); ImagePlus imp = ImageJFunctions.wrapFloat( image, name ); // make a snapshot to be able to reset after modifications imp.getProcessor().snapshot(); imp.getProcessor().log(); imp.updateAndDraw(); imp.getProcessor().resetMinAndMax(); IJ.run(imp,"Fire", null); addImageToList(imp, name); // reset the imp from the log scaling we applied earlier imp.getProcessor().reset(); } protected void addImageToList(ImagePlus imp, String name) { java.awt.Image awtImage = imp.getImage(); try { com.itextpdf.text.Image pdfImage = com.itextpdf.text.Image.getInstance(awtImage, null); pdfImage.setAlt(name); // iText-1.3 setMarkupAttribute("name", name); listOfPDFImages.add(pdfImage); } catch (BadElementException e) { IJ.log("Could not convert image to correct format for PDF generation"); IJ.handleException(e); } catch (IOException e) { IJ.log("Could not convert image to correct format for PDF generation"); IJ.handleException(e); } } @Override public void handleWarning(Warning warning) { PDFwarnings.add(new Paragraph("Warning! " + warning.getShortMessage() + " - " + warning.getLongMessage())); } @Override public void handleValue(String name, String value) { listOfPDFTexts.add(new Paragraph(name + ": " + value)); } @Override public void handleValue(String name, double value) { handleValue(name, value, 3); } @Override public void handleValue(String name, double value, int decimals) { listOfPDFTexts.add(new Paragraph(name + ": " + IJ.d2s(value, decimals))); } /** * Prints an image into the opened PDF. * @param image The image to print. */ protected void addImage(com.itextpdf.text.Image image) throws DocumentException, IOException { if (! isFirst) { document.add(new Paragraph("\n")); float vertPos = writer.getVerticalPosition(true); if (vertPos - document.bottom() < image.getHeight()) { document.newPage(); } else { PdfContentByte cb = writer.getDirectContent(); cb.setLineWidth(1f); if (isLetter) { cb.moveTo(PageSize.LETTER.getLeft(50), vertPos); cb.lineTo(PageSize.LETTER.getRight(50), vertPos); } else { cb.moveTo(PageSize.A4.getLeft(50), vertPos); cb.lineTo(PageSize.A4.getRight(50), vertPos); } cb.stroke(); } } if (showName) { Paragraph paragraph = new Paragraph(image.getAlt()); // iText-1.3: getMarkupAttribute("name")); paragraph.setAlignment(Paragraph.ALIGN_CENTER); document.add(paragraph); //spcNm = 40; } if (showSize) { Paragraph paragraph = new Paragraph(image.getWidth() + " x " + image.getHeight()); paragraph.setAlignment(Paragraph.ALIGN_CENTER); document.add(paragraph); //spcSz = 40; } image.setAlignment(com.itextpdf.text.Image.ALIGN_CENTER); document.add(image); isFirst = false; } @Override public void process() { try { // Use the getJobName() in DataContainer for the job name. String jobName = container.getJobName(); /* If a mask is in use, add a counter * information to the jobName. */ if (container.getMaskType() != MaskType.None) { // maskHash is now used as the mask or ROI unique ID in the // jobName but we can still increment and use succeededPrints at // the end of the filename for PDFs when there is a mask. jobName += (succeededPrints + 1); } // get the path to the file we are about to create SaveDialog sd = new SaveDialog("Save as PDF", jobName, ".pdf"); // update jobName if the user changes it in the save file dialog. jobName = sd.getFileName(); String directory = sd.getDirectory(); // make sure we have what we need next if ((jobName == null) || (directory == null)) { return; } String path = directory+jobName; // create a new iText Document and add date and title document = new Document(isLetter ? PageSize.LETTER : PageSize.A4); document.addCreationDate(); document.addTitle(jobName); // get a writer object to do the actual output writer = PdfWriter.getInstance(document, new FileOutputStream(path)); document.open(); // write job name at the top of the PDF file as a title Paragraph titlePara = new Paragraph(jobName); document.add(titlePara); // iterate over all produced images for (com.itextpdf.text.Image img : listOfPDFImages) { addImage(img); } //iterate over all produced PDFwarnings for (Paragraph p : PDFwarnings) { document.add(p); } //iterate over all produced text objects for (Paragraph p : listOfPDFTexts) { document.add(p); } } catch(DocumentException de) { IJ.showMessage("PDF Writer", de.getMessage()); } catch(IOException ioe) { IJ.showMessage("PDF Writer", ioe.getMessage()); } finally { if (document !=null) { document.close(); succeededPrints++; } } } } diff --git a/src/main/java/results/ResultHandler.java b/src/main/java/sc/fiji/coloc/results/ResultHandler.java similarity index 95% rename from src/main/java/results/ResultHandler.java rename to src/main/java/sc/fiji/coloc/results/ResultHandler.java index bbde71d..ef61582 100644 --- a/src/main/java/results/ResultHandler.java +++ b/src/main/java/sc/fiji/coloc/results/ResultHandler.java @@ -1,56 +1,56 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package results; +package sc.fiji.coloc.results; import net.imglib2.RandomAccessibleInterval; import net.imglib2.type.numeric.RealType; -import algorithms.Histogram2D; +import sc.fiji.coloc.algorithms.Histogram2D; /** * A result handler offers different methods to process results * of algorithms. Algorithms get passed such a result handler and * can let the handler process whatever information they like. * * @param The source images value type */ public interface ResultHandler> { void handleImage(RandomAccessibleInterval image, String name); void handleHistogram(Histogram2D histogram, String name); void handleWarning(Warning warning); void handleValue(String name, String value); void handleValue(String name, double value); void handleValue(String name, double value, int decimals); /** * The process method should start the processing of the * previously collected results. E.g. it could show some * windows or produce a final zip file. */ void process(); } diff --git a/src/main/java/results/SingleWindowDisplay.java b/src/main/java/sc/fiji/coloc/results/SingleWindowDisplay.java similarity index 99% rename from src/main/java/results/SingleWindowDisplay.java rename to src/main/java/sc/fiji/coloc/results/SingleWindowDisplay.java index 8ecfd55..acda598 100644 --- a/src/main/java/results/SingleWindowDisplay.java +++ b/src/main/java/sc/fiji/coloc/results/SingleWindowDisplay.java @@ -1,721 +1,722 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package results; +package sc.fiji.coloc.results; import ij.IJ; import ij.ImageJ; import ij.ImagePlus; import ij.gui.Line; import ij.gui.Overlay; import ij.process.ImageProcessor; import ij.text.TextWindow; import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Panel; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.ClipboardOwner; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JEditorPane; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingConstants; import javax.swing.border.EmptyBorder; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.LongType; -import algorithms.AutoThresholdRegression; -import algorithms.Histogram2D; +import sc.fiji.coloc.algorithms.AutoThresholdRegression; +import sc.fiji.coloc.algorithms.Histogram2D; +import sc.fiji.coloc.gadgets.DataContainer; + import fiji.util.gui.JImagePanel; -import gadgets.DataContainer; /** * This class displays the container contents in one single window and offers * features like the use of different LUTs. * */ public class SingleWindowDisplay> extends JFrame implements ResultHandler, ItemListener, ActionListener, ClipboardOwner, MouseMotionListener { private static final long serialVersionUID = -5642321584354176878L; protected static final int WIN_WIDTH = 350; protected static final int WIN_HEIGHT = 600; // indicates if original images should be displayed or not protected boolean displayOriginalImages = false; // this is the image currently selected by the drop down menu protected RandomAccessibleInterval> currentlyDisplayedImageResult; // a list of the available result images, no matter what specific kinds protected List>>> listOfImages = new ArrayList>>>(); protected Map, Histogram2D> mapOf2DHistograms = new HashMap, Histogram2D>(); // a list of warnings protected List warnings = new ArrayList(); // a list of named values, collected from algorithms protected List valueResults = new ArrayList(); /* * a map of images and corresponding LUTs. When an image is not in there no * LUT should be applied. */ protected Map listOfLUTs = new HashMap(); // make a cursor so we can get pixel values from the image protected RandomAccess> pixelAccessCursor; // A PDF writer to call if user wants PDF print protected PDFWriter pdfWriter; // The current image protected ImagePlus imp; // GUI elements protected JImagePanel imagePanel; protected JButton listButton, copyButton; protected JCheckBox log; /* * The data container with general information about source images */ protected DataContainer dataContainer = null; public SingleWindowDisplay(DataContainer container, PDFWriter pdfWriter) { // Show job name in title bar super(container.getJobName()); setPreferredSize(new Dimension(WIN_WIDTH, WIN_HEIGHT)); // save a reference to the container dataContainer = container; this.pdfWriter = pdfWriter; // don't show ourself on instantiation this.setVisible(false); } public void setup() { JComboBox dropDownList = new JComboBox(); for (NamedContainer>> img : listOfImages) { dropDownList .addItem(new NamedContainer>>(img.object, img.name)); } dropDownList.addItemListener(this); imagePanel = new JImagePanel(ij.IJ.createImage("dummy", "8-bit", 10, 10, 1)); imagePanel.addMouseMotionListener(this); // Create something to display it in final JEditorPane editor = new JEditorPane(); editor.setEditable(false); // we're browsing not editing editor.setContentType("text/html"); // must specify HTML text editor.setText(makeHtmlText()); // specify the text to display // Put the JEditorPane in a scrolling window and add it JScrollPane scrollPane = new JScrollPane(editor); scrollPane.setPreferredSize(new Dimension(256, 150)); Panel buttons = new Panel(); buttons.setLayout(new FlowLayout(FlowLayout.RIGHT)); // add button for data display of histograms listButton = new JButton("List"); listButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { showList(); } }); buttons.add(listButton); // add button for data copy of histograms copyButton = new JButton("Copy"); copyButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { copyToClipboard(); } }); buttons.add(copyButton); // add button for PDF printing JButton pdfButten = new JButton("PDF"); pdfButten.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { pdfWriter.process(); } }); buttons.add(pdfButten); /* * We want the image to be log scale by default so the user can see * something. */ log = new JCheckBox("Log"); log.setSelected(true); log.addActionListener(this); buttons.add(log); final GridBagLayout layout = new GridBagLayout(); final Container pane = getContentPane(); getContentPane().setLayout(layout); final GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.BOTH; c.weightx = 1; c.gridwidth = GridBagConstraints.BOTH; c.gridy++; pane.add(dropDownList, c); c.gridy++; c.weighty = 1; // code to include axis labels JPanel imageAndLabelPanel = new JPanel(); imageAndLabelPanel.setLayout(new BorderLayout()); imageAndLabelPanel.add(imagePanel, BorderLayout.CENTER); JLabel yAxisLabel = new JLabel(labelName(2, dataContainer.getSourceCh2Name())); yAxisLabel.setHorizontalAlignment(SwingConstants.RIGHT); yAxisLabel.setBorder(new EmptyBorder(0, 15, 0, 0)); imageAndLabelPanel.add(yAxisLabel, BorderLayout.WEST); JLabel xAxisLabel = new JLabel(labelName(1, dataContainer.getSourceCh1Name())); xAxisLabel.setHorizontalAlignment(SwingConstants.CENTER); xAxisLabel.setBorder(new EmptyBorder(0, 0, 15, 0)); imageAndLabelPanel.add(xAxisLabel, BorderLayout.SOUTH); pane.add(imageAndLabelPanel, c); c.gridy++; c.weighty = 1; pane.add(scrollPane, c); c.weighty = 0; c.gridy++; pane.add(buttons, c); } private String labelName(int ch, String s) { final int maxLen = 30; final String shortName = s.length() > maxLen ? // s.substring(0, maxLen - 3) + "..." : s; return "
Channel " + ch + "
(" + shortName + ")
"; } @Override public void process() { // if wanted, display source images if (displayOriginalImages) { listOfImages.add(new NamedContainer>>( dataContainer.getSourceImage1(), dataContainer.getSourceImage1Name())); listOfImages.add(new NamedContainer>>( dataContainer.getSourceImage2(), dataContainer.getSourceImage2Name())); } // set up the GUI, which runs makeHtmlText() for the value results // formatting. setup(); // display the first image available, if any if (listOfImages.size() > 0) { adjustDisplayedImage(listOfImages.get(0).object); } // show the GUI setSize(600,600); this.setVisible(true); } @Override public void handleImage(RandomAccessibleInterval image, String name) { listOfImages.add(new NamedContainer>>(image, name)); } @Override public void handleHistogram(Histogram2D histogram, String name) { listOfImages.add( new NamedContainer>>(histogram.getPlotImage(), name)); mapOf2DHistograms.put(histogram.getPlotImage(), histogram); // link the histogram to a LUT listOfLUTs.put(histogram.getPlotImage(), "Fire"); } @Override public void handleWarning(Warning warning) { warnings.add(warning); } @Override public void handleValue(String name, String value) { valueResults.add(new ValueResult(name, value)); } @Override public void handleValue(String name, double value) { handleValue(name, value, 3); } @Override public void handleValue(String name, double value, int decimals) { valueResults.add(new ValueResult(name, value, decimals)); } /** * Prints an HTML table entry onto the stream. */ protected void printTableRow(PrintWriter out, String name, String text) { out.print("" + name + "" + escape(text) + ""); } private String escape(final String text) { final int maxChars = 40, minChars = 10; final StringBuilder sb = new StringBuilder(); boolean first = true; for (final String word : text.split(" ")) { if (first) first = false; else sb.append(" "); sb.append(chop(word, maxChars, minChars)); } return sb.toString(); } /** Split up a monster word into chunks. */ private String chop(String word, int maxChars, int minChars) { final StringBuilder sb = new StringBuilder(); for (int i = 0; i < word.length(); i+=maxChars) { int end = Math.min(i+maxChars, word.length()); String fragment = word.substring(i,end); if (i > 0 && fragment.length() > minChars) sb.append(" "); sb.append(fragment); } return sb.toString(); } /** * Prints an HTML table entry onto the stream. */ protected void printTableRow(PrintWriter out, String name, double number, int decimalPlaces) { String stringNum = IJ.d2s(number, decimalPlaces); printTableRow(out, name, stringNum); } /** * This method creates CSS formatted HTML source out of the results stored * in the member variables and adds some image statistics found in the data * container. * * @return The HTML source to display */ protected String makeHtmlText() { StringWriter sout = new StringWriter(); PrintWriter out = new PrintWriter(sout); out.print(""); // add some style information out.print(""); out.print(""); // print out warnings, if any if (warnings.size() > 0) { out.print("

Warnings

"); // Print out the table out.print(""); out.print(""); for (Warning w : warnings) { printTableRow(out, w.getShortMessage(), w.getLongMessage()); } out.println("
TypeMessage
"); } else { out.print("

No warnings occurred.

"); } // Spit warnings to the IJ log IJ.log("!!! WARNINGS !!!"); for (Warning war : warnings) { IJ.log("Warning! " + war.getShortMessage() + " - " + war.getLongMessage()); } // print out simple value results out.print("

Results

"); // Print out the table // out.print(""); out.print("
"); out.print(""); // Print table rows and spit results to the IJ log. IJ.log("RESULTS:"); for (ValueResult vr : valueResults) { if (vr.isNumber) { printTableRow(out, vr.name, vr.number, vr.decimals); IJ.log(vr.name + ", " + IJ.d2s(vr.number, vr.decimals)); } else { printTableRow(out, vr.name, vr.value); IJ.log(vr.name + ", " + vr.value); } } out.println("
NameResult
"); out.print(""); out.close(); // Get the string of HTML from the StringWriter and return it. return sout.toString(); } /** * If the currently selected ImageResult is an HistrogramResult, a table of * x-values, y-values and the counts. */ protected void showList() { /* * check if we are dealing with an histogram result or a generic image * result */ if (isHistogram(currentlyDisplayedImageResult)) { Histogram2D hr = mapOf2DHistograms.get(currentlyDisplayedImageResult); double xBinWidth = 1.0 / hr.getXBinWidth(); double yBinWidth = 1.0 / hr.getYBinWidth(); // check if we have bins of size one or other ones boolean xBinWidthIsOne = Math.abs(xBinWidth - 1.0) < 0.00001; boolean yBinWidthIsOne = Math.abs(yBinWidth - 1.0) < 0.00001; // configure table headings accordingly String vHeadingX = xBinWidthIsOne ? "X value" : "X bin start"; String vHeadingY = yBinWidthIsOne ? "Y value" : "Y bin start"; // get the actual histogram data String histogramData = hr.getData(); TextWindow tw = new TextWindow(getTitle(), vHeadingX + "\t" + vHeadingY + "\tcount", histogramData, 250, 400); tw.setVisible(true); } } /** * If the currently selected ImageResult is an HistogramRestult, this method * copies its data into to the clipboard. */ protected void copyToClipboard() { /* * check if we are dealing with an histogram result or a generic image * result */ if (isHistogram(currentlyDisplayedImageResult)) { /* * try to get the system clipboard and return if we can't get it */ Clipboard systemClipboard = null; try { systemClipboard = getToolkit().getSystemClipboard(); } catch (Exception e) { systemClipboard = null; } if (systemClipboard == null) { IJ.error("Unable to copy to Clipboard."); return; } // copy histogram values IJ.showStatus("Copying histogram values..."); String text = mapOf2DHistograms.get(currentlyDisplayedImageResult).getData(); StringSelection contents = new StringSelection(text); systemClipboard.setContents(contents, this); IJ.showStatus(text.length() + " characters copied to Clipboard"); } } @Override public void mouseDragged(MouseEvent e) { // nothing to do here } @Override public void mouseMoved(MouseEvent e) { if (e.getSource().equals(imagePanel)) { /* * calculate the mouse position relative to the upper left corner of * the displayed image. */ final int imgWidth = imagePanel.getSrcRect().width; final int imgHeight = imagePanel.getSrcRect().height; int displayWidth = (int) (imgWidth * imagePanel.getMagnification()); int displayHeight = (int) (imgHeight * imagePanel.getMagnification()); int offsetX = (imagePanel.getWidth() - displayWidth) / 2; int offsetY = (imagePanel.getHeight() - displayHeight) / 2; int onImageX = imagePanel.screenX(e.getX() - offsetX); int onImageY = imagePanel.screenY(e.getY() - offsetY); // make sure we stay within the image boundaries if (onImageX >= 0 && onImageX < imgWidth && onImageY >= 0 && onImageY < imgHeight) { mouseMoved(onImageX, onImageY); } else { IJ.showStatus(""); } } } /** * Displays information about the pixel below the mouse cursor of the * currently displayed image result. The coordinates passed are expected to * be within the image boundaries. * * @param x * @param y */ public void mouseMoved(int x, int y) { final ImageJ ij = IJ.getInstance(); if (ij != null && currentlyDisplayedImageResult != null) { /* * If Alt key is not pressed, display the calibrated data. If not, * display image positions and data. Non log image intensity from * original image or 2D histogram result is always shown in status * bar, not the log intensity that might actually be displayed in * the image. */ if (!IJ.altKeyDown()) { // the alt key is not pressed use x and y values that are bin // widths or calibrated intensities not the x y image // coordinates. if (isHistogram(currentlyDisplayedImageResult)) { Histogram2D histogram = mapOf2DHistograms.get(currentlyDisplayedImageResult); synchronized (pixelAccessCursor) { // set position of output cursor pixelAccessCursor.setPosition(x, 0); pixelAccessCursor.setPosition(y, 1); // for a histogram coordinate display we need to invert // the Y axis y = (int) currentlyDisplayedImageResult.dimension(1) - 1 - y; // get current value at position RandomAccess cursor = (RandomAccess) pixelAccessCursor; long val = cursor.get().getIntegerLong(); double calibratedXBinBottom = histogram.getXMin() + x / histogram.getXBinWidth(); double calibratedXBinTop = histogram.getXMin() + (x + 1) / histogram.getXBinWidth(); double calibratedYBinBottom = histogram.getYMin() + y / histogram.getYBinWidth(); double calibratedYBinTop = histogram.getYMin() + (y + 1) / histogram.getYBinWidth(); IJ.showStatus("x = " + IJ.d2s(calibratedXBinBottom) + " to " + IJ.d2s(calibratedXBinTop) + ", y = " + IJ.d2s(calibratedYBinBottom) + " to " + IJ.d2s(calibratedYBinTop) + ", value = " + val); } } else { RandomAccessibleInterval img = (RandomAccessibleInterval) currentlyDisplayedImageResult; ImagePlus imp = ImageJFunctions.wrapFloat(img, "TODO"); imp.mouseMoved(x, y); } } else { // alt key is down, so show the image coordinates for x y in // status bar. RandomAccessibleInterval img = (RandomAccessibleInterval) currentlyDisplayedImageResult; ImagePlus imp = ImageJFunctions.wrapFloat(img, "TODO"); imp.mouseMoved(x, y); } } } /** * Draws the passed ImageResult on the ImagePlus of this class. If the image * is part of a CompositeImageResult then contained lines will also be drawn */ protected void drawImage(RandomAccessibleInterval> img) { // get ImgLib image as ImageJ image imp = ImageJFunctions.wrapFloat((RandomAccessibleInterval) img, "TODO"); imagePanel.updateImage(imp); // set the display range // check if a LUT should be applied if (listOfLUTs.containsKey(img)) { // select linked look up table IJ.run(imp, listOfLUTs.get(img), null); } imp.getProcessor().resetMinAndMax(); boolean overlayModified = false; Overlay overlay = new Overlay(); // if it is the 2d histogram, we want to show the regression line if (isHistogram(img)) { Histogram2D histogram = mapOf2DHistograms.get(img); /* * check if we should draw a regression line for the current * histogram. */ if (histogram.getDrawingSettings().contains(Histogram2D.DrawingFlags.RegressionLine)) { AutoThresholdRegression autoThreshold = dataContainer.getAutoThreshold(); if (histogram != null && autoThreshold != null) { if (img == histogram.getPlotImage()) { drawLine(overlay, img, autoThreshold.getAutoThresholdSlope(), autoThreshold.getAutoThresholdIntercept()); overlayModified = true; } } } } if (overlayModified) { overlay.setStrokeColor(java.awt.Color.WHITE); imp.setOverlay(overlay); } imagePanel.repaint(); } /** * Tests whether the given image is a histogram or not. * * @param img * The image to test * @return true if histogram, false otherwise */ protected boolean isHistogram(RandomAccessibleInterval> img) { return mapOf2DHistograms.containsKey(img); } /** * Draws the line on the overlay. */ protected void drawLine(Overlay overlay, RandomAccessibleInterval> img, double slope, double intercept) { double startX, startY, endX, endY; long imgWidth = img.dimension(0); long imgHeight = img.dimension(1); /* * since we want to draw the line over the whole image we can directly * use screen coordinates for x values. */ startX = 0.0; endX = imgWidth; // check if we can get some exta information for drawing if (isHistogram(img)) { Histogram2D histogram = mapOf2DHistograms.get(img); // get calibrated start y coordinates double calibratedStartY = slope * histogram.getXMin() + intercept; double calibratedEndY = slope * histogram.getXMax() + intercept; // convert calibrated coordinates to screen coordinates startY = calibratedStartY * histogram.getYBinWidth(); endY = calibratedEndY * histogram.getYBinWidth(); } else { startY = slope * startX + intercept; endY = slope * endX + intercept; } /* * since the screen origin is in the top left of the image, we need to * x-mirror our line */ startY = (imgHeight - 1) - startY; endY = (imgHeight - 1) - endY; // create the line ROI and add it to the overlay Line lineROI = new Line(startX, startY, endX, endY); /* * Set drawing width of line to one, in case it has been changed * globally. */ lineROI.setStrokeWidth(1.0f); overlay.add(lineROI); } protected void adjustDisplayedImage(RandomAccessibleInterval> img) { /* * when changing the result image to display need to set the image we * were looking at back to not log scale, so we don't log it twice if * its reselected. */ if (log.isSelected()) toggleLogarithmic(false); currentlyDisplayedImageResult = img; pixelAccessCursor = img.randomAccess(); // Currently disabled, due to lag of non-histograms :-) // disable list and copy button if it is no histogram result listButton.setEnabled(isHistogram(img)); copyButton.setEnabled(isHistogram(img)); drawImage(img); toggleLogarithmic(log.isSelected()); // ensure a valid layout, we changed the image getContentPane().validate(); getContentPane().repaint(); } @Override public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { RandomAccessibleInterval> img = ((NamedContainer>>) (e .getItem())).getObject(); adjustDisplayedImage(img); } } protected void toggleLogarithmic(boolean enabled) { if (imp == null) return; ImageProcessor ip = imp.getProcessor(); if (enabled) { ip.snapshot(); ip.log(); ip.resetMinAndMax(); } else ip.reset(); imagePanel.repaint(); } @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == log) { toggleLogarithmic(log.isSelected()); } } @Override public void lostOwnership(Clipboard clipboard, Transferable contents) { // nothing to do here } } diff --git a/src/main/java/results/ValueResult.java b/src/main/java/sc/fiji/coloc/results/ValueResult.java similarity index 97% rename from src/main/java/results/ValueResult.java rename to src/main/java/sc/fiji/coloc/results/ValueResult.java index a270429..f021da4 100644 --- a/src/main/java/results/ValueResult.java +++ b/src/main/java/sc/fiji/coloc/results/ValueResult.java @@ -1,47 +1,47 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package results; +package sc.fiji.coloc.results; /** * A small structure to keep decimal places information * with numbers along with a name or a simple named text. */ public class ValueResult { public String name; public double number; public int decimals; public String value; public boolean isNumber; public ValueResult( String name, double number, int decimals ) { this.name = name; this.number = number; this.decimals = decimals; this.isNumber = true; } public ValueResult( String name, String value) { this.name = name; this.value = value; this.isNumber = false; } } diff --git a/src/main/java/results/Warning.java b/src/main/java/sc/fiji/coloc/results/Warning.java similarity index 97% rename from src/main/java/results/Warning.java rename to src/main/java/sc/fiji/coloc/results/Warning.java index 8d83cbe..7f76ef2 100644 --- a/src/main/java/results/Warning.java +++ b/src/main/java/sc/fiji/coloc/results/Warning.java @@ -1,48 +1,48 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package results; +package sc.fiji.coloc.results; /** * A class representing a warning, combining a short and * a long message. Typically Algorithms can produce such * warnings if they find problems with the input data. */ public class Warning { private String shortMessage; private String longMessage; public Warning(String shortMessage, String longMessage) { this.shortMessage = shortMessage; this.longMessage = longMessage; } public String getShortMessage() { return shortMessage; } public String getLongMessage() { return longMessage; } } diff --git a/src/main/resources/plugins.config b/src/main/resources/plugins.config index e325a08..c61b370 100644 --- a/src/main/resources/plugins.config +++ b/src/main/resources/plugins.config @@ -1,24 +1,24 @@ ### # #%L # Fiji's plugin for colocalization analysis. # %% # Copyright (C) 2009 - 2017 Fiji developers. # %% # 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 # . # #L% ### -Analyze>Colocalization, "Colocalization Threshold", Colocalisation_Threshold -Analyze>Colocalization, "Colocalization Test", Colocalisation_Test -Analyze>Colocalization, "Coloc 2", Coloc_2 +Analyze>Colocalization, "Colocalization Threshold", sc.fiji.coloc.Colocalisation_Threshold +Analyze>Colocalization, "Colocalization Test", sc.fiji.coloc.Colocalisation_Test +Analyze>Colocalization, "Coloc 2", sc.fiji.coloc.Coloc_2 diff --git a/src/main/resources/script_templates/Examples/Colocalisation.groovy b/src/main/resources/script_templates/Examples/Colocalisation.groovy index 7204e9b..530339c 100644 --- a/src/main/resources/script_templates/Examples/Colocalisation.groovy +++ b/src/main/resources/script_templates/Examples/Colocalisation.groovy @@ -1,60 +1,61 @@ // @ImagePlus imp1 // @ImagePlus imp2 // Colocalisation.groovy // // This script demonstrates programmatic usage of Fiji's Coloc 2 plugin, // including how to extract quantitative measurements after execution. +import sc.fiji.coloc.Coloc_2 coloc2 = new Coloc_2() indexMask = 0 indexRegr = 0 autoSavePdf = false displayImages = false displayShuffledCostes = false useLiCh1 = true useLiCh2 = true useLiICQ = true useSpearmanRank = true useManders = true useKendallTau = true useScatterplot = true useCostes = true psf = 3 nrCostesRandomisations = 10 coloc2.initializeSettings( imp1, imp2, indexMask, indexRegr, autoSavePdf, displayImages, displayShuffledCostes, useLiCh1, useLiCh2, useLiICQ, useSpearmanRank, useManders, useKendallTau, useScatterplot, useCostes, psf, nrCostesRandomisations) img1 = coloc2.img1 img2 = coloc2.img2 box = coloc2.masks[0].roi mask = coloc2.masks[0].mask // NB: Passing a different bounding box and/or mask here // may work, but is (as of this writing) UNTESTED. results = coloc2.colocalise(img1, img2, box, mask, null) for (v in results.values()) { println(v.name + " = " + (v.isNumber ? v.number : v.value)) } println("I also have histograms:") for (h in results.histograms()) { println("\t" + h) } diff --git a/src/test/java/Main.java b/src/test/java/sc/fiji/coloc/Main.java similarity index 99% rename from src/test/java/Main.java rename to src/test/java/sc/fiji/coloc/Main.java index 7dedeaf..42f03b0 100644 --- a/src/test/java/Main.java +++ b/src/test/java/sc/fiji/coloc/Main.java @@ -1,70 +1,71 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ +package sc.fiji.coloc; import ij.IJ; import ij.ImageJ; import ij.ImagePlus; import ij.plugin.ChannelSplitter; /** * Test class for Coloc 2 functionality. * * @author Ellen T Arena */ public class Main { /** * Main method for debugging. * * For debugging, it is convenient to have a method that starts ImageJ, loads an * image and calls the plugin, e.g. after setting breakpoints. * * @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 = Coloc_2.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(); // open the FluorescentCells sample (to test single slice images) ImagePlus fluorCellImage = IJ.openImage("http://imagej.net/images/FluorescentCells.zip"); ImagePlus[] fluorCellchannels = ChannelSplitter.split(fluorCellImage); fluorCellchannels[0].show(); fluorCellchannels[1].show(); // run the plugin, Coloc 2 IJ.run("Coloc 2","channel_1=C1-FluorescentCells.tif channel_2=C2-FluorescentCells.tif roi_or_mask= threshold_regression=Costes display_images_in_result li_histogram_channel_1 li_histogram_channel_2 li_icq spearman's_rank_correlation manders'_correlation kendall's_tau_rank_correlation 2d_instensity_histogram costes'_significance_test psf=3 costes_randomisations=10"); // // open the Confocal Series sample (to test z-stacks) // ImagePlus confocalImage = IJ.openImage("http://imagej.net/images/confocal-series.zip"); // ImagePlus[] confocalchannels = ChannelSplitter.split(confocalImage); // confocalchannels[0].show(); // confocalchannels[1].show(); // // run the plugin, Coloc 2 // IJ.run("Coloc 2", "channel_1=C1-confocal-series.tif channel_2=C2-confocal-series.tif roi_or_mask= threshold_regression=Costes display_images_in_result li_histogram_channel_1 li_histogram_channel_2 li_icq spearman's_rank_correlation manders'_correlation kendall's_tau_rank_correlation 2d_instensity_histogram costes'_significance_test psf=3 costes_randomisations=10"); } } diff --git a/src/test/java/tests/AutoThresholdRegressionTest.java b/src/test/java/sc/fiji/coloc/tests/AutoThresholdRegressionTest.java similarity index 91% rename from src/test/java/tests/AutoThresholdRegressionTest.java rename to src/test/java/sc/fiji/coloc/tests/AutoThresholdRegressionTest.java index cfcd30e..157e4c8 100644 --- a/src/test/java/tests/AutoThresholdRegressionTest.java +++ b/src/test/java/sc/fiji/coloc/tests/AutoThresholdRegressionTest.java @@ -1,94 +1,94 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package tests; +package sc.fiji.coloc.tests; import static org.junit.Assert.assertEquals; import net.imglib2.RandomAccessibleInterval; import net.imglib2.type.numeric.integer.UnsignedByteType; import org.junit.Test; -import algorithms.AutoThresholdRegression; -import algorithms.AutoThresholdRegression.Implementation; -import algorithms.MissingPreconditionException; -import algorithms.PearsonsCorrelation; -import gadgets.DataContainer; +import sc.fiji.coloc.algorithms.AutoThresholdRegression; +import sc.fiji.coloc.algorithms.AutoThresholdRegression.Implementation; +import sc.fiji.coloc.algorithms.MissingPreconditionException; +import sc.fiji.coloc.algorithms.PearsonsCorrelation; +import sc.fiji.coloc.gadgets.DataContainer; public class AutoThresholdRegressionTest extends ColocalisationTest { @Test public void clampHelperTest() throws MissingPreconditionException { assertEquals(4, AutoThresholdRegression.clamp(5, 1, 4), 0.00001); assertEquals(1, AutoThresholdRegression.clamp(-2, 1, 4), 0.00001); assertEquals(1, AutoThresholdRegression.clamp(5, 1, 1), 0.00001); assertEquals(2, AutoThresholdRegression.clamp(2, -1, 3), 0.00001); } /** * This test makes sure the test images A and B lead to the same thresholds, * regardless whether they are added in the order A, B or B, A to the data * container. * * @throws MissingPreconditionException */ @Test public void cummutativityTest() throws MissingPreconditionException { _cummutativityTest(Implementation.Costes); _cummutativityTest(Implementation.Bisection); } protected void _cummutativityTest(Implementation atrImplementation) throws MissingPreconditionException { PearsonsCorrelation pc1 = new PearsonsCorrelation( PearsonsCorrelation.Implementation.Fast); PearsonsCorrelation pc2 = new PearsonsCorrelation( PearsonsCorrelation.Implementation.Fast); AutoThresholdRegression atr1 = new AutoThresholdRegression(pc1, atrImplementation); AutoThresholdRegression atr2 = new AutoThresholdRegression(pc2, atrImplementation); RandomAccessibleInterval img1 = syntheticNegativeCorrelationImageCh1; RandomAccessibleInterval img2 = syntheticNegativeCorrelationImageCh2; DataContainer container1 = new DataContainer(img1, img2, 1, 1, "Channel 1", "Channel 2"); DataContainer container2 = new DataContainer(img2, img1, 1, 1, "Channel 2", "Channel 1"); atr1.execute(container1); atr2.execute(container2); assertEquals(atr1.getCh1MinThreshold(), atr2.getCh2MinThreshold()); assertEquals(atr1.getCh1MaxThreshold(), atr2.getCh2MaxThreshold()); assertEquals(atr1.getCh2MinThreshold(), atr2.getCh1MinThreshold()); assertEquals(atr1.getCh2MaxThreshold(), atr2.getCh1MaxThreshold()); } } diff --git a/src/test/java/tests/ColocalisationTest.java b/src/test/java/sc/fiji/coloc/tests/ColocalisationTest.java similarity index 98% rename from src/test/java/tests/ColocalisationTest.java rename to src/test/java/sc/fiji/coloc/tests/ColocalisationTest.java index 76110db..56861f2 100644 --- a/src/test/java/tests/ColocalisationTest.java +++ b/src/test/java/sc/fiji/coloc/tests/ColocalisationTest.java @@ -1,154 +1,154 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package tests; +package sc.fiji.coloc.tests; import net.imglib2.RandomAccessibleInterval; import net.imglib2.algorithm.math.ImageStatistics; import net.imglib2.img.Img; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.UnsignedByteType; import org.junit.After; import org.junit.Before; -import gadgets.MaskFactory; +import sc.fiji.coloc.gadgets.MaskFactory; public abstract class ColocalisationTest { // images and meta data for zero correlation RandomAccessibleInterval zeroCorrelationImageCh1; RandomAccessibleInterval zeroCorrelationImageCh2; RandomAccessibleInterval zeroCorrelationAlwaysTrueMask; double zeroCorrelationImageCh1Mean; double zeroCorrelationImageCh2Mean; // images and meta data for positive correlation test // and real noisy image Manders' coeff with mask test RandomAccessibleInterval positiveCorrelationImageCh1; RandomAccessibleInterval positiveCorrelationImageCh2; // open mask image as a bit type cursor Img positiveCorrelationMaskImage; RandomAccessibleInterval positiveCorrelationAlwaysTrueMask; double positiveCorrelationImageCh1Mean; double positiveCorrelationImageCh2Mean; // images and meta data for a synthetic negative correlation dataset RandomAccessibleInterval syntheticNegativeCorrelationImageCh1; RandomAccessibleInterval syntheticNegativeCorrelationImageCh2; RandomAccessibleInterval syntheticNegativeCorrelationAlwaysTrueMask; double syntheticNegativeCorrelationImageCh1Mean; double syntheticNegativeCorrelationImageCh2Mean; // images like in the Manders paper RandomAccessibleInterval mandersA, mandersB, mandersC, mandersD, mandersE, mandersF, mandersG, mandersH, mandersI; RandomAccessibleInterval mandersAlwaysTrueMask; /** * This method is run before every single test is run and is meant to set up * the images and meta data needed for testing image colocalisation. */ @Before public void setup() { zeroCorrelationImageCh1 = TestImageAccessor.loadTiffFromJar("/greenZstack.tif"); zeroCorrelationImageCh1Mean = ImageStatistics.getImageMean(zeroCorrelationImageCh1); zeroCorrelationImageCh2 = TestImageAccessor.loadTiffFromJar("/redZstack.tif"); zeroCorrelationImageCh2Mean = ImageStatistics.getImageMean(zeroCorrelationImageCh2); final long[] dimZeroCorrCh1 = new long[ zeroCorrelationImageCh1.numDimensions() ]; zeroCorrelationImageCh1.dimensions(dimZeroCorrCh1); zeroCorrelationAlwaysTrueMask = MaskFactory.createMask(dimZeroCorrCh1, true); positiveCorrelationImageCh1 = TestImageAccessor.loadTiffFromJar("/colocsample1b-green.tif"); positiveCorrelationImageCh1Mean = ImageStatistics.getImageMean(positiveCorrelationImageCh1); positiveCorrelationImageCh2 = TestImageAccessor.loadTiffFromJar("/colocsample1b-red.tif"); positiveCorrelationImageCh2Mean = ImageStatistics.getImageMean(positiveCorrelationImageCh2); positiveCorrelationMaskImage = TestImageAccessor.loadTiffFromJarAsImg("/colocsample1b-mask.tif"); final long[] dimPosCorrCh1 = new long[ positiveCorrelationImageCh1.numDimensions() ]; positiveCorrelationImageCh1.dimensions(dimPosCorrCh1); positiveCorrelationAlwaysTrueMask = MaskFactory.createMask(dimPosCorrCh1, true); syntheticNegativeCorrelationImageCh1 = TestImageAccessor.loadTiffFromJar("/syntheticNegCh1.tif"); syntheticNegativeCorrelationImageCh1Mean = ImageStatistics.getImageMean(syntheticNegativeCorrelationImageCh1); syntheticNegativeCorrelationImageCh2 = TestImageAccessor.loadTiffFromJar("/syntheticNegCh2.tif"); syntheticNegativeCorrelationImageCh2Mean = ImageStatistics.getImageMean(syntheticNegativeCorrelationImageCh2); final long[] dimSynthNegCorrCh1 = new long[ syntheticNegativeCorrelationImageCh1.numDimensions() ]; syntheticNegativeCorrelationImageCh1.dimensions(dimSynthNegCorrCh1); syntheticNegativeCorrelationAlwaysTrueMask = MaskFactory.createMask(dimSynthNegCorrCh1, true); mandersA = TestImageAccessor.loadTiffFromJar("/mandersA.tiff"); mandersB = TestImageAccessor.loadTiffFromJar("/mandersB.tiff"); mandersC = TestImageAccessor.loadTiffFromJar("/mandersC.tiff"); mandersD = TestImageAccessor.loadTiffFromJar("/mandersD.tiff"); mandersE = TestImageAccessor.loadTiffFromJar("/mandersE.tiff"); mandersF = TestImageAccessor.loadTiffFromJar("/mandersF.tiff"); mandersG = TestImageAccessor.loadTiffFromJar("/mandersG.tiff"); mandersH = TestImageAccessor.loadTiffFromJar("/mandersH.tiff"); mandersI = TestImageAccessor.loadTiffFromJar("/mandersI.tiff"); final long[] dimMandersA = new long[ mandersA.numDimensions() ]; mandersA.dimensions(dimMandersA); mandersAlwaysTrueMask = MaskFactory.createMask(dimMandersA, true); } /** * This method is run after every single test and is meant to clean up. */ @After public void cleanup() { // nothing to do } /** * Creates a ROI offset array with a distance of 1/4 to the origin * in each dimension. */ protected > long[] createRoiOffset(RandomAccessibleInterval img) { final long[] offset = new long[ img.numDimensions() ]; img.dimensions(offset); for (int i=0; i> long[] createRoiSize(RandomAccessibleInterval img) { final long[] size = new long[ img.numDimensions() ]; img.dimensions(size); for (int i=0; i. * #L% */ -package tests; +package sc.fiji.coloc.tests; import static org.junit.Assert.assertEquals; import net.imglib2.RandomAccessibleInterval; import net.imglib2.TwinCursor; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.view.Views; import org.junit.Test; -import algorithms.LiICQ; -import algorithms.MandersColocalization; -import algorithms.MandersColocalization.MandersResults; -import algorithms.MissingPreconditionException; -import algorithms.PearsonsCorrelation; -import algorithms.SpearmanRankCorrelation; +import sc.fiji.coloc.algorithms.LiICQ; +import sc.fiji.coloc.algorithms.MandersColocalization; +import sc.fiji.coloc.algorithms.MandersColocalization.MandersResults; +import sc.fiji.coloc.algorithms.MissingPreconditionException; +import sc.fiji.coloc.algorithms.PearsonsCorrelation; +import sc.fiji.coloc.algorithms.SpearmanRankCorrelation; public class CommutativityTest extends ColocalisationTest { /** * This test makes sure the test images A and B lead to the same results, * regardless whether they are added in the order A, B or B, A to the data * container. * * @throws MissingPreconditionException */ @Test public void cummutativityTest() throws MissingPreconditionException { assertCommutativity(zeroCorrelationImageCh1, zeroCorrelationImageCh2, zeroCorrelationAlwaysTrueMask, zeroCorrelationImageCh1Mean, zeroCorrelationImageCh2Mean); assertCommutativity(positiveCorrelationImageCh1, positiveCorrelationImageCh1, positiveCorrelationAlwaysTrueMask, positiveCorrelationImageCh1Mean, positiveCorrelationImageCh2Mean); assertCommutativity(syntheticNegativeCorrelationImageCh1, syntheticNegativeCorrelationImageCh2, syntheticNegativeCorrelationAlwaysTrueMask, syntheticNegativeCorrelationImageCh1Mean, syntheticNegativeCorrelationImageCh2Mean); } protected static > void assertCommutativity( RandomAccessibleInterval ch1, RandomAccessibleInterval ch2, RandomAccessibleInterval mask, double mean1, double mean2) throws MissingPreconditionException { // create a twin value range cursor that iterates over all pixels of the input data TwinCursor cursor1 = new TwinCursor(ch1.randomAccess(), ch2.randomAccess(), Views.iterable(mask).localizingCursor()); TwinCursor cursor2 = new TwinCursor(ch1.randomAccess(), ch2.randomAccess(), Views.iterable(mask).localizingCursor()); // get the Pearson's values double pearsonsR1 = PearsonsCorrelation.fastPearsons(cursor1); double pearsonsR2 = PearsonsCorrelation.fastPearsons(cursor2); // check Pearsons R is the same assertEquals(pearsonsR1, pearsonsR2, 0.0001); // get Li's ICQ values double icq1 = LiICQ.calculateLisICQ(cursor1, mean1, mean2); double icq2 = LiICQ.calculateLisICQ(cursor2, mean2, mean1); // check Li's ICQ is the same assertEquals(icq1, icq2, 0.0001); // get Manders values MandersColocalization mc = new MandersColocalization(); MandersResults manders1 = mc.calculateMandersCorrelation(cursor1, ch1.randomAccess().get().createVariable()); MandersResults manders2 = mc.calculateMandersCorrelation(cursor2, ch2.randomAccess().get().createVariable()); // check Manders m1 and m2 values are the same assertEquals(manders1.m1, manders2.m2, 0.0001); assertEquals(manders1.m2, manders2.m1, 0.0001); // calculate Spearman's Rank rho values SpearmanRankCorrelation src = new SpearmanRankCorrelation(); double rho1 = src.calculateSpearmanRank(cursor1); double rho2 = src.calculateSpearmanRank(cursor2); // make sure both ranks are the same assertEquals(rho1, rho2, 0.0001); } } diff --git a/src/test/java/tests/CostesSignificanceTest.java b/src/test/java/sc/fiji/coloc/tests/CostesSignificanceTest.java similarity index 91% rename from src/test/java/tests/CostesSignificanceTest.java rename to src/test/java/sc/fiji/coloc/tests/CostesSignificanceTest.java index ccb73ba..c583bdb 100644 --- a/src/test/java/tests/CostesSignificanceTest.java +++ b/src/test/java/sc/fiji/coloc/tests/CostesSignificanceTest.java @@ -1,121 +1,121 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package tests; +package sc.fiji.coloc.tests; import static org.junit.Assert.assertTrue; import net.imglib2.RandomAccessibleInterval; import net.imglib2.type.numeric.real.FloatType; import org.junit.Test; -import algorithms.AutoThresholdRegression; -import algorithms.MissingPreconditionException; -import algorithms.PearsonsCorrelation; -import gadgets.DataContainer; +import sc.fiji.coloc.algorithms.AutoThresholdRegression; +import sc.fiji.coloc.algorithms.MissingPreconditionException; +import sc.fiji.coloc.algorithms.PearsonsCorrelation; +import sc.fiji.coloc.gadgets.DataContainer; /** * This class contains JUnit 4 test cases for the Costes * statistical significance test. * * @author Dan White * @author Tom Kazimiers */ public class CostesSignificanceTest extends ColocalisationTest { /** * This test checks the Costes statistical significance test implementation * by artificially disturbing known colocalisation. It simulates what Costes * describes in figure 3 of section "Simulated data". An image representing * colocalised data is generated. This is put onto two random Perlin noise * images. A smoothing step is applied after the combination. With the two * resulting images the Costes calculation and shuffling is done. These steps * are done multiple times, every time with an increasing percentage of * colocalised data in the images. As stated by Costes, colocalisation data * percentages above three percent should be detected (P value > 0.95. This * is the assertion of this test and checked with every iteration. Percentages * to test are calculated as percentage = 10^x. Five iterations are done, * increasing "x" in steps of 0.5, starting at 0. The test uses circles with * a diameter of 7 as objects (similar to Costes' paper, he uses 7x7 squares). */ @Test public void backgroundNoiseTest() throws MissingPreconditionException { final int width = 512; final int height = 512; final double z = 2.178; final double scale = 0.1; final int psf = 3; final int objectSize = 7; final double[] sigma = new double[] {3.0,3.0}; for (double exp=0; exp < 2.5; exp=exp+0.5) { double colocPercentage = Math.pow(10, exp); RandomAccessibleInterval ch1 = TestImageAccessor.producePerlinNoiseImage( new FloatType(), width, height, z, scale); RandomAccessibleInterval ch2 = TestImageAccessor.producePerlinNoiseImage( new FloatType(), width, height, z, scale); /* calculate the number of colocalised pixels, based on the percentage and the * space one noise point will take (here 9, because we use 3x3 dots) */ int nrColocPixels = (int) ( ( (width * height / 100.0) * colocPercentage ) / (objectSize * objectSize) ); // create non-smoothed coloc image. add it to the noise images and smooth them RandomAccessibleInterval colocImg = TestImageAccessor.produceNoiseImage( width, height, objectSize, nrColocPixels); TestImageAccessor.combineImages(ch1, colocImg); ch1 = TestImageAccessor.gaussianSmooth(ch1, sigma); TestImageAccessor.combineImages(ch2, colocImg); ch2 = TestImageAccessor.gaussianSmooth(ch2, sigma); DataContainer container = new DataContainer(ch1, ch2, 1, 1, "Channel 1", "Channel 2"); PearsonsCorrelation pc = new PearsonsCorrelation(PearsonsCorrelation.Implementation.Fast); AutoThresholdRegression atr = new AutoThresholdRegression(pc); container.setAutoThreshold(atr); atr.execute(container); try { pc.execute(container); } catch (MissingPreconditionException e) { /* this can happen for random noise data in seldom cases, * but we are not after this here. The cases that are * important for Costes work well, but are again sanity * checked here. */ if (pc.getPearsonsCorrelationValue() == Double.NaN) throw e; } - algorithms.CostesSignificanceTest costes - = new algorithms.CostesSignificanceTest(pc, psf, 10, false); + sc.fiji.coloc.algorithms.CostesSignificanceTest costes + = new sc.fiji.coloc.algorithms.CostesSignificanceTest(pc, psf, 10, false); costes.execute(container); // check if we can expect a high P if (colocPercentage > 3.0) { double pVal = costes.getCostesPValue(); assertTrue("Costes P value was " + pVal, pVal > 0.95); } } } } diff --git a/src/test/java/tests/ImprovedNoise.java b/src/test/java/sc/fiji/coloc/tests/ImprovedNoise.java similarity index 99% rename from src/test/java/tests/ImprovedNoise.java rename to src/test/java/sc/fiji/coloc/tests/ImprovedNoise.java index 07b8df3..cbd9ac9 100644 --- a/src/test/java/tests/ImprovedNoise.java +++ b/src/test/java/sc/fiji/coloc/tests/ImprovedNoise.java @@ -1,74 +1,74 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package tests; +package sc.fiji.coloc.tests; /** * JAVA REFERENCE IMPLEMENTATION OF IMPROVED NOISE - COPYRIGHT 2002 KEN PERLIN. * From: http://mrl.nyu.edu/~perlin/noise/ */ public final class ImprovedNoise { static public double noise(double x, double y, double z) { int X = (int)Math.floor(x) & 255, // FIND UNIT CUBE THAT Y = (int)Math.floor(y) & 255, // CONTAINS POINT. Z = (int)Math.floor(z) & 255; x -= Math.floor(x); // FIND RELATIVE X,Y,Z y -= Math.floor(y); // OF POINT IN CUBE. z -= Math.floor(z); double u = fade(x), // COMPUTE FADE CURVES v = fade(y), // FOR EACH OF X,Y,Z. w = fade(z); int A = p[X ]+Y, AA = p[A]+Z, AB = p[A+1]+Z, // HASH COORDINATES OF B = p[X+1]+Y, BA = p[B]+Z, BB = p[B+1]+Z; // THE 8 CUBE CORNERS, return lerp(w, lerp(v, lerp(u, grad(p[AA ], x , y , z ), // AND ADD grad(p[BA ], x-1, y , z )), // BLENDED lerp(u, grad(p[AB ], x , y-1, z ), // RESULTS grad(p[BB ], x-1, y-1, z ))),// FROM 8 lerp(v, lerp(u, grad(p[AA+1], x , y , z-1 ), // CORNERS grad(p[BA+1], x-1, y , z-1 )), // OF CUBE lerp(u, grad(p[AB+1], x , y-1, z-1 ), grad(p[BB+1], x-1, y-1, z-1 )))); } static double fade(double t) { return t * t * t * (t * (t * 6 - 15) + 10); } static double lerp(double t, double a, double b) { return a + t * (b - a); } static double grad(int hash, double x, double y, double z) { int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE double u = h<8 ? x : y, // INTO 12 GRADIENT DIRECTIONS. v = h<4 ? y : h==12||h==14 ? x : z; return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v); } static final int p[] = new int[512], permutation[] = { 151,160,137,91,90,15, 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 }; static { for (int i=0; i < 256 ; i++) p[256+i] = p[i] = permutation[i]; } } diff --git a/src/test/java/tests/KendallTauTest.java b/src/test/java/sc/fiji/coloc/tests/KendallTauTest.java similarity index 96% rename from src/test/java/tests/KendallTauTest.java rename to src/test/java/sc/fiji/coloc/tests/KendallTauTest.java index 5e26d4b..f53a83b 100644 --- a/src/test/java/tests/KendallTauTest.java +++ b/src/test/java/sc/fiji/coloc/tests/KendallTauTest.java @@ -1,139 +1,139 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package tests; +package sc.fiji.coloc.tests; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import net.imglib2.PairIterator; import net.imglib2.type.numeric.real.DoubleType; import org.junit.Test; -import algorithms.KendallTauRankCorrelation; -import algorithms.MissingPreconditionException; +import sc.fiji.coloc.algorithms.KendallTauRankCorrelation; +import sc.fiji.coloc.algorithms.MissingPreconditionException; /** * Tests the Kendall Tau implementation. */ public class KendallTauTest { private boolean exhaustive = false; @Test public void testSimple() throws MissingPreconditionException { assumeTrue(!exhaustive); // From Armitage P, Berry G. Statistical Methods in Medical Research (3rd edition). Blackwell 1994, p. 466. assertTau(23.0 / 45.0, new int[] { 4, 10, 3, 1, 9, 2, 6, 7, 8, 5 }, new int[] { 5, 8, 6, 2, 10, 3, 9, 4, 7, 1 }); } @Test public void testPathological() throws MissingPreconditionException { assumeTrue(!exhaustive); assertTau(Double.NaN, new int[] { 1, 1, 1, 1 }, new int[] { 2, 2, 2, 2 }); } @Test public void testSomeDuplicates() throws MissingPreconditionException { assumeTrue(!exhaustive); // for pairs (1, 3), (1, 2), (2, 1), (3, 1), // n = 4, // n0 = n * (n - 1) / 2 = 4 * 3 / 2 = 6 // n1 = 1 + 0 + 0 + 0 = 1 // n2 = 1 + 0 + 0 + 0 = 1 // nc = #{ } = 0 // nd = #{ (1, 3)x(2, 1), (1, 3)x(3, 1), (1, 2)x(2, 1), (1, 2)x(3, 1) } = 4 // therefore Tau_b = -4 / sqrt(5 * 5) = -0.8 assertTau(-0.8, new int[] { 1, 1, 2, 3 }, new int[] { 3, 2, 1, 1 }); } private PairIterator pairIterator(final int[] values1, final int[] values2) { assertEquals(values1.length, values2.length); return new PairIterator() { private int i = -1; private DoubleType ch1 = new DoubleType(); private DoubleType ch2 = new DoubleType(); @Override public boolean hasNext() { return i + 1 < values1.length; } @Override public void reset() { i = -1; } @Override public void fwd() { i++; } @Override public DoubleType getFirst() { ch1.set(values1[i]); return ch1; } @Override public DoubleType getSecond() { ch2.set(values2[i]); return ch2; } }; } private void assertTau(final double expected, final int[] values1, final int[] values2) throws MissingPreconditionException { final PairIterator iter = pairIterator(values1, values2); assertEquals(expected, KendallTauRankCorrelation.calculateMergeSort(iter), 1e-10); } private int seed; private int pseudoRandom() { return seed = 3170425 * seed + 132102; } @Test public void exhaustiveTesting() throws Exception { assumeTrue(exhaustive); final int n = 5, m = 10; final int[] values1 = new int[n], values2 = new int[n]; for (int i = 0; i < 100; i++) { for (int j = 0; j < n; j++) { values1[j] = Math.abs(pseudoRandom()) % m; values2[j] = Math.abs(pseudoRandom()) % m; } final PairIterator iter = pairIterator(values1, values2); double value1 = KendallTauRankCorrelation.calculateNaive(iter); iter.reset(); double value2 = KendallTauRankCorrelation.calculateMergeSort(iter); if (Double.isNaN(value1)) { assertTrue("i: " + i + ", value2: " + value2, Double.isInfinite(value2) || Double.isNaN(value2)); } else { assertEquals("i: " + i, value1, value2, 1e-10); } } } } diff --git a/src/test/java/tests/LiICQTest.java b/src/test/java/sc/fiji/coloc/tests/LiICQTest.java similarity index 97% rename from src/test/java/tests/LiICQTest.java rename to src/test/java/sc/fiji/coloc/tests/LiICQTest.java index af498e7..9a6c5f7 100644 --- a/src/test/java/tests/LiICQTest.java +++ b/src/test/java/sc/fiji/coloc/tests/LiICQTest.java @@ -1,73 +1,73 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package tests; +package sc.fiji.coloc.tests; import static org.junit.Assert.assertTrue; import net.imglib2.TwinCursor; import net.imglib2.type.numeric.integer.UnsignedByteType; import net.imglib2.view.Views; import org.junit.Test; -import algorithms.LiICQ; +import sc.fiji.coloc.algorithms.LiICQ; /** * This class contains JUnit 4 test cases for the calculation of Li's * ICQ value. * * @author Dan White * @author Tom Kazimiers */ public class LiICQTest extends ColocalisationTest { /** * Checks Li's ICQ value for positive correlated images. */ @Test public void liPositiveCorrTest() { TwinCursor cursor = new TwinCursor( positiveCorrelationImageCh1.randomAccess(), positiveCorrelationImageCh2.randomAccess(), Views.iterable(positiveCorrelationAlwaysTrueMask).localizingCursor()); // calculate Li's ICQ value double icq = LiICQ.calculateLisICQ(cursor, positiveCorrelationImageCh1Mean, positiveCorrelationImageCh2Mean); assertTrue(icq > 0.34 && icq < 0.35); } /** * Checks Li's ICQ value for zero correlated images. The ICQ value * should be about zero. */ @Test public void liZeroCorrTest() { TwinCursor cursor = new TwinCursor( zeroCorrelationImageCh1.randomAccess(), zeroCorrelationImageCh2.randomAccess(), Views.iterable(zeroCorrelationAlwaysTrueMask).localizingCursor()); // calculate Li's ICQ value double icq = LiICQ.calculateLisICQ(cursor, zeroCorrelationImageCh1Mean, zeroCorrelationImageCh2Mean); assertTrue(Math.abs(icq) < 0.01); } } diff --git a/src/test/java/tests/MandersColocalizationTest.java b/src/test/java/sc/fiji/coloc/tests/MandersColocalizationTest.java similarity index 96% rename from src/test/java/tests/MandersColocalizationTest.java rename to src/test/java/sc/fiji/coloc/tests/MandersColocalizationTest.java index 15204d7..ee9238a 100644 --- a/src/test/java/tests/MandersColocalizationTest.java +++ b/src/test/java/sc/fiji/coloc/tests/MandersColocalizationTest.java @@ -1,221 +1,221 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package tests; +package sc.fiji.coloc.tests; import static org.junit.Assert.assertEquals; import net.imglib2.Cursor; import net.imglib2.TwinCursor; import net.imglib2.converter.Converter; import net.imglib2.converter.Converters; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.integer.UnsignedByteType; import net.imglib2.view.Views; import org.junit.Test; -import algorithms.MandersColocalization; -import algorithms.MandersColocalization.MandersResults; -import algorithms.MissingPreconditionException; -import gadgets.ThresholdMode; +import sc.fiji.coloc.algorithms.MandersColocalization; +import sc.fiji.coloc.algorithms.MandersColocalization.MandersResults; +import sc.fiji.coloc.algorithms.MissingPreconditionException; +import sc.fiji.coloc.gadgets.ThresholdMode; /** * This class contains JUnit 4 test cases for the calculation * of Manders' split colocalization coefficients * * @author Dan White * @author Tom Kazimiers */ public class MandersColocalizationTest extends ColocalisationTest { /** * This method tests artificial test images as detailed in * the Manders et al. paper, using above zero threshold (none). * Note: It is not sensitive to choosing the wrong channel to test for * above threshold, because threshold is same for both channels: above zero, * and also that the blobs overlap perfectly or not at all. */ @Test public void mandersPaperImagesTest() throws MissingPreconditionException { MandersColocalization mc = new MandersColocalization(); TwinCursor cursor; MandersResults r; // test A-A combination cursor = new TwinCursor( mandersA.randomAccess(), mandersA.randomAccess(), Views.iterable(mandersAlwaysTrueMask).localizingCursor()); r = mc.calculateMandersCorrelation(cursor, mandersA.randomAccess().get().createVariable()); assertEquals(1.0d, r.m1, 0.0001); assertEquals(1.0d, r.m2, 0.0001); // test A-B combination cursor = new TwinCursor( mandersA.randomAccess(), mandersB.randomAccess(), Views.iterable(mandersAlwaysTrueMask).localizingCursor()); r = mc.calculateMandersCorrelation(cursor, mandersA.randomAccess().get()); assertEquals(0.75d, r.m1, 0.0001); assertEquals(0.75d, r.m2, 0.0001); // test A-C combination cursor = new TwinCursor( mandersA.randomAccess(), mandersC.randomAccess(), Views.iterable(mandersAlwaysTrueMask).localizingCursor()); r = mc.calculateMandersCorrelation(cursor, mandersA.randomAccess().get()); assertEquals(0.5d, r.m1, 0.0001); assertEquals(0.5d, r.m2, 0.0001); // test A-D combination cursor = new TwinCursor( mandersA.randomAccess(), mandersD.randomAccess(), Views.iterable(mandersAlwaysTrueMask).localizingCursor()); r = mc.calculateMandersCorrelation(cursor, mandersA.randomAccess().get()); assertEquals(0.25d, r.m1, 0.0001); assertEquals(0.25d, r.m2, 0.0001); // test A-E combination cursor = new TwinCursor( mandersA.randomAccess(), mandersE.randomAccess(), Views.iterable(mandersAlwaysTrueMask).localizingCursor()); r = mc.calculateMandersCorrelation(cursor, mandersA.randomAccess().get()); assertEquals(0.0d, r.m1, 0.0001); assertEquals(0.0d, r.m2, 0.0001); // test A-F combination cursor = new TwinCursor( mandersA.randomAccess(), mandersF.randomAccess(), Views.iterable(mandersAlwaysTrueMask).localizingCursor()); r = mc.calculateMandersCorrelation(cursor, mandersA.randomAccess().get()); assertEquals(0.25d, r.m1, 0.0001); assertEquals(0.3333d, r.m2, 0.0001); // test A-G combination.firstElement( cursor = new TwinCursor( mandersA.randomAccess(), mandersG.randomAccess(), Views.iterable(mandersAlwaysTrueMask).localizingCursor()); r = mc.calculateMandersCorrelation(cursor, mandersA.randomAccess().get()); assertEquals(0.25d, r.m1, 0.0001); assertEquals(0.50d, r.m2, 0.0001); // test A-H combination cursor = new TwinCursor( mandersA.randomAccess(), mandersH.randomAccess(), Views.iterable(mandersAlwaysTrueMask).localizingCursor()); r = mc.calculateMandersCorrelation(cursor, mandersA.randomAccess().get()); assertEquals(0.25d, r.m1, 0.0001); assertEquals(1.00d, r.m2, 0.0001); // test A-I combination cursor = new TwinCursor( mandersA.randomAccess(), mandersI.randomAccess(), Views.iterable(mandersAlwaysTrueMask).localizingCursor()); r = mc.calculateMandersCorrelation(cursor, mandersA.randomAccess().get()); assertEquals(0.083d, r.m1, 0.001); assertEquals(0.75d, r.m2, 0.0001); } /** * This method tests real experimental noisy but * biologically perfectly colocalized test images, * using previously calculated autothresholds (.above mode) * Amongst other things, hopefully it is sensitive to * choosing the wrong channel to test for above threshold */ @Test public void mandersRealNoisyImagesTest() throws MissingPreconditionException { MandersColocalization mrnc = new MandersColocalization(); // test biologically perfect but noisy image coloc combination // this cast is bad, so use Views.iterable instead. //Cursor mask = Converters.convert((IterableInterval) positiveCorrelationMaskImage, Cursor mask = Converters.convert(Views.iterable(positiveCorrelationMaskImage), new Converter() { @Override public void convert(UnsignedByteType arg0, BitType arg1) { arg1.set(arg0.get() > 0); } }, new BitType()).cursor(); TwinCursor twinCursor; MandersResults r; // Manually set the thresholds for ch1 and ch2 with the results from a // Costes Autothreshold using bisection implementation of regression, of the images used UnsignedByteType thresholdCh1 = new UnsignedByteType(); thresholdCh1.setInteger(70); UnsignedByteType thresholdCh2 = new UnsignedByteType(); thresholdCh2.setInteger(53); //Set the threshold mode ThresholdMode tMode; tMode = ThresholdMode.Above; // Set the TwinCursor to have the mask image channel, and 2 images. twinCursor = new TwinCursor( positiveCorrelationImageCh1.randomAccess(), positiveCorrelationImageCh2.randomAccess(), mask); // Use the constructor that takes ch1 and ch2 autothresholds and threshold mode. r = mrnc.calculateMandersCorrelation(twinCursor, thresholdCh1, thresholdCh2, tMode); assertEquals(0.705665d, r.m1, 0.000001); assertEquals(0.724752d, r.m2, 0.000001); } } diff --git a/src/test/java/tests/MaskAndRoiTest.java b/src/test/java/sc/fiji/coloc/tests/MaskAndRoiTest.java similarity index 99% rename from src/test/java/tests/MaskAndRoiTest.java rename to src/test/java/sc/fiji/coloc/tests/MaskAndRoiTest.java index 9827a4b..eee0716 100644 --- a/src/test/java/tests/MaskAndRoiTest.java +++ b/src/test/java/sc/fiji/coloc/tests/MaskAndRoiTest.java @@ -1,397 +1,397 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package tests; +package sc.fiji.coloc.tests; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.Arrays; import net.imglib2.Cursor; import net.imglib2.PredicateCursor; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; import net.imglib2.TwinCursor; import net.imglib2.algorithm.math.ImageStatistics; import net.imglib2.img.ImgFactory; import net.imglib2.img.array.ArrayImgFactory; import net.imglib2.predicate.MaskPredicate; import net.imglib2.predicate.Predicate; import net.imglib2.roi.RectangleRegionOfInterest; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.integer.UnsignedByteType; import net.imglib2.view.Views; import org.junit.Test; -import algorithms.MissingPreconditionException; -import gadgets.MaskFactory; +import sc.fiji.coloc.algorithms.MissingPreconditionException; +import sc.fiji.coloc.gadgets.MaskFactory; /** * This class contains JUnit 4 test cases for the ROI and masks * implementation. * * @author Dan White * @author Tom Kazimiers */ public class MaskAndRoiTest extends ColocalisationTest { /** * Tests if a masked walk over an image refers to the correct data * by copying the data to a separate image and then compare it with * the original image data. The position data in the original image * is calculated based on the ROI offset and the relative position * in the copied ROI image. * @throws MissingPreconditionException */ @Test public void maskContentTest() throws MissingPreconditionException { // load a 3D test image final RandomAccessibleInterval img = positiveCorrelationImageCh1; final long[] roiOffset = createRoiOffset(img); final long[] roiSize = createRoiSize(img); final long[] dim = new long[ img.numDimensions() ]; img.dimensions(dim); final RandomAccessibleInterval mask = MaskFactory.createMask(dim, roiOffset, roiSize); // create cursor to walk an image with respect to a mask TwinCursor cursor = new TwinCursor( img.randomAccess(), img.randomAccess(), Views.iterable(mask).localizingCursor()); // create an image for the "clipped ROI" ImgFactory maskFactory = new ArrayImgFactory(); RandomAccessibleInterval clippedRoiImage = maskFactory.create( roiSize, new UnsignedByteType() ); // "Clipped ROI" ); RandomAccess outputCursor = clippedRoiImage.randomAccess(); // copy ROI data to new image long[] pos = new long[ clippedRoiImage.numDimensions() ]; while (cursor.hasNext()) { cursor.fwd(); cursor.localize(pos); // shift position by offset for (int i=0; i roiCopyCursor = Views.iterable(clippedRoiImage).localizingCursor(); RandomAccess imgCursor = img.randomAccess(); // create variable for summing up and set it to zero double sum = 0; pos = new long [ clippedRoiImage.numDimensions() ]; while (roiCopyCursor.hasNext()) { roiCopyCursor.fwd(); roiCopyCursor.localize(pos); // shift position by offset for (int i=0; i img = positiveCorrelationImageCh1; long[] roiOffset = createRoiOffset(img); long[] roiSize = createRoiSize(img); long[] dim = new long[ img.numDimensions() ]; img.dimensions(dim); RandomAccessibleInterval mask = MaskFactory.createMask(dim, roiOffset, roiSize); // create cursor to walk an image with respect to a mask final Predicate predicate = new MaskPredicate(); Cursor roiCursor = new PredicateCursor( Views.iterable(mask).localizingCursor(), predicate); // test if all visited voxels are "true" while (roiCursor.hasNext()) { roiCursor.fwd(); assertTrue(roiCursor.get().get()); } } /** * This test test if a regular mask is created by the MaskFactory * correctly. First, the dimensions are checked, they must be the * same as the original images ones. Then it is checked if all * values in the mask image have the value they should have. For * a regular ROI this is easy to tell as one can calculate it out * of the position. */ @Test public void regularMaskCreationTest() throws MissingPreconditionException { // load a 3D test image RandomAccessibleInterval img = positiveCorrelationImageCh1; final long[] roiOffset = createRoiOffset(img); final long[] roiSize = createRoiSize(img); final long[] dim = new long[ img.numDimensions() ]; img.dimensions(dim); RandomAccessibleInterval mask = MaskFactory.createMask(dim, roiOffset, roiSize); // is the number of dimensions the same as in the image? final long[] dimMask = new long[ mask.numDimensions() ]; mask.dimensions(dimMask); assertTrue( Arrays.equals(dim, dimMask) ); // go through the mask and check if all valid points are in the ROI long[] pos = new long[ img.numDimensions() ]; final Cursor cursor = Views.iterable(mask).localizingCursor(); while ( cursor.hasNext() ) { cursor.fwd(); cursor.localize(pos); // get values in mask image boolean onInMask = cursor.get().get(); // calculate value that the current point *should* have boolean onInROI = true; for(int i=0; i= roiOffset[i] && pos[i] < (roiOffset[i] + roiSize[i]); // both values must match assertTrue(onInMask == onInROI); } /* go once more trough the image wrt. the mask to build a * bounding box */ // create cursor to walk an image with respect to a mask final Predicate predicate = new MaskPredicate(); Cursor roiCursor = new PredicateCursor( Views.iterable(mask).localizingCursor(), predicate); long[] min = new long[ mask.numDimensions() ]; long[] max = new long[ mask.numDimensions() ]; Arrays.fill(min, Integer.MAX_VALUE); Arrays.fill(max, Integer.MIN_VALUE); while (roiCursor.hasNext()) { roiCursor.fwd(); roiCursor.localize(pos); for (int i=0; i max[i]) max[i] = pos[i]; } } // the bounding box min should equal the ROI offset assertTrue(Arrays.equals(min, roiOffset)); // create theoretical bounding box max and check it long[] roiMax = roiOffset.clone(); for (int i=0; i img = positiveCorrelationImageCh1; // first, create an always true mask final long[] dim = new long[ img.numDimensions() ]; img.dimensions(dim); RandomAccessibleInterval mask = MaskFactory.createMask(dim, true); final Predicate predicate = new MaskPredicate(); Cursor cursor = new PredicateCursor( Views.iterable(mask).localizingCursor(), predicate); // iterate over mask and count values long count = 0; while (cursor.hasNext()) { cursor.fwd(); count++; assertTrue(cursor.get().get()); } assertEquals(ImageStatistics.getNumPixels(mask), count); // second, create an always false mask mask = MaskFactory.createMask(dim, false); cursor = new PredicateCursor( Views.iterable(mask).localizingCursor(), predicate); // iterate over mask and count values count = 0; while (cursor.hasNext()) { cursor.fwd(); count++; } assertEquals(0, count); } /** * Tests against the implementation of irregular ROIs alias * masks. Masks can also be produced by mask images open in * another Fiji window. * * This test generates a random black/white noise image and * uses first itself and then an inverted version of it as * mask. While iterating over it, the pixel values are * checked. Is the first version only non-zero values should * be present, while only zeros should be there in the second * one. */ @Test public void irregularRoiTest() { // create a random noise 2D image -- set roiWidh/roiSize accordingly RandomAccessibleInterval img = TestImageAccessor.produceSticksNoiseImage(300, 300, 50, 2, 10); final long[] dim = new long[ img.numDimensions() ]; img.dimensions(dim); /* first test - using itself as a mask */ RandomAccessibleInterval mask = MaskFactory.createMask(dim, img); TwinCursor cursor = new TwinCursor( img.randomAccess(), img.randomAccess(), Views.iterable(mask).localizingCursor()); while (cursor.hasNext()) { cursor.fwd(); assertTrue( cursor.getFirst().getInteger() != 0 ); } /* second test - using inverted image */ RandomAccessibleInterval invImg = TestImageAccessor.invertImage(img); RandomAccessibleInterval invMask = MaskFactory.createMask(dim, invImg); cursor = new TwinCursor( img.randomAccess(), img.randomAccess(), Views.iterable(invMask).localizingCursor()); while (cursor.hasNext()) { cursor.fwd(); assertEquals( 0, cursor.getFirst().getInteger() ); } } /** * This test makes sure that a mask that is based on a lower dimension * image has the correct dimensionality. */ @Test public void irregularRoiDimensionTest() { // load a 3D test image RandomAccessibleInterval img = positiveCorrelationImageCh1; final long width = img.dimension(0); final long height = img.dimension(1); final long slices = img.dimension(2); final long[] dimImg = new long[ img.numDimensions() ]; img.dimensions(dimImg); // create a random noise 2D image -- set roiWidh/roiSize accordingly RandomAccessibleInterval maskSlice = TestImageAccessor.produceSticksNoiseImage( (int) width, (int) height, 50, 2, 10); RandomAccessibleInterval mask = MaskFactory.createMask(dimImg, maskSlice); final long[] dimMask = new long[ mask.numDimensions() ]; mask.dimensions(dimMask); // check the dimensions of the mask org.junit.Assert.assertArrayEquals(dimImg, dimMask); // make sure the mask actually got the same content on every slice final double[] offset = new double[ mask.numDimensions() ]; Arrays.fill(offset, 0); double[] size = new double[ mask.numDimensions() ]; size[0] = width; size[1] = height; size[2] = 1; RandomAccess maskCursor = mask.randomAccess(); RectangleRegionOfInterest roi = new RectangleRegionOfInterest( offset, size); Cursor firstSliceCursor = roi.getIterableIntervalOverROI(mask).cursor(); final long[] pos = new long[ mask.numDimensions() ]; while (firstSliceCursor.hasNext()) { firstSliceCursor.fwd(); firstSliceCursor.localize(pos); BitType maskValue = firstSliceCursor.get(); // go through all slices for (int i=1; i img = positiveCorrelationImageCh1; final long[] roiOffset = createRoiOffset(img); final long[] roiSize = createRoiSize(img); final long width = img.dimension(0); final long height = img.dimension(1); RandomAccessibleInterval maskImg = TestImageAccessor.createRectengularMaskImage(width, height, roiOffset, roiSize); final long[] dim = new long[ img.numDimensions() ]; img.dimensions(dim); RandomAccessibleInterval mask = MaskFactory.createMask(dim, maskImg); TwinCursor cursor = new TwinCursor( img.randomAccess(), img.randomAccess(), Views.iterable(mask).localizingCursor()); // calculate volume of mask bounding box long roiVolume = roiSize[0] * roiSize[1] * img.dimension(2); long count = 0; while (cursor.hasNext()) { cursor.fwd(); count++; } assertEquals(roiVolume, count); } } diff --git a/src/test/java/tests/PearsonsCorrelationTest.java b/src/test/java/sc/fiji/coloc/tests/PearsonsCorrelationTest.java similarity index 96% rename from src/test/java/tests/PearsonsCorrelationTest.java rename to src/test/java/sc/fiji/coloc/tests/PearsonsCorrelationTest.java index 5b8498d..97c1d71 100644 --- a/src/test/java/tests/PearsonsCorrelationTest.java +++ b/src/test/java/sc/fiji/coloc/tests/PearsonsCorrelationTest.java @@ -1,199 +1,199 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package tests; +package sc.fiji.coloc.tests; import static org.junit.Assert.assertEquals; import net.imglib2.RandomAccessibleInterval; import net.imglib2.TwinCursor; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.integer.UnsignedByteType; import net.imglib2.type.numeric.real.FloatType; import net.imglib2.view.Views; import org.junit.Test; -import algorithms.MissingPreconditionException; -import algorithms.PearsonsCorrelation; -import algorithms.PearsonsCorrelation.Implementation; -import gadgets.MaskFactory; +import sc.fiji.coloc.algorithms.MissingPreconditionException; +import sc.fiji.coloc.algorithms.PearsonsCorrelation; +import sc.fiji.coloc.algorithms.PearsonsCorrelation.Implementation; +import sc.fiji.coloc.gadgets.MaskFactory; /** * This class contains JUnit 4 test cases for the Pearson's correlation * implementation. * * @author Dan White * @author Tom Kazimiers */ public class PearsonsCorrelationTest extends ColocalisationTest { /** * Tests if the fast implementation of Pearson's correlation with two * zero correlated images produce a Pearson's R value of about zero. */ @Test public void fastPearsonsZeroCorrTest() throws MissingPreconditionException { // create a twin value range cursor that iterates over all pixels of the input data TwinCursor cursor = new TwinCursor( zeroCorrelationImageCh1.randomAccess(), zeroCorrelationImageCh2.randomAccess(), Views.iterable(zeroCorrelationAlwaysTrueMask).localizingCursor()); // get the Pearson's value double pearsonsR = PearsonsCorrelation.fastPearsons(cursor); // check Pearsons R is close to zero assertEquals(0.0, pearsonsR, 0.05); } /** * Tests if the fast implementation of Pearson's correlation with two * positive correlated images produce a Pearson's R value of about 0.75. */ @Test public void fastPearsonsPositiveCorrTest() throws MissingPreconditionException { // create a twin value range cursor that iterates over all pixels of the input data TwinCursor cursor = new TwinCursor( positiveCorrelationImageCh1.randomAccess(), positiveCorrelationImageCh2.randomAccess(), Views.iterable(positiveCorrelationAlwaysTrueMask).localizingCursor()); // get the Pearson's value double pearsonsR = PearsonsCorrelation.fastPearsons(cursor); // check Pearsons R is close to 0.75 assertEquals(0.75, pearsonsR, 0.01); } /** * Tests if the classic implementation of Pearson's correlation with two * zero correlated images produce a Pearson's R value of about zero. */ @Test public void classicPearsonsZeroCorrTest() throws MissingPreconditionException { // create a twin value range cursor that iterates over all pixels of the input data TwinCursor cursor = new TwinCursor( zeroCorrelationImageCh1.randomAccess(), zeroCorrelationImageCh2.randomAccess(), Views.iterable(zeroCorrelationAlwaysTrueMask).localizingCursor()); // get the Pearson's value double pearsonsR = PearsonsCorrelation .classicPearsons(cursor, zeroCorrelationImageCh1Mean, zeroCorrelationImageCh2Mean); // check Pearsons R is close to zero assertEquals(0.0, pearsonsR, 0.05); } /** * Tests if the classic implementation of Pearson's correlation with two * positive correlated images produce a Pearson's R value of about 0.75. */ @Test public void classicPearsonsPositiveCorrTest() throws MissingPreconditionException { // create a twin value range cursor that iterates over all pixels of the input data TwinCursor cursor = new TwinCursor( positiveCorrelationImageCh1.randomAccess(), positiveCorrelationImageCh2.randomAccess(), Views.iterable(positiveCorrelationAlwaysTrueMask).localizingCursor()); // get the Pearson's value double pearsonsR = PearsonsCorrelation .classicPearsons(cursor, positiveCorrelationImageCh1Mean, positiveCorrelationImageCh2Mean); // check Pearsons R is close to 0.75 assertEquals(0.75, pearsonsR, 0.01); } /** * Tests Pearson's correlation stays close to zero for image pairs with the same mean and spread * of randomized pixel values around that mean. */ @Test public void differentMeansTest() throws MissingPreconditionException { final double initialMean = 0.2; final double spread = 0.1; final double[] sigma = new double[] {3.0, 3.0}; RandomAccessibleInterval mask = MaskFactory.createMask(new long[] {512, 512}, true); for (double mean = initialMean; mean < 1; mean += spread) { RandomAccessibleInterval ch1 = TestImageAccessor.produceMeanBasedNoiseImage(new FloatType(), 512, 512, mean, spread, sigma); RandomAccessibleInterval ch2 = TestImageAccessor.produceMeanBasedNoiseImage(new FloatType(), 512, 512, mean, spread, sigma); // create a twin value range cursor that iterates over all pixels of the input data TwinCursor cursor = new TwinCursor(ch1.randomAccess(), ch2.randomAccess(), Views.iterable(mask).localizingCursor()); double resultFast = PearsonsCorrelation.fastPearsons(cursor); assertEquals(0.0, resultFast, 0.1); /* This test will throw Missing PreconsitionException, as the means are the same * which causes a numerical problem in the classic implementation of Pearson's * double resultClassic = PearsonsCorrelation.classicPearsons(cursor, mean, mean); * assertTrue(Math.abs(resultClassic) < 0.1); */ } } /** * The 1993 paper of Manders et. al about colocalization presents an own * method and testing data for it. For that testing data there are * Pearson colocalization numbers, too, and these get tested in this test. * @throws MissingPreconditionException */ @Test public void mandersPaperImagesTest() throws MissingPreconditionException { PearsonsCorrelation pc = new PearsonsCorrelation(Implementation.Classic); double r; // test A-A combination r = pc.calculatePearsons(mandersA, mandersA, mandersAlwaysTrueMask); assertEquals(1.0d, r, 0.01); // test A-B combination r = pc.calculatePearsons(mandersA, mandersB, mandersAlwaysTrueMask); assertEquals(0.72d, r, 0.01); // test A-C combination r = pc.calculatePearsons(mandersA, mandersC, mandersAlwaysTrueMask); assertEquals(0.44d, r, 0.01); // test A-D combination r = pc.calculatePearsons(mandersA, mandersD, mandersAlwaysTrueMask); assertEquals(0.16d, r, 0.01); // test A-E combination r = pc.calculatePearsons(mandersA, mandersE, mandersAlwaysTrueMask); assertEquals(-0.12d, r, 0.01); // test A-F combination r = pc.calculatePearsons(mandersA, mandersF, mandersAlwaysTrueMask); assertEquals(0.22d, r, 0.01); // test A-G combination r = pc.calculatePearsons(mandersA, mandersG, mandersAlwaysTrueMask); assertEquals(0.30d, r, 0.01); // test A-H combination r = pc.calculatePearsons(mandersA, mandersH, mandersAlwaysTrueMask); assertEquals(0.48d, r, 0.01); // test A-I combination r = pc.calculatePearsons(mandersA, mandersI, mandersAlwaysTrueMask); assertEquals(0.23d, r, 0.01); } } diff --git a/src/test/java/tests/SpearmanRankTest.java b/src/test/java/sc/fiji/coloc/tests/SpearmanRankTest.java similarity index 96% rename from src/test/java/tests/SpearmanRankTest.java rename to src/test/java/sc/fiji/coloc/tests/SpearmanRankTest.java index afbf017..d340088 100644 --- a/src/test/java/tests/SpearmanRankTest.java +++ b/src/test/java/sc/fiji/coloc/tests/SpearmanRankTest.java @@ -1,140 +1,140 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package tests; +package sc.fiji.coloc.tests; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import net.imglib2.TwinCursor; import net.imglib2.type.numeric.integer.UnsignedByteType; import net.imglib2.view.Views; import org.junit.Test; -import algorithms.MissingPreconditionException; -import algorithms.SpearmanRankCorrelation; +import sc.fiji.coloc.algorithms.MissingPreconditionException; +import sc.fiji.coloc.algorithms.SpearmanRankCorrelation; /** * This class contains JUnit 4 test cases for the calculation of * Spearman's Rank Correlation (rho). * * @author Leonardo Guizzetti */ public class SpearmanRankTest extends ColocalisationTest { /** * Checks Spearman's Rank Correlation rho for positive correlated images. */ @Test public void spearmanPositiveCorrTest() throws MissingPreconditionException { TwinCursor cursor = new TwinCursor( positiveCorrelationImageCh1.randomAccess(), positiveCorrelationImageCh2.randomAccess(), Views.iterable(positiveCorrelationAlwaysTrueMask).localizingCursor()); // calculate Spearman's Rank rho value double rho = new SpearmanRankCorrelation().calculateSpearmanRank(cursor); // Rho value = 0.5463... assertTrue(rho > 0.546 && rho < 0.547); } /** * Checks Spearman's Rank Correlation value for zero correlated images. The rho value * should be about zero. */ @Test public void spearmanZeroCorrTest() throws MissingPreconditionException { TwinCursor cursor = new TwinCursor( zeroCorrelationImageCh1.randomAccess(), zeroCorrelationImageCh2.randomAccess(), Views.iterable(zeroCorrelationAlwaysTrueMask).localizingCursor()); // calculate Spearman's Rank rho value double rho = new SpearmanRankCorrelation().calculateSpearmanRank(cursor); // Rho value = -0.11... assertTrue(Math.abs(rho) < 0.012); } /** * Checks Spearman's Rank Correlation value for slightly negative correlated synthetic data. * */ @Test public void statisticsTest() throws MissingPreconditionException { double[][] data = new double[][] { {1,113}, {2,43}, {3,11}, {6,86}, {5,59}, {8,47}, {4,92}, {0,152}, {6,23}, {4,9}, {7,33}, {3,69}, {2,75}, {9,135}, {3,30} }; int n = data.length; final SpearmanRankCorrelation src = new SpearmanRankCorrelation(); /* * Check the arithmetic for the rho calculation. * Rho is exactly -0.1743 (to 4 decimal points) using the * exact calculation for Spearman's rho as implemented here. */ double rho = src.calculateSpearmanRank(data); assertEquals(-0.1743, rho, 0.001); // check the degrees of freedom calculation ( df = n - 2 ) int df = 0; df = src.getSpearmanDF(n); assertEquals(df, n - 2); // check the t-statistic calculation ( t = rho * sqrt( df / (1-rho^2) ) ) // The t-stat = -0.6382 double tstat = 0.0; tstat = src.getTStatistic(rho, n); assertEquals(-0.6382, tstat, 0.001); } /** * Checks Spearman's Rank Correlation value for synthetic test image. * This tests the same dataset as the statisticsTest() but tests reading in image * data, the rank transform, and the calling of the statistics calculation methods. */ @Test public void spearmanSyntheticNegCorrTest() throws MissingPreconditionException { TwinCursor cursor = new TwinCursor( syntheticNegativeCorrelationImageCh1.randomAccess(), syntheticNegativeCorrelationImageCh2.randomAccess(), Views.iterable(syntheticNegativeCorrelationAlwaysTrueMask).localizingCursor()); // calculate Spearman's Rank rho value double rho = new SpearmanRankCorrelation().calculateSpearmanRank(cursor); assertTrue((rho > -0.178) && (rho < -0.173)); } } diff --git a/src/test/java/tests/StatisticsTest.java b/src/test/java/sc/fiji/coloc/tests/StatisticsTest.java similarity index 97% rename from src/test/java/tests/StatisticsTest.java rename to src/test/java/sc/fiji/coloc/tests/StatisticsTest.java index a986361..292431d 100644 --- a/src/test/java/tests/StatisticsTest.java +++ b/src/test/java/sc/fiji/coloc/tests/StatisticsTest.java @@ -1,113 +1,113 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package tests; +package sc.fiji.coloc.tests; import static org.junit.Assert.assertTrue; import java.util.Arrays; import java.util.List; import org.junit.Test; -import gadgets.Statistics; +import sc.fiji.coloc.gadgets.Statistics; /** * This class contains JUnit 4 test cases for the implementation * of different statistics methods. * * @author Tom Kazimiers */ public class StatisticsTest { /** * Test the error function. Test values have been taken from: * http://en.wikipedia.org/wiki/Error_function */ @Test public void erfTest() { // erf(0) = 0 double erf = Statistics.erf(0.0); assertTrue( Math.abs(erf) < 0.0001); // erf(0.5) = 0.5204999 erf = Statistics.erf(0.5); assertTrue( Math.abs(erf - 0.5204999) < 0.0001); // erf(1.0) = 0.8427008 erf = Statistics.erf(1.0); assertTrue( Math.abs(erf - 0.8427008) < 0.0001); // erf(-0.5) = -0.5204999 erf = Statistics.erf(-0.5); assertTrue( Math.abs(erf + 0.5204999) < 0.0001); // erf(-1.0) = -0.8427008 erf = Statistics.erf(-1.0); assertTrue( Math.abs(erf + 0.8427008) < 0.0001); } /** * Test the cumulative distribution function (phi) for the * standard normal distribution. */ @Test public void phiTest() { // phi(0) = 0.5 double phi = Statistics.phi(0.0); assertTrue( Math.abs(phi - 0.5) < 0.0001 ); // phi(-1) = 0.158655 phi = Statistics.phi(-1.0); assertTrue( Math.abs(phi - 0.158655) < 0.0001 ); // phi(0.5) = 0.691462 phi = Statistics.phi(0.5); assertTrue( Math.abs(phi - 0.691462) < 0.0001 ); // phi(1.960) = 0.975002 phi = Statistics.phi(1.960); assertTrue( Math.abs(phi - 0.975002) < 0.0001 ); } /** * Test the cumulative distribution function (phi) for the * normal distribution (based on mean and standard derivation). */ @Test public void phiDifferentDistributionTest() { // phi(0.5, 0, 1) = 0.691462 double phi = Statistics.phi(0.5, 0.0, 1.0); assertTrue( Math.abs(phi - 0.691462) < 0.0001 ); // phi(0.5, 20, 12) = 0.052081 phi = Statistics.phi(0.5, 20, 12); assertTrue( Math.abs(phi - 0.052081) < 0.0001 ); // phi(-1, 42, 33) = 0.096282 phi = Statistics.phi(-1, 42, 33); assertTrue( Math.abs(phi - 0.096282) < 0.0001 ); } /** * Tests the calculation of the standard deviation of a list * of values. */ @Test public void stdDeviationTest() { /* the standard deviation of the list * [1, 3, 4, 6, 9, 19] is 6.48074069 */ List values = Arrays.asList( new Double[] {1.0, 3.0, 4.0, 6.0, 9.0, 19.0} ); double sd = Statistics.stdDeviation(values); assertTrue( Math.abs( sd - 6.48074069 ) < 0.0001); } } diff --git a/src/test/java/tests/TestImageAccessor.java b/src/test/java/sc/fiji/coloc/tests/TestImageAccessor.java similarity index 99% rename from src/test/java/tests/TestImageAccessor.java rename to src/test/java/sc/fiji/coloc/tests/TestImageAccessor.java index 3d7d05e..c5681f6 100644 --- a/src/test/java/tests/TestImageAccessor.java +++ b/src/test/java/sc/fiji/coloc/tests/TestImageAccessor.java @@ -1,443 +1,443 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package tests; +package sc.fiji.coloc.tests; import static org.junit.Assume.assumeNotNull; import ij.ImagePlus; import ij.gui.NewImage; import ij.gui.Roi; import ij.io.Opener; import ij.process.ImageProcessor; import java.awt.Color; import java.io.BufferedInputStream; import java.io.InputStream; import java.util.Arrays; import net.imglib2.Cursor; import net.imglib2.Interval; import net.imglib2.Localizable; import net.imglib2.Point; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.TwinCursor; import net.imglib2.algorithm.gauss.Gauss; import net.imglib2.algorithm.math.ImageStatistics; import net.imglib2.img.ImagePlusAdapter; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; import net.imglib2.img.array.ArrayImgFactory; import net.imglib2.type.NativeType; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.real.FloatType; import net.imglib2.view.Views; -import algorithms.MissingPreconditionException; -import gadgets.MaskFactory; +import sc.fiji.coloc.algorithms.MissingPreconditionException; +import sc.fiji.coloc.gadgets.MaskFactory; /** * A class containing some testing helper methods. It allows * to open Tiffs from within the Jar file and can generate noise * images. * * @author Dan White * @author Tom Kazimiers */ public class TestImageAccessor { /* a static opener for opening images without the * need for creating every time a new opener */ static Opener opener = new Opener(); /** * Loads a Tiff file from within the jar. The given path is treated * as relative to this tests-package (i.e. "Data/test.tiff" refers * to the test.tiff in sub-folder Data). * * @param The wanted output type. * @param relPath The relative path to the Tiff file. * @return The file as ImgLib image. */ public static & NativeType> RandomAccessibleInterval loadTiffFromJar(String relPath) { InputStream is = TestImageAccessor.class.getResourceAsStream(relPath); BufferedInputStream bis = new BufferedInputStream(is); ImagePlus imp = opener.openTiff(bis, "The Test Image"); assumeNotNull(imp); return ImagePlusAdapter.wrap(imp); } /** * Loads a Tiff file from within the jar to use as a mask Cursor. * So we use Img which has a cursor() method. * The given path is treated * as relative to this tests-package (i.e. "Data/test.tiff" refers * to the test.tiff in sub-folder Data). * * @param The wanted output type. * @param relPath The relative path to the Tiff file. * @return The file as ImgLib image. */ public static & NativeType> Img loadTiffFromJarAsImg(String relPath) { InputStream is = TestImageAccessor.class.getResourceAsStream(relPath); BufferedInputStream bis = new BufferedInputStream(is); ImagePlus imp = opener.openTiff(bis, "The Test Image"); assumeNotNull(imp); return ImagePlusAdapter.wrap(imp); } /** * Creates a noisy image that is created by repeatedly adding points * with random intensity to the canvas. That way it tries to mimic the * way a microscope produces images. This convenience method uses the * default values of a point size of 3.0 and produces 5000 points. * After the creation the image is smoothed with a sigma of one in each * direction. * * @param The wanted output type. * @param width The image width. * @param height The image height. * @return The noise image. */ public static & NativeType> RandomAccessibleInterval produceNoiseImageSmoothed(T type, int width, int height) { return produceNoiseImageSmoothed(type, width, height, 3.0f, 5000, new double[] {1.0,1.0}); } /** * Creates a noisy image that is created by repeatedly adding points * with random intensity to the canvas. That way it tries to mimic the * way a microscope produces images. * * @param The wanted output type. * @param width The image width. * @param height The image height. * @param dotSize The size of the dots. * @param numDots The number of dots. * @param smoothingSigma The two dimensional sigma for smoothing. * @return The noise image. */ public static & NativeType> RandomAccessibleInterval produceNoiseImage(int width, int height, float dotSize, int numDots) { /* For now (probably until ImageJ2 is out) we use an * ImageJ image to draw circles. */ int options = NewImage.FILL_BLACK + NewImage.CHECK_AVAILABLE_MEMORY; ImagePlus img = NewImage.createByteImage("Noise", width, height, 1, options); ImageProcessor imp = img.getProcessor(); float dotRadius = dotSize * 0.5f; int dotIntSize = (int) dotSize; for (int i=0; i < numDots; i++) { int x = (int) (Math.random() * width - dotRadius); int y = (int) (Math.random() * height - dotRadius); imp.setColor(Color.WHITE); imp.fillOval(x, y, dotIntSize, dotIntSize); } // we changed the data, so update it img.updateImage(); // create the new image RandomAccessibleInterval noiseImage = ImagePlusAdapter.wrap(img); return noiseImage; } public static & NativeType> RandomAccessibleInterval produceNoiseImageSmoothed(T type, int width, int height, float dotSize, int numDots, double[] smoothingSigma) { RandomAccessibleInterval noiseImage = produceNoiseImage(width, height, dotSize, numDots); return gaussianSmooth(noiseImage, smoothingSigma); } /** * This method creates a noise image that has a specified mean. * Every pixel has a value uniformly distributed around mean with * the maximum spread specified. * * @return a new noise image * @throws MissingPreconditionException if specified means and spreads are not valid */ public static & NativeType> RandomAccessibleInterval produceMeanBasedNoiseImage(T type, int width, int height, double mean, double spread, double[] smoothingSigma) throws MissingPreconditionException { if (mean < spread || (mean + spread) > type.getMaxValue()) { throw new MissingPreconditionException("Mean must be larger than spread, and mean plus spread must be smaller than max of the type"); } // create the new image ImgFactory imgFactory = new ArrayImgFactory(); RandomAccessibleInterval noiseImage = imgFactory.create( new int[] {width, height}, type); // "Noise image"); for (T value : Views.iterable(noiseImage)) { value.setReal( mean + ( (Math.random() - 0.5) * spread ) ); } return gaussianSmooth(noiseImage, smoothingSigma); } /** * This method creates a noise image that is made of many little * sticks oriented in a random direction. How many of them and * what the length of them are can be specified. * * @return a new noise image that is not smoothed */ public static & NativeType> RandomAccessibleInterval produceSticksNoiseImage(int width, int height, int numSticks, int lineWidth, double maxLength) { /* For now (probably until ImageJ2 is out) we use an * ImageJ image to draw lines. */ int options = NewImage.FILL_BLACK + NewImage.CHECK_AVAILABLE_MEMORY; ImagePlus img = NewImage.createByteImage("Noise", width, height, 1, options); ImageProcessor imp = img.getProcessor(); imp.setColor(Color.WHITE); imp.setLineWidth(lineWidth); for (int i=0; i < numSticks; i++) { // find random starting point int x = (int) (Math.random() * width); int y = (int) (Math.random() * height); // create random stick length and direction double length = Math.random() * maxLength; double angle = Math.random() * 2 * Math.PI; // calculate random point on circle, for the direction int destX = x + (int) (length * Math.cos(angle)); int destY = y + (int) (length * Math.sin(angle)); // now draw the line imp.drawLine(x, y, destX, destY); } // we changed the data, so update it img.updateImage(); return ImagePlusAdapter.wrap(img); } /** * This method creates a smoothed noise image that is made of * many little sticks oriented in a random direction. How many * of them and what the length of them are can be specified. * * @return a new noise image that is smoothed */ public static & NativeType> RandomAccessibleInterval produceSticksNoiseImageSmoothed(T type, int width, int height, int numSticks, int lineWidth, double maxLength, double[] smoothingSigma) { RandomAccessibleInterval noiseImage = produceSticksNoiseImage(width, height, numSticks, lineWidth, maxLength); return gaussianSmooth(noiseImage, smoothingSigma); } /** * Generates a Perlin noise image. It is based on Ken Perlin's * reference implementation (ImprovedNoise class) and a small * bit of Kas Thomas' sample code (http://asserttrue.blogspot.com/). */ public static & NativeType> RandomAccessibleInterval producePerlinNoiseImage(T type, int width, int height, double z, double scale) { // create the new image ImgFactory imgFactory = new ArrayImgFactory(); RandomAccessibleInterval noiseImage = imgFactory.create( new int[] {width, height}, type); Cursor noiseCursor = Views.iterable(noiseImage).localizingCursor(); double xOffset = Math.random() * (width*width); double yOffset = Math.random() * (height*height); while (noiseCursor.hasNext()) { noiseCursor.fwd(); double x = (noiseCursor.getDoublePosition(0) + xOffset) * scale; double y = (noiseCursor.getDoublePosition(1) + yOffset) * scale; float t = (float)ImprovedNoise.noise( x, y, z); // ImprovedNoise.noise returns a float in the range [-1..1], // whereas we want a float in the range [0..1], so: t = (1 + t) * 0.5f; noiseCursor.get().setReal(t); } //return gaussianSmooth(noiseImage, imgFactory, smoothingSigma); return noiseImage; } /** * Gaussian Smooth of the input image using intermediate float format. * @param * @param img * @param sigma * @return */ public static & NativeType> RandomAccessibleInterval gaussianSmooth( RandomAccessibleInterval img, double[] sigma) { Interval interval = Views.iterable(img); ImgFactory outputFactory = new ArrayImgFactory(); final long[] dim = new long[ img.numDimensions() ]; img.dimensions(dim); RandomAccessibleInterval output = outputFactory.create( dim, img.randomAccess().get().createVariable() ); final long[] pos = new long[ img.numDimensions() ]; Arrays.fill(pos, 0); Localizable origin = new Point(pos); ImgFactory tempFactory = new ArrayImgFactory(); RandomAccessible input = Views.extendMirrorSingle(img); Gauss.inFloat(sigma, input, interval, output, origin, tempFactory); return output; } /** * Inverts an image. * * @param The images data type. * @param image The image to convert. * @return The inverted image. */ public static & NativeType> RandomAccessibleInterval invertImage( RandomAccessibleInterval image) { Cursor imgCursor = Views.iterable(image).localizingCursor(); // invert the image long[] dim = new long[ image.numDimensions() ]; image.dimensions(dim); ArrayImgFactory imgFactory = new ArrayImgFactory(); RandomAccessibleInterval invImg = imgFactory.create( dim, image.randomAccess().get().createVariable() ); // "Inverted " + image.getName()); RandomAccess invCursor = invImg.randomAccess(); while (imgCursor.hasNext()) { imgCursor.fwd(); invCursor.setPosition(imgCursor); invCursor.get().setReal( imgCursor.get().getMaxValue() - imgCursor.get().getRealDouble() ); } return invImg; } /** * Converts an arbitrary image to a black/white version of it. * All image data lower or equal 0.5 times the maximum value * of the image type will get black, the rest will turn white. */ public static & NativeType> RandomAccessibleInterval makeBinaryImage( RandomAccessibleInterval image) { T binSplitValue = image.randomAccess().get(); binSplitValue.setReal( binSplitValue.getMaxValue() * 0.5 ); return TestImageAccessor.makeBinaryImage(image, binSplitValue); } /** * Converts an arbitrary image to a black/white version of it. * All image data lower or equal the splitValue will get black, * the rest will turn white. */ public static & NativeType> RandomAccessibleInterval makeBinaryImage( RandomAccessibleInterval image, T splitValue) { Cursor imgCursor = Views.iterable(image).localizingCursor(); // make a new image of the same type, but binary long[] dim = new long[ image.numDimensions() ]; image.dimensions(dim); ArrayImgFactory imgFactory = new ArrayImgFactory(); RandomAccessibleInterval binImg = imgFactory.create( dim, image.randomAccess().get().createVariable() ); // "Binary image of " + image.getName()); RandomAccess invCursor = binImg.randomAccess(); while (imgCursor.hasNext()) { imgCursor.fwd(); invCursor.setPosition(imgCursor); T currentValue = invCursor.get(); if (currentValue.compareTo(splitValue) > 0) currentValue.setReal( currentValue.getMaxValue() ); else currentValue.setZero(); } return binImg; } /** * A method to combine a foreground image and a background image. * If data on the foreground image is above zero, it will be * placed on the background. While doing that, the image data from * the foreground is scaled to be in range of the background. */ public static > void combineImages(RandomAccessibleInterval background, RandomAccessibleInterval foreground) { final long[] dim = new long[ background.numDimensions() ]; background.dimensions(dim); RandomAccessibleInterval alwaysTrueMask = MaskFactory.createMask(dim, true); TwinCursor cursor = new TwinCursor( background.randomAccess(), foreground.randomAccess(), Views.iterable(alwaysTrueMask).localizingCursor()); // find a scaling factor for scale forground range into background double bgMin = ImageStatistics.getImageMin(background).getRealDouble(); double bgMax = ImageStatistics.getImageMax(background).getRealDouble(); double fgMin = ImageStatistics.getImageMin(foreground).getRealDouble(); double fgMax = ImageStatistics.getImageMax(foreground).getRealDouble(); double scaling = (bgMax - bgMin ) / (fgMax - fgMin); // iterate over both images while (cursor.hasNext()) { cursor.fwd(); T bgData = cursor.getFirst(); double fgData = cursor.getSecond().getRealDouble() * scaling; if (fgData > 0.01) { /* if the foreground data is above zero, copy * it to the background. */ bgData.setReal(fgData); } } } /** * Creates a mask image with a black background and a white * rectangular foreground. * * @param width The width of the result image. * @param height The height of the result image. * @param offset The offset of the rectangular mask. * @param size The size of the rectangular mask. * @return A black image with a white rectangle on it. */ public static & NativeType> RandomAccessibleInterval createRectengularMaskImage( long width, long height, long[] offset, long[] size) { /* For now (probably until ImageJ2 is out) we use an * ImageJ image to draw lines. */ int options = NewImage.FILL_BLACK + NewImage.CHECK_AVAILABLE_MEMORY; ImagePlus img = NewImage.createByteImage("Noise", (int)width, (int)height, 1, options); ImageProcessor imp = img.getProcessor(); imp.setColor(Color.WHITE); Roi rect = new Roi(offset[0], offset[1], size[0], size[1]); imp.fill(rect); // we changed the data, so update it img.updateImage(); return ImagePlusAdapter.wrap(img); } } diff --git a/src/test/java/tests/TestInteractively.java b/src/test/java/sc/fiji/coloc/tests/TestInteractively.java similarity index 98% rename from src/test/java/tests/TestInteractively.java rename to src/test/java/sc/fiji/coloc/tests/TestInteractively.java index 2137cff..0b5e1ee 100644 --- a/src/test/java/tests/TestInteractively.java +++ b/src/test/java/sc/fiji/coloc/tests/TestInteractively.java @@ -1,51 +1,51 @@ /*- * #%L * Fiji's plugin for colocalization analysis. * %% * Copyright (C) 2009 - 2017 Fiji developers. * %% * 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 * . * #L% */ -package tests; +package sc.fiji.coloc.tests; import fiji.Debug; public class TestInteractively { public static void main(String[] args) { // dummy call: initialize debug session Debug.run("Close All", ""); final String image1 = "colocsample1b-green.tif"; final String image2 = "colocsample1b-red.tif"; String testsDataDir = System.getProperty("plugins.dir") + "/../src/test/resources/"; Debug.run("Open...", "open=" + testsDataDir + image1); Debug.run("Open...", "open=" + testsDataDir + image2); Debug.run("Coloc 2", "channel_1=" + image1 + " channel_2=" + image2 + " roi_or_mask= " + "li_histogram_channel_1 " + "li_histogram_channel_2 " + "li_icq " + "spearman's_rank_correlation " + "manders'_correlation " + "kendall's_tau_rank_correlation " + "2d_instensity_histogram " + "costes'_significance_test " + "psf=3 " + "costes_randomisations=10" ); } }