diff --git a/Modern/utilities/src/main/java/org/bonej/utilities/AxisUtils.java b/Modern/utilities/src/main/java/org/bonej/utilities/AxisUtils.java
index de961350..3202c79a 100644
--- a/Modern/utilities/src/main/java/org/bonej/utilities/AxisUtils.java
+++ b/Modern/utilities/src/main/java/org/bonej/utilities/AxisUtils.java
@@ -140,6 +140,36 @@ private AxisUtils() {}
return axisStream(space).anyMatch(a -> a.type() == Axes.TIME);
}
+ /**
+ * Checks whether the given annotated space contains any dimension (axis)
+ * that is not one of the standard X, Y, Z, Channel, or Time axes.
+ *
+ *
This method is useful for detecting the presence of non-standard axes
+ * (e.g., Angle, Lambda, Phase, etc.) in an image or dataset, which may require
+ * special handling in algorithms or processing pipelines.
+ *
+ * @param the type of the annotated space, which must extend {@code AnnotatedSpace}
+ * @param the type of the axis, which must extend {@code TypedAxis}
+ * @param space the annotated space (e.g., image or dataset) to check for non-standard dimensions
+ * @return {@code true} if the space contains at least one axis that is not
+ * X, Y, Z, Channel, or Time; {@code false} otherwise
+ *
+ * @see AnnotatedSpace
+ * @see TypedAxis
+ * @see Axes
+ */
+ public static , A extends TypedAxis> boolean
+ hasNonXYZCTDimension(final S space)
+ {
+ return axisStream(space).anyMatch(a -> (
+ a.type() != Axes.X &&
+ a.type() != Axes.Y &&
+ a.type() != Axes.Z &&
+ a.type() != Axes.CHANNEL &&
+ a.type() != Axes.TIME
+ ));
+ }
+
/**
* Checks if the spatial axes in the space have the same i.e. isotropic
* scaling.
diff --git a/Modern/utilities/src/main/java/org/bonej/utilities/RoiManagerUtil.java b/Modern/utilities/src/main/java/org/bonej/utilities/RoiManagerUtil.java
index 94bcb0b3..7a526f89 100644
--- a/Modern/utilities/src/main/java/org/bonej/utilities/RoiManagerUtil.java
+++ b/Modern/utilities/src/main/java/org/bonej/utilities/RoiManagerUtil.java
@@ -2,7 +2,7 @@
* #%L
* Utility methods for BoneJ2
* %%
- * Copyright (C) 2015 - 2025 Michael Doube, BoneJ developers
+ * Copyright (C) 2015 - 2026 Michael Doube, BoneJ developers
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -32,7 +32,17 @@
import ij.gui.Roi;
import ij.plugin.frame.RoiManager;
+import ij.process.ImageProcessor;
+import net.imglib2.Cursor;
+import net.imglib2.FinalInterval;
+import net.imglib2.RandomAccess;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.img.array.ArrayImgs;
+import net.imglib2.type.logic.BitType;
+import net.imglib2.view.Views;
+import java.awt.Rectangle;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@@ -71,10 +81,156 @@ public static boolean isActiveOnAllSlices(final int sliceNumber) {
public static List pointROICoordinates(final RoiManager manager) {
final Roi[] rois = manager.getRoisAsArray();
return Arrays.stream(rois).filter(roi -> roi.getType() == Roi.POINT).map(
- roi -> {
- final int z = roi.getZPosition();
- return Arrays.stream(roi.getContainedPoints()).distinct().map(
- p -> new Vector3d(p.x, p.y, z));
- }).flatMap(s -> s).distinct().collect(Collectors.toList());
+ roi -> {
+ final int z = roi.getZPosition();
+ return Arrays.stream(roi.getContainedPoints()).distinct().map(
+ p -> new Vector3d(p.x, p.y, z));
+ }).flatMap(s -> s).distinct().collect(Collectors.toList());
+ }
+
+ /**
+ * Determine whether the ROI Manager is not active or has no entries
+ *
+ * @return true if there are no ROIs to process
+ */
+ public static boolean roiManagerIsEmpty() {
+ final RoiManager rm = RoiManager.getInstance2();
+ //if there are no ROIs or no ROI Manager
+ return (rm == null || rm.getCount() == 0);
+ }
+
+ /**
+ * Build a 2D union mask (BitType) for the given XY view and (z,t,c) plane using ROIs from the
+ * (visible) IJ1 RoiManager. The returned mask is aligned to the view's interval.
+ *
+ * @param xyView 2D view (may have non-zero min).
+ * @param z1 1-based Z position (IJ1 convention)
+ * @param t1 1-based T position (IJ1 convention)
+ * @param c1 1-based C position (IJ1 convention)
+ * @return a BitType mask that represents all the ROIs active on this XY slice or null if the ROI
+ * Manager is null or empty, or if there are no ROIs active on this XY slice.
+ */
+ public static RandomAccessibleInterval unionMaskFromRoiManager(
+ final RandomAccessibleInterval> xyView,
+ final int z1,
+ final int t1,
+ final int c1
+ ) {
+
+ //if there are no ROIs or no ROI Manager
+ if (roiManagerIsEmpty()) {
+ return null;
+ }
+
+ final RoiManager rm = RoiManager.getInstance2();
+
+ final Roi[] all = rm.getRoisAsArray();
+ final List rois = new ArrayList<>();
+ for (final Roi r : all) {
+ if (matchesPlane(r, z1, t1, c1)) rois.add(r);
+ }
+ if (rois.isEmpty()) {
+ return null;
+ }
+
+ // View geometry
+ final long minX = xyView.min(0);
+ final long minY = xyView.min(1);
+ final int w = (int) xyView.dimension(0);
+ final int h = (int) xyView.dimension(1);
+
+ // Bit mask aligned to xyView interval
+ final RandomAccessibleInterval mask =
+ ArrayImgs.bits(w, h); // min at (0,0) for now
+ final RandomAccessibleInterval alignedMask =
+ Views.translate(mask, minX, minY); // now mask coords match xyView coords
+
+ // Fill union by OR-ing each ROI's raster mask into alignedMask
+ for (final Roi roi : rois) {
+ orRoiIntoMask(roi, alignedMask);
+ }
+
+ return alignedMask;
+ }
+
+ private static boolean matchesPlane(final Roi roi, final int z1, final int t1, final int c1) {
+ final int rz = roi.getZPosition();
+ final int rt = roi.getTPosition();
+ final int rc = roi.getCPosition();
+ return (rz == 0 || rz == z1) && (rt == 0 || rt == t1) && (rc == 0 || rc == c1);
+ }
+
+ /**
+ * Create a mask that matches this view in size and position and which has all true (1) values
+ *
+ * @param xyView
+ * @return an all-true mask that matches the given xyView
+ */
+ private static RandomAccessibleInterval fullMaskLike(final RandomAccessibleInterval> xyView) {
+ final int w = (int) xyView.dimension(0);
+ final int h = (int) xyView.dimension(1);
+ final long minX = xyView.min(0);
+ final long minY = xyView.min(1);
+ final RandomAccessibleInterval m = ArrayImgs.bits(w, h);
+ final Cursor cursor = Views.flatIterable(m).cursor();
+ while (cursor.hasNext()) {
+ cursor.next().set(true);
+ }
+ return Views.translate(m, minX, minY);
+ }
+
+ private static RandomAccessibleInterval emptyMaskLike(final RandomAccessibleInterval> xyView) {
+ final int w = (int) xyView.dimension(0);
+ final int h = (int) xyView.dimension(1);
+ final long minX = xyView.min(0);
+ final long minY = xyView.min(1);
+ final RandomAccessibleInterval m = ArrayImgs.bits(w, h);
+ return Views.translate(m, minX, minY);
+ }
+
+ /**
+ * OR a single IJ1 Roi into an ImgLib2 BitType mask.
+ * The mask must be in the same pixel coordinate system as the Roi (typically image coordinates).
+ */
+ private static void orRoiIntoMask(final Roi roi, final RandomAccessibleInterval mask) {
+ final Rectangle b = roi.getBounds(); // ROI bounds in image pixel coords
+ final ImageProcessor ipMask = roi.getMask(); // ROI-local mask (may be null for rectangle ROIs)
+
+ // Intersect bounds with mask interval to stay safe
+ final long x0 = Math.max(b.x, mask.min(0));
+ final long y0 = Math.max(b.y, mask.min(1));
+ final long x1 = Math.min(b.x + b.width - 1L, mask.max(0));
+ final long y1 = Math.min(b.y + b.height - 1L, mask.max(1));
+ if (x1 < x0 || y1 < y0) return;
+
+ if (ipMask == null) {
+ // Rectangle ROI: set all pixels in intersected bounds
+ final RandomAccessibleInterval view = Views.interval(mask, new FinalInterval(new long[]{x0, y0}, new long[]{x1, y1}));
+ for (final BitType bt : Views.flatIterable(view)) bt.setOne();
+ return;
+ }
+
+ // Non-rectangular ROI: use ROI-local mask pixels and copy into global mask with OR semantics.
+ // ipMask is in ROI-local coordinates: (0..b.width-1, 0..b.height-1)
+ final ImageProcessor byteMask = ipMask.convertToByte(false);
+ final byte[] mp = (byte[]) byteMask.getPixels();
+ final int mw = byteMask.getWidth();
+
+ for (long yy = y0; yy <= y1; yy++) {
+ final int my = (int) (yy - b.y);
+ final int mRow = my * mw;
+
+ for (long xx = x0; xx <= x1; xx++) {
+ final int mx = (int) (xx - b.x);
+ if ((mp[mRow + mx] & 0xff) != 0) {
+ mask.randomAccess().setPositionAndGet(xx, 0); // can't do two dims at once
+ // Use a small RA helper for clarity/efficiency:
+ final RandomAccess ra = mask.randomAccess();
+ ra.setPosition(xx, 0);
+ ra.setPosition(yy, 1);
+ ra.get().setOne();
+ }
+ }
+ }
}
}
diff --git a/Modern/wrapperPlugins/pom.xml b/Modern/wrapperPlugins/pom.xml
index 81f3ed8e..21d746e8 100644
--- a/Modern/wrapperPlugins/pom.xml
+++ b/Modern/wrapperPlugins/pom.xml
@@ -119,6 +119,7 @@
org.bonej
bonej-utilities
+ 7.1.10-SNAPSHOT
org.bonej
diff --git a/Modern/wrapperPlugins/src/main/java/org/bonej/wrapperPlugins/CommonMessages.java b/Modern/wrapperPlugins/src/main/java/org/bonej/wrapperPlugins/CommonMessages.java
index a1edfa04..22098a9b 100644
--- a/Modern/wrapperPlugins/src/main/java/org/bonej/wrapperPlugins/CommonMessages.java
+++ b/Modern/wrapperPlugins/src/main/java/org/bonej/wrapperPlugins/CommonMessages.java
@@ -45,6 +45,7 @@ public final class CommonMessages {
static final String NOT_8_BIT_BINARY_IMAGE = "Need an 8-bit binary image";
static final String NO_IMAGE_OPEN = "No image open";
static final String NO_SKELETONS = "Image contained no skeletons";
+ static final String HAS_NONSTANDARD_DIMENSIONS = "Image has non-standard dimensions";
private CommonMessages() {}
}
diff --git a/Modern/wrapperPlugins/src/main/java/org/bonej/wrapperPlugins/ElementFractionWrapper.java b/Modern/wrapperPlugins/src/main/java/org/bonej/wrapperPlugins/ElementFractionWrapper.java
index 2928d930..a1199fbd 100644
--- a/Modern/wrapperPlugins/src/main/java/org/bonej/wrapperPlugins/ElementFractionWrapper.java
+++ b/Modern/wrapperPlugins/src/main/java/org/bonej/wrapperPlugins/ElementFractionWrapper.java
@@ -30,7 +30,6 @@
package org.bonej.wrapperPlugins;
-import static java.util.stream.Collectors.toList;
import static org.bonej.wrapperPlugins.CommonMessages.NOT_BINARY;
import static org.bonej.wrapperPlugins.CommonMessages.NO_IMAGE_OPEN;
import static org.bonej.wrapperPlugins.CommonMessages.WEIRD_SPATIAL;
@@ -41,26 +40,32 @@
import net.imagej.axis.AxisType;
import net.imagej.ops.OpService;
import net.imagej.units.UnitService;
-import net.imglib2.IterableInterval;
+import net.imglib2.Cursor;
+import net.imglib2.RandomAccessibleInterval;
import net.imglib2.type.NativeType;
import net.imglib2.type.logic.BitType;
import net.imglib2.type.numeric.RealType;
+import net.imglib2.type.numeric.integer.UnsignedByteType;
import net.imglib2.view.Views;
+
import org.bonej.utilities.AxisUtils;
import org.bonej.utilities.ElementUtil;
+import org.bonej.utilities.RoiManagerUtil;
import org.bonej.utilities.SharedTable;
-import org.bonej.wrapperPlugins.wrapperUtils.Common;
-import org.bonej.wrapperPlugins.wrapperUtils.HyperstackUtils;
-import org.bonej.wrapperPlugins.wrapperUtils.HyperstackUtils.Subspace;
import org.bonej.wrapperPlugins.wrapperUtils.ResultUtils;
import org.scijava.app.StatusService;
import org.scijava.command.Command;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
+//import ij.plugin.frame.RoiManager;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
-import java.util.stream.Stream;
+import java.util.stream.IntStream;
/**
* This command estimates the size of the given sample by counting its
@@ -70,9 +75,10 @@
* results table. Results are shown in calibrated units, if possible.
*
* @author Richard Domander
+ * @author Michael Doube
*/
@Plugin(type = Command.class,
- menuPath = "Plugins>BoneJ>Fraction>Area/Volume fraction")
+menuPath = "Plugins>BoneJ>Fraction>Area/Volume fraction")
public class ElementFractionWrapper & NativeType> extends BoneJCommand
{
@@ -94,48 +100,138 @@ public class ElementFractionWrapper & NativeType> exten
/** The calibrated size of an element in the image */
private double elementSize;
+
@Override
public void run() {
statusService.showStatus("Element fraction: initializing");
- findSubspaces(inputImage);
prepareResultDisplay();
final String name = inputImage.getName();
- for (int i = 0; i < subspaces.size(); i++) {
- final Subspace subspace = subspaces.get(i);
- statusService.showStatus("Element fraction: calculating subspace #" + (i +
- 1));
- statusService.showProgress(i, subspaces.size());
- // The value of each foreground element in a bit type image is 1, so we
- // can count their number just by summing
- final IterableInterval interval = Views.flatIterable(
- subspace.interval);
- final double foregroundSize = opService.stats().sum(interval)
- .getRealDouble() * elementSize;
- final double totalSize = interval.size() * elementSize;
- final double ratio = foregroundSize / totalSize;
- final String suffix = subspace.toString();
- final String label = suffix.isEmpty() ? name : name + " " + suffix;
- addResults(label, foregroundSize, totalSize, ratio);
- }
- resultsTable = SharedTable.getTable();
- }
- private void findSubspaces(final ImgPlus inputImage) {
- if (AxisUtils.countSpatialDimensions(inputImage) == 3) {
- subspaces = find3DSubspaces(inputImage);
- } else {
- subspaces = find2DSubspaces(inputImage);
- }
- }
+ //get the number of slices, channels and time points to iterate over
+ int zIdx = axisIndex(inputImage, Axes.Z);
+ int tIdx = axisIndex(inputImage, Axes.TIME);
+ int cIdx = axisIndex(inputImage, Axes.CHANNEL);
+ // System.out.println("axisIndex (w,h,d,t,c) = ("+xIdx+", "+yIdx+", "+zIdx+", "+tIdx+", "+cIdx+")");
+
+ //if an axis is missing, set its size to 1, otherwise get its size.
+ int cSize = (cIdx >= 0) ? (int) inputImage.dimension(cIdx) : 1;
+ int zSize = (zIdx >= 0) ? (int) inputImage.dimension(zIdx) : 1;
+ int tSize = (tIdx >= 0) ? (int) inputImage.dimension(tIdx) : 1;
+
+ //thread-safe counters
+ long[] fgCounts = new long[zSize];
+ long[] totalCounts = new long[zSize];
+
+ //iterate over all the timepoints and channels, and for each iterate over z.
+// long start = System.nanoTime();
+
+ for (int t = 0; t < tSize; t++) {
+ final int time = t;
+ for (int c = 0; c < cSize; c++) {
+ final int channel = c;
+ boolean aborted = IntStream.range(0, zSize).parallel().anyMatch(z -> {
+ statusService.showStatus("Element fraction: channel "+channel+", time "+time+", z "+z);
+ //counters for this thread and z-position
+ long total = 0;
+ long fg = 0;
+ totalCounts[z] = 0;
+ fgCounts[z] = 0;
+
+ //create a 2D view into the data
+ RandomAccessibleInterval xyView = get2DSlice(inputImage, z, time, channel);
+
+ //If the ROI Manager contains ROIs, use them
+ if (!RoiManagerUtil.roiManagerIsEmpty()) {
+
+ //get a mask for this xyView from ROIs in the ROI Manager
+ RandomAccessibleInterval mask =
+ RoiManagerUtil.unionMaskFromRoiManager(xyView, z + 1, time + 1, channel + 1);
+
+ //don't process slices that lack a mask
+ if (mask == null) return false;
+
+ //Iterate over the mask and the slice
+ Cursor sliceCursor = Views.flatIterable(xyView).cursor();
+ Cursor maskCursor = Views.flatIterable(mask).cursor();
+
+ while (maskCursor.hasNext()) {
+ maskCursor.fwd();
+ sliceCursor.fwd();
+ //if we are inside an ROI
+ if (maskCursor.get().get()) {
+ total++;
+ final double v = sliceCursor.get().getRealDouble();
+ //if foreground
+ if (v == 255.0) {
+ fg++;
+ } else if (v != 0.0) {
+ cancelMacroSafe(this, NOT_BINARY);
+ return true;
+ }
+ }
+ }
+ //Otherwise process all pixels in the image
+ } else {
+ Cursor sliceCursor = Views.flatIterable(xyView).cursor();
+ while (sliceCursor.hasNext()) {
+ sliceCursor.fwd();
+ total++;
+ final double v = sliceCursor.get().getRealDouble();
+ //if foreground
+ if (v == 255.0) {
+ fg++;
+ } else if (v != 0.0) {
+ cancelMacroSafe(this, NOT_BINARY);
+ return true;
+ }
+ }
+ }
+ totalCounts[z] = total;
+ fgCounts[z] = fg;
+ //successful completion (aborted = false)
+ return false;
+ });
+
+ if (aborted) {
+ //no need to do anything
+ }
+
+// long end = System.nanoTime();
+
+// System.out.println("Volume fraction took "+(end-start) / 1E6+" ms");
+
+ //don't show any results for cancelled plugins
+ if (this.isCanceled()) return;
+
+ long total = 0;
+ long fg = 0;
+ for (int z = 0; z < zSize; z++) {
+ total += totalCounts[z];
+ fg += fgCounts[z];
+ }
+
+ double tv = total * elementSize;
+ double bv = fg * elementSize;
+
+ String label = name;
+
+ //add a channel suffix if there is more than one channel
+ label = cSize > 1 ? label + " Channel: " + c : label;
+ //add a comma between channel and timepoint if both are more than one
+ label = (cSize > 1 && tSize > 1) ? label +"," : label;
+ //add a time suffix if there is more than one timepoint
+ label = tSize > 1 ? label + " Time: " + t : label;
- private List> find2DSubspaces(final ImgPlus inputImage) {
- final List axisTypes = Stream.of(Axes.X, Axes.Y).collect(toList());
- final ImgPlus bitImgPlus = Common.toBitTypeImgPlus(opService, inputImage);
- return HyperstackUtils.splitSubspaces(bitImgPlus, axisTypes).collect(toList());
+ addResults(label, bv, tv, bv/tv);
+
+ // Ensure SharedTable is populated
+ resultsTable = SharedTable.getTable();
+ }
+ }
}
private void addResults(final String label, final double foregroundSize,
- final double totalSize, final double ratio)
+ final double totalSize, final double ratio)
{
SharedTable.add(label, boneSizeHeader, foregroundSize);
SharedTable.add(label, totalSizeHeader, totalSize);
@@ -146,14 +242,14 @@ private void addResults(final String label, final double foregroundSize,
private void prepareResultDisplay() {
final char exponent = ResultUtils.getExponent(inputImage);
final String unitHeader = ResultUtils.getUnitHeader(inputImage, unitService,
- String.valueOf(exponent));
+ String.valueOf(exponent));
final String sizeDescription = ResultUtils.getSizeDescription(inputImage);
boneSizeHeader = "B" + sizeDescription + " " + unitHeader;
totalSizeHeader = "T" + sizeDescription + " " + unitHeader;
ratioHeader = "B" + sizeDescription + "/T" + sizeDescription;
elementSize = ElementUtil.calibratedSpatialElementSize(inputImage,
- unitService);
+ unitService);
}
@@ -164,14 +260,70 @@ private void validateImage() {
return;
}
- if (!ElementUtil.isBinary(inputImage)) {
- cancelMacroSafe(this, NOT_BINARY);
- }
-
final long spatialDimensions = AxisUtils.countSpatialDimensions(inputImage);
if (spatialDimensions < 2 || spatialDimensions > 3) {
+ inputImage = null;
cancelMacroSafe(this, WEIRD_SPATIAL);
+ return;
+ }
+
+ if (AxisUtils.hasNonXYZCTDimension(inputImage)) {
+ inputImage = null;
+ cancelMacroSafe(this, CommonMessages.HAS_NONSTANDARD_DIMENSIONS);
+ }
+
+ T type = inputImage.firstElement();
+ //enforce 8-bit (IJ1 binary is 0 and 255)
+ if (type instanceof UnsignedByteType)
+ return;
+ else {
+ inputImage = null;
+ cancelMacroSafe(this, NOT_BINARY);
+ return;
+ }
+ }
+
+ /** Return the index of the given axis in the ImgPlus, or -1 if absent. */
+ private static int axisIndex(ImgPlus> img, AxisType axis) {
+ for (int d = 0; d < img.numDimensions(); d++) {
+ if (img.axis(d).type().equals(axis)) return d;
+ }
+ return -1;
+ }
+
+ public static RandomAccessibleInterval get2DSlice(ImgPlus img, int z, int t, int c) {
+ // Get the indices of Z, TIME, and CHANNEL
+ int zIdx = axisIndex(img, Axes.Z);
+ int tIdx = axisIndex(img, Axes.TIME);
+ int cIdx = axisIndex(img, Axes.CHANNEL);
+
+ // Collect all non-spatial dimensions (Z, TIME, CHANNEL) and their target slice indices
+ List dimensionsToSlice = new ArrayList<>();
+ if (zIdx >= 0 && img.dimension(zIdx) > 1) dimensionsToSlice.add(new DimensionSlice(zIdx, z));
+ if (tIdx >= 0 && img.dimension(tIdx) > 1) dimensionsToSlice.add(new DimensionSlice(tIdx, t));
+ if (cIdx >= 0 && img.dimension(cIdx) > 1) dimensionsToSlice.add(new DimensionSlice(cIdx, c));
+
+ // Sort dimensions by index in descending order
+ Collections.sort(dimensionsToSlice, Comparator.comparingInt(ds -> -ds.index));
+
+ // Slice along dimensions in descending order of their indices
+ RandomAccessibleInterval view = img;
+ for (DimensionSlice ds : dimensionsToSlice) {
+ view = Views.hyperSlice(view, ds.index, ds.slice);
+ }
+
+ // The result is now a 2D XY slice
+ return view;
+ }
+
+ // Helper class to store dimension index and slice index
+ private static class DimensionSlice {
+ int index;
+ int slice;
+
+ DimensionSlice(int index, int slice) {
+ this.index = index;
+ this.slice = slice;
}
}
- // endregion
}
diff --git a/Modern/wrapperPlugins/src/test/java/org/bonej/wrapperPlugins/CommonWrapperTests.java b/Modern/wrapperPlugins/src/test/java/org/bonej/wrapperPlugins/CommonWrapperTests.java
index 0539c387..00214122 100644
--- a/Modern/wrapperPlugins/src/test/java/org/bonej/wrapperPlugins/CommonWrapperTests.java
+++ b/Modern/wrapperPlugins/src/test/java/org/bonej/wrapperPlugins/CommonWrapperTests.java
@@ -153,7 +153,7 @@ static void testNonBinaryImageCancelsPlugin(
// VERIFY
assertTrue(
- "An image with more than two colours should have cancelled the plugin",
+ "An image with more than two values should have cancelled the plugin",
module.isCanceled());
assertEquals("Cancel reason is incorrect", CommonMessages.NOT_BINARY, module
.getCancelReason());
@@ -189,7 +189,7 @@ static void testNonBinaryImagePlusCancelsPlugin(
// VERIFY
assertTrue(
- "An image with more than two colours should have cancelled the plugin",
+ "An image with more than two values should have cancelled the plugin",
module.isCanceled());
assertEquals("Cancel reason is incorrect",
CommonMessages.NOT_8_BIT_BINARY_IMAGE, module.getCancelReason());
diff --git a/Modern/wrapperPlugins/src/test/java/org/bonej/wrapperPlugins/ElementFractionWrapperTest.java b/Modern/wrapperPlugins/src/test/java/org/bonej/wrapperPlugins/ElementFractionWrapperTest.java
index 406af297..460e5ab7 100644
--- a/Modern/wrapperPlugins/src/test/java/org/bonej/wrapperPlugins/ElementFractionWrapperTest.java
+++ b/Modern/wrapperPlugins/src/test/java/org/bonej/wrapperPlugins/ElementFractionWrapperTest.java
@@ -48,7 +48,7 @@
import net.imagej.axis.DefaultLinearAxis;
import net.imglib2.img.Img;
import net.imglib2.img.array.ArrayImgs;
-import net.imglib2.type.logic.BitType;
+import net.imglib2.type.numeric.integer.UnsignedByteType;
import net.imglib2.type.numeric.real.DoubleType;
import net.imglib2.view.Views;
@@ -80,6 +80,7 @@ public void testNullImageCancelsElementFraction() {
@Test
public void testResults3DHyperstack() throws Exception {
+ System.out.println("testResults3DHyperstack()");
// SETUP
final String unit = "mm";
final double scale = 0.9;
@@ -98,25 +99,25 @@ public void testResults3DHyperstack() throws Exception {
expectedRatios };
final String[] expectedHeaders = { "BV (" + unit + "³)",
"TV (" + unit + "³)", "BV/TV" };
- // Create an hyperstack Img with a cube at (channel:0, frame:0) and (c:1,
- // f:1)
- final Img img = ArrayImgs.bits(stackSide, stackSide, stackSide, 2,
- 2);
- Views.interval(img, new long[] { 1, 1, 1, 0, 0 }, new long[] { 5, 5, 5, 0,
- 0 }).forEach(BitType::setOne);
- Views.interval(img, new long[] { 1, 1, 1, 1, 1 }, new long[] { 5, 5, 5, 1,
- 1 }).forEach(BitType::setOne);
+ // Create a hyperstack Img with a cube at (channel:0, frame:0) and (c:1, f:1)
+ final Img img = ArrayImgs.unsignedBytes(stackSide, stackSide, stackSide, 2, 2);
+ Views.interval(img, new long[] { 1, 1, 1, 0, 0 }, new long[] { 5, 5, 5, 0, 0 }).forEach(t -> t.set(255));
+ Views.interval(img, new long[] { 1, 1, 1, 1, 1 }, new long[] { 5, 5, 5, 1, 1 }).forEach(t -> t.set(255));
+
// Wrap Img in a calibrated ImgPlus
final double[] calibration = { scale, scale, scale, 1.0, 1.0 };
final String[] units = { unit, unit, unit, "", "" };
final AxisType[] axes = { Axes.X, Axes.Y, Axes.Z, Axes.TIME, Axes.CHANNEL };
- final ImgPlus imgPlus = new ImgPlus<>(img, "Cube", axes,
- calibration, units);
-
+ final ImgPlus imgPlus = new ImgPlus<>(img, "Cube", axes,
+ calibration, units);
+
// EXECUTE
final CommandModule module = command().run(
ElementFractionWrapper.class, true, "inputImage", imgPlus).get();
+ //Make sure the plugin wasn't cancelled
+ assertTrue(module.getCancelReason(), !module.isCanceled());
+
// VERIFY
@SuppressWarnings("unchecked")
final List> table =
@@ -138,6 +139,7 @@ public void testResults3DHyperstack() throws Exception {
@Test
public void testResultsComposite2D() throws Exception {
+ System.out.println("testResultsComposite2D()");
// SETUP
final String unit = "mm";
final int squareSide = 5;
@@ -154,24 +156,27 @@ public void testResultsComposite2D() throws Exception {
final String[] expectedHeaders = { "BA (" + unit + "\u00B2)",
"TA (" + unit + "\u00B2)", "BA/TA" };
// Create an 2D image with two channels with a square drawn on channel 2
- final Img img = ArrayImgs.bits(stackSide, stackSide, 2);
- Views.interval(img, new long[] { 1, 1, 1 }, new long[] { 5, 5, 1 }).forEach(
- BitType::setOne);
+ final Img img = ArrayImgs.unsignedBytes(stackSide, stackSide, 2);
+ // Set the square region to 255 (foreground)
+ Views.interval(img, new long[] { 1, 1, 1 }, new long[] { 5, 5, 1 }).forEach(t -> t.set(255));
// Wrap Img in an ImgPlus
final double[] calibration = { 1.0, 1.0, 1.0 };
final String[] units = { unit, unit, "" };
final AxisType[] axes = { Axes.X, Axes.Y, Axes.CHANNEL };
- final ImgPlus imgPlus = new ImgPlus<>(img, "Square", axes,
- calibration, units);
+ final ImgPlus imgPlus = new ImgPlus<>(img, "Square", axes, calibration, units);
// EXECUTE
final CommandModule module = command().run(
ElementFractionWrapper.class, true, "inputImage", imgPlus).get();
+
+ //Make sure the plugin wasn't cancelled
+ assertTrue(module.getCancelReason(), !module.isCanceled());
// VERIFY
@SuppressWarnings("unchecked")
final List> table =
(List>) module.getOutput("resultsTable");
+
assertNotNull(table);
assertEquals("Wrong number of columns", 3, table.size());
for (int i = 0; i < 3; i++) {