001package plugins.kernel.roi.roi3d; 002 003import icy.canvas.IcyCanvas; 004import icy.common.CollapsibleEvent; 005import icy.math.Line3DIterator; 006import icy.painter.Anchor3D; 007import icy.resource.ResourceUtil; 008import icy.roi.ROI; 009import icy.roi.ROIEvent; 010import icy.sequence.Sequence; 011import icy.type.geom.Line3D; 012import icy.type.geom.Polyline3D; 013import icy.type.point.Point3D; 014import icy.type.point.Point5D; 015import icy.util.StringUtil; 016import icy.util.XMLUtil; 017import icy.vtk.IcyVtkPanel; 018 019import java.awt.Graphics2D; 020import java.awt.Rectangle; 021import java.awt.geom.Line2D; 022import java.util.ArrayList; 023import java.util.List; 024 025import org.w3c.dom.Element; 026import org.w3c.dom.Node; 027 028import plugins.kernel.canvas.VtkCanvas; 029import vtk.vtkTubeFilter; 030 031/** 032 * 3D Polyline ROI 033 * 034 * @author Stephane Dallongeville 035 */ 036public class ROI3DPolyLine extends ROI3DShape 037{ 038 public class ROI3DPolyLinePainter extends ROI3DShapePainter 039 { 040 // extra VTK 3D objects 041 protected vtkTubeFilter tubeFilter; 042 043 public ROI3DPolyLinePainter() 044 { 045 super(); 046 047 // don't create VTK object on constructor 048 tubeFilter = null; 049 } 050 051 @Override 052 protected void finalize() throws Throwable 053 { 054 super.finalize(); 055 056 // release allocated VTK resources 057 if (tubeFilter != null) 058 tubeFilter.Delete(); 059 }; 060 061 @Override 062 protected void initVtkObjects() 063 { 064 super.initVtkObjects(); 065 066 // init specific tube filter 067 tubeFilter = new vtkTubeFilter(); 068 tubeFilter.SetInputData(polyData); 069 tubeFilter.SetRadius(1d); 070 tubeFilter.CappingOn(); 071 tubeFilter.SetNumberOfSides(8); 072 // tubeFilter.SidesShareVerticesOff(); 073 polyMapper.SetInputConnection(tubeFilter.GetOutputPort()); 074 } 075 076 /** 077 * update 3D painter for 3D canvas (called only when VTK is loaded). 078 */ 079 @Override 080 protected void rebuildVtkObjects() 081 { 082 super.rebuildVtkObjects(); 083 084 final VtkCanvas canvas = canvas3d.get(); 085 // canvas was closed 086 if (canvas == null) 087 return; 088 089 final IcyVtkPanel vtkPanel = canvas.getVtkPanel(); 090 // canvas was closed 091 if (vtkPanel == null) 092 return; 093 094 // sub VTK object not yet initialized (it can happen, have to check why ??) 095 if (tubeFilter == null) 096 return; 097 098 // actor can be accessed in canvas3d for rendering so we need to synchronize access 099 vtkPanel.lock(); 100 try 101 { 102 // just be sure the tube filter is also up to date 103 tubeFilter.Update(); 104 } 105 finally 106 { 107 vtkPanel.unlock(); 108 } 109 } 110 111 protected void updateVtkTubeRadius() 112 { 113 // VTK object not yet initialized 114 if (actor == null) 115 return; 116 117 final VtkCanvas canvas = canvas3d.get(); 118 // canvas was closed 119 if (canvas == null) 120 return; 121 122 final IcyVtkPanel vtkPanel = canvas.getVtkPanel(); 123 // canvas was closed 124 if (vtkPanel == null) 125 return; 126 127 // sub VTK object not yet initialized (it can happen, have to check why ??) 128 if (tubeFilter == null) 129 return; 130 131 // update tube radius base on canvas scale X and image scale X 132 final double radius = canvas.canvasToImageLogDeltaX((int) getStroke()) * scaling[0]; 133 134 if (tubeFilter.GetRadius() != radius) 135 { 136 // actor can be accessed in canvas3d for rendering so we need to synchronize access 137 vtkPanel.lock(); 138 try 139 { 140 tubeFilter.SetRadius(radius); 141 tubeFilter.Update(); 142 } 143 finally 144 { 145 vtkPanel.unlock(); 146 } 147 148 // need to repaint 149 painterChanged(); 150 } 151 } 152 153 @Override 154 public void drawROI(Graphics2D g, Sequence sequence, IcyCanvas canvas) 155 { 156 super.drawROI(g, sequence, canvas); 157 158 // update VTK tube radius if needed 159 if (canvas instanceof VtkCanvas) 160 updateVtkTubeRadius(); 161 } 162 163 @Override 164 protected void drawShape(Graphics2D g, Sequence sequence, IcyCanvas canvas, boolean simplified) 165 { 166 drawShape(g, sequence, canvas, simplified, false); 167 } 168 } 169 170 /** 171 * 172 */ 173 public ROI3DPolyLine(Point3D pt) 174 { 175 super(new Polyline3D()); 176 177 // add points to list 178 final Anchor3D anchor = createAnchor(pt); 179 // just add the new point at last position 180 addPoint(anchor); 181 // always select 182 anchor.setSelected(true); 183 184 updatePolyline(); 185 186 // set icon 187 setIcon(ResourceUtil.ICON_ROI_POLYLINE); 188 } 189 190 /** 191 * Generic constructor for interactive mode 192 */ 193 public ROI3DPolyLine(Point5D pt) 194 { 195 this(pt.toPoint3D()); 196 } 197 198 public ROI3DPolyLine(Polyline3D polyline) 199 { 200 this(new Point3D.Double()); 201 202 setPolyline3D(polyline); 203 } 204 205 public ROI3DPolyLine(List<Point3D> points) 206 { 207 this(new Point3D.Double()); 208 209 setPoints(points); 210 } 211 212 public ROI3DPolyLine() 213 { 214 this(new Point3D.Double()); 215 } 216 217 @Override 218 public String getDefaultName() 219 { 220 return "PolyLine3D"; 221 } 222 223 @Override 224 protected ROI3DPolyLinePainter createPainter() 225 { 226 return new ROI3DPolyLinePainter(); 227 } 228 229 public Polyline3D getPolyline3D() 230 { 231 return (Polyline3D) shape; 232 } 233 234 public void setPoints(List<Point3D> pts) 235 { 236 beginUpdate(); 237 try 238 { 239 removeAllPoint(); 240 for (Point3D pt : pts) 241 addNewPoint(pt, false); 242 } 243 finally 244 { 245 endUpdate(); 246 } 247 } 248 249 public void setPolyline3D(Polyline3D value) 250 { 251 beginUpdate(); 252 try 253 { 254 removeAllPoint(); 255 for (int i = 0; i < value.npoints; i++) 256 addNewPoint(new Point3D.Double(value.xpoints[i], value.ypoints[i], value.zpoints[i]), false); 257 } 258 finally 259 { 260 endUpdate(); 261 } 262 } 263 264 @Override 265 protected double getTotalDistance(List<Point3D> points, double factorX, double factorY, double factorZ) 266 { 267 // for polyline the total length don't need last point connection 268 return Point3D.getTotalDistance(points, factorX, factorY, factorZ, false); 269 } 270 271 @Override 272 public boolean[] getBooleanMask2D(int x, int y, int width, int height, int z, boolean inclusive) 273 { 274 if ((width <= 0) || (height <= 0)) 275 return new boolean[0]; 276 277 final List<Point3D> points = getPointsInternal(); 278 final boolean[] result = new boolean[width * height]; 279 280 // 2D bounds 281 final Rectangle bounds2d = new Rectangle(x, y, width, height); 282 283 for (int i = 1; i < points.size(); i++) 284 drawLine3DInBooleanMask2D(bounds2d, result, z, points.get(i - 1), points.get(i)); 285 286 return result; 287 } 288 289 public static void drawLine3DInBooleanMask2D(Rectangle bounds2d, boolean[] result, int z, Point3D p1, Point3D p2) 290 { 291 final Line2D l = new Line2D.Double(p1.getX(), p1.getY(), p2.getX(), p2.getY()); 292 293 // 2D intersection ? 294 if (l.intersects(bounds2d)) 295 { 296 // 3D intersection ? 297 if (((p1.getZ() <= z) && (p2.getZ() >= z)) || ((p2.getZ() <= z) && (p1.getZ() >= z))) 298 { 299 final int bx = bounds2d.x; 300 final int by = bounds2d.y; 301 final int pitch = bounds2d.width; 302 final Line3DIterator it = new Line3DIterator(new Line3D(p1, p2), 1d); 303 304 while (it.hasNext()) 305 { 306 final Point3D pt = it.next(); 307 308 // same Z ? 309 if (Math.floor(pt.getZ()) == z) 310 { 311 final int x = (int) Math.floor(pt.getX()); 312 final int y = (int) Math.floor(pt.getY()); 313 314 // draw inside the mask 315 if (bounds2d.contains(x, y)) 316 result[(x - bx) + ((y - by) * pitch)] = true; 317 } 318 } 319 } 320 } 321 } 322 323 /** 324 * roi changed 325 */ 326 @Override 327 public void onChanged(CollapsibleEvent object) 328 { 329 final ROIEvent event = (ROIEvent) object; 330 331 // do here global process on ROI change 332 switch (event.getType()) 333 { 334 case ROI_CHANGED: 335 // refresh shape 336 updatePolyline(); 337 break; 338 339 case FOCUS_CHANGED: 340 ((ROI3DPolyLinePainter) getOverlay()).updateVtkDisplayProperties(); 341 break; 342 343 case SELECTION_CHANGED: 344 final boolean s = isSelected(); 345 346 // update controls point state given the selection state of the ROI 347 synchronized (controlPoints) 348 { 349 for (Anchor3D pt : controlPoints) 350 { 351 pt.setVisible(s); 352 if (!s) 353 pt.setSelected(false); 354 } 355 } 356 357 ((ROI3DPolyLinePainter) getOverlay()).updateVtkDisplayProperties(); 358 break; 359 360 case PROPERTY_CHANGED: 361 final String property = event.getPropertyName(); 362 363 if (StringUtil.equals(property, PROPERTY_STROKE) || StringUtil.equals(property, PROPERTY_COLOR) 364 || StringUtil.equals(property, PROPERTY_OPACITY)) 365 ((ROI3DPolyLinePainter) getOverlay()).updateVtkDisplayProperties(); 366 break; 367 368 default: 369 break; 370 } 371 372 super.onChanged(object); 373 } 374 375 @Override 376 public double computeNumberOfPoints() 377 { 378 return 0d; 379 } 380 381 @Override 382 public boolean contains(ROI roi) 383 { 384 return false; 385 } 386 387 protected void updatePolyline() 388 { 389 final int len = controlPoints.size(); 390 final double ptsX[] = new double[len]; 391 final double ptsY[] = new double[len]; 392 final double ptsZ[] = new double[len]; 393 394 for (int i = 0; i < len; i++) 395 { 396 final Anchor3D pt = controlPoints.get(i); 397 398 ptsX[i] = pt.getX(); 399 ptsY[i] = pt.getY(); 400 ptsZ[i] = pt.getZ(); 401 } 402 403 final Polyline3D polyline3d = getPolyline3D(); 404 405 // we can have a problem here if we try to redraw while we are modifying the polygon points 406 synchronized (polyline3d) 407 { 408 polyline3d.npoints = len; 409 polyline3d.xpoints = ptsX; 410 polyline3d.ypoints = ptsY; 411 polyline3d.zpoints = ptsZ; 412 polyline3d.calculateLines(); 413 } 414 415 // the shape should have been rebuilt here 416 ((ROI3DPolyLinePainter) painter).needRebuild = true; 417 } 418 419 @Override 420 public boolean loadFromXML(Node node) 421 { 422 beginUpdate(); 423 try 424 { 425 if (!super.loadFromXML(node)) 426 return false; 427 428 removeAllPoint(); 429 430 final ArrayList<Node> nodesPoint = XMLUtil.getChildren(XMLUtil.getElement(node, ID_POINTS), ID_POINT); 431 if (nodesPoint != null) 432 { 433 for (Node n : nodesPoint) 434 { 435 final Anchor3D pt = createAnchor(new Point3D.Double()); 436 pt.loadPositionFromXML(n); 437 addPoint(pt); 438 } 439 } 440 } 441 finally 442 { 443 endUpdate(); 444 } 445 446 return true; 447 } 448 449 @Override 450 public boolean saveToXML(Node node) 451 { 452 if (!super.saveToXML(node)) 453 return false; 454 455 final Element dependances = XMLUtil.setElement(node, ID_POINTS); 456 for (Anchor3D pt : controlPoints) 457 pt.savePositionToXML(XMLUtil.addElement(dependances, ID_POINT)); 458 459 return true; 460 } 461}