/* * 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.roi5d; import java.awt.geom.Point2D; import java.util.Map.Entry; import icy.roi.BooleanMask4D; import icy.roi.BooleanMask5D; import icy.roi.ROI; import icy.roi.ROI4D; import icy.type.point.Point5D; import icy.type.rectangle.Rectangle5D; import plugins.kernel.roi.roi4d.ROI4DArea; /** * 5D Area ROI. * * @author Stephane */ public class ROI5DArea extends ROI5DStack<ROI4DArea> { public ROI5DArea() { super(ROI4DArea.class); setName("4D area"); } public ROI5DArea(Point5D pt) { this(); addBrush(pt.toPoint2D(), (int) pt.getZ(), (int) pt.getT(), (int) pt.getC()); } /** * Create a 3D Area ROI type from the specified {@link BooleanMask5D}. */ public ROI5DArea(BooleanMask5D mask) { this(); setAsBooleanMask(mask); } /** * Create a copy of the specified 5D Area ROI. */ public ROI5DArea(ROI5DArea area) { this(); // copy the source 4D area ROI for (Entry<Integer, ROI4DArea> entry : area.slices.entrySet()) slices.put(entry.getKey(), new ROI4DArea(entry.getValue())); roiChanged(true); } @Override public String getDefaultName() { return "Area5D"; } /** * Adds the specified point to this ROI */ public void addPoint(int x, int y, int z, int t, int c) { setPoint(x, y, z, t, c, true); } /** * Remove a point from the mask.<br> * Don't forget to call optimizeBounds() after consecutive remove operation * to refresh the mask bounds. */ public void removePoint(int x, int y, int z, int t, int c) { setPoint(x, y, z, t, c, false); } /** * Set the value for the specified point in the mask. * Don't forget to call optimizeBounds() after consecutive remove point operation * to refresh the mask bounds. */ public void setPoint(int x, int y, int z, int t, int c, boolean value) { final ROI4DArea slice = getSlice(c, value); if (slice != null) slice.setPoint(x, y, z, t, value); } /** * Add brush point at specified position and for specified Z,T,C slice. */ public void addBrush(Point2D pos, int z, int t, int c) { getSlice(c, true).addBrush(pos, z, t); } /** * Remove brush point from the mask at specified position and for specified Z,T,C slice.<br> * Don't forget to call optimizeBounds() after consecutive remove operation * to refresh the mask bounds. */ public void removeBrush(Point2D pos, int z, int t, int c) { final ROI4DArea slice = getSlice(c, false); if (slice != null) slice.removeBrush(pos, z, t); } /** * Sets the ROI slice at given C position to this 5D ROI * * @param c * the position where the slice must be set * @param roiSlice * the 4D ROI to set * @param merge * <code>true</code> if the given slice should be merged with the existing slice, or * <code>false</code> to * replace the existing slice. */ public void setSlice(int c, ROI4D roiSlice, boolean merge) { if (roiSlice == null) throw new IllegalArgumentException("Cannot add an empty slice in a 5D ROI"); final ROI4DArea currentSlice = getSlice(c); final ROI newSlice; // merge both slice if ((currentSlice != null) && merge) { // we need to modify the C position so we do the merge correctly roiSlice.setC(c); // do ROI union newSlice = currentSlice.getUnion(roiSlice); } else newSlice = roiSlice; if (newSlice instanceof ROI4DArea) setSlice(c, (ROI4DArea) newSlice); else if (newSlice instanceof ROI4D) setSlice(c, new ROI4DArea(((ROI4D) newSlice).getBooleanMask(true))); else throw new IllegalArgumentException( "Can't add the result of the merge operation on 4D slice " + c + ": " + newSlice.getClassName()); } /** * Returns true if the ROI is empty (the mask does not contains any point). */ @Override public boolean isEmpty() { for (ROI4DArea area : slices.values()) if (!area.isEmpty()) return false; return true; } /** * Set the mask from a BooleanMask5D object<br> * If specified mask is <i>null</i> then ROI is cleared. */ public void setAsBooleanMask(BooleanMask5D mask) { // mask empty ? --> just clear the ROI if ((mask == null) || mask.isEmpty()) clear(); else { final Rectangle5D.Integer bounds5d = mask.bounds; final int startC = bounds5d.c; final int sizeC = bounds5d.sizeC; final BooleanMask4D masks4d[] = new BooleanMask4D[sizeC]; for (int c = 0; c < sizeC; c++) masks4d[c] = mask.getMask4D(startC + c); setAsBooleanMask(bounds5d, masks4d); } } /** * Set the 5D mask from a 4D boolean mask array * * @param rect * the 5D region defined by 4D boolean mask array * @param mask * the 5D mask data (array length should be equals to rect.sizeC) */ public void setAsBooleanMask(Rectangle5D.Integer rect, BooleanMask4D[] mask) { if (rect.isInfiniteC()) throw new IllegalArgumentException("Cannot set infinite C dimension on the 5D Area ROI."); beginUpdate(); try { clear(); for (int c = 0; c < rect.sizeC; c++) setSlice(c + rect.c, new ROI4DArea(mask[c])); } finally { endUpdate(); } } /** * Optimize the bounds size to the minimum surface which still include all mask.<br> * You should call it after consecutive remove operations. */ public void optimizeBounds() { final Rectangle5D.Integer bounds = getBounds(); beginUpdate(); try { for (int c = bounds.c; c < bounds.c + bounds.sizeC; c++) { final ROI4DArea roi = getSlice(c); if (roi != null) { if (roi.isEmpty()) removeSlice(c); else roi.optimizeBounds(); } } } finally { endUpdate(); } } }