/// <summary> /// Allows specification of the slice plane, through point, and extent via two points in patient space /// </summary> public static VolumeSlicerParams Create(IVolumeHeader volume, Vector3D sourceOrientationColumnPatient, Vector3D sourceOrientationRowPatient, Vector3D startPointPatient, Vector3D endPointPatient) { Vector3D sourceOrientationNormalPatient = sourceOrientationColumnPatient.Cross(sourceOrientationRowPatient); Vector3D normalLinePatient = (endPointPatient - startPointPatient).Normalize(); Vector3D normalPerpLinePatient = sourceOrientationNormalPatient.Cross(normalLinePatient); Vector3D slicePlanePatientX = normalLinePatient; Vector3D slicePlanePatientY = sourceOrientationNormalPatient; Vector3D slicePlanePatientZ = normalPerpLinePatient; Matrix slicePlanePatientOrientation = Math3D.OrientationMatrixFromVectors(slicePlanePatientX, slicePlanePatientY, slicePlanePatientZ); Matrix _resliceAxes = volume.RotateToVolumeOrientation(slicePlanePatientOrientation); Vector3D lineMiddlePointPatient = new Vector3D( (startPointPatient.X + endPointPatient.X) / 2, (startPointPatient.Y + endPointPatient.Y) / 2, (startPointPatient.Z + endPointPatient.Z) / 2); VolumeSlicerParams slicerParams = new VolumeSlicerParams(_resliceAxes); slicerParams.SliceThroughPointPatient = new Vector3D(lineMiddlePointPatient); slicerParams.SliceExtentXMillimeters = (endPointPatient - startPointPatient).Magnitude; return(slicerParams); }
private static float GetIdealSliceSpacing(IVolumeHeader volumeHeader, Vector3D unitSpacingAxis) { // the ideal spacing is simply the diagonal of the voxel projected on to the spacing axis // any larger than this value, and it becomes possible for an entire voxel to fit in between two consecutive output locations (i.e. missed for interpolation) // any smaller than this value, and some voxels will have two or more output locations within their bounds return(Math.Abs(unitSpacingAxis.Dot(volumeHeader.RotateToPatientOrientation(volumeHeader.VoxelSpacing)))); }
private static float GetIdealSliceSpacing(IVolumeHeader volumeHeader, Vector3D unitSpacingAxis) { // the ideal spacing is simply the diagonal of the voxel projected on to the spacing axis // any larger than this value, and it becomes possible for an entire voxel to fit in between two consecutive output locations (i.e. missed for interpolation) // any smaller than this value, and some voxels will have two or more output locations within their bounds // note: voxel actually has 4 possible diagonals depending on the orientation, so we do this calculation for all diagonals and take the largest one var p = volumeHeader.RotateToPatientOrientation(volumeHeader.VoxelSpacing); return(new[] { p, new Vector3D(-p.X, p.Y, p.Z), new Vector3D(p.X, -p.Y, p.Z), new Vector3D(-p.X, -p.Y, p.Z) } .Select(v => Math.Abs(unitSpacingAxis.Dot(v))).Max()); }
/// <summary> /// The effective spacing defines output pixel spacing for slices generated by the VolumeSlicer. /// </summary> private static float GetEffectiveSpacing(IVolumeHeader volume) { // Because we supply the real spacing to the VTK reslicer, the slices are interpolated // as if the volume were isotropic. This results in an effective spacing that is the // minimum spacing for the volume. // // N.B.: this behaviour is different than if had asked VTK to render it, because we are // asking directly for pixel data out. If VTK renders it, then we scrape the rendering // for the pixel data, the spacing would be exactly 1mm because the interpolation would // happened during rendering. return(volume.GetMinimumSpacing()); }
private SizeF GetPixelSpacing(IVolumeHeader volumeHeader) { var columnHasValue = ColumnSpacing.HasValue; var rowHasValue = RowSpacing.HasValue; if (!columnHasValue && !rowHasValue) { var spacing = GetMinimumComponent(volumeHeader.VoxelSpacing); return(new SizeF(spacing, spacing)); } else if (columnHasValue && rowHasValue) { return(new SizeF(ColumnSpacing.Value, RowSpacing.Value)); } else { var spacing = columnHasValue ? ColumnSpacing.Value : RowSpacing.Value; return(new SizeF(spacing, spacing)); } }
// Derived from either a specified extent in millimeters or from the volume dimensions (default) private static Size GetSliceExtent(IVolumeHeader volume, IVolumeSlicerParams slicerParams) { var effectiveSpacing = GetEffectiveSpacing(volume); var longOutputDimension = volume.GetLongAxisMagnitude() / effectiveSpacing; var shortOutputDimenstion = volume.GetShortAxisMagnitude() / effectiveSpacing; var diagonalDimension = (int)Math.Sqrt(longOutputDimension * longOutputDimension + shortOutputDimenstion * shortOutputDimenstion); var columns = diagonalDimension; if (!FloatComparer.AreEqual(slicerParams.SliceExtentXMillimeters, 0f)) { columns = (int)(slicerParams.SliceExtentXMillimeters / effectiveSpacing + 0.5f); } var rows = diagonalDimension; if (!FloatComparer.AreEqual(slicerParams.SliceExtentYMillimeters, 0f)) { rows = (int)(slicerParams.SliceExtentYMillimeters / effectiveSpacing + 0.5f); } return(new Size(columns, rows)); }
// VTK treats the reslice point as the center of the output image. Given the plane orientation // and size of the output image, we can derive the top left of the output image in patient space private static Vector3D GetTopLeftOfSlicePatient(Size frameSize, Vector3D throughPoint, IVolumeHeader volume, IVolumeSlicerParams slicerParams) { // This is the center of the output image var centerImageCoord = new PointF(frameSize.Width / 2f, frameSize.Height / 2f); // These offsets define the x and y vector magnitudes to arrive at our point var effectiveSpacing = GetEffectiveSpacing(volume); var offsetX = centerImageCoord.X * effectiveSpacing; var offsetY = centerImageCoord.Y * effectiveSpacing; // To determine top left of slice in volume, subtract offset vectors along x and y // // Our reslice plane x and y vectors var resliceAxes = slicerParams.SlicingPlaneRotation; var xVec = new Vector3D(resliceAxes[0, 0], resliceAxes[0, 1], resliceAxes[0, 2]); var yVec = new Vector3D(resliceAxes[1, 0], resliceAxes[1, 1], resliceAxes[1, 2]); // Offset along x and y from reslicePoint var topLeftOfSliceVolume = throughPoint - (offsetX * xVec + offsetY * yVec); // Convert volume point to patient space return(volume.ConvertToPatient(topLeftOfSliceVolume)); }
private static float GetIdealSliceSpacing(IVolumeHeader volumeHeader, Vector3D unitSpacingAxis) { // the ideal spacing is simply the diagonal of the voxel projected on to the spacing axis // any larger than this value, and it becomes possible for an entire voxel to fit in between two consecutive output locations (i.e. missed for interpolation) // any smaller than this value, and some voxels will have two or more output locations within their bounds // note: voxel actually has 4 possible diagonals depending on the orientation, so we do this calculation for all diagonals and take the largest one var p = volumeHeader.RotateToPatientOrientation(volumeHeader.VoxelSpacing); return new[] {p, new Vector3D(-p.X, p.Y, p.Z), new Vector3D(p.X, -p.Y, p.Z), new Vector3D(-p.X, -p.Y, p.Z)} .Select(v => Math.Abs(unitSpacingAxis.Dot(v))).Max(); }
public static float GetLongAxisMagnitude(this IVolumeHeader volume) { return(volume.VolumeSize.Max()); }
private static float GetIdealSliceSpacing(IVolumeHeader volumeHeader, Vector3D unitSpacingAxis) { // the ideal spacing is simply the diagonal of the voxel projected on to the spacing axis // any larger than this value, and it becomes possible for an entire voxel to fit in between two consecutive output locations (i.e. missed for interpolation) // any smaller than this value, and some voxels will have two or more output locations within their bounds return Math.Abs(unitSpacingAxis.Dot(volumeHeader.RotateToPatientOrientation(volumeHeader.VoxelSpacing))); }
/// <summary> /// The effective spacing defines output pixel spacing for slices generated by the VolumeSlicer. /// </summary> private static float GetEffectiveSpacing(IVolumeHeader volume) { // Because we supply the real spacing to the VTK reslicer, the slices are interpolated // as if the volume were isotropic. This results in an effective spacing that is the // minimum spacing for the volume. // // N.B.: this behaviour is different than if had asked VTK to render it, because we are // asking directly for pixel data out. If VTK renders it, then we scrape the rendering // for the pixel data, the spacing would be exactly 1mm because the interpolation would // happened during rendering. return volume.GetMinimumSpacing(); }
// Derived from either a specified extent in millimeters or from the volume dimensions (default) private static Size GetSliceExtent(IVolumeHeader volume, IVolumeSlicerParams slicerParams) { var effectiveSpacing = GetEffectiveSpacing(volume); var longOutputDimension = volume.GetLongAxisMagnitude()/effectiveSpacing; var shortOutputDimenstion = volume.GetShortAxisMagnitude()/effectiveSpacing; var diagonalDimension = (int) Math.Sqrt(longOutputDimension*longOutputDimension + shortOutputDimenstion*shortOutputDimenstion); var columns = diagonalDimension; if (!FloatComparer.AreEqual(slicerParams.SliceExtentXMillimeters, 0f)) columns = (int) (slicerParams.SliceExtentXMillimeters/effectiveSpacing + 0.5f); var rows = diagonalDimension; if (!FloatComparer.AreEqual(slicerParams.SliceExtentYMillimeters, 0f)) rows = (int) (slicerParams.SliceExtentYMillimeters/effectiveSpacing + 0.5f); return new Size(columns, rows); }
// VTK treats the reslice point as the center of the output image. Given the plane orientation // and size of the output image, we can derive the top left of the output image in patient space private static Vector3D GetTopLeftOfSlicePatient(Size frameSize, Vector3D throughPoint, IVolumeHeader volume, IVolumeSlicerParams slicerParams) { // This is the center of the output image var centerImageCoord = new PointF(frameSize.Width/2f, frameSize.Height/2f); // These offsets define the x and y vector magnitudes to arrive at our point var effectiveSpacing = GetEffectiveSpacing(volume); var offsetX = centerImageCoord.X*effectiveSpacing; var offsetY = centerImageCoord.Y*effectiveSpacing; // To determine top left of slice in volume, subtract offset vectors along x and y // // Our reslice plane x and y vectors var resliceAxes = slicerParams.SlicingPlaneRotation; var xVec = new Vector3D(resliceAxes[0, 0], resliceAxes[0, 1], resliceAxes[0, 2]); var yVec = new Vector3D(resliceAxes[1, 0], resliceAxes[1, 1], resliceAxes[1, 2]); // Offset along x and y from reslicePoint var topLeftOfSliceVolume = throughPoint - (offsetX*xVec + offsetY*yVec); // Convert volume point to patient space return volume.ConvertToPatient(topLeftOfSliceVolume); }
public static float GetShortAxisMagnitude(this IVolumeHeader volume) { return(volume.VolumeSize.Min()); }
private float GetSliceSpacing(IVolumeHeader volumeHeader, Vector3D slicerAxisZ) { return(SliceSpacing ?? (SliceThickness.HasValue ? SliceThickness.Value : GetIdealSliceSpacing(volumeHeader, slicerAxisZ))); }
private float GetSliceSpacing(IVolumeHeader volumeHeader, Vector3D slicerAxisZ) { return SliceSpacing ?? (SliceThickness.HasValue ? SliceThickness.Value : GetIdealSliceSpacing(volumeHeader, slicerAxisZ)); }
private SizeF GetPixelSpacing(IVolumeHeader volumeHeader) { var columnHasValue = ColumnSpacing.HasValue; var rowHasValue = RowSpacing.HasValue; if (!columnHasValue && !rowHasValue) { var spacing = GetMinimumComponent(volumeHeader.VoxelSpacing); return new SizeF(spacing, spacing); } else if (columnHasValue && rowHasValue) { return new SizeF(ColumnSpacing.Value, RowSpacing.Value); } else { var spacing = columnHasValue ? ColumnSpacing.Value : RowSpacing.Value; return new SizeF(spacing, spacing); } }
/// <summary> /// Allows specification of the slice plane, through point, and extent via two points in patient space /// </summary> public static VolumeSlicerParams Create(IVolumeHeader volume, Vector3D sourceOrientationColumnPatient, Vector3D sourceOrientationRowPatient, Vector3D startPointPatient, Vector3D endPointPatient) { Vector3D sourceOrientationNormalPatient = sourceOrientationColumnPatient.Cross(sourceOrientationRowPatient); Vector3D normalLinePatient = (endPointPatient - startPointPatient).Normalize(); Vector3D normalPerpLinePatient = sourceOrientationNormalPatient.Cross(normalLinePatient); Vector3D slicePlanePatientX = normalLinePatient; Vector3D slicePlanePatientY = sourceOrientationNormalPatient; Vector3D slicePlanePatientZ = normalPerpLinePatient; Matrix slicePlanePatientOrientation = Math3D.OrientationMatrixFromVectors(slicePlanePatientX, slicePlanePatientY, slicePlanePatientZ); Matrix _resliceAxes = volume.RotateToVolumeOrientation(slicePlanePatientOrientation); Vector3D lineMiddlePointPatient = new Vector3D( (startPointPatient.X + endPointPatient.X)/2, (startPointPatient.Y + endPointPatient.Y)/2, (startPointPatient.Z + endPointPatient.Z)/2); VolumeSlicerParams slicerParams = new VolumeSlicerParams(_resliceAxes); slicerParams.SliceThroughPointPatient = new Vector3D(lineMiddlePointPatient); slicerParams.SliceExtentXMillimeters = (endPointPatient - startPointPatient).Magnitude; return slicerParams; }
public static float GetMaximumSpacing(this IVolumeHeader volume) { return(volume.VoxelSpacing.Max()); }