/*
* 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();
}
}
}