Exemple #1
0
        /// <summary>
        /// Provides the transformation from IEC coordinates to patient DICOM coordinates. Useful for rendering
        /// structures.
        /// </summary>
        /// <param name="orient"></param>
        /// <returns>4x4 transformation matrix</returns>
        public static Matrix3D IECToDICOM(PatientOrientation orient)
        {
            var tx = DICOM2IEC(orient);

            tx.Invert();
            return(tx);
        }
		/// <summary>
		/// Initializes a new instance of <see cref="MammographyImageSpatialTransform"/> with the specified image plane details.
		/// </summary>
		public MammographyImageSpatialTransform(IGraphic ownerGraphic, int rows, int columns, double pixelSpacingX, double pixelSpacingY, double pixelAspectRatioX, double pixelAspectRatioY, PatientOrientation patientOrientation, string laterality)
			: base(ownerGraphic, rows, columns, pixelSpacingX, pixelSpacingY, pixelAspectRatioX, pixelAspectRatioY)
		{
			// image coordinates are defined with X and Y being equivalent to the screen axes (right and down) and with Z = X cross Y (into the screen)

			Vector3D imagePosterior, imageHead, imageLeft; // patient orientation vectors in image space
			GetPatientOrientationVectors(patientOrientation, out imageHead, out imageLeft, out imagePosterior);

			// no adjustments if the posterior direction is not represented in the image
			if ((_imagePosterior = imagePosterior) != null)
			{
				Vector3D normativePosterior, normativeHead, normativeLeft; // normative patient orientation vectors in image space
				GetNormativeOrientationVectors(laterality, out normativeHead, out normativeLeft, out normativePosterior);

				// only do any adjustments if laterality implies a normative orientation for the posterior direction
				if (normativePosterior != null)
				{
					// check if the order of the patient vectors are flipped according to the normative vectors
					// we know we need to flip if the direction vector cross products have different signs
					if (imageHead != null)
						FlipX = _coreFlipX = Math.Sign(imagePosterior.Cross(imageHead).Z) != Math.Sign(normativePosterior.Cross(normativeHead).Z);
					else if (imageLeft != null)
						FlipX = _coreFlipX = Math.Sign(imagePosterior.Cross(imageLeft).Z) != Math.Sign(normativePosterior.Cross(normativeLeft).Z);

					// with flip normalized, just rotate to align the current posterior direction with the normative posterior
					var currentPosterior = GetCurrentPosteriorVector(_imagePosterior, SourceWidth, AdjustedSourceHeight, 0, 1, 1, _coreFlipX, false);
					var posteriorAngle = Math.Atan2(currentPosterior.Y, currentPosterior.X);
					var normativeAngle = Math.Atan2(normativePosterior.Y, normativePosterior.X);

					// compute required rotation, rounded to multiples of 90 degrees (PI/2 radians)
					RotationXY = _coreRotation = 90*((int) Math.Round((normativeAngle - posteriorAngle)*2/Math.PI));
				}
			}
		}
        public PatientOrientationHelper(SpatialTransform imageTransform, PatientOrientation patientOrientation)
        {
            Platform.CheckForNullReference(imageTransform, "imageTransform");
            Platform.CheckForNullReference(patientOrientation, "patientOrientation");

            _imageTransform     = imageTransform;
            _patientOrientation = patientOrientation;
            AngleTolerance      = 1;
        }
        public PatientOrientationHelper(ISpatialTransform imageTransform, PatientOrientation patientOrientation)
        {
            Platform.CheckForNullReference(imageTransform, "imageTransform");
            Platform.CheckForNullReference(patientOrientation, "patientOrientation");

            _imageTransform = imageTransform;
            _patientOrientation = patientOrientation;
            AngleTolerance = 1;
        }
Exemple #5
0
        /// <summary>
        /// Provides the transformation from patient DICOM coordinates to IEC coordinates, adjusted for the isocenter position.
        /// Useful for rendering structures.
        /// </summary>
        /// <param name="orient">patient orienation</param>
        /// /// <param name="iso">isocenter position</param>
        /// <returns>4x4 transformation matrix</returns>
        public static Matrix3D DICOM2IEC_IsoAdjusted(PatientOrientation orient, VVector iso)
        {
            var basic  = DICOM2IEC(orient);
            var offset = Matrix3D.Identity;

            offset.OffsetX = -iso.x;
            offset.OffsetY = -iso.y;
            offset.OffsetZ = -iso.z;

            return(Matrix3D.Multiply(offset, basic));
        }
Exemple #6
0
        public StructureSet AddEmptyPhantom(string imageId, PatientOrientation orientation, int xSizePixel,
                                            int ySizePixel, double widthMM, double heightMM, int nrOfPlanes, double planeSepMM)
        {
            var local  = this;
            var retVal = X.Instance.CurrentContext.GetValue(sc =>
            {
                return(new StructureSet(local._client.AddEmptyPhantom(imageId, orientation, xSizePixel, ySizePixel,
                                                                      widthMM, heightMM, nrOfPlanes, planeSepMM)));
            });

            return(retVal);
        }
 public FieldNameChecker(string fieldName, double gantry, double couch, PatientOrientation patientOrientation = PatientOrientation.HeadFirstSupine)
 {
     this.FieldName       = fieldName;
     this.GantryAngle     = gantry;
     this.CouchAngle      = couch;
     this.PatientPosition = patientOrientation;
     fieldNameDecoder(fieldName);
     if (IsLegitName)
     {
         this.Orientation = new FieldOrientation(Name);
         IsAngleMatch     = GantryAngleMatch(this.Orientation, this.GantryAngle, this.CouchAngle, this.PatientPosition);
     }
 }
Exemple #8
0
        /// <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>
        /// Constructor for BeamGeometry
        /// </summary>
        /// <param name="gantryAngle"> Gantry angle in degree </param>
        /// <param name="collimatorAngle"> Collimator angle in degree </param>
        /// <param name="couchAngle"> Couch angle in radian </param>
        /// <param name="isocenter"> Isocenter coordinate in the planning coordinate system in mm </param>
        /// <para name="patientOrientation"> Enum for patient orientation </para>
        public BeamGeometry(double gantryAngle, double collimatorAngle,
                            double couchAngle, double[] isocenter,
                            PatientOrientation patientOrientation = PatientOrientation.NoOrientation)
        {
            this.GantryAngle     = gantryAngle;
            this.CollimatorAngle = collimatorAngle;
            this.CouchAngle      = couchAngle;
            for (int i = 0; i < 3; i++)
            {
                this.Isocenter[i] = isocenter[i];
            }

            CoordinateTransform3D.SourceCoordinateInPlanningCoordinate(
                SourcePosition, isocenter, gantryAngle, collimatorAngle, couchAngle, SourceToAxisDistance);
        }
Exemple #10
0
        /// <summary>
        /// Determines the (untransformed) marker for a particular image edge.
        /// </summary>
        /// <param name="imageEdge">the edge (image coordinates)</param>
        /// <param name="patientOrientation">the patient orientation construct of the image</param>
        /// <returns>a string representation of the direction (a 'marker')</returns>
        private string GetMarker(ImageEdge imageEdge, PatientOrientation patientOrientation)
        {
            bool negativeDirection = (imageEdge == ImageEdge.Left || imageEdge == ImageEdge.Top);
            bool rowValues         = (imageEdge == ImageEdge.Left || imageEdge == ImageEdge.Right);

            var direction = (rowValues ? patientOrientation.Row : patientOrientation.Column) ?? PatientDirection.Empty;

            if (negativeDirection)
            {
                direction = direction.OpposingDirection;
            }

            string markerText = "";

            markerText += GetMarkerText(direction.Primary);
            markerText += GetMarkerText(direction.Secondary);

            return(markerText);
        }
        public string GetMatchingStoredLayoutId(IDicomAttributeProvider dicomAttributeProvider)
        {
            if (dicomAttributeProvider == null)
            {
                return(null);
            }

            var filterCandidates = new List <KeyValuePair <string, string> >
            {
                new KeyValuePair <string, string>("Modality", dicomAttributeProvider[DicomTags.Modality].GetString(0, string.Empty))
            };

            // these are hard-coded as the only filter candidates for now, until more general use cases are identified.
            var patientOrientation = PatientOrientation.FromString(dicomAttributeProvider[DicomTags.PatientOrientation].ToString());

            if (patientOrientation != null && !patientOrientation.IsEmpty)
            {
                filterCandidates.Add(new KeyValuePair <string, string>("PatientOrientation_Row", patientOrientation.PrimaryRow));
                filterCandidates.Add(new KeyValuePair <string, string>("PatientOrientation_Col", patientOrientation.PrimaryColumn));
            }

            return(GetMatchingStoredLayoutId(filterCandidates));
        }
        private bool GantryAngleMatch(FieldOrientation fieldOrientation, double gantry, double couch, PatientOrientation patientOrientation)
        {
            bool checkLR = false;
            bool checkAP = false;
            bool checkSI = false;

            switch (patientOrientation)
            {
            case PatientOrientation.HeadFirstSupine:
            {
                //check some special case
                switch (fieldOrientation.FieldName)
                {
                case "VERTEX":
                    if ((couch == 90.00 && gantry == 270.00) ||
                        (couch == 270.00 & gantry == 90.00))
                    {
                        checkLR = checkAP = checkSI = true;
                    }
                    break;

                case "ANT":
                    if (gantry == 0.00 && couch == 0.00)
                    {
                        checkLR = checkAP = checkSI = true;
                    }
                    break;

                case "POST":
                    if (gantry == 180.00 && couch == 0.00)
                    {
                        checkLR = checkAP = checkSI = true;
                    }
                    break;

                case "LT LAT":
                    if (gantry == 90.00 && couch == 0.00)
                    {
                        checkLR = checkAP = checkSI = true;
                    }
                    break;

                case "RT LAT":
                    if (gantry == 270.00 && couch == 0.00)
                    {
                        checkLR = checkAP = checkSI = true;
                    }
                    break;

                default:
                    //check Left Right
                    if (gantry > 0.00 && gantry < 180.00 && (couch != 90.00 || couch != 270.00))
                    {
                        checkLR = fieldOrientation.LeftRight == FieldOrientation.LeftorRight.Left;
                    }
                    else if (gantry > 180.00 && gantry < 359.99)
                    {
                        checkLR = fieldOrientation.LeftRight == FieldOrientation.LeftorRight.Right;
                    }
                    else
                    {
                        checkLR = fieldOrientation.LeftRight == FieldOrientation.LeftorRight.NA;
                    }

                    //check Antieror Postieror
                    if ((gantry > 270.00 && gantry < 359.99) || (gantry >= 0.00 && gantry < 90.00))
                    {
                        checkAP = fieldOrientation.AntPost == FieldOrientation.AntorPost.Anterior;
                    }
                    else if (gantry > 90.00 && gantry < 270.00)
                    {
                        checkAP = fieldOrientation.AntPost == FieldOrientation.AntorPost.Posterior;
                    }
                    else
                    {
                        checkAP = fieldOrientation.AntPost == FieldOrientation.AntorPost.NA;
                    }

                    //check Superior and Inferior
                    if ((gantry > 0.00 && gantry < 180.00 && couch > 270.00 && couch < 359.99) ||
                        (gantry > 180.00 && gantry < 359.99 && couch > 0.00 && couch < 90.00))
                    {
                        checkSI = fieldOrientation.SupInf == FieldOrientation.SuporInf.Superior;
                    }
                    else if ((gantry > 0.00 && gantry < 180.00 && couch > 0.00 && couch < 90.00) ||
                             (gantry > 180.00 && gantry < 359.99 && couch > 270.00 && couch < 359.99))
                    {
                        checkSI = fieldOrientation.SupInf == FieldOrientation.SuporInf.Inferior;
                    }
                    else
                    {
                        checkSI = fieldOrientation.SupInf == FieldOrientation.SuporInf.NA;
                    }

                    break;
                }
                break;
            }

            case PatientOrientation.HeadFirstProne:
                break;

            case PatientOrientation.FeetFirstSupine:
                break;

            case PatientOrientation.FeetFirstProne:
                break;
            }
            if (!checkLR)
            {
                Warning += System.Environment.NewLine + "Left Right mismatch";
            }
            if (!checkAP)
            {
                Warning += System.Environment.NewLine + "Anterior Posterior mismatch";
            }
            if (!checkSI)
            {
                Warning += System.Environment.NewLine + "Superior Inferior mismatch";
            }
            return(checkLR && checkAP && checkSI);
        }
Exemple #13
0
        /// <summary>
        /// Provides the transformation from patient DICOM coordinates to IEC coordinates. Useful for rendering
        /// structures.
        /// </summary>
        /// <param name="orient"></param>
        /// <returns>4x4 transformation matrix</returns>
        public static Matrix3D DICOM2IEC(PatientOrientation orient)
        {
            switch (orient)
            {
            case PatientOrientation.HeadFirstSupine:
                return(new Matrix3D(
                           1, 0, 0, 0,
                           0, 0, -1, 0,
                           0, 1, 0, 0,
                           0, 0, 0, 1));

            case PatientOrientation.HeadFirstProne:
                return(new Matrix3D(
                           -1, 0, 0, 0,
                           0, 0, 1, 0,
                           0, 1, 0, 0,
                           0, 0, 0, 1));

            case PatientOrientation.FeetFirstSupine:
                return(new Matrix3D(
                           -1, 0, 0, 0,
                           0, 0, -1, 0,
                           0, -1, 0, 0,
                           0, 0, 0, 1));

            case PatientOrientation.FeetFirstProne:
                return(new Matrix3D(
                           1, 0, 0, 0,
                           0, 0, 1, 0,
                           0, -1, 0, 0,
                           0, 0, 0, 1));

            case PatientOrientation.HeadFirstDecubitusLeft:
                return(new Matrix3D(
                           0, 0, -1, 0,
                           -1, 0, 0, 0,
                           0, 1, 0, 0,
                           0, 0, 0, 1));

            case PatientOrientation.HeadFirstDecubitusRight:
                return(new Matrix3D(
                           0, 0, 1, 0,
                           1, 0, 0, 0,
                           0, 1, 0, 0,
                           0, 0, 0, 1));

            case PatientOrientation.FeetFirstDecubitusLeft:
                return(new Matrix3D(
                           0, 0, -1, 0,
                           1, 0, 0, 0,
                           0, -1, 0, 0,
                           0, 0, 0, 1));

            case PatientOrientation.FeetFirstDecubitusRight:
                return(new Matrix3D(
                           0, 0, 1, 0,
                           -1, 0, 0, 0,
                           0, -1, 0, 0,
                           0, 0, 0, 1));

            default: throw new Exception("Don't have transform for this orientation!");
            }
        }
		/// <summary>
		/// Determines the (untransformed) marker for a particular image edge.
		/// </summary>
		/// <param name="imageEdge">the edge (image coordinates)</param>
		/// <param name="patientOrientation">the patient orientation construct of the image</param>
		/// <returns>a string representation of the direction (a 'marker')</returns>
		private string GetMarker(ImageEdge imageEdge, PatientOrientation patientOrientation)
		{
			bool negativeDirection = (imageEdge == ImageEdge.Left || imageEdge == ImageEdge.Top);
			bool rowValues = (imageEdge == ImageEdge.Left || imageEdge == ImageEdge.Right);

			var direction = (rowValues ? patientOrientation.Row : patientOrientation.Column) ?? PatientDirection.Empty;
			if (negativeDirection)
				direction = direction.OpposingDirection;

			string markerText = "";
			markerText += GetMarkerText(direction.Primary);
			markerText += GetMarkerText(direction.Secondary);

			return markerText;
		}
		/// <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);
		}
        private static void GetPatientOrientationVectors(PatientOrientation patientOrientation, out Vector3D headVector, out Vector3D leftVector, out Vector3D posteriorVector)
        {
            headVector = leftVector = posteriorVector = null;
            if (patientOrientation == null)
            {
                return;
            }

            if (!string.IsNullOrEmpty(patientOrientation.Row))
            {
                switch (char.ToUpperInvariant(patientOrientation.Row.Code[0]))
                {
                case _orientationLeft:
                    leftVector = new Vector3D(+1, 0, 0);
                    break;

                case _orientationRight:
                    leftVector = new Vector3D(-1, 0, 0);
                    break;

                case _orientationPosterior:
                    posteriorVector = new Vector3D(+1, 0, 0);
                    break;

                case _orientationAnterior:
                    posteriorVector = new Vector3D(-1, 0, 0);
                    break;

                case _orientationHead:
                    headVector = new Vector3D(+1, 0, 0);
                    break;

                case _orientationFoot:
                    headVector = new Vector3D(-1, 0, 0);
                    break;
                }
            }

            if (!string.IsNullOrEmpty(patientOrientation.Column))
            {
                switch (char.ToUpperInvariant(patientOrientation.Column.Code[0]))
                {
                case _orientationLeft:
                    leftVector = new Vector3D(0, +1, 0);
                    break;

                case _orientationRight:
                    leftVector = new Vector3D(0, -1, 0);
                    break;

                case _orientationPosterior:
                    posteriorVector = new Vector3D(0, +1, 0);
                    break;

                case _orientationAnterior:
                    posteriorVector = new Vector3D(0, -1, 0);
                    break;

                case _orientationHead:
                    headVector = new Vector3D(0, +1, 0);
                    break;

                case _orientationFoot:
                    headVector = new Vector3D(0, -1, 0);
                    break;
                }
            }
        }
		private static void GetPatientOrientationVectors(PatientOrientation patientOrientation, out Vector3D headVector, out Vector3D leftVector, out Vector3D posteriorVector)
		{
			headVector = leftVector = posteriorVector = null;
			if (patientOrientation == null)
				return;

			if (!string.IsNullOrEmpty(patientOrientation.Row))
			{
				switch (char.ToUpperInvariant(patientOrientation.Row.Code[0]))
				{
					case _orientationLeft:
						leftVector = new Vector3D(+1, 0, 0);
						break;
					case _orientationRight:
						leftVector = new Vector3D(-1, 0, 0);
						break;
					case _orientationPosterior:
						posteriorVector = new Vector3D(+1, 0, 0);
						break;
					case _orientationAnterior:
						posteriorVector = new Vector3D(-1, 0, 0);
						break;
					case _orientationHead:
						headVector = new Vector3D(+1, 0, 0);
						break;
					case _orientationFoot:
						headVector = new Vector3D(-1, 0, 0);
						break;
				}
			}

			if (!string.IsNullOrEmpty(patientOrientation.Column))
			{
				switch (char.ToUpperInvariant(patientOrientation.Column.Code[0]))
				{
					case _orientationLeft:
						leftVector = new Vector3D(0, +1, 0);
						break;
					case _orientationRight:
						leftVector = new Vector3D(0, -1, 0);
						break;
					case _orientationPosterior:
						posteriorVector = new Vector3D(0, +1, 0);
						break;
					case _orientationAnterior:
						posteriorVector = new Vector3D(0, -1, 0);
						break;
					case _orientationHead:
						headVector = new Vector3D(0, +1, 0);
						break;
					case _orientationFoot:
						headVector = new Vector3D(0, -1, 0);
						break;
				}
			}
		}
Exemple #18
0
        protected override void RunTest(PlanSetup plan)
        {
            DisplayName     = "Patient Shifts";
            TestExplanation = "Displays shifts from Marker Structure or User Origin";
            Result          = "";
            ResultDetails   = "";
            DisplayColor    = ResultColorChoices.Pass;

            PatientOrientation orientation = plan.TreatmentOrientation;

            // get location of user origin and plan isocenter
            VVector tattoos   = plan.StructureSet.Image.UserOrigin;
            VVector isocenter = plan.Beams.First().IsocenterPosition;

            // calculated shift distance from user origin
            VVector shift     = isocenter - tattoos;
            string  shiftFrom = "User Origin";

            // these sites set iso at sim and import in a "MARKER" structure that shifts will be based off (also they don't use gold markers, so there's no need to worry about those "MARKER" structures)
            if (Department == Department.MPH ||
                Department == Department.FLT ||
                Department == Department.LAP ||
                Department == Department.OWO ||
                Department == Department.DET ||
                Department == Department.FAR)
            {
                // loop through each patient marker and see if it's closer to the iso than the user origin and if it is use that for the calculated shift
                foreach (Structure point in plan.StructureSet.Structures.Where(x => x.DicomType == "MARKER"))
                {
                    if (Math.Round((isocenter - point.CenterPoint).Length, 2) <= Math.Round(shift.Length, 2))
                    {
                        shift     = isocenter - point.CenterPoint;
                        shiftFrom = point.Id;
                    }
                }
            }

            //round it off to prevent very small numbers from appearing and convert to cm for shifts
            shift.x = Math.Round(shift.x / 10, 1);
            shift.y = Math.Round(shift.y / 10, 1);
            shift.z = Math.Round(shift.z / 10, 1);

            if (shift.Length == 0)
            {
                ResultDetails = $"No shifts from {shiftFrom}";
            }
            else
            {
                // Set shift verbiage based on department
                string pat, sup, inf, ant, post;
                if (Department == Department.NOR)
                {
                    pat  = "Table";
                    sup  = "out";
                    inf  = "in";
                    ant  = "down";
                    post = "up";
                }
                else
                {
                    pat  = "Patient";
                    sup  = "superior";
                    inf  = "inferior";
                    ant  = "anterior";
                    post = "posterior";
                }

                //x-axis
                if (shift.x > 0)
                {
                    ResultDetails += $"{pat} left: {shift.x:0.0} cm\n";
                }
                else if (shift.x < 0)
                {
                    ResultDetails += $"{pat} right: {-shift.x:0.0} cm\n";
                }

                //z-axis
                if (shift.z > 0)
                {
                    ResultDetails += $"{pat} {sup}: {shift.z:0.0} cm\n";
                }
                else if (shift.z < 0)
                {
                    ResultDetails += $"{pat} {inf}: {-shift.z:0.0} cm\n";
                }

                //y-axis
                if (shift.y > 0)
                {
                    ResultDetails += $"{pat} {post}: {shift.y:0.0} cm\n";
                }
                else if (shift.y < 0)
                {
                    ResultDetails += $"{pat}  {ant}: {-shift.y:0.0} cm\n";
                }


                //remove negatives
                ResultDetails.Replace("-", string.Empty);

                ResultDetails = $"Shifts from {shiftFrom}\n" + ResultDetails;
            }

            ResultDetails = ResultDetails.TrimEnd('\n');
        }
        /// <summary>
        /// Initializes a new instance of <see cref="MammographyImageSpatialTransform"/> with the specified image plane details.
        /// </summary>
        public MammographyImageSpatialTransform(IGraphic ownerGraphic, int rows, int columns, double pixelSpacingX, double pixelSpacingY, double pixelAspectRatioX, double pixelAspectRatioY, PatientOrientation patientOrientation, string laterality)
            : base(ownerGraphic, rows, columns, pixelSpacingX, pixelSpacingY, pixelAspectRatioX, pixelAspectRatioY)
        {
            // image coordinates are defined with X and Y being equivalent to the screen axes (right and down) and with Z = X cross Y (into the screen)

            Vector3D imagePosterior, imageHead, imageLeft;             // patient orientation vectors in image space

            GetPatientOrientationVectors(patientOrientation, out imageHead, out imageLeft, out imagePosterior);

            // save the posterior vector
            _imagePosterior = imagePosterior;

            if (imagePosterior != null)
            {
                Vector3D normativePosterior, normativeHead, normativeLeft;                 // normative patient orientation vectors in image space
                GetNormativeOrientationVectors(laterality, out normativeHead, out normativeLeft, out normativePosterior);

                // only do any adjustments if laterality implies a normative orientation for the posterior direction
                if (normativePosterior != null)
                {
                    // check if the order of the patient vectors are flipped according to the normative vectors
                    // we know we need to flip if the direction vector cross products have different signs
                    if (imageHead != null)
                    {
                        FlipX = Math.Sign(imagePosterior.Cross(imageHead).Z) != Math.Sign(normativePosterior.Cross(normativeHead).Z);
                    }
                    else if (imageLeft != null)
                    {
                        FlipX = Math.Sign(imagePosterior.Cross(imageLeft).Z) != Math.Sign(normativePosterior.Cross(normativeLeft).Z);
                    }

                    // with flip normalized, just rotate to align the current posterior direction with the normative posterior
                    var currentPosterior = ScreenPosterior;
                    var posteriorAngle   = Math.Atan2(currentPosterior.Y, currentPosterior.X);
                    var normativeAngle   = Math.Atan2(normativePosterior.Y, normativePosterior.X);

                    // compute required rotation, rounded to multiples of 90 degrees (PI/2 radians)
                    RotationXY = 90 * ((int)Math.Round((normativeAngle - posteriorAngle) * 2 / Math.PI));
                }
            }
        }
        //Check Image (Orientation, CT thickness, or missing CT, total number, etc.)
        List <PlanCheckResult> CheckImage(IonPlanSetup plan)
        {
            List <PlanCheckResult> planCheckResults = new List <PlanCheckResult>();


            Image image = plan.StructureSet.Image;

            //Orientation
            PatientOrientation patientOrientation = image.ImagingOrientation;

            if (patientOrientation == PatientOrientation.HeadFirstSupine)
            {
                PlanCheckResult planCheckResult = new PlanCheckResult();
                planCheckResult.Item        = "Patient Orientation";
                planCheckResult.Expected    = PatientOrientation.HeadFirstSupine.ToString();
                planCheckResult.CurrentPlan = patientOrientation.ToString();
                planCheckResult.Pass        = PlanCheckResult.CheckResult.Pass;
                planCheckResult.Comments    = "";
                planCheckResults.Add(planCheckResult);
            }
            else
            {
                PlanCheckResult planCheckResult = new PlanCheckResult();
                planCheckResult.Item        = "Patient Orientation";
                planCheckResult.Expected    = "";
                planCheckResult.CurrentPlan = patientOrientation.ToString();
                planCheckResult.Pass        = PlanCheckResult.CheckResult.Warning;
                planCheckResult.Comments    = "Patient is NOT HeadFirstSupine, please double check patient orientation.";
                planCheckResults.Add(planCheckResult);
            }


            //CT thickness
            double CTThickness        = image.ZRes;
            double defaultCTThickness = 2.5;

            if (CTThickness - defaultCTThickness < 1e-7)
            {
                PlanCheckResult planCheckResult = new PlanCheckResult();
                planCheckResult.Item        = "CT Thickness (mm)";
                planCheckResult.Expected    = "2.5";
                planCheckResult.CurrentPlan = CTThickness.ToString();
                planCheckResult.Pass        = PlanCheckResult.CheckResult.Pass;
                planCheckResult.Comments    = "";
                planCheckResults.Add(planCheckResult);
            }
            else
            {
                PlanCheckResult planCheckResult = new PlanCheckResult();
                planCheckResult.Item        = "CT Thickness (mm)";
                planCheckResult.Expected    = "2.5";
                planCheckResult.CurrentPlan = CTThickness.ToString("0.00");
                planCheckResult.Pass        = PlanCheckResult.CheckResult.Warning;
                planCheckResult.Comments    = "CT thickness is not same as default. Please check.";
                planCheckResults.Add(planCheckResult);
            }


            //CT total slices < 300
            double CTSlices = image.ZSize;
            double maxSlice = 300;

            if (CTSlices <= maxSlice)
            {
                PlanCheckResult planCheckResult = new PlanCheckResult();
                planCheckResult.Item        = "CT Slices";
                planCheckResult.Expected    = "<300";
                planCheckResult.CurrentPlan = CTSlices.ToString();
                planCheckResult.Pass        = PlanCheckResult.CheckResult.Pass;
                planCheckResult.Comments    = "";
                planCheckResults.Add(planCheckResult);
            }
            else
            {
                PlanCheckResult planCheckResult = new PlanCheckResult();
                planCheckResult.Item        = "CT Slices";
                planCheckResult.Expected    = "<300";
                planCheckResult.CurrentPlan = CTSlices.ToString();
                planCheckResult.Pass        = PlanCheckResult.CheckResult.Warning;
                planCheckResult.Comments    = "CT slice is more than 300";
                planCheckResults.Add(planCheckResult);
            }

            return(planCheckResults);
        }