package ini.trakem2.imaging; import ij.ImagePlus; import ij.ImageStack; import ij.measure.Calibration; import ij.process.ImageProcessor; import ini.trakem2.display.Layer; import ini.trakem2.display.LayerSet; import ini.trakem2.display.Patch; import ini.trakem2.persistence.Loader; import ini.trakem2.utils.Utils; import java.awt.Rectangle; import java.util.HashMap; import java.util.List; /** This class represents an entire LayerSet of Patch objects only, as it is presented read-only to ImageJ. */ public class LayerStack extends ImageStack { final private List<Layer> layers; final private int type; /** The virtualization scale. */ final private double scale; /** The class of the objects included. */ final private Class<?> clazz; final private int c_alphas; final private boolean invert; /** In world coordinates. */ final Rectangle roi; final HashMap<Long,Long> id_cache = new HashMap<Long, Long>(); private ImagePlus layer_imp = null; public LayerStack(final LayerSet layer_set, final double scale, final int type, final Class<?> clazz, final int c_alphas, final boolean invert) { this(layer_set.getLayers(), layer_set.get2DBounds(), scale, type, clazz, c_alphas, invert); } /** If {@code scale <=0 || scale > 1}, throws {@link IllegalArgumentException}. */ public LayerStack(final List<Layer> layers, final Rectangle roi, final double scale, final int type, final Class<?> clazz, final int c_alphas, final boolean invert) { super((int)(roi.width * scale), (int)(roi.height * scale), Patch.DCM); if (scale <= 0 || scale > 1) throw new IllegalArgumentException("Cannot operate with a scale larger than 1 or smaller or equal to 0!"); this.layers = layers; this.type = type; this.scale = scale; this.clazz = clazz; this.c_alphas = c_alphas; this.invert = invert; this.roi = new Rectangle(roi); } public LayerStack(final LayerSet layer_set, final double scale, final int type, final Class<?> clazz, final int c_alphas) { this(layer_set, scale, type, clazz, c_alphas, false); } /** Does nothing. */ public void addSlice(String sliceLabel, Object pixels) { Utils.log("LayerStack: cannot add slices."); } /** Does nothing. */ @Override public void addSlice(String sliceLabel, ImageProcessor ip) { Utils.log("LayerStack: cannot add slices."); } /** Does nothing. */ @Override public void addSlice(String sliceLabel, ImageProcessor ip, int n) { Utils.log("LayerStack: cannot add slices."); } /** Does nothing. */ @Override public void deleteSlice(int n) { Utils.log("LayerStack: cannot delete slices."); } /** Does nothing. */ @Override public void deleteLastSlice() { Utils.log("LayerStack: cannot delete slices."); } /** Returns the pixel array for the specified slice, where {@code 1<=n<=nslices}. The scale of the returned flat image for the Layer at index 'n-1' will be defined by the LayerSet virtualization options.*/ @Override public Object getPixels(final int n) { if (n < 1 || n > layers.size()) return null; return getProcessor(n).getPixels(); } /** Does nothing. */ @Override public void setPixels(Object pixels, int n) { Utils.log("LayerStack: cannot set pixels."); } /** Returns an ImageProcessor for the specified slice, where {@code 1<=n<=nslices}. Returns null if the stack is empty. */ @Override public ImageProcessor getProcessor(int n) { if (n < 1 || n > layers.size()) return null; // Create a flat image on the fly with everything on it, and return its processor. final Layer layer = layers.get(n-1); final Loader loader = layer.getProject().getLoader(); Long cid; synchronized (id_cache) { cid = id_cache.get(layer.getId()); if (null == cid) { cid = loader.getNextTempId(); id_cache.put(layer.getId(), cid); } } ImageProcessor ip; synchronized (cid) { ImagePlus imp = loader.getCachedImagePlus(cid); if (null == imp || null == imp.getProcessor() || null == imp.getProcessor().getPixels()) { ip = loader.getFlatImage(layer, this.roi, this.scale, this.c_alphas, this.type, this.clazz, null).getProcessor(); if (invert) ip.invert(); loader.cacheImagePlus(cid, new ImagePlus("", ip)); } else ip = imp.getProcessor(); } return ip; } /** Returns the number of slices in this stack. */ @Override public int getSize() { return layers.size(); } /** Returns the file name of the Nth image. */ @Override public String getSliceLabel(int n) { if (n < 1 || n > layers.size()) return null; return layers.get(n-1).getTitle(); } /** Returns a linear array for each slice, real (not virtual)! */ @Override public Object[] getImageArray() { // Release 3 times an RGB stack with this dimensions. layers.get(0).getProject().getLoader().releaseToFit((long)(getSize() * getWidth() * getHeight() * 4 * 3)); final Object[] ia = new Object[getSize()]; for (int i=0; i<ia.length; i++) { ia[i] = getProcessor(i+1).getPixels(); // slices 1<=slice<=n_slices } return ia; } /** Does nothing. */ @Override public void setSliceLabel(String label, int n) { Utils.log("LayerStack: cannot set the slice label."); } /** Always return true. */ @Override public boolean isVirtual() { return true; } /** Override: always false. */ @Override public boolean isHSB() { return false; } /** Override: always false. */ @Override public boolean isRGB() { return false; } /** Does nothing. */ @Override public void trim() {} public int getType() { return type; } synchronized public ImagePlus getImagePlus() { if (null == layer_imp) { layer_imp = new ImagePlus("LayerSet Stack", this); Calibration cal = layers.get(0).getParent().getCalibrationCopy(); // adjust to scale cal.pixelWidth /= scale; cal.pixelHeight /= scale; // Simulate depth: assume all layers have the same thickness cal.pixelDepth = (layers.get(0).getThickness() * cal.pixelWidth) / scale; // not pixelDepth layer_imp.setCalibration(cal); } return layer_imp; } }