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.roi2d;
020
021import icy.canvas.IcyCanvas;
022import icy.canvas.IcyCanvas2D;
023import icy.common.CollapsibleEvent;
024import icy.painter.Anchor2D;
025import icy.painter.OverlayEvent;
026import icy.painter.OverlayEvent.OverlayEventType;
027import icy.resource.ResourceUtil;
028import icy.roi.ROI;
029import icy.roi.ROIEvent;
030import icy.sequence.Sequence;
031import icy.type.point.Point5D;
032import icy.util.StringUtil;
033import icy.util.XMLUtil;
034import icy.vtk.IcyVtkPanel;
035
036import java.awt.Color;
037import java.awt.Graphics2D;
038import java.awt.geom.Ellipse2D;
039import java.awt.geom.Line2D;
040import java.awt.geom.Point2D;
041import java.awt.geom.Rectangle2D;
042
043import org.w3c.dom.Node;
044
045import plugins.kernel.canvas.VtkCanvas;
046import vtk.vtkActor;
047import vtk.vtkPolyDataMapper;
048import vtk.vtkSphereSource;
049
050/**
051 * ROI 2D Point class.<br>
052 * Define a single point ROI<br>
053 * 
054 * @author Stephane Dallongeville
055 */
056public class ROI2DPoint extends ROI2DShape
057{
058    public class ROI2DPointPainter extends ROI2DShapePainter
059    {
060        vtkSphereSource vtkSource;
061
062        @Override
063        protected void finalize() throws Throwable
064        {
065            super.finalize();
066
067            // release extra VTK objects
068            if (vtkSource != null)
069                vtkSource.Delete();
070        }
071
072        @Override
073        protected boolean isSmall(Rectangle2D bounds, Graphics2D g, IcyCanvas canvas)
074        {
075            if (isSelected())
076                return false;
077
078            return true;
079        }
080
081        @Override
082        protected boolean isTiny(Rectangle2D bounds, Graphics2D g, IcyCanvas canvas)
083        {
084            if (isSelected())
085                return false;
086
087            return true;
088        }
089
090        @Override
091        public void drawROI(Graphics2D g, Sequence sequence, IcyCanvas canvas)
092        {
093            if (canvas instanceof IcyCanvas2D)
094            {
095                final Graphics2D g2 = (Graphics2D) g.create();
096
097                if (isSelected() && !isReadOnly())
098                {
099                    // draw control point if selected
100                    synchronized (controlPoints)
101                    {
102                        for (Anchor2D pt : controlPoints)
103                            pt.paint(g2, sequence, canvas);
104                    }
105                }
106                else
107                {
108                    final Point2D pos = getPoint();
109                    final double ray = getAdjustedStroke(canvas);
110                    final Ellipse2D ellipse = new Ellipse2D.Double(pos.getX() - ray, pos.getY() - ray, ray * 2,
111                            ray * 2);
112
113                    // draw shape
114                    g2.setColor(getDisplayColor());
115                    g2.fill(ellipse);
116                }
117
118                g2.dispose();
119            }
120            else
121                // just use parent method
122                super.drawROI(g, sequence, canvas);
123        }
124
125        @Override
126        protected void initVtkObjects()
127        {
128            super.initVtkObjects();
129            
130            // init 3D painters stuff
131            vtkSource = new vtkSphereSource();
132            vtkSource.SetRadius(getStroke());
133            vtkSource.SetThetaResolution(12);
134            vtkSource.SetPhiResolution(12);
135
136            // delete previously created objects that we will recreate
137            if (actor != null)
138                actor.Delete();
139            if (polyMapper != null)
140                polyMapper.Delete();
141            
142            polyMapper = new vtkPolyDataMapper();
143            polyMapper.SetInputConnection((vtkSource).GetOutputPort());
144
145            actor = new vtkActor();
146            actor.SetMapper(polyMapper);
147
148            // initialize color
149            final Color col = getColor();
150            actor.GetProperty().SetColor(col.getRed() / 255d, col.getGreen() / 255d, col.getBlue() / 255d);
151        }
152
153        /**
154         * update 3D painter for 3D canvas (called only when VTK is loaded).
155         */
156        @Override
157        protected void rebuildVtkObjects()
158        {
159            final VtkCanvas canvas = canvas3d.get();
160            // canvas was closed
161            if (canvas == null)
162                return;
163
164            final IcyVtkPanel vtkPanel = canvas.getVtkPanel();
165            // canvas was closed
166            if (vtkPanel == null)
167                return;
168
169            final Sequence seq = canvas.getSequence();
170            // nothing to update
171            if (seq == null)
172                return;
173
174            final Point2D pos = getPoint();
175            double curZ = getZ();
176
177            // all slices ?
178            if (curZ == -1)
179                // set object at middle of the volume
180                curZ = seq.getSizeZ() / 2d;
181
182            // actor can be accessed in canvas3d for rendering so we need to synchronize access
183            vtkPanel.lock();
184            try
185            {
186                // need to handle scaling on radius and position to keep a "round" sphere (else we obtain ellipsoid)
187                vtkSource.SetRadius(getStroke() * scaling[0]);
188                vtkSource.SetCenter(pos.getX() * scaling[0], pos.getY() * scaling[1], (curZ + 0.5d) * scaling[2]);
189                polyMapper.Update();
190
191                // vtkSource.SetRadius(getStroke());
192                // vtkSource.SetCenter(pos.getX(), pos.getY(), curZ);
193                // polyMapper.Update();
194                // actor.SetScale(scaling);
195            }
196            finally
197            {
198                vtkPanel.unlock();
199            }
200
201            // need to repaint
202            painterChanged();
203        }
204
205        @Override
206        protected void updateVtkDisplayProperties()
207        {
208            if (actor == null)
209                return;
210
211            final VtkCanvas cnv = canvas3d.get();
212            final Color col = getDisplayColor();
213            final double r = col.getRed() / 255d;
214            final double g = col.getGreen() / 255d;
215            final double b = col.getBlue() / 255d;
216            // final float opacity = getOpacity();
217
218            final IcyVtkPanel vtkPanel = (cnv != null) ? cnv.getVtkPanel() : null;
219
220            // we need to lock canvas as actor can be accessed during rendering
221            if (vtkPanel != null)
222                vtkPanel.lock();
223            try
224            {
225                actor.GetProperty().SetColor(r, g, b);
226            }
227            finally
228            {
229                if (vtkPanel != null)
230                    vtkPanel.unlock();
231            }
232
233            // need to repaint
234            painterChanged();
235        }
236    }
237
238    public static final String ID_POSITION = "position";
239
240    private final Anchor2D position;
241
242    /**
243     * @deprecated
244     */
245    @Deprecated
246    public ROI2DPoint(Point2D pt, boolean cm)
247    {
248        this(pt);
249    }
250
251    public ROI2DPoint(Point2D position)
252    {
253        super(new Line2D.Double());
254
255        this.position = createAnchor(position);
256        this.position.setSelected(true);
257        addPoint(this.position);
258
259        // set icon (default name is defined by getDefaultName())
260        setIcon(ResourceUtil.ICON_ROI_POINT);
261    }
262
263    /**
264     * Generic constructor for interactive mode
265     */
266    public ROI2DPoint(Point5D pt)
267    {
268        this(pt.toPoint2D());
269        // getOverlay().setMousePos(pt);
270    }
271
272    public ROI2DPoint(double x, double y)
273    {
274        this(new Point2D.Double(x, y));
275    }
276
277    public ROI2DPoint()
278    {
279        this(new Point2D.Double());
280    }
281
282    @Override
283    public String getDefaultName()
284    {
285        return "Point2D";
286    }
287
288    @Override
289    protected ROI2DShapePainter createPainter()
290    {
291        return new ROI2DPointPainter();
292    }
293
294    /**
295     * @deprecated Use {@link #getLine()} instead.
296     */
297    @Deprecated
298    public Rectangle2D getRectangle()
299    {
300        final Point2D pt = getPoint();
301        return new Rectangle2D.Double(pt.getX(), pt.getY(), 0d, 0d);
302    }
303
304    public Line2D getLine()
305    {
306        return (Line2D) shape;
307    }
308
309    public Point2D getPoint()
310    {
311        return position.getPosition();
312    }
313
314    /**
315     * Called when anchor overlay changed
316     */
317    @Override
318    public void controlPointOverlayChanged(OverlayEvent event)
319    {
320        // we only mind about painter change from anchor...
321        if (event.getType() == OverlayEventType.PAINTER_CHANGED)
322        {
323            // here we want to have ROI focused when point is selected (special case for ROIPoint)
324            if (hasSelectedPoint())
325                setFocused(true);
326
327            // anchor changed --> ROI painter changed
328            getOverlay().painterChanged();
329        }
330    }
331
332    @Override
333    public boolean contains(double x, double y)
334    {
335        return false;
336    }
337
338    @Override
339    public boolean contains(Point2D p)
340    {
341        return false;
342    }
343
344    @Override
345    public boolean contains(double x, double y, double w, double h)
346    {
347        return false;
348    }
349
350    @Override
351    public boolean contains(Rectangle2D r)
352    {
353        return false;
354    }
355
356    @Override
357    public boolean contains(ROI roi)
358    {
359        return false;
360    }
361
362    @Override
363    public boolean intersects(ROI r)
364    {
365        // special case of ROI2DPoint
366        if (r instanceof ROI2DPoint)
367            return onSamePos(((ROI2DPoint) r), false) && ((ROI2DPoint) r).getPoint().equals(getPoint());
368
369        return super.intersects(r);
370    }
371
372    /**
373     * roi changed
374     */
375    @Override
376    public void onChanged(CollapsibleEvent object)
377    {
378        final ROIEvent event = (ROIEvent) object;
379
380        // do here global process on ROI change
381        switch (event.getType())
382        {
383            case PROPERTY_CHANGED:
384                final String property = event.getPropertyName();
385
386                // stroke changed --> rebuild vtk object
387                if (StringUtil.equals(property, PROPERTY_STROKE))
388                    ((ROI2DShapePainter) getOverlay()).needRebuild = true;
389                break;
390
391            case SELECTION_CHANGED:
392                // always select the control point when ROI was just selected
393                if (isSelected())
394                    position.setSelected(true);
395                break;
396
397            default:
398                break;
399        }
400
401        super.onChanged(object);
402    }
403
404    @Override
405    protected void updateShape()
406    {
407        final Point2D pt = getPoint();
408        final double x = pt.getX();
409        final double y = pt.getY();
410
411        getLine().setLine(x, y, x, y);
412
413        // call super method after shape has been updated
414        super.updateShape();
415    }
416
417    @Override
418    public boolean canAddPoint()
419    {
420        // this ROI doesn't support point add
421        return false;
422    }
423
424    @Override
425    protected boolean removePoint(IcyCanvas canvas, Anchor2D pt)
426    {
427        if (canvas != null)
428        {
429            // remove point on this ROI remove the ROI from current sequence
430            canvas.getSequence().removeROI(this);
431            return true;
432        }
433
434        return false;
435    }
436
437    @Override
438    public double computeNumberOfContourPoints()
439    {
440        return 0d;
441    }
442
443    @Override
444    public double computeNumberOfPoints()
445    {
446        return 0d;
447    }
448
449    @Override
450    public boolean loadFromXML(Node node)
451    {
452        beginUpdate();
453        try
454        {
455            if (!super.loadFromXML(node))
456                return false;
457
458            position.loadPositionFromXML(XMLUtil.getElement(node, ID_POSITION));
459        }
460        finally
461        {
462            endUpdate();
463        }
464
465        return true;
466    }
467
468    @Override
469    public boolean saveToXML(Node node)
470    {
471        if (!super.saveToXML(node))
472            return false;
473
474        position.savePositionToXML(XMLUtil.setElement(node, ID_POSITION));
475
476        return true;
477    }
478}