Skip to content

Commit

Permalink
Showing 5 changed files with 254 additions and 18 deletions.
5 changes: 3 additions & 2 deletions src/main/java/net/imglib2/algorithm/fill/Filter.java
Original file line number Diff line number Diff line change
@@ -35,15 +35,16 @@
package net.imglib2.algorithm.fill;

/**
* Interface for comparing {@link T} t and {@link U} u and accepting
* them as equivalent in a sense specified by implementation thereof.
* Interface for comparing {@link T} t and {@link U} u and accepting them as
* equivalent in a sense specified by implementation thereof.
*
* @author Philipp Hanslovsky
* @author Stephan Saalfeld
*
* @param <T>
* @param <U>
*/
@Deprecated
public interface Filter< T, U >
{
boolean accept( T t, U u );
198 changes: 189 additions & 9 deletions src/main/java/net/imglib2/algorithm/fill/FloodFill.java
Original file line number Diff line number Diff line change
@@ -34,6 +34,9 @@

package net.imglib2.algorithm.fill;

import java.util.function.BiPredicate;
import java.util.function.Consumer;

import gnu.trove.list.TLongList;
import gnu.trove.list.array.TLongArrayList;
import net.imglib2.Cursor;
@@ -58,6 +61,182 @@ public class FloodFill
// int or long? current TLongList cannot store more than Integer.MAX_VALUE
private static final int CLEANUP_THRESHOLD = ( int ) 1e5;

/**
* Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting
* at seed location, write fillLabel into target at current location and
* continue for each pixel in neighborhood defined by shape if neighborhood
* pixel is in the same connected component and fillLabel has not been
* written into that location yet (comparator evaluates to 0).
*
* Convenience call to
* {@link FloodFill#fill(RandomAccessible, RandomAccessible, Localizable, Object, Type, Shape, BiPredicate)}.
* seedLabel is extracted from source at seed location.
*
* @param source
* input
* @param target
* {@link RandomAccessible} to be written into. May be the same
* as input.
* @param seed
* Start flood fill at this location.
* @param fillLabel
* Immutable. Value to be written into valid flood fill
* locations.
* @param shape
* Defines neighborhood that is considered for connected
* components, e.g.
* {@link net.imglib2.algorithm.neighborhood.DiamondShape}
* @param <T>
* input pixel type
* @param <U>
* fill label type
*/
public static < T extends Type< T >, U extends Type< U > > void fill(
final RandomAccessible< T > source,
final RandomAccessible< U > target,
final Localizable seed,
final U fillLabel,
final Shape shape )
{
final RandomAccess< T > access = source.randomAccess();
access.setPosition( seed );
final T seedValue = access.get().copy();
final BiPredicate< T, U > filter = ( t, u ) -> t.valueEquals( seedValue ) && !u.valueEquals( fillLabel );
fill( source, target, seed, fillLabel, shape, filter );
}

/**
* Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting
* at seed location, write fillLabel into target at current location and
* continue for each pixel in neighborhood defined by shape if neighborhood
* pixel is in the same connected component and fillLabel has not been
* written into that location yet (comparator evaluates to 0).
*
* Convenience call to
* {@link FloodFill#fill(RandomAccessible, RandomAccessible, Localizable, Object, Object, Shape, BiPredicate, Consumer)}
* with {@link TypeWriter} as writer.
*
* @param source
* input
* @param target
* {@link RandomAccessible} to be written into. May be the same
* as input.
* @param seed
* Start flood fill at this location.
* @param fillLabel
* Immutable. Value to be written into valid flood fill
* locations.
* @param shape
* Defines neighborhood that is considered for connected
* components, e.g.
* {@link net.imglib2.algorithm.neighborhood.DiamondShape}
* @param filter
* Returns true if pixel has not been visited yet and should be
* written into. Returns false if target pixel has been visited
* or source pixel is not part of the same connected component.
* @param <T>
* input pixel type
* @param <U>
* fill label type
*/
public static < T, U extends Type< U > > void fill(
final RandomAccessible< T > source,
final RandomAccessible< U > target,
final Localizable seed,
final U fillLabel,
final Shape shape,
final BiPredicate< T, U > filter )
{
fill( source, target, seed, shape, filter, targetPixel -> targetPixel.set( fillLabel ) );
}

/**
*
* Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting
* at seed location, write fillLabel into target at current location and
* continue for each pixel in neighborhood defined by shape if neighborhood
* pixel is in the same connected component and fillLabel has not been
* written into that location yet (comparator evaluates to 0).
*
* @param source
* input
* @param target
* {@link RandomAccessible} to be written into. May be the same
* as input.
* @param seed
* Start flood fill at this location.
* @param shape
* Defines neighborhood that is considered for connected
* components, e.g.
* {@link net.imglib2.algorithm.neighborhood.DiamondShape}
* @param filter
* Returns true if pixel has not been visited yet and should be
* written into. Returns false if target pixel has been visited
* or source pixel is not part of the same connected component.
* @param writer
* Defines how fill label is written into target at current
* location.
* @param <T>
* input pixel type
* @param <U>
* fill label type
*/
public static < T, U > void fill(
final RandomAccessible< T > source,
final RandomAccessible< U > target,
final Localizable seed,
final Shape shape,
final BiPredicate< T, U > filter,
final Consumer< U > writer )
{
final int n = source.numDimensions();

final RandomAccessible< Pair< T, U > > paired = Views.pair( source, target );

TLongList coordinates = new TLongArrayList();
for ( int d = 0; d < n; ++d )
{
coordinates.add( seed.getLongPosition( d ) );
}

final int cleanupThreshold = n * CLEANUP_THRESHOLD;

final RandomAccessible< Neighborhood< Pair< T, U > > > neighborhood = shape.neighborhoodsRandomAccessible( paired );
final RandomAccess< Neighborhood< Pair< T, U > > > neighborhoodAccess = neighborhood.randomAccess();

final RandomAccess< U > targetAccess = target.randomAccess();
targetAccess.setPosition( seed );
writer.accept( targetAccess.get() );

for ( int i = 0; i < coordinates.size(); i += n )
{
for ( int d = 0; d < n; ++d )
neighborhoodAccess.setPosition( coordinates.get( i + d ), d );

final Cursor< Pair< T, U > > neighborhoodCursor = neighborhoodAccess.get().cursor();

while ( neighborhoodCursor.hasNext() )
{
final Pair< T, U > p = neighborhoodCursor.next();
if ( filter.test( p.getA(), p.getB() ) )
{
writer.accept( p.getB() );
for ( int d = 0; d < n; ++d )
coordinates.add( neighborhoodCursor.getLongPosition( d ) );
}
}

if ( i > cleanupThreshold )
{
// TODO should it start from i + n?
coordinates = coordinates.subList( i, coordinates.size() );
i = 0;
}

}

}

/**
* Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting
* at seed location, write fillLabel into target at current location and
@@ -84,10 +263,11 @@ public class FloodFill
* written into. Returns false if target pixel has been visited
* or source pixel is not part of the same connected component.
* @param <T>
* T implements {@code Type<U>}.
* input pixel type
* @param <U>
* U implements {@code Type<U>}.
* fill label type
*/
@Deprecated
public static < T extends Type< T >, U extends Type< U > > void fill( final RandomAccessible< T > source, final RandomAccessible< U > target, final Localizable seed, final U fillLabel, final Shape shape, final Filter< Pair< T, U >, Pair< T, U > > filter )
{
final RandomAccess< T > access = source.randomAccess();
@@ -123,10 +303,11 @@ public static < T extends Type< T >, U extends Type< U > > void fill( final Rand
* written into. Returns false if target pixel has been visited
* or source pixel is not part of the same connected component.
* @param <T>
* No restrictions on {@link T}.
* input pixel type
* @param <U>
* {@link U} implements {@code Type<U>}.
* fill label type
*/
@Deprecated
public static < T, U extends Type< U > > void fill( final RandomAccessible< T > source, final RandomAccessible< U > target, final Localizable seed, final T seedLabel, final U fillLabel, final Shape shape, final Filter< Pair< T, U >, Pair< T, U > > filter )
{
fill( source, target, seed, seedLabel, fillLabel, shape, filter, new TypeWriter< U >() );
@@ -164,17 +345,16 @@ public static < T, U extends Type< U > > void fill( final RandomAccessible< T >
* Defines how fillLabel is written into target at current
* location.
* @param <T>
* No restrictions on T. Appropriate comparator is the only
* requirement.
* input pixel type
* @param <U>
* No restrictions on U. Appropriate comparator and writer is the
* only requirement.
* fill label type
*/
@Deprecated
public static < T, U > void fill( final RandomAccessible< T > source, final RandomAccessible< U > target, final Localizable seed, final T seedLabel, final U fillLabel, final Shape shape, final Filter< Pair< T, U >, Pair< T, U > > filter, final Writer< U > writer )
{
final int n = source.numDimensions();

final ValuePair< T, U > reference = new ValuePair< T, U >( seedLabel, fillLabel );
final ValuePair< T, U > reference = new ValuePair<>( seedLabel, fillLabel );

final RandomAccessible< Pair< T, U > > paired = Views.pair( source, target );

1 change: 1 addition & 0 deletions src/main/java/net/imglib2/algorithm/fill/TypeWriter.java
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@
* @author Philipp Hanslovsky
* @author Stephan Saalfeld
*/
@Deprecated
public class TypeWriter< T extends Type< T > > implements Writer< T >
{
@Override
1 change: 1 addition & 0 deletions src/main/java/net/imglib2/algorithm/fill/Writer.java
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@
*
* @param <U>
*/
@Deprecated
public interface Writer< U >
{
void write( final U source, final U target );
67 changes: 60 additions & 7 deletions src/test/java/net/imglib2/algorithm/fill/FloodFillTest.java
Original file line number Diff line number Diff line change
@@ -61,7 +61,7 @@ public class FloodFillTest

private static final int[] N_DIMS = { 1, 2, 3, 4 };

private static final int SIZE_OF_EACH_DIM = 60;
private static final int SIZE_OF_EACH_DIM = 24;

private static < T extends IntegerType< T > > void runTest( final int nDim, final int sizeOfEachDim, final ImgFactory< T > imageFactory, final T t )
{
@@ -112,14 +112,66 @@ else if ( i.getLongPosition( 0 ) - c[ 0 ] > divisionLine )

final ExtendedRandomAccessibleInterval< T, Img< T > > extendedImg = Views.extendValue( img, fillLabel );

final Filter< Pair< T, T >, Pair< T, T > > filter = new Filter< Pair< T, T >, Pair< T, T > >()
FloodFill.fill( extendedImg, extendedImg, new Point( c ), fillLabel, new DiamondShape( 1 ) );

for ( Cursor< T > imgCursor = img.cursor(), refCursor = refImg.cursor(); imgCursor.hasNext(); )
{
Assert.assertEquals( refCursor.next(), imgCursor.next() );
}

}

@Deprecated
private static < T extends IntegerType< T > > void runTestDeprecated( final int nDim, final int sizeOfEachDim, final ImgFactory< T > imageFactory, final T t )
{
final long[] dim = new long[ nDim ];
final long[] c = new long[ nDim ];
final long r = sizeOfEachDim / 4;
for ( int d = 0; d < nDim; ++d )
{
@Override
public boolean accept( final Pair< T, T > p1, final Pair< T, T > p2 )
dim[ d ] = sizeOfEachDim;
c[ d ] = sizeOfEachDim / 3;
}

final long divisionLine = r / 3;

final Img< T > img = imageFactory.create( dim, t.copy() );
final Img< T > refImg = imageFactory.create( dim, t.copy() );

for ( Cursor< T > i = img.cursor(), ref = refImg.cursor(); i.hasNext(); )
{
i.fwd();
ref.fwd();
long diffSum = 0;
for ( int d = 0; d < nDim; ++d )
{
return ( p1.getB().getIntegerLong() != p2.getB().getIntegerLong() ) && ( p1.getA().getIntegerLong() == p2.getA().getIntegerLong() );
final long diff = i.getLongPosition( d ) - c[ d ];
diffSum += diff * diff;

}

if ( ( diffSum < r * r ) )
{
if ( ( i.getLongPosition( 0 ) - c[ 0 ] < divisionLine ) )
{
i.get().setInteger( START_LABEL );
ref.get().setInteger( FILL_LABEL );
}
else if ( i.getLongPosition( 0 ) - c[ 0 ] > divisionLine )
{
i.get().setInteger( START_LABEL );
ref.get().setInteger( START_LABEL );
}
}
};

}

final T fillLabel = t.createVariable();
fillLabel.setInteger( FILL_LABEL );

final ExtendedRandomAccessibleInterval< T, Img< T > > extendedImg = Views.extendValue( img, fillLabel );

final Filter< Pair< T, T >, Pair< T, T > > filter = ( p1, p2 ) -> ( p1.getB().getIntegerLong() != p2.getB().getIntegerLong() ) && ( p1.getA().getIntegerLong() == p2.getA().getIntegerLong() );

FloodFill.fill( extendedImg, extendedImg, new Point( c ), fillLabel, new DiamondShape( 1 ), filter );

@@ -135,7 +187,8 @@ public void runTests()
{
for ( final int nDim : N_DIMS )
{
runTest( nDim, SIZE_OF_EACH_DIM, new ArrayImgFactory< LongType >(), new LongType() );
runTestDeprecated( nDim, SIZE_OF_EACH_DIM, new ArrayImgFactory<>(), new LongType() );
runTest( nDim, SIZE_OF_EACH_DIM, new ArrayImgFactory<>(), new LongType() );
}
}

0 comments on commit 10bddc5

Please sign in to comment.