Skip to content

Commit 5a1d25a

Browse files
committed
Update FloodFill interface
Addresses #55 Current interface is not intuitive: - Deprecate confusing interfaces: - Filter - Writer - TypeWriter - Replace by: - BiPredicate - Consumer - Deprecate current interfaces and add new interfaces using BiPredicate/Consumer Also, use only single storage array for coordinate queue. This has potential benefits (cache locality) but needs benchmarking.
1 parent 5420e63 commit 5a1d25a

File tree

5 files changed

+254
-8
lines changed

5 files changed

+254
-8
lines changed

src/main/java/net/imglib2/algorithm/fill/Filter.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,16 @@
3535
package net.imglib2.algorithm.fill;
3636

3737
/**
38-
* Interface for comparing {@link T} t and {@link U} u and accepting
39-
* them as equivalent in a sense specified by implementation thereof.
38+
* Interface for comparing {@link T} t and {@link U} u and accepting them as
39+
* equivalent in a sense specified by implementation thereof.
4040
*
4141
* @author Philipp Hanslovsky
4242
* @author Stephan Saalfeld
4343
*
4444
* @param <T>
4545
* @param <U>
4646
*/
47+
@Deprecated
4748
public interface Filter< T, U >
4849
{
4950
boolean accept( T t, U u );

src/main/java/net/imglib2/algorithm/fill/FloodFill.java

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434

3535
package net.imglib2.algorithm.fill;
3636

37+
import java.util.function.BiPredicate;
38+
import java.util.function.Consumer;
39+
3740
import gnu.trove.list.TLongList;
3841
import gnu.trove.list.array.TLongArrayList;
3942
import net.imglib2.Cursor;
@@ -58,6 +61,190 @@ public class FloodFill
5861
// int or long? current TLongList cannot store more than Integer.MAX_VALUE
5962
private static final int CLEANUP_THRESHOLD = ( int ) 1e5;
6063

64+
/**
65+
* Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting
66+
* at seed location, write fillLabel into target at current location and
67+
* continue for each pixel in neighborhood defined by shape if neighborhood
68+
* pixel is in the same connected component and fillLabel has not been
69+
* written into that location yet (comparator evaluates to 0).
70+
*
71+
* Convenience call to
72+
* {@link FloodFill#fill(RandomAccessible, RandomAccessible, Localizable, Object, Type, Shape, BiPredicate)}.
73+
* seedLabel is extracted from source at seed location.
74+
*
75+
* @param source
76+
* input
77+
* @param target
78+
* {@link RandomAccessible} to be written into. May be the same
79+
* as input.
80+
* @param seed
81+
* Start flood fill at this location.
82+
* @param fillLabel
83+
* Immutable. Value to be written into valid flood fill
84+
* locations.
85+
* @param shape
86+
* Defines neighborhood that is considered for connected
87+
* components, e.g.
88+
* {@link net.imglib2.algorithm.neighborhood.DiamondShape}
89+
* @param <T>
90+
* T implements {@code Type<U>}.
91+
* @param <U>
92+
* U implements {@code Type<U>}.
93+
*/
94+
public static < T extends Type< T >, U extends Type< U > > void fill(
95+
final RandomAccessible< T > source,
96+
final RandomAccessible< U > target,
97+
final Localizable seed,
98+
final U fillLabel,
99+
final Shape shape )
100+
{
101+
final RandomAccess< T > access = source.randomAccess();
102+
access.setPosition( seed );
103+
final T seedValue = access.get().copy();
104+
final BiPredicate< T, U > filter = ( t, u ) -> t.valueEquals( seedValue ) && !u.valueEquals( fillLabel );
105+
fill( source, target, seed, fillLabel, shape, filter );
106+
}
107+
108+
/**
109+
* Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting
110+
* at seed location, write fillLabel into target at current location and
111+
* continue for each pixel in neighborhood defined by shape if neighborhood
112+
* pixel is in the same connected component and fillLabel has not been
113+
* written into that location yet (comparator evaluates to 0).
114+
*
115+
* Convenience call to
116+
* {@link FloodFill#fill(RandomAccessible, RandomAccessible, Localizable, Object, Object, Shape, BiPredicate, Consumer)}
117+
* with {@link TypeWriter} as writer.
118+
*
119+
* @param source
120+
* input
121+
* @param target
122+
* {@link RandomAccessible} to be written into. May be the same
123+
* as input.
124+
* @param seed
125+
* Start flood fill at this location.
126+
* @param fillLabel
127+
* Immutable. Value to be written into valid flood fill
128+
* locations.
129+
* @param shape
130+
* Defines neighborhood that is considered for connected
131+
* components, e.g.
132+
* {@link net.imglib2.algorithm.neighborhood.DiamondShape}
133+
* @param filter
134+
* Returns true if pixel has not been visited yet and should be
135+
* written into. Returns false if target pixel has been visited
136+
* or source pixel is not part of the same connected component.
137+
* @param <T>
138+
* No restrictions on {@link T}.
139+
* @param <U>
140+
* {@link U} implements {@code Type<U>}.
141+
*/
142+
public static < T, U extends Type< U > > void fill(
143+
final RandomAccessible< T > source,
144+
final RandomAccessible< U > target,
145+
final Localizable seed,
146+
final U fillLabel,
147+
final Shape shape,
148+
final BiPredicate< T, U > filter )
149+
{
150+
fill( source, target, seed, shape, filter, targetPixel -> targetPixel.set( fillLabel ) );
151+
}
152+
153+
/**
154+
*
155+
* Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting
156+
* at seed location, write fillLabel into target at current location and
157+
* continue for each pixel in neighborhood defined by shape if neighborhood
158+
* pixel is in the same connected component and fillLabel has not been
159+
* written into that location yet (comparator evaluates to 0).
160+
*
161+
* @param source
162+
* input
163+
* @param target
164+
* {@link RandomAccessible} to be written into. May be the same
165+
* as input.
166+
* @param seed
167+
* Start flood fill at this location.
168+
* @param shape
169+
* Defines neighborhood that is considered for connected
170+
* components, e.g.
171+
* {@link net.imglib2.algorithm.neighborhood.DiamondShape}
172+
* @param filter
173+
* Returns true if pixel has not been visited yet and should be
174+
* written into. Returns false if target pixel has been visited
175+
* or source pixel is not part of the same connected component.
176+
* @param writer
177+
* Defines how fill label is written into target at current
178+
* location.
179+
* @param <T>
180+
* No restrictions on T. Appropriate filter is the only
181+
* requirement.
182+
* @param <U>
183+
* No restrictions on U. Appropriate filter and writer is the
184+
* only requirement.
185+
*/
186+
public static < T, U > void fill(
187+
final RandomAccessible< T > source,
188+
final RandomAccessible< U > target,
189+
final Localizable seed,
190+
final Shape shape,
191+
final BiPredicate< T, U > filter,
192+
final Consumer< U > writer )
193+
{
194+
final int n = source.numDimensions();
195+
196+
final RandomAccessible< Pair< T, U > > paired = Views.pair( source, target );
197+
198+
TLongList coordinates = new TLongArrayList();
199+
for ( int d = 0; d < n; ++d )
200+
{
201+
coordinates.add( seed.getLongPosition( d ) );
202+
}
203+
204+
// final TLongList[] coordinates = new TLongList[ n ];
205+
// for ( int d = 0; d < n; ++d )
206+
// {
207+
// coordinates[ d ] = new TLongArrayList();
208+
// coordinates[ d ].add( seed.getLongPosition( d ) );
209+
// }
210+
final int cleanupThreshold = n * CLEANUP_THRESHOLD;
211+
212+
final RandomAccessible< Neighborhood< Pair< T, U > > > neighborhood = shape.neighborhoodsRandomAccessible( paired );
213+
final RandomAccess< Neighborhood< Pair< T, U > > > neighborhoodAccess = neighborhood.randomAccess();
214+
215+
final RandomAccess< U > targetAccess = target.randomAccess();
216+
targetAccess.setPosition( seed );
217+
writer.accept( targetAccess.get() );
218+
219+
for ( int i = 0; i < coordinates.size(); i += n )
220+
{
221+
for ( int d = 0; d < n; ++d )
222+
neighborhoodAccess.setPosition( coordinates.get( i + d ), d );
223+
224+
final Cursor< Pair< T, U > > neighborhoodCursor = neighborhoodAccess.get().cursor();
225+
226+
while ( neighborhoodCursor.hasNext() )
227+
{
228+
final Pair< T, U > p = neighborhoodCursor.next();
229+
if ( filter.test( p.getA(), p.getB() ) )
230+
{
231+
writer.accept( p.getB() );
232+
for ( int d = 0; d < n; ++d )
233+
coordinates.add( neighborhoodCursor.getLongPosition( d ) );
234+
}
235+
}
236+
237+
if ( i > cleanupThreshold )
238+
{
239+
// TODO should it start from i + n?
240+
coordinates = coordinates.subList( i, coordinates.size() );
241+
i = 0;
242+
}
243+
244+
}
245+
246+
}
247+
61248
/**
62249
* Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting
63250
* at seed location, write fillLabel into target at current location and
@@ -88,6 +275,7 @@ public class FloodFill
88275
* @param <U>
89276
* U implements {@code Type<U>}.
90277
*/
278+
@Deprecated
91279
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 )
92280
{
93281
final RandomAccess< T > access = source.randomAccess();
@@ -127,6 +315,7 @@ public static < T extends Type< T >, U extends Type< U > > void fill( final Rand
127315
* @param <U>
128316
* {@link U} implements {@code Type<U>}.
129317
*/
318+
@Deprecated
130319
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 )
131320
{
132321
fill( source, target, seed, seedLabel, fillLabel, shape, filter, new TypeWriter< U >() );
@@ -170,11 +359,12 @@ public static < T, U extends Type< U > > void fill( final RandomAccessible< T >
170359
* No restrictions on U. Appropriate comparator and writer is the
171360
* only requirement.
172361
*/
362+
@Deprecated
173363
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 )
174364
{
175365
final int n = source.numDimensions();
176366

177-
final ValuePair< T, U > reference = new ValuePair< T, U >( seedLabel, fillLabel );
367+
final ValuePair< T, U > reference = new ValuePair<>( seedLabel, fillLabel );
178368

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

src/main/java/net/imglib2/algorithm/fill/TypeWriter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
* @author Philipp Hanslovsky
4343
* @author Stephan Saalfeld
4444
*/
45+
@Deprecated
4546
public class TypeWriter< T extends Type< T > > implements Writer< T >
4647
{
4748
@Override

src/main/java/net/imglib2/algorithm/fill/Writer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
*
4343
* @param <U>
4444
*/
45+
@Deprecated
4546
public interface Writer< U >
4647
{
4748
void write( final U source, final U target );

src/test/java/net/imglib2/algorithm/fill/FloodFillTest.java

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,66 @@ else if ( i.getLongPosition( 0 ) - c[ 0 ] > divisionLine )
112112

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

115-
final Filter< Pair< T, T >, Pair< T, T > > filter = new Filter< Pair< T, T >, Pair< T, T > >()
115+
FloodFill.fill( extendedImg, extendedImg, new Point( c ), fillLabel, new DiamondShape( 1 ) );
116+
117+
for ( Cursor< T > imgCursor = img.cursor(), refCursor = refImg.cursor(); imgCursor.hasNext(); )
118+
{
119+
Assert.assertEquals( refCursor.next(), imgCursor.next() );
120+
}
121+
122+
}
123+
124+
@Deprecated
125+
private static < T extends IntegerType< T > > void runTestDeprecated( final int nDim, final int sizeOfEachDim, final ImgFactory< T > imageFactory, final T t )
126+
{
127+
final long[] dim = new long[ nDim ];
128+
final long[] c = new long[ nDim ];
129+
final long r = sizeOfEachDim / 4;
130+
for ( int d = 0; d < nDim; ++d )
116131
{
117-
@Override
118-
public boolean accept( final Pair< T, T > p1, final Pair< T, T > p2 )
132+
dim[ d ] = sizeOfEachDim;
133+
c[ d ] = sizeOfEachDim / 3;
134+
}
135+
136+
final long divisionLine = r / 3;
137+
138+
final Img< T > img = imageFactory.create( dim, t.copy() );
139+
final Img< T > refImg = imageFactory.create( dim, t.copy() );
140+
141+
for ( Cursor< T > i = img.cursor(), ref = refImg.cursor(); i.hasNext(); )
142+
{
143+
i.fwd();
144+
ref.fwd();
145+
long diffSum = 0;
146+
for ( int d = 0; d < nDim; ++d )
119147
{
120-
return ( p1.getB().getIntegerLong() != p2.getB().getIntegerLong() ) && ( p1.getA().getIntegerLong() == p2.getA().getIntegerLong() );
148+
final long diff = i.getLongPosition( d ) - c[ d ];
149+
diffSum += diff * diff;
150+
151+
}
152+
153+
if ( ( diffSum < r * r ) )
154+
{
155+
if ( ( i.getLongPosition( 0 ) - c[ 0 ] < divisionLine ) )
156+
{
157+
i.get().setInteger( START_LABEL );
158+
ref.get().setInteger( FILL_LABEL );
159+
}
160+
else if ( i.getLongPosition( 0 ) - c[ 0 ] > divisionLine )
161+
{
162+
i.get().setInteger( START_LABEL );
163+
ref.get().setInteger( START_LABEL );
164+
}
121165
}
122-
};
166+
167+
}
168+
169+
final T fillLabel = t.createVariable();
170+
fillLabel.setInteger( FILL_LABEL );
171+
172+
final ExtendedRandomAccessibleInterval< T, Img< T > > extendedImg = Views.extendValue( img, fillLabel );
173+
174+
final Filter< Pair< T, T >, Pair< T, T > > filter = ( p1, p2 ) -> ( p1.getB().getIntegerLong() != p2.getB().getIntegerLong() ) && ( p1.getA().getIntegerLong() == p2.getA().getIntegerLong() );
123175

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

@@ -135,6 +187,7 @@ public void runTests()
135187
{
136188
for ( final int nDim : N_DIMS )
137189
{
190+
runTestDeprecated( nDim, SIZE_OF_EACH_DIM, new ArrayImgFactory< LongType >(), new LongType() );
138191
runTest( nDim, SIZE_OF_EACH_DIM, new ArrayImgFactory< LongType >(), new LongType() );
139192
}
140193
}

0 commit comments

Comments
 (0)