diff --git a/pom.xml b/pom.xml index 1c3463d..5dda3b5 100644 --- a/pom.xml +++ b/pom.xml @@ -1,175 +1,175 @@ 4.0.0 org.scijava pom-scijava 13.2.1 sc.fiji Colocalisation_Analysis - 2.2.4-SNAPSHOT + 3.0.0-SNAPSHOT Coloc 2 Fiji's plugin for colocalization analysis. https://imagej.net/Coloc_2 2009 Fiji http://fiji.sc/ GNU General Public License v3+ http://www.gnu.org/licenses/gpl.html repo chalkie666 Daniel James White http://imagej.net/User:White lead reviewer support maintainer tomka Tom Kazimiers http://imagej.net/User:Kazimiers lead debugger reviewer etarena Ellen Arena http://imagej.net/User:Etarena developer debugger reviewer support ctrueden Curtis Rueden http://imagej.net/User:Rueden maintainer Johannes Schindelin http://imagej.net/User:Schindelin founder dscho Jan Eglinger http://imagej.net/User:Eglinger imagejan Leonardo Guizzetti leonardicus Mark Hiner http://imagej.net/User:Hinerm hinerm Jean-Yves Tinevez http://imagej.net/User:JeanYvesTinevez tinevez ImageJ Forum http://forum.imagej.net/ scm:git:git://github.com/fiji/Colocalisation_Analysis scm:git:git@github.com:fiji/Colocalisation_Analysis HEAD https://github.com/fiji/Colocalisation_Analysis github.com https://github.com/fiji/Colocalisation_Analysis Travis CI https://travis-ci.org/fiji/Colocalisation_Analysis gpl_v3 Fiji developers. imagej.public http://maven.imagej.net/content/groups/public sc.fiji fiji-lib net.imagej ij net.imglib2 imglib2 net.imglib2 imglib2-algorithm net.imglib2 imglib2-ij com.itextpdf itextpdf junit junit test diff --git a/src/main/java/Coloc_2.java b/src/main/java/Coloc_2.java deleted file mode 100644 index 3740eed..0000000 --- a/src/main/java/Coloc_2.java +++ /dev/null @@ -1,796 +0,0 @@ -/*- - * #%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% - */ -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 fiji.Debug; -import gadgets.DataContainer; -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.Frame; -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.ArrayImg; -import net.imglib2.img.array.ArrayImgFactory; -import net.imglib2.img.array.ArrayImgs; -import net.imglib2.type.NativeType; -import net.imglib2.type.logic.BitType; -import net.imglib2.type.numeric.RealType; -import net.imglib2.view.Views; -import results.PDFWriter; -import results.ResultHandler; -import results.SingleWindowDisplay; -import 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< T >> implements PlugIn { - - // a small bounding box container - protected class BoundingBox { - public long[] offset; - public long[] size; - public BoundingBox(long [] offset, 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(BoundingBox roi, 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(String arg0) { - if (showDialog()) { - try { - for (MaskInfo mi : masks) { - colocalise(img1, img2, mi.roi, mi.mask); - } - } catch (MissingPreconditionException e) { - IJ.handleException(e); - IJ.showMessage("An error occured, could not colocalize!"); - return; - } - } - } - - public boolean showDialog() { - // get IDs of open windows - 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"); - - 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" - */ - 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++) { - 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 - Implementation[] regressionImplementations = - AutoThresholdRegression.Implementation.values(); - for (int i=0; i maskImg = ImagePlusAdapter.wrap( maskImp ); - // get a valid mask info for the image - 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 = gd.getNextChoiceIndex(); - - // read out GUI data - autoSavePdf = gd.getNextBoolean(); - displayImages = gd.getNextBoolean(); - displayShuffledCostes = gd.getNextBoolean(); - useLiCh1 = gd.getNextBoolean(); - useLiCh2 = gd.getNextBoolean(); - useLiICQ = gd.getNextBoolean(); - useSpearmanRank = gd.getNextBoolean(); - useManders = gd.getNextBoolean(); - useKendallTau = gd.getNextBoolean(); - useScatterplot = gd.getNextBoolean(); - useCostes = gd.getNextBoolean(); - psf = (int) gd.getNextNumber(); - nrCostesRandomisations = (int) gd.getNextNumber(); - - // save user preferences - Prefs.set(PREF_KEY+"regressionImplementation", indexRegr); - Prefs.set(PREF_KEY+"autoSavePdf", autoSavePdf); - Prefs.set(PREF_KEY+"displayImages", displayImages); - Prefs.set(PREF_KEY+"displayShuffledCostes", displayShuffledCostes); - Prefs.set(PREF_KEY+"useLiCh1", useLiCh1); - Prefs.set(PREF_KEY+"useLiCh2", useLiCh2); - Prefs.set(PREF_KEY+"useLiICQ", useLiICQ); - Prefs.set(PREF_KEY+"useSpearmanRank", useSpearmanRank); - Prefs.set(PREF_KEY+"useManders", useManders); - Prefs.set(PREF_KEY+"useKendallTau", useKendallTau); - Prefs.set(PREF_KEY+"useScatterplot", useScatterplot); - Prefs.set(PREF_KEY+"useCostes", useCostes); - Prefs.set(PREF_KEY+"psf", psf); - Prefs.set(PREF_KEY+"nrCostesRandomisations", nrCostesRandomisations); - - // Parse algorithm options - pearsonsCorrelation = new PearsonsCorrelation(PearsonsCorrelation.Implementation.Fast); - - if (useLiCh1) - liHistogramCh1 = new LiHistogram2D("Li - Ch1", true); - if (useLiCh2) - liHistogramCh2 = new LiHistogram2D("Li - Ch2", false); - if (useLiICQ) - liICQ = new LiICQ(); - if (useSpearmanRank) - SpearmanRankCorrelation = new SpearmanRankCorrelation(); - if (useManders) - mandersCorrelation = new MandersColocalization(); - if (useKendallTau) - kendallTau = new KendallTauRankCorrelation(); - if (useScatterplot) - histogram2D = new Histogram2D("2D intensity histogram"); - if (useCostes) { - costesSignificance = new CostesSignificanceTest(pearsonsCorrelation, - psf, nrCostesRandomisations, displayShuffledCostes); - } - - 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. - * - * @param img1 - * @param img2 - * @param roi - * @param mask - * @throws MissingPreconditionException - */ - public void colocalise(Img img1, Img img2, BoundingBox roi, - Img mask) throws MissingPreconditionException { - // create a new container for the selected images and channels - DataContainer container; - if (mask != null) { - container = new DataContainer(img1, img2, - 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(img1, img2, - img1Channel, img2Channel, Ch1Name, Ch2Name, roi.offset, roi.size); - } else { - // no mask and no ROI is present - container = new DataContainer(img1, img2, - img1Channel, img2Channel, Ch1Name, Ch2Name); - } - - // create a results handler - final List> listOfResultHandlers = new ArrayList>(); - final PDFWriter pdfWriter = new PDFWriter(container); - final SingleWindowDisplay swDisplay = new SingleWindowDisplay(container, pdfWriter); - listOfResultHandlers.add(swDisplay); - listOfResultHandlers.add(pdfWriter); - //ResultHandler resultHandler = new EasyDisplay(container); - - // this list contains the algorithms that will be run when the user clicks ok - 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; - int jobs = userSelectedJobs.size(); - for (Algorithm a : userSelectedJobs){ - try { - count++; - IJ.showStatus(count + "/" + jobs + ": Running " + a.getName()); - a.execute(container); - } - catch (MissingPreconditionException e){ - for (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 (Algorithm a : userSelectedJobs){ - for (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) { - long[] offset = container.getMaskBBOffset(); - 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 (ResultHandler r : listOfResultHandlers) { - r.handleImage (channel1, "Channel 1 (Max Projection)"); - r.handleImage (channel2, "Channel 2 (Max Projection)"); - } - } - // do the actual results processing - swDisplay.process(); - // add window to the IJ window manager - swDisplay.addWindowListener(new WindowAdapter() { - @Override - public void windowClosed(WindowEvent e) { - WindowManager.removeWindow((Frame) swDisplay); - } - }); - WindowManager.addWindow(swDisplay); - // show PDF saving dialog if requested - if (autoSavePdf) - pdfWriter.process(); - } - - private RandomAccessibleInterval project(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 (T sample : output) { - sample.setReal(type.getMinValue()); - } - - // loop over the input image, performing the max projection - final Cursor inPos = input.localizingCursor(); - RandomAccess outPos = output.randomAccess(); - while (inPos.hasNext()) { - final T inPix = inPos.next(); - long xPos = inPos.getLongPosition(0); - long yPos = inPos.getLongPosition(1); - outPos.setPosition(xPos, 0); - outPos.setPosition(yPos, 1); - 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(Img mask) { - Cursor cursor = mask.localizingCursor(); - - int numMaskDims = mask.numDimensions(); - // the "off type" of the mask - T offType = mask.firstElement().createVariable(); - offType.setZero(); - // the corners of the bounding box - long[] min = null; - long[] max = null; - // indicates if mask data has been found - boolean maskFound = false; - // a container for temporary position information - long[] pos = new long[numMaskDims]; - // walk over the mask - while (cursor.hasNext() ) { - cursor.fwd(); - 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 - min = Arrays.copyOf(pos, numMaskDims); - max = 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 max[d]) { - // is it larger than max - max[d] = pos[d]; - } - } - } - } - } - - if (!maskFound) { - return null; - } else { - // calculate size - long[] size = new long[numMaskDims]; - for (int d=0; d a, 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(ImagePlus imp) { - Roi roi = imp.getRoi(); - if (roi == null) - return false; - - 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(int val, int min, 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(ImagePlus imp) { - // get ROIs from current image in Fiji - 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(int width, int height) { - RoiManager roiManager = RoiManager.getInstance(); - if (roiManager == null) { - IJ.error("Could not get ROI Manager instance."); - return false; - } - 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(Roi[] rois, int width, int height) { - // create empty list - masks.clear(); - - for (Roi r : rois ){ - MaskInfo mi = new MaskInfo(); - // add it to the list of masks/ROIs - masks.add(mi); - // get the ROIs/masks bounding box - Rectangle rect = r.getBounds(); - mi.roi = new BoundingBox( - new long[] {rect.x, rect.y} , - new long[] {rect.width, rect.height}); - 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 - 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 - 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( - RandomAccessibleInterval image, RandomAccessibleInterval mask, - long[] offset, long[] size) throws MissingPreconditionException { - 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 - TwinCursor cursor = new TwinCursor( - image.randomAccess(), - image.randomAccess(), - Views.iterable(mask).localizingCursor()); - // prepare output image - ImgFactory maskFactory = new ArrayImgFactory(); - //Img maskImage = maskFactory.create( size, name ); - RandomAccessibleInterval maskImage = maskFactory.create( size, - image.randomAccess().get().createVariable() ); - 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(Roi roi) { - if (roi instanceof ShapeRoi) - return ((ShapeRoi)roi).getRois(); - return new Roi[] { roi }; - } - - /** - * Main method for easier development. To run this plugin with Maven, use: - * mvn exec:java -Dexec.mainClass="Coloc_2" - */ - public static void main(String[] args) { - Debug.run(null, null); - } -} 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/sc/fiji/coloc/Coloc_2.java b/src/main/java/sc/fiji/coloc/Coloc_2.java new file mode 100644 index 0000000..db72f5a --- /dev/null +++ b/src/main/java/sc/fiji/coloc/Coloc_2.java @@ -0,0 +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 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 5b17c5e..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; - -import gadgets.DataContainer; +package sc.fiji.coloc.algorithms; import java.util.ArrayList; import java.util.List; import net.imglib2.type.numeric.RealType; -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 532c016..b91b356 100644 --- a/src/main/java/algorithms/AutoThresholdRegression.java +++ b/src/main/java/sc/fiji/coloc/algorithms/AutoThresholdRegression.java @@ -1,348 +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 gadgets.DataContainer; -import gadgets.ThresholdMode; 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 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 e2363ab..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; - -import gadgets.DataContainer; -import gadgets.DataContainer.MaskType; -import gadgets.Statistics; +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 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 33f749b..79a8b0a 100644 --- a/src/main/java/algorithms/Histogram2D.java +++ b/src/main/java/sc/fiji/coloc/algorithms/Histogram2D.java @@ -1,414 +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 gadgets.DataContainer; 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 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 f0225d3..c090202 100644 --- a/src/main/java/algorithms/InputCheck.java +++ b/src/main/java/sc/fiji/coloc/algorithms/InputCheck.java @@ -1,205 +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 gadgets.DataContainer; -import gadgets.DataContainer.MaskType; 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 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 0b2c547..d9caa4e 100644 --- a/src/main/java/algorithms/KendallTauRankCorrelation.java +++ b/src/main/java/sc/fiji/coloc/algorithms/KendallTauRankCorrelation.java @@ -1,362 +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 gadgets.DataContainer; 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 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 5329f83..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; - -import gadgets.DataContainer; +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 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 738d8e6..b57ff80 100644 --- a/src/main/java/algorithms/LiICQ.java +++ b/src/main/java/sc/fiji/coloc/algorithms/LiICQ.java @@ -1,107 +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 gadgets.DataContainer; 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 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 6e9e9cf..1b01f8f 100644 --- a/src/main/java/algorithms/MandersColocalization.java +++ b/src/main/java/sc/fiji/coloc/algorithms/MandersColocalization.java @@ -1,288 +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 gadgets.DataContainer; -import gadgets.ThresholdMode; 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 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 6e233e1..d6398bb 100644 --- a/src/main/java/algorithms/PearsonsCorrelation.java +++ b/src/main/java/sc/fiji/coloc/algorithms/PearsonsCorrelation.java @@ -1,408 +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 gadgets.DataContainer; -import gadgets.MaskFactory; -import gadgets.ThresholdMode; 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 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 92% rename from src/main/java/algorithms/SpearmanRankCorrelation.java rename to src/main/java/sc/fiji/coloc/algorithms/SpearmanRankCorrelation.java index eb7c795..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; - -import gadgets.DataContainer; +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 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 - static double rhoValue; - static double tStatisticSpearman; - static int dfSpearman; + double rhoValue; + double tStatisticSpearman; + int dfSpearman; // create two paired arrays: one with raw pixel values and one for the corresponding ranks - static double[][] data; - static double[] ch1raw; - static double[] ch2raw; - static double[] ch1ranks; - static double[] ch2ranks; + 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 static > double calculateSpearmanRank(TwinCursor cursor) { + 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 static double calculateSpearmanRank(double[][] data) { + 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 static int getSpearmanDF(int n) { + 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 static double getTStatistic(double rho, int n) { + 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 static double[] rankValues(double[] sortedVals) { + 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 514b775..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; - -import algorithms.Algorithm; -import algorithms.AutoThresholdRegression; -import algorithms.InputCheck; -import algorithms.MissingPreconditionException; +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 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 ee53f2c..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; - -import algorithms.MissingPreconditionException; +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 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/sc/fiji/coloc/results/AnalysisResults.java b/src/main/java/sc/fiji/coloc/results/AnalysisResults.java new file mode 100644 index 0000000..4baf757 --- /dev/null +++ b/src/main/java/sc/fiji/coloc/results/AnalysisResults.java @@ -0,0 +1,114 @@ + +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 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 2020e84..977ce60 100644 --- a/src/main/java/results/EasyDisplay.java +++ b/src/main/java/sc/fiji/coloc/results/EasyDisplay.java @@ -1,117 +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 algorithms.Histogram2D; -import gadgets.DataContainer; 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 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 bf594e5..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; - -import algorithms.Histogram2D; +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 gadgets.DataContainer; -import gadgets.DataContainer.MaskType; 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 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 47df741..ef61582 100644 --- a/src/main/java/results/ResultHandler.java +++ b/src/main/java/sc/fiji/coloc/results/ResultHandler.java @@ -1,55 +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 algorithms.Histogram2D; import net.imglib2.RandomAccessibleInterval; import net.imglib2.type.numeric.RealType; +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 98% rename from src/main/java/results/SingleWindowDisplay.java rename to src/main/java/sc/fiji/coloc/results/SingleWindowDisplay.java index 9dc439c..acda598 100644 --- a/src/main/java/results/SingleWindowDisplay.java +++ b/src/main/java/sc/fiji/coloc/results/SingleWindowDisplay.java @@ -1,724 +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 algorithms.AutoThresholdRegression; -import algorithms.Histogram2D; -import fiji.util.gui.JImagePanel; -import gadgets.DataContainer; -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 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 sc.fiji.coloc.algorithms.AutoThresholdRegression; +import sc.fiji.coloc.algorithms.Histogram2D; +import sc.fiji.coloc.gadgets.DataContainer; + +import fiji.util.gui.JImagePanel; + /** * 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; - // a static list for keeping track of all other SingleWindowDisplays - protected static ArrayList> displays = new ArrayList>(); - // 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); - // add ourself to the list of single window displays - displays.add(this); } 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 new file mode 100644 index 0000000..530339c --- /dev/null +++ b/src/main/resources/script_templates/Examples/Colocalisation.groovy @@ -0,0 +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 55% rename from src/test/java/Main.java rename to src/test/java/sc/fiji/coloc/Main.java index 4b3303f..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(); + // 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-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"); + 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 5d15513..157e4c8 100644 --- a/src/test/java/tests/AutoThresholdRegressionTest.java +++ b/src/test/java/sc/fiji/coloc/tests/AutoThresholdRegressionTest.java @@ -1,93 +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 gadgets.DataContainer; + 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 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 1011f0f..56861f2 100644 --- a/src/test/java/tests/ColocalisationTest.java +++ b/src/test/java/sc/fiji/coloc/tests/ColocalisationTest.java @@ -1,153 +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 gadgets.MaskFactory; 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 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 - double rho1 = SpearmanRankCorrelation.calculateSpearmanRank(cursor1); - double rho2 = SpearmanRankCorrelation.calculateSpearmanRank(cursor2); + 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 b5af98c..c583bdb 100644 --- a/src/test/java/tests/CostesSignificanceTest.java +++ b/src/test/java/sc/fiji/coloc/tests/CostesSignificanceTest.java @@ -1,119 +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 algorithms.AutoThresholdRegression; -import algorithms.MissingPreconditionException; -import algorithms.PearsonsCorrelation; -import gadgets.DataContainer; + import net.imglib2.RandomAccessibleInterval; import net.imglib2.type.numeric.real.FloatType; import org.junit.Test; +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 0a7bb72..f53a83b 100644 --- a/src/test/java/tests/KendallTauTest.java +++ b/src/test/java/sc/fiji/coloc/tests/KendallTauTest.java @@ -1,137 +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 algorithms.KendallTauRankCorrelation; -import algorithms.MissingPreconditionException; + import net.imglib2.PairIterator; import net.imglib2.type.numeric.real.DoubleType; import org.junit.Test; +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 514ed76..9a6c5f7 100644 --- a/src/test/java/tests/LiICQTest.java +++ b/src/test/java/sc/fiji/coloc/tests/LiICQTest.java @@ -1,71 +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 algorithms.LiICQ; + import net.imglib2.TwinCursor; import net.imglib2.type.numeric.integer.UnsignedByteType; import net.imglib2.view.Views; import org.junit.Test; +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 b5374dc..ee9238a 100644 --- a/src/test/java/tests/MandersColocalizationTest.java +++ b/src/test/java/sc/fiji/coloc/tests/MandersColocalizationTest.java @@ -1,220 +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 algorithms.MandersColocalization; -import algorithms.MandersColocalization.MandersResults; -import algorithms.MissingPreconditionException; + 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 gadgets.ThresholdMode; - import org.junit.Test; +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 d8e7512..eee0716 100644 --- a/src/test/java/tests/MaskAndRoiTest.java +++ b/src/test/java/sc/fiji/coloc/tests/MaskAndRoiTest.java @@ -1,396 +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 algorithms.MissingPreconditionException; -import gadgets.MaskFactory; 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 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 74e1219..97c1d71 100644 --- a/src/test/java/tests/PearsonsCorrelationTest.java +++ b/src/test/java/sc/fiji/coloc/tests/PearsonsCorrelationTest.java @@ -1,197 +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 algorithms.MissingPreconditionException; -import algorithms.PearsonsCorrelation; -import algorithms.PearsonsCorrelation.Implementation; -import gadgets.MaskFactory; + 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 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 87% rename from src/test/java/tests/SpearmanRankTest.java rename to src/test/java/sc/fiji/coloc/tests/SpearmanRankTest.java index 277d8c7..d340088 100644 --- a/src/test/java/tests/SpearmanRankTest.java +++ b/src/test/java/sc/fiji/coloc/tests/SpearmanRankTest.java @@ -1,136 +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 algorithms.MissingPreconditionException; -import algorithms.SpearmanRankCorrelation; + import net.imglib2.TwinCursor; import net.imglib2.type.numeric.integer.UnsignedByteType; import net.imglib2.view.Views; import org.junit.Test; +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 = SpearmanRankCorrelation.calculateSpearmanRank(cursor); + 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 = SpearmanRankCorrelation.calculateSpearmanRank(cursor); + 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 = SpearmanRankCorrelation.calculateSpearmanRank(data); + double rho = src.calculateSpearmanRank(data); assertEquals(-0.1743, rho, 0.001); // check the degrees of freedom calculation ( df = n - 2 ) int df = 0; - df = SpearmanRankCorrelation.getSpearmanDF(n); + 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 = SpearmanRankCorrelation.getTStatistic(rho, n); + 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 = SpearmanRankCorrelation.calculateSpearmanRank(cursor); + 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 cc69aa1..292431d 100644 --- a/src/test/java/tests/StatisticsTest.java +++ b/src/test/java/sc/fiji/coloc/tests/StatisticsTest.java @@ -1,112 +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 gadgets.Statistics; import java.util.Arrays; import java.util.List; import org.junit.Test; +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 3aed05a..c5681f6 100644 --- a/src/test/java/tests/TestImageAccessor.java +++ b/src/test/java/sc/fiji/coloc/tests/TestImageAccessor.java @@ -1,441 +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 algorithms.MissingPreconditionException; -import gadgets.MaskFactory; + 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.Img; 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 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" ); } }