/* * Copyright 2010-2015 Institut Pasteur. * * This file is part of Icy. * * Icy 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. * * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>. */ package plugins.kernel.roi.roi3d; import icy.canvas.IcyCanvas; import icy.painter.OverlayEvent; import icy.painter.OverlayListener; import icy.roi.BooleanMask2D; import icy.roi.ROI; import icy.roi.ROI2D; import icy.roi.ROI2D.ROI2DPainter; import icy.roi.ROI3D; import icy.roi.ROIEvent; import icy.roi.ROIListener; import icy.sequence.Sequence; import icy.system.IcyExceptionHandler; import icy.type.point.Point5D; import icy.type.rectangle.Rectangle3D; import icy.util.XMLUtil; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.geom.Rectangle2D; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.Semaphore; import org.w3c.dom.Element; import org.w3c.dom.Node; /** * Base class defining a generic 3D ROI as a stack of individual 2D ROI slices. * * @author Alexandre Dufour * @author Stephane Dallongeville * @param <R> * the type of 2D ROI for each slice of this 3D ROI */ public class ROI3DStack<R extends ROI2D> extends ROI3D implements ROIListener, OverlayListener, Iterable<R> { /** * @deprecated this property does not exist anymore */ @Deprecated public static final String PROPERTY_USECHILDCOLOR = "useChildColor"; protected final TreeMap<Integer, R> slices = new TreeMap<Integer, R>(); protected final Class<? extends R> roiClass; protected Semaphore modifyingSlice; protected double translateZ; /** * Creates a new 3D ROI based on the given 2D ROI type. */ public ROI3DStack(Class<? extends R> roiClass) { super(); this.roiClass = roiClass; modifyingSlice = new Semaphore(1); translateZ = 0d; } @Override public String getDefaultName() { return "ROI2D stack"; } @Override protected ROIPainter createPainter() { return new ROI3DStackPainter(); } /** * Create a new empty 2D ROI slice. */ protected R createSlice() { try { return roiClass.newInstance(); } catch (Exception e) { IcyExceptionHandler.showErrorMessage(e, true, true); return null; } } /** * Returns <code>true</code> if the ROI directly uses the 2D slice color draw property and <code>false</code> if it * uses the global 3D ROI color draw property. */ @SuppressWarnings("unchecked") public boolean getUseChildColor() { return ((ROI3DStackPainter) getOverlay()).getUseChildColor(); } /** * Set to <code>true</code> if you want to directly use the 2D slice color draw property and <code>false</code> to * keep the global 3D ROI color draw property. * * @see #setColor(int, Color) */ @SuppressWarnings("unchecked") public void setUseChildColor(boolean value) { ((ROI3DStackPainter) getOverlay()).setUseChildColor(value); } /** * Set the painter color for the specified ROI slice. * * @see #setUseChildColor(boolean) */ @SuppressWarnings("unchecked") public void setColor(int z, Color value) { ((ROI3DStackPainter) getOverlay()).setColor(z, value); } @Override public void setCreating(boolean value) { beginUpdate(); try { super.setCreating(value); modifyingSlice.acquireUninterruptibly(); try { for (R slice : slices.values()) slice.setCreating(value); } finally { modifyingSlice.release(); } } finally { endUpdate(); } } @Override public void setReadOnly(boolean value) { beginUpdate(); try { super.setReadOnly(value); modifyingSlice.acquireUninterruptibly(); try { for (R slice : slices.values()) slice.setReadOnly(value); } finally { modifyingSlice.release(); } } finally { endUpdate(); } } @Override public void setFocused(boolean value) { beginUpdate(); try { super.setFocused(value); modifyingSlice.acquireUninterruptibly(); try { for (R slice : slices.values()) slice.setFocused(value); } finally { modifyingSlice.release(); } } finally { endUpdate(); } } @Override public void setSelected(boolean value) { beginUpdate(); try { super.setSelected(value); modifyingSlice.acquireUninterruptibly(); try { for (R slice : slices.values()) slice.setSelected(value); } finally { modifyingSlice.release(); } } finally { endUpdate(); } } @Override public void setName(String value) { beginUpdate(); try { super.setName(value); modifyingSlice.acquireUninterruptibly(); try { for (R slice : slices.values()) slice.setName(value); } finally { modifyingSlice.release(); } } finally { endUpdate(); } } @Override public void setT(int value) { beginUpdate(); try { super.setT(value); modifyingSlice.acquireUninterruptibly(); try { for (R slice : slices.values()) slice.setT(value); } finally { modifyingSlice.release(); } } finally { endUpdate(); } } @Override public void setC(int value) { beginUpdate(); try { super.setC(value); modifyingSlice.acquireUninterruptibly(); try { for (R slice : slices.values()) slice.setC(value); } finally { modifyingSlice.release(); } } finally { endUpdate(); } } /** * Returns <code>true</code> if the ROI stack is empty. */ @Override public boolean isEmpty() { return slices.isEmpty(); } /** * @return The size of this ROI stack along Z.<br> * Note that the returned value indicates the difference between upper and lower bounds * of this ROI, but doesn't guarantee that all slices in-between exist ( {@link #getSlice(int)} may still * return <code>null</code>.<br> */ public int getSizeZ() { if (slices.isEmpty()) return 0; return (slices.lastKey().intValue() - slices.firstKey().intValue()) + 1; } /** * Returns the ROI slice at given Z position. */ public R getSlice(int z) { return slices.get(Integer.valueOf(z)); } /** * Returns the ROI slice at given Z position. */ public R getSlice(int z, boolean createIfNull) { R result = getSlice(z); if ((result == null) && createIfNull) { result = createSlice(); if (result != null) setSlice(z, result); } return result; } /** * Sets the ROI slice for the given Z position. */ public void setSlice(int z, R roi2d) { // nothing to do if (getSlice(z) == roi2d) return; // remove previous removeSlice(z); if (roi2d != null) { // set Z, T and C position roi2d.setZ(z); roi2d.setT(getT()); roi2d.setC(getC()); // listen events from this ROI and its overlay roi2d.addListener(this); roi2d.getOverlay().addOverlayListener(this); // set new slice slices.put(Integer.valueOf(z), roi2d); } // notify ROI changed roiChanged(true); } /** * Removes slice at the given Z position and returns it. */ public R removeSlice(int z) { // remove the current slice (if any) final R result = slices.remove(Integer.valueOf(z)); // remove listeners if (result != null) { result.removeListener(this); result.getOverlay().removeOverlayListener(this); // notify ROI changed roiChanged(true); } return result; } /** * Removes all slices. */ public void clear() { // nothing to do if (isEmpty()) return; for (R slice : slices.values()) { slice.removeListener(this); slice.getOverlay().removeOverlayListener(this); } slices.clear(); roiChanged(true); } /** * Add the specified {@link ROI3DStack} content to this ROI3DStack */ public void add(ROI3DStack<R> roi) throws UnsupportedOperationException { beginUpdate(); try { for (Entry<Integer, R> entry : roi.slices.entrySet()) add(entry.getKey().intValue(), entry.getValue()); } finally { endUpdate(); } } /** * Exclusively add the specified {@link ROI3DStack} content to this ROI3DStack */ public void exclusiveAdd(ROI3DStack<R> roi) throws UnsupportedOperationException { beginUpdate(); try { for (Entry<Integer, R> entry : roi.slices.entrySet()) exclusiveAdd(entry.getKey().intValue(), entry.getValue()); } finally { endUpdate(); } } /** * Process intersection of the specified {@link ROI3DStack} with this ROI3DStack. */ public void intersect(ROI3DStack<R> roi) throws UnsupportedOperationException { beginUpdate(); try { final Set<Integer> keys = roi.slices.keySet(); final Set<Integer> toRemove = new HashSet<Integer>(); // remove slices which are not contained for (Integer key : slices.keySet()) if (!keys.contains(key)) toRemove.add(key); // do remove first for (Integer key : toRemove) removeSlice(key.intValue()); // then process intersection for (Entry<Integer, R> entry : roi.slices.entrySet()) intersect(entry.getKey().intValue(), entry.getValue()); } finally { endUpdate(); } } /** * Remove the specified {@link ROI3DStack} from this ROI3DStack */ public void subtract(ROI3DStack<R> roi) throws UnsupportedOperationException { beginUpdate(); try { for (Entry<Integer, R> entry : roi.slices.entrySet()) subtract(entry.getKey().intValue(), entry.getValue()); } finally { endUpdate(); } } @Override public ROI add(ROI roi, boolean allowCreate) throws UnsupportedOperationException { if (roi instanceof ROI3D) { final ROI3D roi3d = (ROI3D) roi; // only if on same position if ((getT() == roi3d.getT()) && (getC() == roi3d.getC())) { if (this.getClass().isInstance(roi3d)) { add((ROI3DStack) roi3d); return this; } } } else if (roiClass.isInstance(roi)) { final ROI2D roi2d = (ROI2D) roi; // only if on same position if ((roi2d.getZ() != -1) && (getT() == roi2d.getT()) && (getC() == roi2d.getC())) { try { add(roi2d.getZ(), (R) roi2d); return this; } catch (UnsupportedOperationException e) { // not supported, try generic method instead return super.add(roi, allowCreate); } } } return super.add(roi, allowCreate); } @Override public ROI intersect(ROI roi, boolean allowCreate) throws UnsupportedOperationException { if (roi instanceof ROI3D) { final ROI3D roi3d = (ROI3D) roi; // only if on same position if ((getT() == roi3d.getT()) && (getC() == roi3d.getC())) { if (this.getClass().isInstance(roi3d)) { intersect((ROI3DStack) roi3d); return this; } } else if (roiClass.isInstance(roi)) { final ROI2D roi2d = (ROI2D) roi; // only if on same position if ((roi2d.getZ() != -1) && (getT() == roi2d.getT()) && (getC() == roi2d.getC())) { try { intersect(roi2d.getZ(), (R) roi2d); return this; } catch (UnsupportedOperationException e) { // not supported, try generic method instead return super.intersect(roi, allowCreate); } } } } return super.intersect(roi, allowCreate); } @Override public ROI exclusiveAdd(ROI roi, boolean allowCreate) throws UnsupportedOperationException { if (roi instanceof ROI3D) { final ROI3D roi3d = (ROI3D) roi; // only if on same position if ((getT() == roi3d.getT()) && (getC() == roi3d.getC())) { if (this.getClass().isInstance(roi3d)) { exclusiveAdd((ROI3DStack) roi3d); return this; } } else if (roiClass.isInstance(roi)) { final ROI2D roi2d = (ROI2D) roi; // only if on same position if ((roi2d.getZ() != -1) && (getT() == roi2d.getT()) && (getC() == roi2d.getC())) { try { exclusiveAdd(roi2d.getZ(), (R) roi2d); return this; } catch (UnsupportedOperationException e) { // not supported, try generic method instead return super.add(roi, allowCreate); } } } } return super.add(roi, allowCreate); } @Override public ROI subtract(ROI roi, boolean allowCreate) throws UnsupportedOperationException { if (roi instanceof ROI3D) { final ROI3D roi3d = (ROI3D) roi; // only if on same position if ((getT() == roi3d.getT()) && (getC() == roi3d.getC())) { if (this.getClass().isInstance(roi3d)) { subtract((ROI3DStack<R>) roi3d); return this; } } else if (roiClass.isInstance(roi)) { final ROI2D roi2d = (ROI2D) roi; // only if on same position if ((roi2d.getZ() != -1) && (getT() == roi2d.getT()) && (getC() == roi2d.getC())) { try { subtract(roi2d.getZ(), (R) roi2d); return this; } catch (UnsupportedOperationException e) { // not supported, try generic method instead return super.subtract(roi, allowCreate); } } } } return super.subtract(roi, allowCreate); } /** * Adds content of specified <code>ROI</code> slice into the <code>ROI</code> slice at given Z position. * The resulting content of this <code>ROI</code> will include the union of both ROI's contents.<br> * If no slice was present at the specified Z position then the method is equivalent to * {@link #setSlice(int, ROI2D)} * * @param z * the position where the slice must be merged * @param roiSlice * the 2D ROI to merge * @throws UnsupportedOperationException * if the given ROI slice cannot be added to this ROI */ public void add(int z, R roiSlice) { if (roiSlice == null) return; final R currentSlice = getSlice(z); final ROI newSlice; // merge both slice if (currentSlice != null) { // we need to modify the Z, T and C position so we do the merge correctly roiSlice.setZ(z); roiSlice.setT(getT()); roiSlice.setC(getC()); // do ROI union newSlice = currentSlice.add(roiSlice, true); // check the resulting ROI is the same type if (!newSlice.getClass().isInstance(currentSlice)) throw new UnsupportedOperationException("Can't add the result of the merge operation on 2D slice " + z + ": " + newSlice.getClassName()); } else // get a copy newSlice = roiSlice.getCopy(); // set slice setSlice(z, (R) newSlice); } /** * Sets the content of the <code>ROI</code> slice at given Z position to be the union of its current content and the * content of the specified <code>ROI</code>, minus their intersection. * The resulting <code>ROI</code> will include only content that were contained in either this <code>ROI</code> or * in the specified <code>ROI</code>, but not in both.<br> * If no slice was present at the specified Z position then the method is equivalent to * {@link #setSlice(int, ROI2D)} * * @param z * the position where the slice must be merged * @param roiSlice * the 2D ROI to merge * @throws UnsupportedOperationException * if the given ROI slice cannot be exclusively added to this ROI */ public void exclusiveAdd(int z, R roiSlice) { if (roiSlice == null) return; final R currentSlice = getSlice(z); final ROI newSlice; // merge both slice if (currentSlice != null) { // we need to modify the Z, T and C position so we do the merge correctly roiSlice.setZ(z); roiSlice.setT(getT()); roiSlice.setC(getC()); // do ROI exclusive union newSlice = currentSlice.exclusiveAdd(roiSlice, true); // check the resulting ROI is same type if (!newSlice.getClass().isInstance(currentSlice)) throw new UnsupportedOperationException("Can't add the result of the merge operation on 2D slice " + z + ": " + newSlice.getClassName()); } else // get a copy newSlice = roiSlice.getCopy(); if (newSlice.isEmpty()) removeSlice(z); else setSlice(z, (R) newSlice); } /** * Sets the content of the <code>ROI</code> slice at given Z position to the intersection of * its current content and the content of the specified <code>ROI</code>. * The resulting ROI will include only contents that were contained in both ROI.<br> * If no slice was present at the specified Z position then the method does nothing. * * @param z * the position where the slice must be merged * @param roiSlice * the 2D ROI to merge * @throws UnsupportedOperationException * if the given ROI slice cannot be intersected with this ROI */ public void intersect(int z, R roiSlice) { // better to throw an exception here than removing slice if (roiSlice == null) throw new IllegalArgumentException("Cannot intersect an empty slice in a 3D ROI"); final R currentSlice = getSlice(z); // merge both slice if (currentSlice != null) { // we need to modify the Z, T and C position so we do the merge correctly roiSlice.setZ(z); roiSlice.setT(getT()); roiSlice.setC(getC()); // do ROI intersection final ROI newSlice = currentSlice.intersect(roiSlice, true); // check the resulting ROI is same type if (!newSlice.getClass().isInstance(currentSlice)) throw new UnsupportedOperationException("Can't add the result of the merge operation on 2D slice " + z + ": " + newSlice.getClassName()); if (newSlice.isEmpty()) removeSlice(z); else setSlice(z, (R) newSlice); } } /** * Subtract the specified <code>ROI</code> content from the <code>ROI</code> slice at given Z position.<br> * If no slice was present at the specified Z position then the method does nothing. * * @param z * the position where the subtraction should be done * @param roiSlice * the 2D ROI to subtract from Z slice * @throws UnsupportedOperationException * if the given ROI slice cannot be subtracted from this ROI */ public void subtract(int z, R roiSlice) throws UnsupportedOperationException { if (roiSlice == null) return; final R currentSlice = getSlice(z); // merge both slice if (currentSlice != null) { // we need to modify the Z, T and C position so we do the merge correctly roiSlice.setZ(z); roiSlice.setT(getT()); roiSlice.setC(getC()); // do ROI subtraction final ROI newSlice = currentSlice.subtract(roiSlice, true); // check the resulting ROI is same type if (!newSlice.getClass().isInstance(currentSlice)) throw new UnsupportedOperationException("Can't add the result of the merge operation on 2D slice " + z + ": " + newSlice.getClassName()); if (newSlice.isEmpty()) removeSlice(z); else setSlice(z, (R) newSlice); } } /** * Called when a ROI slice has changed. */ protected void sliceChanged(ROIEvent event) { if (modifyingSlice.availablePermits() <= 0) return; final ROI source = event.getSource(); switch (event.getType()) { case ROI_CHANGED: // position change of a slice can change global bounds --> transform to 'content changed' event type roiChanged(true); // roiChanged(StringUtil.equals(event.getPropertyName(), ROI_CHANGED_ALL)); break; case FOCUS_CHANGED: setFocused(source.isFocused()); break; case SELECTION_CHANGED: setSelected(source.isSelected()); break; case PROPERTY_CHANGED: final String propertyName = event.getPropertyName(); if ((propertyName == null) || propertyName.equals(PROPERTY_READONLY)) setReadOnly(source.isReadOnly()); if ((propertyName == null) || propertyName.equals(PROPERTY_CREATING)) setCreating(source.isCreating()); break; } } /** * Called when a ROI slice overlay has changed. */ protected void sliceOverlayChanged(OverlayEvent event) { switch (event.getType()) { case PAINTER_CHANGED: // forward the event to ROI stack overlay getOverlay().painterChanged(); break; case PROPERTY_CHANGED: // forward the event to ROI stack overlay getOverlay().propertyChanged(event.getPropertyName()); break; } } @Override public Rectangle3D computeBounds3D() { Rectangle2D xyBounds = null; for (R slice : slices.values()) { final Rectangle2D bnd2d = slice.getBounds2D(); // only add non empty bounds if (!bnd2d.isEmpty()) { if (xyBounds == null) xyBounds = (Rectangle2D) bnd2d.clone(); else xyBounds.add(bnd2d); } } // create empty 2D bounds if (xyBounds == null) xyBounds = new Rectangle2D.Double(); final int z; final int sizeZ; if (!slices.isEmpty()) { z = slices.firstKey().intValue(); sizeZ = getSizeZ(); } else { z = 0; sizeZ = 0; } return new Rectangle3D.Double(xyBounds.getX(), xyBounds.getY(), z, xyBounds.getWidth(), xyBounds.getHeight(), sizeZ); } @Override public boolean contains(double x, double y, double z) { final R roi2d = getSlice((int) Math.floor(z)); if (roi2d != null) return roi2d.contains(x, y); return false; } @Override public boolean contains(double x, double y, double z, double sizeX, double sizeY, double sizeZ) { final Rectangle3D bounds = getBounds3D(); // easy discard if (!bounds.contains(x, y, z, sizeX, sizeY, sizeZ)) return false; final int lim = (int) Math.floor(z + sizeZ); for (int zc = (int) Math.floor(z); zc < lim; zc++) { final R roi2d = getSlice(zc); if ((roi2d == null) || !roi2d.contains(x, y, sizeX, sizeY)) return false; } return true; } @Override public boolean intersects(double x, double y, double z, double sizeX, double sizeY, double sizeZ) { final Rectangle3D bounds = getBounds3D(); // easy discard if (!bounds.intersects(x, y, z, sizeX, sizeY, sizeZ)) return false; final int lim = (int) Math.floor(z + sizeZ); for (int zc = (int) Math.floor(z); zc < lim; zc++) { final R roi2d = getSlice(zc); if ((roi2d != null) && roi2d.intersects(x, y, sizeX, sizeY)) return true; } return false; } @Override public boolean hasSelectedPoint() { // default return false; } @Override public void unselectAllPoints() { beginUpdate(); try { modifyingSlice.acquireUninterruptibly(); try { for (R slice : slices.values()) slice.unselectAllPoints(); } finally { modifyingSlice.release(); } } finally { endUpdate(); } } // default approximated implementation for ROI3DStack @Override public double computeSurfaceArea(Sequence sequence) throws UnsupportedOperationException { // 3D contour points = first slice points + all slices perimeter + last slice points double result = 0; if (!slices.isEmpty()) { final double psx = sequence.getPixelSizeX(); final double psy = sequence.getPixelSizeY(); final double psz = sequence.getPixelSizeZ(); result = slices.firstEntry().getValue().getNumberOfPoints() * psx * psy; result += slices.lastEntry().getValue().getNumberOfPoints() * psx * psy; for (R slice : slices.values()) result += slice.getLength(sequence) * psz; } return result; } // default approximated implementation for ROI3DStack @Override public double computeNumberOfContourPoints() { // 3D contour points = first slice points + inter slices contour points + last slice points double result = 0; if (slices.size() <= 2) { for (R slice : slices.values()) result += slice.getNumberOfPoints(); } else { final Entry<Integer, R> firstEntry = slices.firstEntry(); final Entry<Integer, R> lastEntry = slices.lastEntry(); final Integer firstKey = firstEntry.getKey(); final Integer lastKey = lastEntry.getKey(); result = firstEntry.getValue().getNumberOfPoints(); for (R slice : slices.subMap(firstKey, false, lastKey, false).values()) result += slice.getNumberOfContourPoints(); result += lastEntry.getValue().getNumberOfPoints(); } return result; } @Override public double computeNumberOfPoints() { double volume = 0; for (R slice : slices.values()) volume += slice.getNumberOfPoints(); return volume; } @Override public boolean canTranslate() { // only need to test the first entry if (!slices.isEmpty()) return slices.firstEntry().getValue().canTranslate(); return false; } /** * Translate the stack of specified Z position. */ public void translate(int z) { // easy optimizations if ((z == 0) || isEmpty()) return; final Map<Integer, R> map = new HashMap<Integer, R>(slices); slices.clear(); for (Entry<Integer, R> entry : map.entrySet()) { final R roi = entry.getValue(); final int newZ = roi.getZ() + z; // // only positive value accepted // if (newZ >= 0) // { roi.setZ(newZ); slices.put(Integer.valueOf(newZ), roi); // } } // notify ROI changed roiChanged(false); } @Override public void translate(double dx, double dy, double dz) { beginUpdate(); try { translateZ += dz; // convert to integer final int dzi = (int) translateZ; // keep trace of not used floating part translateZ -= dzi; translate(dzi); modifyingSlice.acquireUninterruptibly(); try { for (R slice : slices.values()) slice.translate(dx, dy); } finally { modifyingSlice.release(); } // notify ROI changed because we modified slice 'internally' if ((dx != 0d) || (dy != 0d)) roiChanged(false); } finally { endUpdate(); } } @Override public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, boolean inclusive) { final R roi2d = getSlice(z); if (roi2d != null) return roi2d.getBooleanMask(x, y, width, height, inclusive); return new boolean[width * height]; } @Override public BooleanMask2D getBooleanMask2D(int z, boolean inclusive) { final R roi2d = getSlice(z); if (roi2d != null) return roi2d.getBooleanMask(inclusive); return new BooleanMask2D(new Rectangle(), new boolean[0]); } // called when one of the slice ROI changed @Override public void roiChanged(ROIEvent event) { // propagate children change event sliceChanged(event); } // called when one of the slice ROI overlay changed @Override public void overlayChanged(OverlayEvent event) { // propagate children overlay change event sliceOverlayChanged(event); } @Override public Iterator<R> iterator() { return slices.values().iterator(); } @Override public boolean loadFromXML(Node node) { beginUpdate(); try { if (!super.loadFromXML(node)) return false; // we don't need to save the 2D ROI class as the parent class already do it clear(); for (Element e : XMLUtil.getElements(node, "slice")) { // faster than using complete XML serialization final R slice = createSlice(); // error while reloading the ROI from XML if ((slice == null) || !slice.loadFromXML(e)) return false; setSlice(slice.getZ(), slice); } } finally { endUpdate(); } return true; } @Override public boolean saveToXML(Node node) { if (!super.saveToXML(node)) return false; for (R slice : slices.values()) { Element sliceNode = XMLUtil.addElement(node, "slice"); if (!slice.saveToXML(sliceNode)) return false; } return true; } public class ROI3DStackPainter extends ROI3DPainter { protected ROIPainter getSliceOverlayForCanvas(IcyCanvas canvas) { final int z = canvas.getPositionZ(); // canvas position of -1 mean 3D canvas (all Z visible) if (z >= 0) return getSliceOverlay(z); return null; } /** * Returns the ROI overlay at given Z position. */ protected ROIPainter getSliceOverlay(int z) { R roi = getSlice(z); if (roi != null) return roi.getOverlay(); return null; } /** * @deprecated this property does not exist anymore (always return <code>false</code>) */ @Deprecated public boolean getUseChildColor() { return false; } /** * @deprecated this property does not exist anymore */ @Deprecated public void setUseChildColor(boolean value) { // } /** * Set the painter color for the specified ROI slice. * * @see #setUseChildColor(boolean) */ public void setColor(int z, Color value) { final ROIPainter sliceOverlay = getSliceOverlay(z); if (sliceOverlay != null) { modifyingSlice.acquireUninterruptibly(); try { sliceOverlay.setColor(value); } finally { modifyingSlice.release(); } } } @Override public void setColor(Color value) { beginUpdate(); try { super.setColor(value); if (!getUseChildColor()) { modifyingSlice.acquireUninterruptibly(); try { for (R slice : slices.values()) slice.getOverlay().setColor(value); } finally { modifyingSlice.release(); } } } finally { endUpdate(); } } @Override public void setOpacity(float value) { beginUpdate(); try { super.setOpacity(value); modifyingSlice.acquireUninterruptibly(); try { for (R slice : slices.values()) slice.getOverlay().setOpacity(value); } finally { modifyingSlice.release(); } } finally { endUpdate(); } } @Override public void setStroke(double value) { beginUpdate(); try { super.setStroke(value); modifyingSlice.acquireUninterruptibly(); try { for (R slice : slices.values()) slice.getOverlay().setStroke(value); } finally { modifyingSlice.release(); } } finally { endUpdate(); } } @Override public void setShowName(boolean value) { beginUpdate(); try { super.setShowName(value); modifyingSlice.acquireUninterruptibly(); try { for (R slice : slices.values()) slice.getOverlay().setShowName(value); } finally { modifyingSlice.release(); } } finally { endUpdate(); } } @Override public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) { // 2D canvas --> use slice implementation if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) { // forward event to current slice final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); if (sliceOverlay != null) sliceOverlay.paint(g, sequence, canvas); } // use default parent implementation else super.paint(g, sequence, canvas); } @Override public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { // 2D canvas --> use slice implementation if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) { // forward event to current slice final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); if (sliceOverlay != null) sliceOverlay.keyPressed(e, imagePoint, canvas); } // use default parent implementation else super.keyPressed(e, imagePoint, canvas); } @Override public void keyReleased(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { // 2D canvas --> use slice implementation if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) { // forward event to current slice final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); if (sliceOverlay != null) sliceOverlay.keyReleased(e, imagePoint, canvas); } // use default parent implementation else super.keyReleased(e, imagePoint, canvas); } @Override public void mouseEntered(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { // 2D canvas --> use slice implementation if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) { // forward event to current slice final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); if (sliceOverlay != null) sliceOverlay.mouseEntered(e, imagePoint, canvas); } // use default parent implementation else super.mouseEntered(e, imagePoint, canvas); } @Override public void mouseExited(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { // 2D canvas --> use slice implementation if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) { // forward event to current slice final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); if (sliceOverlay != null) sliceOverlay.mouseExited(e, imagePoint, canvas); } // use default parent implementation else super.mouseExited(e, imagePoint, canvas); } @Override public void mouseMove(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { // 2D canvas --> use slice implementation if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) { // forward event to current slice final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); if (sliceOverlay != null) sliceOverlay.mouseMove(e, imagePoint, canvas); } // use default parent implementation else super.mouseMove(e, imagePoint, canvas); } @Override public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { // 2D canvas --> use slice implementation if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) { // forward event to current slice final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); if (sliceOverlay != null) sliceOverlay.mouseDrag(e, imagePoint, canvas); } // use default parent implementation else super.mouseDrag(e, imagePoint, canvas); } @Override public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { // 2D canvas --> use slice implementation if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) { // forward event to current slice final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); if (sliceOverlay != null) sliceOverlay.mousePressed(e, imagePoint, canvas); } // use default parent implementation else super.mousePressed(e, imagePoint, canvas); } @Override public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { // 2D canvas --> use slice implementation if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) { // forward event to current slice final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); if (sliceOverlay != null) sliceOverlay.mouseReleased(e, imagePoint, canvas); } // use default parent implementation else super.mouseReleased(e, imagePoint, canvas); } @Override public void mouseClick(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { // 2D canvas --> use slice implementation if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) { // forward event to current slice final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); if (sliceOverlay != null) sliceOverlay.mouseClick(e, imagePoint, canvas); } // use default parent implementation else super.mouseClick(e, imagePoint, canvas); } @Override public void mouseWheelMoved(MouseWheelEvent e, Point5D.Double imagePoint, IcyCanvas canvas) { // 2D canvas --> use slice implementation if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) { // forward event to current slice final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); if (sliceOverlay != null) sliceOverlay.mouseWheelMoved(e, imagePoint, canvas); } // use default parent implementation else super.mouseWheelMoved(e, imagePoint, canvas); } @Override public void drawROI(Graphics2D g, Sequence sequence, IcyCanvas canvas) { // 2D canvas --> use slice implementation if possible if ((canvas.getPositionZ() >= 0) && isActiveFor(canvas)) { // forward event to current slice final ROIPainter sliceOverlay = getSliceOverlayForCanvas(canvas); if (sliceOverlay instanceof ROI2DPainter) ((ROI2DPainter) sliceOverlay).drawROI(g, sequence, canvas); } // nothing to do... } } }