001package plugins.kernel.canvas;
002
003import java.awt.AWTException;
004import java.awt.BorderLayout;
005import java.awt.Color;
006import java.awt.Component;
007import java.awt.Cursor;
008import java.awt.Graphics;
009import java.awt.Graphics2D;
010import java.awt.Image;
011import java.awt.Point;
012import java.awt.Rectangle;
013import java.awt.Robot;
014import java.awt.event.ActionEvent;
015import java.awt.event.ActionListener;
016import java.awt.event.KeyEvent;
017import java.awt.event.MouseEvent;
018import java.awt.event.MouseWheelEvent;
019import java.awt.image.BufferedImage;
020import java.beans.PropertyChangeEvent;
021import java.util.Arrays;
022import java.util.LinkedList;
023import java.util.List;
024import java.util.concurrent.Callable;
025import java.util.concurrent.LinkedBlockingQueue;
026
027import javax.swing.JToolBar;
028
029import icy.canvas.Canvas3D;
030import icy.canvas.CanvasLayerEvent;
031import icy.canvas.CanvasLayerEvent.LayersEventType;
032import icy.canvas.IcyCanvas;
033import icy.canvas.IcyCanvasEvent;
034import icy.canvas.IcyCanvasEvent.IcyCanvasEventType;
035import icy.canvas.Layer;
036import icy.common.exception.TooLargeArrayException;
037import icy.gui.component.button.IcyToggleButton;
038import icy.gui.util.ComponentUtil;
039import icy.gui.util.GuiUtil;
040import icy.gui.viewer.Viewer;
041import icy.image.IcyBufferedImage;
042import icy.image.lut.LUT;
043import icy.image.lut.LUT.LUTChannel;
044import icy.painter.Overlay;
045import icy.painter.VtkPainter;
046import icy.preferences.CanvasPreferences;
047import icy.preferences.XMLPreferences;
048import icy.resource.ResourceUtil;
049import icy.resource.icon.IcyIcon;
050import icy.roi.ROI;
051import icy.sequence.Sequence;
052import icy.sequence.SequenceEvent.SequenceEventType;
053import icy.system.IcyExceptionHandler;
054import icy.system.thread.ThreadUtil;
055import icy.type.collection.array.Array1DUtil;
056import icy.type.point.Point3D;
057import icy.util.ColorUtil;
058import icy.util.EventUtil;
059import icy.util.StringUtil;
060import icy.vtk.IcyVtkPanel;
061import icy.vtk.VtkImageVolume;
062import icy.vtk.VtkImageVolume.VtkVolumeBlendType;
063import icy.vtk.VtkImageVolume.VtkVolumeMapperType;
064import icy.vtk.VtkUtil;
065import plugins.kernel.canvas.VtkSettingPanel.SettingChangeListener;
066import vtk.vtkActor;
067import vtk.vtkActor2D;
068import vtk.vtkAxesActor;
069import vtk.vtkCamera;
070import vtk.vtkColorTransferFunction;
071import vtk.vtkCubeAxesActor;
072import vtk.vtkImageData;
073import vtk.vtkInformation;
074import vtk.vtkInformationIntegerKey;
075import vtk.vtkLight;
076import vtk.vtkOrientationMarkerWidget;
077import vtk.vtkPicker;
078import vtk.vtkPiecewiseFunction;
079import vtk.vtkProp;
080import vtk.vtkRenderWindow;
081import vtk.vtkRenderer;
082import vtk.vtkTextActor;
083import vtk.vtkTextProperty;
084
085/**
086 * VTK 3D canvas class.
087 * 
088 * @author Stephane
089 */
090@SuppressWarnings("deprecation")
091public class VtkCanvas extends Canvas3D implements ActionListener, SettingChangeListener
092{
093    /**
094     * 
095     */
096    private static final long serialVersionUID = -1274251057822161271L;
097
098    /**
099     * icons
100     */
101    public static final Image ICON_AXES3D = ResourceUtil.getAlphaIconAsImage("axes3d.png");
102    public static final Image ICON_BOUNDINGBOX = ResourceUtil.getAlphaIconAsImage("bbox.png");
103    public static final Image ICON_GRID = ResourceUtil.getAlphaIconAsImage("3x3_grid.png");
104    public static final Image ICON_RULER = ResourceUtil.getAlphaIconAsImage("ruler.png");
105    public static final Image ICON_RULERLABEL = ResourceUtil.getAlphaIconAsImage("ruler_label.png");
106    public static final Image ICON_TARGET = ResourceUtil.getAlphaIconAsImage("target.png");
107
108    /**
109     * properties
110     */
111    public static final String PROPERTY_AXES = "axis";
112    public static final String PROPERTY_BOUNDINGBOX = "boundingBox";
113    public static final String PROPERTY_BOUNDINGBOX_GRID = "boundingBoxGrid";
114    public static final String PROPERTY_BOUNDINGBOX_RULES = "boundingBoxRules";
115    public static final String PROPERTY_BOUNDINGBOX_LABELS = "boundingBoxLabels";
116    public static final String PROPERTY_LUT = "lut";
117    public static final String PROPERTY_DATA = "data";
118    public static final String PROPERTY_SCALE = "scale";
119    public static final String PROPERTY_BOUNDS = "bounds";
120
121    /**
122     * Used for outline visibility information in vtkActor
123     */
124
125    public static final vtkInformationIntegerKey visibilityKey = new vtkInformationIntegerKey().MakeKey("Visibility",
126            "Property");
127    /**
128     * preferences id
129     */
130    protected static final String PREF_ID = "vtkCanvas";
131
132    /**
133     * id
134     */
135    protected static final String ID_BOUNDINGBOX = PROPERTY_BOUNDINGBOX;
136    protected static final String ID_BOUNDINGBOX_GRID = PROPERTY_BOUNDINGBOX_GRID;
137    protected static final String ID_BOUNDINGBOX_RULES = PROPERTY_BOUNDINGBOX_RULES;
138    protected static final String ID_BOUNDINGBOX_LABELS = PROPERTY_BOUNDINGBOX_LABELS;
139    // protected static final String ID_PICKONMOUSEMOVE = "pickOnMouseMove";
140    protected static final String ID_AXES = PROPERTY_AXES;
141    protected static final String ID_SHADING = VtkSettingPanel.PROPERTY_SHADING;
142    protected static final String ID_BGCOLOR = VtkSettingPanel.PROPERTY_BG_COLOR;
143    protected static final String ID_MAPPER = VtkSettingPanel.PROPERTY_MAPPER;
144    protected static final String ID_SAMPLE = VtkSettingPanel.PROPERTY_SAMPLE;
145    protected static final String ID_BLENDING = VtkSettingPanel.PROPERTY_BLENDING;
146    protected static final String ID_INTERPOLATION = VtkSettingPanel.PROPERTY_INTERPOLATION;
147    protected static final String ID_AMBIENT = VtkSettingPanel.PROPERTY_AMBIENT;
148    protected static final String ID_DIFFUSE = VtkSettingPanel.PROPERTY_DIFFUSE;
149    protected static final String ID_SPECULAR = VtkSettingPanel.PROPERTY_SPECULAR;
150
151    /**
152     * basic vtk objects
153     */
154    protected vtkRenderer renderer;
155    protected vtkRenderWindow renderWindow;
156    protected vtkCamera camera;
157    // protected vtkAxesActor axes;
158    protected vtkCubeAxesActor boundingBox;
159    protected vtkCubeAxesActor rulerBox;
160    protected vtkTextActor textInfo;
161    protected vtkTextProperty textProperty;
162    // protected vtkOrientationMarkerWidget widget;
163    protected vtkProp pickedObject;
164
165    /**
166     * volume data
167     */
168    protected VtkImageVolume imageVolume;
169
170    /**
171     * GUI
172     */
173    protected VtkSettingPanel settingPanel;
174    protected CustomVtkPanel panel3D;
175    protected IcyToggleButton axesButton;
176    protected IcyToggleButton boundingBoxButton;
177    protected IcyToggleButton gridButton;
178    protected IcyToggleButton rulerButton;
179    protected IcyToggleButton rulerLabelButton;
180    // protected IcyToggleButton pickOnMouseMoveButton;
181
182    /**
183     * internals
184     */
185    protected PropertiesUpdater propertiesUpdater;
186    protected VtkOverlayUpdater overlayUpdater;
187    protected XMLPreferences preferences;
188    protected final EDTTask<Object> edtTask;
189    protected boolean initialized;
190
191    public VtkCanvas(Viewer viewer)
192    {
193        super(viewer);
194
195        initialized = false;
196
197        // more than 4 channels ? --> not supported by VTK
198        if (getImageSizeC() > 4)
199            throw new UnsupportedOperationException(
200                    "VTK does not support image with more than 4 channels !\nYou should remove some channels in your image.");
201
202        // multi channel view
203        posC = -1;
204        // adjust LUT alpha level for 3D view
205        lut.setAlphaToLinear3D();
206
207        pickedObject = null;
208
209        // create the properties and the VTK overlay updater processors
210        propertiesUpdater = new PropertiesUpdater();
211        overlayUpdater = new VtkOverlayUpdater();
212
213        preferences = CanvasPreferences.getPreferences().node(PREF_ID);
214
215        settingPanel = new VtkSettingPanel();
216        panel = settingPanel;
217
218        // initialize VTK components & main GUI
219        panel3D = new CustomVtkPanel();
220        panel3D.addKeyListener(this);
221        // set 3D view in center
222        add(panel3D, BorderLayout.CENTER);
223
224        // update nav bar & mouse infos
225        mouseInfPanel.setVisible(false);
226        updateZNav();
227        updateTNav();
228
229        // create toolbar buttons
230        axesButton = new IcyToggleButton(new IcyIcon(ICON_AXES3D));
231        axesButton.setFocusable(false);
232        axesButton.setToolTipText("Display 3D axis");
233        boundingBoxButton = new IcyToggleButton(new IcyIcon(ICON_BOUNDINGBOX));
234        boundingBoxButton.setFocusable(false);
235        boundingBoxButton.setToolTipText("Display bounding box");
236        gridButton = new IcyToggleButton(new IcyIcon(ICON_GRID));
237        gridButton.setFocusable(false);
238        gridButton.setToolTipText("Display grid");
239        rulerButton = new IcyToggleButton(new IcyIcon(ICON_RULER));
240        rulerButton.setFocusable(false);
241        rulerButton.setToolTipText("Display rules");
242        rulerLabelButton = new IcyToggleButton(new IcyIcon(ICON_RULERLABEL));
243        rulerLabelButton.setFocusable(false);
244        rulerLabelButton.setToolTipText("Display rules label");
245        // pickOnMouseMoveButton = new IcyToggleButton(new IcyIcon(ICON_TARGET));
246        // pickOnMouseMoveButton.setFocusable(false);
247        // pickOnMouseMoveButton.setToolTipText("Enabled object focus on mouse over (slow)");
248
249        // set fast rendering during initialization
250        panel3D.setCoarseRendering(1000);
251
252        renderer = panel3D.getRenderer();
253        renderWindow = panel3D.getRenderWindow();
254        camera = renderer.GetActiveCamera();
255
256        // initialize text info actor (need to be done before the first getImageData() call !
257        textInfo = new vtkTextActor();
258        textInfo.SetInput("Not enough memory to display this 3D image !");
259        textInfo.SetPosition(10, 10);
260        // not visible by default
261        textInfo.SetVisibility(0);
262
263        // change text properties
264        textProperty = textInfo.GetTextProperty();
265        textProperty.SetFontFamilyToArial();
266
267        // rebuild volume image
268        updateImageData(getImageData());
269
270        final Sequence seq = getSequence();
271        // setup volume scaling
272        if (seq != null)
273            imageVolume.setScale(seq.getPixelSizeX(), seq.getPixelSizeY(), seq.getPixelSizeZ());
274        // setup volume LUT
275        imageVolume.setLUT(getLut());
276
277        // initialize axe
278        // axes = new vtkAxesActor();
279        // widget = new vtkOrientationMarkerWidget();
280        // widget.SetOrientationMarker(axes);
281        // widget.SetInteractor(interactor);
282        // widget.SetViewport(0, 0, 0.3, 0.3);
283        // widget.SetEnabled(1);
284
285        // initialize bounding box
286        boundingBox = new vtkCubeAxesActor();
287        boundingBox.SetBounds(imageVolume.getVolume().GetBounds());
288        boundingBox.SetCamera(camera);
289        // set bounding box labels properties
290        boundingBox.SetFlyModeToStaticEdges();
291        boundingBox.SetUseBounds(true);
292        boundingBox.XAxisLabelVisibilityOff();
293        boundingBox.XAxisMinorTickVisibilityOff();
294        boundingBox.XAxisTickVisibilityOff();
295        boundingBox.YAxisLabelVisibilityOff();
296        boundingBox.YAxisMinorTickVisibilityOff();
297        boundingBox.YAxisTickVisibilityOff();
298        boundingBox.ZAxisLabelVisibilityOff();
299        boundingBox.ZAxisMinorTickVisibilityOff();
300        boundingBox.ZAxisTickVisibilityOff();
301
302        // initialize rules and box axis
303        rulerBox = new vtkCubeAxesActor();
304        rulerBox.SetBounds(imageVolume.getVolume().GetBounds());
305        rulerBox.SetCamera(camera);
306
307        // set bounding box labels properties
308        rulerBox.SetXUnits("micro meter");
309        rulerBox.GetTitleTextProperty(0).SetColor(1.0, 0.0, 0.0);
310        rulerBox.GetLabelTextProperty(0).SetColor(1.0, 0.0, 0.0);
311        rulerBox.GetXAxesGridlinesProperty().SetColor(1.0, 0.0, 0.0);
312        rulerBox.GetXAxesGridpolysProperty().SetColor(1.0, 0.0, 0.0);
313        rulerBox.GetXAxesInnerGridlinesProperty().SetColor(1.0, 0.0, 0.0);
314
315        rulerBox.SetYUnits("micro meter");
316        rulerBox.GetTitleTextProperty(1).SetColor(0.0, 1.0, 0.0);
317        rulerBox.GetLabelTextProperty(1).SetColor(0.0, 1.0, 0.0);
318        rulerBox.GetYAxesGridlinesProperty().SetColor(0.0, 1.0, 0.0);
319        rulerBox.GetYAxesGridpolysProperty().SetColor(0.0, 1.0, 0.0);
320        rulerBox.GetYAxesInnerGridlinesProperty().SetColor(0.0, 1.0, 0.0);
321
322        rulerBox.SetZUnits("micro meter");
323        rulerBox.GetTitleTextProperty(2).SetColor(0.0, 0.0, 1.0);
324        rulerBox.GetLabelTextProperty(2).SetColor(0.0, 0.0, 1.0);
325        rulerBox.GetZAxesGridlinesProperty().SetColor(0.0, 0.0, 1.0);
326        rulerBox.GetZAxesGridpolysProperty().SetColor(0.0, 0.0, 1.0);
327        rulerBox.GetZAxesInnerGridlinesProperty().SetColor(0.0, 0.0, 1.0);
328
329        rulerBox.XAxisVisibilityOff();
330        rulerBox.YAxisVisibilityOff();
331        rulerBox.ZAxisVisibilityOff();
332
333        rulerBox.SetGridLineLocation(VtkUtil.VTK_GRID_LINES_FURTHEST);
334        rulerBox.SetFlyModeToOuterEdges();
335        rulerBox.SetUseBounds(true);
336
337        // restore settings
338        settingPanel.setBackgroundColor(new Color(preferences.getInt(ID_BGCOLOR, 0x000000)));
339        settingPanel.setVolumeBlendingMode(
340                VtkVolumeBlendType.values()[preferences.getInt(ID_BLENDING, VtkVolumeBlendType.COMPOSITE.ordinal())]);
341
342        // volume mapper
343        settingPanel.setGPURendering(preferences.getInt(ID_MAPPER, 0) != 0);
344        settingPanel.setVolumeInterpolation(preferences.getInt(ID_INTERPOLATION, VtkUtil.VTK_LINEAR_INTERPOLATION));
345        settingPanel.setVolumeSample(preferences.getInt(ID_SAMPLE, 0));
346        settingPanel.setVolumeAmbient(preferences.getDouble(ID_AMBIENT, 0.2d));
347        settingPanel.setVolumeDiffuse(preferences.getDouble(ID_DIFFUSE, 0.4d));
348        settingPanel.setVolumeSpecular(preferences.getDouble(ID_SPECULAR, 0.4d));
349        settingPanel.setVolumeShading(preferences.getBoolean(ID_SHADING, false));
350        axesButton.setSelected(preferences.getBoolean(ID_AXES, true));
351        boundingBoxButton.setSelected(preferences.getBoolean(ID_BOUNDINGBOX, true));
352        gridButton.setSelected(preferences.getBoolean(ID_BOUNDINGBOX_GRID, true));
353        rulerButton.setSelected(preferences.getBoolean(ID_BOUNDINGBOX_RULES, false));
354        rulerLabelButton.setSelected(preferences.getBoolean(ID_BOUNDINGBOX_LABELS, false));
355        // pickOnMouseMoveButton.setSelected(preferences.getBoolean(ID_PICKONMOUSEMOVE, false));
356        // always false by default (preferable)
357        // pickOnMouseMoveButton.setSelected(false);
358
359        // apply restored settings
360        setBackgroundColorInternal(settingPanel.getBackgroundColor());
361        imageVolume.setBlendingMode(settingPanel.getVolumeBlendingMode());
362        imageVolume.setGPURendering(settingPanel.getGPURendering());
363        // mapper may change blending mode
364        settingPanel.setVolumeBlendingMode(imageVolume.getBlendingMode());
365        imageVolume.setInterpolationMode(settingPanel.getVolumeInterpolation());
366        imageVolume.setSampleResolution(settingPanel.getVolumeSample());
367        imageVolume.setAmbient(settingPanel.getVolumeAmbient());
368        imageVolume.setDiffuse(settingPanel.getVolumeDiffuse());
369        imageVolume.setSpecular(settingPanel.getVolumeSpecular());
370        imageVolume.setShade(settingPanel.getVolumeShading());
371        // axes.SetVisibility(axesButton.isSelected() ? 1 : 0);
372        boundingBox.SetVisibility(boundingBoxButton.isSelected() ? 1 : 0);
373        rulerBox.SetDrawXGridlines(gridButton.isSelected() ? 1 : 0);
374        rulerBox.SetDrawYGridlines(gridButton.isSelected() ? 1 : 0);
375        rulerBox.SetDrawZGridlines(gridButton.isSelected() ? 1 : 0);
376        rulerBox.SetXAxisTickVisibility(rulerButton.isSelected() ? 1 : 0);
377        rulerBox.SetXAxisMinorTickVisibility(rulerButton.isSelected() ? 1 : 0);
378        rulerBox.SetYAxisTickVisibility(rulerButton.isSelected() ? 1 : 0);
379        rulerBox.SetYAxisMinorTickVisibility(rulerButton.isSelected() ? 1 : 0);
380        rulerBox.SetZAxisTickVisibility(rulerButton.isSelected() ? 1 : 0);
381        rulerBox.SetZAxisMinorTickVisibility(rulerButton.isSelected() ? 1 : 0);
382        rulerBox.SetXAxisLabelVisibility(rulerLabelButton.isSelected() ? 1 : 0);
383        rulerBox.SetYAxisLabelVisibility(rulerLabelButton.isSelected() ? 1 : 0);
384        rulerBox.SetZAxisLabelVisibility(rulerLabelButton.isSelected() ? 1 : 0);
385        // setPickOnMouseMove(pickOnMouseMoveButton.isSelected());
386
387        // add volume to renderer
388        renderer.AddVolume(imageVolume.getVolume());
389        // add bounding box & ruler
390        renderer.AddViewProp(boundingBox);
391        renderer.AddViewProp(rulerBox);
392        renderer.AddViewProp(textInfo);
393
394        // reset camera
395        resetCamera();
396        // apply lut depending channel configuration
397        updateLut();
398
399        // we can now listen for setting changes
400        settingPanel.addSettingChangeListener(this);
401        axesButton.addActionListener(this);
402        boundingBoxButton.addActionListener(this);
403        gridButton.addActionListener(this);
404        rulerButton.addActionListener(this);
405        rulerLabelButton.addActionListener(this);
406        // pickOnMouseMoveButton.addActionListener(this);
407
408        // create EDTTask object
409        edtTask = new EDTTask<Object>();
410        // start the properties and VTK overlay updater processors
411        propertiesUpdater.start();
412        overlayUpdater.start();
413
414        // initialized !
415        initialized = true;
416
417        // add layers actors
418        overlayUpdater.addProps(VtkUtil.getLayersProps(getLayers(false)));
419    }
420
421    @Override
422    public void shutDown()
423    {
424        final long st = System.currentTimeMillis();
425        // wait for initialization to complete before shutdown (max 5s)
426        while (((System.currentTimeMillis() - st) < 5000L) && !initialized)
427            ThreadUtil.sleep(1);
428
429        propertiesUpdater.interrupt();
430        try
431        {
432            // be sure there is no more processing here
433            propertiesUpdater.join();
434        }
435        catch (InterruptedException e)
436        {
437            // can ignore safely
438        }
439        overlayUpdater.interrupt();
440        try
441        {
442            // be sure there is no more processing here
443            overlayUpdater.join();
444        }
445        catch (InterruptedException e)
446        {
447            // can ignore safely
448        }
449
450        // no more initialized (prevent extra useless processing)
451        initialized = false;
452        propertiesUpdater = null;
453        overlayUpdater = null;
454
455        // VTK stuff in EDT
456        invokeOnEDTSilent(new Runnable()
457        {
458            @Override
459            public void run()
460            {
461                renderer.RemoveAllViewProps();
462                // renderer.Delete();
463                // renderWindow.Delete();
464                imageVolume.release();
465                // widget.Delete();
466                // axes.Delete();
467                boundingBox.Delete();
468                // camera.Delete();
469
470                // dispose extra panel 3D stuff
471                panel3D.disposeInternal();
472            }
473        });
474
475        // AWTMultiCaster of vtkPanel keep reference of this frame so
476        // we have to release as most stuff we can
477        removeAll();
478        panel.removeAll();
479
480        renderer = null;
481        renderWindow = null;
482        imageVolume = null;
483        // widget = null;
484        // axes = null;
485        boundingBox = null;
486        camera = null;
487
488        panel3D.removeKeyListener(this);
489        panel3D = null;
490        panel = null;
491
492        // do parent shutdown now
493        super.shutDown();
494
495        // call VTK GC: better if we can avoid this !
496        // vtkObjectBase.JAVA_OBJECT_MANAGER.gc(false);
497    }
498
499    /**
500     * Returns initialized state of VtkCanvas
501     */
502    public boolean isInitialized()
503    {
504        return initialized;
505    }
506
507    @Override
508    public void customizeToolbar(JToolBar toolBar)
509    {
510        toolBar.addSeparator();
511        toolBar.add(axesButton);
512        toolBar.addSeparator();
513        toolBar.add(boundingBoxButton);
514        toolBar.add(gridButton);
515        toolBar.add(rulerButton);
516        toolBar.add(rulerLabelButton);
517        // toolBar.addSeparator();
518        // toolBar.add(pickOnMouseMoveButton);
519    }
520
521    @Override
522    protected Overlay createImageOverlay()
523    {
524        return new VtkCanvasImageOverlay();
525    }
526
527    /**
528     * Request exclusive access to VTK rendering.<br>
529     * 
530     * @deprecated Use <code>getVtkPanel().lock()</code> instead.
531     */
532    @Deprecated
533    public void lock()
534    {
535        if (panel3D != null)
536            panel3D.lock();
537    }
538
539    /**
540     * Release exclusive access from VTK rendering.
541     * 
542     * @deprecated Use <code>getVtkPanel().unlock()</code> instead.
543     */
544    @Deprecated
545    public void unlock()
546    {
547        if (panel3D != null)
548            panel3D.unlock();
549    }
550
551    /**
552     * @deprecated Use {@link #getCamera()} instead
553     */
554    @Deprecated
555    public vtkCamera getActiveCam()
556    {
557        return getCamera();
558    }
559
560    /**
561     * @return the VTK scene camera object
562     */
563    public vtkCamera getCamera()
564    {
565        return camera;
566    }
567
568    /**
569     * @return the VTK default scene light object.<br>
570     *         Can be <code>null</code> if render window is not yet initialized.
571     */
572    public vtkLight getLight()
573    {
574        return renderer.GetLights().GetNextItem();
575    }
576
577    /**
578     * @return the VTK axes object
579     */
580    public vtkAxesActor getAxes()
581    {
582        return panel3D.getAxesActor();
583    }
584
585    /**
586     * @return the VTK bounding box object
587     */
588    public vtkCubeAxesActor getBoundingBox()
589    {
590        return boundingBox;
591    }
592
593    /**
594     * @return the VTK ruler box object
595     */
596    public vtkCubeAxesActor getRulerBox()
597    {
598        return rulerBox;
599    }
600
601    /**
602     * @deprecated there is no more orientation widget because of the jogl bug with multiple viewport.
603     */
604    @Deprecated
605    public vtkOrientationMarkerWidget getWidget()
606    {
607        return null;
608        // return widget;
609    }
610
611    /**
612     * @return the VTK image volume object
613     */
614    public VtkImageVolume getImageVolume()
615    {
616        return imageVolume;
617    }
618
619    /**
620     * Returns rendering background color
621     */
622    public Color getBackgroundColor()
623    {
624        return settingPanel.getBackgroundColor();
625    }
626
627    /**
628     * Sets rendering background color
629     */
630    public void setBackgroundColor(Color value)
631    {
632        settingPanel.setBackgroundColor(value);
633    }
634
635    /**
636     * Returns <code>true</code> if the volume bounding box is visible.
637     */
638    public boolean isBoundingBoxVisible()
639    {
640        return boundingBoxButton.isSelected();
641    }
642
643    /**
644     * Enable / disable volume bounding box display.
645     */
646    public void setBoundingBoxVisible(boolean value)
647    {
648        if (boundingBoxButton.isSelected() != value)
649            boundingBoxButton.doClick();
650    }
651
652    /**
653     * Returns <code>true</code> if the volume bounding box grid is visible.
654     */
655    public boolean isBoundingBoxGridVisible()
656    {
657        return gridButton.isSelected();
658    }
659
660    /**
661     * Enable / disable volume bounding box grid display.
662     */
663    public void setBoundingBoxGridVisible(boolean value)
664    {
665        if (gridButton.isSelected() != value)
666            gridButton.doClick();
667    }
668
669    /**
670     * Returns <code>true</code> if the volume bounding box ruler are visible.
671     */
672    public boolean isBoundingBoxRulerVisible()
673    {
674        return rulerButton.isSelected();
675    }
676
677    /**
678     * Enable / disable volume bounding box ruler display.
679     */
680    public void setBoundingBoxRulerVisible(boolean value)
681    {
682        if (rulerButton.isSelected() != value)
683            rulerButton.doClick();
684    }
685
686    /**
687     * Returns <code>true</code> if the volume bounding box ruler labels are visible.
688     */
689    public boolean isBoundingBoxRulerLabelsVisible()
690    {
691        return rulerLabelButton.isSelected();
692    }
693
694    /**
695     * Enable / disable volume bounding box ruler labels display.
696     */
697    public void setBoundingBoxRulerLabelsVisible(boolean value)
698    {
699        if (rulerLabelButton.isSelected() != value)
700            rulerLabelButton.doClick();
701    }
702
703    /**
704     * @deprecated USe {@link #setBackgroundColorInternal(Color)}
705     */
706    @Deprecated
707    public void setBoundingBoxColor(Color color)
708    {
709        setBackgroundColorInternal(color);
710    }
711
712    /**
713     * Set background color (internal)
714     */
715    public void setBackgroundColorInternal(Color color)
716    {
717        renderer.SetBackground(Array1DUtil.floatArrayToDoubleArray(color.getColorComponents(null)));
718
719        final Color oppositeColor;
720
721        // adjust bounding box color
722        if (ColorUtil.getLuminance(color) > 128)
723            oppositeColor = Color.black;
724        else
725            oppositeColor = Color.white;
726
727        final float[] comp = oppositeColor.getRGBColorComponents(null);
728
729        final float r = comp[0];
730        final float g = comp[0];
731        final float b = comp[0];
732
733        boundingBox.GetXAxesLinesProperty().SetColor(r, g, b);
734        boundingBox.GetYAxesLinesProperty().SetColor(r, g, b);
735        boundingBox.GetZAxesLinesProperty().SetColor(r, g, b);
736
737        rulerBox.GetXAxesGridlinesProperty().SetColor(r, g, b);
738        rulerBox.GetXAxesGridpolysProperty().SetColor(r, g, b);
739        rulerBox.GetXAxesInnerGridlinesProperty().SetColor(r, g, b);
740        rulerBox.GetXAxesLinesProperty().SetColor(r, g, b);
741
742        rulerBox.GetYAxesGridlinesProperty().SetColor(r, g, b);
743        rulerBox.GetYAxesGridpolysProperty().SetColor(r, g, b);
744        rulerBox.GetYAxesInnerGridlinesProperty().SetColor(r, g, b);
745        rulerBox.GetYAxesLinesProperty().SetColor(r, g, b);
746
747        rulerBox.GetZAxesGridlinesProperty().SetColor(r, g, b);
748        rulerBox.GetZAxesGridpolysProperty().SetColor(r, g, b);
749        rulerBox.GetZAxesInnerGridlinesProperty().SetColor(r, g, b);
750        rulerBox.GetZAxesLinesProperty().SetColor(r, g, b);
751
752        textProperty.SetColor(r, g, b);
753    }
754
755    /**
756     * Returns <code>true</code> if the 3D axis are visible.
757     */
758    public boolean isAxisVisible()
759    {
760        return axesButton.isSelected();
761    }
762
763    /**
764     * Enable / disable 3D axis display.
765     */
766    public void setAxisVisible(boolean value)
767    {
768        if (axesButton.isSelected() != value)
769            axesButton.doClick();
770    }
771
772    /**
773     * @see VtkImageVolume#getBlendingMode()
774     */
775    public VtkVolumeBlendType getVolumeBlendingMode()
776    {
777        return settingPanel.getVolumeBlendingMode();
778    }
779
780    /**
781     * @see VtkImageVolume#setBlendingMode(VtkVolumeBlendType)
782     */
783    public void setVolumeBlendingMode(VtkVolumeBlendType value)
784    {
785        settingPanel.setVolumeBlendingMode(value);
786    }
787
788    /**
789     * @see VtkImageVolume#getSampleResolution()
790     */
791    public int getVolumeSample()
792    {
793        return settingPanel.getVolumeSample();
794    }
795
796    /**
797     * @see VtkImageVolume#setSampleResolution(double)
798     */
799    public void setVolumeSample(int value)
800    {
801        settingPanel.setVolumeSample(value);
802    }
803
804    /**
805     * @see VtkImageVolume#getShade()
806     */
807    public boolean isVolumeShadingEnable()
808    {
809        return settingPanel.getVolumeShading();
810    }
811
812    /**
813     * @see VtkImageVolume#setShade(boolean)
814     */
815    public void setVolumeShadingEnable(boolean value)
816    {
817        settingPanel.setVolumeShading(value);
818    }
819
820    /**
821     * @see VtkImageVolume#getAmbient()
822     */
823    public double getVolumeAmbient()
824    {
825        return settingPanel.getVolumeAmbient();
826    }
827
828    /**
829     * @see VtkImageVolume#setAmbient(double)
830     */
831    public void setVolumeAmbient(double value)
832    {
833        settingPanel.setVolumeAmbient(value);
834    }
835
836    /**
837     * @see VtkImageVolume#getDiffuse()
838     */
839    public double getVolumeDiffuse()
840    {
841        return settingPanel.getVolumeDiffuse();
842    }
843
844    /**
845     * @see VtkImageVolume#setDiffuse(double)
846     */
847    public void setVolumeDiffuse(double value)
848    {
849        settingPanel.setVolumeDiffuse(value);
850    }
851
852    /**
853     * @see VtkImageVolume#getSpecular()
854     */
855    public double getVolumeSpecular()
856    {
857        return settingPanel.getVolumeSpecular();
858    }
859
860    /**
861     * @see VtkImageVolume#setSpecular(double)
862     */
863    public void setVolumeSpecular(double value)
864    {
865        settingPanel.setVolumeSpecular(value);
866    }
867
868    /**
869     * @see VtkImageVolume#getInterpolationMode()
870     */
871    public int getVolumeInterpolation()
872    {
873        return settingPanel.getVolumeInterpolation();
874    }
875
876    /**
877     * @see VtkImageVolume#setInterpolationMode(int)
878     */
879    public void setVolumeInterpolation(int value)
880    {
881        settingPanel.setVolumeInterpolation(value);
882    }
883
884    /**
885     * @see VtkImageVolume#getGPURendering()
886     */
887    public boolean getGPURendering()
888    {
889        return settingPanel.getGPURendering();
890    }
891
892    /**
893     * @see VtkImageVolume#setGPURendering(boolean)
894     */
895    public void setGPURendering(boolean value)
896    {
897        settingPanel.setGPURendering(value);
898    }
899
900    /**
901     * @deprecated Use {@link #getGPURendering()} instead
902     */
903    @Deprecated
904    public VtkVolumeMapperType getVolumeMapperType()
905    {
906        if (getGPURendering())
907            return VtkVolumeMapperType.RAYCAST_GPU_OPENGL;
908
909        return VtkVolumeMapperType.RAYCAST_CPU_FIXEDPOINT;
910    }
911
912    /**
913     * @deprecated Use {@link #setGPURendering(boolean)} instead
914     */
915    @Deprecated
916    public void setVolumeMapperType(VtkVolumeMapperType value)
917    {
918        setGPURendering(VtkVolumeMapperType.RAYCAST_GPU_OPENGL.equals(value));
919    }
920
921    /**
922     * @return visible state of the image volume object
923     * @see VtkImageVolume#isVisible()
924     */
925    public boolean isVolumeVisible()
926    {
927        return imageVolume.isVisible();
928    }
929
930    /**
931     * Sets the visible state of the image volume object
932     * 
933     * @see VtkImageVolume#setVisible(boolean)
934     */
935    public void setVolumeVisible(boolean value)
936    {
937        imageVolume.setVisible(value);
938    }
939
940    /**
941     * Force render refresh
942     */
943    @Override
944    public void refresh()
945    {
946        if (!initialized)
947            return;
948
949        // refresh rendering
950        if (panel3D != null)
951            panel3D.repaint();
952    }
953
954    // private void test()
955    // {
956    // // exemple de clipping a utiliser par la suite.
957    // if ( false )
958    // {
959    // vtkPlane plane = new vtkPlane();
960    // plane.SetOrigin(1000, 1000, 1000);
961    // plane.SetNormal( 1, 1, 0);
962    // volumeMapper.AddClippingPlane( plane );
963    // }
964    //
965    // vtkOrientationMarkerWidget ow = new vtkOrientationMarkerWidget();
966    // }
967
968    protected void resetCamera()
969    {
970        camera.SetViewUp(0, -1, 0);
971        renderer.ResetCamera();
972        camera.Elevation(200);
973        renderer.ResetCameraClippingRange();
974    }
975
976    /**
977     * @deprecated Use {@link #setVolumeSample(int)} instead
978     */
979    @Deprecated
980    @Override
981    public void setVolumeDistanceSample(int value)
982    {
983        setVolumeSample(value);
984    }
985
986    // /**
987    // * Returns channel position based on enabled channel in LUT
988    // */
989    // protected int getChannelPos()
990    // {
991    // final LUT lut = getLut();
992    // int result = -1;
993    //
994    // for (int c = 0; c < lut.getNumChannel(); c++)
995    // {
996    // final LUTChannel lutChannel = lut.getLutChannel(c);
997    //
998    // if (lutChannel.isEnabled())
999    // {
1000    // if (result == -1)
1001    // result = c;
1002    // else
1003    // return -1;
1004    // }
1005    // }
1006    //
1007    // return result;
1008    // }
1009
1010    /**
1011     * @deprecated Always enabled now (always return <code>true</code>)
1012     */
1013    @Deprecated
1014    public boolean getPickOnMouseMove()
1015    {
1016        return true;
1017        // return pickOnMouseMoveButton.isSelected();
1018    }
1019
1020    /**
1021     * @deprecated Always enable now
1022     */
1023    @Deprecated
1024    public void setPickOnMouseMove(boolean value)
1025    {
1026        // if (pickOnMouseMoveButton.isSelected() != value)
1027        // pickOnMouseMoveButton.doClick();
1028    }
1029
1030    /**
1031     * Returns the picked object on the last mouse move/drag event (can be <code>null</code> if no object was picked).
1032     * 
1033     * @see #pickProp(int, int)
1034     */
1035    public vtkProp getPickedObject()
1036    {
1037        return pickedObject;
1038    }
1039
1040    /**
1041     * @deprecated use {@link #pickProp(int, int)} instead.
1042     */
1043    @Deprecated
1044    public vtkActor pick(int x, int y)
1045    {
1046        return (vtkActor) panel3D.pick(x, y);
1047    }
1048
1049    /**
1050     * Pick object at specified position and return it.
1051     *
1052     * @see #getPickedObject()
1053     * @see icy.vtk.IcyVtkPanel#pick(int, int)
1054     */
1055    public vtkProp pickProp(int x, int y)
1056    {
1057        return panel3D.pick(x, y);
1058    }
1059
1060    /**
1061     * Return reached z world position (normalized) for specified display position
1062     */
1063    public double getWorldZ(int x, int y)
1064    {
1065        final vtkRenderer r = getRenderer();
1066        final vtkRenderWindow rw = getRenderWindow();
1067
1068        if ((r == null) || (rw == null))
1069            return 0d;
1070
1071        // need to revert Y axis
1072        return r.GetZ(x, rw.GetSize()[1] - y);
1073    }
1074
1075    public double getWorldZ(Point pt)
1076    {
1077        return getWorldZ(pt.x, pt.y);
1078    }
1079
1080    /**
1081     * Convert world coordinates to display coordinates
1082     */
1083    public Point3D worldToDisplay(Point3D pt)
1084    {
1085        if (pt == null)
1086            return new Point3D.Double();
1087
1088        return worldToDisplay(pt.getX(), pt.getY(), pt.getZ());
1089    }
1090
1091    /**
1092     * Convert world coordinates to display coordinates
1093     */
1094    public Point3D worldToDisplay(double x, double y, double z)
1095    {
1096        final vtkRenderer r = getRenderer();
1097        final vtkRenderWindow rw = getRenderWindow();
1098
1099        if ((r == null) || (rw == null))
1100            return new Point3D.Double();
1101
1102        r.SetWorldPoint(x, y, z, 1d);
1103        r.WorldToDisplay();
1104        final Point3D result = new Point3D.Double(r.GetDisplayPoint());
1105
1106        // need to revert Y axis
1107        result.setY(rw.GetSize()[1] - result.getY());
1108
1109        return result;
1110    }
1111
1112    /**
1113     * Convert display coordinates to world coordinates.
1114     */
1115    public Point3D displayToWorld(Point pt)
1116    {
1117        if (pt == null)
1118            return new Point3D.Double();
1119
1120        return displayToWorld(pt.x, pt.y);
1121    }
1122
1123    /**
1124     * Convert display coordinates to world coordinates.<br>
1125     */
1126    public Point3D displayToWorld(int x, int y)
1127    {
1128        // get camera focal point
1129        final double[] fp = camera.GetFocalPoint();
1130        // transform it to display position (with Z info)
1131        final Point3D displayFP = worldToDisplay(fp[0], fp[1], fp[2]);
1132        // keep the Z info from focal point
1133        return displayToWorld(x, y, displayFP.getZ());
1134        // return displayToWorld(x, y, getWorldZ(x, y));
1135    }
1136
1137    /**
1138     * Convert display coordinates to world coordinates.<br>
1139     * Default value for Z should be 0d
1140     */
1141    public Point3D displayToWorld(Point pt, double z)
1142    {
1143        if (pt == null)
1144            return new Point3D.Double();
1145
1146        return displayToWorld(pt.getX(), pt.getY(), z);
1147    }
1148
1149    /**
1150     * Convert display coordinates to world coordinates.<br>
1151     * Default value for Z is 0d
1152     */
1153    public Point3D displayToWorld(double x, double y, double z)
1154    {
1155        final vtkRenderer r = getRenderer();
1156        final vtkRenderWindow rw = getRenderWindow();
1157
1158        if ((r == null) || (rw == null))
1159            return new Point3D.Double();
1160
1161        // need to revert Y axis
1162        r.SetDisplayPoint(x, rw.GetSize()[1] - y, z);
1163        r.DisplayToWorld();
1164        final double[] result = r.GetWorldPoint();
1165
1166        // final vtkPicker picker = getPicker();
1167        // pickProp((int)x, (int)y);
1168        // // picker.Pick(x, rw.GetSize()[1] - y, 0, r);
1169        // double[] pos = picker.GetPickPosition();
1170        //
1171        // System.out.println("displayToWorld(" + x + ", " + y + ", " + z + "):");
1172        // System.out.println(String.format("%.5g, %.5g, %.5g", result[0], result[1], result[2]));
1173        // System.out.println(String.format("from Pick: %.5g, %.5g, %.5g", pos[0], pos[1], pos[2]));
1174
1175        // normalize
1176        if (result[3] != 0d)
1177        {
1178            result[0] /= result[3];
1179            result[1] /= result[3];
1180            result[2] /= result[3];
1181        }
1182        else
1183        {
1184            result[0] = 0d;
1185            result[1] = 0d;
1186            result[2] = 0d;
1187        }
1188
1189        return new Point3D.Double(result[0], result[1], result[2]);
1190    }
1191
1192    @Override
1193    public Point imageToCanvas(double x, double y, double z)
1194    {
1195        final double[] scaling = getVolumeScale();
1196        final Point3D result = worldToDisplay(x * scaling[0], y * scaling[1], z * scaling[2]);
1197
1198        // System.out.println("imageToCanvas(" + x + ", " + y + ", " + z + "): " + result);
1199
1200        // ignore Z coordinate
1201        return new Point((int) result.getX(), (int) result.getY());
1202    }
1203
1204    @Override
1205    public Point3D.Double canvasToImage(int x, int y)
1206    {
1207        final double[] scaling = getVolumeScale();
1208
1209        // check scaling does not contains any 0
1210        for (double d : scaling)
1211        {
1212            if (d == 0d)
1213                return new Point3D.Double();
1214        }
1215
1216        // get image position in 3D
1217        Point3D result = displayToWorld(x, y);
1218
1219        // get the view axis
1220        final double[] directionOfProjection = getCamera().GetDirectionOfProjection();
1221        final double dirX = Math.abs(directionOfProjection[0]);
1222        final double dirY = Math.abs(directionOfProjection[1]);
1223        final double dirZ = Math.abs(directionOfProjection[2]);
1224
1225        // we always want to have 2D coordinates cancel position which is not "visible" axis
1226        if (dirX > dirY)
1227        {
1228            if (dirX > dirZ)
1229                result.setX(Double.NaN);
1230            else
1231                result.setZ(Double.NaN);
1232        }
1233        else
1234        {
1235            if (dirY > dirZ)
1236                result.setY(Double.NaN);
1237            else
1238                result.setZ(Double.NaN);
1239        }
1240
1241        result = new Point3D.Double(result.getX() / scaling[0], result.getY() / scaling[1], result.getZ() / scaling[2]);
1242
1243        // System.out.println("canvasToImage(" + x + ", " + y + "): "
1244        // + String.format("%.5g, %.5g, %.5g", result.getX(), result.getY(), result.getZ()));
1245
1246        return (Point3D.Double) result;
1247    }
1248
1249    /**
1250     * @deprecated Use {@link VtkUtil#getLayerProps(Layer)} instead.
1251     */
1252    @Deprecated
1253    protected vtkProp[] getLayerActors(Layer layer)
1254    {
1255        return VtkUtil.getLayerProps(layer);
1256    }
1257
1258    protected void addLayerActors(Layer layer)
1259    {
1260        // not yet (or no more) initialized
1261        if (overlayUpdater == null)
1262            return;
1263
1264        overlayUpdater.addProps(VtkUtil.getLayerProps(layer));
1265    }
1266
1267    protected void removeLayerActors(Layer layer)
1268    {
1269        // not yet (or no more) initialized
1270        if (overlayUpdater == null)
1271            return;
1272
1273        overlayUpdater.removeProps(VtkUtil.getLayerProps(layer));
1274    }
1275
1276    protected void addLayersActors(List<Layer> layers)
1277    {
1278        // not yet (or no more) initialized
1279        if (overlayUpdater == null)
1280            return;
1281
1282        overlayUpdater.addProps(VtkUtil.getLayersProps(layers));
1283    }
1284
1285    protected void updateBoundingBoxSize()
1286    {
1287        final double[] bounds = imageVolume.getVolume().GetBounds();
1288
1289        boundingBox.SetBounds(bounds);
1290        rulerBox.SetBounds(bounds);
1291    }
1292
1293    /**
1294     * Build and get image data
1295     */
1296    protected vtkImageData getImageData()
1297    {
1298        try
1299        {
1300            return VtkUtil.getImageData(getSequence(), getPositionT(), getPositionC());
1301        }
1302        catch (TooLargeArrayException e)
1303        {
1304            // cannot allocate a such large contiguous array
1305            return null;
1306        }
1307        catch (OutOfMemoryError e)
1308        {
1309            // just not enough memory
1310            return null;
1311        }
1312    }
1313
1314    /**
1315     * update image data
1316     */
1317    protected void updateImageData(vtkImageData data)
1318    {
1319        if (data != null)
1320        {
1321            imageVolume.setVolumeData(data);
1322            imageVolume.getVolume().SetVisibility(getImageLayer().isVisible() ? 1 : 0);
1323
1324            if (textInfo != null)
1325                textInfo.SetVisibility(0);
1326        }
1327        else
1328        {
1329            // no data --> hide volume
1330            imageVolume.getVolume().SetVisibility(0);
1331
1332            if (textInfo != null)
1333            {
1334                final Sequence seq = getSequence();
1335
1336                // we have an image --> not enough memory to display it (show message)
1337                if ((seq != null) && !seq.isEmpty())
1338                    textInfo.SetVisibility(1);
1339            }
1340        }
1341    }
1342
1343    protected void updateLut()
1344    {
1345        final LUT lut = getLut();
1346
1347        // update the whole LUT
1348        for (int c = 0; c < lut.getNumChannel(); c++)
1349            updateLut(lut.getLutChannel(c), c);
1350    }
1351
1352    protected void updateLut(LUTChannel lutChannel, int channel)
1353    {
1354        final Sequence sequence = getSequence();
1355        if ((sequence == null) || sequence.isEmpty())
1356            return;
1357
1358        final int ch = channel;
1359        final vtkColorTransferFunction colorMap = VtkUtil.getColorMap(lutChannel);
1360        final vtkPiecewiseFunction opacityMap = VtkUtil.getOpacityMap(lutChannel);
1361
1362        imageVolume.setColorMap(colorMap, ch);
1363        imageVolume.setOpacityMap(opacityMap, ch);
1364    }
1365
1366    @Override
1367    public Component getViewComponent()
1368    {
1369        return getVtkPanel();
1370    }
1371
1372    public IcyVtkPanel getVtkPanel()
1373    {
1374        return panel3D;
1375    }
1376
1377    /**
1378     * @deprecated Use {@link #getVtkPanel()}
1379     */
1380    @Deprecated
1381    @Override
1382    public IcyVtkPanel getPanel3D()
1383    {
1384        return getVtkPanel();
1385    }
1386
1387    @Override
1388    public vtkRenderer getRenderer()
1389    {
1390        return renderer;
1391    }
1392
1393    public vtkRenderWindow getRenderWindow()
1394    {
1395        return renderWindow;
1396    }
1397
1398    /**
1399     * @see icy.vtk.IcyVtkPanel#getPicker()
1400     */
1401    public vtkPicker getPicker()
1402    {
1403        return panel3D.getPicker();
1404    }
1405
1406    /**
1407     * Get scaling for image volume rendering
1408     */
1409    @Override
1410    public double[] getVolumeScale()
1411    {
1412        return imageVolume.getScale();
1413    }
1414
1415    /**
1416     * Set scaling for image volume rendering
1417     */
1418    @Override
1419    public void setVolumeScale(double x, double y, double z)
1420    {
1421        propertyChange(PROPERTY_SCALE, new double[] {x, y, z});
1422    }
1423
1424    @Override
1425    public void keyPressed(KeyEvent e)
1426    {
1427        // send to overlays
1428        super.keyPressed(e);
1429
1430        // forward to view
1431        panel3D.keyPressed(e);
1432
1433        if (!e.isConsumed())
1434        {
1435            switch (e.getKeyCode())
1436            {
1437                case KeyEvent.VK_LEFT:
1438                    if (EventUtil.isMenuControlDown(e, true))
1439                        setPositionT(Math.max(getPositionT() - 5, 0));
1440                    else
1441                        setPositionT(Math.max(getPositionT() - 1, 0));
1442                    e.consume();
1443                    break;
1444
1445                case KeyEvent.VK_RIGHT:
1446                    if (EventUtil.isMenuControlDown(e, true))
1447                        setPositionT(getPositionT() + 5);
1448                    else
1449                        setPositionT(getPositionT() + 1);
1450                    e.consume();
1451                    break;
1452
1453                case KeyEvent.VK_NUMPAD2:
1454                    if (EventUtil.isMenuControlDown(e, true))
1455                        panel3D.translateView(0, -50);
1456                    else
1457                        panel3D.translateView(0, -10);
1458                    refresh();
1459                    e.consume();
1460                    break;
1461
1462                case KeyEvent.VK_NUMPAD4:
1463                    if (EventUtil.isMenuControlDown(e, true))
1464                        panel3D.translateView(-50, 0);
1465                    else
1466                        panel3D.translateView(-10, 0);
1467                    refresh();
1468                    e.consume();
1469                    break;
1470
1471                case KeyEvent.VK_NUMPAD6:
1472                    if (EventUtil.isMenuControlDown(e, true))
1473                        panel3D.translateView(50, 0);
1474                    else
1475                        panel3D.translateView(10, 0);
1476                    refresh();
1477                    e.consume();
1478                    break;
1479
1480                case KeyEvent.VK_NUMPAD8:
1481                    if (EventUtil.isMenuControlDown(e, true))
1482                        panel3D.translateView(0, 50);
1483                    else
1484                        panel3D.translateView(0, 10);
1485                    refresh();
1486                    e.consume();
1487                    break;
1488            }
1489        }
1490    }
1491
1492    @Override
1493    public void keyReleased(KeyEvent e)
1494    {
1495        // send to overlays
1496        super.keyReleased(e);
1497
1498        // forward to view
1499        panel3D.keyReleased(e);
1500    }
1501
1502    @Override
1503    protected void setPositionZInternal(int z)
1504    {
1505        // not supported, Z should stay at -1
1506    }
1507
1508    @Override
1509    protected void setPositionCInternal(int c)
1510    {
1511        // single channel mode is not possible here
1512        if (c != -1)
1513            return;
1514
1515        super.setPositionCInternal(c);
1516    }
1517
1518    @Override
1519    public double getScaleX()
1520    {
1521        final double dist = getCamera().GetDistance();
1522        // cannot compute scaling
1523        if (dist <= 0d)
1524            return 1d;
1525
1526        final double imageSizeX = getImageSizeX();
1527        // FIXME: from where come that x2 factor
1528        final double result = (2 * imageSizeX * getVolumeScale()[0]) / dist;
1529        final double canvasImageRatio = getCanvasSizeX() / ((imageSizeX == 0d) ? 1d : imageSizeX);
1530
1531        return result * canvasImageRatio;
1532    }
1533
1534    @Override
1535    public double getScaleY()
1536    {
1537        final double dist = getCamera().GetDistance();
1538        // cannot compute scaling
1539        if (dist <= 0d)
1540            return 1d;
1541
1542        final double imageSizeY = getImageSizeY();
1543        // FIXME: from where come that x2 factor
1544        final double result = (2 * imageSizeY * getVolumeScale()[1]) / dist;
1545        final double canvasImageRatio = getCanvasSizeY() / ((imageSizeY == 0d) ? 1d : imageSizeY);
1546
1547        return result * canvasImageRatio;
1548    }
1549
1550    @Override
1551    public void setMouseImagePosX(double value)
1552    {
1553        // just ignore NaN position (canvasToImage(..) can return NaN for specific dimension)
1554        if (!Double.isNaN(value))
1555            super.setMouseImagePosX(value);
1556    }
1557
1558    @Override
1559    public void setMouseImagePosY(double value)
1560    {
1561        // just ignore NaN position (canvasToImage(..) can return NaN for specific dimension)
1562        if (!Double.isNaN(value))
1563            super.setMouseImagePosY(value);
1564    }
1565
1566    @Override
1567    public void setMouseImagePosZ(double value)
1568    {
1569        // just ignore NaN position (canvasToImage(..) can return NaN for specific dimension)
1570        if (!Double.isNaN(value))
1571            super.setMouseImagePosZ(value);
1572    }
1573
1574    @Override
1575    public BufferedImage getRenderedImage(int t, int c)
1576    {
1577        final CustomVtkPanel vp = panel3D;
1578        if (vp == null)
1579            return null;
1580
1581        // save position
1582        final int prevT = getPositionT();
1583        final int prevC = getPositionC();
1584
1585        // set wanted position (needed for correct overlay drawing)
1586        // we have to fire events else some stuff can miss the change
1587        setPositionT(t);
1588        setPositionC(c);
1589        try
1590        {
1591            final vtkImageData imageData = getImageData();
1592
1593            // VTK need this to be called in the EDT
1594            invokeOnEDTSilent(new Runnable()
1595            {
1596                @Override
1597                public void run()
1598                {
1599                    // set image data
1600                    updateImageData(imageData);
1601
1602                    // force fine rendering here
1603                    vp.setForceFineRendering(true);
1604                    try
1605                    {
1606                        // render now !
1607                        vp.paint(vp.getGraphics());
1608                    }
1609                    finally
1610                    {
1611                        vp.setForceFineRendering(false);
1612                    }
1613                }
1614            });
1615
1616            try
1617            {
1618                final Robot robot = new Robot();
1619                final Rectangle bounds = vp.getBounds();
1620                // transform in screen coordinates
1621                bounds.setLocation(ComponentUtil.convertPointToScreen(bounds.getLocation(), vp));
1622                // do the capture
1623                return robot.createScreenCapture(bounds);
1624            }
1625            catch (AWTException e)
1626            {
1627                IcyExceptionHandler.showErrorMessage(e, true);
1628                return null;
1629            }
1630
1631            // final int[] size = renderWindow.GetSize();
1632            // final int w = size[0];
1633            // final int h = size[1];
1634            // final vtkUnsignedCharArray array = new vtkUnsignedCharArray();
1635            // final vtkImageData imageData = getImageData();
1636            // final BufferedImage[] result = new BufferedImage[1];
1637            //
1638            // // VTK need this to be called in the EDT
1639            // invokeOnEDTSilent(new Runnable()
1640            // {
1641            // @Override
1642            // public void run()
1643            // {
1644            // // set image data
1645            // updateImageData(imageData);
1646            //
1647            // // force fine rendering here
1648            // panel3D.setForceFineRendering(true);
1649            // try
1650            // {
1651            // // render now !
1652            // panel3D.paint(panel3D.getGraphics());
1653            // }
1654            // finally
1655            // {
1656            // panel3D.setForceFineRendering(false);
1657            // }
1658            //
1659            // try
1660            // {
1661            // Robot r = new Robot();
1662            // result[0] = r.createScreenCapture(SwingUtilities.convertRectangle(panel3D, getBounds(), null));
1663            // }
1664            // catch (AWTException e)
1665            // {
1666            // // TODO Auto-generated catch block
1667            // e.printStackTrace();
1668            // }
1669            //
1670            // // NOTE: in vtk the [0,0] pixel is bottom left, so a vertical flip is required
1671            // // NOTE: GetRGBACharPixelData gives problematic results depending on the platform
1672            // // (see comment about alpha and platform-dependence in the doc for vtkWindowToImageFilter)
1673            // // Since the canvas is opaque, simply use GetPixelData.
1674            // renderWindow.GetPixelData(0, 0, w - 1, h - 1, 1, array);
1675            // }
1676            // });
1677            //
1678            // // convert the vtk array into a IcyBufferedImage
1679            // final byte[] inData = array.GetJavaArray();
1680            // final BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
1681            // final int[] outData = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
1682            //
1683            // int inOffset = 0;
1684            // for (int y = h - 1; y >= 0; y--)
1685            // {
1686            // int outOffset = y * w;
1687            //
1688            // for (int x = 0; x < w; x++)
1689            // {
1690            // final int r = TypeUtil.unsign(inData[inOffset++]);
1691            // final int g = TypeUtil.unsign(inData[inOffset++]);
1692            // final int b = TypeUtil.unsign(inData[inOffset++]);
1693            //
1694            // outData[outOffset++] = (r << 16) | (g << 8) | (b << 0);
1695            // }
1696            // }
1697            //
1698            // return image;
1699        }
1700        finally
1701        {
1702            // restore position
1703            setPositionT(prevT);
1704            setPositionC(prevC);
1705        }
1706    }
1707
1708    @Override
1709    public BufferedImage getRenderedImage(int t, int z, int c, boolean canvasView)
1710    {
1711        if (z != -1)
1712            throw new UnsupportedOperationException(
1713                    "Error: getRenderedImage(..) with z != -1 not supported on Canvas3D.");
1714        if (!canvasView)
1715            System.out.println("Warning: getRenderedImage(..) with canvasView = false not supported on Canvas3D.");
1716
1717        return getRenderedImage(t, c);
1718    }
1719
1720    protected void invokeOnEDT(Runnable task) throws InterruptedException
1721    {
1722        // in initialization --> just execute
1723        if (edtTask == null)
1724        {
1725            task.run();
1726            return;
1727        }
1728
1729        edtTask.setTask(task);
1730
1731        try
1732        {
1733            ThreadUtil.invokeNow(edtTask);
1734        }
1735        catch (InterruptedException e)
1736        {
1737            throw e;
1738        }
1739        catch (Exception t)
1740        {
1741            // just ignore as this is async process
1742            System.out.println("[VTKCanvas] Warning:" + t);
1743        }
1744    }
1745
1746    protected void invokeOnEDTSilent(Runnable task)
1747    {
1748        try
1749        {
1750            invokeOnEDT(task);
1751        }
1752        catch (InterruptedException e)
1753        {
1754            // just ignore
1755        }
1756    }
1757
1758    @Override
1759    public void changed(IcyCanvasEvent event)
1760    {
1761        super.changed(event);
1762
1763        // avoid useless process during canvas initialization
1764        if (!initialized)
1765            return;
1766
1767        if (event.getType() == IcyCanvasEventType.POSITION_CHANGED)
1768        {
1769            switch (event.getDim())
1770            {
1771                case C:
1772                    propertyChange(PROPERTY_DATA, null);
1773                    // layers can change depending position
1774                    refresh();
1775                    break;
1776
1777                case T:
1778                    propertyChange(PROPERTY_DATA, null);
1779                    // layers can change depending position
1780                    refresh();
1781                    break;
1782
1783                case Z:
1784                    // shouldn't happen
1785                    break;
1786            }
1787        }
1788    }
1789
1790    @Override
1791    protected void lutChanged(int channel)
1792    {
1793        super.lutChanged(channel);
1794
1795        // avoid useless process during canvas initialization
1796        if (!initialized)
1797            return;
1798
1799        propertyChange(PROPERTY_LUT, Integer.valueOf(channel));
1800    }
1801
1802    @Override
1803    protected void sequenceOverlayChanged(Overlay overlay, SequenceEventType type)
1804    {
1805        super.sequenceOverlayChanged(overlay, type);
1806
1807        if (!initialized)
1808            return;
1809
1810        // refresh
1811        refresh();
1812    }
1813
1814    @Override
1815    protected void sequenceDataChanged(IcyBufferedImage image, SequenceEventType type)
1816    {
1817        super.sequenceDataChanged(image, type);
1818
1819        // rebuild image data and bounds
1820        propertyChange(PROPERTY_DATA, null);
1821        propertyChange(PROPERTY_BOUNDS, null);
1822    }
1823
1824    @Override
1825    protected void sequenceMetaChanged(String metadataName)
1826    {
1827        super.sequenceMetaChanged(metadataName);
1828
1829        final Sequence sequence = getSequence();
1830        if ((sequence == null) || sequence.isEmpty())
1831            return;
1832
1833        // need to set scale ?
1834        if (StringUtil.isEmpty(metadataName) || (StringUtil.equals(metadataName, Sequence.ID_PIXEL_SIZE_X)
1835                || StringUtil.equals(metadataName, Sequence.ID_PIXEL_SIZE_Y)
1836                || StringUtil.equals(metadataName, Sequence.ID_PIXEL_SIZE_Z)))
1837        {
1838            setVolumeScale(sequence.getPixelSizeX(), sequence.getPixelSizeY(), sequence.getPixelSizeZ());
1839        }
1840    }
1841
1842    @Override
1843    protected void layerChanged(CanvasLayerEvent event)
1844    {
1845        super.layerChanged(event);
1846
1847        if (!initialized)
1848            return;
1849
1850        if (event.getType() == LayersEventType.CHANGED)
1851        {
1852            final String propertyName = event.getProperty();
1853
1854            // we ignore priority property here as we display in 3D
1855            if (propertyName.equals(Layer.PROPERTY_OPACITY) || propertyName.equals(Layer.PROPERTY_VISIBLE))
1856                propertyChange(PROPERTY_LAYERS_VISIBLE, event.getSource());
1857        }
1858    }
1859
1860    @Override
1861    protected void layerAdded(Layer layer)
1862    {
1863        super.layerAdded(layer);
1864
1865        addLayerActors(layer);
1866    }
1867
1868    @Override
1869    protected void layerRemoved(Layer layer)
1870    {
1871        super.layerRemoved(layer);
1872
1873        removeLayerActors(layer);
1874    }
1875
1876    @Override
1877    protected void layersVisibleChanged()
1878    {
1879        propertyChange(PROPERTY_LAYERS_VISIBLE, null);
1880    }
1881
1882    @Override
1883    public void actionPerformed(ActionEvent e)
1884    {
1885        final Object source = e.getSource();
1886
1887        // translate button action to property change event
1888        if (source == axesButton)
1889            propertyChange(PROPERTY_AXES, Boolean.valueOf(axesButton.isSelected()));
1890        else if (source == boundingBoxButton)
1891            propertyChange(PROPERTY_BOUNDINGBOX, Boolean.valueOf(boundingBoxButton.isSelected()));
1892        else if (source == gridButton)
1893            propertyChange(PROPERTY_BOUNDINGBOX_GRID, Boolean.valueOf(gridButton.isSelected()));
1894        else if (source == rulerButton)
1895            propertyChange(PROPERTY_BOUNDINGBOX_RULES, Boolean.valueOf(rulerButton.isSelected()));
1896        else if (source == rulerLabelButton)
1897            propertyChange(PROPERTY_BOUNDINGBOX_LABELS, Boolean.valueOf(rulerLabelButton.isSelected()));
1898        // else if (source == pickOnMouseMoveButton)
1899        // preferences.putBoolean(ID_PICKONMOUSEMOVE, pickOnMouseMoveButton.isSelected());
1900    }
1901
1902    protected void propertyChange(String name, Object value)
1903    {
1904        // we can ignore it in this case
1905        if (propertiesUpdater == null)
1906            return;
1907
1908        final Property prop = new Property(name, value);
1909
1910        propertiesUpdater.submit(prop);
1911    }
1912
1913    /*
1914     * Called when one of the value in setting panel has changed
1915     */
1916    @Override
1917    public void settingChange(PropertyChangeEvent evt)
1918    {
1919        propertyChange(evt.getPropertyName(), evt.getNewValue());
1920    }
1921
1922    protected class EDTTask<T> implements Callable<T>
1923    {
1924        protected Runnable task;
1925
1926        public void setTask(Runnable task)
1927        {
1928            this.task = task;
1929        }
1930
1931        @Override
1932        public T call() throws Exception
1933        {
1934            task.run();
1935
1936            return null;
1937        }
1938    }
1939
1940    protected class CustomVtkPanel extends IcyVtkPanel
1941    {
1942        /**
1943         * 
1944         */
1945        private static final long serialVersionUID = -7399887230624608711L;
1946
1947        long lastRefreshTime;
1948        boolean forceFineRendering;
1949
1950        public CustomVtkPanel()
1951        {
1952            super();
1953
1954            lastRefreshTime = 0L;
1955            forceFineRendering = false;
1956            // key events should be forwarded from the viewer
1957            removeKeyListener(this);
1958        }
1959
1960        public boolean getForceFineRendering()
1961        {
1962            return forceFineRendering;
1963        }
1964
1965        public void setForceFineRendering(boolean value)
1966        {
1967            forceFineRendering = value;
1968        }
1969
1970        /**
1971         * Update mouse cursor
1972         * 
1973         * @param b
1974         */
1975        protected void updateCursor(boolean consumedByCanvas)
1976        {
1977            // don't change custom cursor
1978            if (getCursor().getType() == Cursor.CUSTOM_CURSOR)
1979                return;
1980
1981            // consumed by canvas --> return it to origin
1982            if (consumedByCanvas)
1983            {
1984                GuiUtil.setCursor(this, Cursor.HAND_CURSOR);
1985                return;
1986            }
1987
1988            final Sequence seq = getSequence();
1989
1990            if (seq != null)
1991            {
1992                final ROI overlappedRoi = seq.getFocusedROI();
1993
1994                // overlapping an ROI ?
1995                if (overlappedRoi != null)
1996                {
1997                    final Layer layer = getLayer(overlappedRoi);
1998
1999                    if ((layer != null) && layer.isVisible())
2000                    {
2001                        GuiUtil.setCursor(this, Cursor.HAND_CURSOR);
2002                        return;
2003                    }
2004                }
2005
2006                final List<ROI> selectedRois = seq.getSelectedROIs();
2007
2008                // search if we are overriding ROI control points
2009                for (ROI selectedRoi : selectedRois)
2010                {
2011                    final Layer layer = getLayer(selectedRoi);
2012
2013                    if ((layer != null) && layer.isVisible() && selectedRoi.hasSelectedPoint())
2014                    {
2015                        GuiUtil.setCursor(this, Cursor.HAND_CURSOR);
2016                        return;
2017                    }
2018                }
2019            }
2020
2021            GuiUtil.setCursor(this, Cursor.DEFAULT_CURSOR);
2022        }
2023
2024        @Override
2025        public void paint(Graphics g)
2026        {
2027            if (forceFineRendering)
2028                setFineRendering();
2029            else
2030            {
2031                // several repaint in a short period of time --> set fast rendering for 1 second
2032                if ((lastRefreshTime != 0) && ((System.currentTimeMillis() - lastRefreshTime) < 250))
2033                    setCoarseRendering(1000);
2034            }
2035
2036            // call paint on overlays first
2037            paintLayers(getLayers(true), getImageLayer(), getSequence());
2038
2039            // then do 3D rendering
2040            super.paint(g);
2041
2042            lastRefreshTime = System.currentTimeMillis();
2043        }
2044
2045        /**
2046         * Paint layers
2047         */
2048        protected void paintLayers(List<Layer> sortedLayers, final Layer imageLayer, Sequence seq)
2049        {
2050            final boolean lv = isLayersVisible();
2051
2052            // call paint in inverse order to have first overlay "at top"
2053            for (int i = sortedLayers.size() - 1; i >= 0; i--)
2054            {
2055                final Layer layer = sortedLayers.get(i);
2056
2057                if (layer == null)
2058                    continue;
2059                
2060                // update VTK visibility flag
2061                for (vtkProp prop : VtkUtil.getLayerProps(layer))
2062                {
2063                    // image layer is not impacted by global layer visibility
2064                    if (layer == imageLayer)
2065                    {
2066                        // we have a no empty image --> display it if layer is visible
2067                        if (layer.isVisible() && (seq != null) && !seq.isEmpty())
2068                            prop.SetVisibility(1);
2069                        else
2070                            prop.SetVisibility(0);
2071                    }
2072                    else
2073                    {
2074                        boolean visible = lv && layer.isVisible();
2075                        // FIXME: find a better method to know visibility flags should not be impacted here
2076                        final vtkInformation vtkInfo = prop.GetPropertyKeys();
2077
2078                        if (vtkInfo != null)
2079                        {
2080                            // pick the visibility info
2081                            if ((vtkInfo.Has(visibilityKey) != 0) && (vtkInfo.Get(visibilityKey) == 0))
2082                                visible = false;
2083                        }
2084
2085                        // finally set the visibility state
2086                        prop.SetVisibility(visible ? 1 : 0);
2087                    }
2088
2089                    // opacity seems to not be correctly handled in VTK ??
2090                    if (prop instanceof vtkActor)
2091                        ((vtkActor) prop).GetProperty().SetOpacity(layer.getOpacity());
2092                    else if (prop instanceof vtkActor2D)
2093                        ((vtkActor2D) prop).GetProperty().SetOpacity(layer.getOpacity());
2094                }
2095
2096                // then do layer painting
2097                if (lv && layer.isVisible())
2098                    // important to set Graphics to null for VtkCanvas as some plugins rely on it to detect VtkCanvas
2099                    // (note that this is not a good way of doing it)
2100                    layer.getOverlay().paint(null, seq, VtkCanvas.this);
2101            }
2102        }
2103
2104        @Override
2105        public void mouseEntered(MouseEvent e)
2106        {
2107            // send mouse event to overlays
2108            VtkCanvas.this.mouseEntered(e, getMouseImagePos5D());
2109
2110            super.mouseEntered(e);
2111        }
2112
2113        @Override
2114        public void mouseExited(MouseEvent e)
2115        {
2116            // send mouse event to overlays
2117            VtkCanvas.this.mouseExited(e, getMouseImagePos5D());
2118
2119            super.mouseExited(e);
2120        }
2121
2122        @Override
2123        public void mouseClicked(MouseEvent e)
2124        {
2125            // send mouse event to overlays
2126            VtkCanvas.this.mouseClick(e, getMouseImagePos5D());
2127
2128            super.mouseClicked(e);
2129        }
2130
2131        @Override
2132        public void mouseMoved(MouseEvent e)
2133        {
2134            // update mouse position
2135            setMousePos(e.getPoint());
2136
2137            // get picked object (mouse move/drag event)
2138            pickedObject = pick(e.getX(), e.getY());
2139
2140            // send mouse event to overlays
2141            VtkCanvas.this.mouseMove(e, getMouseImagePos5D());
2142
2143            final boolean consumed = e.isConsumed();
2144
2145            super.mouseMoved(e);
2146
2147            // refresh mouse cursor (do it after all process)
2148            updateCursor(!consumed && e.isConsumed());
2149        }
2150
2151        @Override
2152        public void mouseDragged(MouseEvent e)
2153        {
2154            // update mouse position
2155            setMousePos(e.getPoint());
2156
2157            // get picked object (mouse move/drag event)
2158            pickedObject = pick(e.getX(), e.getY());
2159
2160            // send mouse event to overlays
2161            VtkCanvas.this.mouseDrag(e, getMouseImagePos5D());
2162
2163            final boolean consumed = e.isConsumed();
2164
2165            super.mouseDragged(e);
2166
2167            // refresh mouse cursor (do it after all process)
2168            updateCursor(!consumed && e.isConsumed());
2169        }
2170
2171        @Override
2172        public void mousePressed(MouseEvent e)
2173        {
2174            // send mouse event to overlays
2175            VtkCanvas.this.mousePressed(e, getMouseImagePos5D());
2176
2177            super.mousePressed(e);
2178        }
2179
2180        @Override
2181        public void mouseReleased(MouseEvent e)
2182        {
2183            // send mouse event to overlays
2184            VtkCanvas.this.mouseReleased(e, getMouseImagePos5D());
2185
2186            super.mouseReleased(e);
2187        }
2188
2189        @Override
2190        public void mouseWheelMoved(MouseWheelEvent e)
2191        {
2192            // send mouse event to overlays
2193            VtkCanvas.this.mouseWheelMoved(e, getMouseImagePos5D());
2194
2195            super.mouseWheelMoved(e);
2196        }
2197
2198        @Override
2199        public void keyPressed(KeyEvent e)
2200        {
2201            if (!e.isConsumed())
2202            {
2203                switch (e.getKeyCode())
2204                {
2205                    case KeyEvent.VK_R:
2206                        // reset view
2207                        resetCamera();
2208
2209                        // also reset LUT
2210                        if (EventUtil.isShiftDown(e, true))
2211                        {
2212                            final Sequence sequence = getSequence();
2213                            final Viewer viewer = getViewer();
2214
2215                            if ((viewer != null) && (sequence != null))
2216                            {
2217                                final LUT lut = sequence.createCompatibleLUT();
2218
2219                                // set default opacity for 3D display
2220                                lut.setAlphaToLinear3D();
2221                                viewer.setLut(lut);
2222                            }
2223                        }
2224                        else
2225                            repaint();
2226
2227                        e.consume();
2228                        break;
2229                }
2230            }
2231
2232            super.keyPressed(e);
2233        }
2234    }
2235
2236    /**
2237     * Image overlay to encapsulate VTK image volume in a canvas layer
2238     */
2239    protected class VtkCanvasImageOverlay extends IcyCanvasImageOverlay implements VtkPainter
2240    {
2241        public VtkCanvasImageOverlay()
2242        {
2243            super();
2244
2245            // create image volume
2246            imageVolume = new VtkImageVolume();
2247        }
2248
2249        @Override
2250        public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas)
2251        {
2252            // nothing here
2253        }
2254
2255        @Override
2256        public vtkProp[] getProps()
2257        {
2258            // return the image volume as prop
2259            return new vtkProp[] {imageVolume.getVolume()};
2260        }
2261    }
2262
2263    /**
2264     * Property to update
2265     */
2266    protected static class Property
2267    {
2268        String name;
2269        Object value;
2270
2271        public Property(String name, Object value)
2272        {
2273            super();
2274
2275            this.name = name;
2276            this.value = value;
2277        }
2278
2279        @Override
2280        public boolean equals(Object obj)
2281        {
2282            if (obj instanceof Property)
2283                return name.equals(((Property) obj).name);
2284
2285            return super.equals(obj);
2286        }
2287
2288        @Override
2289        public int hashCode()
2290        {
2291            return name.hashCode();
2292        }
2293    };
2294
2295    /**
2296     * Properties updater helper class
2297     */
2298    protected class PropertiesUpdater extends Thread
2299    {
2300        final LinkedBlockingQueue<Property> toUpdate;
2301
2302        public PropertiesUpdater()
2303        {
2304            super("VTK canvas properties updater");
2305
2306            toUpdate = new LinkedBlockingQueue<VtkCanvas.Property>(256);
2307        }
2308
2309        public synchronized void submit(Property prop)
2310        {
2311            // remove previous property of same name
2312            if (toUpdate.remove(prop))
2313            {
2314                // if we already had a layers visible update then we update all layers
2315                if (prop.name.equals(PROPERTY_LAYERS_VISIBLE))
2316                    prop.value = null;
2317            }
2318
2319            // add the property
2320            toUpdate.add(prop);
2321        }
2322
2323        protected void updateProperty(Property prop) throws InterruptedException
2324        {
2325            final String name = prop.name;
2326            final Object value = prop.value;
2327
2328            if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_AMBIENT))
2329            {
2330                final double d = ((Double) value).doubleValue();
2331
2332                invokeOnEDT(new Runnable()
2333                {
2334                    @Override
2335                    public void run()
2336                    {
2337                        imageVolume.setAmbient(d);
2338                    }
2339                });
2340
2341                preferences.putDouble(ID_AMBIENT, d);
2342            }
2343            else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_DIFFUSE))
2344            {
2345                final double d = ((Double) value).doubleValue();
2346
2347                invokeOnEDT(new Runnable()
2348                {
2349                    @Override
2350                    public void run()
2351                    {
2352                        imageVolume.setDiffuse(d);
2353                    }
2354                });
2355
2356                preferences.putDouble(ID_DIFFUSE, d);
2357            }
2358            else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_SPECULAR))
2359            {
2360                final double d = ((Double) value).doubleValue();
2361
2362                invokeOnEDT(new Runnable()
2363                {
2364                    @Override
2365                    public void run()
2366                    {
2367                        imageVolume.setSpecular(d);
2368                    }
2369                });
2370
2371                preferences.putDouble(ID_SPECULAR, d);
2372            }
2373            else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_BG_COLOR))
2374            {
2375                final Color color = (Color) value;
2376
2377                invokeOnEDT(new Runnable()
2378                {
2379                    @Override
2380                    public void run()
2381                    {
2382                        setBackgroundColorInternal(color);
2383                    }
2384                });
2385
2386                preferences.putInt(ID_BGCOLOR, color.getRGB());
2387            }
2388            else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_INTERPOLATION))
2389            {
2390                final int i = ((Integer) value).intValue();
2391
2392                invokeOnEDT(new Runnable()
2393                {
2394                    @Override
2395                    public void run()
2396                    {
2397                        imageVolume.setInterpolationMode(i);
2398                    }
2399                });
2400
2401                preferences.putInt(ID_INTERPOLATION, i);
2402            }
2403            else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_MAPPER))
2404            {
2405                final boolean gpuRendering = ((Boolean) value).booleanValue();
2406
2407                invokeOnEDT(new Runnable()
2408                {
2409                    @Override
2410                    public void run()
2411                    {
2412                        imageVolume.setGPURendering(gpuRendering);
2413                    }
2414                });
2415
2416                preferences.putInt(ID_MAPPER, gpuRendering ? 1 : 0);
2417            }
2418            else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_BLENDING))
2419            {
2420                final VtkVolumeBlendType type = (VtkVolumeBlendType) value;
2421
2422                invokeOnEDT(new Runnable()
2423                {
2424                    @Override
2425                    public void run()
2426                    {
2427                        imageVolume.setBlendingMode(type);
2428                    }
2429                });
2430
2431                preferences.putInt(ID_BLENDING, getVolumeBlendingMode().ordinal());
2432            }
2433            else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_SAMPLE))
2434            {
2435                final int i = ((Integer) value).intValue();
2436
2437                invokeOnEDT(new Runnable()
2438                {
2439                    @Override
2440                    public void run()
2441                    {
2442                        imageVolume.setSampleResolution(i);
2443                    }
2444                });
2445
2446                preferences.putDouble(ID_SAMPLE, i);
2447            }
2448            else if (StringUtil.equals(name, PROPERTY_AXES))
2449            {
2450                final boolean b = ((Boolean) value).booleanValue();
2451
2452                invokeOnEDT(new Runnable()
2453                {
2454                    @Override
2455                    public void run()
2456                    {
2457                        panel3D.setAxisOrientationDisplayEnable(b);
2458                    }
2459                });
2460
2461                preferences.putBoolean(ID_AXES, b);
2462            }
2463            else if (StringUtil.equals(name, PROPERTY_BOUNDINGBOX))
2464            {
2465                final boolean b = ((Boolean) value).booleanValue();
2466
2467                invokeOnEDT(new Runnable()
2468                {
2469                    @Override
2470                    public void run()
2471                    {
2472                        boundingBox.SetVisibility(b ? 1 : 0);
2473                    }
2474                });
2475
2476                preferences.putBoolean(ID_BOUNDINGBOX, b);
2477            }
2478            else if (StringUtil.equals(name, PROPERTY_BOUNDINGBOX_GRID))
2479            {
2480                final boolean b = ((Boolean) value).booleanValue();
2481
2482                invokeOnEDT(new Runnable()
2483                {
2484                    @Override
2485                    public void run()
2486                    {
2487                        rulerBox.SetDrawXGridlines(b ? 1 : 0);
2488                        rulerBox.SetDrawYGridlines(b ? 1 : 0);
2489                        rulerBox.SetDrawZGridlines(b ? 1 : 0);
2490                    }
2491                });
2492
2493                preferences.putBoolean(ID_BOUNDINGBOX_GRID, b);
2494            }
2495            else if (StringUtil.equals(name, PROPERTY_BOUNDINGBOX_RULES))
2496            {
2497                final boolean b = ((Boolean) value).booleanValue();
2498
2499                invokeOnEDT(new Runnable()
2500                {
2501                    @Override
2502                    public void run()
2503                    {
2504                        rulerBox.SetXAxisTickVisibility(b ? 1 : 0);
2505                        rulerBox.SetXAxisMinorTickVisibility(b ? 1 : 0);
2506                        rulerBox.SetYAxisTickVisibility(b ? 1 : 0);
2507                        rulerBox.SetYAxisMinorTickVisibility(b ? 1 : 0);
2508                        rulerBox.SetZAxisTickVisibility(b ? 1 : 0);
2509                        rulerBox.SetZAxisMinorTickVisibility(b ? 1 : 0);
2510                    }
2511                });
2512
2513                preferences.putBoolean(ID_BOUNDINGBOX_RULES, b);
2514            }
2515            else if (StringUtil.equals(name, PROPERTY_BOUNDINGBOX_LABELS))
2516            {
2517                final boolean b = ((Boolean) value).booleanValue();
2518
2519                invokeOnEDT(new Runnable()
2520                {
2521                    @Override
2522                    public void run()
2523                    {
2524                        rulerBox.SetXAxisLabelVisibility(b ? 1 : 0);
2525                        rulerBox.SetYAxisLabelVisibility(b ? 1 : 0);
2526                        rulerBox.SetZAxisLabelVisibility(b ? 1 : 0);
2527                    }
2528                });
2529
2530                preferences.putBoolean(ID_BOUNDINGBOX_LABELS, b);
2531            }
2532            else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_SHADING))
2533            {
2534                final boolean b = ((Boolean) value).booleanValue();
2535
2536                invokeOnEDT(new Runnable()
2537                {
2538                    @Override
2539                    public void run()
2540                    {
2541                        imageVolume.setShade(b);
2542                    }
2543                });
2544
2545                preferences.putBoolean(ID_SHADING, b);
2546            }
2547            else if (StringUtil.equals(name, PROPERTY_LUT))
2548            {
2549                updateLut();
2550            }
2551            else if (StringUtil.equals(name, PROPERTY_SCALE))
2552            {
2553                final double[] oldScale = getVolumeScale();
2554                final double[] newScale = (double[]) value;
2555
2556                if (!Arrays.equals(oldScale, newScale))
2557                {
2558                    invokeOnEDT(new Runnable()
2559                    {
2560                        @Override
2561                        public void run()
2562                        {
2563                            imageVolume.setScale(newScale);
2564                            // need to update bounding box as well
2565                            updateBoundingBoxSize();
2566                        }
2567                    });
2568                }
2569            }
2570            else if (StringUtil.equals(name, PROPERTY_DATA))
2571            {
2572                final vtkImageData data = getImageData();
2573
2574                invokeOnEDT(new Runnable()
2575                {
2576                    @Override
2577                    public void run()
2578                    {
2579                        // set image data
2580                        updateImageData(data);
2581                    }
2582                });
2583            }
2584            else if (StringUtil.equals(name, PROPERTY_BOUNDS))
2585            {
2586                invokeOnEDT(new Runnable()
2587                {
2588                    @Override
2589                    public void run()
2590                    {
2591                        updateBoundingBoxSize();
2592                    }
2593                });
2594            }
2595            else if (StringUtil.equals(name, PROPERTY_LAYERS_VISIBLE))
2596            {
2597                // force refresh
2598                refresh();
2599            }
2600        }
2601
2602        @Override
2603        public void run()
2604        {
2605            while (!isInterrupted())
2606            {
2607                try
2608                {
2609                    updateProperty(toUpdate.take());
2610                }
2611                catch (InterruptedException e)
2612                {
2613                    // just end process
2614                    break;
2615                }
2616
2617                // need to refresh rendering
2618                if (toUpdate.isEmpty())
2619                    refresh();
2620            }
2621
2622            // help GC
2623            toUpdate.clear();
2624        }
2625    }
2626
2627    /**
2628     * VTK overlay updater helper class
2629     */
2630    protected class VtkOverlayUpdater extends Thread
2631    {
2632        final LinkedList<vtkProp> propToAdd;
2633        final LinkedList<vtkProp> propToRemove;
2634
2635        public VtkOverlayUpdater()
2636        {
2637            super("VTK canvas overlay updater");
2638
2639            propToAdd = new LinkedList<vtkProp>();
2640            propToRemove = new LinkedList<vtkProp>();
2641        }
2642
2643        public void addProp(vtkProp prop)
2644        {
2645            synchronized (propToAdd)
2646            {
2647                propToAdd.add(prop);
2648            }
2649        }
2650
2651        public void removeProp(vtkProp prop)
2652        {
2653            synchronized (propToRemove)
2654            {
2655                propToRemove.add(prop);
2656            }
2657        }
2658
2659        public void addProps(List<vtkProp> props)
2660        {
2661            synchronized (propToAdd)
2662            {
2663                propToAdd.addAll(props);
2664            }
2665        }
2666
2667        public void removeProps(List<vtkProp> props)
2668        {
2669            synchronized (propToRemove)
2670            {
2671                propToRemove.addAll(props);
2672            }
2673        }
2674
2675        public void addProps(vtkProp[] props)
2676        {
2677            synchronized (propToAdd)
2678            {
2679                for (vtkProp prop : props)
2680                    propToAdd.add(prop);
2681            }
2682        }
2683
2684        public void removeProps(vtkProp[] props)
2685        {
2686            synchronized (propToAdd)
2687            {
2688                for (vtkProp prop : props)
2689                    propToRemove.add(prop);
2690            }
2691        }
2692
2693        @Override
2694        public void run()
2695        {
2696            while (!isInterrupted())
2697            {
2698                while (!isInterrupted() && !propToAdd.isEmpty())
2699                {
2700                    invokeOnEDTSilent(new Runnable()
2701                    {
2702                        @Override
2703                        public void run()
2704                        {
2705                            final vtkRenderer r = getRenderer();
2706                            final vtkCamera cam = getCamera();
2707                            int done = 0;
2708
2709                            if ((r != null) && (cam != null))
2710                            {
2711                                // add actor by packet of 1000
2712                                while (!propToAdd.isEmpty() && (done++ < 1000))
2713                                {
2714                                    final vtkProp prop = propToAdd.removeFirst();
2715
2716                                    // actor not yet present in renderer ?
2717                                    if (r.HasViewProp(prop) == 0)
2718                                    {
2719                                        // refresh camera property for this specific kind of actor
2720                                        if (prop instanceof vtkCubeAxesActor)
2721                                            ((vtkCubeAxesActor) prop).SetCamera(cam);
2722
2723                                        // add the actor to the renderer
2724                                        r.AddViewProp(prop);
2725                                    }
2726                                }
2727                            }
2728                        }
2729                    });
2730
2731                    // sleep a bit to offer a bit of responsiveness
2732                    ThreadUtil.sleep(10);
2733                    // and refresh
2734                    refresh();
2735                }
2736
2737                while (!isInterrupted() && !propToRemove.isEmpty())
2738                {
2739                    invokeOnEDTSilent(new Runnable()
2740                    {
2741                        @Override
2742                        public void run()
2743                        {
2744                            final vtkRenderer r = getRenderer();
2745                            final vtkCamera cam = getCamera();
2746                            int done = 0;
2747
2748                            if ((r != null) && (cam != null))
2749                            {
2750                                // remove actors from renderer by packet of 1000
2751                                while (!propToRemove.isEmpty() && (done++ < 1000))
2752                                    r.RemoveViewProp(propToRemove.removeFirst());
2753                            }
2754                        }
2755                    });
2756
2757                    // sleep a bit to offer a bit of responsiveness
2758                    ThreadUtil.sleep(10);
2759                    // and refresh
2760                    refresh();
2761                }
2762
2763                // sleep a bit
2764                ThreadUtil.sleep(1);
2765            }
2766
2767            // help GC
2768            propToAdd.clear();
2769            propToRemove.clear();
2770        }
2771    }
2772}