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.roi5d;
020
021import icy.roi.BooleanMask4D;
022import icy.roi.BooleanMask5D;
023import icy.roi.ROI;
024import icy.roi.ROI4D;
025import icy.type.point.Point5D;
026import icy.type.rectangle.Rectangle5D;
027
028import java.awt.geom.Point2D;
029import java.util.Map.Entry;
030
031import plugins.kernel.roi.roi4d.ROI4DArea;
032
033/**
034 * 5D Area ROI.
035 * 
036 * @author Stephane
037 */
038public class ROI5DArea extends ROI5DStack<ROI4DArea>
039{
040    public ROI5DArea()
041    {
042        super(ROI4DArea.class);
043
044        setName("4D area");
045    }
046
047    public ROI5DArea(Point5D pt)
048    {
049        this();
050
051        addBrush(pt.toPoint2D(), (int) pt.getZ(), (int) pt.getT(), (int) pt.getC());
052    }
053
054    /**
055     * Create a 3D Area ROI type from the specified {@link BooleanMask5D}.
056     */
057    public ROI5DArea(BooleanMask5D mask)
058    {
059        this();
060
061        setAsBooleanMask(mask);
062    }
063
064    /**
065     * Create a copy of the specified 5D Area ROI.
066     */
067    public ROI5DArea(ROI5DArea area)
068    {
069        this();
070
071        // copy the source 4D area ROI
072        for (Entry<Integer, ROI4DArea> entry : area.slices.entrySet())
073            slices.put(entry.getKey(), new ROI4DArea(entry.getValue()));
074
075        roiChanged(true);
076    }
077
078    @Override
079    public String getDefaultName()
080    {
081        return "Area5D";
082    }
083
084    /**
085     * Adds the specified point to this ROI
086     */
087    public void addPoint(int x, int y, int z, int t, int c)
088    {
089        setPoint(x, y, z, t, c, true);
090    }
091
092    /**
093     * Remove a point from the mask.<br>
094     * Don't forget to call optimizeBounds() after consecutive remove operation
095     * to refresh the mask bounds.
096     */
097    public void removePoint(int x, int y, int z, int t, int c)
098    {
099        setPoint(x, y, z, t, c, false);
100    }
101
102    /**
103     * Set the value for the specified point in the mask.
104     * Don't forget to call optimizeBounds() after consecutive remove point operation
105     * to refresh the mask bounds.
106     */
107    public void setPoint(int x, int y, int z, int t, int c, boolean value)
108    {
109        final ROI4DArea slice = getSlice(c, value);
110
111        if (slice != null)
112            slice.setPoint(x, y, z, t, value);
113    }
114
115    /**
116     * Add brush point at specified position and for specified Z,T,C slice.
117     */
118    public void addBrush(Point2D pos, int z, int t, int c)
119    {
120        getSlice(c, true).addBrush(pos, z, t);
121    }
122
123    /**
124     * Remove brush point from the mask at specified position and for specified Z,T,C slice.<br>
125     * Don't forget to call optimizeBounds() after consecutive remove operation
126     * to refresh the mask bounds.
127     */
128    public void removeBrush(Point2D pos, int z, int t, int c)
129    {
130        final ROI4DArea slice = getSlice(c, false);
131
132        if (slice != null)
133            slice.removeBrush(pos, z, t);
134    }
135
136    /**
137     * Sets the ROI slice at given C position to this 5D ROI
138     * 
139     * @param c
140     *        the position where the slice must be set
141     * @param roiSlice
142     *        the 4D ROI to set
143     * @param merge
144     *        <code>true</code> if the given slice should be merged with the existing slice, or
145     *        <code>false</code> to
146     *        replace the existing slice.
147     */
148    public void setSlice(int c, ROI4D roiSlice, boolean merge)
149    {
150        if (roiSlice == null)
151            throw new IllegalArgumentException("Cannot add an empty slice in a 5D ROI");
152
153        final ROI4DArea currentSlice = getSlice(c);
154        final ROI newSlice;
155
156        // merge both slice
157        if ((currentSlice != null) && merge)
158        {
159            // we need to modify the C position so we do the merge correctly
160            roiSlice.setC(c);
161            // do ROI union
162            newSlice = currentSlice.getUnion(roiSlice);
163        }
164        else
165            newSlice = roiSlice;
166
167        if (newSlice instanceof ROI4DArea)
168            setSlice(c, (ROI4DArea) newSlice);
169        else if (newSlice instanceof ROI4D)
170            setSlice(c, new ROI4DArea(((ROI4D) newSlice).getBooleanMask(true)));
171        else
172            throw new IllegalArgumentException(
173                    "Can't add the result of the merge operation on 4D slice " + c + ": " + newSlice.getClassName());
174    }
175
176    /**
177     * Returns true if the ROI is empty (the mask does not contains any point).
178     */
179    @Override
180    public boolean isEmpty()
181    {
182        for (ROI4DArea area : slices.values())
183            if (!area.isEmpty())
184                return false;
185
186        return true;
187    }
188
189    /**
190     * Set the mask from a BooleanMask5D object<br>
191     * If specified mask is <i>null</i> then ROI is cleared.
192     */
193    public void setAsBooleanMask(BooleanMask5D mask)
194    {
195        // mask empty ? --> just clear the ROI
196        if ((mask == null) || mask.isEmpty())
197            clear();
198        else
199        {
200            final Rectangle5D.Integer bounds5d = mask.bounds;
201            final int startC = bounds5d.c;
202            final int sizeC = bounds5d.sizeC;
203            final BooleanMask4D masks4d[] = new BooleanMask4D[sizeC];
204
205            for (int c = 0; c < sizeC; c++)
206                masks4d[c] = mask.getMask4D(startC + c);
207
208            setAsBooleanMask(bounds5d, masks4d);
209        }
210    }
211
212    /**
213     * Set the 5D mask from a 4D boolean mask array
214     * 
215     * @param rect
216     *        the 5D region defined by 4D boolean mask array
217     * @param mask
218     *        the 5D mask data (array length should be equals to rect.sizeC)
219     */
220    public void setAsBooleanMask(Rectangle5D.Integer rect, BooleanMask4D[] mask)
221    {
222        if (rect.isInfiniteC())
223            throw new IllegalArgumentException("Cannot set infinite C dimension on the 5D Area ROI.");
224
225        beginUpdate();
226        try
227        {
228            clear();
229
230            for (int c = 0; c < rect.sizeC; c++)
231                setSlice(c + rect.c, new ROI4DArea(mask[c]));
232        }
233        finally
234        {
235            endUpdate();
236        }
237    }
238
239    /**
240     * Optimize the bounds size to the minimum surface which still include all mask.<br>
241     * You should call it after consecutive remove operations.
242     */
243    public void optimizeBounds()
244    {
245        final Rectangle5D.Integer bounds = getBounds();
246
247        beginUpdate();
248        try
249        {
250            for (int c = bounds.c; c < bounds.c + bounds.sizeC; c++)
251            {
252                final ROI4DArea roi = getSlice(c);
253
254                if (roi != null)
255                {
256                    if (roi.isEmpty())
257                        removeSlice(c);
258                    else
259                        roi.optimizeBounds();
260                }
261            }
262        }
263        finally
264        {
265            endUpdate();
266        }
267    }
268}