/* Copyright (c) 2001-2015, David A. Clunie DBA Pixelmed Publishing. All rights reserved. */ package org.weasis.dicom.codec.geometry; import java.awt.geom.Point2D; import javax.vecmath.Point3d; import javax.vecmath.Tuple3d; import javax.vecmath.Vector3d; /** * <p> * A class to describe the spatial geometry of a single cross-sectional image slice. * </p> * * <p> * The 3D coordinate space used is the DICOM coordinate space, which is LPH+, that is, the x-axis is increasing to the * left hand side of the patient, the y-axis is increasing to the posterior side of the patient, and the z-axis is * increasing toward the head of the patient. * </p> * * @author dclunie */ public class GeometryOfSlice { protected double[] rowArray; protected Vector3d row; protected double[] columnArray; protected Vector3d column; protected Point3d tlhc; protected double[] tlhcArray; protected Tuple3d voxelSpacing; // row spacing (between centers of adjacent rows), then column spacing, then slice protected double[] voxelSpacingArray; protected double sliceThickness; protected Tuple3d dimensions; // number of rows, then number of columns, then number of slices protected Vector3d normal; protected double[] normalArray; /** * <p> * Construct the geometry. * </p> * * @param row * the direction of the row as X, Y and Z components (direction cosines, unit vector) LPH+ * @param column * the direction of the column as X, Y and Z components (direction cosines, unit vector) LPH+ * @param tlhc * the position of the top left hand corner of the slice as a point (X, Y and Z) LPH+ * @param voxelSpacing * the row and column spacing and, if a volume, the slice interval (spacing between the centers of * parallel slices) in mm * @param sliceThickness * the slice thickness in mm * @param dimensions * the row and column dimensions and 1 for the third dimension */ public GeometryOfSlice(Vector3d row, Vector3d column, Point3d tlhc, Tuple3d voxelSpacing, double sliceThickness, Tuple3d dimensions) { this.row = row; rowArray = new double[3]; row.get(rowArray); this.column = column; columnArray = new double[3]; column.get(columnArray); this.tlhc = tlhc; tlhcArray = new double[3]; tlhc.get(tlhcArray); this.voxelSpacing = voxelSpacing; voxelSpacingArray = new double[3]; voxelSpacing.get(voxelSpacingArray); this.sliceThickness = sliceThickness; this.dimensions = dimensions; makeNormal(); } /** * <p> * Construct the geometry. * </p> * * @param rowArray * the direction of the row as X, Y and Z components (direction cosines, unit vector) LPH+ * @param columnArray * the direction of the column as X, Y and Z components (direction cosines, unit vector) LPH+ * @param tlhcArray * the position of the top left hand corner of the slice as a point (X, Y and Z) LPH+ * @param voxelSpacingArray * the row and column spacing and, if a volume, the slice interval (spacing between the centers of * parallel slices) in mm * @param sliceThickness * the slice thickness in mm * @param dimensions * the row and column dimensions and 1 for the third dimension */ public GeometryOfSlice(double[] rowArray, double[] columnArray, double[] tlhcArray, double[] voxelSpacingArray, double sliceThickness, double[] dimensions) { this.rowArray = rowArray; this.row = new Vector3d(rowArray); this.columnArray = columnArray; this.column = new Vector3d(columnArray); this.tlhcArray = tlhcArray; this.tlhc = new Point3d(tlhcArray); this.voxelSpacingArray = voxelSpacingArray; this.voxelSpacing = new Vector3d(voxelSpacingArray); this.sliceThickness = sliceThickness; this.dimensions = new Vector3d(dimensions); makeNormal(); } protected final void makeNormal() { normal = new Vector3d(); normal.cross(row, column); normal.normalize(); normalArray = new double[3]; normal.get(normalArray); // depends of vector system (right/left-handed system): normalArray[2] = normalArray[2] * -1 normal = new Vector3d(normalArray); } /** * <p> * Get the row direction. * </p> * * @return the direction of the row as X, Y and Z components (direction cosines, unit vector) LPH+ */ public final Vector3d getRow() { return row; } /** * <p> * Get the row direction. * </p> * * @return the direction of the row as X, Y and Z components (direction cosines, unit vector) LPH+ */ public final double[] getRowArray() { return rowArray; } /** * <p> * Get the column direction. * </p> * * @return the direction of the column as X, Y and Z components (direction cosines, unit vector) LPH+ */ public final Vector3d getColumn() { return column; } /** * <p> * Get the column direction. * </p> * * @return the direction of the column as X, Y and Z components (direction cosines, unit vector) LPH+ */ public final double[] getColumnArray() { return columnArray; } /** * <p> * Get the normal direction. * </p> * * @return the direction of the normal to the plane of the slices, as X, Y and Z components (direction cosines, unit * vector) LPH+ */ public final Vector3d getNormal() { return normal; } /** * <p> * Get the normal direction. * </p> * * @return the direction of the normal to the plane of the slices, as X, Y and Z components (direction cosines, unit * vector) LPH+ */ public final double[] getNormalArray() { return normalArray; } /** * <p> * Get the position of the top left hand corner. * </p> * * @return the position of the top left hand corner of the slice as a point (X, Y and Z) LPH+ */ public final Point3d getTLHC() { return tlhc; } public final Point3d getPosition(Point2D p) { return new Point3d( row.x * voxelSpacing.x * p.getX() + column.x * voxelSpacing.y * p.getY() + tlhc.x, row.y * voxelSpacing.x * p.getX() + column.y * voxelSpacing.y * p.getY() + tlhc.y, row.z * voxelSpacing.x * p.getX() + column.z * voxelSpacing.y * p.getY() + tlhc.z); } public final Point2D getImagePosition(Point3d p3) { if (voxelSpacing.x < 0.00001 || voxelSpacing.y < 0.00001) { return null; } double ix = ((p3.x - tlhc.x) * row.x + (p3.y - tlhc.y) * row.y + (p3.z - tlhc.z) * row.z) / voxelSpacing.x; double iy = ((p3.x - tlhc.x) * column.x + (p3.y - tlhc.y) * column.y + (p3.z - tlhc.z) * column.z) / voxelSpacing.y; return new Point2D.Double(ix, iy); } /** * <p> * Get the position of the top left hand corner. * </p> * * @return the position of the top left hand corner of the slice as a point (X, Y and Z) LPH+ */ public final double[] getTLHCArray() { return tlhcArray; } /** * <p> * Get the spacing between centers of the voxel in three dimension. * </p> * * @return the row and column spacing and, if a volume, the slice interval (spacing between the centers of parallel * slices) in mm */ public final Tuple3d getVoxelSpacing() { return voxelSpacing; } /** * <p> * Get the spacing between centers of the voxel in three dimension. * </p> * * @return the row and column spacing and, if a volume, the slice interval (spacing between the centers of parallel * slices) in mm */ public final double[] getVoxelSpacingArray() { return voxelSpacingArray; } /** * <p> * Get the spacing between centers of the voxel in three dimension. * </p> * * @return the slice thickness in mm */ public final double getSliceThickness() { return sliceThickness; } /** * <p> * Get the dimensions of the voxel. * </p> * * @return the row and column dimensions and 1 for the third dimension */ public final Tuple3d getDimensions() { return dimensions; } /** * <p> * Get the letter representation of the orientation of a vector. * </p> * * <p> * For bipeds, L or R, A or P, H or F. * </p> * * <p> * For quadrupeds, Le or Rt, V or D, Cr or Cd (with lower case; use toUpperCase() to produce valid CodeString for * PatientOrientation). * </p> * * @param orientation * the orientation * @param quadruped * true if subject is a quadruped rather than a biped * @return a string rendering of the orientation, more than one letter if oblique to the orthogonal axes, or empty * string (not null) if fails */ public static final String getOrientation(double[] orientation, boolean quadruped) { StringBuilder strbuf = new StringBuilder(); if (orientation != null && orientation.length == 3) { String orientationX = orientation[0] < 0 ? (quadruped ? "Rt" : "R") : (quadruped ? "Le" : "L"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ String orientationY = orientation[1] < 0 ? (quadruped ? "V" : "A") : (quadruped ? "D" : "P"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ String orientationZ = orientation[2] < 0 ? (quadruped ? "Cd" : "F") : (quadruped ? "Cr" : "H"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ double absX = Math.abs(orientation[0]); double absY = Math.abs(orientation[1]); double absZ = Math.abs(orientation[2]); for (int i = 0; i < 3; ++i) { if (absX > 0.0001 && absX > absY && absX > absZ) { strbuf.append(orientationX); absX = 0; } else if (absY > 0.0001 && absY > absX && absY > absZ) { strbuf.append(orientationY); absY = 0; } else if (absZ > 0.0001 && absZ > absX && absZ > absY) { strbuf.append(orientationZ); absZ = 0; } } } return strbuf.toString(); } /** * <p> * Get the letter representation of the orientation of a vector. * </p> * * <p> * Assumes a biped rather than a quadruped, so returns L or R, A or P, H or F. * </p> * * @param orientation * the orientation * @return a string rendering of the orientation, more than one letter if oblique to the orthogonal axes, or empty * string (not null) if fails */ public static final String getOrientation(double[] orientation) { return getOrientation(orientation, false); } /** * <p> * Get the letter representation of the orientation of the rows of this slice. * </p> * * <p> * For bipeds, L or R, A or P, H or F. * </p> * * <p> * For quadrupeds, Le or Rt, V or D, Cr or Cd (with lower case; use toUpperCase() to produce valid CodeString for * PatientOrientation). * </p> * * @param quadruped * true if subject is a quadruped rather than a biped * @return a string rendering of the row orientation, more than one letter if oblique to the orthogonal axes, or * empty string (not null) if fails */ public final String getRowOrientation(boolean quadruped) { return getOrientation(rowArray, quadruped); } /** * <p> * Get the letter representation of the orientation of the columns of this slice. * </p> * * <p> * For bipeds, L or R, A or P, H or F. * </p> * * <p> * For quadrupeds, Le or Rt, V or D, Cr or Cd (with lower case; use toUpperCase() to produce valid CodeString for * PatientOrientation). * </p> * * @param quadruped * true if subject is a quadruped rather than a biped * @return a string rendering of the column orientation, more than one letter if oblique to the orthogonal axes, or * empty string (not null) if fails */ public final String getColumnOrientation(boolean quadruped) { return getOrientation(columnArray, quadruped); } /** * <p> * Get the letter representation of the orientation of the rows of this slice. * </p> * * <p> * Assumes a biped rather than a quadruped, so returns L or R, A or P, H or F. * </p> * * @return a string rendering of the row orientation, more than one letter if oblique to the orthogonal axes, or * empty string (not null) if fails */ public final String getRowOrientation() { return getRowOrientation(false); } /** * <p> * Get the letter representation of the orientation of the columns of this slice. * </p> * * <p> * Assumes a biped rather than a quadruped, so returns L or R, A or P, H or F. * </p> * * @return a string rendering of the column orientation, more than one letter if oblique to the orthogonal axes, or * empty string (not null) if fails */ public final String getColumnOrientation() { return getColumnOrientation(false); } }