/// <summary> /// Gets the vectors describing the tick marks in source coordinates. /// </summary> private void GetTickVectors(out SizeF majorTickVector, out SizeF minorTickVector) { // compute tick vectors in display coordinates (since they are effectively invariant graphics) // but then convert them back to source coordinates because the graphics all use source coordinates CoordinateSystem = CoordinateSystem.Destination; try { var pt0 = Point1; var pt1 = Point2; // compute normal to the base line var normalX = (double)pt0.Y - pt1.Y; var normalY = (double)pt1.X - pt0.X; var normalMagnitude = Math.Sqrt(normalX * normalX + normalY * normalY); var normalUnit = new SizeF((float)(Math.Abs(normalX) / normalMagnitude), (float)(Math.Abs(normalY) / normalMagnitude)); var majorTickLength = _isMirrored ? -_majorTickLength : _majorTickLength; var minorTickLength = _isMirrored ? -_minorTickLength : _minorTickLength; majorTickVector = SpatialTransform.ConvertToSource(new SizeF(majorTickLength * normalUnit.Width, majorTickLength * normalUnit.Height)); minorTickVector = SpatialTransform.ConvertToSource(new SizeF(minorTickLength * normalUnit.Width, minorTickLength * normalUnit.Height)); } finally { ResetCoordinateSystem(); } }
protected override PointF ConstrainControlPointLocation(int controlPointIndex, PointF cursorLocation) { // this operation must be performed in source coodinates because the definition of top/left/right/bottom controls are relative to image not tile! var sourcePoint = SpatialTransform.ConvertToSource(cursorLocation); CoordinateSystem = CoordinateSystem.Source; try { var rect = Subject.Rectangle; switch (controlPointIndex) { case _top: case _bottom: return(SpatialTransform.ConvertToDestination(new PointF(rect.Left + rect.Width / 2, sourcePoint.Y))); case _left: case _right: return(SpatialTransform.ConvertToDestination(new PointF(sourcePoint.X, rect.Top + rect.Height / 2))); } } finally { ResetCoordinateSystem(); } return(base.ConstrainControlPointLocation(controlPointIndex, cursorLocation)); }
public void IsTransformed_ScaleUnity_RotateZeroPanZero_IsFalse() { var transform = new SpatialTransform { Scale = 1.0, Rotation = 0, Pan = new Point2(0, 0) }; Assert.False(transform.IsTransformed); }
public void IsTransformed_PanNonZero_IsTrue() { var transform = new SpatialTransform { Pan = new Point2(5, -7) }; Assert.True(transform.IsTransformed); }
/// <summary> /// Computes positions of tick marks along the base line. /// </summary> private Tick[] ComputeTickMarks() { // compute tick locations in source coordinates because all the underlying graphics use source coordinates, so we save on a ton of ridiculous back-and-forth conversions CoordinateSystem = CoordinateSystem.Source; try { var srcPoint1 = Point1; var srcPoint2 = Point2; // get the pixel dimensions of the image - if it's not calibrated, there's nothing to show! double pxW, pxH; bool isCalibrated = TryGetPixelDimensions(out pxW, out pxH); if (!isCalibrated) { return(null); } // figure out how long the base line is in mm, and thus how many ticks apart they are var mmLength = GetDistance(srcPoint1.X * pxW, srcPoint1.Y * pxH, srcPoint2.X * pxW, srcPoint2.Y * pxH); var countTickUnits = mmLength / _minorTick; // round down and add 1 to get the number of tick marks displayable on the given base line var countTickMarks = 1 + (int)countTickUnits; // get the total distance between the end points in screen units var dstLength = Vector.Distance(SpatialTransform.ConvertToDestination(srcPoint1), SpatialTransform.ConvertToDestination(srcPoint2)); // compute number of screen pixels between consecutive ticks // if the effective spacing is less than 3 pixels, the ticks will not render distinctly so we can stop here // additionally, we need at least two tick visible tick marks in order to have anything to show if (dstLength / countTickUnits < 3 || countTickMarks < 2) { return(null); } // only distinguish between major/minor ticks if there are at least MAJOR_TICK_FREQ+1 ticks of spacing (i.e. at least two major ticks) var useMajorTicks = countTickMarks >= _majorTickFrequency + 1; var src1 = new Vector3D(srcPoint1.X, srcPoint1.Y, 0); var src2 = new Vector3D(srcPoint2.X, srcPoint2.Y, 0); var srcV = src2 - src1; // vector from point2 to point1 var ticks = new Tick[countTickMarks]; for (var n = 0; n < countTickMarks; ++n) { // compute location for each tick from point1 + the vector scaled appropriately by index and the number of tick units along the line // don't just compute one offset and keep adding it, as the floating point errors would then be cumulative var location = src1 + srcV * (float)(n / countTickUnits); ticks[n] = new Tick(location.X, location.Y, useMajorTicks && n % _majorTickFrequency == 0); } return(ticks); } finally { ResetCoordinateSystem(); } }
public PatientOrientationHelper(SpatialTransform imageTransform, PatientOrientation patientOrientation) { Platform.CheckForNullReference(imageTransform, "imageTransform"); Platform.CheckForNullReference(patientOrientation, "patientOrientation"); _imageTransform = imageTransform; _patientOrientation = patientOrientation; AngleTolerance = 1; }
public PatientOrientationHelper(SpatialTransform imageTransform, ImageOrientationPatient imageOrientationPatient) { Platform.CheckForNullReference(imageTransform, "imageTransform"); Platform.CheckForNullReference(imageOrientationPatient, "imageOrientationPatient"); _imageTransform = imageTransform; _imageOrientationPatient = imageOrientationPatient; AngleTolerance = 1; }
public override void OnDrawing() { if (base.ParentPresentationImage != null) { SpatialTransform transform = base.SpatialTransform; transform.TranslationX = (base.ParentPresentationImage.ClientRectangle.Width - this.Width) / 2f; transform.TranslationY = (base.ParentPresentationImage.ClientRectangle.Height - this.Height) / 2f; } base.OnDrawing(); }
public override bool HitTest(Vector3D point) { CoordinateSystem = CoordinateSystem.Source; try { return(HitTest(SpatialTransform.ConvertPointToSource(point), Rectangle, SpatialTransform)); } finally { ResetCoordinateSystem(); } }
/// <summary> /// Called by GetAnnotationText (and also by Unit Test code). Making this function internal simply makes it easier /// to write unit tests for this class (don't have to implement a fake PresentationImage). /// </summary> /// <param name="imageTransform">the image transform</param> /// <param name="patientOrientation">the image orientation patient (direction cosines)</param> /// <returns></returns> internal string GetAnnotationTextInternal(SpatialTransform imageTransform, PatientOrientation patientOrientation) { SizeF[] imageEdgeVectors = new SizeF[4]; for (int i = 0; i < 4; ++i) { imageEdgeVectors[i] = imageTransform.ConvertToDestination(_edgeVectors[i]); } //find out which source image edge got transformed to coincide with this viewport edge. ImageEdge transformedEdge = GetTransformedEdge(imageEdgeVectors); //get the marker for the appropriate (source) image edge. return(GetMarker(transformedEdge, patientOrientation)); }
/// <summary> /// Sets both endpoints of the scale's base line segment in one atomic operation, cause only one update and draw. /// </summary> /// <param name="point1">One endpoint of the base line segment.</param> /// <param name="point2">The other endpoint of the base line segment.</param> public void SetEndPoints(PointF point1, PointF point2) { if (CoordinateSystem != CoordinateSystem.Source) { point1 = SpatialTransform.ConvertToSource(point1); point2 = SpatialTransform.ConvertToSource(point2); } if (point1 != _point1 || point2 != _point2) { _point1 = point1; _point2 = point2; FlagAsDirty(); } }
public static DicomImagePlane FromImage(IPresentationImage sourceImage) { if (sourceImage == null) { return(null); } Frame frame = GetFrame(sourceImage); SpatialTransform transform = GetSpatialTransform(sourceImage); if (transform == null || frame == null) { return(null); } if (String.IsNullOrEmpty(frame.FrameOfReferenceUid) || String.IsNullOrEmpty(frame.ParentImageSop.StudyInstanceUID) ) { return(null); } DicomImagePlane plane; if (_referenceCount > 0) { plane = CreateFromCache(frame); } else { plane = CreateFromFrame(frame); } if (plane != null) { plane._sourceImage = sourceImage; plane._sourceImageTransform = transform; plane._sourceFrame = frame; } return(plane); }
private void IncrementPan(int xIncrement, int yIncrement) { if (!CanPan()) { return; } SpatialTransform transform = (SpatialTransform)_operation.GetOriginator(this.SelectedPresentationImage); // Because the pan increment is in destination coordinates, we have to convert // them to source coordinates, since the transform translation is in source coordinates. // This will allow the pan to work properly irrespective of the zoom, flip and rotation. SizeF sourceIncrement = transform.ConvertToSource(new SizeF(xIncrement, yIncrement)); transform.TranslationX += sourceIncrement.Width; transform.TranslationY += sourceIncrement.Height; this.SelectedPresentationImage.Draw(); }
/// <summary> /// Called to notify the derived class of a control point change event. /// </summary> /// <param name="index">The index of the point that changed.</param> /// <param name="point">The value of the point that changed.</param> protected override void OnControlPointChanged(int index, PointF point) { // this operation must be performed in source coodinates because the definition of top-left/bottom-left/top-right/bottom-right controls are relative to image not tile! var sourcePoint = CoordinateSystem == CoordinateSystem.Destination ? SpatialTransform.ConvertToSource(point) : point; CoordinateSystem = CoordinateSystem.Source; try { var subject = Subject; var rect = subject.Rectangle; switch (index) { case _topLeft: subject.TopLeft = sourcePoint; break; case _bottomRight: subject.BottomRight = sourcePoint; break; case _topRight: subject.TopLeft = new PointF(rect.Left, sourcePoint.Y); subject.BottomRight = new PointF(sourcePoint.X, rect.Bottom); break; case _bottomLeft: subject.TopLeft = new PointF(sourcePoint.X, rect.Top); subject.BottomRight = new PointF(rect.Right, sourcePoint.Y); break; } } finally { ResetCoordinateSystem(); } base.OnControlPointChanged(index, point); }
private static void SerializeDashedLine(PointF point1, PointF point2, SpatialTransform spatialTransform, GraphicAnnotationSequenceItem serializationState, bool showHashes) { // these control parameters are in screen pixels at the nominal presentation zoom level const float period = 8; const float amplitude = 0.5f; SizeF normalVector; SizeF dashVector; float periods; // compute the dash vector and cross hash vector to be sized relative to screen pixels at nominal presentation zoom level { PointF dstLineVector = spatialTransform.ConvertToDestination(new SizeF(point2) - new SizeF(point1)).ToPointF(); float dstMagnitude = (float)Math.Sqrt(dstLineVector.X * dstLineVector.X + dstLineVector.Y * dstLineVector.Y); periods = dstMagnitude / period; dstLineVector.X /= dstMagnitude; dstLineVector.Y /= dstMagnitude; dashVector = spatialTransform.ConvertToSource(new SizeF(dstLineVector.X * period / 2, dstLineVector.Y * period / 2)); normalVector = spatialTransform.ConvertToSource(new SizeF(-dstLineVector.Y * amplitude, dstLineVector.X * amplitude)); } PointF start = point1; int limit = (int)periods; for (int n = 0; n < limit; n++) { PointF midPeriod = start + dashVector; start = midPeriod + dashVector; GraphicObject dash = new GraphicObject(); dash.GraphicAnnotationUnits = GraphicAnnotationSequenceItem.GraphicAnnotationUnits.Pixel; dash.GraphicData = new PointF[] { midPeriod, start }; dash.GraphicDimensions = 2; dash.GraphicFilled = GraphicAnnotationSequenceItem.GraphicFilled.N; dash.GraphicType = GraphicAnnotationSequenceItem.GraphicType.Polyline; dash.NumberOfGraphicPoints = 2; serializationState.AppendGraphicObjectSequence(dash); } // the first half of each period has no line, so this is only necessary if the residual is over half a period if (periods - limit > 0.5f) { GraphicObject dash = new GraphicObject(); dash.GraphicAnnotationUnits = GraphicAnnotationSequenceItem.GraphicAnnotationUnits.Pixel; dash.GraphicData = new PointF[] { start + dashVector, point2 }; dash.GraphicDimensions = 2; dash.GraphicFilled = GraphicAnnotationSequenceItem.GraphicFilled.N; dash.GraphicType = GraphicAnnotationSequenceItem.GraphicType.Polyline; dash.NumberOfGraphicPoints = 2; serializationState.AppendGraphicObjectSequence(dash); } if (showHashes) { GraphicObject hash1 = new GraphicObject(); hash1.GraphicAnnotationUnits = GraphicAnnotationSequenceItem.GraphicAnnotationUnits.Pixel; hash1.GraphicData = new PointF[] { point1 - normalVector, point1 + normalVector }; hash1.GraphicDimensions = 2; hash1.GraphicFilled = GraphicAnnotationSequenceItem.GraphicFilled.N; hash1.GraphicType = GraphicAnnotationSequenceItem.GraphicType.Polyline; hash1.NumberOfGraphicPoints = 2; serializationState.AppendGraphicObjectSequence(hash1); GraphicObject hash2 = new GraphicObject(); hash2.GraphicAnnotationUnits = GraphicAnnotationSequenceItem.GraphicAnnotationUnits.Pixel; hash2.GraphicData = new PointF[] { point2 - normalVector, point2 + normalVector }; hash2.GraphicDimensions = 2; hash2.GraphicFilled = GraphicAnnotationSequenceItem.GraphicFilled.N; hash2.GraphicType = GraphicAnnotationSequenceItem.GraphicType.Polyline; hash2.NumberOfGraphicPoints = 2; serializationState.AppendGraphicObjectSequence(hash2); } }
internal void SetParentGraphic(IGraphic3D parentGraphic) { _parentGraphic = parentGraphic; SpatialTransform.ForceRecalculation(); }