Skip to content

Commit eec8c6c

Browse files
committed
add connected components for affinity maps
1 parent 836d026 commit eec8c6c

File tree

2 files changed

+220
-1
lines changed

2 files changed

+220
-1
lines changed

src/main/java/net/imglib2/algorithm/labeling/ConnectedComponentAnalysis.java

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

3535
package net.imglib2.algorithm.labeling;
3636

37+
import java.util.Arrays;
3738
import java.util.function.LongFunction;
3839
import java.util.function.LongUnaryOperator;
3940
import java.util.function.ToLongBiFunction;
@@ -55,7 +56,10 @@
5556
import net.imglib2.type.numeric.IntegerType;
5657
import net.imglib2.util.IntervalIndexer;
5758
import net.imglib2.util.Intervals;
59+
import net.imglib2.util.Util;
60+
import net.imglib2.view.ExtendedRandomAccessibleInterval;
5861
import net.imglib2.view.Views;
62+
import net.imglib2.view.composite.RealComposite;
5963

6064
/**
6165
*
@@ -133,7 +137,8 @@ public static < B extends BooleanType< B >, L extends IntegerType< L > > void co
133137
*
134138
* Implementation of connected component analysis that uses
135139
* {@link IntArrayRankedUnionFind} to find sets of pixels that are connected
136-
* with respect to a neighborhood ({@code shape}) over a binary mask. {@code mask}
140+
* with respect to a neighborhood ({@code shape}) over a binary mask.
141+
* {@code mask}
137142
* and {@code labeling} are expected to have equal min and max.
138143
*
139144
* @param mask
@@ -313,7 +318,128 @@ private static < B extends BooleanType< B >, L extends IntegerType< L > > void c
313318
}
314319
}
315320
}
321+
}
322+
323+
/**
324+
* Connected components on a regular arbitrary boolean affinity graph.
325+
*
326+
* @param <B>
327+
* boolean type used for affinity scalars
328+
* @param <C>
329+
* affinity vector
330+
* @param <L>
331+
* label output must fit number of components
332+
* @param affinities
333+
* affinity vector for each pixel
334+
* @param affinityOffsets
335+
* offset vector for each affinity index
336+
* @param labeling
337+
* output
338+
* @param uf
339+
* union find
340+
* @param id
341+
* id generator for pixels/ locations
342+
* @param idForSet
343+
* id generator for components
344+
*/
345+
public static < B extends BooleanType< B >, C extends RealComposite< B >, L extends IntegerType< L > > void connectedComponentsOnAffinities(
346+
final RandomAccessible< C > affinities,
347+
final long[][] affinityOffsets,
348+
final RandomAccessibleInterval< L > labeling,
349+
final UnionFind uf,
350+
final ToLongBiFunction< Localizable, L > id,
351+
final LongUnaryOperator idForSet )
352+
{
353+
final ExtendedRandomAccessibleInterval< L, RandomAccessibleInterval< L > > extendedLabeling = Views.extendValue( labeling, Util.getTypeFromInterval( labeling ).createVariable() );
316354

355+
@SuppressWarnings( "unchecked" )
356+
final Cursor< L >[] offsetLabelingCursors = new Cursor[ affinityOffsets.length ];
357+
Arrays.setAll( offsetLabelingCursors, i -> Views.flatIterable( Views.interval( Views.offset( extendedLabeling, affinityOffsets[ i ] ), labeling ) ).cursor() );
358+
359+
final Cursor< L > target = Views.flatIterable( labeling ).cursor();
360+
final Cursor< C > affinitiesCursor = Views.flatIterable( Views.interval( affinities, labeling ) ).cursor();
361+
362+
while ( target.hasNext() )
363+
{
364+
final C affinitiesVector = affinitiesCursor.next();
365+
final L targetLabel = target.next();
366+
final long labelId = uf.findRoot( id.applyAsLong( target, targetLabel ) );
367+
targetLabel.setInteger( labelId );
368+
369+
for ( int i = 0; i < affinityOffsets.length; ++i )
370+
{
371+
offsetLabelingCursors[ i ].fwd();
372+
if ( affinitiesVector.get( i ).get() )
373+
{
374+
final long otherLabelId = uf.findRoot( id.applyAsLong( offsetLabelingCursors[ i ], offsetLabelingCursors[ i ].get() ) );
375+
if ( labelId != otherLabelId )
376+
uf.join( labelId, otherLabelId );
377+
}
378+
}
379+
}
380+
UnionFind.relabel( labeling, uf, id, idForSet );
317381
}
318382

383+
/**
384+
* Connected components on a regular arbitrary boolean affinity graph.
385+
* Each component gets the id of the first pixel (in flat iteration order)
386+
* inside the component increased by firstIndex.
387+
*
388+
* @param <B>
389+
* boolean type used for affinity scalars
390+
* @param <C>
391+
* affinity vector
392+
* @param <L>
393+
* label output must fit number of components
394+
* @param affinities
395+
* affinity vector for each pixel
396+
* @param affinityOffsets
397+
* offset vector for each affinity index
398+
* @param labeling
399+
* output
400+
* @param firstIndex
401+
*/
402+
public static < B extends BooleanType< B >, C extends RealComposite< B >, L extends IntegerType< L > > void connectedComponentsOnAffinities(
403+
final RandomAccessible< C > affinities,
404+
final long[][] affinityOffsets,
405+
final RandomAccessibleInterval< L > labeling,
406+
final UnionFind uf,
407+
final long firstIndex )
408+
{
409+
final ExtendedRandomAccessibleInterval< L, RandomAccessibleInterval< L > > extendedLabeling = Views.extendValue( labeling, Util.getTypeFromInterval( labeling ).createVariable() );
410+
411+
/* populate labeling with index */
412+
long index = firstIndex;
413+
for ( final L label : Views.flatIterable( labeling ) )
414+
{
415+
label.setInteger( index );
416+
++index;
417+
}
418+
419+
@SuppressWarnings( "unchecked" )
420+
final Cursor< L >[] offsetLabelingCursors = new Cursor[ affinityOffsets.length ];
421+
Arrays.setAll( offsetLabelingCursors, i -> Views.flatIterable( Views.interval( Views.offset( extendedLabeling, affinityOffsets[ i ] ), labeling ) ).cursor() );
422+
423+
final Cursor< L > target = Views.flatIterable( labeling ).cursor();
424+
final Cursor< C > affinitiesCursor = Views.flatIterable( Views.interval( affinities, labeling ) ).cursor();
425+
426+
while ( target.hasNext() )
427+
{
428+
final C affinitiesVector = affinitiesCursor.next();
429+
final L targetLabel = target.next();
430+
final long labelId = uf.findRoot( targetLabel.getIntegerLong() );
431+
432+
for ( int i = 0; i < affinityOffsets.length; ++i )
433+
{
434+
offsetLabelingCursors[ i ].fwd();
435+
if ( affinitiesVector.get( i ).get() )
436+
{
437+
final long otherLabelId = uf.findRoot( offsetLabelingCursors[ i ].get().getIntegerLong() );
438+
if ( labelId != otherLabelId )
439+
uf.join( labelId, otherLabelId );
440+
}
441+
}
442+
}
443+
uf.relabel( labeling );
444+
}
319445
}

src/test/java/net/imglib2/algorithm/labeling/ConnectedComponentAnalysisTest.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,28 @@
3434

3535
package net.imglib2.algorithm.labeling;
3636

37+
import java.util.function.LongUnaryOperator;
38+
3739
import org.junit.Assert;
3840
import org.junit.Test;
3941

42+
import gnu.trove.map.hash.TLongLongHashMap;
43+
import net.imglib2.RandomAccessible;
4044
import net.imglib2.RandomAccessibleInterval;
4145
import net.imglib2.algorithm.neighborhood.DiamondShape;
4246
import net.imglib2.algorithm.neighborhood.RectangleShape;
47+
import net.imglib2.algorithm.util.unionfind.IntArrayUnionFind;
48+
import net.imglib2.algorithm.util.unionfind.UnionFind;
4349
import net.imglib2.converter.Converters;
4450
import net.imglib2.img.array.ArrayImg;
4551
import net.imglib2.img.array.ArrayImgs;
4652
import net.imglib2.img.basictypeaccess.array.LongArray;
4753
import net.imglib2.type.logic.BitType;
54+
import net.imglib2.type.logic.BoolType;
55+
import net.imglib2.type.numeric.integer.ByteType;
4856
import net.imglib2.type.numeric.integer.UnsignedLongType;
4957
import net.imglib2.view.Views;
58+
import net.imglib2.view.composite.RealComposite;
5059

5160
/**
5261
*
@@ -150,6 +159,32 @@ public class ConnectedComponentAnalysisTest
150159
1, 0, 0, 0, 3
151160
};
152161

162+
private final long[] components = new long[] {
163+
0, 0, 0, 0, 0,
164+
0, 6, 7, 7, 0,
165+
0, 6, 6, 7, 0,
166+
0, 0, 0, 0, 0
167+
};
168+
169+
private final long[] componentsCompact = new long[] {
170+
1, 1, 1, 1, 1,
171+
1, 2, 3, 3, 1,
172+
1, 2, 2, 3, 1,
173+
1, 1, 1, 1, 1
174+
};
175+
176+
private final byte[] affinities = new byte[] {
177+
0, 1, 1, 1, 1,
178+
0, 0, 0, 1, 0,
179+
0, 0, 1, 0, 0,
180+
0, 1, 1, 1, 1,
181+
182+
0, 0, 0, 0, 0,
183+
1, 0, 0, 0, 1,
184+
1, 1, 0, 1, 1,
185+
1, 0, 0, 0, 1
186+
};
187+
153188
private final RandomAccessibleInterval< UnsignedLongType > maskStore2D = ArrayImgs.unsignedLongs( maskData2D, dims2D );
154189

155190
private final RandomAccessibleInterval< BitType > mask2D = Converters.convert( maskStore2D, ( s, t ) -> t.set( s.get() == 1 ), new BitType() );
@@ -265,4 +300,62 @@ private void testDefault3D()
265300
Assert.assertArrayEquals( labelingStore1, labelingStore2 );
266301
}
267302

303+
@Test
304+
public void testAffinities()
305+
{
306+
307+
final RandomAccessible< RealComposite< BoolType > > affinityMap = Views.collapseReal(
308+
Views.extendValue(
309+
Converters.convert(
310+
( RandomAccessibleInterval< ByteType > ) ArrayImgs.bytes( affinities, 5, 4, 2 ),
311+
( a, b ) -> { b.set( a.get() > 0 ); },
312+
new BoolType() ),
313+
new BoolType() ),
314+
2 );
315+
316+
final UnionFind uf = new IntArrayUnionFind( 4 * 5 );
317+
318+
final long[] labels = new long[ 4 * 5 ];
319+
final ArrayImg< UnsignedLongType, LongArray > labelMap = ArrayImgs.unsignedLongs( labels, 5, 4 );
320+
321+
ConnectedComponentAnalysis.connectedComponentsOnAffinities(
322+
affinityMap,
323+
new long[][] {
324+
{ -1, 0 },
325+
{ 0, -1 } },
326+
labelMap,
327+
uf,
328+
0 );
329+
330+
Assert.assertArrayEquals( components, labels );
331+
332+
final LongUnaryOperator startAtOneIdForNextSet = new LongUnaryOperator()
333+
{
334+
335+
private final TLongLongHashMap setMappings = new TLongLongHashMap();
336+
337+
@Override
338+
public long applyAsLong( final long root )
339+
{
340+
341+
if ( !setMappings.containsKey( root ) )
342+
{
343+
setMappings.put( root, setMappings.size() + 1 );
344+
}
345+
return setMappings.get( root );
346+
}
347+
};
348+
349+
ConnectedComponentAnalysis.connectedComponentsOnAffinities(
350+
affinityMap,
351+
new long[][] {
352+
{ -1, 0 },
353+
{ 0, -1 } },
354+
labelMap,
355+
uf,
356+
ConnectedComponentAnalysis.idFromIntervalIndexer( labelMap ),
357+
startAtOneIdForNextSet );
358+
359+
Assert.assertArrayEquals( componentsCompact, labels );
360+
}
268361
}

0 commit comments

Comments
 (0)