001/** 002 * 003 */ 004package plugins.kernel.roi.roi3d; 005 006import icy.canvas.IcyCanvas; 007import icy.painter.VtkPainter; 008import icy.sequence.Sequence; 009import icy.system.thread.ThreadUtil; 010import icy.type.point.Point5D; 011import icy.type.rectangle.Rectangle3D; 012import icy.vtk.IcyVtkPanel; 013import icy.vtk.VtkUtil; 014 015import java.awt.Color; 016import java.awt.Graphics2D; 017import java.awt.event.InputEvent; 018import java.awt.geom.PathIterator; 019import java.lang.ref.WeakReference; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.List; 023 024import plugins.kernel.canvas.VtkCanvas; 025import plugins.kernel.roi.roi2d.ROI2DShape; 026import vtk.vtkActor; 027import vtk.vtkCellArray; 028import vtk.vtkInformation; 029import vtk.vtkPoints; 030import vtk.vtkPolyData; 031import vtk.vtkPolyDataMapper; 032import vtk.vtkProp; 033import vtk.vtkProperty; 034 035/** 036 * Base class defining a generic 3D Shape ROI as a stack of individual 2D Shape ROI. 037 * 038 * @author Stephane 039 */ 040public abstract class ROI3DStackShape extends ROI3DStack<ROI2DShape> 041{ 042 public ROI3DStackShape(Class<? extends ROI2DShape> roiClass) 043 { 044 super(roiClass); 045 } 046 047 @Override 048 protected ROIPainter createPainter() 049 { 050 return new ROI3DStackShapePainter(); 051 } 052 053 @Override 054 public boolean isOverEdge(IcyCanvas canvas, double x, double y, double z) 055 { 056 final ROI2DShape slice = getSlice((int) z); 057 058 if (slice != null) 059 return slice.isOverEdge(canvas, x, y); 060 061 return false; 062 } 063 064 public class ROI3DStackShapePainter extends ROI3DStackPainter implements VtkPainter, Runnable 065 { 066 // VTK 3D objects 067 protected vtkPolyData outline; 068 protected vtkPolyDataMapper outlineMapper; 069 protected vtkActor outlineActor; 070 protected vtkInformation vtkInfo; 071 protected vtkCellArray vCells; 072 protected vtkPoints vPoints; 073 protected vtkPolyData polyData; 074 protected vtkPolyDataMapper polyMapper; 075 protected vtkActor actor; 076 // 3D internal 077 protected boolean needRebuild; 078 protected double scaling[]; 079 protected WeakReference<VtkCanvas> canvas3d; 080 081 public ROI3DStackShapePainter() 082 { 083 super(); 084 085 // don't create VTK object on constructor 086 outline = null; 087 outlineMapper = null; 088 outlineActor = null; 089 vtkInfo = null; 090 vCells = null; 091 vPoints = null; 092 polyData = null; 093 polyMapper = null; 094 actor = null; 095 096 scaling = new double[3]; 097 Arrays.fill(scaling, 1d); 098 099 needRebuild = true; 100 canvas3d = new WeakReference<VtkCanvas>(null); 101 } 102 103 @Override 104 protected void finalize() throws Throwable 105 { 106 super.finalize(); 107 108 // release allocated VTK resources 109 if (actor != null) 110 actor.Delete(); 111 if (polyMapper != null) 112 polyMapper.Delete(); 113 if (polyData != null) 114 polyData.Delete(); 115 if (vPoints != null) 116 vPoints.Delete(); 117 if (vCells != null) 118 vCells.Delete(); 119 if (outlineActor != null) 120 { 121 outlineActor.SetPropertyKeys(null); 122 outlineActor.Delete(); 123 } 124 if (vtkInfo != null) 125 { 126 vtkInfo.Remove(VtkCanvas.visibilityKey); 127 vtkInfo.Delete(); 128 } 129 if (outlineMapper != null) 130 outlineMapper.Delete(); 131 if (outline != null) 132 { 133 outline.GetPointData().GetScalars().Delete(); 134 outline.GetPointData().Delete(); 135 outline.Delete(); 136 } 137 }; 138 139 protected void initVtkObjects() 140 { 141 outline = VtkUtil.getOutline(0d, 1d, 0d, 1d, 0d, 1d); 142 outlineMapper = new vtkPolyDataMapper(); 143 outlineActor = new vtkActor(); 144 outlineActor.SetMapper(outlineMapper); 145 // disable picking on the outline 146 outlineActor.SetPickable(0); 147 // and set it to wireframe representation 148 outlineActor.GetProperty().SetRepresentationToWireframe(); 149 // use vtkInformations to store outline visibility state (hacky) 150 vtkInfo = new vtkInformation(); 151 vtkInfo.Set(VtkCanvas.visibilityKey, 0); 152 // VtkCanvas use this to restore correctly outline visibility flag 153 outlineActor.SetPropertyKeys(vtkInfo); 154 155 // init poly data object 156 polyData = new vtkPolyData(); 157 polyMapper = new vtkPolyDataMapper(); 158 polyMapper.SetInputData(polyData); 159 actor = new vtkActor(); 160 actor.SetMapper(polyMapper); 161 162 // initialize color and stroke 163 final Color col = getColor(); 164 final double r = col.getRed() / 255d; 165 final double g = col.getGreen() / 255d; 166 final double b = col.getBlue() / 255d; 167 168 outlineActor.GetProperty().SetColor(r, g, b); 169 final vtkProperty property = actor.GetProperty(); 170 property.SetPointSize(getStroke()); 171 property.SetColor(r, g, b); 172 } 173 174 /** 175 * update 3D painter for 3D canvas (called only when VTK is loaded). 176 */ 177 protected void rebuildVtkObjects() 178 { 179 final VtkCanvas canvas = canvas3d.get(); 180 // canvas was closed 181 if (canvas == null) 182 return; 183 184 final IcyVtkPanel vtkPanel = canvas.getVtkPanel(); 185 // canvas was closed 186 if (vtkPanel == null) 187 return; 188 189 final Sequence seq = canvas.getSequence(); 190 // nothing to update 191 if (seq == null) 192 return; 193 194 // get bounds 195 final Rectangle3D bounds = getBounds3D(); 196 197 // update outline 198 VtkUtil.setOutlineBounds(outline, bounds.getMinX() * scaling[0], bounds.getMaxX() * scaling[0], 199 bounds.getMinY() * scaling[1], bounds.getMaxY() * scaling[1], bounds.getMinZ() * scaling[2], 200 bounds.getMaxZ() * scaling[2], canvas); 201 202 // update polydata object 203 final List<double[]> point3DList = new ArrayList<double[]>(); 204 final List<int[]> polyList = new ArrayList<int[]>(); 205 final double[] coords = new double[6]; 206 207 // starting position 208 double xm = 0d; 209 double ym = 0d; 210 double x0 = 0d; 211 double y0 = 0d; 212 double x1 = 0d; 213 double y1 = 0d; 214 double xs = scaling[0]; 215 double ys = scaling[1]; 216 int ind; 217 218 for (double z = bounds.getMinZ(); z <= bounds.getMaxZ(); z += 1d) 219 { 220 // get ROI shape for this slice 221 final ROI2DShape roi2dShape = getSlice((int) z); 222 223 // no ROI here --> continue 224 if (roi2dShape == null) 225 continue; 226 227 final double z0 = (z + 0d) * scaling[2]; 228 final double z1 = (z + 1d) * scaling[2]; 229 230 // use flat path 231 final PathIterator path = roi2dShape.getPathIterator(null, 0.5d); 232 233 // build point data 234 while (!path.isDone()) 235 { 236 switch (path.currentSegment(coords)) 237 { 238 case PathIterator.SEG_MOVETO: 239 x0 = xm = coords[0] * xs; 240 y0 = ym = coords[1] * ys; 241 break; 242 243 case PathIterator.SEG_LINETO: 244 x1 = coords[0] * xs; 245 y1 = coords[1] * ys; 246 247 ind = point3DList.size(); 248 249 point3DList.add(new double[] {x0, y0, z0}); 250 point3DList.add(new double[] {x1, y1, z0}); 251 point3DList.add(new double[] {x0, y0, z1}); 252 point3DList.add(new double[] {x1, y1, z1}); 253 polyList.add(new int[] {1 + ind, 2 + ind, 0 + ind}); 254 polyList.add(new int[] {3 + ind, 2 + ind, 1 + ind}); 255 256 x0 = x1; 257 y0 = y1; 258 break; 259 260 case PathIterator.SEG_CLOSE: 261 x1 = xm; 262 y1 = ym; 263 264 ind = point3DList.size(); 265 266 point3DList.add(new double[] {x0, y0, z0}); 267 point3DList.add(new double[] {x1, y1, z0}); 268 point3DList.add(new double[] {x0, y0, z1}); 269 point3DList.add(new double[] {x1, y1, z1}); 270 polyList.add(new int[] {1 + ind, 2 + ind, 0 + ind}); 271 polyList.add(new int[] {3 + ind, 2 + ind, 1 + ind}); 272 273 x0 = x1; 274 y0 = y1; 275 break; 276 } 277 278 path.next(); 279 } 280 } 281 282 // convert to array 283 final double[][] vertices = new double[point3DList.size()][3]; 284 final int[][] indexes = new int[polyList.size()][3]; 285 286 ind = 0; 287 for (double[] pt3D : point3DList) 288 vertices[ind++] = pt3D; 289 290 ind = 0; 291 for (int[] poly : polyList) 292 indexes[ind++] = poly; 293 294 final vtkCellArray previousCells = vCells; 295 final vtkPoints previousPoints = vPoints; 296 vCells = VtkUtil.getCells(polyList.size(), VtkUtil.prepareCells(indexes)); 297 vPoints = VtkUtil.getPoints(vertices); 298 299 // actor can be accessed in canvas3d for rendering so we need to synchronize access 300 vtkPanel.lock(); 301 try 302 { 303 // update outline polygon data 304 outlineMapper.SetInputData(outline); 305 outlineMapper.Update(); 306 // update polygon data from cell and points 307 polyData.SetPolys(vCells); 308 polyData.SetPoints(vPoints); 309 polyMapper.Update(); 310 311 // release previous allocated VTK objects 312 if (previousCells != null) 313 previousCells.Delete(); 314 if (previousPoints != null) 315 previousPoints.Delete(); 316 } 317 finally 318 { 319 vtkPanel.unlock(); 320 } 321 322 // update color and others properties 323 updateVtkDisplayProperties(); 324 } 325 326 protected void updateVtkDisplayProperties() 327 { 328 if (actor == null) 329 return; 330 331 final VtkCanvas cnv = canvas3d.get(); 332 final vtkProperty vtkProperty = actor.GetProperty(); 333 final Color col = getDisplayColor(); 334 final double r = col.getRed() / 255d; 335 final double g = col.getGreen() / 255d; 336 final double b = col.getBlue() / 255d; 337 final double strk = getStroke(); 338 // final float opacity = getOpacity(); 339 340 final IcyVtkPanel vtkPanel = (cnv != null) ? cnv.getVtkPanel() : null; 341 342 // we need to lock canvas as actor can be accessed during rendering 343 if (vtkPanel != null) 344 vtkPanel.lock(); 345 try 346 { 347 // set actors color 348 outlineActor.GetProperty().SetColor(r, g, b); 349 if (isSelected()) 350 { 351 outlineActor.GetProperty().SetRepresentationToWireframe(); 352 outlineActor.SetVisibility(1); 353 vtkInfo.Set(VtkCanvas.visibilityKey, 1); 354 } 355 else 356 { 357 outlineActor.GetProperty().SetRepresentationToPoints(); 358 outlineActor.SetVisibility(0); 359 vtkInfo.Set(VtkCanvas.visibilityKey, 0); 360 } 361 vtkProperty.SetColor(r, g, b); 362 vtkProperty.SetPointSize(strk); 363 // opacity here is about ROI content, global opacity is handled by Layer 364 // vtkProperty.SetOpacity(opacity); 365 setVtkObjectsColor(col); 366 } 367 finally 368 { 369 if (vtkPanel != null) 370 vtkPanel.unlock(); 371 } 372 373 // need to repaint 374 painterChanged(); 375 } 376 377 protected void setVtkObjectsColor(Color color) 378 { 379 if (outline != null) 380 VtkUtil.setPolyDataColor(outline, color, canvas3d.get()); 381 if (polyData != null) 382 VtkUtil.setPolyDataColor(polyData, color, canvas3d.get()); 383 } 384 385 @Override 386 public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) 387 { 388 if (isActiveFor(canvas)) 389 { 390 if (canvas instanceof VtkCanvas) 391 { 392 // 3D canvas 393 final VtkCanvas cnv = (VtkCanvas) canvas; 394 // update reference if needed 395 if (canvas3d.get() != cnv) 396 canvas3d = new WeakReference<VtkCanvas>(cnv); 397 398 // FIXME : need a better implementation 399 final double[] s = cnv.getVolumeScale(); 400 401 // scaling changed ? 402 if (!Arrays.equals(scaling, s)) 403 { 404 // update scaling 405 scaling = s; 406 // need rebuild 407 needRebuild = true; 408 } 409 410 // need to rebuild 3D data structures ? 411 if (needRebuild) 412 { 413 // initialize VTK objects if not yet done 414 if (actor == null) 415 initVtkObjects(); 416 417 // request rebuild 3D objects 418 ThreadUtil.runSingle(this); 419 needRebuild = false; 420 } 421 } 422 else 423 super.paint(g, sequence, canvas); 424 } 425 } 426 427 @Override 428 protected boolean updateFocus(InputEvent e, Point5D imagePoint, IcyCanvas canvas) 429 { 430 // specific VTK canvas processing 431 if (canvas instanceof VtkCanvas) 432 { 433 // mouse is over the ROI actor ? --> focus the ROI 434 final boolean focused = (actor != null) && (actor == ((VtkCanvas) canvas).getPickedObject()); 435 436 setFocused(focused); 437 438 return focused; 439 } 440 441 return super.updateFocus(e, imagePoint, canvas); 442 } 443 444 @Override 445 public vtkProp[] getProps() 446 { 447 // initialize VTK objects if not yet done 448 if (actor == null) 449 initVtkObjects(); 450 451 return new vtkActor[] {actor, outlineActor}; 452 } 453 454 @Override 455 public void run() 456 { 457 rebuildVtkObjects(); 458 } 459 } 460}