Ejemplo n.º 1
0
  public void run(String arg) {
    imp = IJ.getImage();
    int stackSize = imp.getStackSize();
    if (imp == null) {
      IJ.noImage();
      return;
    }

    //  Make sure input image is a stack.
    if (stackSize == 1) {
      IJ.error("Z Project", "Stack required");
      return;
    }

    //  Check for inverting LUT.
    if (imp.getProcessor().isInvertedLut()) {
      if (!IJ.showMessageWithCancel("ZProjection", lutMessage)) return;
    }

    // Set default bounds.
    int channels = imp.getNChannels();
    int frames = imp.getNFrames();
    int slices = imp.getNSlices();
    isHyperstack =
        imp.isHyperStack()
            || (ij.macro.Interpreter.isBatchMode()
                && ((frames > 1 && frames < stackSize) || (slices > 1 && slices < stackSize)));
    boolean simpleComposite = channels == stackSize;
    if (simpleComposite) isHyperstack = false;
    startSlice = 1;
    if (isHyperstack) {
      int nSlices = imp.getNSlices();
      if (nSlices > 1) stopSlice = nSlices;
      else stopSlice = imp.getNFrames();
    } else stopSlice = stackSize;

    // Build control dialog
    GenericDialog gd = buildControlDialog(startSlice, stopSlice);
    gd.showDialog();
    if (gd.wasCanceled()) return;

    if (!imp.lock()) return; // exit if in use
    long tstart = System.currentTimeMillis();
    setStartSlice((int) gd.getNextNumber());
    setStopSlice((int) gd.getNextNumber());
    method = gd.getNextChoiceIndex();
    Prefs.set(METHOD_KEY, method);
    if (isHyperstack) {
      allTimeFrames = imp.getNFrames() > 1 && imp.getNSlices() > 1 ? gd.getNextBoolean() : false;
      doHyperStackProjection(allTimeFrames);
    } else if (imp.getType() == ImagePlus.COLOR_RGB) doRGBProjection(true);
    else doProjection(true);

    if (arg.equals("") && projImage != null) {
      long tstop = System.currentTimeMillis();
      projImage.setCalibration(imp.getCalibration());
      if (simpleComposite) IJ.run(projImage, "Grays", "");
      projImage.show("ZProjector: " + IJ.d2s((tstop - tstart) / 1000.0, 2) + " seconds");
    }

    imp.unlock();
    IJ.register(ZProjector.class);
    return;
  }
Ejemplo n.º 2
0
/**
 * This plugin performs a z-projection of the input stack. Type of output image is same as type of
 * input image.
 *
 * @author Patrick Kelly <*****@*****.**>
 */
public class ZProjector implements PlugIn {
  public static final int AVG_METHOD = 0;
  public static final int MAX_METHOD = 1;
  public static final int MIN_METHOD = 2;
  public static final int SUM_METHOD = 3;
  public static final int SD_METHOD = 4;
  public static final int MEDIAN_METHOD = 5;
  public static final String[] METHODS = {
    "Average Intensity",
    "Max Intensity",
    "Min Intensity",
    "Sum Slices",
    "Standard Deviation",
    "Median"
  };
  private static final String METHOD_KEY = "zproject.method";
  private int method = (int) Prefs.get(METHOD_KEY, AVG_METHOD);

  private static final int BYTE_TYPE = 0;
  private static final int SHORT_TYPE = 1;
  private static final int FLOAT_TYPE = 2;

  public static final String lutMessage =
      "Stacks with inverter LUTs may not project correctly.\n"
          + "To create a standard LUT, invert the stack (Edit/Invert)\n"
          + "and invert the LUT (Image/Lookup Tables/Invert LUT).";

  /** Image to hold z-projection. */
  private ImagePlus projImage = null;

  /** Image stack to project. */
  private ImagePlus imp = null;

  /** Projection starts from this slice. */
  private int startSlice = 1;
  /** Projection ends at this slice. */
  private int stopSlice = 1;
  /** Project all time points? */
  private boolean allTimeFrames = true;

  private String color = "";
  private boolean isHyperstack;
  private int increment = 1;
  private int sliceCount;

  public ZProjector() {}

  /** Construction of ZProjector with image to be projected. */
  public ZProjector(ImagePlus imp) {
    setImage(imp);
  }

  /**
   * Explicitly set image to be projected. This is useful if ZProjection_ object is to be used not
   * as a plugin but as a stand alone processing object.
   */
  public void setImage(ImagePlus imp) {
    this.imp = imp;
    startSlice = 1;
    stopSlice = imp.getStackSize();
  }

  public void setStartSlice(int slice) {
    if (imp == null || slice < 1 || slice > imp.getStackSize()) return;
    startSlice = slice;
  }

  public void setStopSlice(int slice) {
    if (imp == null || slice < 1 || slice > imp.getStackSize()) return;
    stopSlice = slice;
  }

  public void setMethod(int projMethod) {
    method = projMethod;
  }

  /** Retrieve results of most recent projection operation. */
  public ImagePlus getProjection() {
    return projImage;
  }

  public void run(String arg) {
    imp = IJ.getImage();
    int stackSize = imp.getStackSize();
    if (imp == null) {
      IJ.noImage();
      return;
    }

    //  Make sure input image is a stack.
    if (stackSize == 1) {
      IJ.error("Z Project", "Stack required");
      return;
    }

    //  Check for inverting LUT.
    if (imp.getProcessor().isInvertedLut()) {
      if (!IJ.showMessageWithCancel("ZProjection", lutMessage)) return;
    }

    // Set default bounds.
    int channels = imp.getNChannels();
    int frames = imp.getNFrames();
    int slices = imp.getNSlices();
    isHyperstack =
        imp.isHyperStack()
            || (ij.macro.Interpreter.isBatchMode()
                && ((frames > 1 && frames < stackSize) || (slices > 1 && slices < stackSize)));
    boolean simpleComposite = channels == stackSize;
    if (simpleComposite) isHyperstack = false;
    startSlice = 1;
    if (isHyperstack) {
      int nSlices = imp.getNSlices();
      if (nSlices > 1) stopSlice = nSlices;
      else stopSlice = imp.getNFrames();
    } else stopSlice = stackSize;

    // Build control dialog
    GenericDialog gd = buildControlDialog(startSlice, stopSlice);
    gd.showDialog();
    if (gd.wasCanceled()) return;

    if (!imp.lock()) return; // exit if in use
    long tstart = System.currentTimeMillis();
    setStartSlice((int) gd.getNextNumber());
    setStopSlice((int) gd.getNextNumber());
    method = gd.getNextChoiceIndex();
    Prefs.set(METHOD_KEY, method);
    if (isHyperstack) {
      allTimeFrames = imp.getNFrames() > 1 && imp.getNSlices() > 1 ? gd.getNextBoolean() : false;
      doHyperStackProjection(allTimeFrames);
    } else if (imp.getType() == ImagePlus.COLOR_RGB) doRGBProjection(true);
    else doProjection(true);

    if (arg.equals("") && projImage != null) {
      long tstop = System.currentTimeMillis();
      projImage.setCalibration(imp.getCalibration());
      if (simpleComposite) IJ.run(projImage, "Grays", "");
      projImage.show("ZProjector: " + IJ.d2s((tstop - tstart) / 1000.0, 2) + " seconds");
    }

    imp.unlock();
    IJ.register(ZProjector.class);
    return;
  }

  public void doRGBProjection() {
    doRGBProjection(imp.getStack());
  }

  // Added by Marcel Boeglin 2013.09.23
  public void doRGBProjection(boolean handleOverlay) {
    doRGBProjection(imp.getStack());
    Overlay overlay = imp.getOverlay();
    if (handleOverlay && overlay != null) projImage.setOverlay(projectRGBHyperStackRois(overlay));
  }

  private void doRGBProjection(ImageStack stack) {
    ImageStack[] channels = ChannelSplitter.splitRGB(stack, true);
    ImagePlus red = new ImagePlus("Red", channels[0]);
    ImagePlus green = new ImagePlus("Green", channels[1]);
    ImagePlus blue = new ImagePlus("Blue", channels[2]);
    imp.unlock();
    ImagePlus saveImp = imp;
    imp = red;
    color = "(red)";
    doProjection();
    ImagePlus red2 = projImage;
    imp = green;
    color = "(green)";
    doProjection();
    ImagePlus green2 = projImage;
    imp = blue;
    color = "(blue)";
    doProjection();
    ImagePlus blue2 = projImage;
    int w = red2.getWidth(), h = red2.getHeight(), d = red2.getStackSize();
    if (method == SD_METHOD) {
      ImageProcessor r = red2.getProcessor();
      ImageProcessor g = green2.getProcessor();
      ImageProcessor b = blue2.getProcessor();
      double max = 0;
      double rmax = r.getStatistics().max;
      if (rmax > max) max = rmax;
      double gmax = g.getStatistics().max;
      if (gmax > max) max = gmax;
      double bmax = b.getStatistics().max;
      if (bmax > max) max = bmax;
      double scale = 255 / max;
      r.multiply(scale);
      g.multiply(scale);
      b.multiply(scale);
      red2.setProcessor(r.convertToByte(false));
      green2.setProcessor(g.convertToByte(false));
      blue2.setProcessor(b.convertToByte(false));
    }
    RGBStackMerge merge = new RGBStackMerge();
    ImageStack stack2 =
        merge.mergeStacks(w, h, d, red2.getStack(), green2.getStack(), blue2.getStack(), true);
    imp = saveImp;
    projImage = new ImagePlus(makeTitle(), stack2);
  }

  /**
   * Builds dialog to query users for projection parameters.
   *
   * @param start starting slice to display
   * @param stop last slice
   */
  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);
    return gd;
  }

  /** Performs actual projection using specified method. */
  public void doProjection() {
    if (imp == null) return;
    sliceCount = 0;
    if (method < AVG_METHOD || method > MEDIAN_METHOD) method = AVG_METHOD;
    for (int slice = startSlice; slice <= stopSlice; slice += increment) sliceCount++;
    if (method == MEDIAN_METHOD) {
      projImage = doMedianProjection();
      return;
    }

    // Create new float processor for projected pixels.
    FloatProcessor fp = new FloatProcessor(imp.getWidth(), imp.getHeight());
    ImageStack stack = imp.getStack();
    RayFunction rayFunc = getRayFunction(method, fp);
    if (IJ.debugMode == true) {
      IJ.log("\nProjecting stack from: " + startSlice + " to: " + stopSlice);
    }

    // Determine type of input image. Explicit determination of
    // processor type is required for subsequent pixel
    // manipulation.  This approach is more efficient than the
    // more general use of ImageProcessor's getPixelValue and
    // putPixel methods.
    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;
    }

    // Do the projection.
    for (int n = startSlice; n <= stopSlice; n += increment) {
      IJ.showStatus("ZProjection " + color + ": " + n + "/" + stopSlice);
      IJ.showProgress(n - startSlice, stopSlice - startSlice);
      projectSlice(stack.getPixels(n), rayFunc, ptype);
    }

    // Finish up projection.
    if (method == SUM_METHOD) {
      fp.resetMinAndMax();
      projImage = new ImagePlus(makeTitle(), fp);
    } else if (method == SD_METHOD) {
      rayFunc.postProcess();
      fp.resetMinAndMax();
      projImage = new ImagePlus(makeTitle(), fp);
    } else {
      rayFunc.postProcess();
      projImage = makeOutputImage(imp, fp, ptype);
    }

    if (projImage == null) IJ.error("Z Project", "Error computing projection.");
  }

  // Added by Marcel Boeglin 2013.09.23
  /**
   * Performs actual projection using specified method. If handleOverlay, adds stack overlay
   * elements from startSlice to stopSlice to projection
   */
  public void doProjection(boolean handleOverlay) {
    doProjection();
    Overlay overlay = imp.getOverlay();
    if (handleOverlay && overlay != null) projImage.setOverlay(projectStackRois(overlay));
  }

  // Added by Marcel Boeglin 2013.09.23
  private Overlay projectStackRois(Overlay overlay) {
    if (overlay == null) return null;
    Overlay overlay2 = new Overlay();
    Roi roi;
    int s;
    for (Roi r : overlay.toArray()) {
      s = r.getPosition();
      roi = (Roi) r.clone();
      if (s >= startSlice && s <= stopSlice || s == 0) {
        roi.setPosition(s);
        overlay2.add(roi);
      }
    }
    return overlay2;
  }

  public void doHyperStackProjection(boolean allTimeFrames) {
    int start = startSlice;
    int stop = stopSlice;
    int firstFrame = 1;
    int lastFrame = imp.getNFrames();
    if (!allTimeFrames) firstFrame = lastFrame = imp.getFrame();
    ImageStack stack = new ImageStack(imp.getWidth(), imp.getHeight());
    int channels = imp.getNChannels();
    int slices = imp.getNSlices();
    if (slices == 1) {
      slices = imp.getNFrames();
      firstFrame = lastFrame = 1;
    }
    int frames = lastFrame - firstFrame + 1;
    increment = channels;
    boolean rgb = imp.getBitDepth() == 24;
    for (int frame = firstFrame; frame <= lastFrame; frame++) {
      for (int channel = 1; channel <= channels; channel++) {
        startSlice = (frame - 1) * channels * slices + (start - 1) * channels + channel;
        stopSlice = (frame - 1) * channels * slices + (stop - 1) * channels + channel;
        if (rgb) doHSRGBProjection(imp);
        else doProjection();
        stack.addSlice(null, projImage.getProcessor());
      }
    }
    projImage = new ImagePlus(makeTitle(), stack);
    projImage.setDimensions(channels, 1, frames);
    if (channels > 1) {
      projImage = new CompositeImage(projImage, 0);
      ((CompositeImage) projImage).copyLuts(imp);
      if (method == SUM_METHOD || method == SD_METHOD)
        ((CompositeImage) projImage).resetDisplayRanges();
    }
    if (frames > 1) projImage.setOpenAsHyperStack(true);
    Overlay overlay = imp.getOverlay();
    if (overlay != null) {
      startSlice = start;
      stopSlice = stop;
      if (imp.getType() == ImagePlus.COLOR_RGB)
        projImage.setOverlay(projectRGBHyperStackRois(overlay));
      else projImage.setOverlay(projectHyperStackRois(overlay));
    }
    IJ.showProgress(1, 1);
  }

  // Added by Marcel Boeglin 2013.09.22
  private Overlay projectRGBHyperStackRois(Overlay overlay) {
    if (overlay == null) return null;
    int frames = projImage.getNFrames();
    int t1 = imp.getFrame();
    Overlay overlay2 = new Overlay();
    Roi roi;
    int c, z, t;
    for (Roi r : overlay.toArray()) {
      c = r.getCPosition();
      z = r.getZPosition();
      t = r.getTPosition();
      roi = (Roi) r.clone();
      if (z >= startSlice && z <= stopSlice || z == 0 || c == 0 || t == 0) {
        if (frames == 1 && t != t1 && t != 0) // current time frame
        continue;
        roi.setPosition(t);
        overlay2.add(roi);
      }
    }
    return overlay2;
  }

  // Added by Marcel Boeglin 2013.09.22
  private Overlay projectHyperStackRois(Overlay overlay) {
    if (overlay == null) return null;
    int t1 = imp.getFrame();
    int channels = projImage.getNChannels();
    int slices = 1;
    int frames = projImage.getNFrames();
    Overlay overlay2 = new Overlay();
    Roi roi;
    int c, z, t;
    int size = channels * slices * frames;
    for (Roi r : overlay.toArray()) {
      c = r.getCPosition();
      z = r.getZPosition();
      t = r.getTPosition();
      roi = (Roi) r.clone();
      if (size == channels) { // current time frame
        if (z >= startSlice && z <= stopSlice && t == t1 || c == 0) {
          roi.setPosition(c);
          overlay2.add(roi);
        }
      } else if (size == frames * channels) { // all time frames
        if (z >= startSlice && z <= stopSlice) roi.setPosition(c, 1, t);
        else if (z == 0) roi.setPosition(c, 0, t);
        else continue;
        overlay2.add(roi);
      }
    }
    return overlay2;
  }

  private void doHSRGBProjection(ImagePlus rgbImp) {
    ImageStack stack = rgbImp.getStack();
    ImageStack stack2 = new ImageStack(stack.getWidth(), stack.getHeight());
    for (int i = startSlice; i <= stopSlice; i++) stack2.addSlice(null, stack.getProcessor(i));
    startSlice = 1;
    stopSlice = stack2.getSize();
    doRGBProjection(stack2);
  }

  private RayFunction getRayFunction(int method, FloatProcessor fp) {
    switch (method) {
      case AVG_METHOD:
      case SUM_METHOD:
        return new AverageIntensity(fp, sliceCount);
      case MAX_METHOD:
        return new MaxIntensity(fp);
      case MIN_METHOD:
        return new MinIntensity(fp);
      case SD_METHOD:
        return new StandardDeviation(fp, sliceCount);
      default:
        IJ.error("Z Project", "Unknown method.");
        return null;
    }
  }

  /** Generate output image whose type is same as input image. */
  private ImagePlus makeOutputImage(ImagePlus imp, FloatProcessor fp, int ptype) {
    int width = imp.getWidth();
    int height = imp.getHeight();
    float[] pixels = (float[]) fp.getPixels();
    ImageProcessor oip = null;

    // Create output image consistent w/ type of input image.
    int size = pixels.length;
    switch (ptype) {
      case BYTE_TYPE:
        oip = imp.getProcessor().createProcessor(width, height);
        byte[] pixels8 = (byte[]) oip.getPixels();
        for (int i = 0; i < size; i++) pixels8[i] = (byte) pixels[i];
        break;
      case SHORT_TYPE:
        oip = imp.getProcessor().createProcessor(width, height);
        short[] pixels16 = (short[]) oip.getPixels();
        for (int i = 0; i < size; i++) pixels16[i] = (short) pixels[i];
        break;
      case FLOAT_TYPE:
        oip = new FloatProcessor(width, height, pixels, null);
        break;
    }

    // Adjust for display.
    // Calling this on non-ByteProcessors ensures image
    // processor is set up to correctly display image.
    oip.resetMinAndMax();

    // Create new image plus object. Don't use
    // ImagePlus.createImagePlus here because there may be
    // attributes of input image that are not appropriate for
    // projection.
    return new ImagePlus(makeTitle(), oip);
  }

  /**
   * Handles mechanics of projection by selecting appropriate pixel array type. We do this rather
   * than using more general ImageProcessor getPixelValue() and putPixel() methods because direct
   * manipulation of pixel arrays is much more efficient.
   */
  private void projectSlice(Object pixelArray, RayFunction rayFunc, int ptype) {
    switch (ptype) {
      case BYTE_TYPE:
        rayFunc.projectSlice((byte[]) pixelArray);
        break;
      case SHORT_TYPE:
        rayFunc.projectSlice((short[]) pixelArray);
        break;
      case FLOAT_TYPE:
        rayFunc.projectSlice((float[]) pixelArray);
        break;
    }
  }

  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;
    }
    return WindowManager.makeUniqueName(prefix + imp.getTitle());
  }

  ImagePlus doMedianProjection() {
    IJ.showStatus("Calculating median...");
    ImageStack stack = imp.getStack();
    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; y < height; y++) {
      if (y % inc == 0) IJ.showProgress(y, height - 1);
      for (int x = 0; x < width; x++) {
        for (int i = 0; i < sliceCount; i++) values[i] = slices[i].getPixelValue(x, y);
        ip2.putPixelValue(x, y, median(values));
      }
    }
    if (imp.getBitDepth() == 8) ip2 = ip2.convertToByte(false);
    IJ.showProgress(1, 1);
    return new ImagePlus(makeTitle(), ip2);
  }

  float median(float[] a) {
    Arrays.sort(a);
    int middle = a.length / 2;
    if ((a.length & 1) == 0) // even
    return (a[middle - 1] + a[middle]) / 2f;
    else return a[middle];
  }

  /**
   * Abstract class that specifies structure of ray function. Preprocessing should be done in
   * derived class constructors.
   */
  abstract class RayFunction {
    /** Do actual slice projection for specific data types. */
    public abstract void projectSlice(byte[] pixels);

    public abstract void projectSlice(short[] pixels);

    public abstract void projectSlice(float[] pixels);

    /** Perform any necessary post processing operations, e.g. averging values. */
    public void postProcess() {}
  } // end RayFunction

  /** Compute average intensity projection. */
  class AverageIntensity extends RayFunction {
    private float[] fpixels;
    private int num, len;

    /**
     * Constructor requires number of slices to be projected. This is used to determine average at
     * each pixel.
     */
    public AverageIntensity(FloatProcessor fp, int num) {
      fpixels = (float[]) fp.getPixels();
      len = fpixels.length;
      this.num = num;
    }

    public void projectSlice(byte[] pixels) {
      for (int i = 0; i < len; i++) fpixels[i] += (pixels[i] & 0xff);
    }

    public void projectSlice(short[] pixels) {
      for (int i = 0; i < len; i++) fpixels[i] += pixels[i] & 0xffff;
    }

    public void projectSlice(float[] pixels) {
      for (int i = 0; i < len; i++) fpixels[i] += pixels[i];
    }

    public void postProcess() {
      float fnum = num;
      for (int i = 0; i < len; i++) fpixels[i] /= fnum;
    }
  } // end AverageIntensity

  /** Compute max intensity projection. */
  class MaxIntensity extends RayFunction {
    private float[] fpixels;
    private int len;

    /** Simple constructor since no preprocessing is necessary. */
    public MaxIntensity(FloatProcessor fp) {
      fpixels = (float[]) fp.getPixels();
      len = fpixels.length;
      for (int i = 0; i < len; i++) fpixels[i] = -Float.MAX_VALUE;
    }

    public void projectSlice(byte[] pixels) {
      for (int i = 0; i < len; i++) {
        if ((pixels[i] & 0xff) > fpixels[i]) fpixels[i] = (pixels[i] & 0xff);
      }
    }

    public void projectSlice(short[] pixels) {
      for (int i = 0; i < len; i++) {
        if ((pixels[i] & 0xffff) > fpixels[i]) fpixels[i] = pixels[i] & 0xffff;
      }
    }

    public void projectSlice(float[] pixels) {
      for (int i = 0; i < len; i++) {
        if (pixels[i] > fpixels[i]) fpixels[i] = pixels[i];
      }
    }
  } // end MaxIntensity

  /** Compute min intensity projection. */
  class MinIntensity extends RayFunction {
    private float[] fpixels;
    private int len;

    /** Simple constructor since no preprocessing is necessary. */
    public MinIntensity(FloatProcessor fp) {
      fpixels = (float[]) fp.getPixels();
      len = fpixels.length;
      for (int i = 0; i < len; i++) fpixels[i] = Float.MAX_VALUE;
    }

    public void projectSlice(byte[] pixels) {
      for (int i = 0; i < len; i++) {
        if ((pixels[i] & 0xff) < fpixels[i]) fpixels[i] = (pixels[i] & 0xff);
      }
    }

    public void projectSlice(short[] pixels) {
      for (int i = 0; i < len; i++) {
        if ((pixels[i] & 0xffff) < fpixels[i]) fpixels[i] = pixels[i] & 0xffff;
      }
    }

    public void projectSlice(float[] pixels) {
      for (int i = 0; i < len; i++) {
        if (pixels[i] < fpixels[i]) fpixels[i] = pixels[i];
      }
    }
  } // end MaxIntensity

  /** Compute standard deviation projection. */
  class StandardDeviation extends RayFunction {
    private float[] result;
    private double[] sum, sum2;
    private int num, len;

    public StandardDeviation(FloatProcessor fp, int num) {
      result = (float[]) fp.getPixels();
      len = result.length;
      this.num = num;
      sum = new double[len];
      sum2 = new double[len];
    }

    public void projectSlice(byte[] pixels) {
      int v;
      for (int i = 0; i < len; i++) {
        v = pixels[i] & 0xff;
        sum[i] += v;
        sum2[i] += v * v;
      }
    }

    public void projectSlice(short[] pixels) {
      double v;
      for (int i = 0; i < len; i++) {
        v = pixels[i] & 0xffff;
        sum[i] += v;
        sum2[i] += v * v;
      }
    }

    public void projectSlice(float[] pixels) {
      double v;
      for (int i = 0; i < len; i++) {
        v = pixels[i];
        sum[i] += v;
        sum2[i] += v * v;
      }
    }

    public void postProcess() {
      double stdDev;
      double n = num;
      for (int i = 0; i < len; i++) {
        if (num > 1) {
          stdDev = (n * sum2[i] - sum[i] * sum[i]) / n;
          if (stdDev > 0.0) result[i] = (float) Math.sqrt(stdDev / (n - 1.0));
          else result[i] = 0f;
        } else result[i] = 0f;
      }
    }
  } // end StandardDeviation
} // end ZProjection