diff --git a/src/main/java/net/imglib2/algorithm/region/CircleCursor.java b/src/main/java/net/imglib2/algorithm/region/CircleCursor.java new file mode 100644 index 000000000..558bd496b --- /dev/null +++ b/src/main/java/net/imglib2/algorithm/region/CircleCursor.java @@ -0,0 +1,335 @@ +package net.imglib2.algorithm.region; + +import net.imglib2.Cursor; +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.Localizable; +import net.imglib2.RandomAccess; +import net.imglib2.RandomAccessible; +import net.imglib2.Sampler; + +/** + * Iterates over a Bresenham 2D circle using the mid-point algorithm. + *

+ * Each point of the circle is iterated exactly once, and there is no "hole" in + * the circle. Contrary to the algorithm linked below, the circles generated by + * this cursor are "slim": each pixel of the circle is connected with + * 8-connectivity. For instance, a bitmap excerpt from such a circle looks like + * this: + * + *

+ * ..........
+ * OOO.......
+ * ...OO.....
+ * .....O....
+ * .....O....
+ * 
+ * + * @author Jean-Yves Tinevez + * @see Midpoint + * circle algorithm on Wikipedia. + * + */ +public class CircleCursor< T > implements Cursor< T > +{ + private final RandomAccessible< T > rai; + + private final RandomAccess< T > ra; + + private final Localizable center; + + private final long radius; + + private final int dimX; + + private final int dimY; + + private long x; + + private long y; + + private long dx; + + private long dy; + + private long f; + + private Octant octant; + + private boolean hasNext; + + private static enum Octant + { + INIT, EAST, NORTH, WEST, SOUTH, O1, O2, O3, O4, O5, O6, O7, O8; + } + + /** + * Iterates over a Bresenham circle in the target {@link RandomAccessible}. + * Each point of the circle is iterated exactly once, and there is no "hole" + * in the circle. + * + * @param rai + * the random accessible. It is the caller responsibility to + * ensure it can be accessed everywhere the circle will be + * iterated. + * @param center + * the circle center. Must be at least of dimension 2. Dimensions + * 0 and 1 are used to specify the circle center. + * @param radius + * the circle radius. The circle is written in a plane in + * dimensions 0 and 1. + */ + public CircleCursor( final RandomAccessible< T > rai, final Localizable center, final long radius ) + { + this( rai, center, radius, 0, 1 ); + } + + /** + * Iterates over a Bresenham circle in the target {@link RandomAccessible}. + * Each point of the circle is iterated exactly once, and there is no "hole" + * in the circle. + * + * @param rai + * the random accessible. It is the caller responsibility to + * ensure it can be accessed everywhere the circle will be + * iterated. + * @param center + * the circle center. Must at least contain dimensions specified + * dimX and dimY. + * @param radius + * the circle radius. + * @param dimX + * the first dimension of the plane in which to draw the circle. + * @param dimY + * the second dimension of the plane in which to draw the circle. + */ + public CircleCursor( final RandomAccessible< T > rai, final Localizable center, final long radius, final int dimX, final int dimY ) + { + this.rai = rai; + this.center = center; + this.radius = radius; + this.dimX = dimX; + this.dimY = dimY; + // Make an interval to signal where we will access data. + final int numDimensions = rai.numDimensions(); + final long[] minmax = new long[ 2 * numDimensions ]; + for ( int d = 0; d < numDimensions; d++ ) + { + if ( d == dimX || d == dimY ) + minmax[ d ] = center.getLongPosition( d ) - radius; + else + minmax[ d ] = center.getLongPosition( d ); + } + for ( int d = 0; d < numDimensions; d++ ) + { + if ( d == dimX || d == dimY ) + minmax[ d + numDimensions ] = center.getLongPosition( d ) + radius; + else + minmax[ d + numDimensions ] = center.getLongPosition( d ); + } + final Interval interval = FinalInterval.createMinMax( minmax ); + this.ra = rai.randomAccess( interval ); + reset(); + } + + @Override + public void reset() + { + x = 0; + y = radius; + f = 1 - radius; + dx = 1; + dy = -2 * radius; + octant = Octant.INIT; + ra.setPosition( center ); + hasNext = true; + } + + @Override + public void fwd() + { + switch ( octant ) + { + default: + case INIT: + ra.setPosition( center.getLongPosition( dimY ) + radius, dimY ); + octant = Octant.NORTH; + break; + + case NORTH: + ra.setPosition( center.getLongPosition( dimY ) - radius, dimY ); + octant = Octant.SOUTH; + break; + + case SOUTH: + ra.setPosition( center.getLongPosition( dimX ) - radius, dimX ); + ra.setPosition( center.getLongPosition( dimY ), dimY ); + octant = Octant.WEST; + break; + + case WEST: + ra.setPosition( center.getLongPosition( dimX ) + radius, dimX ); + octant = Octant.EAST; + break; + + case EAST: + x = x + 1; + dx = dx + 2; + f = f + dx; + ra.setPosition( center.getLongPosition( dimX ) + x, dimX ); + ra.setPosition( center.getLongPosition( dimY ) + y, dimY ); + octant = Octant.O1; + break; + + case O1: + ra.setPosition( center.getLongPosition( dimX ) - x, dimX ); + octant = Octant.O2; + break; + + case O2: + ra.setPosition( center.getLongPosition( dimY ) - y, dimY ); + octant = Octant.O3; + break; + + case O3: + ra.setPosition( center.getLongPosition( dimX ) + x, dimX ); + octant = Octant.O4; + // Stop here if x==y, lest the 45ยบ will be iterated twice. + if ( x >= y ) + hasNext = false; + break; + + case O4: + ra.setPosition( center.getLongPosition( dimX ) + y, dimX ); + ra.setPosition( center.getLongPosition( dimY ) - x, dimY ); + octant = Octant.O5; + break; + + case O5: + ra.setPosition( center.getLongPosition( dimX ) - y, dimX ); + octant = Octant.O6; + break; + + case O6: + ra.setPosition( center.getLongPosition( dimY ) + x, dimY ); + octant = Octant.O7; + break; + + case O7: + ra.setPosition( center.getLongPosition( dimX ) + y, dimX ); + octant = Octant.O8; + // Stop here if dx would cross y. + if ( x >= y - 1 ) + hasNext = false; + break; + + case O8: + if ( f > 0 ) + { + y = y - 1; + dy = dy + 2; + f = f + dy; + } + x = x + 1; + dx = dx + 2; + f = f + dx; + ra.setPosition( center.getLongPosition( dimX ) + x, dimX ); + ra.setPosition( center.getLongPosition( dimY ) + y, dimY ); + octant = Octant.O1; + break; + } + } + + @Override + public boolean hasNext() + { + return hasNext; + } + + @Override + public void localize( final float[] position ) + { + ra.localize( position ); + } + + @Override + public void localize( final double[] position ) + { + ra.localize( position ); + } + + @Override + public float getFloatPosition( final int d ) + { + return ra.getFloatPosition( d ); + } + + @Override + public double getDoublePosition( final int d ) + { + return ra.getDoublePosition( d ); + } + + @Override + public int numDimensions() + { + return ra.numDimensions(); + } + + @Override + public T get() + { + return ra.get(); + } + + @Override + public Sampler< T > copy() + { + return ra.copy(); + } + + @Override + public void jumpFwd( final long steps ) + { + for ( int i = 0; i < steps; i++ ) + fwd(); + } + + @Override + public T next() + { + fwd(); + return get(); + } + + @Override + public void localize( final int[] position ) + { + ra.localize( position ); + } + + @Override + public void localize( final long[] position ) + { + ra.localize( position ); + } + + @Override + public int getIntPosition( final int d ) + { + return ra.getIntPosition( d ); + } + + @Override + public long getLongPosition( final int d ) + { + return ra.getLongPosition( d ); + } + + @Override + public Cursor< T > copyCursor() + { + return new CircleCursor<>( rai, center, radius, dimX, dimY ); + } +} diff --git a/src/main/java/net/imglib2/algorithm/region/Circles.java b/src/main/java/net/imglib2/algorithm/region/Circles.java new file mode 100644 index 000000000..b00d59b43 --- /dev/null +++ b/src/main/java/net/imglib2/algorithm/region/Circles.java @@ -0,0 +1,193 @@ +package net.imglib2.algorithm.region; + +import net.imglib2.Cursor; +import net.imglib2.Localizable; +import net.imglib2.RandomAccessible; +import net.imglib2.type.Type; +import net.imglib2.type.numeric.RealType; +import net.imglib2.type.operators.Add; + +/** + * Write circles in an image using the midpoint algorithm. + * + * @author Jean-Yves Tinevez + * @see Midpoint + * circle algorithm on Wikipedia. + * @see CircleCursor {@link CircleCursor} + */ +public class Circles +{ + + /** + * Writes a circle in the target {@link RandomAccessible}. The circle is + * written by incrementing the pixel values by 1 along the circle. + * The circle is written in a plane in dimensions 0 and 1. + * + * @param rai + * the random accessible. It is the caller responsibility to + * ensure it can be accessed everywhere the circle will be + * iterated. + * @param center + * the circle center. Must be at least of dimension 2. Dimensions + * 0 and 1 are used to specify the circle center. + * @param radius + * the circle radius. + * @param + * the type of the target image. + */ + public static < T extends RealType< T > > void inc( final RandomAccessible< T > rai, final Localizable center, final long radius ) + { + inc( rai, center, radius, 0, 1 ); + } + + /** + * Writes a circle in the target {@link RandomAccessible}. The circle is + * written by incrementing the pixel values by 1 along the circle. + * + * @param rai + * the random accessible. It is the caller responsibility to + * ensure it can be accessed everywhere the circle will be + * iterated. + * @param center + * the circle center. Must contain at least of dimensions + * dimX and dimY, used to specify the + * circle center. + * @param radius + * the circle radius. + * @param dimX + * the first dimension of the plane in which to draw the circle. + * @param dimY + * the second dimension of the plane in which to draw the circle. + * @param + * the type of the target image. + */ + public static < T extends RealType< T > > void inc( final RandomAccessible< T > rai, final Localizable center, + final long radius, final int dimX, final int dimY ) + { + final Cursor< T > cursor = new CircleCursor< T >( rai, center, radius, dimX, dimY ); + while ( cursor.hasNext() ) + { + cursor.fwd(); + cursor.get().inc(); + } + } + + /** + * Writes a circle in the target {@link RandomAccessible}. The circle is + * written by setting the pixel values with the specified value. The + * circle is written in a plane in dimensions 0 and 1. + * + * @param rai + * the target random accessible. It is the caller responsibility + * to ensure it can be accessed everywhere the circle will be + * iterated. + * @param center + * the circle center. Must be at least of dimension 2. Dimensions + * 0 and 1 are used to specify the circle center. + * @param radius + * the circle radius. + * @param value + * the value to write along the circle. + * @param + * the type of the target image. + */ + public static < T extends Type< T > > void set( final RandomAccessible< T > rai, final Localizable center, final long radius, final T value ) + { + set( rai, center, radius, 0, 1, value ); + } + + /** + * Writes a circle in the target {@link RandomAccessible}. The circle is + * written by setting the pixel values with the specified value. + * + * @param rai + * the target random accessible. It is the caller responsibility + * to ensure it can be accessed everywhere the circle will be + * iterated. + * @param center + * the circle center. Must contain at least of dimensions + * dimX and dimY, used to specify the + * circle center. + * @param radius + * the circle radius. + * @param dimX + * the first dimension of the plane in which to draw the circle. + * @param dimY + * the second dimension of the plane in which to draw the circle. + * @param value + * the value to write along the circle. + * @param + * the type of the target image. + */ + public static < T extends Type< T > > void set( final RandomAccessible< T > rai, final Localizable center, final long radius, final int dimX, final int dimY, final T value ) + { + final Cursor< T > cursor = new CircleCursor< T >( rai, center, radius, dimX, dimY ); + while ( cursor.hasNext() ) + { + cursor.fwd(); + cursor.get().set( value ); + } + } + + /** + * Writes a circle in the target {@link RandomAccessible}. The circle is + * written by adding the specified value to the pixel values already + * in the image. The circle is written in a plane in dimensions 0 and 1. + * + * @param rai + * the random accessible. It is the caller responsibility to + * ensure it can be accessed everywhere the circle will be + * iterated. + * @param center + * the circle center. Must be at least of dimension 2. Dimensions + * 0 and 1 are used to specify the circle center. + * @param radius + * the circle radius. + * @param value + * the value to add along the circle. + * @param + * the type of the target image. + */ + public static < T extends Add< T > > void add( final RandomAccessible< T > rai, final Localizable center, final long radius, final T value ) + { + add( rai, center, radius, 0, 1, value ); + } + + /** + * Writes a circle in the target {@link RandomAccessible}. The circle is + * written by adding the specified value to the pixel values already + * in the image. + * + * @param rai + * the random accessible. It is the caller responsibility to + * ensure it can be accessed everywhere the circle will be + * iterated. + * @param center + * the circle center. Must contain at least of dimensions + * dimX and dimY, used to specify the + * circle center. + * @param radius + * the circle radius. + * @param dimX + * the first dimension of the plane in which to draw the circle. + * @param dimY + * the second dimension of the plane in which to draw the circle. + * @param value + * the value to add along the circle. + * @param + * the type of the target image. + */ + public static < T extends Add< T > > void add( final RandomAccessible< T > rai, final Localizable center, final long radius, final int dimX, final int dimY, final T value ) + { + final Cursor< T > cursor = new CircleCursor< T >( rai, center, radius, dimX, dimY ); + while ( cursor.hasNext() ) + { + cursor.fwd(); + cursor.get().add( value ); + } + } + + private Circles() + {} +} diff --git a/src/main/java/net/imglib2/algorithm/region/EllipseCursor.java b/src/main/java/net/imglib2/algorithm/region/EllipseCursor.java new file mode 100644 index 000000000..c75e322cf --- /dev/null +++ b/src/main/java/net/imglib2/algorithm/region/EllipseCursor.java @@ -0,0 +1,423 @@ +package net.imglib2.algorithm.region; + +import net.imglib2.Cursor; +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.Localizable; +import net.imglib2.RandomAccess; +import net.imglib2.RandomAccessible; +import net.imglib2.Sampler; +import net.imglib2.util.Util; + +/** + * Iterates over a Bresenham 2D ellipse using the mid-point algorithm. + *

+ * Each point of the ellipse is iterated exactly once, and there is no "hole" in + * the ellipse. A bitmap excerpt from such a ellipse looks like this: + * + *

+ * ..........
+ * OOO.......
+ * ...OO.....
+ * .....O....
+ * .....O....
+ * 
+ *

+ * The circle generated with equal X and Y radii is not strictly equivalent to + * the circle generated by the {@link CircleCursor}. + * + * @author Jean-Yves Tinevez + */ +public class EllipseCursor< T > implements Cursor< T > +{ + private final RandomAccessible< T > rai; + + private final RandomAccess< T > ra; + + private final Localizable center; + + private final long radiusX; + + private final long radiusY; + + private final int dimX; + + private final int dimY; + + private long x; + + private long y; + + private double px; + + private double py; + + private double p; + + private Octant octant; + + private boolean hasNext; + + private static enum Octant + { + INIT, + EAST, NORTH, WEST, SOUTH, + REGION1_Q1, REGION1_Q2, REGION1_Q3, REGION1_Q4, + REGION2_Q1, REGION2_Q2, REGION2_Q3, REGION2_Q4; + } + + /** + * Iterates over a Bresenham ellipse in the target {@link RandomAccessible}. + * Each point of the ellipse is iterated exactly once, and there is no + * "hole" in the ellipse. The ellipse is written in a plane in dimensions 0 + * and 1. + * + * @param rai + * the random accessible. It is the caller responsibility to + * ensure it can be accessed everywhere the ellipse will be + * iterated. + * @param center + * the ellipse center. Must be at least of dimension 2. + * Dimensions 0 and 1 are used to specify the ellipse center. + * @param radiusX + * the ellipse radius in the dimension 0. + * @param radiusY + * the ellipse radius in the dimension 1. + */ + public EllipseCursor( final RandomAccessible< T > rai, final Localizable center, final long radiusX, final long radiusY ) + { + this( rai, center, radiusX, radiusY, 0, 1 ); + } + + /** + * Iterates over a Bresenham ellipse in the target {@link RandomAccessible}. + * Each point of the ellipse is iterated exactly once, and there is no + * "hole" in the ellipse. + * + * @param rai + * the random accessible. It is the caller responsibility to + * ensure it can be accessed everywhere the ellipse will be + * iterated. + * @param center + * the ellipse center. Must be at least of dimension 2. + * Dimensions 0 and 1 are used to specify the ellipse center. + * @param radiusX + * the ellipse radius in dimension X. + * @param radiusY + * the ellipse radius in dimension Y. + * @param dimX + * the first dimension of the plane in which to draw the ellipse. + * @param dimY + * the second dimension of the plane in which to draw the + * ellipse. + */ + public EllipseCursor( final RandomAccessible< T > rai, final Localizable center, final long radiusX, final long radiusY, final int dimX, final int dimY ) + { + this.rai = rai; + this.center = center; + if ( radiusX > radiusY ) + { + // Swap X & Y if radiusX if larger than radiusY and avoid issues + // with slim ellipses. + this.radiusX = radiusY; + this.radiusY = radiusX; + this.dimX = dimY; + this.dimY = dimX; + } + else + { + this.radiusX = radiusX; + this.radiusY = radiusY; + this.dimX = dimX; + this.dimY = dimY; + } + // Make an interval to signal where we will access data. + final int numDimensions = rai.numDimensions(); + final long[] minmax = new long[ 2 * numDimensions ]; + for ( int d = 0; d < numDimensions; d++ ) + { + if ( d == dimX ) + minmax[ d ] = center.getLongPosition( d ) - radiusX; + else if ( d == dimY ) + minmax[ d ] = center.getLongPosition( d ) - radiusY; + else + minmax[ d ] = center.getLongPosition( d ); + } + for ( int d = 0; d < numDimensions; d++ ) + { + if ( d == dimX ) + minmax[ d + numDimensions ] = center.getLongPosition( d ) + radiusX; + else if ( d == dimY ) + minmax[ d + numDimensions ] = center.getLongPosition( d ) + radiusY; + else + minmax[ d + numDimensions ] = center.getLongPosition( d ); + } + final Interval interval = FinalInterval.createMinMax( minmax ); + this.ra = rai.randomAccess( interval ); + reset(); + } + + @Override + public void reset() + { + // Prepare for region 1. + x = 0l; + y = radiusY; + px = 0.; + py = 2. * radiusX * radiusX * y; + p = radiusY * radiusY - ( radiusX * radiusX * radiusY ) + ( 0.25 * radiusX * radiusX ); + + octant = Octant.INIT; + ra.setPosition( center ); + hasNext = true; + } + + @Override + public void fwd() + { + switch ( octant ) + { + default: + case INIT: + ra.setPosition( center.getLongPosition( dimY ) + radiusY, dimY ); + octant = Octant.NORTH; + break; + + case NORTH: + ra.setPosition( center.getLongPosition( dimY ) - radiusY, dimY ); + octant = Octant.SOUTH; + break; + + case SOUTH: + ra.setPosition( center.getLongPosition( dimX ) - radiusX, dimX ); + ra.setPosition( center.getLongPosition( dimY ), dimY ); + octant = Octant.WEST; + break; + + case WEST: + ra.setPosition( center.getLongPosition( dimX ) + radiusX, dimX ); + octant = Octant.EAST; + + // Special case: rx = ry = 1. + if ( radiusX == 1 && radiusY == 1 ) + hasNext = false; + + break; + + case EAST: + // Move to region 1. + x++; + px += 2. * radiusY * radiusY; + if ( p < 0 ) + { + p += ( radiusY * radiusY ) + px; + } + else + { + y--; + py -= 2 * ( radiusX * radiusX ); + p += ( radiusY * radiusY ) + px - py; + } + + ra.setPosition( center.getLongPosition( dimX ) + x, dimX ); + ra.setPosition( center.getLongPosition( dimY ) + y, dimY ); + octant = Octant.REGION1_Q1; + break; + + case REGION1_Q1: + ra.setPosition( center.getLongPosition( dimX ) - x, dimX ); + octant = Octant.REGION1_Q2; + break; + + case REGION1_Q2: + ra.setPosition( center.getLongPosition( dimY ) - y, dimY ); + octant = Octant.REGION1_Q3; + break; + + case REGION1_Q3: + ra.setPosition( center.getLongPosition( dimX ) + x, dimX ); + octant = Octant.REGION1_Q4; + + // Don't overwrite equator for small radii. + if ( y == 1 && x <= 2 ) + hasNext = false; + + break; + + case REGION1_Q4: + + if ( px < py ) + { + // Back to Q1 of Region 1. + x++; + px += 2. * radiusY * radiusY; + if ( p < 0 ) + { + p += ( radiusY * radiusY ) + px; + } + else + { + y--; + py -= 2 * ( radiusX * radiusX ); + p += ( radiusY * radiusY ) + px - py; + } + + octant = Octant.REGION1_Q1; + } + else + { + // Maybe go to Region 2. + p = radiusY * radiusY * ( x + 0.5 ) * ( x + 0.5 ) + radiusX * radiusX * ( y - 1 ) * ( y - 1 ) - radiusX * radiusX * radiusY * radiusY; + y--; + py -= 2. * radiusX * radiusX; + if ( p > 0 ) + { + p += radiusX * radiusX - py; + } + else + { + x++; + px += 2. * radiusY * radiusY; + p += radiusX * radiusX - py + px; + } + octant = Octant.REGION2_Q1; + } + + ra.setPosition( center.getLongPosition( dimX ) + x, dimX ); + ra.setPosition( center.getLongPosition( dimY ) + y, dimY ); + break; + + case REGION2_Q1: + ra.setPosition( center.getLongPosition( dimX ) - x, dimX ); + octant = Octant.REGION2_Q2; + break; + + case REGION2_Q2: + ra.setPosition( center.getLongPosition( dimY ) - y, dimY ); + octant = Octant.REGION2_Q3; + break; + + case REGION2_Q3: + ra.setPosition( center.getLongPosition( dimX ) + x, dimX ); + octant = Octant.REGION2_Q4; + if ( y <= 1 ) + hasNext = false; + break; + + case REGION2_Q4: + y--; + py -= 2. * radiusX * radiusX; + if ( p > 0 ) + { + p += radiusX * radiusX - py; + } + else + { + x++; + px += 2. * radiusY * radiusY; + p += radiusX * radiusX - py + px; + } + octant = Octant.REGION2_Q1; + ra.setPosition( center.getLongPosition( dimX ) + x, dimX ); + ra.setPosition( center.getLongPosition( dimY ) + y, dimY ); + } + } + + @Override + public boolean hasNext() + { + return hasNext; + } + + @Override + public void localize( final float[] position ) + { + ra.localize( position ); + } + + @Override + public void localize( final double[] position ) + { + ra.localize( position ); + } + + @Override + public float getFloatPosition( final int d ) + { + return ra.getFloatPosition( d ); + } + + @Override + public double getDoublePosition( final int d ) + { + return ra.getDoublePosition( d ); + } + + @Override + public int numDimensions() + { + return ra.numDimensions(); + } + + @Override + public T get() + { + return ra.get(); + } + + @Override + public Sampler< T > copy() + { + return ra.copy(); + } + + @Override + public void jumpFwd( final long steps ) + { + for ( int i = 0; i < steps; i++ ) + fwd(); + } + + @Override + public T next() + { + fwd(); + return get(); + } + + @Override + public void localize( final int[] position ) + { + ra.localize( position ); + } + + @Override + public void localize( final long[] position ) + { + ra.localize( position ); + } + + @Override + public int getIntPosition( final int d ) + { + return ra.getIntPosition( d ); + } + + @Override + public long getLongPosition( final int d ) + { + return ra.getLongPosition( d ); + } + + @Override + public Cursor< T > copyCursor() + { + return new EllipseCursor<>( rai, center, radiusX, radiusY, dimX, dimY ); + } + + @Override + public String toString() + { + return super.toString() + " pos=" + Util.printCoordinates( this ) + ",\t" + octant + ",\tx = " + x + ",\ty = " + y; + } +} diff --git a/src/main/java/net/imglib2/algorithm/region/Ellipses.java b/src/main/java/net/imglib2/algorithm/region/Ellipses.java new file mode 100644 index 000000000..8bc7968ca --- /dev/null +++ b/src/main/java/net/imglib2/algorithm/region/Ellipses.java @@ -0,0 +1,209 @@ +package net.imglib2.algorithm.region; + +import net.imglib2.Cursor; +import net.imglib2.Localizable; +import net.imglib2.RandomAccessible; +import net.imglib2.type.Type; +import net.imglib2.type.numeric.RealType; +import net.imglib2.type.operators.Add; + +/** + * Write ellipses in an image using the midpoint algorithm. + * + * @author Jean-Yves Tinevez + * @see EllipseCursor {@link EllipseCursor} + * @see EllipseCursor {@link EllipseCursor} + */ +public class Ellipses +{ + + /** + * Writes an ellipse in the target {@link RandomAccessible}. The ellipse is + * written by incrementing the pixel values by 1 along the ellipse. + * The ellipse is written in a plane in dimensions 0 and 1. + * + * @param rai + * the random accessible. It is the caller responsibility to + * ensure it can be accessed everywhere the ellipse will be + * iterated. + * @param center + * the ellipse center. Must be at least of dimension 2. + * Dimensions 0 and 1 are used to specify the ellipse center. + * @param radiusX + * the ellipse X radius. + * @param radiusY + * the ellipse Y radius. + * @param + * the type of the target image. + */ + public static < T extends RealType< T > > void inc( final RandomAccessible< T > rai, final Localizable center, final long radiusX, final long radiusY ) + { + inc( rai, center, radiusX, radiusY, 0, 1 ); + } + + /** + * Writes an ellipse in the target {@link RandomAccessible}. The ellipse is + * written by incrementing the pixel values by 1 along the ellipse. + * The ellipse is written in a plane in specified dimensions. + * + * @param rai + * the random accessible. It is the caller responsibility to + * ensure it can be accessed everywhere the ellipse will be + * iterated. + * @param center + * the ellipse center. Must contain at least dimensions + * dimX and dimY, used to specify the + * ellipse center. + * @param radiusX + * the ellipse X radius. + * @param radiusY + * the ellipse Y radius. + * @param dimX + * the first dimension of the plane in which to draw the ellipse. + * @param dimY + * the second dimension of the plane in which to draw the + * ellipse. + * @param + * the type of the target image. + */ + public static < T extends RealType< T > > void inc( final RandomAccessible< T > rai, final Localizable center, + final long radiusX, final long radiusY, final int dimX, final int dimY ) + { + final Cursor< T > cursor = new EllipseCursor< T >( rai, center, radiusX, radiusY, dimX, dimY ); + while ( cursor.hasNext() ) + { + cursor.fwd(); + cursor.get().inc(); + } + } + + /** + * Writes an ellipse in the target {@link RandomAccessible}. The ellipse is + * written by setting the pixel values with the specified value. The + * ellipse is written in a plane in dimensions 0 and 1. + * + * @param rai + * the target random accessible. It is the caller responsibility + * to ensure it can be accessed everywhere the ellipse will be + * iterated. + * @param center + * the ellipse center. Must be at least of dimension 2. + * Dimensions 0 and 1 are used to specify the ellipse center. + * @param radiusX + * the ellipse X radius. + * @param radiusY + * the ellipse Y radius. + * @param value + * the value to write along the ellipse. + * @param + * the type of the target image. + */ + public static < T extends Type< T > > void set( final RandomAccessible< T > rai, final Localizable center, final long radiusX, final long radiusY, final T value ) + { + set( rai, center, radiusX, radiusY, 0, 1, value ); + } + + /** + * Writes an ellipse in the target {@link RandomAccessible}. The ellipse is + * written by setting the pixel values with the specified value. + * + * @param rai + * the target random accessible. It is the caller responsibility + * to ensure it can be accessed everywhere the ellipse will be + * iterated. + * @param center + * the ellipse center. Must contain at least dimensions + * dimX and dimY, used to specify the + * ellipse center. + * @param radiusX + * the ellipse X radius. + * @param radiusY + * the ellipse Y radius. + * @param dimX + * the first dimension of the plane in which to draw the ellipse. + * @param dimY + * the second dimension of the plane in which to draw the + * ellipse. + * @param value + * the value to write along the ellipse. + * @param + * the type of the target image. + */ + public static < T extends Type< T > > void set( final RandomAccessible< T > rai, final Localizable center, + final long radiusX, final long radiusY, final int dimX, final int dimY, final T value ) + { + final Cursor< T > cursor = new EllipseCursor< T >( rai, center, radiusX, radiusY, dimX, dimY ); + while ( cursor.hasNext() ) + { + cursor.fwd(); + cursor.get().set( value ); + } + } + + /** + * Writes an ellipse in the target {@link RandomAccessible}. The ellipse is + * written by adding the specified value to the pixel values already + * in the image. The ellipse is written in a plane in dimensions 0 and 1. + * + * @param rai + * the random accessible. It is the caller responsibility to + * ensure it can be accessed everywhere the ellipse will be + * iterated. + * @param center + * the ellipse center. Must be at least of dimension 2. + * Dimensions 0 and 1 are used to specify the ellipse center. + * @param radiusX + * the ellipse X radius. + * @param radiusY + * the ellipse Y radius. + * @param value + * the value to add along the ellipse. + * @param + * the type of the target image. + */ + public static < T extends Add< T > > void add( final RandomAccessible< T > rai, final Localizable center, final long radiusX, final long radiusY, final T value ) + { + add( rai, center, radiusX, radiusY, 0, 1, value ); + } + + /** + * Writes an ellipse in the target {@link RandomAccessible}. The ellipse is + * written by adding the specified value to the pixel values already + * in the image. + * + * @param rai + * the random accessible. It is the caller responsibility to + * ensure it can be accessed everywhere the ellipse will be + * iterated. + * @param center + * the ellipse center. Must contain at least dimensions + * dimX and dimY, used to specify the + * ellipse center. + * @param radiusX + * the ellipse X radius. + * @param radiusY + * the ellipse Y radius. + * @param dimX + * the first dimension of the plane in which to draw the ellipse. + * @param dimY + * the second dimension of the plane in which to draw the + * ellipse. + * @param value + * the value to add along the ellipse. + * @param + * the type of the target image. + */ + public static < T extends Add< T > > void add( final RandomAccessible< T > rai, final Localizable center, + final long radiusX, final long radiusY, final int dimX, final int dimY, final T value ) + { + final Cursor< T > cursor = new EllipseCursor< T >( rai, center, radiusX, radiusY, dimX, dimY ); + while ( cursor.hasNext() ) + { + cursor.fwd(); + cursor.get().add( value ); + } + } + + private Ellipses() + {} +} diff --git a/src/test/java/net/imglib2/algorithm/region/CircleTest.java b/src/test/java/net/imglib2/algorithm/region/CircleTest.java new file mode 100644 index 000000000..b3d713c4a --- /dev/null +++ b/src/test/java/net/imglib2/algorithm/region/CircleTest.java @@ -0,0 +1,65 @@ +package net.imglib2.algorithm.region; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import net.imglib2.Point; +import net.imglib2.img.array.ArrayImg; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.img.basictypeaccess.array.ShortArray; +import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.util.Util; + +public class CircleTest +{ + + @Test + public void testIterateOnce() + { + final ArrayImg< UnsignedShortType, ShortArray > img = ArrayImgs.unsignedShorts( 512l, 512l ); + + final Point center = new Point( 255l, 255l ); + for ( long r = 1l; r < 256l; r++ ) + { + Circles.inc( img, center, r ); + + final CircleCursor< UnsignedShortType > cursor = new CircleCursor<>( img, center, r ); + while ( cursor.hasNext() ) + { + cursor.fwd(); + assertEquals( "Pixel at coordinates " + Util.printCoordinates( cursor ) + " has not been iterated one time.", 1, cursor.get().get() ); + } + } + } + + @Test + public void testDistanceToCenter() + { + final ArrayImg< UnsignedShortType, ShortArray > img = ArrayImgs.unsignedShorts( 512l, 512l ); + + final Point center = new Point( 255l, 255l ); + final long[] pos = new long[ img.numDimensions() ]; + + for ( long r = 1l; r < 256l; r++ ) + { + final CircleCursor< UnsignedShortType > cursor = new CircleCursor<>( img, center, r ); + while ( cursor.hasNext() ) + { + cursor.fwd(); + cursor.localize( pos ); + + double d2 = 0.; + for ( int d = 0; d < img.numDimensions(); d++ ) + { + final double dx = center.getDoublePosition( d ) - cursor.getDoublePosition( d ); + d2 += dx * dx; + } + final double dist = Math.sqrt( d2 ); + + assertEquals( "Circle coordinates " + Util.printCoordinates( cursor ) + " is too far from expected radius " + r + ".", + r, dist, 0.5 ); + } + } + } +} diff --git a/src/test/java/net/imglib2/algorithm/region/EllipseTest.java b/src/test/java/net/imglib2/algorithm/region/EllipseTest.java new file mode 100644 index 000000000..cce38d30c --- /dev/null +++ b/src/test/java/net/imglib2/algorithm/region/EllipseTest.java @@ -0,0 +1,73 @@ +package net.imglib2.algorithm.region; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import net.imglib2.Point; +import net.imglib2.img.array.ArrayImg; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.img.basictypeaccess.array.ShortArray; +import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.util.Util; + +public class EllipseTest +{ + + @Test + public void testIterateOnce() + { + final ArrayImg< UnsignedShortType, ShortArray > img = ArrayImgs.unsignedShorts( 512l, 512l ); + + final Point center = new Point( 255l, 255l ); + + for ( long rx = 1l; rx < 56l; rx++ ) + { + for ( long ry = 1l; ry < 56l; ry++ ) + { + Ellipses.inc( img, center, rx, ry ); + final EllipseCursor< UnsignedShortType > cursor = new EllipseCursor<>( img, center, rx, ry ); + while ( cursor.hasNext() ) + { + cursor.fwd(); + assertEquals( "For radii (" + rx + ", " + ry + "), pixel at coordinates " + + Util.printCoordinates( cursor ) + " has not been iterated one time.", + 1, cursor.get().get() ); + } + // Reset image to 0. + for ( final UnsignedShortType p : img ) + p.setZero(); + } + } + } + + @Test + public void testDistanceToCenter() + { + final ArrayImg< UnsignedShortType, ShortArray > img = ArrayImgs.unsignedShorts( 512l, 512l ); + + final Point center = new Point( 255l, 255l ); + final long[] pos = new long[ img.numDimensions() ]; + + for ( long r = 1l; r < 256l; r++ ) + { + final EllipseCursor< UnsignedShortType > cursor = new EllipseCursor<>( img, center, r, r ); + while ( cursor.hasNext() ) + { + cursor.fwd(); + cursor.localize( pos ); + + double d2 = 0.; + for ( int d = 0; d < img.numDimensions(); d++ ) + { + final double dx = center.getDoublePosition( d ) - cursor.getDoublePosition( d ); + d2 += dx * dx; + } + final double dist = Math.sqrt( d2 ); + + assertEquals( "Ellipse coordinates " + Util.printCoordinates( cursor ) + " is too far from expected radius " + r + ".", + r, dist, 0.5 ); + } + } + } +}