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.gui.component.button.IcyToggleButton; import icy.gui.dialog.MessageDialog; 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.sequence.Sequence; import icy.sequence.SequenceEvent.SequenceEventType; import icy.system.thread.ThreadUtil; import icy.type.TypeUtil; import icy.type.collection.array.Array1DUtil; 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.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; 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.awt.image.DataBufferInt; import java.beans.PropertyChangeEvent; import java.util.Arrays; 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.vtkLight; import vtk.vtkOrientationMarkerWidget; import vtk.vtkPanel; import vtk.vtkPiecewiseFunction; import vtk.vtkProp; import vtk.vtkPropPicker; import vtk.vtkRenderWindow; import vtk.vtkRenderWindowInteractor; import vtk.vtkRenderer; import vtk.vtkTextActor; import vtk.vtkTextProperty; import vtk.vtkUnsignedCharArray; /** * VTK 3D canvas class. * * @author Stephane */ @SuppressWarnings("deprecation") public class VtkCanvas extends Canvas3D implements Runnable, ActionListener, SettingChangeListener { /** * */ private static final long serialVersionUID = -1274251057822161271L; /** * icons */ protected static final Image ICON_AXES3D = ResourceUtil.getAlphaIconAsImage("axes3d.png"); protected static final Image ICON_BOUNDINGBOX = ResourceUtil.getAlphaIconAsImage("bbox.png"); protected static final Image ICON_GRID = ResourceUtil.getAlphaIconAsImage("3x3_grid.png"); protected static final Image ICON_RULER = ResourceUtil.getAlphaIconAsImage("ruler.png"); protected static final Image ICON_RULERLABEL = ResourceUtil.getAlphaIconAsImage("ruler_label.png"); protected static final Image ICON_SHADING = ResourceUtil.getColorIconAsImage("shading.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_SHADING = "shading"; 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"; /** * 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_AXES = PROPERTY_AXES; protected static final String ID_SHADING = 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; /** * 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(); } }; /** * 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; /** * 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 shadingButton; /** * internals */ protected final Thread propertiesUpdater; protected XMLPreferences preferences; protected final LinkedBlockingQueue<Property> propertiesToUpdate; protected final EDTTask<Object> edtTask; protected boolean initialized; public VtkCanvas(Viewer viewer) { super(viewer); initialized = false; // create the processor propertiesUpdater = new Thread(this, "VTK canvas properties updater"); propertiesToUpdate = new LinkedBlockingQueue<VtkCanvas.Property>(256); // more than 4 channels ? can't use multi channel view --> display single channel if (getImageSizeC() > 4) posC = 0; else // all channel visible at once by default posC = -1; 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"); shadingButton = new IcyToggleButton(new IcyIcon(ICON_SHADING, false)); shadingButton.setFocusable(false); shadingButton.setToolTipText("Enable volume shadow"); renderer = panel3D.GetRenderer(); renderWindow = panel3D.GetRenderWindow(); // set renderer properties renderer.SetLightFollowCamera(1); // set interactor final vtkRenderWindowInteractor interactor = new vtkRenderWindowInteractor(); interactor.SetRenderWindow(renderWindow); camera = renderer.GetActiveCamera(); // set camera properties // camera.Azimuth(20.0); // camera.Dolly(1.60); // 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.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.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.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); // initialize text info actor textInfo = new vtkTextActor(); textInfo.SetInput("No 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(); // restore settings settingPanel.setBackgroundColor(new Color(preferences.getInt(ID_BGCOLOR, 0x000000))); settingPanel.setVolumeBlendingMode(VtkVolumeBlendType.values()[preferences.getInt(ID_BLENDING, VtkVolumeBlendType.COMPOSITE.ordinal())]); // volume mapper VtkVolumeMapperType mapperType = VtkVolumeMapperType.values()[preferences.getInt(ID_MAPPER, VtkVolumeMapperType.RAYCAST_CPU_FIXEDPOINT.ordinal())]; // multi channel mapper does not support more than 4 channels if (VtkImageVolume.isMultiChannelVolumeMapper(mapperType) && (getImageSizeC() > 4)) // use the GPU texture 2D mapper instead mapperType = VtkVolumeMapperType.TEXTURE2D_OPENGL; settingPanel.setVolumeMapperType(mapperType); settingPanel.setVolumeInterpolation(preferences.getInt(ID_INTERPOLATION, VtkUtil.VTK_LINEAR_INTERPOLATION)); settingPanel.setVolumeSample(preferences.getInt(ID_SAMPLE, 0)); settingPanel.setVolumeAmbient(preferences.getDouble(ID_AMBIENT, 0.5d)); settingPanel.setVolumeDiffuse(preferences.getDouble(ID_DIFFUSE, 0.4d)); settingPanel.setVolumeSpecular(preferences.getDouble(ID_SPECULAR, 0.4d)); 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)); shadingButton.setSelected(preferences.getBoolean(ID_SHADING, false)); // apply restored settings setBackgroundColorInternal(settingPanel.getBackgroundColor()); imageVolume.setBlendingMode(settingPanel.getVolumeBlendingMode()); imageVolume.setVolumeMapperType(settingPanel.getVolumeMapperType()); // 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(shadingButton.isSelected()); 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); // add volume to renderer renderer.AddVolume(imageVolume.getVolume()); // add bounding box & ruler renderer.AddViewProp(boundingBox); renderer.AddViewProp(rulerBox); renderer.AddViewProp(textInfo); // then vtkPainter actors to the renderer for (Layer l : getLayers(false)) addLayerActors(l); // reset camera resetCamera(); // 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); shadingButton.addActionListener(this); // create EDTTask object edtTask = new EDTTask<Object>(); // start the properties updater thread propertiesUpdater.start(); initialized = true; } @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); super.shutDown(); propertiesUpdater.interrupt(); propertiesToUpdate.clear(); try { // be sure there is no more processing here propertiesUpdater.join(); } catch (InterruptedException e) { // can ignore safely } // no more initialized (prevent extra useless processing) initialized = false; // 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(); } }); // 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 = null; panel = null; } @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(shadingButton); } @Override protected Overlay createImageOverlay() { return new VtkCanvasImageOverlay(); } /** * Request exclusive access to VTK rendering. */ public void lock() { if (panel3D != null) panel3D.lock(); } /** * Release exclusive access from VTK rendering. */ 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 axes; } /** * @return the VTK bounding box object */ public vtkCubeAxesActor getBoundingBox() { return boundingBox; } /** * @return the VTK ruler box object */ public vtkCubeAxesActor getRulerBox() { return rulerBox; } /** * @return the VTK widget object used to display axes */ public vtkOrientationMarkerWidget getWidget() { 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 shadingButton.isSelected(); } /** * @see VtkImageVolume#setShade(boolean) */ public void setVolumeShadingEnable(boolean value) { if (shadingButton.isSelected() != value) shadingButton.doClick(); } /** * @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#getVolumeMapperType() */ public VtkVolumeMapperType getVolumeMapperType() { return settingPanel.getVolumeMapperType(); } /** * @see VtkImageVolume#setVolumeMapperType(VtkVolumeMapperType) */ public void setVolumeMapperType(VtkVolumeMapperType value) { settingPanel.setVolumeMapperType(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(180); 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 * * @return */ 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; } /** * @see icy.vtk.IcyVtkPanel#getPicker() */ public vtkPropPicker getPicker() { return panel3D.getPicker(); } /** * @see icy.vtk.IcyVtkPanel#pick(int, int) */ public vtkActor pick(int x, int y) { return panel3D.pick(x, y); } protected vtkProp[] getLayerActors(Layer layer) { if (layer != null) { // add painter actor from the vtk render final Overlay overlay = layer.getOverlay(); if (overlay instanceof VtkPainter) return ((VtkPainter) overlay).getProps(); } return new vtkProp[0]; } protected void addLayerActors(Layer layer) { final vtkProp[] props = getLayerActors(layer); invokeOnEDTSilent(new Runnable() { @Override public void run() { for (vtkProp actor : props) VtkUtil.addProp(renderer, actor); } }); } protected void removeLayerActors(Layer layer) { final vtkProp[] props = getLayerActors(layer); invokeOnEDTSilent(new Runnable() { @Override public void run() { for (vtkProp actor : props) VtkUtil.removeProp(renderer, actor); } }); } protected void updateBoundingBoxSize() { final double[] bounds = imageVolume.getVolume().GetBounds(); boundingBox.SetBounds(bounds); rulerBox.SetBounds(bounds); } /** * Build and get image data */ protected vtkImageData getImageData() { final Sequence sequence = getSequence(); if ((sequence == null) || sequence.isEmpty()) return null; final int posT = getPositionT(); final int posC = getPositionC(); final Object data; long size; try { if (posC == -1) { size = sequence.getSizeX(); size *= sequence.getSizeY(); size *= sequence.getSizeZ(); size *= sequence.getSizeC(); // can't allocate if (size > Integer.MAX_VALUE) return null; data = sequence.getDataCopyCXYZ(posT); return VtkUtil.getImageData(data, sequence.getDataType_(), sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeZ(), sequence.getSizeC()); } size = sequence.getSizeX(); size *= sequence.getSizeY(); size *= sequence.getSizeZ(); // can't allocate if (size > Integer.MAX_VALUE) return null; data = sequence.getDataCopyXYZ(posT, posC); return VtkUtil.getImageData(data, sequence.getDataType_(), sequence.getSizeX(), sequence.getSizeY(), sequence.getSizeZ(), 1); } catch (OutOfMemoryError e) { // just not enough memory return null; } } /** * 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(); final int posC = getPositionC(); // multi channel volume rendering mapper ? if (imageVolume.isMultiChannelVolumeMapper()) { // update the whole LUT for (int c = 0; c < lut.getNumChannel(); c++) updateLut(lut.getLutChannel(c), c); } // single channel mapper, always set channel 0 else if (posC != -1) updateLut(lut.getLutChannel(posC), 0); } 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 panel3D; } @Override public vtkPanel getPanel3D() { return panel3D; } @Override public vtkRenderer getRenderer() { return renderer; } public vtkRenderWindow getRenderWindow() { return renderWindow; } /** * 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 double getMouseImagePosX() { // not supported return 0d; } @Override public double getMouseImagePosY() { // not supported return 0d; } @Override public double getMouseImagePosZ() { // not supported return 0d; } @Override public double getMouseImagePosT() { // not supported return 0d; } @Override public double getMouseImagePosC() { // not supported return 0d; } @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 public BufferedImage getRenderedImage(int t, int c) { // 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 vtkRenderWindow renderWindow = renderer.GetRenderWindow(); final int[] size = renderWindow.GetSize(); final int w = size[0]; final int h = size[1]; final vtkUnsignedCharArray array = new vtkUnsignedCharArray(); 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); // render now ! panel3D.paint(panel3D.getGraphics()); // 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 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 VtkVolumeMapperType type = (VtkVolumeMapperType) value; final boolean prevMC = imageVolume.isMultiChannelVolumeMapper(); invokeOnEDT(new Runnable() { @Override public void run() { // multi channel mapper does not support more than 4 channels if (VtkImageVolume.isMultiChannelVolumeMapper(type) && (getImageSizeC() > 4)) { MessageDialog.showDialog( "Multi channel volume rendering is not supported on image with more than 4 channels !", MessageDialog.INFORMATION_MESSAGE); // use the GPU texture 2D mapper instead setVolumeMapperType(VtkVolumeMapperType.TEXTURE2D_OPENGL); return; } imageVolume.setVolumeMapperType(type); // FIXME: this line actually make VTK to crash // mapper not supported ? --> switch back to default one // if (!sequenceVolume.isMapperSupported(renderer)) // sequenceVolume.setVolumeMapperType(VtkVolumeMapperType.RAYCAST_CPU_FIXEDPOINT); // blending mode can change when mapper changed setVolumeBlendingMode(imageVolume.getBlendingMode()); final boolean newMC = imageVolume.isMultiChannelVolumeMapper(); if (prevMC != newMC) { // changed to multi channel mapper --> display all channel if (newMC) setPositionC(-1); // changed to single channel mapper ? else { // find channel pos from enabled channel final int c = getChannelPos(); if (c == -1) setPositionC(0); else { // this won't do any LUT change event so do it manually setPositionC(c); updateLut(); } } } } }); preferences.putInt(ID_MAPPER, type.ordinal()); } 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() { axes.SetVisibility(b ? 1 : 0); } }); 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, 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 (!propertiesUpdater.isInterrupted()) { try { updateProperty(propertiesToUpdate.take()); } catch (InterruptedException e) { // just end process return; } // need to refresh rendering if (propertiesToUpdate.isEmpty()) refresh(); } } 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 } } 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; // layer visibility property modified ? if ((event.getType() == LayersEventType.CHANGED) && Layer.isPaintProperty(event.getProperty())) 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() { // super.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 : getLayerActors(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 prop.SetVisibility((lv && l.isVisible()) ? 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 : getLayerActors(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 prop.SetVisibility((lv && layer.isVisible()) ? 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 == shadingButton) propertyChange(PROPERTY_SHADING, Boolean.valueOf(shadingButton.isSelected())); } protected void propertyChange(String name, Object value) { final Property prop = new Property(name, value); // remove previous property of same name if (propertiesToUpdate.remove(prop)) { // if we already had a layers visible update then we update all layers if (name.equals(PROPERTY_LAYERS_VISIBLE)) prop.value = null; } // add the property propertiesToUpdate.add(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; public CustomVtkPanel() { super(); lastRefreshTime = 0L; // key events should be forwarded from the viewer removeKeyListener(this); } @Override public void paint(Graphics g) { // 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, null); super.mouseEntered(e); } @Override public void mouseExited(MouseEvent e) { // send mouse event to overlays VtkCanvas.this.mouseExited(e, null); super.mouseExited(e); } @Override public void mouseClicked(MouseEvent e) { // send mouse event to overlays VtkCanvas.this.mouseClick(e, null); super.mouseClicked(e); } @Override public void mouseMoved(MouseEvent e) { // send mouse event to overlays VtkCanvas.this.mouseMove(e, null); super.mouseMoved(e); } @Override public void mouseDragged(MouseEvent e) { // send mouse event to overlays VtkCanvas.this.mouseDrag(e, null); super.mouseDragged(e); } @Override public void mousePressed(MouseEvent e) { // send mouse event to overlays VtkCanvas.this.mousePressed(e, null); super.mousePressed(e); } @Override public void mouseReleased(MouseEvent e) { // send mouse event to overlays VtkCanvas.this.mouseReleased(e, null); super.mouseReleased(e); } @Override public void mouseWheelMoved(MouseWheelEvent e) { // send mouse event to overlays VtkCanvas.this.mouseWheelMoved(e, null); 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 Render(); 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) { } @Override public vtkProp[] getProps() { // return the image volume as prop return new vtkProp[] {imageVolume.getVolume()}; } } }