Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix min/max String Exception bug #577

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 24 additions & 20 deletions src/main/java/net/imagej/ops/image/ascii/DefaultASCII.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -46,7 +47,7 @@
* <p>
* Only the first two dimensions of the image are considered.
* </p>
*
*
* @author Curtis Rueden
*/
@Plugin(type = Ops.Image.ASCII.class)
Expand Down Expand Up @@ -79,47 +80,50 @@ public String calculate(final IterableInterval<T> 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<DoubleType> converted = ops().convert().float64(input);
IterableInterval<DoubleType> normalized = ops().image().normalize(converted,
minSource, maxSource, minTarget, maxTarget);

return ascii(normalized);
}

// -- Utility methods --

public static <T extends RealType<T>> String ascii(
final IterableInterval<T> image, final T min, final T max)
{
public static String ascii(final IterableInterval<DoubleType> 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++) {
c[w * y - 1] = '\n'; // end of row
}

// loop over all available positions
final Cursor<T> cursor = image.localizingCursor();
final Cursor<DoubleType> 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);
Expand Down
41 changes: 37 additions & 4 deletions src/test/java/net/imagej/ops/image/ascii/ASCIITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -40,8 +40,9 @@

/**
* Tests {@link net.imagej.ops.Ops.Image.ASCII}.
*
*
* @author Leon Yang
* @author Gabe Selzer
*/
public class ASCIITest extends AbstractOpTest {

Expand All @@ -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<UnsignedByteType> 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);

}
}