package org.weasis.dicom.codec.geometry;
import org.dcm4che3.data.Tag;
import org.weasis.core.api.media.data.MediaSeries;
import org.weasis.core.api.media.data.MediaSeries.MEDIA_POSITION;
import org.weasis.dicom.codec.DicomImageElement;
import org.weasis.dicom.codec.TagD;
/**
* <p>
* A class of static methods to provide descriptions of images, including image orientation relative to the patient from
* the mathematical position and orientation attributes, and including other descriptive attributes such as from dicom
* directory records and images using multi-frame functional groups.
*
* C.7.6.1.1.1 Patient Orientation. The Patient Orientation (0020,0020) relative to the image plane shall be specified
* by two values that designate the anatomical direction of the positive row axis (left to right) and the positive
* column axis (top to bottom). The first entry is the direction of the rows, given by the direction of the last pixel
* in the first row from the first pixel in that row. The second entry is the direction of the columns, given by the
* direction of the last pixel in the first column from the first pixel in that column. Anatomical direction shall be
* designated by the capital letters: A (anterior), P (posterior), R (right), L (left), H (head), F (foot). Each value
* of the orientation attribute shall contain at least one of these characters. If refinements in the orientation
* descriptions are to be specified, then they shall be designated by one or two additional letters in each value.
* Within each value, the letters shall be ordered with the principal orientation designated in the first character.
*
* C.7.6.2.1.1 Image Position And Image Orientation. The Image Position (0020,0032) specifies the x, y, and z
* coordinates of the upper left hand corner of the image; it is the center of the first voxel transmitted. Image
* Orientation (0020,0037) specifies the direction cosines of the first row and the first column with respect to the
* patient. These Attributes shall be provide as a pair. Row value for the x, y, and z axes respectively followed by the
* Column value for the x, y, and z axes respectively. The direction of the axes is defined fully by the patient's
* orientation. 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. The z-axis is increasing toward the head of the patient. The patient based coordinate system is
* a right handed system, i.e. the vector cross product of a unit vector along the positive x-axis and a unit vector
* along the positive y-axis is equal to a unit vector along the positive z-axis.
* </p>
*
* @author dclunie
*/
public abstract class ImageOrientation {
public enum Label {
UNKNOWN, AXIAL, SAGITTAL, CORONAL, OBLIQUE
}
public static final String DIR_R = "R"; //$NON-NLS-1$
public static final String DIR_L = "L"; //$NON-NLS-1$
public static final String DIR_A = "A"; //$NON-NLS-1$
public static final String DIR_P = "P"; //$NON-NLS-1$
public static final String DIR_F = "F"; //$NON-NLS-1$
public static final String DIR_H = "H"; //$NON-NLS-1$
private static final double OBLIQUITY_THRESHOLD = 0.8;
/**
* <p>
* Get a label describing the major axis from a unit vector (direction cosine) as found in ImageOrientationPatient.
* </p>
*
* <p>
* Some degree of deviation from one of the standard orthogonal axes is allowed before deciding no major axis
* applies and returning null.
* </p>
*
* @param x
* @param y
* @param z
* @return the string describing the orientation of the vector, or null if oblique
*/
public static final String getMajorAxisFromPatientRelativeDirectionCosine(double x, double y, double z) {
String axis = null;
String orientationX = x < 0 ? DIR_R : DIR_L;
String orientationY = y < 0 ? DIR_A : DIR_P;
String orientationZ = z < 0 ? DIR_F : DIR_H;
double absX = Math.abs(x);
double absY = Math.abs(y);
double absZ = Math.abs(z);
// The tests here really don't need to check the other dimensions,
// just the threshold, since the sum of the squares should be == 1.0
// but just in case ...
if (absX > OBLIQUITY_THRESHOLD && absX > absY && absX > absZ) {
axis = orientationX;
} else if (absY > OBLIQUITY_THRESHOLD && absY > absX && absY > absZ) {
axis = orientationY;
} else if (absZ > OBLIQUITY_THRESHOLD && absZ > absX && absZ > absY) {
axis = orientationZ;
}
return axis;
}
/**
* <p>
* Get a label describing the axial, coronal or sagittal plane from row and column unit vectors (direction cosines)
* as found in ImageOrientationPatient.
* </p>
*
* <p>
* Some degree of deviation from one of the standard orthogonal planes is allowed before deciding the plane is
* OBLIQUE.
* </p>
*
* @param rowX
* @param rowY
* @param rowZ
* @param colX
* @param colY
* @param colZ
* @return the string describing the plane of orientation, AXIAL, CORONAL, SAGITTAL or OBLIQUE
*/
public static final Label makeImageOrientationLabelFromImageOrientationPatient(double rowX, double rowY,
double rowZ, double colX, double colY, double colZ) {
String rowAxis = getMajorAxisFromPatientRelativeDirectionCosine(rowX, rowY, rowZ);
String colAxis = getMajorAxisFromPatientRelativeDirectionCosine(colX, colY, colZ);
if (rowAxis != null && colAxis != null) {
if ((rowAxis.equals(DIR_R) || rowAxis.equals(DIR_L)) && (colAxis.equals(DIR_A) || colAxis.equals(DIR_P))) {
return Label.AXIAL;
} else if ((colAxis.equals(DIR_R) || colAxis.equals(DIR_L))
&& (rowAxis.equals(DIR_A) || rowAxis.equals(DIR_P))) {
return Label.AXIAL;
} else if ((rowAxis.equals(DIR_R) || rowAxis.equals(DIR_L))
&& (colAxis.equals(DIR_H) || colAxis.equals(DIR_F))) {
return Label.CORONAL;
} else if ((colAxis.equals(DIR_R) || colAxis.equals(DIR_L))
&& (rowAxis.equals(DIR_H) || rowAxis.equals(DIR_F))) {
return Label.CORONAL;
} else if ((rowAxis.equals(DIR_A) || rowAxis.equals(DIR_P))
&& (colAxis.equals(DIR_H) || colAxis.equals(DIR_F))) {
return Label.SAGITTAL;
} else if ((colAxis.equals(DIR_A) || colAxis.equals(DIR_P))
&& (rowAxis.equals(DIR_H) || rowAxis.equals(DIR_F))) {
return Label.SAGITTAL;
}
}
return Label.OBLIQUE;
}
public static final Label makeImageOrientationLabelFromImageOrientationPatient(double[] v) {
if (v == null || v.length < 6) {
return null;
}
return ImageOrientation.makeImageOrientationLabelFromImageOrientationPatient(v[0], v[1], v[2], v[3], v[4],
v[5]);
}
/**
* <p>
* Get a PatientOrientation style string from a unit vector (direction cosine) as found in ImageOrientationPatient.
* </p>
*
* <p>
* Returns letters representing R (right) or L (left), A (anterior) or P (posterior), F (feet) or H (head).
* </p>
*
* <p>
* If the orientation is not precisely orthogonal to one of the major axes, more than one letter is returned, from
* major to minor axes, with up to three letters in the case of a "double oblique".
* </p>
*
* @param x
* @param y
* @param z
* @return the string describing the orientation of the vector
*/
public static final String makePatientOrientationFromPatientRelativeDirectionCosine(double x, double y, double z) {
StringBuilder buffer = new StringBuilder();
String orientationX = x < 0 ? DIR_R : DIR_L;
String orientationY = y < 0 ? DIR_A : DIR_P;
String orientationZ = z < 0 ? DIR_F : DIR_H;
double absX = Math.abs(x);
double absY = Math.abs(y);
double absZ = Math.abs(z);
for (int i = 0; i < 3; ++i) {
if (absX > .0001 && absX >= absY && absX >= absZ) {
buffer.append(orientationX);
absX = 0;
} else if (absY > .0001 && absY >= absX && absY >= absZ) {
buffer.append(orientationY);
absY = 0;
} else if (absZ > .0001 && absZ >= absX && absZ >= absY) {
buffer.append(orientationZ);
absZ = 0;
} else {
break;
}
}
return buffer.toString();
}
/**
* <p>
* Get a PatientOrientation style string from row and column unit vectors (direction cosines) as found in
* ImageOrientationPatient.
* </p>
*
* <p>
* Returns letters representing R (right) or L (left), A (anterior) or P (posterior), F (feet) or H (head).
* </p>
*
* <p>
* If the orientation is not precisely orthogonal to one of the major axes, more than one letter is returned, from
* major to minor axes, with up to three letters in the case of a "double oblique".
* </p>
*
* <p>
* The row and column letters returned are separated by the usual DICOM string delimiter, a backslash.
* </p>
*
* @param rowX
* @param rowY
* @param rowZ
* @param colX
* @param colY
* @param colZ
* @return the string describing the row and then the column
*/
public static final String makePatientOrientationFromImageOrientationPatient(double rowX, double rowY, double rowZ,
double colX, double colY, double colZ) {
return makePatientOrientationFromPatientRelativeDirectionCosine(rowX, rowY, rowZ) + "\\" //$NON-NLS-1$
+ makePatientOrientationFromPatientRelativeDirectionCosine(colX, colY, colZ);
}
public static double[] computeNormalVectorOfPlan(double[] vector) {
if (vector != null && vector.length == 6) {
double[] norm = new double[3];
norm[0] = vector[1] * vector[5] - vector[2] * vector[4];
norm[1] = vector[2] * vector[3] - vector[0] * vector[5];
norm[2] = vector[0] * vector[4] - vector[1] * vector[3];
return norm;
}
return null;
}
public static boolean hasSameOrientation(MediaSeries<DicomImageElement> series1,
MediaSeries<DicomImageElement> series2) {
// Test if the two series have the same orientation
if (series1 != null && series2 != null) {
DicomImageElement image1 = series1.getMedia(MEDIA_POSITION.MIDDLE, null, null);
DicomImageElement image2 = series2.getMedia(MEDIA_POSITION.MIDDLE, null, null);
return hasSameOrientation(image1, image2);
}
return false;
}
public static boolean hasSameOrientation(DicomImageElement image1, DicomImageElement image2) {
// Test if the two images have the same orientation
if (image1 != null && image2 != null) {
double[] v1 = TagD.getTagValue(image1, Tag.ImageOrientationPatient, double[].class);
double[] v2 = TagD.getTagValue(image2, Tag.ImageOrientationPatient, double[].class);
if (v1 != null && v1.length == 6 && v2 != null && v2.length == 6) {
Label label1 = ImageOrientation.makeImageOrientationLabelFromImageOrientationPatient(v1[0], v1[1],
v1[2], v1[3], v1[4], v1[5]);
Label label2 = ImageOrientation.makeImageOrientationLabelFromImageOrientationPatient(v2[0], v2[1],
v2[2], v2[3], v2[4], v2[5]);
if (label1 != null && !label1.equals(Label.OBLIQUE)) {
return label1.equals(label2);
}
// If oblique search if the plan has approximately the same orientation
double[] postion1 = computeNormalVectorOfPlan(v1);
double[] postion2 = computeNormalVectorOfPlan(v2);
if (postion1 != null && postion2 != null) {
double prod = postion1[0] * postion2[0] + postion1[1] * postion2[1] + postion1[2] * postion2[2];
// A little tolerance
if (prod > 0.95) {
return true;
}
}
}
}
return false;
}
}