/* * 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.roi4d; import java.awt.geom.Point2D; import java.util.Map.Entry; import icy.roi.BooleanMask3D; import icy.roi.BooleanMask4D; import icy.roi.ROI; import icy.roi.ROI3D; import icy.type.point.Point4D; import icy.type.point.Point5D; import icy.type.rectangle.Rectangle4D; import plugins.kernel.roi.roi3d.ROI3DArea; /** * 4D Area ROI. * * @author Stephane */ public class ROI4DArea extends ROI4DStack<ROI3DArea> { public ROI4DArea() { super(ROI3DArea.class); } public ROI4DArea(Point4D pt) { this(); addBrush(pt.toPoint2D(), (int) pt.getZ(), (int) pt.getT()); } public ROI4DArea(Point5D pt) { this(pt.toPoint4D()); } /** * Create a 3D Area ROI type from the specified {@link BooleanMask4D}. */ public ROI4DArea(BooleanMask4D mask) { this(); setAsBooleanMask(mask); } /** * Create a copy of the specified 4D Area ROI. */ public ROI4DArea(ROI4DArea area) { this(); // copy the source 4D area ROI for (Entry<Integer, ROI3DArea> entry : area.slices.entrySet()) slices.put(entry.getKey(), new ROI3DArea(entry.getValue())); roiChanged(true); } @Override public String getDefaultName() { return "Area4D"; } /** * Adds the specified point to this ROI */ public void addPoint(int x, int y, int z, int t) { setPoint(x, y, z, t, 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) { setPoint(x, y, z, t, 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, boolean value) { final ROI3DArea slice = getSlice(t, value); if (slice != null) slice.setPoint(x, y, z, value); } /** * Add brush point at specified position and for specified Z,T slice. */ public void addBrush(Point2D pos, int z, int t) { getSlice(t, true).addBrush(pos, z); } /** * Remove brush point from the mask at specified position and for specified Z,T 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) { final ROI3DArea slice = getSlice(t, false); if (slice != null) slice.removeBrush(pos, z); } /** * Sets the ROI slice at given T position to this 4D ROI * * @param t * the position where the slice must be set * @param roiSlice * the 3D 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 t, ROI3D roiSlice, boolean merge) { if (roiSlice == null) throw new IllegalArgumentException("Cannot add an empty slice in a 4D ROI"); final ROI3DArea currentSlice = getSlice(t); final ROI newSlice; // merge both slice if ((currentSlice != null) && merge) { // we need to modify the T and C position so we do the merge correctly roiSlice.setT(t); roiSlice.setC(getC()); // do ROI union newSlice = currentSlice.getUnion(roiSlice); } else newSlice = roiSlice; if (newSlice instanceof ROI3DArea) setSlice(t, (ROI3DArea) newSlice); else if (newSlice instanceof ROI3D) setSlice(t, new ROI3DArea(((ROI3D) newSlice).getBooleanMask(true))); else throw new IllegalArgumentException( "Can't add the result of the merge operation on 3D slice " + t + ": " + newSlice.getClassName()); } /** * Returns true if the ROI is empty (the mask does not contains any point). */ @Override public boolean isEmpty() { for (ROI3DArea area : slices.values()) if (!area.isEmpty()) return false; return true; } /** * Set the mask from a BooleanMask4D object<br> * If specified mask is <i>null</i> then ROI is cleared. */ public void setAsBooleanMask(BooleanMask4D mask) { // mask empty ? --> just clear the ROI if ((mask == null) || mask.isEmpty()) clear(); else { final Rectangle4D.Integer bounds4d = mask.bounds; final int startT = bounds4d.t; final int sizeT = bounds4d.sizeT; final BooleanMask3D masks3d[] = new BooleanMask3D[sizeT]; for (int t = 0; t < sizeT; t++) masks3d[t] = mask.getMask3D(startT + t); setAsBooleanMask(bounds4d, masks3d); } } /** * Set the 4D mask from a 3D boolean mask array * * @param rect * the 4D region defined by 3D boolean mask array * @param mask * the 4D mask data (array length should be equals to rect.sizeT) */ public void setAsBooleanMask(Rectangle4D.Integer rect, BooleanMask3D[] mask) { if (rect.isInfiniteT()) throw new IllegalArgumentException("Cannot set infinite T dimension on the 4D Area ROI."); beginUpdate(); try { clear(); for (int t = 0; t < rect.sizeT; t++) setSlice(t + rect.t, new ROI3DArea(mask[t])); } 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 Rectangle4D.Integer bounds = getBounds(); beginUpdate(); try { for (int t = bounds.t; t < bounds.t + bounds.sizeT; t++) { final ROI3DArea roi = getSlice(t); if (roi != null) { if (roi.isEmpty()) removeSlice(t); else roi.optimizeBounds(); } } } finally { endUpdate(); } } }