private static IGraphic CreateInteractivePolyline(IList <PointF> vertices) { var closed = FloatComparer.AreEqual(vertices[0], vertices[vertices.Count - 1]); // use a standard rectangle primitive if the axes defines an axis-aligned rectangle if (closed && vertices.Count == 5 && IsAxisAligned(vertices[0], vertices[1]) && IsAxisAligned(vertices[1], vertices[2]) && IsAxisAligned(vertices[2], vertices[3]) && IsAxisAligned(vertices[3], vertices[4])) { var bounds = RectangleUtilities.ConvertToPositiveRectangle(RectangleUtilities.ComputeBoundingRectangle(vertices[0], vertices[1], vertices[2], vertices[3])); var rectangle = new RectanglePrimitive { TopLeft = bounds.Location, BottomRight = bounds.Location + bounds.Size }; return(new BoundableResizeControlGraphic(new BoundableStretchControlGraphic(new MoveControlGraphic(rectangle)))); } else if (!closed && vertices.Count == 3) { var protractor = new ProtractorGraphic { Points = { vertices[0], vertices[1], vertices[2] } }; return(new VerticesControlGraphic(new MoveControlGraphic(protractor))); } else if (!closed && vertices.Count == 2) { var line = new PolylineGraphic { Points = { vertices[0], vertices[1] } }; return(new VerticesControlGraphic(new MoveControlGraphic(line))); } var polyline = new PolylineGraphic(closed); polyline.Points.AddRange(vertices); return(closed ? new PolygonControlGraphic(true, new MoveControlGraphic(polyline)) : new VerticesControlGraphic(true, new MoveControlGraphic(polyline))); }
public void TestComplexAreaAlgorithmWithComplexPolygonNoBufferOverrun() { // the complex area algorithm is very low resolution compared to simple area, so the error magnitude is higher const int side = 1000; const int expected = side * side / 2; PointF[] data = new PointF[5]; data[0] = new PointF(0, 0); data[1] = new PointF(side, 0); data[2] = new PointF(0, side); data[3] = new PointF(side, side); data[4] = new PointF(side / 2f, side / 2f); PolygonF.TestVertexNormalization(data); RectangleF bounding = RectangleUtilities.ComputeBoundingRectangle(data); // test for a possible buffer overrun in the computation foreach (PointF garbagePoint in SimulatedBufferOverrunPoints) { data[data.Length - 1] = garbagePoint; unsafe { fixed(PointF *points = data) { // inside PolygonF, the vertexCount is always exactly the expected array length // which is 4 (the 5th point simulates garbage data beyond the array) double result = PolygonF.TestComplexAreaComputation(bounding, points, data.Length - 1); Trace.WriteLine(string.Format("Complex Area: Expected {0:f5} Got {1:f5}", expected, result), "UNIT_TESTS"); Assert.IsTrue(Math.Abs(expected - result) < expected / 100f); } } } }
public void TestComplexAreaAlgorithmWithComplexPolygon() { // the complex area algorithm is very low resolution compared to simple area, so the error magnitude is higher const int side = 1000; const int expected = side * side / 2; PointF[] data = new PointF[4]; data[0] = new PointF(0, 0); data[1] = new PointF(side, 0); data[2] = new PointF(0, side); data[3] = new PointF(side, side); PolygonF.TestVertexNormalization(data); RectangleF bounding = RectangleUtilities.ComputeBoundingRectangle(data); unsafe { fixed(PointF *points = data) { // inside PolygonF, the vertexCount is always exactly the expected array length double result = PolygonF.TestComplexAreaComputation(bounding, points, data.Length); Trace.WriteLine(string.Format("Complex Area: Expected {0:f5} Got {1:f5}", expected, result), "UNIT_TESTS"); Assert.IsTrue(Math.Abs(expected - result) < expected / 100f); } } }
private static IGraphic CreateInteractiveEllipse(PointF majorAxisEnd1, PointF majorAxisEnd2, PointF minorAxisEnd1, PointF minorAxisEnd2) { if (IsAxisAligned(majorAxisEnd1, majorAxisEnd2) && IsAxisAligned(minorAxisEnd1, minorAxisEnd2)) { // use a standard ellipse primitive if the axes defines an axis-aligned ellipse var bounds = RectangleUtilities.ConvertToPositiveRectangle(RectangleUtilities.ComputeBoundingRectangle(majorAxisEnd1, majorAxisEnd2, minorAxisEnd1, minorAxisEnd2)); var ellipse = new EllipsePrimitive { TopLeft = bounds.Location, BottomRight = bounds.Location + bounds.Size }; return(new BoundableResizeControlGraphic(new BoundableStretchControlGraphic(new MoveControlGraphic(ellipse)))); } return(new MoveControlGraphic(CreateEllipse(majorAxisEnd1, majorAxisEnd2, minorAxisEnd1, minorAxisEnd2))); }
public static SegFrameImageGraphic AddSegFrameImageGraphicToPresentationImage( IPresentationImage presentationImage, Color color, IEnumerable <PointF> vertices) { var image = presentationImage as IImageGraphicProvider; if (image == null) { return(null); } // Get the base image ImageGraphic baseImage = image.ImageGraphic; var segFrameImageGraphic = new SegFrameImageGraphic(baseImage.Rows, baseImage.Columns, color); segFrameImageGraphic.Alpha = Seg.DefaultOpacity; RectangleF boundingBox = RectangleUtilities.ComputeBoundingRectangle(vertices.ToArray()); // Convert vector polygon to raster on a per pixel basis baseImage.PixelData.ForEachPixel( delegate(int i, int x, int y, int pixelIndex) { var point = new PointF(x, y); if (boundingBox.Contains(point)) { if (IsInPolygon(vertices.ToArray(), point)) { segFrameImageGraphic[x, y] = true; } } } ); var overlayGraphicsProvider = presentationImage as IOverlayGraphicsProvider; if (overlayGraphicsProvider != null) { overlayGraphicsProvider.OverlayGraphics.Add(segFrameImageGraphic); } return(segFrameImageGraphic); }
/// <summary> /// Constructs a new <see cref="IGraphic"/> whose contents are constructed based on a <see cref="GraphicAnnotationSequenceItem">DICOM Graphic Annotation Sequence Item</see>. /// </summary> /// <param name="graphicAnnotationSequenceItem">The DICOM graphic annotation sequence item to render.</param> /// <param name="displayedArea">The image's displayed area with which to </param> public static DicomGraphicAnnotation Create(GraphicAnnotationSequenceItem graphicAnnotationSequenceItem, RectangleF displayedArea) { var subjectGraphic = new SubjectGraphic(); var dataPoints = new List <PointF>(); if (graphicAnnotationSequenceItem.GraphicObjectSequence != null) { foreach (var graphicItem in graphicAnnotationSequenceItem.GraphicObjectSequence) { try { var points = GetGraphicDataAsSourceCoordinates(displayedArea, graphicItem); var graphic = CreateGraphic(graphicItem.GraphicType, points, true); if (graphic != null) { subjectGraphic.Graphics.Add(new ElementGraphic(graphic)); } dataPoints.AddRange(points); } catch (Exception ex) { Platform.Log(LogLevel.Warn, ex, "DICOM Softcopy Presentation State Deserialization Fault (Graphic Object Type {0}). Reprocess with log level DEBUG to see DICOM data dump.", graphicItem.GraphicType); Platform.Log(LogLevel.Debug, graphicItem.DicomSequenceItem.Dump()); } } } var annotations = new List <IGraphic>(); var annotationBounds = RectangleF.Empty; if (dataPoints.Count > 0) { annotationBounds = RectangleUtilities.ComputeBoundingRectangle(dataPoints.ToArray()); } if (graphicAnnotationSequenceItem.TextObjectSequence != null) { foreach (var textItem in graphicAnnotationSequenceItem.TextObjectSequence) { try { annotations.Add(CreateCalloutText(annotationBounds, displayedArea, textItem)); } catch (Exception ex) { Platform.Log(LogLevel.Warn, ex, "DICOM Softcopy Presentation State Deserialization Fault (Text Object). Reprocess with log level DEBUG to see DICOM data dump."); Platform.Log(LogLevel.Debug, textItem.DicomSequenceItem.Dump()); } } } var calloutGraphic = annotations.FirstOrDefault() as ICalloutGraphic; if (subjectGraphic.Graphics.Count == 1 && annotations.Count == 1 && calloutGraphic != null) { // disable the subject graphics before we add the callout, because we want the callout to be moveable subjectGraphic.SetEnabled(false); var subjectElement = (ElementGraphic)subjectGraphic.Graphics.Single(); subjectElement.Graphics.Add(calloutGraphic); subjectElement.Callout = calloutGraphic; } else { subjectGraphic.Graphics.AddRange(annotations.Where(g => !(g is ICalloutGraphic)).Select(g => new TextEditControlGraphic(g))); subjectGraphic.Graphics.AddRange(annotations.OfType <ICalloutGraphic>().Select(g => new UserCalloutGraphic { AnchorPoint = g.AnchorPoint, TextLocation = g.TextLocation, Text = g.Text, ShowArrowhead = !(g is CalloutGraphic) || ((CalloutGraphic)g).ShowArrowhead })); // disable both subject graphiucs and any callouts subjectGraphic.SetEnabled(false); } subjectGraphic.SetColor(Color.LemonChiffon); return(new DicomGraphicAnnotation(subjectGraphic)); }
/// <summary> /// Constructs a new <see cref="IGraphic"/> whose contents are constructed based on a <see cref="GraphicAnnotationSequenceItem">DICOM Graphic Annotation Sequence Item</see>. /// </summary> /// <param name="graphicAnnotationSequenceItem">The DICOM graphic annotation sequence item to render.</param> /// <param name="displayedArea">The image's displayed area with which to </param> public DicomGraphicAnnotation(GraphicAnnotationSequenceItem graphicAnnotationSequenceItem, RectangleF displayedArea) { this.CoordinateSystem = CoordinateSystem.Source; _layerId = graphicAnnotationSequenceItem.GraphicLayer ?? string.Empty; try { List <PointF> dataPoints = new List <PointF>(); if (graphicAnnotationSequenceItem.GraphicObjectSequence != null) { foreach (GraphicAnnotationSequenceItem.GraphicObjectSequenceItem graphicItem in graphicAnnotationSequenceItem.GraphicObjectSequence) { try { IList <PointF> points = GetGraphicDataAsSourceCoordinates(displayedArea, graphicItem); switch (graphicItem.GraphicType) { case GraphicAnnotationSequenceItem.GraphicType.Interpolated: base.Graphics.Add(CreateInterpolated(points)); break; case GraphicAnnotationSequenceItem.GraphicType.Polyline: base.Graphics.Add(CreatePolyline(points)); break; case GraphicAnnotationSequenceItem.GraphicType.Point: base.Graphics.Add(CreatePoint(points[0])); break; case GraphicAnnotationSequenceItem.GraphicType.Circle: base.Graphics.Add(CreateCircle(points[0], (float)Vector.Distance(points[0], points[1]))); break; case GraphicAnnotationSequenceItem.GraphicType.Ellipse: base.Graphics.Add(CreateEllipse(points[0], points[1], points[2], points[3])); break; default: break; } dataPoints.AddRange(points); } catch (Exception ex) { Platform.Log(LogLevel.Warn, ex, "DICOM Softcopy Presentation State Deserialization Fault (Graphic Object Type {0}). Reprocess with log level DEBUG to see DICOM data dump.", graphicItem.GraphicType); Platform.Log(LogLevel.Debug, graphicItem.DicomSequenceItem.Dump()); } } } RectangleF annotationBounds = RectangleF.Empty; if (dataPoints.Count > 0) { annotationBounds = RectangleUtilities.ComputeBoundingRectangle(dataPoints.ToArray()); } if (graphicAnnotationSequenceItem.TextObjectSequence != null) { foreach (GraphicAnnotationSequenceItem.TextObjectSequenceItem textItem in graphicAnnotationSequenceItem.TextObjectSequence) { try { base.Graphics.Add(CreateCalloutText(annotationBounds, displayedArea, textItem)); } catch (Exception ex) { Platform.Log(LogLevel.Warn, ex, "DICOM Softcopy Presentation State Deserialization Fault (Text Object). Reprocess with log level DEBUG to see DICOM data dump."); Platform.Log(LogLevel.Debug, textItem.DicomSequenceItem.Dump()); } } } } finally { this.ResetCoordinateSystem(); } OnColorChanged(); }
/// <summary> /// Called by <see cref="Roi.BoundingBox"/> to compute the tightest bounding box of the region of interest. /// </summary> /// <remarks> /// <para>This method is only called once and the result is cached for future accesses.</para> /// <para> /// Regions of interest have no notion of coordinate system. All coordinates are inherently /// given relative to the image pixel space (i.e. <see cref="CoordinateSystem.Source"/>.) /// </para> /// </remarks> /// <returns>A rectangle defining the bounding box.</returns> protected override RectangleF ComputeBounds() { return(RectangleUtilities.ComputeBoundingRectangle(_points)); }