diff --git a/src/main/java/net/imagej/ops/image/ascii/DefaultASCII.java b/src/main/java/net/imagej/ops/image/ascii/DefaultASCII.java index dcf247105..0e7a979ce 100644 --- a/src/main/java/net/imagej/ops/image/ascii/DefaultASCII.java +++ b/src/main/java/net/imagej/ops/image/ascii/DefaultASCII.java @@ -36,6 +36,7 @@ import net.imglib2.Cursor; import net.imglib2.IterableInterval; import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.real.DoubleType; import net.imglib2.util.Pair; import org.scijava.plugin.Parameter; @@ -46,7 +47,7 @@ *

* Only the first two dimensions of the image are considered. *

- * + * * @author Curtis Rueden */ @Plugin(type = Ops.Image.ASCII.class) @@ -79,24 +80,28 @@ public String calculate(final IterableInterval input) { if (min == null) min = minMax.getA(); if (max == null) max = minMax.getB(); } - return ascii(input, min, max); + + DoubleType minSource = new DoubleType(min.getRealDouble()); + DoubleType maxSource = new DoubleType(max.getRealDouble()); + DoubleType minTarget = new DoubleType(0); + DoubleType maxTarget = new DoubleType(CHARS.length()); + + IterableInterval converted = ops().convert().float64(input); + IterableInterval normalized = ops().image().normalize(converted, + minSource, maxSource, minTarget, maxTarget); + + return ascii(normalized); } // -- Utility methods -- - public static > String ascii( - final IterableInterval image, final T min, final T max) - { + public static String ascii(final IterableInterval image) { final long dim0 = image.dimension(0); final long dim1 = image.dimension(1); - // TODO: Check bounds. + final int w = (int) (dim0 + 1); final int h = (int) dim1; - // span = max - min - final T span = max.copy(); - span.sub(min); - // allocate ASCII character array final char[] c = new char[w * h]; for (int y = 1; y <= h; y++) { @@ -104,22 +109,21 @@ public static > String ascii( } // loop over all available positions - final Cursor cursor = image.localizingCursor(); + final Cursor cursor = image.localizingCursor(); final int[] pos = new int[image.numDimensions()]; - final T tmp = image.firstElement().copy(); while (cursor.hasNext()) { cursor.fwd(); cursor.localize(pos); final int index = w * pos[1] + pos[0]; - // normalized = (value - min) / (max - min) - tmp.set(cursor.get()); - tmp.sub(min); - final double normalized = tmp.getRealDouble() / span.getRealDouble(); - - final int charLen = CHARS.length(); - final int charIndex = (int) (charLen * normalized); - c[index] = CHARS.charAt(charIndex < charLen ? charIndex : charLen - 1); + // grab the value from the normalized image, convert it to an ASCII char. + // N.B. if the original value was at the max for the type range it will be + // equal to the length of the char array after normalization. Thus to + // prevent an exception when converting to ASCII we subtract one when the + // normalized image value is equal to the length. + int val = (int) cursor.get().getRealDouble(); + if (val == CHARS.length()) val--; + c[index] = CHARS.charAt(val); } return new String(c); diff --git a/src/test/java/net/imagej/ops/image/ascii/ASCIITest.java b/src/test/java/net/imagej/ops/image/ascii/ASCIITest.java index 3ceb8f2f2..467e54add 100644 --- a/src/test/java/net/imagej/ops/image/ascii/ASCIITest.java +++ b/src/test/java/net/imagej/ops/image/ascii/ASCIITest.java @@ -29,7 +29,7 @@ package net.imagej.ops.image.ascii; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import net.imagej.ops.AbstractOpTest; import net.imglib2.img.Img; @@ -40,8 +40,9 @@ /** * Tests {@link net.imagej.ops.Ops.Image.ASCII}. - * + * * @author Leon Yang + * @author Gabe Selzer */ public class ASCIITest extends AbstractOpTest { @@ -63,9 +64,41 @@ public void testDefaultASCII() { final String ascii = (String) ops.run(DefaultASCII.class, img); for (int i = 0; i < len; i++) { for (int j = 0; j < width; j++) { - assertTrue(ascii.charAt(i * (width + 1) + j) == CHARS.charAt(i)); + assertEquals(ascii.charAt(i * (width + 1) + j), CHARS.charAt(i)); } - assertTrue(ascii.charAt(i * (width + 1) + width) == '\n'); + assertEquals(ascii.charAt(i * (width + 1) + width), '\n'); } } + + @Test + public void testASCIIMinMax() { + // character set used in DefaultASCII, could be updated if necessary + final String CHARS = "#O*o+-,. "; + final int len = CHARS.length(); + final int width = 10; + final byte[] array = new byte[width * len]; + for (int i = 0; i < len; i++) { + for (int j = 0; j < width; j++) { + array[i * width + j] = (byte) (i * width + j); + } + } + final UnsignedByteType min = new UnsignedByteType(0); + final UnsignedByteType max = new UnsignedByteType(90); + final Img img = ArrayImgs.unsignedBytes(array, width, + len); + final String ascii = (String) ops.run(DefaultASCII.class, img, min, max); + for (int i = 0; i < len; i++) { + for (int j = 0; j < width; j++) { + assertEquals(ascii.charAt(i * (width + 1) + j), CHARS.charAt(i)); + } + assertEquals(ascii.charAt(i * (width + 1) + width), '\n'); + } + + // make sure that the values of the min/max ascii are the same as the + // unclamped version (which will set the minimum and maximum to those of the + // data, which are the same as the ones that we set). + final String asciiUnclamped = (String) ops.run(DefaultASCII.class, img); + assertEquals(asciiUnclamped, ascii); + + } }