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++) {