Skip to content

Commit bb12a4f

Browse files
authored
Merge pull request #90 from imglib/affinity-connected-components
* add connected components for affinity maps * bump to pom-scijava-29.2.1 * enable building on jdk >8 * bugfixes
2 parents f0fa11f + 02ffb21 commit bb12a4f

File tree

4 files changed

+312
-3
lines changed

4 files changed

+312
-3
lines changed

pom.xml

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>org.scijava</groupId>
77
<artifactId>pom-scijava</artifactId>
8-
<version>29.0.0</version>
8+
<version>29.2.1</version>
99
<relativePath />
1010
</parent>
1111

@@ -201,7 +201,12 @@ Jean-Yves Tinevez and Michael Zinsmaier.</license.copyrightOwners>
201201
<!-- NB: Deploy releases to the SciJava Maven repository. -->
202202
<releaseProfiles>deploy-to-scijava</releaseProfiles>
203203

204-
<imglib2.version>5.10.0</imglib2.version>
204+
<!-- TODO remove when pom-scijava-base is updated -->
205+
<scijava.jvm.version>8</scijava.jvm.version>
206+
<scijava.jvm.build.version>[1.8.0-101,)</scijava.jvm.build.version>
207+
208+
<maven-enforcer-plugin.version>3.0.0-M3</maven-enforcer-plugin.version>
209+
<jacoco-maven-plugin.version>0.8.6</jacoco-maven-plugin.version>
205210
</properties>
206211

207212
<repositories>
@@ -273,4 +278,16 @@ Jean-Yves Tinevez and Michael Zinsmaier.</license.copyrightOwners>
273278
</plugin>
274279
</plugins>
275280
</build>
281+
282+
<profiles>
283+
<profile>
284+
<id>java-9</id>
285+
<activation>
286+
<jdk>[9,)</jdk>
287+
</activation>
288+
<properties>
289+
<maven.compiler.release>${scijava.jvm.version}</maven.compiler.release>
290+
</properties>
291+
</profile>
292+
</profiles>
276293
</project>

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

Lines changed: 138 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,139 @@ 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+
uf.relabel( labeling, 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 =
410+
Views.extendValue( labeling, Util.getTypeFromInterval( labeling ).createVariable() );
411+
412+
/* populate labeling with index */
413+
long index = firstIndex;
414+
for ( final L label : Views.flatIterable( labeling ) )
415+
{
416+
label.setInteger( index );
417+
++index;
418+
}
419+
420+
@SuppressWarnings( "unchecked" )
421+
final Cursor< L >[] offsetLabelingCursors = new Cursor[ affinityOffsets.length ];
422+
Arrays.setAll( offsetLabelingCursors, i -> Views.flatIterable(
423+
Views.interval(
424+
Views.offset(
425+
extendedLabeling,
426+
affinityOffsets[ i ] ),
427+
labeling ) )
428+
.cursor() );
429+
430+
final Cursor< L > target = Views.flatIterable( labeling ).cursor();
431+
final Cursor< C > affinitiesCursor = Views.flatIterable( Views.interval( affinities, labeling ) ).cursor();
432+
433+
while ( target.hasNext() )
434+
{
435+
final C affinitiesVector = affinitiesCursor.next();
436+
final L targetLabel = target.next();
437+
long labelId = uf.findRoot( targetLabel.getIntegerLong() );
438+
439+
for ( int i = 0; i < affinityOffsets.length; ++i )
440+
{
441+
offsetLabelingCursors[ i ].fwd();
442+
if ( affinitiesVector.get( i ).get() )
443+
{
444+
final long otherLabelId = uf.findRoot( offsetLabelingCursors[ i ].get().getIntegerLong() );
445+
if ( labelId != otherLabelId )
446+
{
447+
uf.join( labelId, otherLabelId );
448+
if ( i + 1 < affinityOffsets.length )
449+
labelId = uf.findRoot( targetLabel.getIntegerLong() );
450+
}
451+
}
452+
}
453+
}
454+
uf.relabel( labeling );
455+
}
319456
}

src/main/java/net/imglib2/algorithm/util/unionfind/UnionFind.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,66 @@ static < B extends BooleanType< B >, L extends IntegerType< L > > void relabel(
126126
}
127127
}
128128

129+
/**
130+
*
131+
* Relabel all pixels into the representative id of their containing
132+
* sets as defined by {@code unionFind}.
133+
*
134+
* @param labeling
135+
* Output parameter to store labeling: background pixels are
136+
* labeled zero, foreground pixels are greater than zero: 1, 2,
137+
* ..., N. Note that this is expected to be zero as background
138+
* values will not be written.
139+
* @param unionFind
140+
* {@link UnionFind}
141+
* @param idForPixel
142+
* Create id from pixel location and value. Multiple calls with
143+
* the same argument should always return the same result.
144+
* @param idForSet
145+
* Create id for a set from the root id of a set. Multiple calls
146+
* with the same argument should always return the same result.
147+
*/
148+
public default < B extends BooleanType< B >, L extends IntegerType< L > > void relabel(
149+
final RandomAccessibleInterval< L > labeling,
150+
final ToLongBiFunction< Localizable, L > idForPixel,
151+
final LongUnaryOperator idForSet )
152+
{
153+
final Cursor< L > label = Views.flatIterable( labeling ).localizingCursor();
154+
while ( label.hasNext() )
155+
{
156+
final L l = label.next();
157+
final long root = findRoot( idForPixel.applyAsLong( label, l ) );
158+
l.setInteger( idForSet.applyAsLong( root ) );
159+
}
160+
}
161+
162+
/**
163+
* Relabel all pixels into the representative id of their containing
164+
* sets as defined by {@code unionFind}.
165+
*
166+
* @param labeling
167+
* Output parameter to store labeling: background pixels are
168+
* labeled zero, foreground pixels are greater than zero: 1, 2,
169+
* ..., N. Note that this is expected to be zero as background
170+
* values will not be written.
171+
* @param unionFind
172+
* {@link UnionFind}
173+
* @param idForPixel
174+
* Create id from pixel location and value. Multiple calls with
175+
* the same argument should always return the same result.
176+
* @param idForSet
177+
* Create id for a set from the root id of a set. Multiple calls
178+
* with the same argument should always return the same result.
179+
*/
180+
public default < L extends IntegerType< L > > void relabel(
181+
final RandomAccessibleInterval< L > labeling )
182+
{
183+
final Cursor< L > label = Views.flatIterable( labeling ).localizingCursor();
184+
while ( label.hasNext() )
185+
{
186+
final L l = label.next();
187+
final long root = findRoot( l.getIntegerLong() );
188+
l.setInteger( root );
189+
}
190+
}
129191
}

0 commit comments

Comments
 (0)