Skip to content

Commit

Permalink
Merge branch 'rimadoma-issue1' into testing
Browse files Browse the repository at this point in the history
Conflicts:
	src/org/doube/util/ImageCheck.java
  • Loading branch information
mdoube committed Dec 14, 2015
2 parents 5a06670 + a4a82d3 commit 406d182
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 65 deletions.
147 changes: 82 additions & 65 deletions src/org/doube/util/ImageCheck.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

/**
* Check if an image conforms to the type defined by each method.
*
*
* @author Michael Doube
*
*
*/
public class ImageCheck {

Expand All @@ -24,37 +24,37 @@ public class ImageCheck {
* are not included.
*/
public static final String[] blacklistedIJVersions = {
// introduced bug where ROIs added to the ROI Manager
// lost their z-position information
"1.48a" };
// introduced bug where ROIs added to the ROI Manager
// lost their z-position information
"1.48a" };

/**
* Check if image is binary
*
*
* @param imp
* @return true if image is binary
*/
public boolean isBinary(final ImagePlus imp) {
public static boolean isBinary(ImagePlus imp) {
if (imp == null) {
IJ.noImage();
return false;
}
if (imp.getType() != ImagePlus.GRAY8)
return false;

final ImageStatistics stats = imp.getStatistics();
ImageStatistics stats = imp.getStatistics();
if (stats.histogram[0] + stats.histogram[255] != stats.pixelCount)
return false;
return true;
}

/**
* Check if an image is a multi-slice image stack
*
*
* @param imp
* @return true if the image has >= 2 slices
*/
public boolean isMultiSlice(final ImagePlus imp) {
public static boolean isMultiSlice(ImagePlus imp) {
if (imp == null) {
IJ.noImage();
return false;
Expand All @@ -68,57 +68,61 @@ public boolean isMultiSlice(final ImagePlus imp) {
/**
* Check if the image's voxels are isotropic in all 3 dimensions (i.e. are
* placed on a cubic grid)
*
*
* @param imp
* image to test
* @param tolerance
* tolerated fractional deviation from equal length
* @return true if voxel width == height == depth
*/
public boolean isVoxelIsotropic(final ImagePlus imp, final double tolerance) {
public static boolean isVoxelIsotropic(ImagePlus imp, double tolerance) {
if (imp == null) {
IJ.noImage();
IJ.error("No image", "Image is null");
return false;
}
final Calibration cal = imp.getCalibration();
Calibration cal = imp.getCalibration();
final double vW = cal.pixelWidth;
final double vH = cal.pixelHeight;
final double vD = cal.pixelDepth;
final double tLow = 1 - tolerance;
final double tHigh = 1 + tolerance;
final double widthHeightRatio = vW > vH ? vW / vH : vH / vW;
final boolean isStack = (imp.getStackSize() > 1);

if (vW < vH * tLow || vW > vH * tHigh)
return false;
if ((vW < vD * tLow || vW > vD * tHigh) && isStack)
return false;
if ((vH < vD * tLow || vH > vD * tHigh) && isStack)
if (widthHeightRatio < tLow || widthHeightRatio > tHigh) {
return false;
}

return true;
if(!isStack) {
return true;
}

final double vD = cal.pixelDepth;
final double widthDepthRatio = vW > vD ? vW / vD : vD / vW;

return (widthDepthRatio >= tLow && widthDepthRatio <= tHigh);
}

/**
* Run isVoxelIsotropic() with a default tolerance of 0%
*
*
* @param imp
* input image
* @return false if voxel dimensions are not equal
*/
public boolean isVoxelIsotropic(final ImagePlus imp) {
public static boolean isVoxelIsotropic(ImagePlus imp) {
return isVoxelIsotropic(imp, 0);
}

/**
* Check that the voxel thickness is correct
*
*
* @param imp
* @return voxel thickness based on DICOM header information. Returns -1 if
* there is no DICOM slice position information.
*/
public double dicomVoxelDepth(final ImagePlus imp) {
final Calibration cal = imp.getCalibration();
final double vD = cal.pixelDepth;
public static double dicomVoxelDepth(ImagePlus imp) {
Calibration cal = imp.getCalibration();
double vD = cal.pixelDepth;

String position = getDicomAttribute(imp, 1, "0020,0032");
if (position == null) {
Expand All @@ -140,17 +144,19 @@ public double dicomVoxelDepth(final ImagePlus imp) {
else
return -1;

final double sliceSpacing = Math.abs((last - first) / (imp.getStackSize() - 1));
double sliceSpacing = Math.abs((last - first)
/ (imp.getStackSize() - 1));

final String units = cal.getUnits();
String units = cal.getUnits();

final double error = Math.abs((sliceSpacing - vD) / sliceSpacing) * 100;
double error = Math.abs((sliceSpacing - vD) / sliceSpacing) * 100;

if (vD != sliceSpacing) {
IJ.log(imp.getTitle() + ":\n" + "Current voxel depth disagrees by " + error
+ "% with DICOM header slice spacing.\n" + "Current voxel depth: " + IJ.d2s(vD, 6) + " " + units
+ "\n" + "DICOM slice spacing: " + IJ.d2s(sliceSpacing, 6) + " " + units + "\n"
+ "Updating image properties...");
IJ.log(imp.getTitle() + ":\n" + "Current voxel depth disagrees by "
+ error + "% with DICOM header slice spacing.\n"
+ "Current voxel depth: " + IJ.d2s(vD, 6) + " " + units
+ "\n" + "DICOM slice spacing: " + IJ.d2s(sliceSpacing, 6)
+ " " + units + "\n" + "Updating image properties...");
cal.pixelDepth = sliceSpacing;
imp.setCalibration(cal);
} else
Expand All @@ -160,16 +166,16 @@ public double dicomVoxelDepth(final ImagePlus imp) {

/**
* Get the value associated with a DICOM tag from an ImagePlus header
*
*
* @param imp
* @param slice
* @param tag
* , in 0000,0000 format.
* @return the value associated with the tag
*/
private String getDicomAttribute(final ImagePlus imp, final int slice, final String tag) {
final ImageStack stack = imp.getImageStack();
final String header = stack.getSliceLabel(slice);
private static String getDicomAttribute(ImagePlus imp, int slice, String tag) {
ImageStack stack = imp.getImageStack();
String header = stack.getSliceLabel(slice);
// tag must be in format 0000,0000
if (slice < 1 || slice > stack.getSize()) {
return null;
Expand All @@ -179,9 +185,9 @@ private String getDicomAttribute(final ImagePlus imp, final int slice, final Str
}
String attribute = " ";
String value = " ";
final int idx1 = header.indexOf(tag);
final int idx2 = header.indexOf(":", idx1);
final int idx3 = header.indexOf("\n", idx2);
int idx1 = header.indexOf(tag);
int idx2 = header.indexOf(":", idx1);
int idx3 = header.indexOf("\n", idx2);
if (idx1 >= 0 && idx2 >= 0 && idx3 >= 0) {
try {
attribute = header.substring(idx1 + 9, idx2);
Expand All @@ -190,7 +196,7 @@ private String getDicomAttribute(final ImagePlus imp, final int slice, final Str
value = value.trim();
// IJ.log("tag = " + tag + ", attribute = " + attribute
// + ", value = " + value);
} catch (final Throwable e) {
} catch (Throwable e) {
return " ";
}
}
Expand All @@ -200,21 +206,27 @@ private String getDicomAttribute(final ImagePlus imp, final int slice, final Str
/**
* Show a message and return false if the version of IJ is too old for BoneJ
* or is a known bad version
*
*
* @return false if the IJ version is too old or blacklisted
*/
private static boolean checkIJVersion() {
if (isIJVersionBlacklisted()) {
IJ.error("Bad ImageJ version",
"The version of ImageJ you are using (v" + IJ.getVersion()
IJ.error(
"Bad ImageJ version",
"The version of ImageJ you are using (v"
+ IJ.getVersion()
+ ") is known to run BoneJ incorrectly.\n"
+ "Please up- or downgrade your ImageJ using Help-Update ImageJ.");
return false;
}

if (requiredIJVersion.compareTo(IJ.getVersion()) > 0) {
IJ.error("Update ImageJ", "You are using an old version of ImageJ, v" + IJ.getVersion() + ".\n"
+ "Please update to at least ImageJ v" + requiredIJVersion + " using Help-Update ImageJ.");
IJ.error(
"Update ImageJ",
"You are using an old version of ImageJ, v"
+ IJ.getVersion() + ".\n"
+ "Please update to at least ImageJ v"
+ requiredIJVersion + " using Help-Update ImageJ.");
return false;
}
return true;
Expand All @@ -223,21 +235,23 @@ private static boolean checkIJVersion() {
/**
* Show a message a return false if any requirement of the environment is
* missing
*
*
* @return
*/
public static boolean checkEnvironment() {
try {
Class.forName("javax.media.j3d.VirtualUniverse");
} catch (final ClassNotFoundException e) {
IJ.showMessage("Java 3D libraries are not installed.\n" + "Please install and run the ImageJ 3D Viewer,\n"
} catch (ClassNotFoundException e) {
IJ.showMessage("Java 3D libraries are not installed.\n"
+ "Please install and run the ImageJ 3D Viewer,\n"
+ "which will automatically install Java's 3D libraries.");
return false;
}
try {
Class.forName("ij3d.ImageJ3DViewer");
} catch (final ClassNotFoundException e) {
IJ.showMessage("ImageJ 3D Viewer is not installed.\n" + "Please install and run the ImageJ 3D Viewer.");
} catch (ClassNotFoundException e) {
IJ.showMessage("ImageJ 3D Viewer is not installed.\n"
+ "Please install and run the ImageJ 3D Viewer.");
return false;
}
if (!checkIJVersion())
Expand All @@ -247,16 +261,17 @@ public static boolean checkEnvironment() {

/**
* Check that IJ has enough memory to do the job
*
*
* @param memoryRequirement
* Estimated required memory
* @return True if there is enough memory or if the user wants to continue.
* False if the user wants to continue despite a risk of
* insufficient memory
*/
public static boolean checkMemory(final long memoryRequirement) {
public static boolean checkMemory(long memoryRequirement) {
if (memoryRequirement > IJ.maxMemory()) {
final String message = "You might not have enough memory to run this job.\n" + "Do you want to continue?";
String message = "You might not have enough memory to run this job.\n"
+ "Do you want to continue?";
if (IJ.showMessageWithCancel("Memory Warning", message)) {
return true;
} else {
Expand All @@ -267,8 +282,9 @@ public static boolean checkMemory(final long memoryRequirement) {
}
}

public static boolean checkMemory(final ImagePlus imp, final double ratio) {
double size = ((double) imp.getWidth() * imp.getHeight() * imp.getStackSize());
public static boolean checkMemory(ImagePlus imp, double ratio) {
double size = ((double) imp.getWidth() * imp.getHeight() * imp
.getStackSize());
switch (imp.getType()) {
case ImagePlus.GRAY8:
case ImagePlus.COLOR_256:
Expand All @@ -283,20 +299,21 @@ public static boolean checkMemory(final ImagePlus imp, final double ratio) {
size *= 4.0;
break;
}
final long memoryRequirement = (long) (size * ratio);
long memoryRequirement = (long) (size * ratio);
return checkMemory(memoryRequirement);
}

/**
* Guess whether an image is Hounsfield unit calibrated
*
*
* @param imp
* @return true if the image might be HU calibrated
*/
public static boolean huCalibrated(final ImagePlus imp) {
final Calibration cal = imp.getCalibration();
final double[] coeff = cal.getCoefficients();
if (!cal.calibrated() || cal == null || (cal.getCValue(0) == 0 && coeff[1] == 1)
public static boolean huCalibrated(ImagePlus imp) {
Calibration cal = imp.getCalibration();
double[] coeff = cal.getCoefficients();
if (!cal.calibrated() || cal == null
|| (cal.getCValue(0) == 0 && coeff[1] == 1)
|| (cal.getCValue(0) == Short.MIN_VALUE && coeff[1] == 1)) {
return false;
} else
Expand All @@ -305,11 +322,11 @@ public static boolean huCalibrated(final ImagePlus imp) {

/**
* Check if the version of IJ has been blacklisted as a known broken release
*
*
* @return true if the IJ version is blacklisted, false otherwise
*/
public static boolean isIJVersionBlacklisted() {
for (final String version : blacklistedIJVersions) {
for (String version : blacklistedIJVersions) {
if (version.equals(IJ.getVersion()))
return true;
}
Expand Down
51 changes: 51 additions & 0 deletions test/org/doube/util/ImageCheckTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.doube.util;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.Test;

import ij.ImagePlus;
import ij.measure.Calibration;

/**
* Unit tests for the org.doube.util.ImageCheck class
*
* Richard Domander
*/
public class ImageCheckTest {
@Test
public void testIsVoxelIsotropicReturnsFalseIfImageIsNull() throws Exception {
boolean result = ImageCheck.isVoxelIsotropic(null);
assertFalse("Null image should not be isotropic", result);
}

@Test
public void testIsVoxelIsotropic() throws Exception {
ImagePlus testImage = mock(ImagePlus.class);
Calibration anisotropicCalibration = new Calibration();

// 2D anisotropic image with 0 tolerance
anisotropicCalibration.pixelWidth = 2;
anisotropicCalibration.pixelHeight = 1;

when(testImage.getCalibration()).thenReturn(anisotropicCalibration);
when(testImage.getStackSize()).thenReturn(1);

boolean result = ImageCheck.isVoxelIsotropic(testImage, 0.0);
assertFalse("Image where width > height should not be isotropic", result);

// 2D image where anisotropy is within tolerance
result = ImageCheck.isVoxelIsotropic(testImage, 1.0);
assertTrue("Image should be isotropic if anisotropy is within tolerance", result);

// 3D image where depth anisotropy is beyond tolerance
anisotropicCalibration.pixelDepth = 1000;
when(testImage.getStackSize()).thenReturn(100);

result = ImageCheck.isVoxelIsotropic(testImage, 1.0);
assertFalse("Pixel depth too great to be anisotropic within tolerance", result);
}
}

0 comments on commit 406d182

Please sign in to comment.