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 icy.roi;
020
021import icy.canvas.IcyCanvas;
022import icy.type.point.Point5D;
023import icy.type.rectangle.Rectangle3D;
024import icy.type.rectangle.Rectangle4D;
025import icy.type.rectangle.Rectangle5D;
026
027import java.util.ArrayList;
028import java.util.List;
029
030/**
031 * 5D ROI base class.
032 */
033public abstract class ROI5D extends ROI
034{
035    /**
036     * @deprecated Use {@link ROI5D#getROI5DList(List)} instead.
037     */
038    @Deprecated
039    public static ArrayList<ROI5D> getROI5DList(ArrayList<ROI> rois)
040    {
041        final ArrayList<ROI5D> result = new ArrayList<ROI5D>();
042
043        for (ROI roi : rois)
044            if (roi instanceof ROI5D)
045                result.add((ROI5D) roi);
046
047        return result;
048    }
049
050    /**
051     * Return all 5D ROI from the ROI list
052     */
053    public static List<ROI5D> getROI5DList(List<ROI> rois)
054    {
055        final List<ROI5D> result = new ArrayList<ROI5D>();
056
057        for (ROI roi : rois)
058            if (roi instanceof ROI5D)
059                result.add((ROI5D) roi);
060
061        return result;
062    }
063
064    public ROI5D()
065    {
066        super();
067    }
068
069    @Override
070    public String getDefaultName()
071    {
072        return "ROI5D";
073    }
074
075    @Override
076    final public int getDimension()
077    {
078        return 5;
079    }
080
081    @Override
082    public boolean isActiveFor(IcyCanvas canvas)
083    {
084        return true;
085    }
086
087    /**
088     * Returns an integer {@link Rectangle5D} that completely encloses the <code>ROI</code>. Note
089     * that there is no guarantee that the returned <code>Rectangle5D</code> is the smallest
090     * bounding box that encloses the <code>ROI</code>, only that the <code>ROI</code> lies entirely
091     * within the indicated <code>Rectangle5D</code>. The returned <code>Rectangle5D</code> might
092     * also fail to completely enclose the <code>ROI</code> if the <code>ROI</code> overflows the
093     * limited range of the integer data type. The <code>getBounds5D</code> method generally returns
094     * a tighter bounding box due to its greater flexibility in representation.
095     * 
096     * @return an integer <code>Rectangle5D</code> that completely encloses the <code>ROI</code>.
097     */
098    public Rectangle5D.Integer getBounds()
099    {
100        return getBounds5D().toInteger();
101    }
102
103    /**
104     * Returns the integer ROI position which normally correspond to the <i>minimum</i> point of the
105     * ROI bounds.
106     * 
107     * @see #getBounds()
108     */
109    public Point5D.Integer getPosition()
110    {
111        final Rectangle5D.Integer bounds = getBounds();
112        return new Point5D.Integer(bounds.x, bounds.y, bounds.z, bounds.t, bounds.c);
113    }
114
115    @Override
116    public boolean canSetBounds()
117    {
118        // default
119        return false;
120    }
121
122    @Override
123    public void setBounds5D(Rectangle5D bounds)
124    {
125        // do nothing by default (not supported)
126    }
127
128    @Override
129    public boolean canSetPosition()
130    {
131        // default implementation use translation if available
132        return canTranslate();
133    }
134
135    @Override
136    public void setPosition5D(Point5D position)
137    {
138        // use translation operation by default if supported
139        if (canTranslate())
140        {
141            final Point5D oldPos = getPosition5D();
142            translate(position.getX() - oldPos.getX(), position.getY() - oldPos.getY(),
143                    position.getZ() - oldPos.getZ(), position.getT() - oldPos.getT(), position.getC() - oldPos.getC());
144        }
145    }
146
147    /**
148     * Returns <code>true</code> if the ROI support translate operation.
149     * 
150     * @see #translate(double, double, double, double, double)
151     */
152    public boolean canTranslate()
153    {
154        // by default
155        return false;
156    }
157
158    /**
159     * Translate the ROI position by the specified delta X/Y/Z/T.<br>
160     * Note that not all ROI support this operation so you should test it by calling {@link #canTranslate()} first.
161     * 
162     * @param dx
163     *        translation value to apply on X dimension
164     * @param dy
165     *        translation value to apply on Y dimension
166     * @param dz
167     *        translation value to apply on Z dimension
168     * @param dt
169     *        translation value to apply on T dimension
170     * @param dc
171     *        translation value to apply on C dimension
172     * @see #canTranslate()
173     * @see #setPosition5D(Point5D)
174     */
175    public void translate(double dx, double dy, double dz, double dt, double dc)
176    {
177
178    }
179
180    /*
181     * Generic implementation using the BooleanMask which is not accurate and slow.
182     * Override this for specific ROI type.
183     */
184    @Override
185    public boolean contains(ROI roi)
186    {
187        if (roi instanceof ROI5D)
188        {
189            final ROI5D roi5d = (ROI5D) roi;
190
191            // special case of ROI Point
192            if (roi5d.isEmpty())
193                return contains(roi5d.getPosition5D());
194
195            BooleanMask5D mask;
196            BooleanMask5D roiMask;
197
198            // take content first
199            mask = getBooleanMask(false);
200            roiMask = roi5d.getBooleanMask(false);
201
202            // test first only on content
203            if (!mask.contains(roiMask))
204                return false;
205
206            // take content and edge
207            mask = getBooleanMask(true);
208            roiMask = roi5d.getBooleanMask(true);
209
210            // then test on content and edge
211            if (!mask.contains(roiMask))
212                return false;
213
214            // contained
215            return true;
216        }
217
218        // use default implementation
219        return super.contains(roi);
220    }
221
222    /*
223     * Generic implementation using the BooleanMask which is not accurate and slow.
224     * Override this for specific ROI type.
225     */
226    @Override
227    public boolean intersects(ROI roi)
228    {
229        if (roi instanceof ROI5D)
230            return getBooleanMask(true).intersects(((ROI5D) roi).getBooleanMask(true));
231
232        // use default implementation
233        return super.intersects(roi);
234    }
235
236    @Override
237    public BooleanMask2D getBooleanMask2D(int z, int t, int c, boolean inclusive)
238    {
239        final BooleanMask2D result = super.getBooleanMask2D(z, t, c, inclusive);
240
241        // optimized bounds to optimize memory usage for this specific Z, T, C slice mask
242        result.optimizeBounds();
243
244        return result;
245    }
246
247    /**
248     * Returns the {@link BooleanMask3D} object representing the XYZ volume content at specified Z,
249     * T, C position.<br>
250     * It contains the 3D rectangle mask bounds and the associated boolean array mask.<br>
251     * 
252     * @param z
253     *        Z position we want to retrieve the boolean mask or -1 to retrieve the whole Z
254     *        dimension
255     * @param t
256     *        T position we want to retrieve the boolean mask.
257     * @param c
258     *        C position we want to retrieve the boolean mask.
259     * @param inclusive
260     *        If true then all partially contained (intersected) pixels are included in the mask.
261     */
262    public BooleanMask3D getBooleanMask3D(int z, int t, int c, boolean inclusive)
263    {
264        // whole Z dimension
265        if (z == -1)
266            return getBooleanMask3D(t, c, inclusive);
267
268        // define bounds
269        final Rectangle3D.Integer bounds = getBounds5D().toRectangle3D().toInteger();
270        bounds.setZ(z);
271        bounds.setSizeZ(1);
272
273        return new BooleanMask3D(bounds, new BooleanMask2D[] {getBooleanMask2D(z, t, c, inclusive)});
274    }
275
276    /**
277     * Returns the {@link BooleanMask3D} object representing the XYZ volume content at specified T,
278     * C position.<br>
279     * It contains the 3D rectangle mask bounds and the associated boolean array mask.<br>
280     * 
281     * @param inclusive
282     *        If true then all partially contained (intersected) pixels are included in the mask.
283     */
284    public BooleanMask3D getBooleanMask3D(int t, int c, boolean inclusive)
285    {
286        final Rectangle3D.Integer bounds = getBounds5D().toRectangle3D().toInteger();
287        final BooleanMask2D masks[] = new BooleanMask2D[bounds.sizeZ];
288
289        for (int z = 0; z < masks.length; z++)
290            masks[z] = getBooleanMask2D(bounds.z + z, t, c, inclusive);
291
292        return new BooleanMask3D(bounds, masks);
293    }
294
295    /**
296     * Returns the {@link BooleanMask4D} object representing the XYZT space content at specified Z
297     * and C position.
298     * 
299     * @param z
300     *        Z position we want to retrieve the boolean mask or -1 to retrieve the whole Z
301     *        dimension
302     * @param t
303     *        T position we want to retrieve the boolean mask or -1 to retrieve the whole T
304     *        dimension
305     * @param c
306     *        C position we want to retrieve the boolean mask.
307     * @param inclusive
308     *        If true then all partially contained (intersected) pixels are included in the mask.
309     */
310    public BooleanMask4D getBooleanMask4D(int z, int t, int c, boolean inclusive)
311    {
312        // whole Z dimension
313        if (z == -1)
314        {
315            // whole Z and T dimension
316            if (t == -1)
317                return getBooleanMask4D(c, inclusive);
318
319            // define bounds
320            final Rectangle4D.Integer bounds = getBounds5D().toRectangle4D().toInteger();
321            bounds.setT(t);
322            bounds.setSizeT(1);
323
324            // whole Z dimension but specific T
325            return new BooleanMask4D(bounds, new BooleanMask3D[] {getBooleanMask3D(t, c, inclusive)});
326        }
327
328        final Rectangle4D.Integer bounds4d = getBounds5D().toRectangle4D().toInteger();
329
330        // specific Z
331        bounds4d.setZ(z);
332        bounds4d.setSizeZ(1);
333
334        // specific T dimension ?
335        if (t != -1)
336        {
337            bounds4d.setT(t);
338            bounds4d.setSizeT(1);
339        }
340
341        final Rectangle3D.Integer bounds3d = (Rectangle3D.Integer) bounds4d.toRectangle3D();
342        final BooleanMask3D masks[] = new BooleanMask3D[bounds4d.sizeT];
343
344        for (int i = 0; i < bounds4d.sizeT; i++)
345            masks[i] = new BooleanMask3D((Rectangle3D.Integer) bounds3d.clone(), new BooleanMask2D[] {getBooleanMask2D(
346                    z, bounds4d.t + i, c, inclusive)});
347
348        return new BooleanMask4D(bounds4d, masks);
349    }
350
351    /**
352     * Get the {@link BooleanMask4D} object representing the roi for specified C position.<br>
353     * It contains the 4D rectangle mask bounds and the associated boolean array mask.<br>
354     * 
355     * @param inclusive
356     *        If true then all partially contained (intersected) pixels are included in the mask.
357     */
358    public BooleanMask4D getBooleanMask4D(int c, boolean inclusive)
359    {
360        final Rectangle4D.Integer bounds = getBounds5D().toRectangle4D().toInteger();
361        final BooleanMask3D masks[] = new BooleanMask3D[bounds.sizeT];
362
363        for (int t = 0; t < masks.length; t++)
364            masks[t] = getBooleanMask3D(bounds.t + t, c, inclusive);
365
366        return new BooleanMask4D(bounds, masks);
367    }
368
369    /**
370     * Returns the {@link BooleanMask5D} object representing the XYZTC space content at specified Z,
371     * T, C position.
372     * 
373     * @param z
374     *        Z position we want to retrieve the boolean mask or -1 to retrieve the whole Z
375     *        dimension
376     * @param t
377     *        T position we want to retrieve the boolean mask or -1 to retrieve the whole T
378     *        dimension
379     * @param c
380     *        C position we want to retrieve the boolean mask or -1 to retrieve the whole C
381     *        dimension
382     * @param inclusive
383     *        If true then all partially contained (intersected) pixels are included in the mask.
384     */
385    public BooleanMask5D getBooleanMask5D(int z, int t, int c, boolean inclusive)
386    {
387        // whole Z dimension
388        if (z == -1)
389        {
390            // whole Z and T dimension
391            if (t == -1)
392            {
393                // whole Z, T and C dimension
394                if (c == -1)
395                    return getBooleanMask(inclusive);
396
397                // define bounds
398                final Rectangle5D.Integer bounds = getBounds5D().toInteger();
399                bounds.setC(c);
400                bounds.setSizeC(1);
401
402                // whole Z and T dimension but specific C
403                return new BooleanMask5D(bounds, new BooleanMask4D[] {getBooleanMask4D(c, inclusive)});
404            }
405
406            final Rectangle5D.Integer bounds5d = getBounds();
407
408            // specific T
409            bounds5d.setT(t);
410            bounds5d.setSizeT(1);
411            // specific C dimension ?
412            if (c != -1)
413            {
414                bounds5d.setC(c);
415                bounds5d.setSizeC(1);
416            }
417
418            final Rectangle4D.Integer bounds4d = (Rectangle4D.Integer) bounds5d.toRectangle4D();
419            final BooleanMask4D masks[] = new BooleanMask4D[bounds5d.sizeC];
420
421            for (int i = 0; i < bounds5d.sizeC; i++)
422                masks[i] = new BooleanMask4D((Rectangle4D.Integer) bounds4d.clone(),
423                        new BooleanMask3D[] {getBooleanMask3D(t, bounds5d.c + i, inclusive)});
424
425            return new BooleanMask5D(bounds5d, masks);
426        }
427
428        final Rectangle5D.Integer bounds5d = getBounds();
429
430        // specific Z
431        bounds5d.setZ(z);
432        bounds5d.setSizeZ(1);
433        // specific T dimension ?
434        if (t != -1)
435        {
436            bounds5d.setT(t);
437            bounds5d.setSizeT(1);
438        }
439        // specific C dimension ?
440        if (c != -1)
441        {
442            bounds5d.setC(c);
443            bounds5d.setSizeC(1);
444        }
445
446        final Rectangle4D.Integer bounds4d = (Rectangle4D.Integer) bounds5d.toRectangle4D();
447        final Rectangle3D.Integer bounds3d = (Rectangle3D.Integer) bounds4d.toRectangle3D();
448        final BooleanMask4D masks[] = new BooleanMask4D[bounds5d.sizeC];
449
450        for (int i = 0; i < bounds5d.sizeC; i++)
451        {
452            final BooleanMask3D masks3d[] = new BooleanMask3D[bounds4d.sizeT];
453
454            for (int j = 0; j < bounds5d.sizeT; j++)
455                masks3d[i] = new BooleanMask3D((Rectangle3D.Integer) bounds3d.clone(),
456                        new BooleanMask2D[] {getBooleanMask2D(z, bounds5d.t + j, bounds5d.c + i, inclusive)});
457
458            masks[i] = new BooleanMask4D((Rectangle4D.Integer) bounds4d.clone(), masks3d);
459        }
460
461        return new BooleanMask5D(bounds5d, masks);
462    }
463
464    /**
465     * Get the {@link BooleanMask5D} object representing the roi.<br>
466     * It contains the 5D rectangle mask bounds and the associated boolean array mask.<br>
467     * 
468     * @param inclusive
469     *        If true then all partially contained (intersected) pixels are included in the mask.
470     */
471    public BooleanMask5D getBooleanMask(boolean inclusive)
472    {
473        final Rectangle5D.Integer bounds = getBounds();
474        final BooleanMask4D masks[] = new BooleanMask4D[bounds.sizeC];
475
476        for (int c = 0; c < masks.length; c++)
477            masks[c] = getBooleanMask4D(bounds.c + c, inclusive);
478
479        return new BooleanMask5D(bounds, masks);
480    }
481
482    /*
483     * Generic implementation for ROI5D using the BooleanMask object so
484     * the result is just an approximation.
485     * Override to optimize for specific ROI.
486     */
487    @Override
488    public double computeNumberOfContourPoints()
489    {
490        // approximation by using number of point of the edge of boolean mask
491        return getBooleanMask(true).getContourPointsAsIntArray().length / getDimension();
492    }
493
494    /*
495     * Generic implementation for ROI5D using the BooleanMask object so
496     * the result is just an approximation.
497     * Override to optimize for specific ROI.
498     */
499    @Override
500    public double computeNumberOfPoints()
501    {
502        double numPoints = 0;
503
504        // approximation by using number of point of boolean mask with and without border
505        numPoints += getBooleanMask(true).getNumberOfPoints();
506        numPoints += getBooleanMask(false).getNumberOfPoints();
507        numPoints /= 2d;
508
509        return numPoints;
510    }
511
512}