package plugins.kernel.canvas; import icy.canvas.Canvas3D; import icy.canvas.CanvasLayerEvent; import icy.canvas.CanvasLayerEvent.LayersEventType; import icy.canvas.IcyCanvas; import icy.canvas.IcyCanvasEvent; import icy.canvas.IcyCanvasEvent.IcyCanvasEventType; import icy.canvas.Layer; import icy.common.exception.TooLargeArrayException; import icy.gui.component.button.IcyToggleButton; import icy.gui.util.ComponentUtil; import icy.gui.util.GuiUtil; import icy.gui.viewer.Viewer; import icy.image.IcyBufferedImage; import icy.image.lut.LUT; import icy.image.lut.LUT.LUTChannel; import icy.painter.Overlay; import icy.painter.VtkPainter; import icy.preferences.CanvasPreferences; import icy.preferences.XMLPreferences; import icy.resource.ResourceUtil; import icy.resource.icon.IcyIcon; import icy.roi.ROI; import icy.sequence.Sequence; import icy.sequence.SequenceEvent.SequenceEventType; import icy.system.IcyExceptionHandler; import icy.system.thread.ThreadUtil; import icy.type.collection.array.Array1DUtil; import icy.type.point.Point3D; import icy.util.ColorUtil; import icy.util.EventUtil; import icy.util.StringUtil; import icy.vtk.IcyVtkPanel; import icy.vtk.VtkImageVolume; import icy.vtk.VtkImageVolume.VtkVolumeBlendType; import icy.vtk.VtkImageVolume.VtkVolumeMapperType; import icy.vtk.VtkUtil; import java.awt.AWTException; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.Robot; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.LinkedBlockingQueue; import javax.swing.JToolBar; import plugins.kernel.canvas.VtkSettingPanel.SettingChangeListener; import vtk.vtkActor; import vtk.vtkActor2D; import vtk.vtkAxesActor; import vtk.vtkCamera; import vtk.vtkColorTransferFunction; import vtk.vtkCubeAxesActor; import vtk.vtkImageData; import vtk.vtkInformation; import vtk.vtkInformationIntegerKey; import vtk.vtkLight; import vtk.vtkOrientationMarkerWidget; import vtk.vtkPicker; import vtk.vtkPiecewiseFunction; import vtk.vtkProp; import vtk.vtkRenderWindow; import vtk.vtkRenderer; import vtk.vtkTextActor; import vtk.vtkTextProperty; /** * VTK 3D canvas class. * * @author Stephane */ @SuppressWarnings("deprecation") public class VtkCanvas extends Canvas3D implements ActionListener, SettingChangeListener { /** * */ private static final long serialVersionUID = -1274251057822161271L; /** * icons */ public static final Image ICON_AXES3D = ResourceUtil.getAlphaIconAsImage("axes3d.png"); public static final Image ICON_BOUNDINGBOX = ResourceUtil.getAlphaIconAsImage("bbox.png"); public static final Image ICON_GRID = ResourceUtil.getAlphaIconAsImage("3x3_grid.png"); public static final Image ICON_RULER = ResourceUtil.getAlphaIconAsImage("ruler.png"); public static final Image ICON_RULERLABEL = ResourceUtil.getAlphaIconAsImage("ruler_label.png"); public static final Image ICON_TARGET = ResourceUtil.getAlphaIconAsImage("target.png"); /** * properties */ public static final String PROPERTY_AXES = "axis"; public static final String PROPERTY_BOUNDINGBOX = "boundingBox"; public static final String PROPERTY_BOUNDINGBOX_GRID = "boundingBoxGrid"; public static final String PROPERTY_BOUNDINGBOX_RULES = "boundingBoxRules"; public static final String PROPERTY_BOUNDINGBOX_LABELS = "boundingBoxLabels"; public static final String PROPERTY_LUT = "lut"; public static final String PROPERTY_DATA = "data"; public static final String PROPERTY_SCALE = "scale"; public static final String PROPERTY_BOUNDS = "bounds"; /** * Used for outline visibility information in vtkActor */ public static final vtkInformationIntegerKey visibilityKey = new vtkInformationIntegerKey().MakeKey("Visibility", "Property"); /** * preferences id */ protected static final String PREF_ID = "vtkCanvas"; /** * id */ protected static final String ID_BOUNDINGBOX = PROPERTY_BOUNDINGBOX; protected static final String ID_BOUNDINGBOX_GRID = PROPERTY_BOUNDINGBOX_GRID; protected static final String ID_BOUNDINGBOX_RULES = PROPERTY_BOUNDINGBOX_RULES; protected static final String ID_BOUNDINGBOX_LABELS = PROPERTY_BOUNDINGBOX_LABELS; // protected static final String ID_PICKONMOUSEMOVE = "pickOnMouseMove"; protected static final String ID_AXES = PROPERTY_AXES; protected static final String ID_SHADING = VtkSettingPanel.PROPERTY_SHADING; protected static final String ID_BGCOLOR = VtkSettingPanel.PROPERTY_BG_COLOR; protected static final String ID_MAPPER = VtkSettingPanel.PROPERTY_MAPPER; protected static final String ID_SAMPLE = VtkSettingPanel.PROPERTY_SAMPLE; protected static final String ID_BLENDING = VtkSettingPanel.PROPERTY_BLENDING; protected static final String ID_INTERPOLATION = VtkSettingPanel.PROPERTY_INTERPOLATION; protected static final String ID_AMBIENT = VtkSettingPanel.PROPERTY_AMBIENT; protected static final String ID_DIFFUSE = VtkSettingPanel.PROPERTY_DIFFUSE; protected static final String ID_SPECULAR = VtkSettingPanel.PROPERTY_SPECULAR; /** * basic vtk objects */ protected vtkRenderer renderer; protected vtkRenderWindow renderWindow; protected vtkCamera camera; // protected vtkAxesActor axes; protected vtkCubeAxesActor boundingBox; protected vtkCubeAxesActor rulerBox; protected vtkTextActor textInfo; protected vtkTextProperty textProperty; // protected vtkOrientationMarkerWidget widget; protected vtkProp pickedObject; /** * volume data */ protected VtkImageVolume imageVolume; /** * GUI */ protected VtkSettingPanel settingPanel; protected CustomVtkPanel panel3D; protected IcyToggleButton axesButton; protected IcyToggleButton boundingBoxButton; protected IcyToggleButton gridButton; protected IcyToggleButton rulerButton; protected IcyToggleButton rulerLabelButton; // protected IcyToggleButton pickOnMouseMoveButton; /** * internals */ protected PropertiesUpdater propertiesUpdater; protected VtkOverlayUpdater overlayUpdater; protected XMLPreferences preferences; protected final EDTTask<Object> edtTask; protected boolean initialized; public VtkCanvas(Viewer viewer) { super(viewer); initialized = false; // more than 4 channels ? --> not supported by VTK if (getImageSizeC() > 4) throw new UnsupportedOperationException( "VTK does not support image with more than 4 channels !\nYou should remove some channels in your image."); // multi channel view posC = -1; // adjust LUT alpha level for 3D view lut.setAlphaToLinear3D(); pickedObject = null; // create the properties and the VTK overlay updater processors propertiesUpdater = new PropertiesUpdater(); overlayUpdater = new VtkOverlayUpdater(); preferences = CanvasPreferences.getPreferences().node(PREF_ID); settingPanel = new VtkSettingPanel(); panel = settingPanel; // initialize VTK components & main GUI panel3D = new CustomVtkPanel(); panel3D.addKeyListener(this); // set 3D view in center add(panel3D, BorderLayout.CENTER); // update nav bar & mouse infos mouseInfPanel.setVisible(false); updateZNav(); updateTNav(); // create toolbar buttons axesButton = new IcyToggleButton(new IcyIcon(ICON_AXES3D)); axesButton.setFocusable(false); axesButton.setToolTipText("Display 3D axis"); boundingBoxButton = new IcyToggleButton(new IcyIcon(ICON_BOUNDINGBOX)); boundingBoxButton.setFocusable(false); boundingBoxButton.setToolTipText("Display bounding box"); gridButton = new IcyToggleButton(new IcyIcon(ICON_GRID)); gridButton.setFocusable(false); gridButton.setToolTipText("Display grid"); rulerButton = new IcyToggleButton(new IcyIcon(ICON_RULER)); rulerButton.setFocusable(false); rulerButton.setToolTipText("Display rules"); rulerLabelButton = new IcyToggleButton(new IcyIcon(ICON_RULERLABEL)); rulerLabelButton.setFocusable(false); rulerLabelButton.setToolTipText("Display rules label"); // pickOnMouseMoveButton = new IcyToggleButton(new IcyIcon(ICON_TARGET)); // pickOnMouseMoveButton.setFocusable(false); // pickOnMouseMoveButton.setToolTipText("Enabled object focus on mouse over (slow)"); // set fast rendering during initialization panel3D.setCoarseRendering(1000); renderer = panel3D.getRenderer(); renderWindow = panel3D.getRenderWindow(); camera = renderer.GetActiveCamera(); // initialize text info actor (need to be done before the first getImageData() call ! textInfo = new vtkTextActor(); textInfo.SetInput("Not enough memory to display this 3D image !"); textInfo.SetPosition(10, 10); // not visible by default textInfo.SetVisibility(0); // change text properties textProperty = textInfo.GetTextProperty(); textProperty.SetFontFamilyToArial(); // rebuild volume image updateImageData(getImageData()); final Sequence seq = getSequence(); // setup volume scaling if (seq != null) imageVolume.setScale(seq.getPixelSizeX(), seq.getPixelSizeY(), seq.getPixelSizeZ()); // setup volume LUT imageVolume.setLUT(getLut()); // initialize axe // axes = new vtkAxesActor(); // widget = new vtkOrientationMarkerWidget(); // widget.SetOrientationMarker(axes); // widget.SetInteractor(interactor); // widget.SetViewport(0, 0, 0.3, 0.3); // widget.SetEnabled(1); // initialize bounding box boundingBox = new vtkCubeAxesActor(); boundingBox.SetBounds(imageVolume.getVolume().GetBounds()); boundingBox.SetCamera(camera); // set bounding box labels properties boundingBox.SetFlyModeToStaticEdges(); boundingBox.SetUseBounds(true); boundingBox.XAxisLabelVisibilityOff(); boundingBox.XAxisMinorTickVisibilityOff(); boundingBox.XAxisTickVisibilityOff(); boundingBox.YAxisLabelVisibilityOff(); boundingBox.YAxisMinorTickVisibilityOff(); boundingBox.YAxisTickVisibilityOff(); boundingBox.ZAxisLabelVisibilityOff(); boundingBox.ZAxisMinorTickVisibilityOff(); boundingBox.ZAxisTickVisibilityOff(); // initialize rules and box axis rulerBox = new vtkCubeAxesActor(); rulerBox.SetBounds(imageVolume.getVolume().GetBounds()); rulerBox.SetCamera(camera); // set bounding box labels properties rulerBox.SetXUnits("micro meter"); rulerBox.GetTitleTextProperty(0).SetColor(1.0, 0.0, 0.0); rulerBox.GetLabelTextProperty(0).SetColor(1.0, 0.0, 0.0); rulerBox.GetXAxesGridlinesProperty().SetColor(1.0, 0.0, 0.0); rulerBox.GetXAxesGridpolysProperty().SetColor(1.0, 0.0, 0.0); rulerBox.GetXAxesInnerGridlinesProperty().SetColor(1.0, 0.0, 0.0); rulerBox.SetYUnits("micro meter"); rulerBox.GetTitleTextProperty(1).SetColor(0.0, 1.0, 0.0); rulerBox.GetLabelTextProperty(1).SetColor(0.0, 1.0, 0.0); rulerBox.GetYAxesGridlinesProperty().SetColor(0.0, 1.0, 0.0); rulerBox.GetYAxesGridpolysProperty().SetColor(0.0, 1.0, 0.0); rulerBox.GetYAxesInnerGridlinesProperty().SetColor(0.0, 1.0, 0.0); rulerBox.SetZUnits("micro meter"); rulerBox.GetTitleTextProperty(2).SetColor(0.0, 0.0, 1.0); rulerBox.GetLabelTextProperty(2).SetColor(0.0, 0.0, 1.0); rulerBox.GetZAxesGridlinesProperty().SetColor(0.0, 0.0, 1.0); rulerBox.GetZAxesGridpolysProperty().SetColor(0.0, 0.0, 1.0); rulerBox.GetZAxesInnerGridlinesProperty().SetColor(0.0, 0.0, 1.0); rulerBox.XAxisVisibilityOff(); rulerBox.YAxisVisibilityOff(); rulerBox.ZAxisVisibilityOff(); rulerBox.SetGridLineLocation(VtkUtil.VTK_GRID_LINES_FURTHEST); rulerBox.SetFlyModeToOuterEdges(); rulerBox.SetUseBounds(true); // restore settings settingPanel.setBackgroundColor(new Color(preferences.getInt(ID_BGCOLOR, 0x000000))); settingPanel.setVolumeBlendingMode(VtkVolumeBlendType.values()[preferences.getInt(ID_BLENDING, VtkVolumeBlendType.COMPOSITE.ordinal())]); // volume mapper settingPanel.setGPURendering(preferences.getInt(ID_MAPPER, 0) != 0); settingPanel.setVolumeInterpolation(preferences.getInt(ID_INTERPOLATION, VtkUtil.VTK_LINEAR_INTERPOLATION)); settingPanel.setVolumeSample(preferences.getInt(ID_SAMPLE, 0)); settingPanel.setVolumeAmbient(preferences.getDouble(ID_AMBIENT, 0.2d)); settingPanel.setVolumeDiffuse(preferences.getDouble(ID_DIFFUSE, 0.4d)); settingPanel.setVolumeSpecular(preferences.getDouble(ID_SPECULAR, 0.4d)); settingPanel.setVolumeShading(preferences.getBoolean(ID_SHADING, false)); axesButton.setSelected(preferences.getBoolean(ID_AXES, true)); boundingBoxButton.setSelected(preferences.getBoolean(ID_BOUNDINGBOX, true)); gridButton.setSelected(preferences.getBoolean(ID_BOUNDINGBOX_GRID, true)); rulerButton.setSelected(preferences.getBoolean(ID_BOUNDINGBOX_RULES, false)); rulerLabelButton.setSelected(preferences.getBoolean(ID_BOUNDINGBOX_LABELS, false)); // pickOnMouseMoveButton.setSelected(preferences.getBoolean(ID_PICKONMOUSEMOVE, false)); // always false by default (preferable) // pickOnMouseMoveButton.setSelected(false); // apply restored settings setBackgroundColorInternal(settingPanel.getBackgroundColor()); imageVolume.setBlendingMode(settingPanel.getVolumeBlendingMode()); imageVolume.setGPURendering(settingPanel.getGPURendering()); // mapper may change blending mode settingPanel.setVolumeBlendingMode(imageVolume.getBlendingMode()); imageVolume.setInterpolationMode(settingPanel.getVolumeInterpolation()); imageVolume.setSampleResolution(settingPanel.getVolumeSample()); imageVolume.setAmbient(settingPanel.getVolumeAmbient()); imageVolume.setDiffuse(settingPanel.getVolumeDiffuse()); imageVolume.setSpecular(settingPanel.getVolumeSpecular()); imageVolume.setShade(settingPanel.getVolumeShading()); // axes.SetVisibility(axesButton.isSelected() ? 1 : 0); boundingBox.SetVisibility(boundingBoxButton.isSelected() ? 1 : 0); rulerBox.SetDrawXGridlines(gridButton.isSelected() ? 1 : 0); rulerBox.SetDrawYGridlines(gridButton.isSelected() ? 1 : 0); rulerBox.SetDrawZGridlines(gridButton.isSelected() ? 1 : 0); rulerBox.SetXAxisTickVisibility(rulerButton.isSelected() ? 1 : 0); rulerBox.SetXAxisMinorTickVisibility(rulerButton.isSelected() ? 1 : 0); rulerBox.SetYAxisTickVisibility(rulerButton.isSelected() ? 1 : 0); rulerBox.SetYAxisMinorTickVisibility(rulerButton.isSelected() ? 1 : 0); rulerBox.SetZAxisTickVisibility(rulerButton.isSelected() ? 1 : 0); rulerBox.SetZAxisMinorTickVisibility(rulerButton.isSelected() ? 1 : 0); rulerBox.SetXAxisLabelVisibility(rulerLabelButton.isSelected() ? 1 : 0); rulerBox.SetYAxisLabelVisibility(rulerLabelButton.isSelected() ? 1 : 0); rulerBox.SetZAxisLabelVisibility(rulerLabelButton.isSelected() ? 1 : 0); // setPickOnMouseMove(pickOnMouseMoveButton.isSelected()); // add volume to renderer renderer.AddVolume(imageVolume.getVolume()); // add bounding box & ruler renderer.AddViewProp(boundingBox); renderer.AddViewProp(rulerBox); renderer.AddViewProp(textInfo); // reset camera resetCamera(); // apply lut depending channel configuration updateLut(); // we can now listen for setting changes settingPanel.addSettingChangeListener(this); axesButton.addActionListener(this); boundingBoxButton.addActionListener(this); gridButton.addActionListener(this); rulerButton.addActionListener(this); rulerLabelButton.addActionListener(this); // pickOnMouseMoveButton.addActionListener(this); // create EDTTask object edtTask = new EDTTask<Object>(); // start the properties and VTK overlay updater processors propertiesUpdater.start(); overlayUpdater.start(); // initialized ! initialized = true; // add layers actors overlayUpdater.addProps(VtkUtil.getLayersProps(getLayers(false))); } @Override public void shutDown() { final long st = System.currentTimeMillis(); // wait for initialization to complete before shutdown (max 5s) while (((System.currentTimeMillis() - st) < 5000L) && !initialized) ThreadUtil.sleep(1); propertiesUpdater.interrupt(); try { // be sure there is no more processing here propertiesUpdater.join(); } catch (InterruptedException e) { // can ignore safely } overlayUpdater.interrupt(); try { // be sure there is no more processing here overlayUpdater.join(); } catch (InterruptedException e) { // can ignore safely } // no more initialized (prevent extra useless processing) initialized = false; propertiesUpdater = null; overlayUpdater = null; // VTK stuff in EDT invokeOnEDTSilent(new Runnable() { @Override public void run() { renderer.RemoveAllViewProps(); // renderer.Delete(); // renderWindow.Delete(); imageVolume.release(); // widget.Delete(); // axes.Delete(); boundingBox.Delete(); // camera.Delete(); // dispose extra panel 3D stuff panel3D.disposeInternal(); } }); // AWTMultiCaster of vtkPanel keep reference of this frame so // we have to release as most stuff we can removeAll(); panel.removeAll(); renderer = null; renderWindow = null; imageVolume = null; // widget = null; // axes = null; boundingBox = null; camera = null; panel3D.removeKeyListener(this); panel3D = null; panel = null; // do parent shutdown now super.shutDown(); // call VTK GC: better if we can avoid this ! // vtkObjectBase.JAVA_OBJECT_MANAGER.gc(false); } /** * Returns initialized state of VtkCanvas */ public boolean isInitialized() { return initialized; } @Override public void customizeToolbar(JToolBar toolBar) { toolBar.addSeparator(); toolBar.add(axesButton); toolBar.addSeparator(); toolBar.add(boundingBoxButton); toolBar.add(gridButton); toolBar.add(rulerButton); toolBar.add(rulerLabelButton); // toolBar.addSeparator(); // toolBar.add(pickOnMouseMoveButton); } @Override protected Overlay createImageOverlay() { return new VtkCanvasImageOverlay(); } /** * Request exclusive access to VTK rendering.<br> * * @deprecated Use <code>getVtkPanel().lock()</code> instead. */ @Deprecated public void lock() { if (panel3D != null) panel3D.lock(); } /** * Release exclusive access from VTK rendering. * * @deprecated Use <code>getVtkPanel().unlock()</code> instead. */ @Deprecated public void unlock() { if (panel3D != null) panel3D.unlock(); } /** * @deprecated Use {@link #getCamera()} instead */ @Deprecated public vtkCamera getActiveCam() { return getCamera(); } /** * @return the VTK scene camera object */ public vtkCamera getCamera() { return camera; } /** * @return the VTK default scene light object.<br> * Can be <code>null</code> if render window is not yet initialized. */ public vtkLight getLight() { return renderer.GetLights().GetNextItem(); } /** * @return the VTK axes object */ public vtkAxesActor getAxes() { return panel3D.getAxesActor(); } /** * @return the VTK bounding box object */ public vtkCubeAxesActor getBoundingBox() { return boundingBox; } /** * @return the VTK ruler box object */ public vtkCubeAxesActor getRulerBox() { return rulerBox; } /** * @deprecated there is no more orientation widget because of the jogl bug with multiple viewport. */ @Deprecated public vtkOrientationMarkerWidget getWidget() { return null; // return widget; } /** * @return the VTK image volume object */ public VtkImageVolume getImageVolume() { return imageVolume; } /** * Returns rendering background color */ public Color getBackgroundColor() { return settingPanel.getBackgroundColor(); } /** * Sets rendering background color */ public void setBackgroundColor(Color value) { settingPanel.setBackgroundColor(value); } /** * Returns <code>true</code> if the volume bounding box is visible. */ public boolean isBoundingBoxVisible() { return boundingBoxButton.isSelected(); } /** * Enable / disable volume bounding box display. */ public void setBoundingBoxVisible(boolean value) { if (boundingBoxButton.isSelected() != value) boundingBoxButton.doClick(); } /** * Returns <code>true</code> if the volume bounding box grid is visible. */ public boolean isBoundingBoxGridVisible() { return gridButton.isSelected(); } /** * Enable / disable volume bounding box grid display. */ public void setBoundingBoxGridVisible(boolean value) { if (gridButton.isSelected() != value) gridButton.doClick(); } /** * Returns <code>true</code> if the volume bounding box ruler are visible. */ public boolean isBoundingBoxRulerVisible() { return rulerButton.isSelected(); } /** * Enable / disable volume bounding box ruler display. */ public void setBoundingBoxRulerVisible(boolean value) { if (rulerButton.isSelected() != value) rulerButton.doClick(); } /** * Returns <code>true</code> if the volume bounding box ruler labels are visible. */ public boolean isBoundingBoxRulerLabelsVisible() { return rulerLabelButton.isSelected(); } /** * Enable / disable volume bounding box ruler labels display. */ public void setBoundingBoxRulerLabelsVisible(boolean value) { if (rulerLabelButton.isSelected() != value) rulerLabelButton.doClick(); } /** * @deprecated USe {@link #setBackgroundColorInternal(Color)} */ @Deprecated public void setBoundingBoxColor(Color color) { setBackgroundColorInternal(color); } /** * Set background color (internal) */ public void setBackgroundColorInternal(Color color) { renderer.SetBackground(Array1DUtil.floatArrayToDoubleArray(color.getColorComponents(null))); final Color oppositeColor; // adjust bounding box color if (ColorUtil.getLuminance(color) > 128) oppositeColor = Color.black; else oppositeColor = Color.white; final float[] comp = oppositeColor.getRGBColorComponents(null); final float r = comp[0]; final float g = comp[0]; final float b = comp[0]; boundingBox.GetXAxesLinesProperty().SetColor(r, g, b); boundingBox.GetYAxesLinesProperty().SetColor(r, g, b); boundingBox.GetZAxesLinesProperty().SetColor(r, g, b); rulerBox.GetXAxesGridlinesProperty().SetColor(r, g, b); rulerBox.GetXAxesGridpolysProperty().SetColor(r, g, b); rulerBox.GetXAxesInnerGridlinesProperty().SetColor(r, g, b); rulerBox.GetXAxesLinesProperty().SetColor(r, g, b); rulerBox.GetYAxesGridlinesProperty().SetColor(r, g, b); rulerBox.GetYAxesGridpolysProperty().SetColor(r, g, b); rulerBox.GetYAxesInnerGridlinesProperty().SetColor(r, g, b); rulerBox.GetYAxesLinesProperty().SetColor(r, g, b); rulerBox.GetZAxesGridlinesProperty().SetColor(r, g, b); rulerBox.GetZAxesGridpolysProperty().SetColor(r, g, b); rulerBox.GetZAxesInnerGridlinesProperty().SetColor(r, g, b); rulerBox.GetZAxesLinesProperty().SetColor(r, g, b); textProperty.SetColor(r, g, b); } /** * Returns <code>true</code> if the 3D axis are visible. */ public boolean isAxisVisible() { return axesButton.isSelected(); } /** * Enable / disable 3D axis display. */ public void setAxisVisible(boolean value) { if (axesButton.isSelected() != value) axesButton.doClick(); } /** * @see VtkImageVolume#getBlendingMode() */ public VtkVolumeBlendType getVolumeBlendingMode() { return settingPanel.getVolumeBlendingMode(); } /** * @see VtkImageVolume#setBlendingMode(VtkVolumeBlendType) */ public void setVolumeBlendingMode(VtkVolumeBlendType value) { settingPanel.setVolumeBlendingMode(value); } /** * @see VtkImageVolume#getSampleResolution() */ public int getVolumeSample() { return settingPanel.getVolumeSample(); } /** * @see VtkImageVolume#setSampleResolution(double) */ public void setVolumeSample(int value) { settingPanel.setVolumeSample(value); } /** * @see VtkImageVolume#getShade() */ public boolean isVolumeShadingEnable() { return settingPanel.getVolumeShading(); } /** * @see VtkImageVolume#setShade(boolean) */ public void setVolumeShadingEnable(boolean value) { settingPanel.setVolumeShading(value); } /** * @see VtkImageVolume#getAmbient() */ public double getVolumeAmbient() { return settingPanel.getVolumeAmbient(); } /** * @see VtkImageVolume#setAmbient(double) */ public void setVolumeAmbient(double value) { settingPanel.setVolumeAmbient(value); } /** * @see VtkImageVolume#getDiffuse() */ public double getVolumeDiffuse() { return settingPanel.getVolumeDiffuse(); } /** * @see VtkImageVolume#setDiffuse(double) */ public void setVolumeDiffuse(double value) { settingPanel.setVolumeDiffuse(value); } /** * @see VtkImageVolume#getSpecular() */ public double getVolumeSpecular() { return settingPanel.getVolumeSpecular(); } /** * @see VtkImageVolume#setSpecular(double) */ public void setVolumeSpecular(double value) { settingPanel.setVolumeSpecular(value); } /** * @see VtkImageVolume#getInterpolationMode() */ public int getVolumeInterpolation() { return settingPanel.getVolumeInterpolation(); } /** * @see VtkImageVolume#setInterpolationMode(int) */ public void setVolumeInterpolation(int value) { settingPanel.setVolumeInterpolation(value); } /** * @see VtkImageVolume#getGPURendering() */ public boolean getGPURendering() { return settingPanel.getGPURendering(); } /** * @see VtkImageVolume#setGPURendering(boolean) */ public void setGPURendering(boolean value) { settingPanel.setGPURendering(value); } /** * @deprecated Use {@link #getGPURendering()} instead */ @Deprecated public VtkVolumeMapperType getVolumeMapperType() { if (getGPURendering()) return VtkVolumeMapperType.RAYCAST_GPU_OPENGL; return VtkVolumeMapperType.RAYCAST_CPU_FIXEDPOINT; } /** * @deprecated Use {@link #setGPURendering(boolean)} instead */ @Deprecated public void setVolumeMapperType(VtkVolumeMapperType value) { setGPURendering(VtkVolumeMapperType.RAYCAST_GPU_OPENGL.equals(value)); } /** * @return visible state of the image volume object * @see VtkImageVolume#isVisible() */ public boolean isVolumeVisible() { return imageVolume.isVisible(); } /** * Sets the visible state of the image volume object * * @see VtkImageVolume#setVisible(boolean) */ public void setVolumeVisible(boolean value) { imageVolume.setVisible(value); } /** * Force render refresh */ @Override public void refresh() { if (!initialized) return; // refresh rendering if (panel3D != null) panel3D.repaint(); } // private void test() // { // // exemple de clipping a utiliser par la suite. // if ( false ) // { // vtkPlane plane = new vtkPlane(); // plane.SetOrigin(1000, 1000, 1000); // plane.SetNormal( 1, 1, 0); // volumeMapper.AddClippingPlane( plane ); // } // // vtkOrientationMarkerWidget ow = new vtkOrientationMarkerWidget(); // } protected void resetCamera() { camera.SetViewUp(0, -1, 0); renderer.ResetCamera(); camera.Elevation(200); renderer.ResetCameraClippingRange(); } /** * @deprecated Use {@link #setVolumeSample(int)} instead */ @Deprecated @Override public void setVolumeDistanceSample(int value) { setVolumeSample(value); } // /** // * Returns channel position based on enabled channel in LUT // */ // protected int getChannelPos() // { // final LUT lut = getLut(); // int result = -1; // // for (int c = 0; c < lut.getNumChannel(); c++) // { // final LUTChannel lutChannel = lut.getLutChannel(c); // // if (lutChannel.isEnabled()) // { // if (result == -1) // result = c; // else // return -1; // } // } // // return result; // } /** * @deprecated Always enabled now (always return <code>true</code>) */ @Deprecated public boolean getPickOnMouseMove() { return true; // return pickOnMouseMoveButton.isSelected(); } /** * @deprecated Always enable now */ @Deprecated public void setPickOnMouseMove(boolean value) { // if (pickOnMouseMoveButton.isSelected() != value) // pickOnMouseMoveButton.doClick(); } /** * Returns the picked object on the last mouse move/drag event (can be <code>null</code> if no object was picked). * * @see #pickProp(int, int) */ public vtkProp getPickedObject() { return pickedObject; } /** * @deprecated use {@link #pickProp(int, int)} instead. */ @Deprecated public vtkActor pick(int x, int y) { return (vtkActor) panel3D.pick(x, y); } /** * Pick object at specified position and return it. * * @see #getPickedObject() * @see icy.vtk.IcyVtkPanel#pick(int, int) */ public vtkProp pickProp(int x, int y) { return panel3D.pick(x, y); } /** * Return reached z world position (normalized) for specified display position */ public double getWorldZ(int x, int y) { final vtkRenderer r = getRenderer(); final vtkRenderWindow rw = getRenderWindow(); if ((r == null) || (rw == null)) return 0d; // need to revert Y axis return r.GetZ(x, rw.GetSize()[1] - y); } public double getWorldZ(Point pt) { return getWorldZ(pt.x, pt.y); } /** * Convert world coordinates to display coordinates */ public Point3D worldToDisplay(Point3D pt) { if (pt == null) return new Point3D.Double(); return worldToDisplay(pt.getX(), pt.getY(), pt.getZ()); } /** * Convert world coordinates to display coordinates */ public Point3D worldToDisplay(double x, double y, double z) { final vtkRenderer r = getRenderer(); final vtkRenderWindow rw = getRenderWindow(); if ((r == null) || (rw == null)) return new Point3D.Double(); r.SetWorldPoint(x, y, z, 1d); r.WorldToDisplay(); final Point3D result = new Point3D.Double(r.GetDisplayPoint()); // need to revert Y axis result.setY(rw.GetSize()[1] - result.getY()); return result; } /** * Convert display coordinates to world coordinates. */ public Point3D displayToWorld(Point pt) { if (pt == null) return new Point3D.Double(); return displayToWorld(pt.x, pt.y); } /** * Convert display coordinates to world coordinates.<br> */ public Point3D displayToWorld(int x, int y) { // get camera focal point final double[] fp = camera.GetFocalPoint(); // transform it to display position (with Z info) final Point3D displayFP = worldToDisplay(fp[0], fp[1], fp[2]); // keep the Z info from focal point return displayToWorld(x, y, displayFP.getZ()); // return displayToWorld(x, y, getWorldZ(x, y)); } /** * Convert display coordinates to world coordinates.<br> * Default value for Z should be 0d */ public Point3D displayToWorld(Point pt, double z) { if (pt == null) return new Point3D.Double(); return displayToWorld(pt.getX(), pt.getY(), z); } /** * Convert display coordinates to world coordinates.<br> * Default value for Z is 0d */ public Point3D displayToWorld(double x, double y, double z) { final vtkRenderer r = getRenderer(); final vtkRenderWindow rw = getRenderWindow(); if ((r == null) || (rw == null)) return new Point3D.Double(); // need to revert Y axis r.SetDisplayPoint(x, rw.GetSize()[1] - y, z); r.DisplayToWorld(); final double[] result = r.GetWorldPoint(); // final vtkPicker picker = getPicker(); // pickProp((int)x, (int)y); // // picker.Pick(x, rw.GetSize()[1] - y, 0, r); // double[] pos = picker.GetPickPosition(); // // System.out.println("displayToWorld(" + x + ", " + y + ", " + z + "):"); // System.out.println(String.format("%.5g, %.5g, %.5g", result[0], result[1], result[2])); // System.out.println(String.format("from Pick: %.5g, %.5g, %.5g", pos[0], pos[1], pos[2])); // normalize if (result[3] != 0d) { result[0] /= result[3]; result[1] /= result[3]; result[2] /= result[3]; } else { result[0] = 0d; result[1] = 0d; result[2] = 0d; } return new Point3D.Double(result[0], result[1], result[2]); } @Override public Point imageToCanvas(double x, double y, double z) { final double[] scaling = getVolumeScale(); final Point3D result = worldToDisplay(x * scaling[0], y * scaling[1], z * scaling[2]); // System.out.println("imageToCanvas(" + x + ", " + y + ", " + z + "): " + result); // ignore Z coordinate return new Point((int) result.getX(), (int) result.getY()); } @Override public Point3D.Double canvasToImage(int x, int y) { final double[] scaling = getVolumeScale(); // check scaling does not contains any 0 for (double d : scaling) { if (d == 0d) return new Point3D.Double(); } // get image position in 3D Point3D result = displayToWorld(x, y); // get the view axis final double[] directionOfProjection = getCamera().GetDirectionOfProjection(); final double dirX = Math.abs(directionOfProjection[0]); final double dirY = Math.abs(directionOfProjection[1]); final double dirZ = Math.abs(directionOfProjection[2]); // we always want to have 2D coordinates cancel position which is not "visible" axis if (dirX > dirY) { if (dirX > dirZ) result.setX(Double.NaN); else result.setZ(Double.NaN); } else { if (dirY > dirZ) result.setY(Double.NaN); else result.setZ(Double.NaN); } result = new Point3D.Double(result.getX() / scaling[0], result.getY() / scaling[1], result.getZ() / scaling[2]); // System.out.println("canvasToImage(" + x + ", " + y + "): " // + String.format("%.5g, %.5g, %.5g", result.getX(), result.getY(), result.getZ())); return (Point3D.Double) result; } /** * @deprecated Use {@link VtkUtil#getLayerProps(Layer)} instead. */ @Deprecated protected vtkProp[] getLayerActors(Layer layer) { return VtkUtil.getLayerProps(layer); } protected void addLayerActors(Layer layer) { // not yet (or no more) initialized if (overlayUpdater == null) return; overlayUpdater.addProps(VtkUtil.getLayerProps(layer)); } protected void removeLayerActors(Layer layer) { // not yet (or no more) initialized if (overlayUpdater == null) return; overlayUpdater.removeProps(VtkUtil.getLayerProps(layer)); } protected void addLayersActors(List<Layer> layers) { // not yet (or no more) initialized if (overlayUpdater == null) return; overlayUpdater.addProps(VtkUtil.getLayersProps(layers)); } protected void updateBoundingBoxSize() { final double[] bounds = imageVolume.getVolume().GetBounds(); boundingBox.SetBounds(bounds); rulerBox.SetBounds(bounds); } /** * Build and get image data */ protected vtkImageData getImageData() { vtkImageData result = null; final Sequence sequence = getSequence(); if ((sequence == null) || sequence.isEmpty()) return result; final int posT = getPositionT(); final int posC = getPositionC(); final Object data; try { if (posC == -1) { data = sequence.getDataCopyCXYZ(posT); result = VtkUtil.getImageData(data, sequence.getDataType_(), sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeZ(), sequence.getSizeC()); } else { data = sequence.getDataCopyXYZ(posT, posC); result = VtkUtil.getImageData(data, sequence.getDataType_(), sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeZ(), 1); } } catch (TooLargeArrayException e) { // cannot allocate a such large contiguous array return null; } catch (OutOfMemoryError e) { // just not enough memory return null; } return result; } /** * update image data */ protected void updateImageData(vtkImageData data) { if (data != null) { imageVolume.setVolumeData(data); imageVolume.getVolume().SetVisibility(getImageLayer().isVisible() ? 1 : 0); if (textInfo != null) textInfo.SetVisibility(0); } else { // no data --> hide volume imageVolume.getVolume().SetVisibility(0); if (textInfo != null) { final Sequence seq = getSequence(); // we have an image --> not enough memory to display it (show message) if ((seq != null) && !seq.isEmpty()) textInfo.SetVisibility(1); } } } protected void updateLut() { final LUT lut = getLut(); // update the whole LUT for (int c = 0; c < lut.getNumChannel(); c++) updateLut(lut.getLutChannel(c), c); } protected void updateLut(LUTChannel lutChannel, int channel) { final Sequence sequence = getSequence(); if ((sequence == null) || sequence.isEmpty()) return; final int ch = channel; final vtkColorTransferFunction colorMap = VtkUtil.getColorMap(lutChannel); final vtkPiecewiseFunction opacityMap = VtkUtil.getOpacityMap(lutChannel); imageVolume.setColorMap(colorMap, ch); imageVolume.setOpacityMap(opacityMap, ch); } @Override public Component getViewComponent() { return getVtkPanel(); } public IcyVtkPanel getVtkPanel() { return panel3D; } /** * @deprecated Use {@link #getVtkPanel()} */ @Deprecated @Override public IcyVtkPanel getPanel3D() { return getVtkPanel(); } @Override public vtkRenderer getRenderer() { return renderer; } public vtkRenderWindow getRenderWindow() { return renderWindow; } /** * @see icy.vtk.IcyVtkPanel#getPicker() */ public vtkPicker getPicker() { return panel3D.getPicker(); } /** * Get scaling for image volume rendering */ @Override public double[] getVolumeScale() { return imageVolume.getScale(); } /** * Set scaling for image volume rendering */ @Override public void setVolumeScale(double x, double y, double z) { propertyChange(PROPERTY_SCALE, new double[] {x, y, z}); } @Override public void keyPressed(KeyEvent e) { // send to overlays super.keyPressed(e); // forward to view panel3D.keyPressed(e); if (!e.isConsumed()) { switch (e.getKeyCode()) { case KeyEvent.VK_LEFT: if (EventUtil.isMenuControlDown(e, true)) setPositionT(Math.max(getPositionT() - 5, 0)); else setPositionT(Math.max(getPositionT() - 1, 0)); e.consume(); break; case KeyEvent.VK_RIGHT: if (EventUtil.isMenuControlDown(e, true)) setPositionT(getPositionT() + 5); else setPositionT(getPositionT() + 1); e.consume(); break; case KeyEvent.VK_NUMPAD2: if (EventUtil.isMenuControlDown(e, true)) panel3D.translateView(0, -50); else panel3D.translateView(0, -10); refresh(); e.consume(); break; case KeyEvent.VK_NUMPAD4: if (EventUtil.isMenuControlDown(e, true)) panel3D.translateView(-50, 0); else panel3D.translateView(-10, 0); refresh(); e.consume(); break; case KeyEvent.VK_NUMPAD6: if (EventUtil.isMenuControlDown(e, true)) panel3D.translateView(50, 0); else panel3D.translateView(10, 0); refresh(); e.consume(); break; case KeyEvent.VK_NUMPAD8: if (EventUtil.isMenuControlDown(e, true)) panel3D.translateView(0, 50); else panel3D.translateView(0, 10); refresh(); e.consume(); break; } } } @Override public void keyReleased(KeyEvent e) { // send to overlays super.keyReleased(e); // forward to view panel3D.keyReleased(e); } @Override protected void setPositionZInternal(int z) { // not supported, Z should stay at -1 } @Override protected void setPositionCInternal(int c) { // single channel mode is not possible here if (c != -1) return; super.setPositionCInternal(c); } @Override public double getScaleX() { final double dist = getCamera().GetDistance(); // cannot compute scaling if (dist <= 0d) return 1d; final double imageSizeX = getImageSizeX(); // FIXME: from where come that x2 factor final double result = (2 * imageSizeX * getVolumeScale()[0]) / dist; final double canvasImageRatio = getCanvasSizeX() / ((imageSizeX == 0d) ? 1d : imageSizeX); return result * canvasImageRatio; } @Override public double getScaleY() { final double dist = getCamera().GetDistance(); // cannot compute scaling if (dist <= 0d) return 1d; final double imageSizeY = getImageSizeY(); // FIXME: from where come that x2 factor final double result = (2 * imageSizeY * getVolumeScale()[1]) / dist; final double canvasImageRatio = getCanvasSizeY() / ((imageSizeY == 0d) ? 1d : imageSizeY); return result * canvasImageRatio; } @Override public void setMouseImagePosX(double value) { // just ignore NaN position (canvasToImage(..) can return NaN for specific dimension) if (!Double.isNaN(value)) super.setMouseImagePosX(value); } @Override public void setMouseImagePosY(double value) { // just ignore NaN position (canvasToImage(..) can return NaN for specific dimension) if (!Double.isNaN(value)) super.setMouseImagePosY(value); } @Override public void setMouseImagePosZ(double value) { // just ignore NaN position (canvasToImage(..) can return NaN for specific dimension) if (!Double.isNaN(value)) super.setMouseImagePosZ(value); } @Override public BufferedImage getRenderedImage(int t, int c) { final CustomVtkPanel vp = panel3D; if (vp == null) return null; // save position final int prevT = getPositionT(); final int prevC = getPositionC(); // set wanted position (needed for correct overlay drawing) // we have to fire events else some stuff can miss the change setPositionT(t); setPositionC(c); try { final vtkImageData imageData = getImageData(); // VTK need this to be called in the EDT invokeOnEDTSilent(new Runnable() { @Override public void run() { // set image data updateImageData(imageData); // force fine rendering here vp.setForceFineRendering(true); try { // render now ! vp.paint(vp.getGraphics()); } finally { vp.setForceFineRendering(false); } } }); try { final Robot robot = new Robot(); final Rectangle bounds = vp.getBounds(); // transform in screen coordinates bounds.setLocation(ComponentUtil.convertPointToScreen(bounds.getLocation(), vp)); // do the capture return robot.createScreenCapture(bounds); } catch (AWTException e) { IcyExceptionHandler.showErrorMessage(e, true); return null; } // final int[] size = renderWindow.GetSize(); // final int w = size[0]; // final int h = size[1]; // final vtkUnsignedCharArray array = new vtkUnsignedCharArray(); // final vtkImageData imageData = getImageData(); // final BufferedImage[] result = new BufferedImage[1]; // // // VTK need this to be called in the EDT // invokeOnEDTSilent(new Runnable() // { // @Override // public void run() // { // // set image data // updateImageData(imageData); // // // force fine rendering here // panel3D.setForceFineRendering(true); // try // { // // render now ! // panel3D.paint(panel3D.getGraphics()); // } // finally // { // panel3D.setForceFineRendering(false); // } // // try // { // Robot r = new Robot(); // result[0] = r.createScreenCapture(SwingUtilities.convertRectangle(panel3D, getBounds(), null)); // } // catch (AWTException e) // { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // // NOTE: in vtk the [0,0] pixel is bottom left, so a vertical flip is required // // NOTE: GetRGBACharPixelData gives problematic results depending on the platform // // (see comment about alpha and platform-dependence in the doc for vtkWindowToImageFilter) // // Since the canvas is opaque, simply use GetPixelData. // renderWindow.GetPixelData(0, 0, w - 1, h - 1, 1, array); // } // }); // // // convert the vtk array into a IcyBufferedImage // final byte[] inData = array.GetJavaArray(); // final BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); // final int[] outData = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); // // int inOffset = 0; // for (int y = h - 1; y >= 0; y--) // { // int outOffset = y * w; // // for (int x = 0; x < w; x++) // { // final int r = TypeUtil.unsign(inData[inOffset++]); // final int g = TypeUtil.unsign(inData[inOffset++]); // final int b = TypeUtil.unsign(inData[inOffset++]); // // outData[outOffset++] = (r << 16) | (g << 8) | (b << 0); // } // } // // return image; } finally { // restore position setPositionT(prevT); setPositionC(prevC); } } @Override public BufferedImage getRenderedImage(int t, int z, int c, boolean canvasView) { if (z != -1) throw new UnsupportedOperationException( "Error: getRenderedImage(..) with z != -1 not supported on Canvas3D."); if (!canvasView) System.out.println("Warning: getRenderedImage(..) with canvasView = false not supported on Canvas3D."); return getRenderedImage(t, c); } protected void invokeOnEDT(Runnable task) throws InterruptedException { // in initialization --> just execute if (edtTask == null) { task.run(); return; } edtTask.setTask(task); try { ThreadUtil.invokeNow(edtTask); } catch (InterruptedException e) { throw e; } catch (Exception t) { // just ignore as this is async process System.out.println("[VTKCanvas] Warning:" + t); } } protected void invokeOnEDTSilent(Runnable task) { try { invokeOnEDT(task); } catch (InterruptedException e) { // just ignore } } @Override public void changed(IcyCanvasEvent event) { super.changed(event); // avoid useless process during canvas initialization if (!initialized) return; if (event.getType() == IcyCanvasEventType.POSITION_CHANGED) { switch (event.getDim()) { case C: propertyChange(PROPERTY_DATA, null); break; case T: propertyChange(PROPERTY_DATA, null); break; case Z: // shouldn't happen break; } } } @Override protected void lutChanged(int channel) { super.lutChanged(channel); // avoid useless process during canvas initialization if (!initialized) return; propertyChange(PROPERTY_LUT, Integer.valueOf(channel)); } @Override protected void sequenceOverlayChanged(Overlay overlay, SequenceEventType type) { super.sequenceOverlayChanged(overlay, type); if (!initialized) return; // refresh refresh(); } @Override protected void sequenceDataChanged(IcyBufferedImage image, SequenceEventType type) { super.sequenceDataChanged(image, type); // rebuild image data and bounds propertyChange(PROPERTY_DATA, null); propertyChange(PROPERTY_BOUNDS, null); } @Override protected void sequenceMetaChanged(String metadataName) { super.sequenceMetaChanged(metadataName); final Sequence sequence = getSequence(); if ((sequence == null) || sequence.isEmpty()) return; // need to set scale ? if (StringUtil.isEmpty(metadataName) || (StringUtil.equals(metadataName, Sequence.ID_PIXEL_SIZE_X) || StringUtil.equals(metadataName, Sequence.ID_PIXEL_SIZE_Y) || StringUtil.equals(metadataName, Sequence.ID_PIXEL_SIZE_Z))) { setVolumeScale(sequence.getPixelSizeX(), sequence.getPixelSizeY(), sequence.getPixelSizeZ()); } } @Override protected void layerChanged(CanvasLayerEvent event) { super.layerChanged(event); if (!initialized) return; if (event.getType() == LayersEventType.CHANGED) { final String propertyName = event.getProperty(); // we ignore priority property here as we display in 3D if (propertyName.equals(Layer.PROPERTY_OPACITY) || propertyName.equals(Layer.PROPERTY_VISIBLE)) propertyChange(PROPERTY_LAYERS_VISIBLE, event.getSource()); } } @Override protected void layerAdded(Layer layer) { super.layerAdded(layer); addLayerActors(layer); } @Override protected void layerRemoved(Layer layer) { super.layerRemoved(layer); removeLayerActors(layer); } @Override protected void layersVisibleChanged() { propertyChange(PROPERTY_LAYERS_VISIBLE, null); } /** * Refresh VTK actor properties from layer properties (alpha and visible) */ protected void refreshLayerProperties(Layer layer) { final boolean lv = isLayersVisible(); // refresh all layers if (layer == null) { for (Layer l : getLayers()) { for (vtkProp prop : VtkUtil.getLayerProps(l)) { // image layer is not impacted by global layer visibility if (l == getImageLayer()) { final Sequence seq = getSequence(); // we have a no empty image --> display it if layer is visible if (l.isVisible() && (seq != null) && !seq.isEmpty()) prop.SetVisibility(1); else prop.SetVisibility(0); } else { boolean visible = lv && l.isVisible(); // FIXME: find a better method to know visibility flags should not be impacted here final vtkInformation vtkInfo = prop.GetPropertyKeys(); if (vtkInfo != null) { // pick the visibility info if ((vtkInfo.Has(visibilityKey) != 0) && (vtkInfo.Get(visibilityKey) == 0)) visible = false; } // finally set the visibility state prop.SetVisibility(visible ? 1 : 0); } // opacity seems to not be correctly handled in VTK ?? if (prop instanceof vtkActor) ((vtkActor) prop).GetProperty().SetOpacity(l.getOpacity()); else if (prop instanceof vtkActor2D) ((vtkActor2D) prop).GetProperty().SetOpacity(l.getOpacity()); } } } else { for (vtkProp prop : VtkUtil.getLayerProps(layer)) { if (layer == getImageLayer()) { final Sequence seq = getSequence(); // we have a no empty image --> display it if layer is visible if (layer.isVisible() && (seq != null) && !seq.isEmpty()) prop.SetVisibility(1); else prop.SetVisibility(0); } else { boolean visible = lv && layer.isVisible(); // FIXME: hacky method to know visibility flags should not be impacted here final vtkInformation vtkInfo = prop.GetPropertyKeys(); if (vtkInfo != null) { // pick the visibility info if ((vtkInfo.Has(visibilityKey) != 0) && (vtkInfo.Get(visibilityKey) == 0)) visible = false; } // finally set the visibility state prop.SetVisibility(visible ? 1 : 0); } // opacity seems to not be correctly handled in VTK ?? if (prop instanceof vtkActor) ((vtkActor) prop).GetProperty().SetOpacity(layer.getOpacity()); else if (prop instanceof vtkActor2D) ((vtkActor2D) prop).GetProperty().SetOpacity(layer.getOpacity()); } } } @Override public void actionPerformed(ActionEvent e) { final Object source = e.getSource(); // translate button action to property change event if (source == axesButton) propertyChange(PROPERTY_AXES, Boolean.valueOf(axesButton.isSelected())); else if (source == boundingBoxButton) propertyChange(PROPERTY_BOUNDINGBOX, Boolean.valueOf(boundingBoxButton.isSelected())); else if (source == gridButton) propertyChange(PROPERTY_BOUNDINGBOX_GRID, Boolean.valueOf(gridButton.isSelected())); else if (source == rulerButton) propertyChange(PROPERTY_BOUNDINGBOX_RULES, Boolean.valueOf(rulerButton.isSelected())); else if (source == rulerLabelButton) propertyChange(PROPERTY_BOUNDINGBOX_LABELS, Boolean.valueOf(rulerLabelButton.isSelected())); // else if (source == pickOnMouseMoveButton) // preferences.putBoolean(ID_PICKONMOUSEMOVE, pickOnMouseMoveButton.isSelected()); } protected void propertyChange(String name, Object value) { final Property prop = new Property(name, value); propertiesUpdater.submit(prop); } /* * Called when one of the value in setting panel has changed */ @Override public void settingChange(PropertyChangeEvent evt) { propertyChange(evt.getPropertyName(), evt.getNewValue()); } protected class EDTTask<T> implements Callable<T> { protected Runnable task; public void setTask(Runnable task) { this.task = task; } @Override public T call() throws Exception { task.run(); return null; } } protected class CustomVtkPanel extends IcyVtkPanel { /** * */ private static final long serialVersionUID = -7399887230624608711L; long lastRefreshTime; boolean forceFineRendering; public CustomVtkPanel() { super(); lastRefreshTime = 0L; forceFineRendering = false; // key events should be forwarded from the viewer removeKeyListener(this); } public boolean getForceFineRendering() { return forceFineRendering; } public void setForceFineRendering(boolean value) { forceFineRendering = value; } /** * Update mouse cursor * * @param b */ protected void updateCursor(boolean consumedByCanvas) { // don't change custom cursor if (getCursor().getType() == Cursor.CUSTOM_CURSOR) return; // consumed by canvas --> return it to origin if (consumedByCanvas) { GuiUtil.setCursor(this, Cursor.HAND_CURSOR); return; } final Sequence seq = getSequence(); if (seq != null) { final ROI overlappedRoi = seq.getFocusedROI(); // overlapping an ROI ? if (overlappedRoi != null) { final Layer layer = getLayer(overlappedRoi); if ((layer != null) && layer.isVisible()) { GuiUtil.setCursor(this, Cursor.HAND_CURSOR); return; } } final List<ROI> selectedRois = seq.getSelectedROIs(); // search if we are overriding ROI control points for (ROI selectedRoi : selectedRois) { final Layer layer = getLayer(selectedRoi); if ((layer != null) && layer.isVisible() && selectedRoi.hasSelectedPoint()) { GuiUtil.setCursor(this, Cursor.HAND_CURSOR); return; } } } GuiUtil.setCursor(this, Cursor.DEFAULT_CURSOR); } @Override public void paint(Graphics g) { if (forceFineRendering) setFineRendering(); else { // several repaint in a short period of time --> set fast rendering for 1 second if ((lastRefreshTime != 0) && ((System.currentTimeMillis() - lastRefreshTime) < 250)) setCoarseRendering(1000); } // call paint on overlays first if (isLayersVisible()) { final List<Layer> layers = getLayers(true); final Layer imageLayer = getImageLayer(); final Sequence seq = getSequence(); // call paint in inverse order to have first overlay "at top" for (int i = layers.size() - 1; i >= 0; i--) { final Layer layer = layers.get(i); // don't call paint on the image layer if (layer != imageLayer) paintLayer(seq, layer); } } // then do 3D rendering super.paint(g); lastRefreshTime = System.currentTimeMillis(); } /** * Draw specified layer */ protected void paintLayer(Sequence seq, Layer layer) { if (layer.isVisible()) layer.getOverlay().paint(null, seq, VtkCanvas.this); } @Override public void mouseEntered(MouseEvent e) { // send mouse event to overlays VtkCanvas.this.mouseEntered(e, getMouseImagePos5D()); super.mouseEntered(e); } @Override public void mouseExited(MouseEvent e) { // send mouse event to overlays VtkCanvas.this.mouseExited(e, getMouseImagePos5D()); super.mouseExited(e); } @Override public void mouseClicked(MouseEvent e) { // send mouse event to overlays VtkCanvas.this.mouseClick(e, getMouseImagePos5D()); super.mouseClicked(e); } @Override public void mouseMoved(MouseEvent e) { // update mouse position setMousePos(e.getPoint()); // get picked object (mouse move/drag event) pickedObject = pick(e.getX(), e.getY()); // send mouse event to overlays VtkCanvas.this.mouseMove(e, getMouseImagePos5D()); final boolean consumed = e.isConsumed(); super.mouseMoved(e); // refresh mouse cursor (do it after all process) updateCursor(!consumed && e.isConsumed()); } @Override public void mouseDragged(MouseEvent e) { // update mouse position setMousePos(e.getPoint()); // get picked object (mouse move/drag event) pickedObject = pick(e.getX(), e.getY()); // send mouse event to overlays VtkCanvas.this.mouseDrag(e, getMouseImagePos5D()); final boolean consumed = e.isConsumed(); super.mouseDragged(e); // refresh mouse cursor (do it after all process) updateCursor(!consumed && e.isConsumed()); } @Override public void mousePressed(MouseEvent e) { // send mouse event to overlays VtkCanvas.this.mousePressed(e, getMouseImagePos5D()); super.mousePressed(e); } @Override public void mouseReleased(MouseEvent e) { // send mouse event to overlays VtkCanvas.this.mouseReleased(e, getMouseImagePos5D()); super.mouseReleased(e); } @Override public void mouseWheelMoved(MouseWheelEvent e) { // send mouse event to overlays VtkCanvas.this.mouseWheelMoved(e, getMouseImagePos5D()); super.mouseWheelMoved(e); } @Override public void keyPressed(KeyEvent e) { if (!e.isConsumed()) { switch (e.getKeyCode()) { case KeyEvent.VK_R: // reset view resetCamera(); // also reset LUT if (EventUtil.isShiftDown(e, true)) { final Sequence sequence = getSequence(); final Viewer viewer = getViewer(); if ((viewer != null) && (sequence != null)) { final LUT lut = sequence.createCompatibleLUT(); // set default opacity for 3D display lut.setAlphaToLinear3D(); viewer.setLut(lut); } } else repaint(); e.consume(); break; } } super.keyPressed(e); } } /** * Image overlay to encapsulate VTK image volume in a canvas layer */ protected class VtkCanvasImageOverlay extends IcyCanvasImageOverlay implements VtkPainter { public VtkCanvasImageOverlay() { super(); // create image volume imageVolume = new VtkImageVolume(); } @Override public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) { // nothing here } @Override public vtkProp[] getProps() { // return the image volume as prop return new vtkProp[] {imageVolume.getVolume()}; } } /** * Property to update */ protected static class Property { String name; Object value; public Property(String name, Object value) { super(); this.name = name; this.value = value; } @Override public boolean equals(Object obj) { if (obj instanceof Property) return name.equals(((Property) obj).name); return super.equals(obj); } @Override public int hashCode() { return name.hashCode(); } }; /** * Properties updater helper class */ protected class PropertiesUpdater extends Thread { final LinkedBlockingQueue<Property> toUpdate; public PropertiesUpdater() { super("VTK canvas properties updater"); toUpdate = new LinkedBlockingQueue<VtkCanvas.Property>(256); } public synchronized void submit(Property prop) { // remove previous property of same name if (toUpdate.remove(prop)) { // if we already had a layers visible update then we update all layers if (prop.name.equals(PROPERTY_LAYERS_VISIBLE)) prop.value = null; } // add the property toUpdate.add(prop); } protected void updateProperty(Property prop) throws InterruptedException { final String name = prop.name; final Object value = prop.value; if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_AMBIENT)) { final double d = ((Double) value).doubleValue(); invokeOnEDT(new Runnable() { @Override public void run() { imageVolume.setAmbient(d); } }); preferences.putDouble(ID_AMBIENT, d); } else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_DIFFUSE)) { final double d = ((Double) value).doubleValue(); invokeOnEDT(new Runnable() { @Override public void run() { imageVolume.setDiffuse(d); } }); preferences.putDouble(ID_DIFFUSE, d); } else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_SPECULAR)) { final double d = ((Double) value).doubleValue(); invokeOnEDT(new Runnable() { @Override public void run() { imageVolume.setSpecular(d); } }); preferences.putDouble(ID_SPECULAR, d); } else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_BG_COLOR)) { final Color color = (Color) value; invokeOnEDT(new Runnable() { @Override public void run() { setBackgroundColorInternal(color); } }); preferences.putInt(ID_BGCOLOR, color.getRGB()); } else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_INTERPOLATION)) { final int i = ((Integer) value).intValue(); invokeOnEDT(new Runnable() { @Override public void run() { imageVolume.setInterpolationMode(i); } }); preferences.putInt(ID_INTERPOLATION, i); } else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_MAPPER)) { final boolean gpuRendering = ((Boolean) value).booleanValue(); invokeOnEDT(new Runnable() { @Override public void run() { imageVolume.setGPURendering(gpuRendering); } }); preferences.putInt(ID_MAPPER, gpuRendering ? 1 : 0); } else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_BLENDING)) { final VtkVolumeBlendType type = (VtkVolumeBlendType) value; invokeOnEDT(new Runnable() { @Override public void run() { imageVolume.setBlendingMode(type); } }); preferences.putInt(ID_BLENDING, getVolumeBlendingMode().ordinal()); } else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_SAMPLE)) { final int i = ((Integer) value).intValue(); invokeOnEDT(new Runnable() { @Override public void run() { imageVolume.setSampleResolution(i); } }); preferences.putDouble(ID_SAMPLE, i); } else if (StringUtil.equals(name, PROPERTY_AXES)) { final boolean b = ((Boolean) value).booleanValue(); invokeOnEDT(new Runnable() { @Override public void run() { panel3D.setAxisOrientationDisplayEnable(b); } }); preferences.putBoolean(ID_AXES, b); } else if (StringUtil.equals(name, PROPERTY_BOUNDINGBOX)) { final boolean b = ((Boolean) value).booleanValue(); invokeOnEDT(new Runnable() { @Override public void run() { boundingBox.SetVisibility(b ? 1 : 0); } }); preferences.putBoolean(ID_BOUNDINGBOX, b); } else if (StringUtil.equals(name, PROPERTY_BOUNDINGBOX_GRID)) { final boolean b = ((Boolean) value).booleanValue(); invokeOnEDT(new Runnable() { @Override public void run() { rulerBox.SetDrawXGridlines(b ? 1 : 0); rulerBox.SetDrawYGridlines(b ? 1 : 0); rulerBox.SetDrawZGridlines(b ? 1 : 0); } }); preferences.putBoolean(ID_BOUNDINGBOX_GRID, b); } else if (StringUtil.equals(name, PROPERTY_BOUNDINGBOX_RULES)) { final boolean b = ((Boolean) value).booleanValue(); invokeOnEDT(new Runnable() { @Override public void run() { rulerBox.SetXAxisTickVisibility(b ? 1 : 0); rulerBox.SetXAxisMinorTickVisibility(b ? 1 : 0); rulerBox.SetYAxisTickVisibility(b ? 1 : 0); rulerBox.SetYAxisMinorTickVisibility(b ? 1 : 0); rulerBox.SetZAxisTickVisibility(b ? 1 : 0); rulerBox.SetZAxisMinorTickVisibility(b ? 1 : 0); } }); preferences.putBoolean(ID_BOUNDINGBOX_RULES, b); } else if (StringUtil.equals(name, PROPERTY_BOUNDINGBOX_LABELS)) { final boolean b = ((Boolean) value).booleanValue(); invokeOnEDT(new Runnable() { @Override public void run() { rulerBox.SetXAxisLabelVisibility(b ? 1 : 0); rulerBox.SetYAxisLabelVisibility(b ? 1 : 0); rulerBox.SetZAxisLabelVisibility(b ? 1 : 0); } }); preferences.putBoolean(ID_BOUNDINGBOX_LABELS, b); } else if (StringUtil.equals(name, VtkSettingPanel.PROPERTY_SHADING)) { final boolean b = ((Boolean) value).booleanValue(); invokeOnEDT(new Runnable() { @Override public void run() { imageVolume.setShade(b); } }); preferences.putBoolean(ID_SHADING, b); } else if (StringUtil.equals(name, PROPERTY_LUT)) { updateLut(); } else if (StringUtil.equals(name, PROPERTY_SCALE)) { final double[] oldScale = getVolumeScale(); final double[] newScale = (double[]) value; if (!Arrays.equals(oldScale, newScale)) { invokeOnEDT(new Runnable() { @Override public void run() { imageVolume.setScale(newScale); // need to update bounding box as well updateBoundingBoxSize(); } }); } } else if (StringUtil.equals(name, PROPERTY_DATA)) { final vtkImageData data = getImageData(); invokeOnEDT(new Runnable() { @Override public void run() { // set image data updateImageData(data); } }); } else if (StringUtil.equals(name, PROPERTY_BOUNDS)) { invokeOnEDT(new Runnable() { @Override public void run() { updateBoundingBoxSize(); } }); } else if (StringUtil.equals(name, PROPERTY_LAYERS_VISIBLE)) { final Layer layer = (Layer) value; invokeOnEDT(new Runnable() { @Override public void run() { refreshLayerProperties(layer); } }); } } @Override public void run() { while (!isInterrupted()) { try { updateProperty(toUpdate.take()); } catch (InterruptedException e) { // just end process break; } // need to refresh rendering if (toUpdate.isEmpty()) refresh(); } // help GC toUpdate.clear(); } } /** * VTK overlay updater helper class */ protected class VtkOverlayUpdater extends Thread { final LinkedList<vtkProp> propToAdd; final LinkedList<vtkProp> propToRemove; public VtkOverlayUpdater() { super("VTK canvas overlay updater"); propToAdd = new LinkedList<vtkProp>(); propToRemove = new LinkedList<vtkProp>(); } public void addProp(vtkProp prop) { synchronized (propToAdd) { propToAdd.add(prop); } } public void removeProp(vtkProp prop) { synchronized (propToRemove) { propToRemove.add(prop); } } public void addProps(List<vtkProp> props) { synchronized (propToAdd) { propToAdd.addAll(props); } } public void removeProps(List<vtkProp> props) { synchronized (propToRemove) { propToRemove.addAll(props); } } public void addProps(vtkProp[] props) { synchronized (propToAdd) { for (vtkProp prop : props) propToAdd.add(prop); } } public void removeProps(vtkProp[] props) { synchronized (propToAdd) { for (vtkProp prop : props) propToRemove.add(prop); } } @Override public void run() { while (!isInterrupted()) { while (!isInterrupted() && !propToAdd.isEmpty()) { invokeOnEDTSilent(new Runnable() { @Override public void run() { final vtkRenderer r = getRenderer(); final vtkCamera cam = getCamera(); int done = 0; if ((r != null) && (cam != null)) { // add actor by packet of 1000 while (!propToAdd.isEmpty() && (done++ < 1000)) { final vtkProp prop = propToAdd.removeFirst(); // actor not yet present in renderer ? if (r.HasViewProp(prop) == 0) { // refresh camera property for this specific kind of actor if (prop instanceof vtkCubeAxesActor) ((vtkCubeAxesActor) prop).SetCamera(cam); // add the actor to the renderer r.AddViewProp(prop); } } } } }); // sleep a bit to offer a bit of responsiveness ThreadUtil.sleep(10); // and refresh refresh(); } while (!isInterrupted() && !propToRemove.isEmpty()) { invokeOnEDTSilent(new Runnable() { @Override public void run() { final vtkRenderer r = getRenderer(); final vtkCamera cam = getCamera(); int done = 0; if ((r != null) && (cam != null)) { // remove actors from renderer by packet of 1000 while (!propToRemove.isEmpty() && (done++ < 1000)) r.RemoveViewProp(propToRemove.removeFirst()); } } }); // sleep a bit to offer a bit of responsiveness ThreadUtil.sleep(10); // and refresh refresh(); } // sleep a bit ThreadUtil.sleep(1); } // help GC propToAdd.clear(); propToRemove.clear(); } } }