/** * */ package plugins.kernel.roi.roi3d; import java.awt.Color; import java.awt.Graphics2D; import java.awt.event.InputEvent; import java.awt.geom.PathIterator; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import icy.canvas.IcyCanvas; import icy.painter.VtkPainter; import icy.sequence.Sequence; import icy.system.thread.ThreadUtil; import icy.type.point.Point5D; import icy.type.rectangle.Rectangle3D; import icy.vtk.IcyVtkPanel; import icy.vtk.VtkUtil; import plugins.kernel.canvas.VtkCanvas; import plugins.kernel.roi.roi2d.ROI2DShape; import vtk.vtkActor; import vtk.vtkCellArray; import vtk.vtkInformation; import vtk.vtkPoints; import vtk.vtkPolyData; import vtk.vtkPolyDataMapper; import vtk.vtkProp; import vtk.vtkProperty; /** * Base class defining a generic 3D Shape ROI as a stack of individual 2D Shape ROI. * * @author Stephane */ public abstract class ROI3DStackShape extends ROI3DStack<ROI2DShape> { public ROI3DStackShape(Class<? extends ROI2DShape> roiClass) { super(roiClass); } @Override protected ROIPainter createPainter() { return new ROI3DStackShapePainter(); } @Override public boolean isOverEdge(IcyCanvas canvas, double x, double y, double z) { final ROI2DShape slice = getSlice((int) z); if (slice != null) return slice.isOverEdge(canvas, x, y); return false; } public class ROI3DStackShapePainter extends ROI3DStackPainter implements VtkPainter, Runnable { // VTK 3D objects protected vtkPolyData outline; protected vtkPolyDataMapper outlineMapper; protected vtkActor outlineActor; protected vtkInformation vtkInfo; protected vtkCellArray vCells; protected vtkPoints vPoints; protected vtkPolyData polyData; protected vtkPolyDataMapper polyMapper; protected vtkActor actor; // 3D internal protected boolean needRebuild; protected double scaling[]; protected WeakReference<VtkCanvas> canvas3d; public ROI3DStackShapePainter() { super(); // don't create VTK object on constructor outline = null; outlineMapper = null; outlineActor = null; vtkInfo = null; vCells = null; vPoints = null; polyData = null; polyMapper = null; actor = null; scaling = new double[3]; Arrays.fill(scaling, 1d); needRebuild = true; canvas3d = new WeakReference<VtkCanvas>(null); } @Override protected void finalize() throws Throwable { super.finalize(); // release allocated VTK resources if (actor != null) actor.Delete(); if (polyMapper != null) polyMapper.Delete(); if (polyData != null) polyData.Delete(); if (vPoints != null) vPoints.Delete(); if (vCells != null) vCells.Delete(); if (outlineActor != null) { outlineActor.SetPropertyKeys(null); outlineActor.Delete(); } if (vtkInfo != null) { vtkInfo.Remove(VtkCanvas.visibilityKey); vtkInfo.Delete(); } if (outlineMapper != null) outlineMapper.Delete(); if (outline != null) { outline.GetPointData().GetScalars().Delete(); outline.GetPointData().Delete(); outline.Delete(); } }; protected void initVtkObjects() { outline = VtkUtil.getOutline(0d, 1d, 0d, 1d, 0d, 1d); outlineMapper = new vtkPolyDataMapper(); outlineActor = new vtkActor(); outlineActor.SetMapper(outlineMapper); // disable picking on the outline outlineActor.SetPickable(0); // and set it to wireframe representation outlineActor.GetProperty().SetRepresentationToWireframe(); // use vtkInformations to store outline visibility state (hacky) vtkInfo = new vtkInformation(); vtkInfo.Set(VtkCanvas.visibilityKey, 0); // VtkCanvas use this to restore correctly outline visibility flag outlineActor.SetPropertyKeys(vtkInfo); // init poly data object polyData = new vtkPolyData(); polyMapper = new vtkPolyDataMapper(); polyMapper.SetInputData(polyData); actor = new vtkActor(); actor.SetMapper(polyMapper); // initialize color and stroke final Color col = getColor(); final double r = col.getRed() / 255d; final double g = col.getGreen() / 255d; final double b = col.getBlue() / 255d; outlineActor.GetProperty().SetColor(r, g, b); final vtkProperty property = actor.GetProperty(); property.SetPointSize(getStroke()); property.SetColor(r, g, b); } /** * update 3D painter for 3D canvas (called only when VTK is loaded). */ protected void rebuildVtkObjects() { final VtkCanvas canvas = canvas3d.get(); // canvas was closed if (canvas == null) return; final IcyVtkPanel vtkPanel = canvas.getVtkPanel(); // canvas was closed if (vtkPanel == null) return; final Sequence seq = canvas.getSequence(); // nothing to update if (seq == null) return; // get bounds final Rectangle3D bounds = getBounds3D(); // update outline VtkUtil.setOutlineBounds(outline, bounds.getMinX() * scaling[0], bounds.getMaxX() * scaling[0], bounds.getMinY() * scaling[1], bounds.getMaxY() * scaling[1], bounds.getMinZ() * scaling[2], bounds.getMaxZ() * scaling[2], canvas); // update polydata object final List<double[]> point3DList = new ArrayList<double[]>(); final List<int[]> polyList = new ArrayList<int[]>(); final double[] coords = new double[6]; // starting position double xm = 0d; double ym = 0d; double x0 = 0d; double y0 = 0d; double x1 = 0d; double y1 = 0d; double xs = scaling[0]; double ys = scaling[1]; int ind; for (double z = bounds.getMinZ(); z <= bounds.getMaxZ(); z += 1d) { // get ROI shape for this slice final ROI2DShape roi2dShape = getSlice((int) z); // no ROI here --> continue if (roi2dShape == null) continue; final double z0 = (z + 0d) * scaling[2]; final double z1 = (z + 1d) * scaling[2]; // use flat path final PathIterator path = roi2dShape.getPathIterator(null, 0.5d); // build point data while (!path.isDone()) { switch (path.currentSegment(coords)) { case PathIterator.SEG_MOVETO: x0 = xm = coords[0] * xs; y0 = ym = coords[1] * ys; break; case PathIterator.SEG_LINETO: x1 = coords[0] * xs; y1 = coords[1] * ys; ind = point3DList.size(); point3DList.add(new double[] {x0, y0, z0}); point3DList.add(new double[] {x1, y1, z0}); point3DList.add(new double[] {x0, y0, z1}); point3DList.add(new double[] {x1, y1, z1}); polyList.add(new int[] {1 + ind, 2 + ind, 0 + ind}); polyList.add(new int[] {3 + ind, 2 + ind, 1 + ind}); x0 = x1; y0 = y1; break; case PathIterator.SEG_CLOSE: x1 = xm; y1 = ym; ind = point3DList.size(); point3DList.add(new double[] {x0, y0, z0}); point3DList.add(new double[] {x1, y1, z0}); point3DList.add(new double[] {x0, y0, z1}); point3DList.add(new double[] {x1, y1, z1}); polyList.add(new int[] {1 + ind, 2 + ind, 0 + ind}); polyList.add(new int[] {3 + ind, 2 + ind, 1 + ind}); x0 = x1; y0 = y1; break; } path.next(); } } // convert to array final double[][] vertices = new double[point3DList.size()][3]; final int[][] indexes = new int[polyList.size()][3]; ind = 0; for (double[] pt3D : point3DList) vertices[ind++] = pt3D; ind = 0; for (int[] poly : polyList) indexes[ind++] = poly; final vtkCellArray previousCells = vCells; final vtkPoints previousPoints = vPoints; vCells = VtkUtil.getCells(polyList.size(), VtkUtil.prepareCells(indexes)); vPoints = VtkUtil.getPoints(vertices); // actor can be accessed in canvas3d for rendering so we need to synchronize access vtkPanel.lock(); try { // update outline polygon data outlineMapper.SetInputData(outline); outlineMapper.Update(); // update polygon data from cell and points polyData.SetPolys(vCells); polyData.SetPoints(vPoints); polyMapper.Update(); // release previous allocated VTK objects if (previousCells != null) previousCells.Delete(); if (previousPoints != null) previousPoints.Delete(); } finally { vtkPanel.unlock(); } // update color and others properties updateVtkDisplayProperties(); } protected void updateVtkDisplayProperties() { if (actor == null) return; final VtkCanvas cnv = canvas3d.get(); final vtkProperty vtkProperty = actor.GetProperty(); final Color col = getDisplayColor(); final double r = col.getRed() / 255d; final double g = col.getGreen() / 255d; final double b = col.getBlue() / 255d; final double strk = getStroke(); // final float opacity = getOpacity(); final IcyVtkPanel vtkPanel = (cnv != null) ? cnv.getVtkPanel() : null; // we need to lock canvas as actor can be accessed during rendering if (vtkPanel != null) vtkPanel.lock(); try { // set actors color outlineActor.GetProperty().SetColor(r, g, b); if (isSelected()) { outlineActor.GetProperty().SetRepresentationToWireframe(); outlineActor.SetVisibility(1); vtkInfo.Set(VtkCanvas.visibilityKey, 1); } else { outlineActor.GetProperty().SetRepresentationToPoints(); outlineActor.SetVisibility(0); vtkInfo.Set(VtkCanvas.visibilityKey, 0); } vtkProperty.SetColor(r, g, b); vtkProperty.SetPointSize(strk); // opacity here is about ROI content, global opacity is handled by Layer // vtkProperty.SetOpacity(opacity); setVtkObjectsColor(col); } finally { if (vtkPanel != null) vtkPanel.unlock(); } // need to repaint painterChanged(); } protected void setVtkObjectsColor(Color color) { if (outline != null) VtkUtil.setPolyDataColor(outline, color, canvas3d.get()); if (polyData != null) VtkUtil.setPolyDataColor(polyData, color, canvas3d.get()); } @Override public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) { if (isActiveFor(canvas)) { if (canvas instanceof VtkCanvas) { // 3D canvas final VtkCanvas cnv = (VtkCanvas) canvas; // update reference if needed if (canvas3d.get() != cnv) canvas3d = new WeakReference<VtkCanvas>(cnv); // FIXME : need a better implementation final double[] s = cnv.getVolumeScale(); // scaling changed ? if (!Arrays.equals(scaling, s)) { // update scaling scaling = s; // need rebuild needRebuild = true; } // need to rebuild 3D data structures ? if (needRebuild) { // initialize VTK objects if not yet done if (actor == null) initVtkObjects(); // request rebuild 3D objects ThreadUtil.runSingle(this); needRebuild = false; } } else super.paint(g, sequence, canvas); } } @Override protected boolean updateFocus(InputEvent e, Point5D imagePoint, IcyCanvas canvas) { // specific VTK canvas processing if (canvas instanceof VtkCanvas) { // mouse is over the ROI actor ? --> focus the ROI final boolean focused = (actor != null) && (actor == ((VtkCanvas) canvas).getPickedObject()); setFocused(focused); return focused; } return super.updateFocus(e, imagePoint, canvas); } @Override public vtkProp[] getProps() { // initialize VTK objects if not yet done if (actor == null) initVtkObjects(); return new vtkActor[] {actor, outlineActor}; } @Override public void run() { rebuildVtkObjects(); } } }