001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package plugins.kernel.roi.roi4d;
020
021import icy.roi.BooleanMask3D;
022import icy.roi.BooleanMask4D;
023import icy.roi.ROI;
024import icy.roi.ROI3D;
025import icy.type.point.Point4D;
026import icy.type.point.Point5D;
027import icy.type.rectangle.Rectangle4D;
028
029import java.awt.geom.Point2D;
030import java.util.Map.Entry;
031
032import plugins.kernel.roi.roi3d.ROI3DArea;
033
034/**
035 * 4D Area ROI.
036 * 
037 * @author Stephane
038 */
039public class ROI4DArea extends ROI4DStack<ROI3DArea>
040{
041    public ROI4DArea()
042    {
043        super(ROI3DArea.class);
044    }
045
046    public ROI4DArea(Point4D pt)
047    {
048        this();
049
050        addBrush(pt.toPoint2D(), (int) pt.getZ(), (int) pt.getT());
051    }
052
053    public ROI4DArea(Point5D pt)
054    {
055        this(pt.toPoint4D());
056    }
057
058    /**
059     * Create a 3D Area ROI type from the specified {@link BooleanMask4D}.
060     */
061    public ROI4DArea(BooleanMask4D mask)
062    {
063        this();
064
065        setAsBooleanMask(mask);
066    }
067
068    /**
069     * Create a copy of the specified 4D Area ROI.
070     */
071    public ROI4DArea(ROI4DArea area)
072    {
073        this();
074
075        // copy the source 4D area ROI
076        for (Entry<Integer, ROI3DArea> entry : area.slices.entrySet())
077            slices.put(entry.getKey(), new ROI3DArea(entry.getValue()));
078
079        roiChanged(true);
080    }
081
082    @Override
083    public String getDefaultName()
084    {
085        return "Area4D";
086    }
087
088    /**
089     * Adds the specified point to this ROI
090     */
091    public void addPoint(int x, int y, int z, int t)
092    {
093        setPoint(x, y, z, t, true);
094    }
095
096    /**
097     * Remove a point from the mask.<br>
098     * Don't forget to call optimizeBounds() after consecutive remove operation
099     * to refresh the mask bounds.
100     */
101    public void removePoint(int x, int y, int z, int t)
102    {
103        setPoint(x, y, z, t, false);
104    }
105
106    /**
107     * Set the value for the specified point in the mask.
108     * Don't forget to call optimizeBounds() after consecutive remove point operation
109     * to refresh the mask bounds.
110     */
111    public void setPoint(int x, int y, int z, int t, boolean value)
112    {
113        final ROI3DArea slice = getSlice(t, value);
114
115        if (slice != null)
116            slice.setPoint(x, y, z, value);
117    }
118
119    /**
120     * Add brush point at specified position and for specified Z,T slice.
121     */
122    public void addBrush(Point2D pos, int z, int t)
123    {
124        getSlice(t, true).addBrush(pos, z);
125    }
126
127    /**
128     * Remove brush point from the mask at specified position and for specified Z,T slice.<br>
129     * Don't forget to call optimizeBounds() after consecutive remove operation
130     * to refresh the mask bounds.
131     */
132    public void removeBrush(Point2D pos, int z, int t)
133    {
134        final ROI3DArea slice = getSlice(t, false);
135
136        if (slice != null)
137            slice.removeBrush(pos, z);
138    }
139
140    /**
141     * Sets the ROI slice at given T position to this 4D ROI
142     * 
143     * @param t
144     *        the position where the slice must be set
145     * @param roiSlice
146     *        the 3D ROI to set
147     * @param merge
148     *        <code>true</code> if the given slice should be merged with the existing slice, or
149     *        <code>false</code> to
150     *        replace the existing slice.
151     */
152    public void setSlice(int t, ROI3D roiSlice, boolean merge)
153    {
154        if (roiSlice == null)
155            throw new IllegalArgumentException("Cannot add an empty slice in a 4D ROI");
156
157        final ROI3DArea currentSlice = getSlice(t);
158        final ROI newSlice;
159
160        // merge both slice
161        if ((currentSlice != null) && merge)
162        {
163            // we need to modify the T and C position so we do the merge correctly
164            roiSlice.setT(t);
165            roiSlice.setC(getC());
166            // do ROI union
167            newSlice = currentSlice.getUnion(roiSlice);
168        }
169        else
170            newSlice = roiSlice;
171
172        if (newSlice instanceof ROI3DArea)
173            setSlice(t, (ROI3DArea) newSlice);
174        else if (newSlice instanceof ROI3D)
175            setSlice(t, new ROI3DArea(((ROI3D) newSlice).getBooleanMask(true)));
176        else
177            throw new IllegalArgumentException(
178                    "Can't add the result of the merge operation on 3D slice " + t + ": " + newSlice.getClassName());
179    }
180
181    /**
182     * Returns true if the ROI is empty (the mask does not contains any point).
183     */
184    @Override
185    public boolean isEmpty()
186    {
187        for (ROI3DArea area : slices.values())
188            if (!area.isEmpty())
189                return false;
190
191        return true;
192    }
193
194    /**
195     * Set the mask from a BooleanMask4D object<br>
196     * If specified mask is <i>null</i> then ROI is cleared.
197     */
198    public void setAsBooleanMask(BooleanMask4D mask)
199    {
200        // mask empty ? --> just clear the ROI
201        if ((mask == null) || mask.isEmpty())
202            clear();
203        else
204        {
205            final Rectangle4D.Integer bounds4d = mask.bounds;
206            final int startT = bounds4d.t;
207            final int sizeT = bounds4d.sizeT;
208            final BooleanMask3D masks3d[] = new BooleanMask3D[sizeT];
209
210            for (int t = 0; t < sizeT; t++)
211                masks3d[t] = mask.getMask3D(startT + t);
212
213            setAsBooleanMask(bounds4d, masks3d);
214        }
215    }
216
217    /**
218     * Set the 4D mask from a 3D boolean mask array
219     * 
220     * @param rect
221     *        the 4D region defined by 3D boolean mask array
222     * @param mask
223     *        the 4D mask data (array length should be equals to rect.sizeT)
224     */
225    public void setAsBooleanMask(Rectangle4D.Integer rect, BooleanMask3D[] mask)
226    {
227        if (rect.isInfiniteT())
228            throw new IllegalArgumentException("Cannot set infinite T dimension on the 4D Area ROI.");
229
230        beginUpdate();
231        try
232        {
233            clear();
234
235            for (int t = 0; t < rect.sizeT; t++)
236                setSlice(t + rect.t, new ROI3DArea(mask[t]));
237        }
238        finally
239        {
240            endUpdate();
241        }
242    }
243
244    /**
245     * Optimize the bounds size to the minimum surface which still include all mask<br>
246     * You should call it after consecutive remove operations.
247     */
248    public void optimizeBounds()
249    {
250        final Rectangle4D.Integer bounds = getBounds();
251
252        beginUpdate();
253        try
254        {
255            for (int t = bounds.t; t < bounds.t + bounds.sizeT; t++)
256            {
257                final ROI3DArea roi = getSlice(t);
258
259                if (roi != null)
260                {
261                    if (roi.isEmpty())
262                        removeSlice(t);
263                    else
264                        roi.optimizeBounds();
265                }
266            }
267        }
268        finally
269        {
270            endUpdate();
271        }
272    }
273}