From 67ae250cf26000398f0ef54de9d95afe671765c1 Mon Sep 17 00:00:00 2001 From: Alison Walter Date: Fri, 25 Jan 2019 14:51:17 +0100 Subject: [PATCH 1/6] Add class for MaskIntervals with an associated label --- .../roi/mask/integer/LabeledMaskInterval.java | 101 +++++++++++++++ .../mask/integer/LabeledMaskIntervalTest.java | 121 ++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 src/main/java/net/imglib2/roi/mask/integer/LabeledMaskInterval.java create mode 100644 src/test/java/net/imglib2/roi/mask/integer/LabeledMaskIntervalTest.java diff --git a/src/main/java/net/imglib2/roi/mask/integer/LabeledMaskInterval.java b/src/main/java/net/imglib2/roi/mask/integer/LabeledMaskInterval.java new file mode 100644 index 000000000..be796ac6e --- /dev/null +++ b/src/main/java/net/imglib2/roi/mask/integer/LabeledMaskInterval.java @@ -0,0 +1,101 @@ +/*- + * #%L + * ImgLib2: a general-purpose, multidimensional image processing library. + * %% + * Copyright (C) 2009 - 2017 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld, + * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke, + * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner, + * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert, + * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin, + * Jean-Yves Tinevez and Michael Zinsmaier. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package net.imglib2.roi.mask.integer; + +import java.util.Objects; + +import net.imglib2.AbstractWrappedInterval; +import net.imglib2.Localizable; +import net.imglib2.roi.BoundaryType; +import net.imglib2.roi.KnownConstant; +import net.imglib2.roi.MaskInterval; + +/** + * A {@link MaskInterval} with an associated label. + * + * @author Alison Walter + * @param + * The type of labels assigned to the points + */ +public class LabeledMaskInterval< T > extends AbstractWrappedInterval< MaskInterval > implements MaskInterval +{ + + private final T label; + + public LabeledMaskInterval( final MaskInterval source, final T label ) + { + super( source ); + this.label = label; + } + + public T getLabel() + { + return label; + } + + @Override + public boolean test( final Localizable t ) + { + return getSource().test( t ); + } + + @Override + public BoundaryType boundaryType() + { + return getSource().boundaryType(); + } + + @Override + public KnownConstant knownConstant() + { + return getSource().knownConstant(); + } + + @Override + public boolean equals( final Object obj ) + { + if ( obj == null ) + return false; + if ( !( obj instanceof LabeledMaskInterval ) ) + return false; + final LabeledMaskInterval< ? > other = ( LabeledMaskInterval< ? > ) obj; + return getSource().equals( other.getSource() ) && label.equals( other.getLabel() ); + } + + @Override + public int hashCode() + { + return Objects.hash( getSource(), label ); + } +} diff --git a/src/test/java/net/imglib2/roi/mask/integer/LabeledMaskIntervalTest.java b/src/test/java/net/imglib2/roi/mask/integer/LabeledMaskIntervalTest.java new file mode 100644 index 000000000..9673e10a0 --- /dev/null +++ b/src/test/java/net/imglib2/roi/mask/integer/LabeledMaskIntervalTest.java @@ -0,0 +1,121 @@ +/*- + * #%L + * ImgLib2: a general-purpose, multidimensional image processing library. + * %% + * Copyright (C) 2009 - 2017 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld, + * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke, + * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner, + * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert, + * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin, + * Jean-Yves Tinevez and Michael Zinsmaier. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package net.imglib2.roi.mask.integer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import net.imglib2.FinalInterval; +import net.imglib2.Point; +import net.imglib2.roi.BoundaryType; +import net.imglib2.roi.KnownConstant; +import net.imglib2.roi.MaskInterval; + +/** + * Tests for {@link LabeledMaskInterval}. + * + * @author Alison Walter + */ +public class LabeledMaskIntervalTest +{ + + @Test + public void testMaskProperties() + { + final MaskInterval b = create2DBox( new long[] { 10, 12 }, new long[] { 15, 20 } ); + final LabeledMaskInterval< String > lb = new LabeledMaskInterval<>( b, "test" ); + + assertEquals( "test", lb.getLabel() ); + assertEquals( b.numDimensions(), lb.numDimensions() ); + assertEquals( b.min( 0 ), lb.min( 0 ) ); + assertEquals( b.max( 0 ), lb.max( 0 ) ); + assertEquals( b.min( 1 ), lb.min( 1 ) ); + assertEquals( b.max( 1 ), lb.max( 1 ) ); + assertEquals( b.boundaryType(), lb.boundaryType() ); + assertEquals( b.knownConstant(), lb.knownConstant() ); + + final Point pt = new Point( new long[] { 10, 18 } ); + assertEquals( b.test( pt ), lb.test( pt ) ); + pt.setPosition( new long[] { 12, 15 } ); + assertEquals( b.test( pt ), lb.test( pt ) ); + pt.setPosition( new long[] { 15, 20 } ); + assertEquals( b.test( pt ), lb.test( pt ) ); + pt.setPosition( new long[] { 0, 4 } ); + assertEquals( b.test( pt ), lb.test( pt ) ); + } + + @Test + public void testEquals() + { + final MaskInterval b1 = create2DBox( new long[] { 5, 5 }, new long[] { 10, 10 } ); + final MaskInterval b2 = create2DBox( new long[] { 8, 10 }, new long[] { 11, 13 } ); + final LabeledMaskInterval< String > labelB1 = new LabeledMaskInterval<>( b1, "test" ); + final LabeledMaskInterval< String > dupLabelB1 = new LabeledMaskInterval<>( b1, "test" ); + final LabeledMaskInterval< String > diffLabelB1 = new LabeledMaskInterval<>( b1, "not test" ); + final LabeledMaskInterval< String > labelB2 = new LabeledMaskInterval< String >( b2, "test" ); + + assertTrue( "Masks with same source and label should be equal!", labelB1.equals( dupLabelB1 ) ); + assertFalse( "Masks with same source different label should not be equal!", labelB1.equals( diffLabelB1 ) ); + assertFalse( "Masks with different sources and equivalent labels should not be equal!", labelB1.equals( labelB2 ) ); + } + + @Test + public void testHashCode() + { + final MaskInterval b1 = create2DBox( new long[] { 5, 5 }, new long[] { 10, 10 } ); + final MaskInterval b2 = create2DBox( new long[] { 8, 10 }, new long[] { 11, 13 } ); + final LabeledMaskInterval< String > labelB1 = new LabeledMaskInterval<>( b1, "test" ); + final LabeledMaskInterval< String > dupLabelB1 = new LabeledMaskInterval<>( b1, "test" ); + final LabeledMaskInterval< String > diffLabelB1 = new LabeledMaskInterval<>( b1, "not test" ); + final LabeledMaskInterval< String > labelB2 = new LabeledMaskInterval< String >( b2, "test" ); + + assertEquals( labelB1.hashCode(), dupLabelB1.hashCode() ); + assertNotEquals( labelB1.hashCode(), diffLabelB1.hashCode() ); + assertNotEquals( labelB1.hashCode(), labelB2.hashCode() ); + } + + // -- Helper methods -- + + private static MaskInterval create2DBox( final long[] min, final long[] max ) + { + return new DefaultMaskInterval( new FinalInterval( min, max ), BoundaryType.CLOSED, // + p -> p.getLongPosition( 0 ) >= min[ 0 ] && p.getLongPosition( 0 ) <= max[ 0 ] && // + p.getLongPosition( 1 ) >= min[ 1 ] && p.getLongPosition( 1 ) <= max[ 1 ], // + KnownConstant.UNKNOWN ); + } +} From ad536f8d11078c467fdd01e5b31633d44811f036 Mon Sep 17 00:00:00 2001 From: Alison Walter Date: Fri, 25 Jan 2019 15:20:42 +0100 Subject: [PATCH 2/6] Add methods for wrapping LabelRegion as MaskInterval and test --- src/main/java/net/imglib2/roi/Masks.java | 43 ++++ .../roi/mask/integer/LabelingToMaskTest.java | 205 ++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 src/test/java/net/imglib2/roi/mask/integer/LabelingToMaskTest.java diff --git a/src/main/java/net/imglib2/roi/Masks.java b/src/main/java/net/imglib2/roi/Masks.java index e385b5bf4..b70119165 100644 --- a/src/main/java/net/imglib2/roi/Masks.java +++ b/src/main/java/net/imglib2/roi/Masks.java @@ -33,7 +33,9 @@ */ package net.imglib2.roi; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.function.Predicate; import net.imglib2.FinalInterval; @@ -46,8 +48,13 @@ import net.imglib2.RealLocalizable; import net.imglib2.RealRandomAccessible; import net.imglib2.RealRandomAccessibleRealInterval; +import net.imglib2.roi.labeling.ImgLabeling; +import net.imglib2.roi.labeling.LabelRegion; +import net.imglib2.roi.labeling.LabelRegions; +import net.imglib2.roi.labeling.LabelingType; import net.imglib2.roi.mask.integer.DefaultMask; import net.imglib2.roi.mask.integer.DefaultMaskInterval; +import net.imglib2.roi.mask.integer.LabeledMaskInterval; import net.imglib2.roi.mask.integer.MaskAsRandomAccessible; import net.imglib2.roi.mask.integer.MaskIntervalAsRandomAccessibleInterval; import net.imglib2.roi.mask.integer.RandomAccessibleAsMask; @@ -332,6 +339,42 @@ public static < B extends BooleanType< B > > RealMaskRealInterval toRealMaskReal return new RealRandomAccessibleRealIntervalAsRealMaskRealInterval<>( rrari ); } + /** + * Wraps the given {@link LabelRegion} as a {@link MaskInterval}. + * + * @param region + * the {@code LabelRegion} to wrap + * @return a {@link LabeledMaskInterval} equivalent to the given + * {@code LabelRegion} + */ + public static < T > LabeledMaskInterval< T > toMaskInterval( final LabelRegion< T > region ) + { + final MaskInterval mask = toMaskInterval( ( RandomAccessibleInterval< BoolType > ) region ); + return new LabeledMaskInterval<>( mask, region.getLabel() ); + } + + /* + * Mask/Labeling Conversions + * =============================================================== + */ + + /** + * Extracts a {@link MaskInterval} for each label in the given labeling. + * + * @param labeling + * an {@link ImgLabeling}, view on ImgLabeling, etc. from which + * the {@code MaskInterval}s will be extracted + * @return a list of {@code MaskInterval}s, one for each label in the + * labeling + */ + public static < T > List< LabeledMaskInterval< T > > extractMaskIntervals( final RandomAccessibleInterval< LabelingType< T > > labeling ) + { + final List< LabeledMaskInterval< T > > masks = new ArrayList<>(); + final LabelRegions< T > regions = new LabelRegions<>( labeling ); + regions.forEach( r -> masks.add( toMaskInterval( r ) ) ); + return masks; + } + /* * Empty Masks * =============================================================== diff --git a/src/test/java/net/imglib2/roi/mask/integer/LabelingToMaskTest.java b/src/test/java/net/imglib2/roi/mask/integer/LabelingToMaskTest.java new file mode 100644 index 000000000..b19b5fa30 --- /dev/null +++ b/src/test/java/net/imglib2/roi/mask/integer/LabelingToMaskTest.java @@ -0,0 +1,205 @@ +/*- + * #%L + * ImgLib2: a general-purpose, multidimensional image processing library. + * %% + * Copyright (C) 2009 - 2017 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld, + * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke, + * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner, + * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert, + * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin, + * Jean-Yves Tinevez and Michael Zinsmaier. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package net.imglib2.roi.mask.integer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Random; + +import org.junit.Test; + +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.Localizable; +import net.imglib2.Point; +import net.imglib2.RandomAccess; +import net.imglib2.img.Img; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.roi.MaskInterval; +import net.imglib2.roi.Masks; +import net.imglib2.roi.labeling.ImgLabeling; +import net.imglib2.roi.labeling.LabelRegion; +import net.imglib2.roi.labeling.LabelRegions; +import net.imglib2.roi.labeling.LabelingType; +import net.imglib2.type.numeric.IntegerType; +import net.imglib2.type.numeric.integer.IntType; + +/** + * Tests for converting {@link LabelRegion}s and {@link ImgLabeling} to + * {@link MaskInterval}. + * + * @author Alison Walter + */ +public class LabelingToMaskTest +{ + + @Test + public void testLabelRegionToMask() + { + final Img< IntType > indexImg = ArrayImgs.ints( 10, 10 ); + final ImgLabeling< String, IntType > imgLabeling = new ImgLabeling<>( indexImg ); + final long[][] labeledPoints = addLabelToLabeling( imgLabeling, "testing", 4923, 10 ); + + final LabelRegion< String > region = new LabelRegions<>( imgLabeling ).getLabelRegion( "testing" ); + final LabeledMaskInterval< String > mask = Masks.toMaskInterval( region ); + + intervalsEqual( region, mask ); + assertEquals( region.getLabel(), mask.getLabel() ); + locationsContained( labeledPoints, mask ); + } + + @Test + public void testImgLabelingToMasks() + { + final Img< IntType > indexImg = ArrayImgs.ints( 100, 100 ); + final ImgLabeling< Long, IntType > imgLabeling = new ImgLabeling<>( indexImg ); + final long[][] labelOnePts = addLabelToLabeling( imgLabeling, 100l, 203, 100 ); + final long[][] labelTwoPts = addLabelToLabeling( imgLabeling, 200l, 984312, 100 ); + final long[][] labelThreePts = addLabelToLabeling( imgLabeling, 300l, -8932149, 100 ); + final long[][] labelFourPts = addLabelToLabeling( imgLabeling, 400l, -7159, 100 ); + + final List< LabeledMaskInterval< Long > > masks = Masks.extractMaskIntervals( imgLabeling ); + final HashMap< Long, LabeledMaskInterval< Long > > map = new HashMap<>( masks.size() ); + masks.forEach( l -> map.put( l.getLabel(), l ) ); + + assertEquals( 4, masks.size() ); + intervalsEqual( createInterval( labelOnePts ), map.get( 100l ) ); + intervalsEqual( createInterval( labelTwoPts ), map.get( 200l ) ); + intervalsEqual( createInterval( labelThreePts ), map.get( 300l ) ); + intervalsEqual( createInterval( labelFourPts ), map.get( 400l ) ); + locationsContained( labelOnePts, map.get( 100l ) ); + locationsContained( labelTwoPts, map.get( 200l ) ); + locationsContained( labelThreePts, map.get( 300l ) ); + locationsContained( labelFourPts, map.get( 400l ) ); + } + + @Test + public void testImgLabelingToMasks4D() + { + final Img< IntType > indexImg = ArrayImgs.ints( 10, 5, 20, 4 ); + final ImgLabeling< Character, IntType > imgLabeling = new ImgLabeling<>( indexImg ); + final long[][] labelOnePts = addLabelToLabeling( imgLabeling, 'q', 932, 500 ); + final long[][] labelTwoPts = addLabelToLabeling( imgLabeling, 'w', 43, 500 ); + + final List< LabeledMaskInterval< Character > > masks = Masks.extractMaskIntervals( imgLabeling ); + final HashMap< Character, LabeledMaskInterval< Character > > map = new HashMap<>( masks.size() ); + masks.forEach( l -> map.put( l.getLabel(), l ) ); + + assertEquals( 2, masks.size() ); + intervalsEqual( createInterval( labelOnePts ), map.get( 'q' ) ); + intervalsEqual( createInterval( labelTwoPts ), map.get( 'w' ) ); + locationsContained( labelOnePts, map.get( 'q' ) ); + locationsContained( labelTwoPts, map.get( 'w' ) ); + } + + // -- Helper methods -- + + private static < T, I extends IntegerType< I > > long[][] addLabelToLabeling( final ImgLabeling< T, I > labeling, final T label, final long seed, final long labelLimit ) + { + final RandomAccess< LabelingType< T > > ra = labeling.randomAccess(); + final Random rand = new Random( seed ); + final int numLabels = ( int ) Math.round( rand.nextDouble() * labelLimit ); + final long[][] points = new long[ numLabels ][ labeling.numDimensions() ]; + + for ( int i = 0; i < numLabels; i++ ) + { + for ( int d = 0; d < labeling.numDimensions(); d++ ) + { + ra.setPosition( rand.nextInt( ( int ) labeling.max( d ) ), d ); + } + ra.get().add( label ); + ra.localize( points[ i ] ); + } + return points; + } + + private static void intervalsEqual( final Interval expected, final Interval actual ) + { + assertEquals( expected.numDimensions(), actual.numDimensions() ); + for ( int d = 0; d < expected.numDimensions(); d++ ) + { + assertEquals( expected.min( d ), actual.min( d ) ); + assertEquals( expected.max( d ), actual.max( d ) ); + } + } + + private static < T > void locationsContained( final long[][] labeledPoints, final LabeledMaskInterval< T > mask ) + { + final Point p = new Point( mask.numDimensions() ); + for ( int i = 0; i < labeledPoints.length; i++ ) + { + p.setPosition( labeledPoints[ i ] ); + assertTrue( "Mask should contain location " + locationToString( p ) + " but doesn't", mask.test( p ) ); + } + } + + private static String locationToString( final Localizable pos ) + { + String separator = ""; + final StringBuilder b = new StringBuilder(); + b.append( "(" ); + for ( int d = 0; d < pos.numDimensions(); d++ ) + { + b.append( separator ); + b.append( pos.getLongPosition( d ) ); + separator = ", "; + } + b.append( ")" ); + return b.toString(); + } + + private static Interval createInterval( final long[][] points ) + { + final long[] min = new long[ points[ 0 ].length ]; + final long[] max = new long[ points[ 0 ].length ]; + Arrays.fill( min, Long.MAX_VALUE ); + Arrays.fill( max, Long.MIN_VALUE ); + for ( int i = 0; i < points.length; i++ ) + { + for ( int d = 0; d < min.length; d++ ) + { + if ( points[ i ][ d ] > max[ d ] ) + max[ d ] = points[ i ][ d ]; + if ( points[ i ][ d ] < min[ d ] ) + min[ d ] = points[ i ][ d ]; + } + } + return new FinalInterval( min, max ); + } + +} From 052c7c01e2ee98ba79bd1027751deaf89f4a2697 Mon Sep 17 00:00:00 2001 From: Alison Walter Date: Fri, 25 Jan 2019 15:40:30 +0100 Subject: [PATCH 3/6] Add method to add Masks to an ImgLabeling and test --- src/main/java/net/imglib2/roi/Masks.java | 88 +++++ .../roi/mask/integer/MaskToLabelingTest.java | 363 ++++++++++++++++++ 2 files changed, 451 insertions(+) create mode 100644 src/test/java/net/imglib2/roi/mask/integer/MaskToLabelingTest.java diff --git a/src/main/java/net/imglib2/roi/Masks.java b/src/main/java/net/imglib2/roi/Masks.java index b70119165..335809147 100644 --- a/src/main/java/net/imglib2/roi/Masks.java +++ b/src/main/java/net/imglib2/roi/Masks.java @@ -38,16 +38,21 @@ import java.util.List; import java.util.function.Predicate; +import net.imglib2.Cursor; import net.imglib2.FinalInterval; import net.imglib2.FinalRealInterval; import net.imglib2.Interval; import net.imglib2.Localizable; +import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealInterval; import net.imglib2.RealLocalizable; import net.imglib2.RealRandomAccessible; import net.imglib2.RealRandomAccessibleRealInterval; +import net.imglib2.roi.composite.BinaryCompositeMaskPredicate; +import net.imglib2.roi.composite.CompositeMaskPredicate; +import net.imglib2.roi.composite.UnaryCompositeMaskPredicate; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegions; @@ -67,6 +72,7 @@ import net.imglib2.roi.mask.real.RealRandomAccessibleRealIntervalAsRealMaskRealInterval; import net.imglib2.type.BooleanType; import net.imglib2.type.logic.BoolType; +import net.imglib2.type.numeric.IntegerType; import net.imglib2.util.Intervals; import net.imglib2.view.Views; @@ -375,6 +381,47 @@ public static < T > List< LabeledMaskInterval< T > > extractMaskIntervals( final return masks; } + /** + * Adds any {@link LabeledMaskInterval}s in the list to the provided + * {@link ImgLabeling}. If a given {@link Mask} is a + * {@link CompositeMaskPredicate}, this method will recurse through the + * children looking for {@link LabeledMaskInterval}s. And if one is found, + * it will be added to the {@code ImgLabeling}. + * + * @param masks + * list of potential {@link Mask}s to add, only the discovered + * {@link LabeledMaskInterval}s will be added + * @param labeling + * {@link ImgLabeling} to add labels/masks to + * @param type + * the class of the user specified labels in the + * {@code ImgLabeling} and {@code LabelMaskPredicate} + */ + public static < T, I extends IntegerType< I > > void addMasksToLabeling( final List< MaskInterval > masks, final ImgLabeling< T, I > labeling, final Class< T > type ) + { + final List< LabeledMaskInterval< ? extends T > > labeledMasks = new ArrayList<>(); + final long[] max = new long[ labeling.numDimensions() ]; + final long[] min = new long[ labeling.numDimensions() ]; + labeling.max( max ); + labeling.min( min ); + + for ( final MaskInterval maskInterval : masks ) + collectLabels( maskInterval, max, min, labeledMasks, type ); + + final RandomAccess< LabelingType< T > > ra = labeling.randomAccess(); + for ( final LabeledMaskInterval< ? extends T > labeledMask : labeledMasks ) + { + final IterableRegion< BoolType > iterable = Regions.iterable( toRandomAccessibleInterval( labeledMask ) ); + final Cursor< Void > c = iterable.cursor(); + while ( c.hasNext() ) + { + c.next(); + ra.setPosition( c ); + ra.get().add( labeledMask.getLabel() ); + } + } + } + /* * Empty Masks * =============================================================== @@ -516,4 +563,45 @@ public static < T, M extends MaskPredicate< T > > boolean sameTypesAndDimensions mask1.boundaryType() == mask2.boundaryType() && // mask1.numDimensions() == mask2.numDimensions(); } + + // -- Helper methods -- + + @SuppressWarnings( "unchecked" ) + private static < T > void collectLabels( final Mask mask, final long[] max, final long[] min, final List< LabeledMaskInterval< ? extends T > > labeledMasks, final Class< T > type ) + { + Mask copy = mask; + if ( mask instanceof MaskInterval ) + { + final MaskInterval maskInterval = ( MaskInterval ) mask; + if ( mask.numDimensions() != max.length ) + throw new IllegalArgumentException( "Incompatible dimensions. Dims labeling: " + max.length + " dims mask: " + mask.numDimensions() ); + for ( int d = 0; d < max.length; d++ ) + { + if ( maskInterval.min( d ) < min[ d ] || maskInterval.max( d ) > max[ d ] ) + throw new IllegalArgumentException( "MaskInterval [" + maskInterval.min( d ) + ", " + maskInterval.max( d ) + "] exceeds the range of labeling [" + min[ d ] + ", " + max[ d ] + "] in dimension " + d ); + } + if ( mask instanceof LabeledMaskInterval ) + { + final Class< ? > labelMaskType = ( ( LabeledMaskInterval< ? > ) mask ).getLabel().getClass(); + if ( !type.isAssignableFrom( labelMaskType ) ) + throw new IllegalArgumentException( "Incompatible label type, " + labelMaskType + ", for labeing type " + type ); + labeledMasks.add( ( LabeledMaskInterval< ? extends T > ) mask ); + copy = ( ( LabeledMaskInterval< ? > ) mask ).getSource(); + } + } + if ( copy instanceof BinaryCompositeMaskPredicate ) + { + final BinaryCompositeMaskPredicate< ? > bcmp = ( BinaryCompositeMaskPredicate< ? > ) copy; + if ( bcmp.arg0() instanceof Mask ) + collectLabels( ( Mask ) bcmp.arg0(), max, min, labeledMasks, type ); + if ( bcmp.arg1() instanceof Mask ) + collectLabels( ( Mask ) bcmp.arg1(), max, min, labeledMasks, type ); + } + if ( copy instanceof UnaryCompositeMaskPredicate ) + { + final UnaryCompositeMaskPredicate< ? > ucmp = ( UnaryCompositeMaskPredicate< ? > ) copy; + if ( ucmp.arg0() instanceof Mask ) + collectLabels( ( Mask ) ucmp.arg0(), max, min, labeledMasks, type ); + } + } } diff --git a/src/test/java/net/imglib2/roi/mask/integer/MaskToLabelingTest.java b/src/test/java/net/imglib2/roi/mask/integer/MaskToLabelingTest.java new file mode 100644 index 000000000..41b990bcd --- /dev/null +++ b/src/test/java/net/imglib2/roi/mask/integer/MaskToLabelingTest.java @@ -0,0 +1,363 @@ +/*- + * #%L + * ImgLib2: a general-purpose, multidimensional image processing library. + * %% + * Copyright (C) 2009 - 2017 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld, + * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke, + * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner, + * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert, + * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin, + * Jean-Yves Tinevez and Michael Zinsmaier. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package net.imglib2.roi.mask.integer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.function.Predicate; + +import org.junit.Test; + +import net.imglib2.Cursor; +import net.imglib2.FinalInterval; +import net.imglib2.Localizable; +import net.imglib2.img.Img; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.roi.BoundaryType; +import net.imglib2.roi.KnownConstant; +import net.imglib2.roi.MaskInterval; +import net.imglib2.roi.Masks; +import net.imglib2.roi.labeling.ImgLabeling; +import net.imglib2.roi.labeling.LabelingType; +import net.imglib2.type.numeric.integer.IntType; + +/** + * Tests for converting {@link MaskInterval}s to {@link ImgLabeling}s. + * + * @author Alison Walter + */ +public class MaskToLabelingTest +{ + + @Test + public void testOneMask() + { + final MaskInterval m = createBox( new long[] { 10, 12 }, new long[] { 22, 20 } ); + final LabeledMaskInterval< String > labeledMask = new LabeledMaskInterval<>( m, "kittyCat" ); + final Img< IntType > indexImg = ArrayImgs.ints( 25, 25 ); + final ImgLabeling< String, IntType > imgLabeling = new ImgLabeling<>( indexImg ); + Masks.addMasksToLabeling( Collections.singletonList( labeledMask ), imgLabeling, String.class ); + + final Cursor< LabelingType< String > > c = imgLabeling.cursor(); + while ( c.hasNext() ) + { + final LabelingType< String > labels = c.next(); + if ( labeledMask.test( c ) ) + { + assertEquals( 1, labels.size() ); + assertTrue( labels.contains( labeledMask.getLabel() ) ); + } + else + assertTrue( labels.isEmpty() ); + } + } + + @Test + public void testOneMask3D() + { + final MaskInterval m = createSphere( new long[] { 9, 19, 14 }, 5 ); + final LabeledMaskInterval< String > labeledMask = new LabeledMaskInterval<>( m, "prettyKitty" ); + final Img< IntType > indexImg = ArrayImgs.ints( 15, 25, 20 ); + final ImgLabeling< String, IntType > imgLabeling = new ImgLabeling<>( indexImg ); + Masks.addMasksToLabeling( Collections.singletonList( labeledMask ), imgLabeling, String.class ); + + final Cursor< LabelingType< String > > c = imgLabeling.cursor(); + while ( c.hasNext() ) + { + final LabelingType< String > labels = c.next(); + if ( labeledMask.test( c ) ) + { + assertEquals( 1, labels.size() ); + assertTrue( labels.contains( labeledMask.getLabel() ) ); + } + else + { + assertTrue( labels.isEmpty() ); + } + } + } + + @Test + public void testManyMasks() + { + final MaskInterval diagonal = new DefaultMaskInterval( new FinalInterval( new long[] { 0, 0 }, new long[] { 10, 10 } ), // + BoundaryType.UNSPECIFIED, p -> p.getLongPosition( 0 ) == p.getLongPosition( 1 ), KnownConstant.UNKNOWN ); + final MaskInterval horizontal = new DefaultMaskInterval( new FinalInterval( new long[] { 2, 3 }, new long[] { 15, 5 } ), // + BoundaryType.UNSPECIFIED, p -> p.getLongPosition( 1 ) == 4, KnownConstant.UNKNOWN ); + final MaskInterval vertical = new DefaultMaskInterval( new FinalInterval( new long[] { 3, 2 }, new long[] { 5, 8 } ), // + BoundaryType.UNSPECIFIED, p -> p.getLongPosition( 0 ) == 4, KnownConstant.UNKNOWN ); + final MaskInterval box = createBox( new long[] { 4, 4 }, new long[] { 10, 10 } ); + final LabeledMaskInterval< Byte > labelDiagonal = new LabeledMaskInterval<>( diagonal, ( byte ) 2 ); + final LabeledMaskInterval< Integer > labelHorizontal = new LabeledMaskInterval<>( horizontal, 33000 ); + final LabeledMaskInterval< Long > labelVertical = new LabeledMaskInterval<>( vertical, 0xFEEDBEEFl ); + final LabeledMaskInterval< Integer > labelBox = new LabeledMaskInterval<>( box, -400024900 ); + final Img< IntType > indexImg = ArrayImgs.ints( 16, 16 ); + final ImgLabeling< Number, IntType > imgLabeling = new ImgLabeling<>( indexImg ); + Masks.addMasksToLabeling( Arrays.asList( labelDiagonal, labelHorizontal, labelVertical, labelBox ), imgLabeling, Number.class ); + + final Cursor< LabelingType< Number > > c = imgLabeling.cursor(); + while ( c.hasNext() ) + { + final LabelingType< Number > labels = c.next(); + int numLabels = 0; + if ( labelDiagonal.test( c ) ) + { + assertTrue( labels.contains( labelDiagonal.getLabel() ) ); + numLabels++; + } + if ( labelHorizontal.test( c ) ) + { + assertTrue( labels.contains( labelHorizontal.getLabel() ) ); + numLabels++; + } + if ( labelVertical.test( c ) ) + { + assertTrue( labels.contains( labelVertical.getLabel() ) ); + numLabels++; + } + if ( labelBox.test( c ) ) + { + assertTrue( labels.contains( labelBox.getLabel() ) ); + numLabels++; + } + assertEquals( numLabels, labels.size() ); + } + } + + @Test + public void testCompositeMasks() + { + final MaskInterval boxOne = createBox( new long[] { 2, 2 }, new long[] { 10, 10 } ); + final MaskInterval boxTwo = createBox( new long[] { 3, 4 }, new long[] { 8, 10 } ); + final MaskInterval boxThree = createBox( new long[] { 5, 5 }, new long[] { 20, 25 } ); + final MaskInterval sphereOne = createSphere( new long[] { 30, 30 }, 5 ); + final MaskInterval sphereTwo = createSphere( new long[] { 30, 30 }, 10 ); + final MaskInterval sphereXor = sphereOne.xor( sphereTwo ); + final LabeledMaskInterval< Character > labelBoxOne = new LabeledMaskInterval<>( boxOne, 'a' ); + final LabeledMaskInterval< Character > labelBoxTwo = new LabeledMaskInterval<>( boxTwo, 'b' ); + final LabeledMaskInterval< Character > labelBoxThree = new LabeledMaskInterval<>( boxThree, 'c' ); + final LabeledMaskInterval< Character > labelXor = new LabeledMaskInterval<>( sphereXor, 'd' ); + final MaskInterval boxComposite = labelBoxThree.and( labelBoxTwo.or( labelBoxOne ) ); + final Img< IntType > indexImg = ArrayImgs.ints( 41, 41 ); + final ImgLabeling< Character, IntType > imgLabeling = new ImgLabeling<>( indexImg ); + Masks.addMasksToLabeling( Arrays.asList( boxComposite, labelXor ), imgLabeling, Character.class ); + + final Cursor< LabelingType< Character > > c = imgLabeling.cursor(); + while ( c.hasNext() ) + { + final LabelingType< Character > labels = c.next(); + int numLabels = 0; + // Even though all box points are not in the composite ROI, they are + // labeled + if ( boxOne.test( c ) ) + { + assertTrue( labels.contains( labelBoxOne.getLabel() ) ); + numLabels++; + } + if ( boxTwo.test( c ) ) + { + assertTrue( labels.contains( labelBoxTwo.getLabel() ) ); + numLabels++; + } + if ( boxThree.test( c ) ) + { + assertTrue( labels.contains( labelBoxThree.getLabel() ) ); + numLabels++; + } + // Label was set on the Xor so none of the inner circles points + // should have labels + if ( sphereXor.test( c ) ) + { + assertTrue( labels.contains( labelXor.getLabel() ) ); + numLabels++; + } + if ( sphereOne.test( c ) ) + assertTrue( labels.isEmpty() ); + + assertEquals( numLabels, labels.size() ); + } + } + + @Test + public void testAddingToExisting() + { + final Img< IntType > indexImg = ArrayImgs.ints( 10, 10 ); + final ImgLabeling< Byte, IntType > imgLabeling = new ImgLabeling<>( indexImg ); + final Cursor< LabelingType< Byte > > c = imgLabeling.cursor(); + while ( c.hasNext() ) + { + final LabelingType< Byte > labels = c.next(); + if ( ( c.getLongPosition( 0 ) + c.getLongPosition( 1 ) ) % 2 == 0 ) + labels.add( ( byte ) 120 ); + } + final LabeledMaskInterval< Byte > labeledMask = new LabeledMaskInterval<>( createSphere( new long[] { 5, 4 }, 4 ), ( byte ) -80 ); + Masks.addMasksToLabeling( Collections.singletonList( labeledMask ), imgLabeling, Byte.class ); + + final Cursor< LabelingType< Byte > > c2 = imgLabeling.cursor(); + while ( c2.hasNext() ) + { + final LabelingType< Byte > labels = c2.next(); + int numLabels = 0; + if ( ( c2.getLongPosition( 0 ) + c2.getLongPosition( 1 ) ) % 2 == 0 ) + { + assertTrue( labels.contains( ( byte ) 120 ) ); + numLabels++; + } + if ( labeledMask.test( c2 ) ) + { + assertTrue( labels.contains( labeledMask.getLabel() ) ); + numLabels++; + } + assertEquals( numLabels, labels.size() ); + } + } + + @Test + public void testIncompatibleLabelType() + { + final LabeledMaskInterval< String > sphereOne = new LabeledMaskInterval<>( createSphere( new long[] { 30, 34 }, 9 ), "abc" ); + final LabeledMaskInterval< Integer > sphereTwo = new LabeledMaskInterval<>( createSphere( new long[] { 29, 39 }, 5 ), 80 ); + final LabeledMaskInterval< Integer > sphereThree = new LabeledMaskInterval<>( createSphere( new long[] { 20, 35 }, 11 ), 981 ); + final LabeledMaskInterval< Integer > sphereFour = new LabeledMaskInterval<>( createSphere( new long[] { 30, 53 }, 4 ), -7932 ); + final LabeledMaskInterval< Integer > sphereOr = new LabeledMaskInterval< Integer >( sphereOne.or( sphereTwo ), -189 ); + final Img< IntType > indexImg = ArrayImgs.ints( 100, 100 ); + final ImgLabeling< Integer, IntType > imgLabeling = new ImgLabeling<>( indexImg ); + + // Ensure exception thrown + Exception e = null; + try + { + Masks.addMasksToLabeling( Arrays.asList( sphereThree, sphereFour, sphereOr ), imgLabeling, Integer.class ); + } + catch ( final Exception ex ) + { + e = ex; + } + + assertNotNull( "Expected an exception but none was thrown!", e ); + assertEquals( IllegalArgumentException.class, e.getClass() ); + + // Ensure ImgLabeling was not modified + final Cursor< LabelingType< Integer > > c = imgLabeling.cursor(); + while ( c.hasNext() ) + assertTrue( c.next().isEmpty() ); + } + + @Test + public void testMaskOutsideInterval() + { + final LabeledMaskInterval< String > sphere = new LabeledMaskInterval<>( createSphere( new long[] { 20, 25 }, 10 ), "cat" ); + final Img< IntType > indexImg = ArrayImgs.ints( 30, 30 ); + final ImgLabeling< String, IntType > imgLabeling = new ImgLabeling<>( indexImg ); + + // Ensure exception thrown + Exception e = null; + try + { + Masks.addMasksToLabeling( Collections.singletonList( sphere ), imgLabeling, String.class ); + } + catch ( final Exception ex ) + { + e = ex; + } + assertNotNull( "Expected an exception but none was thrown!", e ); + assertEquals( IllegalArgumentException.class, e.getClass() ); + + // Ensure ImgLabeling was not modified + final Cursor< LabelingType< String > > c = imgLabeling.cursor(); + while ( c.hasNext() ) + assertTrue( c.next().isEmpty() ); + } + + @Test + public void testDifferentDimensions() + { + final LabeledMaskInterval< String > box = new LabeledMaskInterval<>( createBox( new long[] { 10, 14, 9 }, new long[] { 21, 35, 20 } ), "quadruped" ); + final Img< IntType > indexImg = ArrayImgs.ints( 50, 50 ); + final ImgLabeling< String, IntType > imgLabeling = new ImgLabeling<>( indexImg ); + + // Ensure exception thrown + Exception e = null; + try + { + Masks.addMasksToLabeling( Collections.singletonList( box ), imgLabeling, String.class ); + } + catch ( final Exception ex ) + { + e = ex; + } + assertNotNull( "Expected an exception but none was thrown!", e ); + assertEquals( IllegalArgumentException.class, e.getClass() ); + + // Ensure ImgLabeling was not modified + final Cursor< LabelingType< String > > c = imgLabeling.cursor(); + while ( c.hasNext() ) + assertTrue( c.next().isEmpty() ); + } + + // -- Helper methods -- + + private static MaskInterval createBox( final long[] min, final long[] max ) + { + final Predicate< Localizable > p = l -> { + boolean inside = true; + for ( int d = 0; d < l.numDimensions(); d++ ) + inside &= ( l.getLongPosition( d ) < max[ d ] && l.getLongPosition( d ) > min[ d ] ); + return inside; + }; + return new DefaultMaskInterval( new FinalInterval( min, max ), BoundaryType.OPEN, p, KnownConstant.UNKNOWN ); + } + + private static MaskInterval createSphere( final long[] center, final long radius ) + { + final Predicate< Localizable > p = l -> { + long distancePowered = 0; + for ( int d = 0; d < l.numDimensions(); d++ ) + distancePowered += ( l.getLongPosition( d ) - center[ d ] ) * ( l.getLongPosition( d ) - center[ d ] ); + return distancePowered < ( radius * radius ); + }; + final long[] min = new long[ center.length ]; + final long[] max = new long[ center.length ]; + for ( int d = 0; d < center.length; d++ ) + { + min[ d ] = center[ d ] - radius; + max[ d ] = center[ d ] + radius; + } + return new DefaultMaskInterval( new FinalInterval( min, max ), BoundaryType.OPEN, p, KnownConstant.UNKNOWN ); + } +} From 97fad44bda68c64809c5daca31f129766334f3bf Mon Sep 17 00:00:00 2001 From: Alison Walter Date: Fri, 25 Jan 2019 18:14:31 +0100 Subject: [PATCH 4/6] Modify to only add labels at root Mask locations --- src/main/java/net/imglib2/roi/Masks.java | 63 ++++++++++++++----- .../roi/mask/integer/MaskToLabelingTest.java | 31 +++++---- 2 files changed, 64 insertions(+), 30 deletions(-) diff --git a/src/main/java/net/imglib2/roi/Masks.java b/src/main/java/net/imglib2/roi/Masks.java index 335809147..75a5ac104 100644 --- a/src/main/java/net/imglib2/roi/Masks.java +++ b/src/main/java/net/imglib2/roi/Masks.java @@ -35,7 +35,10 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.function.Predicate; import net.imglib2.Cursor; @@ -386,7 +389,10 @@ public static < T > List< LabeledMaskInterval< T > > extractMaskIntervals( final * {@link ImgLabeling}. If a given {@link Mask} is a * {@link CompositeMaskPredicate}, this method will recurse through the * children looking for {@link LabeledMaskInterval}s. And if one is found, - * it will be added to the {@code ImgLabeling}. + * its label will be added to the {@code ImgLabeling} only at the locations + * in the root/parent {@code CompositeMaskPredicate}. If the given + * {@code Mask} is not a {@code LabeledMaskInterval} it will not be written + * to the {@code ImgLabeling} * * @param masks * list of potential {@link Mask}s to add, only the discovered @@ -399,25 +405,31 @@ public static < T > List< LabeledMaskInterval< T > > extractMaskIntervals( final */ public static < T, I extends IntegerType< I > > void addMasksToLabeling( final List< MaskInterval > masks, final ImgLabeling< T, I > labeling, final Class< T > type ) { - final List< LabeledMaskInterval< ? extends T > > labeledMasks = new ArrayList<>(); + final Map< Mask, List< LabeledMaskInterval< ? extends T > > > labeledMasks = new HashMap<>(); final long[] max = new long[ labeling.numDimensions() ]; final long[] min = new long[ labeling.numDimensions() ]; labeling.max( max ); labeling.min( min ); for ( final MaskInterval maskInterval : masks ) - collectLabels( maskInterval, max, min, labeledMasks, type ); + collectLabels( maskInterval, max, min, labeledMasks, type, null ); final RandomAccess< LabelingType< T > > ra = labeling.randomAccess(); - for ( final LabeledMaskInterval< ? extends T > labeledMask : labeledMasks ) + for ( final Entry< Mask, List< LabeledMaskInterval< ? extends T > > > s : labeledMasks.entrySet() ) { - final IterableRegion< BoolType > iterable = Regions.iterable( toRandomAccessibleInterval( labeledMask ) ); - final Cursor< Void > c = iterable.cursor(); - while ( c.hasNext() ) + for ( final LabeledMaskInterval< ? extends T > labeledMask : s.getValue() ) { - c.next(); - ra.setPosition( c ); - ra.get().add( labeledMask.getLabel() ); + final IterableRegion< BoolType > iterable = Regions.iterable( toRandomAccessibleInterval( labeledMask ) ); + final Cursor< Void > c = iterable.cursor(); + while ( c.hasNext() ) + { + c.next(); + if ( s.getKey().test( c ) ) + { + ra.setPosition( c ); + ra.get().add( labeledMask.getLabel() ); + } + } } } } @@ -566,8 +578,7 @@ public static < T, M extends MaskPredicate< T > > boolean sameTypesAndDimensions // -- Helper methods -- - @SuppressWarnings( "unchecked" ) - private static < T > void collectLabels( final Mask mask, final long[] max, final long[] min, final List< LabeledMaskInterval< ? extends T > > labeledMasks, final Class< T > type ) + private static < T > void collectLabels( final Mask mask, final long[] max, final long[] min, final Map< Mask, List< LabeledMaskInterval< ? extends T > > > labeledMasks, final Class< T > type, final Mask parent ) { Mask copy = mask; if ( mask instanceof MaskInterval ) @@ -585,23 +596,41 @@ private static < T > void collectLabels( final Mask mask, final long[] max, fina final Class< ? > labelMaskType = ( ( LabeledMaskInterval< ? > ) mask ).getLabel().getClass(); if ( !type.isAssignableFrom( labelMaskType ) ) throw new IllegalArgumentException( "Incompatible label type, " + labelMaskType + ", for labeing type " + type ); - labeledMasks.add( ( LabeledMaskInterval< ? extends T > ) mask ); - copy = ( ( LabeledMaskInterval< ? > ) mask ).getSource(); + @SuppressWarnings( "unchecked" ) + final LabeledMaskInterval< ? extends T > labeledMask = ( LabeledMaskInterval< ? extends T > ) mask; + if ( parent == null ) + { + final List< LabeledMaskInterval< ? extends T > > l = new ArrayList<>(); + l.add( labeledMask ); + labeledMasks.put( labeledMask.getSource(), l ); + } + else if ( labeledMasks.containsKey( parent ) ) + { + labeledMasks.get( parent ).add( labeledMask ); + } + else + { + final List< LabeledMaskInterval< ? extends T > > l = new ArrayList<>(); + l.add( labeledMask ); + labeledMasks.put( parent, l ); + } + copy = labeledMask.getSource(); } } if ( copy instanceof BinaryCompositeMaskPredicate ) { final BinaryCompositeMaskPredicate< ? > bcmp = ( BinaryCompositeMaskPredicate< ? > ) copy; + final Mask p = parent == null ? copy : parent; if ( bcmp.arg0() instanceof Mask ) - collectLabels( ( Mask ) bcmp.arg0(), max, min, labeledMasks, type ); + collectLabels( ( Mask ) bcmp.arg0(), max, min, labeledMasks, type, p ); if ( bcmp.arg1() instanceof Mask ) - collectLabels( ( Mask ) bcmp.arg1(), max, min, labeledMasks, type ); + collectLabels( ( Mask ) bcmp.arg1(), max, min, labeledMasks, type, p ); } if ( copy instanceof UnaryCompositeMaskPredicate ) { final UnaryCompositeMaskPredicate< ? > ucmp = ( UnaryCompositeMaskPredicate< ? > ) copy; if ( ucmp.arg0() instanceof Mask ) - collectLabels( ( Mask ) ucmp.arg0(), max, min, labeledMasks, type ); + collectLabels( ( Mask ) ucmp.arg0(), max, min, labeledMasks, type, parent == null ? copy : parent ); } } } diff --git a/src/test/java/net/imglib2/roi/mask/integer/MaskToLabelingTest.java b/src/test/java/net/imglib2/roi/mask/integer/MaskToLabelingTest.java index 41b990bcd..1b29e3dbd 100644 --- a/src/test/java/net/imglib2/roi/mask/integer/MaskToLabelingTest.java +++ b/src/test/java/net/imglib2/roi/mask/integer/MaskToLabelingTest.java @@ -177,27 +177,33 @@ public void testCompositeMasks() final ImgLabeling< Character, IntType > imgLabeling = new ImgLabeling<>( indexImg ); Masks.addMasksToLabeling( Arrays.asList( boxComposite, labelXor ), imgLabeling, Character.class ); + final MaskInterval XorBoxComposite = labelBoxThree.xor( labelBoxTwo.or( labelBoxOne ) ); final Cursor< LabelingType< Character > > c = imgLabeling.cursor(); while ( c.hasNext() ) { - final LabelingType< Character > labels = c.next(); int numLabels = 0; - // Even though all box points are not in the composite ROI, they are - // labeled - if ( boxOne.test( c ) ) - { - assertTrue( labels.contains( labelBoxOne.getLabel() ) ); - numLabels++; - } - if ( boxTwo.test( c ) ) + final LabelingType< Character > labels = c.next(); + // Only labels of the children with the parent ROI should be written + // to the ImgLabeling + // So none of the labels in the xor should be in the ImgLabeling + if ( XorBoxComposite.test( c ) ) { - assertTrue( labels.contains( labelBoxTwo.getLabel() ) ); - numLabels++; + assertTrue( labels.isEmpty() ); } - if ( boxThree.test( c ) ) + if ( boxComposite.test( c ) ) { assertTrue( labels.contains( labelBoxThree.getLabel() ) ); numLabels++; + if ( boxOne.test( c ) ) + { + assertTrue( labels.contains( labelBoxOne.getLabel() ) ); + numLabels++; + } + if ( boxTwo.test( c ) ) + { + assertTrue( labels.contains( labelBoxTwo.getLabel() ) ); + numLabels++; + } } // Label was set on the Xor so none of the inner circles points // should have labels @@ -208,7 +214,6 @@ public void testCompositeMasks() } if ( sphereOne.test( c ) ) assertTrue( labels.isEmpty() ); - assertEquals( numLabels, labels.size() ); } } From d7dfd6a5da3ac9505d1f82b1744c69a2251cd12f Mon Sep 17 00:00:00 2001 From: Alison Walter Date: Fri, 25 Jan 2019 21:02:29 +0100 Subject: [PATCH 5/6] Simplify Mask to Labeling conversion --- src/main/java/net/imglib2/roi/Masks.java | 75 +++++++++--------------- 1 file changed, 28 insertions(+), 47 deletions(-) diff --git a/src/main/java/net/imglib2/roi/Masks.java b/src/main/java/net/imglib2/roi/Masks.java index 75a5ac104..af2c8d1bd 100644 --- a/src/main/java/net/imglib2/roi/Masks.java +++ b/src/main/java/net/imglib2/roi/Masks.java @@ -386,7 +386,7 @@ public static < T > List< LabeledMaskInterval< T > > extractMaskIntervals( final /** * Adds any {@link LabeledMaskInterval}s in the list to the provided - * {@link ImgLabeling}. If a given {@link Mask} is a + * {@link ImgLabeling}. If a given {@link MaskInterval} is a * {@link CompositeMaskPredicate}, this method will recurse through the * children looking for {@link LabeledMaskInterval}s. And if one is found, * its label will be added to the {@code ImgLabeling} only at the locations @@ -395,8 +395,8 @@ public static < T > List< LabeledMaskInterval< T > > extractMaskIntervals( final * to the {@code ImgLabeling} * * @param masks - * list of potential {@link Mask}s to add, only the discovered - * {@link LabeledMaskInterval}s will be added + * list of potential {@link MaskInterval}s to add, only the + * discovered {@link LabeledMaskInterval}s will be added * @param labeling * {@link ImgLabeling} to add labels/masks to * @param type @@ -406,13 +406,21 @@ public static < T > List< LabeledMaskInterval< T > > extractMaskIntervals( final public static < T, I extends IntegerType< I > > void addMasksToLabeling( final List< MaskInterval > masks, final ImgLabeling< T, I > labeling, final Class< T > type ) { final Map< Mask, List< LabeledMaskInterval< ? extends T > > > labeledMasks = new HashMap<>(); - final long[] max = new long[ labeling.numDimensions() ]; - final long[] min = new long[ labeling.numDimensions() ]; - labeling.max( max ); - labeling.min( min ); for ( final MaskInterval maskInterval : masks ) - collectLabels( maskInterval, max, min, labeledMasks, type, null ); + { + if ( maskInterval.numDimensions() != labeling.numDimensions() ) + throw new IllegalArgumentException( "Incompatible dimensions. Dims labeling: " + labeling.numDimensions() + " dims mask: " + maskInterval.numDimensions() ); + for ( int d = 0; d < labeling.numDimensions(); d++ ) + { + if ( maskInterval.min( d ) < labeling.min( d ) || maskInterval.max( d ) > labeling.max( d ) ) + throw new IllegalArgumentException( "MaskInterval [" + maskInterval.min( d ) + ", " + maskInterval.max( d ) + "] exceeds the range of labeling [" + labeling.min( d ) + ", " + labeling.max( d ) + "] in dimension " + d ); + } + final List< LabeledMaskInterval< ? extends T > > list = new ArrayList<>(); + collectLabels( maskInterval, list, type ); + if ( !list.isEmpty() ) + labeledMasks.put( maskInterval, list ); + } final RandomAccess< LabelingType< T > > ra = labeling.randomAccess(); for ( final Entry< Mask, List< LabeledMaskInterval< ? extends T > > > s : labeledMasks.entrySet() ) @@ -578,59 +586,32 @@ public static < T, M extends MaskPredicate< T > > boolean sameTypesAndDimensions // -- Helper methods -- - private static < T > void collectLabels( final Mask mask, final long[] max, final long[] min, final Map< Mask, List< LabeledMaskInterval< ? extends T > > > labeledMasks, final Class< T > type, final Mask parent ) + private static < T > void collectLabels( final Mask mask, final List< LabeledMaskInterval< ? extends T > > labeledMasks, final Class< T > type ) { Mask copy = mask; - if ( mask instanceof MaskInterval ) + if ( mask instanceof LabeledMaskInterval ) { - final MaskInterval maskInterval = ( MaskInterval ) mask; - if ( mask.numDimensions() != max.length ) - throw new IllegalArgumentException( "Incompatible dimensions. Dims labeling: " + max.length + " dims mask: " + mask.numDimensions() ); - for ( int d = 0; d < max.length; d++ ) - { - if ( maskInterval.min( d ) < min[ d ] || maskInterval.max( d ) > max[ d ] ) - throw new IllegalArgumentException( "MaskInterval [" + maskInterval.min( d ) + ", " + maskInterval.max( d ) + "] exceeds the range of labeling [" + min[ d ] + ", " + max[ d ] + "] in dimension " + d ); - } - if ( mask instanceof LabeledMaskInterval ) - { - final Class< ? > labelMaskType = ( ( LabeledMaskInterval< ? > ) mask ).getLabel().getClass(); - if ( !type.isAssignableFrom( labelMaskType ) ) - throw new IllegalArgumentException( "Incompatible label type, " + labelMaskType + ", for labeing type " + type ); - @SuppressWarnings( "unchecked" ) - final LabeledMaskInterval< ? extends T > labeledMask = ( LabeledMaskInterval< ? extends T > ) mask; - if ( parent == null ) - { - final List< LabeledMaskInterval< ? extends T > > l = new ArrayList<>(); - l.add( labeledMask ); - labeledMasks.put( labeledMask.getSource(), l ); - } - else if ( labeledMasks.containsKey( parent ) ) - { - labeledMasks.get( parent ).add( labeledMask ); - } - else - { - final List< LabeledMaskInterval< ? extends T > > l = new ArrayList<>(); - l.add( labeledMask ); - labeledMasks.put( parent, l ); - } - copy = labeledMask.getSource(); - } + final Class< ? > labelMaskType = ( ( LabeledMaskInterval< ? > ) mask ).getLabel().getClass(); + if ( !type.isAssignableFrom( labelMaskType ) ) + throw new IllegalArgumentException( "Incompatible label type, " + labelMaskType + ", for labeing type " + type ); + @SuppressWarnings( "unchecked" ) + final LabeledMaskInterval< ? extends T > labeledMask = ( LabeledMaskInterval< ? extends T > ) mask; + labeledMasks.add( labeledMask ); + copy = labeledMask.getSource(); } if ( copy instanceof BinaryCompositeMaskPredicate ) { final BinaryCompositeMaskPredicate< ? > bcmp = ( BinaryCompositeMaskPredicate< ? > ) copy; - final Mask p = parent == null ? copy : parent; if ( bcmp.arg0() instanceof Mask ) - collectLabels( ( Mask ) bcmp.arg0(), max, min, labeledMasks, type, p ); + collectLabels( ( Mask ) bcmp.arg0(), labeledMasks, type ); if ( bcmp.arg1() instanceof Mask ) - collectLabels( ( Mask ) bcmp.arg1(), max, min, labeledMasks, type, p ); + collectLabels( ( Mask ) bcmp.arg1(), labeledMasks, type ); } if ( copy instanceof UnaryCompositeMaskPredicate ) { final UnaryCompositeMaskPredicate< ? > ucmp = ( UnaryCompositeMaskPredicate< ? > ) copy; if ( ucmp.arg0() instanceof Mask ) - collectLabels( ( Mask ) ucmp.arg0(), max, min, labeledMasks, type, parent == null ? copy : parent ); + collectLabels( ( Mask ) ucmp.arg0(), labeledMasks, type ); } } } From 1d114ca4967fc4eff2c9af541dd01bbb6bf91d6e Mon Sep 17 00:00:00 2001 From: Alison Walter Date: Fri, 25 Jan 2019 22:12:33 +0100 Subject: [PATCH 6/6] Add method to add masks to labelings with default label and test --- src/main/java/net/imglib2/roi/Masks.java | 38 +++++++++++++ .../roi/mask/integer/MaskToLabelingTest.java | 54 +++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/src/main/java/net/imglib2/roi/Masks.java b/src/main/java/net/imglib2/roi/Masks.java index af2c8d1bd..058f7c9d4 100644 --- a/src/main/java/net/imglib2/roi/Masks.java +++ b/src/main/java/net/imglib2/roi/Masks.java @@ -442,6 +442,44 @@ public static < T, I extends IntegerType< I > > void addMasksToLabeling( final L } } + /** + * Adds any {@link LabeledMaskInterval}s in the list to the provided + * {@link ImgLabeling}. If a given {@link MaskInterval} is a + * {@link CompositeMaskPredicate}, this method will recurse through the + * children looking for {@link LabeledMaskInterval}s. And if one is found, + * its label will be added to the {@code ImgLabeling} only at the locations + * in the root/parent {@code CompositeMaskPredicate}. If a given + * {@link MaskInterval} or part of a {@code CompositeMaskPredicate} does not + * contain a label, the given default label will be set at those locations. + * + * @param masks + * list of potential {@link MaskInterval}s to add + * @param labeling + * {@link ImgLabeling} to add labels/masks to + * @param defaultLabel + * the label to assign to pixels inside the given + * {@link MaskInterval}s that do not already have labels (i.e. + * not part of a {@link LabeledMaskInterval} + */ + @SuppressWarnings( "unchecked" ) + public static < T, I extends IntegerType< I > > void addMasksToLabeling( final List< MaskInterval > masks, final ImgLabeling< T, I > labeling, final T defaultLabel ) + { + addMasksToLabeling( masks, labeling, ( Class< T > ) defaultLabel.getClass() ); + + final RandomAccess< LabelingType< T > > ra = labeling.randomAccess(); + for ( final MaskInterval mi : masks ) + { + final Cursor< Void > c = Regions.iterable( Masks.toRandomAccessibleInterval( mi ) ).cursor(); + while ( c.hasNext() ) + { + c.next(); + ra.setPosition( c ); + if ( ra.get().isEmpty() ) + ra.get().add( defaultLabel ); + } + } + } + /* * Empty Masks * =============================================================== diff --git a/src/test/java/net/imglib2/roi/mask/integer/MaskToLabelingTest.java b/src/test/java/net/imglib2/roi/mask/integer/MaskToLabelingTest.java index 1b29e3dbd..33dca8466 100644 --- a/src/test/java/net/imglib2/roi/mask/integer/MaskToLabelingTest.java +++ b/src/test/java/net/imglib2/roi/mask/integer/MaskToLabelingTest.java @@ -335,6 +335,60 @@ public void testDifferentDimensions() assertTrue( c.next().isEmpty() ); } + @Test + public void testDefaultLabelEntireMask() + { + final MaskInterval m = createBox( new long[] { 10, 10 }, new long[] { 20, 20 } ); + final Img< IntType > indexImg = ArrayImgs.ints( 21, 21 ); + final ImgLabeling< String, IntType > imgLabeling = new ImgLabeling<>( indexImg ); + final String defaultLabel = "default"; + Masks.addMasksToLabeling( Collections.singletonList( m ), imgLabeling, defaultLabel ); + + final Cursor< LabelingType< String > > c = imgLabeling.cursor(); + while ( c.hasNext() ) + { + final LabelingType< String > labels = c.next(); + if ( m.test( c ) ) + { + assertTrue( labels.contains( defaultLabel ) ); + assertEquals( 1, labels.size() ); + } + else + assertTrue( labels.isEmpty() ); + } + } + + @Test + public void testDefaultLabelPartialMask() + { + final LabeledMaskInterval< String > labeledSphere = new LabeledMaskInterval<>( createSphere( new long[] { 10, 10 }, 5 ), "label" ); + final MaskInterval sphere = createSphere( new long[] { 15, 13 }, 4 ); + final MaskInterval or = labeledSphere.or( sphere ); + final MaskInterval minus = sphere.minus( labeledSphere ); + final String defaultLabel = "default"; + final Img< IntType > indexImg = ArrayImgs.ints( 20, 20 ); + final ImgLabeling< String, IntType > imgLabeling = new ImgLabeling<>( indexImg ); + Masks.addMasksToLabeling( Collections.singletonList( or ), imgLabeling, defaultLabel ); + + final Cursor< LabelingType< String > > c = imgLabeling.cursor(); + while ( c.hasNext() ) + { + final LabelingType< String > labels = c.next(); + if ( labeledSphere.test( c ) ) + { + assertTrue( labels.contains( labeledSphere.getLabel() ) ); + assertEquals( 1, labels.size() ); + } + else if ( minus.test( c ) ) + { + assertTrue( labels.contains( defaultLabel ) ); + assertEquals( 1, labels.size() ); + } + else + assertTrue( labels.isEmpty() ); + } + } + // -- Helper methods -- private static MaskInterval createBox( final long[] min, final long[] max )