package ij.plugin; import java.util.Arrays; import ij.IJ; import ij.ImagePlus; import ij.ImageStack; import ij.WindowManager; import ij.gui.GenericDialog; import ij.process.ByteProcessor; import ij.process.FloatProcessor; import ij.process.ImageProcessor; import ij.process.ImageStatistics; import ij.process.ShortProcessor; /** * Extend the ZProjector to support mode intensity projection. * * Note: This class extends a copy of the default ImageJ ZProjector so that certain methods and properties can be * changed to protected from the private/default scope. Extending a copy allows easier update when the super class * changes. */ public class ZProjector2 extends ZProjectorCopy { public static final int MODE_METHOD = 6; public static final int MODE_IGNORE_ZERO_METHOD = 7; public static final String[] METHODS = { "Average Intensity", "Max Intensity", "Min Intensity", "Sum Slices", "Standard Deviation", "Median", "Mode", "Mode (ignore zero)" }; @Override public void run(String arg) { super.run(arg); if (projImage != null) { // Set the display range ImageProcessor ip = projImage.getProcessor(); if (ip instanceof ByteProcessor) { // We do not want the standard 0-255 range for ByteProcessor but the min/max range for (int c = 1; c <= projImage.getNChannels(); c++) { int index = projImage.getStackIndex(c, 1, 1); projImage.setSliceWithoutUpdate(index); ip = projImage.getProcessor(); ImageStatistics stats = ImageStatistics.getStatistics(ip, ImageStatistics.MIN_MAX, null); ip.setMinAndMax(stats.min, stats.max); } projImage.setSliceWithoutUpdate(projImage.getStackIndex(1, 1, 1)); } else { projImage.resetDisplayRange(); } projImage.updateAndDraw(); } } @Override protected GenericDialog buildControlDialog(int start, int stop) { GenericDialog gd = new GenericDialog("ZProjection", IJ.getInstance()); gd.addNumericField("Start slice:", startSlice, 0/* digits */); gd.addNumericField("Stop slice:", stopSlice, 0/* digits */); gd.addChoice("Projection type", METHODS, METHODS[method]); if (isHyperstack && imp.getNFrames() > 1 && imp.getNSlices() > 1) gd.addCheckbox("All time frames", allTimeFrames); gd.addHelp(gdsc.help.URL.UTILITY); return gd; } @Override protected String makeTitle() { String prefix = "AVG_"; switch (method) { case SUM_METHOD: prefix = "SUM_"; break; case MAX_METHOD: prefix = "MAX_"; break; case MIN_METHOD: prefix = "MIN_"; break; case SD_METHOD: prefix = "STD_"; break; case MEDIAN_METHOD: prefix = "MED_"; break; case MODE_METHOD: case MODE_IGNORE_ZERO_METHOD: prefix = "MOD_"; break; } return WindowManager.makeUniqueName(prefix + imp.getTitle()); } @Override public void doProjection() { if (imp == null) return; sliceCount = 0; for (int slice = startSlice; slice <= stopSlice; slice += increment) sliceCount++; if (method >= MODE_METHOD) { projImage = doModeProjection(method == MODE_IGNORE_ZERO_METHOD); return; } if (method == MEDIAN_METHOD) { projImage = doMedianProjection(); return; } super.doProjection(); } private interface Projector { float value(float[] values); } private ImagePlus doProjection(String name, Projector p) { IJ.showStatus("Calculating " + name + "..."); ImageStack stack = imp.getStack(); // Check not an RGB stack int ptype; if (stack.getProcessor(1) instanceof ByteProcessor) ptype = BYTE_TYPE; else if (stack.getProcessor(1) instanceof ShortProcessor) ptype = SHORT_TYPE; else if (stack.getProcessor(1) instanceof FloatProcessor) ptype = FLOAT_TYPE; else { IJ.error("Z Project", "Non-RGB stack required"); return null; } ImageProcessor[] slices = new ImageProcessor[sliceCount]; int index = 0; for (int slice = startSlice; slice <= stopSlice; slice += increment) slices[index++] = stack.getProcessor(slice); ImageProcessor ip2 = slices[0].duplicate(); ip2 = ip2.convertToFloat(); float[] values = new float[sliceCount]; int width = ip2.getWidth(); int height = ip2.getHeight(); int inc = Math.max(height / 30, 1); for (int y = 0, k = 0; y < height; y++) { if (y % inc == 0) IJ.showProgress(y, height - 1); for (int x = 0; x < width; x++, k++) { for (int i = 0; i < sliceCount; i++) //values[i] = slices[i].getPixelValue(x, y); values[i] = slices[i].getf(k); //ip2.putPixelValue(x, y, p.value(values)); ip2.setf(k, p.value(values)); } } ImagePlus projImage = makeOutputImage(imp, (FloatProcessor) ip2, ptype); IJ.showProgress(1, 1); return projImage; } /* * (non-Javadoc) * * @see ij.plugin.ZProjectorCopy#doMedianProjection() */ @Override protected ImagePlus doMedianProjection() { // Override to change the method for accessing pixel values to getf() return doProjection("median", new Projector() { public float value(float[] values) { return median(values); } }); } protected ImagePlus doModeProjection(final boolean ignoreZero) { return doProjection("mode", new Projector() { public float value(float[] values) { return getMode(values, ignoreZero); } }); } /** * Return the mode of the array. Return the mode with the highest value in the event of a tie. * * NaN values are ignored. The mode may be NaN only if the array is zero length or contains only NaN. * * @param a * Array * @param ignoreBelowZero * Ignore all values less than or equal to zero. If no values are above zero the return is zero (not * NaN). * @return The mode */ public static float mode(float[] a, boolean ignoreBelowZero) { if (a == null || a.length == 0) return Float.NaN; return getMode(a, ignoreBelowZero); } /** * Return the mode of the array. Return the mode with the highest value in the event of a tie. * * NaN values are ignored. The mode may be NaN only if the array is zero length or contains only NaN. * * @param a * Array * @param ignoreBelowZero * Ignore all values less than or equal to zero. If no values are above zero the return is zero (not * NaN). * @return The mode */ private static float getMode(float[] a, boolean ignoreBelowZero) { // Assume array is not null or empty Arrays.sort(a); // NaN will be placed at the end final int length; if (Float.isNaN(a[a.length - 1])) { // Ignore NaN values int i = a.length; while (i-- > 0) { if (!Float.isNaN(a[i])) break; } length = i + 1; if (length == 0) return Float.NaN; } else { length = a.length; } int modeCount = 0; float mode = 0; int i = 0; if (ignoreBelowZero) { while (i < length && a[i] <= 0) i++; if (length == i) return 0; //Float.NaN; } int currentCount = 1; float currentValue = a[i]; while (++i < length) { if (a[i] != currentValue) { if (modeCount <= currentCount) { modeCount = currentCount; mode = currentValue; } currentCount = 1; } else { currentCount++; } currentValue = a[i]; } // Do the final check if (modeCount <= currentCount) { modeCount = currentCount; mode = currentValue; } return mode; } }