package gdsc.threshold; import java.util.ArrayList; import gdsc.UsageTracker; import gdsc.core.threshold.AutoThreshold; /*----------------------------------------------------------------------------- * GDSC Plugins for ImageJ * * Copyright (C) 2011 Alex Herbert * Genome Damage and Stability Centre * University of Sussex, UK * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. *---------------------------------------------------------------------------*/ import ij.IJ; import ij.ImagePlus; import ij.ImageStack; import ij.gui.GenericDialog; import ij.plugin.filter.PlugInFilter; import ij.process.ByteProcessor; import ij.process.ImageProcessor; /** * Processes an image stack and applies thresholding to create a mask for each channel+frame combination. */ public class Stack_Threshold implements PlugInFilter { // Store a reference to the current working image private ImagePlus imp; private static String TITLE = "Stack Threshold"; // ImageJ indexes for the dimensions array // private final int X = 0; // private final int Y = 1; private final int C = 2; private final int Z = 3; private final int T = 4; private static String methodOption = AutoThreshold.Method.OTSU.name; // Options flags private static boolean logThresholds = false; private static boolean compositeColour = false; private static boolean newImage = true; /* * (non-Javadoc) * * @see ij.plugin.filter.PlugInFilter#setup(java.lang.String, ij.ImagePlus) */ public int setup(String arg, ImagePlus imp) { UsageTracker.recordPlugin(this.getClass(), arg); if (imp == null) { IJ.noImage(); return DONE; } this.imp = imp; return DOES_16 + DOES_8G + NO_CHANGES; } /* * (non-Javadoc) * * @see ij.plugin.filter.PlugInFilter#run(ij.process.ImageProcessor) */ public void run(ImageProcessor inputProcessor) { int[] dimensions = imp.getDimensions(); int currentSlice = imp.getCurrentSlice(); for (String method : getMethods()) { ImageStack maskStack = new ImageStack(imp.getWidth(), imp.getHeight(), imp.getStackSize()); // Process each frame for (int t = 1; t <= dimensions[T]; t++) { ArrayList<SliceCollection> sliceCollections = new ArrayList<SliceCollection>(); // Extract the channels for (int c = 1; c <= dimensions[C]; c++) { // Process all slices together SliceCollection sliceCollection = new SliceCollection(c); for (int z = 1; z <= dimensions[Z]; z++) { sliceCollection.add(imp.getStackIndex(c, z, t)); } sliceCollections.add(sliceCollection); } // Create masks for (SliceCollection s : sliceCollections) createMask(method, maskStack, t, s); } if (newImage) { ImagePlus newImg = new ImagePlus(imp.getTitle() + ":" + method, maskStack); newImg.setDimensions(dimensions[C], dimensions[Z], dimensions[T]); if (imp.getNDimensions() > 3) newImg.setOpenAsHyperStack(true); newImg.show(); if (compositeColour) { IJ.run("Make Composite", "display=Color"); } } else { imp.setStack(maskStack, imp.getNChannels(), imp.getNSlices(), imp.getNFrames()); for (int slice = 1; slice <= imp.getStackSize(); slice++) { imp.setSliceWithoutUpdate(slice); imp.resetDisplayRange(); } imp.updateAndRepaintWindow(); } } imp.setSlice(currentSlice); } private void createMask(String method, ImageStack maskStack, int t, SliceCollection sliceCollection) { sliceCollection.createStack(imp); sliceCollection.createMask(method); if (logThresholds) IJ.log("t" + t + sliceCollection.getSliceName() + " threshold = " + sliceCollection.threshold); for (int s = 1; s <= sliceCollection.maskStack.getSize(); s++) { int originalSliceNumber = sliceCollection.slices.get(s - 1); maskStack.setSliceLabel(method + ":" + imp.getStack().getSliceLabel(originalSliceNumber), originalSliceNumber); maskStack.setPixels(sliceCollection.maskStack.getPixels(s), originalSliceNumber); } } private String[] getMethods() { GenericDialog gd = new GenericDialog(TITLE); gd.addMessage(TITLE); // Commented out the methods that take a long time on 16-bit images. String[] methods = { "Try all", AutoThreshold.Method.DEFAULT.name, // "Huang", // "Intermodes", // "IsoData", AutoThreshold.Method.LI.name, AutoThreshold.Method.MAX_ENTROPY.name, AutoThreshold.Method.MEAN.name, AutoThreshold.Method.MIN_ERROR_I.name, // "Minimum", AutoThreshold.Method.MOMENTS.name, AutoThreshold.Method.OTSU.name, AutoThreshold.Method.PERCENTILE.name, AutoThreshold.Method.RENYI_ENTROPY.name, // "Shanbhag", AutoThreshold.Method.TRIANGLE.name, AutoThreshold.Method.YEN.name }; gd.addChoice("Method", methods, methodOption); gd.addCheckbox("Log_thresholds", logThresholds); if (imp.getDimensions()[C] > 1) gd.addCheckbox("Composite_colour", compositeColour); gd.addCheckbox("New_image", newImage); gd.addHelp(gdsc.help.URL.FIND_FOCI); gd.showDialog(); if (gd.wasCanceled()) return new String[0]; methodOption = gd.getNextChoice(); logThresholds = gd.getNextBoolean(); if (imp.getDimensions()[C] > 1) compositeColour = gd.getNextBoolean(); newImage = gd.getNextBoolean(); if (!methodOption.equals("Try all")) { // Ensure that the string contains known methods (to avoid passing bad macro arguments) methods = extractMethods(methodOption.split(" "), methods); } else { // Shift the array to remove the try all option String[] newMethods = new String[methods.length - 1]; for (int i = 0; i < newMethods.length; i++) newMethods[i] = methods[i + 1]; methods = newMethods; } if (methods.length == 0) return failed("No valid thresholding method(s) specified"); // Cannot update original with more than one method if (methods.length > 1) newImage = true; return methods; } private String[] failed(String message) { IJ.error(TITLE, message); return new String[0]; } /** * Filtered the set of options using the allowed methods array. * * @param options * @param allowedMethods * @return filtered options */ private String[] extractMethods(String[] options, String[] allowedMethods) { ArrayList<String> methods = new ArrayList<String>(); for (String option : options) { for (String allowedMethod : allowedMethods) { if (option.equals(allowedMethod)) { methods.add(option); break; } } } return methods.toArray(new String[0]); } /** * Provides functionality to process a collection of slices from an Image */ public class SliceCollection { public int c; public int z; public ArrayList<Integer> slices; private String sliceName = null; public ImageStack imageStack; public ImageStack maskStack; public int threshold; /** * @param c * The channel * @param z * The z dimension */ SliceCollection(int c, int z) { this.c = c; this.z = z; slices = new ArrayList<Integer>(1); } /** * @param c * The channel */ SliceCollection(int c) { this.c = c; this.z = 0; slices = new ArrayList<Integer>(); } /** * Utility method * * @param i */ public void add(Integer i) { slices.add(i); } public String getSliceName() { if (sliceName == null) { StringBuffer sb = new StringBuffer(); sb.append("c").append(c); if (z != 0) { sb.append("z").append(z); } sliceName = sb.toString(); } return sliceName; } /** * Extracts the configured slices from the image into a stack * * @param imp */ public void createStack(ImagePlus imp) { imageStack = new ImageStack(imp.getWidth(), imp.getHeight()); for (int slice : slices) { imp.setSliceWithoutUpdate(slice); imageStack.addSlice(Integer.toString(slice), imp.getProcessor().duplicate()); } } /** * Creates a mask using the specified thresholding method * * @param ip * @param method * @return the mask */ private void createMask(String method) { // Create an aggregate histogram int[] data = imageStack.getProcessor(1).getHistogram(); int[] temp = new int[data.length]; for (int s = 2; s <= imageStack.getSize(); s++) { temp = imageStack.getProcessor(s).getHistogram(); for (int i = 0; i < data.length; i++) { data[i] += temp[i]; } } threshold = AutoThreshold.getThreshold(method, data); // Create a mask for each image in the stack maskStack = new ImageStack(imageStack.getWidth(), imageStack.getHeight()); for (int s = 1; s <= imageStack.getSize(); s++) { ByteProcessor bp = new ByteProcessor(imageStack.getWidth(), imageStack.getHeight()); ImageProcessor ip = imageStack.getProcessor(s); for (int i = bp.getPixelCount(); i-- > 0;) { if (ip.get(i) > threshold) { bp.set(i, 255); } } maskStack.addSlice(null, bp); } } } }