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

Update FloodFill interface #58

Merged
merged 3 commits into from
Apr 18, 2018
Merged
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
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
Expand Up @@ -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 );
Expand Down
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
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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();
Expand Down Expand Up @@ -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 >() );
Expand Down Expand Up @@ -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 );

Expand Down
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
Expand Up @@ -42,6 +42,7 @@
* @author Philipp Hanslovsky
* @author Stephan Saalfeld
*/
@Deprecated
public class TypeWriter< T extends Type< T > > implements Writer< T >
{
@Override
Expand Down
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
Expand Up @@ -42,6 +42,7 @@
*
* @param <U>
*/
@Deprecated
public interface Writer< U >
{
void write( final U source, final U target );
Expand Down
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
Expand Up @@ -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 )
{
Expand Down Expand Up @@ -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 );

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

Expand Down