Skip to content

Commit

Permalink
Merge pull request #539 from imagej/merge-labeling
Browse files Browse the repository at this point in the history
Add op for merging two ImgLabelings
  • Loading branch information
ctrueden authored Jun 13, 2018
2 parents 50fd247 + 8a8a85f commit c63e09e
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 0 deletions.
40 changes: 40 additions & 0 deletions src/main/java/net/imagej/ops/labeling/LabelingNamespace.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.algorithm.labeling.ConnectedComponents.StructuringElement;
import net.imglib2.roi.labeling.ImgLabeling;
import net.imglib2.type.BooleanType;
import net.imglib2.type.numeric.IntegerType;

import org.scijava.plugin.Plugin;
Expand All @@ -50,6 +51,10 @@
@Plugin(type = Namespace.class)
public class LabelingNamespace extends AbstractNamespace {

// -- Labeling namespace ops --

// -- CCA --

@OpMethod(op = net.imagej.ops.labeling.cca.DefaultCCA.class)
public <T extends IntegerType<T>, L, I extends IntegerType<I>>
ImgLabeling<L, I> cca(final ImgLabeling<L, I> out,
Expand Down Expand Up @@ -88,6 +93,41 @@ ImgLabeling<L, I> cca(final RandomAccessibleInterval<T> in,
return result;
}

// -- merge --

@OpMethod(op = net.imagej.ops.labeling.MergeLabeling.class)
public <L, I extends IntegerType<I>> ImgLabeling<L, I> merge(
final ImgLabeling<L, I> in1, final ImgLabeling<L, I> in2)
{
@SuppressWarnings("unchecked")
final ImgLabeling<L, I> result = (ImgLabeling<L, I>) ops().run(
net.imagej.ops.Ops.Labeling.Merge.class, in1, in2);
return result;
}

@OpMethod(op = net.imagej.ops.labeling.MergeLabeling.class)
public <L, I extends IntegerType<I>> ImgLabeling<L, I> merge(
final ImgLabeling<L, I> out, final ImgLabeling<L, I> in1,
final ImgLabeling<L, I> in2)
{
@SuppressWarnings("unchecked")
final ImgLabeling<L, I> result = (ImgLabeling<L, I>) ops().run(
net.imagej.ops.Ops.Labeling.Merge.class, out, in1, in2);
return result;
}

@OpMethod(op = net.imagej.ops.labeling.MergeLabeling.class)
public <L, I extends IntegerType<I>, B extends BooleanType<B>>
ImgLabeling<L, I> merge(final ImgLabeling<L, I> out,
final ImgLabeling<L, I> in1, final ImgLabeling<L, I> in2,
final RandomAccessibleInterval<B> mask)
{
@SuppressWarnings("unchecked")
final ImgLabeling<L, I> result = (ImgLabeling<L, I>) ops().run(
net.imagej.ops.Ops.Labeling.Merge.class, out, in1, in2, mask);
return result;
}

@Override
public String getName() {
return "labeling";
Expand Down
132 changes: 132 additions & 0 deletions src/main/java/net/imagej/ops/labeling/MergeLabeling.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* #%L
* ImageJ software for multidimensional image processing and analysis.
* %%
* Copyright (C) 2014 - 2018 ImageJ developers.
* %%
* 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.imagej.ops.labeling;

import net.imagej.ops.Contingent;
import net.imagej.ops.Ops;
import net.imagej.ops.map.Maps;
import net.imagej.ops.special.computer.AbstractBinaryComputerOp;
import net.imagej.ops.special.function.Functions;
import net.imagej.ops.special.function.UnaryFunctionOp;
import net.imagej.ops.special.hybrid.AbstractBinaryHybridCF;
import net.imglib2.Cursor;
import net.imglib2.Interval;
import net.imglib2.IterableInterval;
import net.imglib2.RandomAccess;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.roi.IterableRegion;
import net.imglib2.roi.Regions;
import net.imglib2.roi.labeling.ImgLabeling;
import net.imglib2.roi.labeling.LabelingType;
import net.imglib2.type.BooleanType;
import net.imglib2.type.numeric.IntegerType;
import net.imglib2.util.Intervals;

import org.scijava.Priority;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;

/**
* Merges the labels of two {@link ImgLabeling} within a defined mask (if
* provided). Outside of the mask, labels will be empty.
*
* @author Stefan Helfrich (University of Konstanz)
*/
@Plugin(type = Ops.Labeling.Merge.class, priority = Priority.HIGH)
public class MergeLabeling<L, I extends IntegerType<I>, B extends BooleanType<B>>
extends
AbstractBinaryHybridCF<ImgLabeling<L, I>, ImgLabeling<L, I>, ImgLabeling<L, I>>
implements Contingent, Ops.Labeling.Merge
{

@Parameter(required = false)
private RandomAccessibleInterval<B> mask;

private UnaryFunctionOp<Interval, ImgLabeling<L, I>> imgLabelingCreator;

@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public void initialize() {
imgLabelingCreator = (UnaryFunctionOp) Functions.unary(ops(),
Ops.Create.ImgLabeling.class, ImgLabeling.class, in());
}

@Override
public boolean conforms() {
if (out() == null) return true;
// TODO We could in future think about generalizing that scheme
return Intervals.equalDimensions(in(), out());
}

@SuppressWarnings({ "unchecked", "rawtypes", "hiding" })
@Override
public void compute(final ImgLabeling<L, I> input1,
final ImgLabeling<L, I> input2, final ImgLabeling<L, I> output)
{
if (mask != null) {
final IterableRegion iterable = Regions.iterable(mask);
final IterableInterval<LabelingType<L>> sample = Regions.sample(iterable,
output);
final RandomAccess<LabelingType<L>> randomAccess = input1.randomAccess();
final RandomAccess<LabelingType<L>> randomAccess2 = input2.randomAccess();
final Cursor<LabelingType<L>> cursor = sample.cursor();
while (cursor.hasNext()) {
final LabelingType<L> outLabeling = cursor.next();
randomAccess.setPosition(cursor);
outLabeling.addAll(randomAccess.get());
randomAccess2.setPosition(cursor);
outLabeling.addAll(randomAccess2.get());
}
}
else {
Maps.map((IterableInterval) input1, (IterableInterval) input2,
(IterableInterval) output,
new AbstractBinaryComputerOp<LabelingType<L>, LabelingType<L>, LabelingType<L>>()
{

@Override
public void compute(final LabelingType<L> input1,
final LabelingType<L> input2, final LabelingType<L> output)
{
output.addAll(input1);
output.addAll(input2);
}
});
}
}

@Override
public ImgLabeling<L, I> createOutput(final ImgLabeling<L, I> input1,
final ImgLabeling<L, I> input2)
{
return imgLabelingCreator.calculate(input1);
}

}
1 change: 1 addition & 0 deletions src/main/templates/net/imagej/ops/Ops.list
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ namespaces = ```
]],
[name: "labeling", iface: "Labeling", ops: [
[name: "cca", iface: "CCA", aliases: ["connectedComponents", "connectedComponentAnalysis"]],
[name: "merge", iface: "Merge"],
]],
[name: "lbp", iface: "LBP", ops: [
[name: "lbp2D", iface: "LBP2D"]
Expand Down
116 changes: 116 additions & 0 deletions src/test/java/net/imagej/ops/labeling/MergeLabelingTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* #%L
* ImageJ software for multidimensional image processing and analysis.
* %%
* Copyright (C) 2014 - 2018 ImageJ developers.
* %%
* 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.imagej.ops.labeling;

import static org.junit.Assert.assertTrue;

import net.imagej.ops.AbstractOpTest;
import net.imglib2.FinalInterval;
import net.imglib2.RandomAccess;
import net.imglib2.img.Img;
import net.imglib2.roi.labeling.ImgLabeling;
import net.imglib2.roi.labeling.LabelingType;
import net.imglib2.type.logic.BitType;
import net.imglib2.type.numeric.integer.ByteType;

import org.junit.Before;
import org.junit.Test;

/**
* Tests {@link MergeLabeling}.
*
* @author Stefan Helfrich (University of Konstanz)
*/
public class MergeLabelingTest extends AbstractOpTest {

private ImgLabeling<Integer, ByteType> in1;
private ImgLabeling<Integer, ByteType> in2;
private ImgLabeling<Integer, ByteType> out;

@Override
@Before
public void setUp() {
super.setUp();
in1 = ops.create().imgLabeling(new FinalInterval(2, 2));
RandomAccess<LabelingType<Integer>> randomAccess = in1.randomAccess();
randomAccess.setPosition(new int[] { 0, 0 });
randomAccess.get().add(0);
randomAccess.setPosition(new int[] { 0, 1 });
randomAccess.get().add(1);
randomAccess.setPosition(new int[] { 1, 0 });
randomAccess.get().add(2);
randomAccess.setPosition(new int[] { 1, 1 });
randomAccess.get().add(3);

in2 = ops.create().imgLabeling(new FinalInterval(2, 2));
randomAccess = in2.randomAccess();
randomAccess.setPosition(new int[] { 0, 0 });
randomAccess.get().add(10);
randomAccess.setPosition(new int[] { 0, 1 });
randomAccess.get().add(11);
randomAccess.setPosition(new int[] { 1, 0 });
randomAccess.get().add(12);
randomAccess.setPosition(new int[] { 1, 1 });
randomAccess.get().add(13);

out = ops.create().imgLabeling(new FinalInterval(2, 2));
}

@Test
public void testMerging() {
@SuppressWarnings("unchecked")
final ImgLabeling<Integer, ByteType> run =
(ImgLabeling<Integer, ByteType>) ops.run(MergeLabeling.class, in1, in2);
assertTrue(run.firstElement().contains(0));
assertTrue(run.firstElement().contains(10));
assertTrue(!run.firstElement().contains(3));
}

@Test
public void testMask() {
final Img<BitType> mask = ops.create().img(in1, new BitType());
final RandomAccess<BitType> maskRA = mask.randomAccess();
maskRA.setPosition(new int[] { 0, 0 });
maskRA.get().set(true);
maskRA.setPosition(new int[] { 1, 1 });
maskRA.get().set(true);
@SuppressWarnings("unchecked")
final MergeLabeling<Integer, ByteType, BitType> op = ops.op(
MergeLabeling.class, out, in1, in2, mask);
op.compute(in1, in2, out);
final RandomAccess<LabelingType<Integer>> outRA = out.randomAccess();
outRA.setPosition(new int[] { 0, 0 });
assertTrue(outRA.get().contains(0));
assertTrue(outRA.get().contains(10));
outRA.setPosition(new int[] { 0, 1 });
assertTrue(outRA.get().isEmpty());
}

}

0 comments on commit c63e09e

Please sign in to comment.