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.roi3d;
020
021import icy.canvas.IcyCanvas;
022import icy.common.CollapsibleEvent;
023import icy.gui.inspector.RoisPanel;
024import icy.main.Icy;
025import icy.painter.VtkPainter;
026import icy.roi.BooleanMask2D;
027import icy.roi.BooleanMask3D;
028import icy.roi.ROI;
029import icy.roi.ROI2D;
030import icy.roi.ROI3D;
031import icy.roi.ROIEvent;
032import icy.sequence.Sequence;
033import icy.system.thread.ThreadUtil;
034import icy.type.point.Point3D;
035import icy.type.point.Point5D;
036import icy.type.rectangle.Rectangle3D;
037import icy.util.StringUtil;
038import icy.vtk.IcyVtkPanel;
039import icy.vtk.VtkUtil;
040
041import java.awt.Color;
042import java.awt.Graphics2D;
043import java.awt.event.InputEvent;
044import java.awt.event.MouseEvent;
045import java.awt.geom.Point2D;
046import java.lang.ref.WeakReference;
047import java.util.Arrays;
048import java.util.HashSet;
049import java.util.Map.Entry;
050import java.util.Set;
051
052import plugins.kernel.canvas.VtkCanvas;
053import plugins.kernel.roi.roi2d.ROI2DArea;
054import vtk.vtkActor;
055import vtk.vtkImageData;
056import vtk.vtkInformation;
057import vtk.vtkPolyData;
058import vtk.vtkPolyDataMapper;
059import vtk.vtkProp;
060
061/**
062 * 3D Area ROI.
063 * 
064 * @author Stephane
065 */
066public class ROI3DArea extends ROI3DStack<ROI2DArea>
067{
068    public class ROI3DAreaPainter extends ROI3DStackPainter implements Runnable
069    {
070        // VTK 3D objects
071        protected vtkPolyData outline;
072        protected vtkPolyDataMapper outlineMapper;
073        protected vtkActor outlineActor;
074        protected vtkInformation vtkInfo;
075        protected vtkPolyData polyData;
076        protected vtkPolyDataMapper polyMapper;
077        protected vtkActor surfaceActor;
078        // 3D internal
079        protected boolean needRebuild;
080        protected double scaling[];
081        protected WeakReference<VtkCanvas> canvas3d;
082
083        public ROI3DAreaPainter()
084        {
085            super();
086
087            outline = null;
088            outlineMapper = null;
089            outlineActor = null;
090            vtkInfo = null;
091            polyData = null;
092            polyMapper = null;
093            surfaceActor = null;
094
095            scaling = new double[3];
096            Arrays.fill(scaling, 1d);
097
098            needRebuild = true;
099            canvas3d = new WeakReference<VtkCanvas>(null);
100        }
101
102        @Override
103        protected void finalize() throws Throwable
104        {
105            super.finalize();
106
107            // release allocated VTK resources
108            if (surfaceActor != null)
109                surfaceActor.Delete();
110            if (polyMapper != null)
111                polyMapper.Delete();
112            if (polyData != null)
113            {
114                polyData.GetPointData().GetScalars().Delete();
115                polyData.GetPointData().Delete();
116                polyData.Delete();
117            }
118            if (outlineActor != null)
119            {
120                outlineActor.SetPropertyKeys(null);
121                outlineActor.Delete();
122            }
123            if (vtkInfo != null)
124            {
125                vtkInfo.Remove(VtkCanvas.visibilityKey);
126                vtkInfo.Delete();
127            }
128            if (outlineMapper != null)
129                outlineMapper.Delete();
130            if (outline != null)
131            {
132                outline.GetPointData().GetScalars().Delete();
133                outline.GetPointData().Delete();
134                outline.Delete();
135            }
136        };
137
138        protected void initVtkObjects()
139        {
140            outline = VtkUtil.getOutline(0d, 1d, 0d, 1d, 0d, 1d);
141            outlineMapper = new vtkPolyDataMapper();
142            outlineMapper.SetInputData(outline);
143            outlineActor = new vtkActor();
144            outlineActor.SetMapper(outlineMapper);
145            // disable picking on the outline
146            outlineActor.SetPickable(0);
147            // and set it to wireframe representation
148            outlineActor.GetProperty().SetRepresentationToWireframe();
149            // use vtkInformations to store outline visibility state (hacky)
150            vtkInfo = new vtkInformation();
151            vtkInfo.Set(VtkCanvas.visibilityKey, 0);
152            // VtkCanvas use this to restore correctly outline visibility flag
153            outlineActor.SetPropertyKeys(vtkInfo);
154
155            polyMapper = new vtkPolyDataMapper();
156            surfaceActor = new vtkActor();
157            surfaceActor.SetMapper(polyMapper);
158
159            final Color col = getColor();
160            final double r = col.getRed() / 255d;
161            final double g = col.getGreen() / 255d;
162            final double b = col.getBlue() / 255d;
163
164            // set actors color
165            outlineActor.GetProperty().SetColor(r, g, b);
166            surfaceActor.GetProperty().SetColor(r, g, b);
167        }
168
169        /**
170         * rebuild VTK objects (called only when VTK canvas is selected).
171         */
172        protected void rebuildVtkObjects()
173        {
174            final VtkCanvas canvas = canvas3d.get();
175            // canvas was closed
176            if (canvas == null)
177                return;
178
179            final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
180            // canvas was closed
181            if (vtkPanel == null)
182                return;
183
184            final Sequence seq = canvas.getSequence();
185            // nothing to update
186            if (seq == null)
187                return;
188
189            // get previous polydata object
190            final vtkPolyData previousPolyData = polyData;
191
192            // get VTK binary image from ROI mask
193            final vtkImageData imageData = VtkUtil.getBinaryImageData(ROI3DArea.this, seq.getSizeZ(),
194                    canvas.getPositionT());
195            // adjust spacing
196            imageData.SetSpacing(scaling[0], scaling[1], scaling[2]);
197            // get VTK polygon data representing the surface of the binary image
198            polyData = VtkUtil.getSurfaceFromImage(imageData, 0.5d);
199
200            // get bounds
201            final Rectangle3D bounds = getBounds3D();
202            // apply scaling on bounds
203            bounds.setX(bounds.getX() * scaling[0]);
204            bounds.setSizeX(bounds.getSizeX() * scaling[0]);
205            bounds.setY(bounds.getY() * scaling[1]);
206            bounds.setSizeY(bounds.getSizeY() * scaling[1]);
207            if (bounds.isInfiniteZ())
208            {
209                bounds.setZ(0);
210                bounds.setSizeZ(seq.getSizeZ() * scaling[2]);
211            }
212            else
213            {
214                bounds.setZ(bounds.getZ() * scaling[2]);
215                bounds.setSizeZ(bounds.getSizeZ() * scaling[2]);
216            }
217
218            // actor can be accessed in canvas3d for rendering so we need to synchronize access
219            vtkPanel.lock();
220            try
221            {
222                // update outline data
223                VtkUtil.setOutlineBounds(outline, bounds.getMinX(), bounds.getMaxX(), bounds.getMinY(),
224                        bounds.getMaxY(), bounds.getMinZ(), bounds.getMaxZ(), canvas);
225                outlineMapper.Update();
226                // update surface polygon data
227                polyMapper.SetInputData(polyData);
228                polyMapper.Update();
229
230                // update actor position
231                surfaceActor.SetPosition(bounds.getX(), bounds.getY(), bounds.getZ());
232
233                // release image data
234                imageData.GetPointData().GetScalars().Delete();
235                imageData.GetPointData().Delete();
236                imageData.Delete();
237
238                // release previous polydata
239                if (previousPolyData != null)
240                {
241                    previousPolyData.GetPointData().GetScalars().Delete();
242                    previousPolyData.GetPointData().Delete();
243                    previousPolyData.Delete();
244                }
245            }
246            finally
247            {
248                vtkPanel.unlock();
249            }
250
251            // update color and others properties
252            updateVtkDisplayProperties();
253        }
254
255        protected void updateVtkDisplayProperties()
256        {
257            if (surfaceActor == null)
258                return;
259
260            final VtkCanvas cnv = canvas3d.get();
261            final Color col = getDisplayColor();
262            final double r = col.getRed() / 255d;
263            final double g = col.getGreen() / 255d;
264            final double b = col.getBlue() / 255d;
265            // final double strk = getStroke();
266            // final float opacity = getOpacity();
267
268            final IcyVtkPanel vtkPanel = (cnv != null) ? cnv.getVtkPanel() : null;
269
270            // we need to lock canvas as actor can be accessed during rendering
271            if (vtkPanel != null)
272                vtkPanel.lock();
273            try
274            {
275                // set actors color
276                outlineActor.GetProperty().SetColor(r, g, b);
277                if (isSelected())
278                {
279                    outlineActor.GetProperty().SetRepresentationToWireframe();
280                    outlineActor.SetVisibility(1);
281                    vtkInfo.Set(VtkCanvas.visibilityKey, 1);
282                }
283                else
284                {
285                    outlineActor.GetProperty().SetRepresentationToPoints();
286                    outlineActor.SetVisibility(0);
287                    vtkInfo.Set(VtkCanvas.visibilityKey, 0);
288                }
289                surfaceActor.GetProperty().SetColor(r, g, b);
290                // opacity here is about ROI content, global opacity is handled by Layer
291                // surfaceActor.GetProperty().SetOpacity(opacity);
292                setVtkObjectsColor(col);
293            }
294            finally
295            {
296                if (vtkPanel != null)
297                    vtkPanel.unlock();
298            }
299
300            // need to repaint
301            painterChanged();
302        }
303
304        protected void setVtkObjectsColor(Color color)
305        {
306            if (outline != null)
307                VtkUtil.setPolyDataColor(outline, color, canvas3d.get());
308            if (polyData != null)
309                VtkUtil.setPolyDataColor(polyData, color, canvas3d.get());
310        }
311
312        @Override
313        public void mouseClick(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas)
314        {
315            // provide backward compatibility
316            if (imagePoint != null)
317                mouseClick(e, imagePoint.toPoint2D(), canvas);
318            else
319                mouseClick(e, (Point2D) null, canvas);
320
321            // not yet consumed...
322            if (!e.isConsumed())
323            {
324                // and process ROI stuff now
325                if (isActiveFor(canvas))
326                {
327                    final int clickCount = e.getClickCount();
328
329                    // double click
330                    if (clickCount == 2)
331                    {
332                        // focused ?
333                        if (isFocused())
334                        {
335                            // show in ROI panel
336                            final RoisPanel roiPanel = Icy.getMainInterface().getRoisPanel();
337
338                            if (roiPanel != null)
339                            {
340                                roiPanel.scrollTo(ROI3DArea.this);
341                                // consume event
342                                e.consume();
343                            }
344                        }
345                    }
346                }
347            }
348        }
349
350        @Override
351        public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
352        {
353            super.paint(g, sequence, canvas);
354
355            if (isActiveFor(canvas))
356            {
357                if (canvas instanceof VtkCanvas)
358                {
359                    // 3D canvas
360                    final VtkCanvas cnv = (VtkCanvas) canvas;
361                    // update reference if needed
362                    if (canvas3d.get() != cnv)
363                        canvas3d = new WeakReference<VtkCanvas>(cnv);
364
365                    // FIXME : need a better implementation
366                    final double[] s = cnv.getVolumeScale();
367
368                    // scaling changed ?
369                    if (!Arrays.equals(scaling, s))
370                    {
371                        // update scaling
372                        scaling = s;
373                        // need rebuild
374                        needRebuild = true;
375                    }
376
377                    // need to rebuild 3D data structures ?
378                    if (needRebuild)
379                    {
380                        // initialize VTK objects if not yet done
381                        if (surfaceActor == null)
382                            initVtkObjects();
383
384                        // request rebuild 3D objects
385                        ThreadUtil.runSingle(this);
386                        needRebuild = false;
387                    }
388                }
389            }
390        }
391
392        @Override
393        protected boolean updateFocus(InputEvent e, Point5D imagePoint, IcyCanvas canvas)
394        {
395            // specific VTK canvas processing
396            if (canvas instanceof VtkCanvas)
397            {
398                // mouse is over the ROI actor ? --> focus the ROI
399                final boolean focused = (surfaceActor != null)
400                        && (surfaceActor == ((VtkCanvas) canvas).getPickedObject());
401
402                setFocused(focused);
403
404                return focused;
405            }
406
407            return super.updateFocus(e, imagePoint, canvas);
408        }
409
410        @Override
411        public vtkProp[] getProps()
412        {
413            // initialize VTK objects if not yet done
414            if (surfaceActor == null)
415                initVtkObjects();
416
417            return new vtkActor[] {surfaceActor, outlineActor};
418        }
419
420        @Override
421        public void run()
422        {
423            rebuildVtkObjects();
424        }
425    }
426
427    public ROI3DArea()
428    {
429        super(ROI2DArea.class);
430    }
431
432    public ROI3DArea(Point3D pt)
433    {
434        this();
435
436        addBrush(pt.toPoint2D(), (int) pt.getZ());
437    }
438
439    public ROI3DArea(Point5D pt)
440    {
441        this(pt.toPoint3D());
442    }
443
444    /**
445     * Create a 3D Area ROI type from the specified {@link BooleanMask3D}.
446     */
447    public ROI3DArea(BooleanMask3D mask)
448    {
449        this();
450
451        setAsBooleanMask(mask);
452    }
453
454    /**
455     * Create a copy of the specified 3D Area ROI.
456     */
457    public ROI3DArea(ROI3DArea area)
458    {
459        this();
460
461        // copy the source 3D area ROI
462        for (Entry<Integer, ROI2DArea> entry : area.slices.entrySet())
463            slices.put(entry.getKey(), new ROI2DArea(entry.getValue()));
464
465        roiChanged(true);
466    }
467
468    /**
469     * Create a 3D Area ROI type from the specified {@link BooleanMask3D}.
470     */
471    public ROI3DArea(BooleanMask2D mask2d, int zMin, int zMax)
472    {
473        this();
474
475        if (zMax < zMin)
476            throw new IllegalArgumentException("ROI3DArea: cannot create the ROI (zMax < zMin).");
477
478        beginUpdate();
479        try
480        {
481            for (int z = zMin; z <= zMax; z++)
482                setSlice(z, new ROI2DArea(mask2d));
483        }
484        finally
485        {
486            endUpdate();
487        }
488    }
489
490    @Override
491    public String getDefaultName()
492    {
493        return "Area3D";
494    }
495
496    @Override
497    protected ROIPainter createPainter()
498    {
499        return new ROI3DAreaPainter();
500    }
501
502    /**
503     * Adds the specified point to this ROI
504     */
505    public void addPoint(int x, int y, int z)
506    {
507        setPoint(x, y, z, true);
508    }
509
510    /**
511     * Remove a point from the mask.<br>
512     * Don't forget to call optimizeBounds() after consecutive remove operation
513     * to refresh the mask bounds.
514     */
515    public void removePoint(int x, int y, int z)
516    {
517        setPoint(x, y, z, false);
518    }
519
520    /**
521     * Set the value for the specified point in the mask.
522     * Don't forget to call optimizeBounds() after consecutive remove point operation
523     * to refresh the mask bounds.
524     */
525    public void setPoint(int x, int y, int z, boolean value)
526    {
527        final ROI2DArea slice = getSlice(z, value);
528
529        if (slice != null)
530            slice.setPoint(x, y, value);
531    }
532
533    /**
534     * Add brush point at specified position and for specified Z slice.
535     */
536    public void addBrush(Point2D pos, int z)
537    {
538        getSlice(z, true).addBrush(pos);
539    }
540
541    /**
542     * Remove brush point from the mask at specified position and for specified Z slice.<br>
543     * Don't forget to call optimizeBounds() after consecutive remove operation
544     * to refresh the mask bounds.
545     */
546    public void removeBrush(Point2D pos, int z)
547    {
548        final ROI2DArea slice = getSlice(z, false);
549
550        if (slice != null)
551            slice.removeBrush(pos);
552    }
553
554    /**
555     * Add the specified {@link BooleanMask3D} content to this ROI3DArea
556     */
557    public void add(BooleanMask3D mask)
558    {
559        beginUpdate();
560        try
561        {
562            for (Entry<Integer, BooleanMask2D> entry : mask.mask.entrySet())
563                add(entry.getKey().intValue(), entry.getValue());
564        }
565        finally
566        {
567            endUpdate();
568        }
569    }
570
571    /**
572     * Add the specified BooleanMask2D with the existing slice at given Z position.<br>
573     * If there is no slice at this Z position then the method is equivalent to {@link #setSlice(int, ROI2DArea)} with
574     * <code>new ROI2DArea(maskSlice)</code>
575     * 
576     * @param z
577     *        the position where the slice must be added
578     * @param maskSlice
579     *        the 2D boolean mask to merge
580     */
581    public void add(int z, BooleanMask2D maskSlice)
582    {
583        if (maskSlice == null)
584            return;
585
586        final ROI2DArea currentSlice = getSlice(z);
587
588        if (currentSlice != null)
589            // merge slices
590            currentSlice.add(maskSlice);
591        else
592            // add new slice
593            setSlice(z, new ROI2DArea(maskSlice));
594    }
595
596    /**
597     * Exclusively add the specified {@link BooleanMask3D} content to this ROI3DArea
598     */
599    public void exclusiveAdd(BooleanMask3D mask)
600    {
601        beginUpdate();
602        try
603        {
604            for (Entry<Integer, BooleanMask2D> entry : mask.mask.entrySet())
605                exclusiveAdd(entry.getKey().intValue(), entry.getValue());
606        }
607        finally
608        {
609            endUpdate();
610        }
611    }
612
613    /**
614     * Exclusively add the specified BooleanMask2D with the existing slice at given Z position.<br>
615     * If there is no slice at this Z position then the method is equivalent to {@link #setSlice(int, ROI2DArea)} with
616     * <code>new ROI2DArea(maskSlice)</code>
617     * 
618     * @param z
619     *        the position where the slice must be exclusively added
620     * @param maskSlice
621     *        the 2D boolean mask to merge
622     */
623    public void exclusiveAdd(int z, BooleanMask2D maskSlice)
624    {
625        if (maskSlice == null)
626            return;
627
628        final ROI2DArea currentSlice = getSlice(z);
629
630        // merge both slice
631        if (currentSlice != null)
632        {
633            // process exclusive add
634            currentSlice.exclusiveAdd(maskSlice);
635            // remove it if empty
636            if (currentSlice.isEmpty())
637                removeSlice(z);
638        }
639        // add new slice
640        else
641            setSlice(z, new ROI2DArea(maskSlice));
642    }
643
644    /**
645     * Intersect the specified {@link BooleanMask3D} content with this ROI3DArea
646     */
647    public void intersect(BooleanMask3D mask)
648    {
649        beginUpdate();
650        try
651        {
652            final Set<Integer> keys = mask.mask.keySet();
653            final Set<Integer> toRemove = new HashSet<Integer>();
654
655            // remove slices which are not contained
656            for (Integer key : slices.keySet())
657                if (!keys.contains(key))
658                    toRemove.add(key);
659
660            // do remove first
661            for (Integer key : toRemove)
662                removeSlice(key.intValue());
663
664            // then process intersection
665            for (Entry<Integer, BooleanMask2D> entry : mask.mask.entrySet())
666                intersect(entry.getKey().intValue(), entry.getValue());
667        }
668        finally
669        {
670            endUpdate();
671        }
672    }
673
674    /**
675     * Intersect the specified BooleanMask2D with the existing slice at given Z position.
676     * 
677     * @param z
678     *        the position where the slice must be set
679     * @param maskSlice
680     *        the 2D boolean mask to merge
681     */
682    public void intersect(int z, BooleanMask2D maskSlice)
683    {
684        // better to throw an exception here than removing slice
685        if (maskSlice == null)
686            throw new IllegalArgumentException("Cannot intersect an empty slice in a 3D ROI");
687
688        final ROI2DArea currentSlice = getSlice(z);
689
690        if (currentSlice != null)
691        {
692            // build ROI from mask
693            final ROI2DArea roi = new ROI2DArea(maskSlice);
694
695            // set same position as slice
696            roi.setT(currentSlice.getT());
697            roi.setZ(currentSlice.getZ());
698            roi.setC(currentSlice.getC());
699            // compute intersection
700            currentSlice.intersect(roi, false);
701
702            // remove it if empty
703            if (currentSlice.isEmpty())
704                removeSlice(z);
705        }
706    }
707
708    /**
709     * Subtract the specified {@link BooleanMask3D} from this ROI3DArea
710     */
711    public void subtract(BooleanMask3D mask)
712    {
713        beginUpdate();
714        try
715        {
716            for (Entry<Integer, BooleanMask2D> entry : mask.mask.entrySet())
717                subtract(entry.getKey().intValue(), entry.getValue());
718        }
719        finally
720        {
721            endUpdate();
722        }
723    }
724
725    /**
726     * Subtract the specified BooleanMask2D from the existing slice at given Z position.<br>
727     * 
728     * @param z
729     *        the position where the slice must be subtracted
730     * @param maskSlice
731     *        the 2D boolean mask to subtract
732     */
733    public void subtract(int z, BooleanMask2D maskSlice)
734    {
735        if (maskSlice == null)
736            return;
737
738        final ROI2DArea currentSlice = getSlice(z);
739
740        // merge both slice
741        if (currentSlice != null)
742        {
743            // process exclusive add
744            currentSlice.subtract(maskSlice);
745            // remove it if empty
746            if (currentSlice.isEmpty())
747                removeSlice(z);
748        }
749    }
750
751    @Override
752    public ROI add(ROI roi, boolean allowCreate) throws UnsupportedOperationException
753    {
754        if (roi instanceof ROI3D)
755        {
756            final ROI3D roi3d = (ROI3D) roi;
757
758            // only if on same position
759            if ((getT() == roi3d.getT()) && (getC() == roi3d.getC()))
760            {
761                if (roi3d instanceof ROI3DArea)
762                    add((ROI3DArea) roi3d);
763                else
764                    add(roi3d.getBooleanMask(true));
765
766                return this;
767            }
768        }
769
770        return super.add(roi, allowCreate);
771    }
772
773    @Override
774    public ROI exclusiveAdd(ROI roi, boolean allowCreate) throws UnsupportedOperationException
775    {
776        if (roi instanceof ROI3D)
777        {
778            final ROI3D roi3d = (ROI3D) roi;
779
780            // only if on same position
781            if ((getT() == roi3d.getT()) && (getC() == roi3d.getC()))
782            {
783                if (roi3d instanceof ROI3DArea)
784                    exclusiveAdd((ROI3DArea) roi3d);
785                else
786                    exclusiveAdd(roi3d.getBooleanMask(true));
787
788                return this;
789            }
790        }
791
792        return super.exclusiveAdd(roi, allowCreate);
793    }
794
795    @Override
796    public ROI intersect(ROI roi, boolean allowCreate) throws UnsupportedOperationException
797    {
798        if (roi instanceof ROI3D)
799        {
800            final ROI3D roi3d = (ROI3D) roi;
801
802            // only if on same position
803            if ((getT() == roi3d.getT()) && (getC() == roi3d.getC()))
804            {
805                if (roi3d instanceof ROI3DArea)
806                    intersect((ROI3DArea) roi3d);
807                else
808                    intersect(roi3d.getBooleanMask(true));
809
810                return this;
811            }
812        }
813
814        return super.intersect(roi, allowCreate);
815    }
816
817    @Override
818    public ROI subtract(ROI roi, boolean allowCreate) throws UnsupportedOperationException
819    {
820        if (roi instanceof ROI3D)
821        {
822            final ROI3D roi3d = (ROI3D) roi;
823
824            // only if on same position
825            if ((getT() == roi3d.getT()) && (getC() == roi3d.getC()))
826            {
827                if (roi3d instanceof ROI3DArea)
828                    subtract((ROI3DArea) roi3d);
829                else
830                    subtract(roi3d.getBooleanMask(true));
831
832                return this;
833            }
834        }
835
836        return super.subtract(roi, allowCreate);
837    }
838
839    /**
840     * Sets the BooleanMask2D slice at given Z position to this 3D ROI
841     * 
842     * @param z
843     *        the position where the slice must be set
844     * @param maskSlice
845     *        the BooleanMask2D to set
846     */
847    public void setSlice(int z, BooleanMask2D maskSlice)
848    {
849        // empty mask --> just remove previous
850        if (maskSlice == null)
851        {
852            removeSlice(z);
853            return;
854        }
855
856        setSlice(z, new ROI2DArea(maskSlice));
857    }
858
859    /**
860     * @deprecated Use one of these methods instead :<br>
861     *             {@link #setSlice(int, ROI2DArea)}, {@link #setSlice(int, BooleanMask2D)},
862     *             {@link #add(int, ROI2DArea)} or {@link #add(BooleanMask3D)}
863     */
864    @Deprecated
865    public void setSlice(int z, ROI2D roiSlice, boolean merge)
866    {
867        if (roiSlice == null)
868            throw new IllegalArgumentException("Cannot add an empty slice in a 3D ROI");
869
870        final ROI2DArea currentSlice = getSlice(z);
871        final ROI newSlice;
872
873        // merge both slice
874        if ((currentSlice != null) && merge)
875        {
876            // we need to modify the Z, T and C position so we do the merge correctly
877            roiSlice.setZ(z);
878            roiSlice.setT(getT());
879            roiSlice.setC(getC());
880            // do ROI union
881            newSlice = currentSlice.getUnion(roiSlice);
882        }
883        else
884            newSlice = roiSlice;
885
886        if (newSlice instanceof ROI2DArea)
887            setSlice(z, (ROI2DArea) newSlice);
888        else if (newSlice instanceof ROI2D)
889            setSlice(z, new ROI2DArea(((ROI2D) newSlice).getBooleanMask(true)));
890        else
891            throw new IllegalArgumentException(
892                    "Can't add the result of the merge operation on 2D slice " + z + ": " + newSlice.getClassName());
893    }
894
895    // /**
896    // * Merge the specified ROI with the existing slice at given Z position.<br>
897    // * If there is no slice at this Z position then the method is equivalent to {@link #setSlice(int, ROI2DArea)}
898    // *
899    // * @param z
900    // * the position where the slice must be set
901    // * @param roiSlice
902    // * the 2D ROI to merge
903    // */
904    // public void addROI2DSlice(int z, ROI2D roiSlice)
905    // {
906    // if (roiSlice == null)
907    // return;
908    //
909    // final ROI2DArea currentSlice = getSlice(z);
910    //
911    // // merge both slice
912    // if (currentSlice != null)
913    // {
914    // // we need to modify the Z, T and C position so we do the merge correctly
915    // roiSlice.setZ(z);
916    // roiSlice.setT(getT());
917    // roiSlice.setC(getC());
918    // // do ROI union
919    // currentSlice.add(roiSlice, true);
920    // }
921    // else
922    // setSlice(z, new ROI2DArea(roiSlice.getBooleanMask(true)));
923    // }
924
925    /**
926     * Returns true if the ROI is empty (the mask does not contains any point).
927     */
928    @Override
929    public boolean isEmpty()
930    {
931        for (ROI2DArea area : slices.values())
932            if (!area.isEmpty())
933                return false;
934
935        return true;
936    }
937
938    /**
939     * @deprecated Use {@link #getBooleanMask(boolean)} and {@link BooleanMask3D#getContourPoints()} instead.
940     */
941    @Deprecated
942    public Point3D[] getEdgePoints()
943    {
944        return getBooleanMask(true).getContourPoints();
945    }
946
947    /**
948     * @deprecated Use {@link #getBooleanMask(boolean)} and {@link BooleanMask3D#getPoints()} instead.
949     */
950    @Deprecated
951    public Point3D[] getPoints()
952    {
953        return getBooleanMask(true).getPoints();
954    }
955
956    /**
957     * @deprecated Use {@link #translate(double, double, double)} instead.
958     */
959    @Deprecated
960    public void translate(double dx, double dy)
961    {
962        beginUpdate();
963        try
964        {
965            for (ROI2DArea slice : slices.values())
966                slice.translate(dx, dy);
967        }
968        finally
969        {
970            endUpdate();
971        }
972    }
973
974    @Override
975    public boolean isOverEdge(IcyCanvas canvas, double x, double y, double z)
976    {
977        final ROI2DArea slice = getSlice((int) z);
978
979        if (slice != null)
980            return slice.isOverEdge(canvas, x, y);
981
982        return false;
983    }
984
985    /**
986     * Set all 2D slices ROI to same position.<br>
987     */
988    public void setPosition2D(Point2D newPosition)
989    {
990        beginUpdate();
991        try
992        {
993            for (ROI2DArea slice : slices.values())
994                slice.setPosition2D(newPosition);
995        }
996        finally
997        {
998            endUpdate();
999        }
1000    }
1001
1002    /**
1003     * Set the mask from a BooleanMask3D object.<br>
1004     * If specified mask is <i>null</i> then ROI is cleared.
1005     */
1006    public void setAsBooleanMask(BooleanMask3D mask)
1007    {
1008        // mask empty ? --> just clear the ROI
1009        if ((mask == null) || mask.isEmpty())
1010            clear();
1011        else
1012        {
1013            final Rectangle3D.Integer bounds3d = mask.bounds;
1014            final int startZ = bounds3d.z;
1015            final int sizeZ = bounds3d.sizeZ;
1016            final BooleanMask2D masks2d[] = new BooleanMask2D[sizeZ];
1017
1018            for (int z = 0; z < sizeZ; z++)
1019                masks2d[z] = mask.getMask2D(startZ + z);
1020
1021            setAsBooleanMask(bounds3d, masks2d);
1022        }
1023    }
1024
1025    /**
1026     * Set the 3D mask from a 2D boolean mask array
1027     * 
1028     * @param rect
1029     *        the 3D region defined by 2D boolean mask array
1030     * @param mask
1031     *        the 3D mask data (array length should be equals to rect.sizeZ)
1032     */
1033    public void setAsBooleanMask(Rectangle3D.Integer rect, BooleanMask2D[] mask)
1034    {
1035        if (rect.isInfiniteZ())
1036            throw new IllegalArgumentException("Cannot set infinite Z dimension on the 3D Area ROI.");
1037
1038        beginUpdate();
1039        try
1040        {
1041            clear();
1042
1043            for (int z = 0; z < rect.sizeZ; z++)
1044                setSlice(z + rect.z, new ROI2DArea(mask[z]));
1045        }
1046        finally
1047        {
1048            endUpdate();
1049        }
1050    }
1051
1052    /**
1053     * Fast up scaling by a factor of 2 (each point become a 2x2x2 block points)
1054     */
1055    public void upscale()
1056    {
1057        setAsBooleanMask(getBooleanMask(true).upscale());
1058    }
1059
1060    /**
1061     * Fast 2x down scaling (each 2x2x2 block points become 1 point).<br>
1062     * 
1063     * @param nbPointForTrue
1064     *        the minimum number of <code>true</code>points from a 2x2x2 block to give a <code>true</code> resulting
1065     *        point.<br>
1066     *        Accepted value: 1 to 5 (default is 5)
1067     */
1068    public void downscale(int nbPointForTrue)
1069    {
1070        setAsBooleanMask(getBooleanMask(true).downscale(nbPointForTrue));
1071    }
1072
1073    /**
1074     * Fast 2x down scaling (each 2x2x2 block points become 1 point).<br>
1075     */
1076    public void downscale()
1077    {
1078        setAsBooleanMask(getBooleanMask(true).downscale());
1079    }
1080
1081    /**
1082     * Fast up scaling by a factor of 2 (each point become a 2x2 block points)
1083     * 2D version (down scale is done on XY dimension only).<br>
1084     */
1085    public void upscale2D()
1086    {
1087        setAsBooleanMask(getBooleanMask(true).upscale2D());
1088    }
1089
1090    /**
1091     * Fast 2x down scaling (each 2x2 block points become 1 point).<br>
1092     * 2D version (down scale is done on XY dimension only).<br>
1093     * 
1094     * @param nbPointForTrue
1095     *        the minimum number of <code>true</code>points from a 2x2 block to give a <code>true</code> resulting
1096     *        point.<br>
1097     *        Accepted value: 1 to 4
1098     */
1099    public void downscale2D(int nbPointForTrue)
1100    {
1101        setAsBooleanMask(getBooleanMask(true).downscale2D(nbPointForTrue));
1102    }
1103
1104    /**
1105     * Fast 2x down scaling (each 2x2 block points become 1 point).<br>
1106     * 2D version (down scale is done on XY dimension only).<br>
1107     */
1108    public void downscale2D()
1109    {
1110        setAsBooleanMask(getBooleanMask(true).downscale2D());
1111    }
1112
1113    /**
1114     * Optimize the bounds size to the minimum surface which still include all mask.<br>
1115     * You should call it after consecutive remove operations if you directly addressed mask data.
1116     */
1117    public void optimizeBounds()
1118    {
1119        final Rectangle3D.Integer bounds = getBounds();
1120
1121        beginUpdate();
1122        try
1123        {
1124            for (int z = bounds.z; z < bounds.z + bounds.sizeZ; z++)
1125            {
1126                final ROI2DArea roi = getSlice(z);
1127
1128                if (roi != null)
1129                {
1130                    if (roi.isEmpty())
1131                        removeSlice(z);
1132                    else
1133                    {
1134                        if (roi.optimizeBounds())
1135                            roi.roiChanged(true);
1136                    }
1137                }
1138            }
1139        }
1140        finally
1141        {
1142            endUpdate();
1143        }
1144    }
1145
1146    /**
1147     * roi changed
1148     */
1149    @Override
1150    public void onChanged(CollapsibleEvent object)
1151    {
1152        final ROIEvent event = (ROIEvent) object;
1153
1154        // do here global process on ROI change
1155        switch (event.getType())
1156        {
1157            case ROI_CHANGED:
1158                // the painter need to be rebuild
1159                ((ROI3DAreaPainter) painter).needRebuild = true;
1160                break;
1161
1162            case FOCUS_CHANGED:
1163            case SELECTION_CHANGED:
1164                ((ROI3DAreaPainter) getOverlay()).updateVtkDisplayProperties();
1165                break;
1166
1167            case PROPERTY_CHANGED:
1168                final String property = event.getPropertyName();
1169
1170                if (StringUtil.equals(property, PROPERTY_STROKE) || StringUtil.equals(property, PROPERTY_COLOR)
1171                        || StringUtil.equals(property, PROPERTY_OPACITY))
1172                    ((ROI3DAreaPainter) getOverlay()).updateVtkDisplayProperties();
1173                break;
1174
1175            default:
1176                break;
1177        }
1178
1179        super.onChanged(object);
1180    }
1181}