001/** 002 * 003 */ 004package plugins.kernel.roi.roi3d; 005 006import icy.canvas.IcyCanvas; 007import icy.canvas.IcyCanvas2D; 008import icy.common.CollapsibleEvent; 009import icy.painter.Anchor3D; 010import icy.painter.OverlayEvent; 011import icy.painter.OverlayEvent.OverlayEventType; 012import icy.resource.ResourceUtil; 013import icy.roi.ROI; 014import icy.roi.ROIEvent; 015import icy.sequence.Sequence; 016import icy.type.geom.Line3D; 017import icy.type.point.Point3D; 018import icy.type.point.Point5D; 019import icy.util.GraphicsUtil; 020import icy.util.StringUtil; 021import icy.util.XMLUtil; 022import icy.vtk.IcyVtkPanel; 023 024import java.awt.Color; 025import java.awt.Graphics2D; 026import java.awt.Rectangle; 027import java.awt.geom.Ellipse2D; 028import java.awt.geom.Rectangle2D; 029 030import org.w3c.dom.Node; 031 032import plugins.kernel.canvas.VtkCanvas; 033import vtk.vtkActor; 034import vtk.vtkPolyDataMapper; 035import vtk.vtkSphereSource; 036 037/** 038 * ROI 3D Point class.<br> 039 * 040 * @author Stephane Dallongeville 041 */ 042public class ROI3DPoint extends ROI3DShape 043{ 044 public class ROI3DPointPainter extends ROI3DShapePainter 045 { 046 vtkSphereSource vtkSource; 047 048 @Override 049 protected void finalize() throws Throwable 050 { 051 super.finalize(); 052 053 // release extra VTK objects 054 if (vtkSource != null) 055 vtkSource.Delete(); 056 } 057 058 @Override 059 protected boolean isSmall(Rectangle2D bounds, Graphics2D g, IcyCanvas canvas) 060 { 061 if (isSelected()) 062 return false; 063 064 return true; 065 } 066 067 @Override 068 protected boolean isTiny(Rectangle2D bounds, Graphics2D g, IcyCanvas canvas) 069 { 070 if (isSelected()) 071 return false; 072 073 return true; 074 } 075 076 @Override 077 public void drawROI(Graphics2D g, Sequence sequence, IcyCanvas canvas) 078 { 079 if (canvas instanceof IcyCanvas2D) 080 { 081 final Graphics2D g2 = (Graphics2D) g.create(); 082 083 if (isSelected() && !isReadOnly()) 084 { 085 // draw control point if selected 086 synchronized (controlPoints) 087 { 088 for (Anchor3D pt : controlPoints) 089 pt.paint(g2, sequence, canvas); 090 } 091 } 092 else 093 { 094 final Point3D pos = getPoint(); 095 final double ray = getAdjustedStroke(canvas); 096 final Ellipse2D ellipse = new Ellipse2D.Double(pos.getX() - ray, pos.getY() - ray, ray * 2, 097 ray * 2); 098 099 // get canvas Z position 100 final int cnvZ = canvas.getPositionZ(); 101 // calculate z fade range 102 final double zRange = Math.min(10d, Math.max(3d, sequence.getSizeZ() / 8d)); 103 104 // get Z pos 105 final double z = pos.getZ(); 106 // get delta Z (difference between canvas Z position and point Z pos) 107 final double dz = Math.abs(z - cnvZ); 108 109 // not visible on this Z position 110 if (dz > zRange) 111 return; 112 113 // ratio for size / opacity 114 final float ratio = 1f - (float) (dz / zRange); 115 116 if (ratio != 1f) 117 GraphicsUtil.mixAlpha(g2, ratio); 118 119 // draw shape 120 g2.setColor(getDisplayColor()); 121 g2.fill(ellipse); 122 } 123 124 g2.dispose(); 125 } 126 else 127 // just use parent method 128 super.drawROI(g, sequence, canvas); 129 } 130 131 @Override 132 protected void initVtkObjects() 133 { 134 super.initVtkObjects(); 135 136 // init 3D painters stuff 137 vtkSource = new vtkSphereSource(); 138 vtkSource.SetRadius(getStroke()); 139 vtkSource.SetThetaResolution(12); 140 vtkSource.SetPhiResolution(12); 141 142 // delete previously created objects that we will recreate 143 if (actor != null) 144 actor.Delete(); 145 if (polyMapper != null) 146 polyMapper.Delete(); 147 148 polyMapper = new vtkPolyDataMapper(); 149 polyMapper.SetInputConnection((vtkSource).GetOutputPort()); 150 151 actor = new vtkActor(); 152 actor.SetMapper(polyMapper); 153 154 // initialize color 155 final Color col = getColor(); 156 actor.GetProperty().SetColor(col.getRed() / 255d, col.getGreen() / 255d, col.getBlue() / 255d); 157 } 158 159 /** 160 * update 3D painter for 3D canvas (called only when VTK is loaded). 161 */ 162 @Override 163 protected void rebuildVtkObjects() 164 { 165 final VtkCanvas canvas = canvas3d.get(); 166 // canvas was closed 167 if (canvas == null) 168 return; 169 170 final IcyVtkPanel vtkPanel = canvas.getVtkPanel(); 171 // canvas was closed 172 if (vtkPanel == null) 173 return; 174 175 final Sequence seq = canvas.getSequence(); 176 // nothing to update 177 if (seq == null) 178 return; 179 180 final Point3D pos = getPoint(); 181 182 // actor can be accessed in canvas3d for rendering so we need to synchronize access 183 vtkPanel.lock(); 184 try 185 { 186 // need to handle scaling on radius and position to keep a "round" sphere (else we obtain ellipsoid) 187 vtkSource.SetRadius(getStroke() * scaling[0]); 188 vtkSource.SetCenter(pos.getX() * scaling[0], pos.getY() * scaling[1], pos.getZ() * scaling[2]); 189 polyMapper.Update(); 190 191 // vtkSource.SetRadius(getStroke()); 192 // vtkSource.SetCenter(pos.getX(), pos.getY(), curZ); 193 // polyMapper.Update(); 194 // actor.SetScale(scaling); 195 } 196 finally 197 { 198 vtkPanel.unlock(); 199 } 200 201 // need to repaint 202 painterChanged(); 203 } 204 205 @Override 206 protected void updateVtkDisplayProperties() 207 { 208 if (actor == null) 209 return; 210 211 final VtkCanvas cnv = canvas3d.get(); 212 final Color col = getDisplayColor(); 213 final double r = col.getRed() / 255d; 214 final double g = col.getGreen() / 255d; 215 final double b = col.getBlue() / 255d; 216 // final float opacity = getOpacity(); 217 218 final IcyVtkPanel vtkPanel = (cnv != null) ? cnv.getVtkPanel() : null; 219 220 // we need to lock canvas as actor can be accessed during rendering 221 if (vtkPanel != null) 222 vtkPanel.lock(); 223 try 224 { 225 actor.GetProperty().SetColor(r, g, b); 226 } 227 finally 228 { 229 if (vtkPanel != null) 230 vtkPanel.unlock(); 231 } 232 233 // need to repaint 234 painterChanged(); 235 } 236 } 237 238 public static final String ID_POSITION = "position"; 239 240 private final Anchor3D position; 241 242 /** 243 * @deprecated 244 */ 245 @Deprecated 246 public ROI3DPoint(Point3D pt, boolean cm) 247 { 248 this(pt); 249 } 250 251 public ROI3DPoint(Point3D position) 252 { 253 super(new Line3D()); 254 255 this.position = createAnchor(position); 256 this.position.setSelected(true); 257 addPoint(this.position); 258 259 // set icon 260 setIcon(ResourceUtil.ICON_ROI_POINT); 261 } 262 263 /** 264 * Generic constructor for interactive mode 265 */ 266 public ROI3DPoint(Point5D pt) 267 { 268 this(pt.toPoint3D()); 269 } 270 271 public ROI3DPoint(double x, double y, double z) 272 { 273 this(new Point3D.Double(x, y, z)); 274 } 275 276 public ROI3DPoint() 277 { 278 this(new Point3D.Double()); 279 } 280 281 @Override 282 public String getDefaultName() 283 { 284 return "Point3D"; 285 } 286 287 @Override 288 protected ROI3DShapePainter createPainter() 289 { 290 return new ROI3DPointPainter(); 291 } 292 293 public Line3D getLine() 294 { 295 return (Line3D) shape; 296 } 297 298 public Point3D getPoint() 299 { 300 return position.getPosition(); 301 } 302 303 /** 304 * Called when anchor overlay changed 305 */ 306 @Override 307 public void controlPointOverlayChanged(OverlayEvent event) 308 { 309 // we only mind about painter change from anchor... 310 if (event.getType() == OverlayEventType.PAINTER_CHANGED) 311 { 312 // here we want to have ROI focused when point is selected (special case for ROIPoint) 313 if (hasSelectedPoint()) 314 setFocused(true); 315 316 // anchor changed --> ROI painter changed 317 getOverlay().painterChanged(); 318 } 319 } 320 321 @Override 322 public boolean contains(ROI roi) 323 { 324 return false; 325 } 326 327 @Override 328 public boolean intersects(ROI r) 329 { 330 // special case of ROI3DPoint 331 if (r instanceof ROI3DPoint) 332 return onSamePos(((ROI3DPoint) r), false) && ((ROI3DPoint) r).getPoint().equals(getPoint()); 333 334 return super.intersects(r); 335 } 336 337 @Override 338 public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, boolean inclusive) 339 { 340 if ((width <= 0) || (height <= 0)) 341 return new boolean[0]; 342 343 final boolean[] result = new boolean[width * height]; 344 // 2D bounds 345 final Rectangle bounds2d = new Rectangle(x, y, width, height); 346 347 final Point3D pos = getPoint(); 348 349 // same Z ? 350 if (Math.floor(pos.getZ()) == z) 351 { 352 // inside the mask ? 353 if (bounds2d.contains(pos.toPoint2D())) 354 { 355 final int px = (int) Math.floor(pos.getX()); 356 final int py = (int) Math.floor(pos.getY()); 357 358 // set the pixel 359 result[(px - x) + ((py - y) * width)] = true; 360 } 361 } 362 363 return result; 364 } 365 366 /** 367 * roi changed 368 */ 369 @Override 370 public void onChanged(CollapsibleEvent object) 371 { 372 final ROIEvent event = (ROIEvent) object; 373 374 // do here global process on ROI change 375 switch (event.getType()) 376 { 377 case PROPERTY_CHANGED: 378 final String property = event.getPropertyName(); 379 380 // stroke changed --> rebuild vtk object 381 if (StringUtil.equals(property, PROPERTY_STROKE)) 382 ((ROI3DShapePainter) getOverlay()).needRebuild = true; 383 break; 384 385 case SELECTION_CHANGED: 386 // always select the control point when ROI was just selected 387 if (isSelected()) 388 position.setSelected(true); 389 break; 390 391 default: 392 break; 393 } 394 395 super.onChanged(object); 396 } 397 398 @Override 399 protected void updateShape() 400 { 401 final Point3D pt = getPoint(); 402 final double x = pt.getX(); 403 final double y = pt.getY(); 404 final double z = pt.getZ(); 405 406 getLine().setLine(x, y, z, x, y, z); 407 408 // call super method after shape has been updated 409 super.updateShape(); 410 } 411 412 @Override 413 public boolean canAddPoint() 414 { 415 // this ROI doesn't support point add 416 return false; 417 } 418 419 @Override 420 protected boolean removePoint(IcyCanvas canvas, Anchor3D pt) 421 { 422 if (canvas != null) 423 { 424 // remove point on this ROI remove the ROI from current sequence 425 canvas.getSequence().removeROI(this); 426 return true; 427 } 428 429 return false; 430 } 431 432 @Override 433 public double computeNumberOfContourPoints() 434 { 435 return 0d; 436 } 437 438 @Override 439 public double computeNumberOfPoints() 440 { 441 return 0d; 442 } 443 444 @Override 445 public boolean loadFromXML(Node node) 446 { 447 beginUpdate(); 448 try 449 { 450 if (!super.loadFromXML(node)) 451 return false; 452 453 position.loadPositionFromXML(XMLUtil.getElement(node, ID_POSITION)); 454 } 455 finally 456 { 457 endUpdate(); 458 } 459 460 return true; 461 } 462 463 @Override 464 public boolean saveToXML(Node node) 465 { 466 if (!super.saveToXML(node)) 467 return false; 468 469 position.savePositionToXML(XMLUtil.setElement(node, ID_POSITION)); 470 471 return true; 472 } 473}