diff --git a/pom.xml b/pom.xml
index 53f4c2442c..9615c72992 100644
--- a/pom.xml
+++ b/pom.xml
@@ -219,6 +219,10 @@
org.scijava
scripting-javascript
+
+ org.scijava
+ vecmath
+
diff --git a/src/main/java/net/imagej/ops/geom/CentroidTuple3D.java b/src/main/java/net/imagej/ops/geom/CentroidTuple3D.java
new file mode 100644
index 0000000000..32a9770c71
--- /dev/null
+++ b/src/main/java/net/imagej/ops/geom/CentroidTuple3D.java
@@ -0,0 +1,32 @@
+package net.imagej.ops.geom;
+
+import net.imagej.ops.Ops;
+import net.imagej.ops.special.function.AbstractUnaryFunctionOp;
+import org.scijava.plugin.Plugin;
+import org.scijava.vecmath.Tuple3d;
+import org.scijava.vecmath.Vector3d;
+
+import java.util.Collection;
+
+/**
+ * Calculates the centroid (geometrical centre) of the given tuples
+ *
+ * @author Richard Domander (Royal Veterinary College, London)
+ */
+@Plugin(type = Ops.Geometric.Centroid.class)
+public class CentroidTuple3D extends AbstractUnaryFunctionOp, Vector3d>
+ implements Ops.Geometric.Centroid {
+ @Override
+ public Vector3d compute1(final Collection extends Tuple3d> input) {
+ final int vectors = input.size();
+ if (vectors == 0) {
+ return new Vector3d(Double.NaN, Double.NaN, Double.NaN);
+ }
+
+ final Vector3d sum = new Vector3d(0.0, 0.0, 0.0);
+ input.stream().forEach(sum::add);
+ sum.scale(1.0 / vectors);
+
+ return sum;
+ }
+}
diff --git a/src/main/java/net/imagej/ops/geom/GeomNamespace.java b/src/main/java/net/imagej/ops/geom/GeomNamespace.java
index 690265efbf..9b99f3cf55 100644
--- a/src/main/java/net/imagej/ops/geom/GeomNamespace.java
+++ b/src/main/java/net/imagej/ops/geom/GeomNamespace.java
@@ -51,6 +51,10 @@
import net.imglib2.util.Pair;
import org.scijava.plugin.Plugin;
+import org.scijava.vecmath.Tuple3d;
+import org.scijava.vecmath.Vector3d;
+
+import java.util.Collection;
/**
* Namespace for Geom.
@@ -221,6 +225,11 @@ public RealLocalizable centroid(final Mesh in) {
return result;
}
+ @OpMethod(op = net.imagej.ops.geom.CentroidTuple3D.class)
+ public Vector3d centroid(final Collection extends Tuple3d> in) {
+ return (Vector3d) ops().run(net.imagej.ops.Ops.Geometric.Centroid.class, in);
+ }
+
@OpMethod(op = net.imagej.ops.geom.geom2d.DefaultCircularity.class)
public DoubleType circularity(final Polygon in) {
final DoubleType result =
diff --git a/src/test/java/net/imagej/ops/geom/CentroidTuple3DTest.java b/src/test/java/net/imagej/ops/geom/CentroidTuple3DTest.java
new file mode 100644
index 0000000000..fa67648311
--- /dev/null
+++ b/src/test/java/net/imagej/ops/geom/CentroidTuple3DTest.java
@@ -0,0 +1,59 @@
+package net.imagej.ops.geom;
+
+import net.imagej.ops.AbstractOpTest;
+import org.junit.Test;
+import org.scijava.vecmath.Vector3d;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.DoubleStream;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for {@link CentroidTuple3D}
+ *
+ * @author Richard Domander (Royal Veterinary College, London)
+ */
+public class CentroidTuple3DTest extends AbstractOpTest {
+ @Test
+ public void testCompute1EmptyCollection() throws Exception {
+ final List emptyList = Collections.emptyList();
+
+ final Vector3d result = (Vector3d) ops.run(CentroidTuple3D.class, emptyList);
+
+ assertTrue("Coordinates should all be NaN",
+ DoubleStream.of(result.x, result.y, result.z).allMatch(Double::isNaN));
+ }
+
+ @Test
+ public void testCompute1SingleVector() throws Exception {
+ final Vector3d vector = new Vector3d(1.0, 2.0, 3.0);
+ final List vectors = Collections.singletonList(vector);
+
+ final Vector3d result = (Vector3d) ops.run(CentroidTuple3D.class, vectors);
+
+ assertEquals("Incorrect centroid vector", vector, result);
+ }
+
+ @Test
+ public void testCompute1() throws Exception {
+ final Vector3d expected = new Vector3d(0.5, 0.5, 0.5);
+ final List cubeVectors = Arrays.asList(
+ new Vector3d(0.0, 0.0, 0.0),
+ new Vector3d(1.0, 0.0, 0.0),
+ new Vector3d(1.0, 1.0, 0.0),
+ new Vector3d(0.0, 1.0, 0.0),
+ new Vector3d(0.0, 0.0, 1.0),
+ new Vector3d(1.0, 0.0, 1.0),
+ new Vector3d(1.0, 1.0, 1.0),
+ new Vector3d(0.0, 1.0, 1.0)
+ );
+
+ final Vector3d result = (Vector3d) ops.run(CentroidTuple3D.class, cubeVectors);
+
+ assertEquals("Incorrect centroid vector", expected, result);
+ }
+}
\ No newline at end of file