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();
}
}
}