From 2944afd3ce3123ec127b48c13907296860e2c69a Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Sun, 6 Apr 2025 08:25:00 -0400 Subject: [PATCH 1/4] Add VolumeUtils with methods to access original data and current view --- .../kotlin/sc/iview/process/VolumeUtils.kt | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/main/kotlin/sc/iview/process/VolumeUtils.kt diff --git a/src/main/kotlin/sc/iview/process/VolumeUtils.kt b/src/main/kotlin/sc/iview/process/VolumeUtils.kt new file mode 100644 index 00000000..8e1817c0 --- /dev/null +++ b/src/main/kotlin/sc/iview/process/VolumeUtils.kt @@ -0,0 +1,73 @@ +/*- + * #%L + * Scenery-backed 3D visualization package for ImageJ. + * %% + * Copyright (C) 2016 - 2024 sciview 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 sc.iview.process + +import graphics.scenery.volumes.Volume +import net.imglib2.RandomAccessibleInterval +import net.imglib2.view.IntervalView +import net.imglib2.view.Views +import org.scijava.log.LogService + +/** + * Utility methods for working with Volumes + * + * @author You-Name-Here + */ +object VolumeUtils { + /** + * Gets the original data source of a Volume as a RandomAccessibleInterval + * + * @param volume The volume to get the original data from + * @return The original RandomAccessibleInterval, or null if not available + */ + @Suppress("UNCHECKED_CAST") + fun getOriginalRandomAccessibleInterval(volume: Volume): RandomAccessibleInterval? { + return volume.metadata["RandomAccessibleInterval"] as? RandomAccessibleInterval + } + + /** + * Gets the current view of a Volume based on the current timepoint + * + * @param volume The volume to get the current view from + * @return An IntervalView representing the current timepoint of the volume, or null if not available + */ + @Suppress("UNCHECKED_CAST") + fun getCurrentView(volume: Volume): IntervalView? { + val originalRAI = getOriginalRandomAccessibleInterval(volume) ?: return null + + // Check if the original data is time-series (has more than 3 dimensions) + return if (originalRAI.numDimensions() > 3) { + // Create a view for the current timepoint by fixing the 4th dimension (time) + Views.hyperSlice(originalRAI, 3, volume.currentTimepoint.toLong()) as IntervalView + } else { + // If it's not a time-series, just return the original data as an IntervalView + Views.interval(originalRAI, originalRAI) as IntervalView + } + } +} From 0e4ae969ae13345b20a770fbb914e43aad5f068a Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Sun, 6 Apr 2025 08:25:19 -0400 Subject: [PATCH 2/4] Add extension functions for Volume to get original data and current view --- .../sc/iview/process/VolumeExtensions.kt | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/main/kotlin/sc/iview/process/VolumeExtensions.kt diff --git a/src/main/kotlin/sc/iview/process/VolumeExtensions.kt b/src/main/kotlin/sc/iview/process/VolumeExtensions.kt new file mode 100644 index 00000000..1ab4ec31 --- /dev/null +++ b/src/main/kotlin/sc/iview/process/VolumeExtensions.kt @@ -0,0 +1,59 @@ +/*- + * #%L + * Scenery-backed 3D visualization package for ImageJ. + * %% + * Copyright (C) 2016 - 2024 sciview 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 sc.iview.process + +import graphics.scenery.volumes.Volume +import net.imglib2.RandomAccessibleInterval +import net.imglib2.view.IntervalView + +/** + * Extension functions for the Volume class + * + * @author You-Name-Here + */ + +/** + * Gets the original data source of a Volume as a RandomAccessibleInterval + * + * @return The original RandomAccessibleInterval, or null if not available + */ +@Suppress("UNCHECKED_CAST") +fun Volume.getOriginalRandomAccessibleInterval(): RandomAccessibleInterval? { + return VolumeUtils.getOriginalRandomAccessibleInterval(this) +} + +/** + * Gets the current view of a Volume based on the current timepoint + * + * @return An IntervalView representing the current timepoint of the volume, or null if not available + */ +@Suppress("UNCHECKED_CAST") +fun Volume.getCurrentView(): IntervalView? { + return VolumeUtils.getCurrentView(this) +} From 155c0e565c76bbd17e88a93a31f125ab2cffc1ad Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Sun, 6 Apr 2025 08:25:51 -0400 Subject: [PATCH 3/4] Add example command to demonstrate usage of new Volume extension methods --- .../commands/process/VolumeMarchingCubes.java | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/main/java/sc/iview/commands/process/VolumeMarchingCubes.java diff --git a/src/main/java/sc/iview/commands/process/VolumeMarchingCubes.java b/src/main/java/sc/iview/commands/process/VolumeMarchingCubes.java new file mode 100644 index 00000000..1bec6569 --- /dev/null +++ b/src/main/java/sc/iview/commands/process/VolumeMarchingCubes.java @@ -0,0 +1,132 @@ +/*- + * #%L + * Scenery-backed 3D visualization package for ImageJ. + * %% + * Copyright (C) 2016 - 2024 sciview 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 sc.iview.commands.process; + +import graphics.scenery.Group; +import graphics.scenery.Mesh; +import graphics.scenery.Node; +import graphics.scenery.volumes.Volume; +import net.imagej.mesh.MeshConnectedComponents; +import net.imagej.mesh.RemoveDuplicateVertices; +import net.imagej.ops.OpService; +import net.imagej.ops.geom.geom3d.mesh.BitTypeVertexInterpolator; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.Img; +import net.imglib2.type.logic.BitType; +import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.view.IntervalView; +import org.joml.Vector3f; +import org.scijava.command.Command; +import org.scijava.log.LogService; +import org.scijava.plugin.Menu; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.ui.DialogPrompt; +import org.scijava.ui.UIService; +import sc.iview.SciView; +import sc.iview.process.MeshConverter; +import sc.iview.process.VolumeExtensionsKt; + +import static sc.iview.commands.MenuWeights.PROCESS; +import static sc.iview.commands.MenuWeights.PROCESS_ISOSURFACE; + +/** + * Command to create a mesh from the active Volume using VolumeUtils/VolumeExtensions + * @param a RealType + * + * @author You-Name-Here + */ +@Plugin(type = Command.class, menuRoot = "SciView", // + menu = {@Menu(label = "Process", weight = PROCESS), // + @Menu(label = "Volume Marching Cubes", weight = PROCESS_ISOSURFACE)}) +public class VolumeMarchingCubes implements Command { + + @Parameter + private OpService ops; + + @Parameter + private SciView sciView; + + @Parameter + private UIService ui; + + @Parameter + private LogService log; + + @Parameter(label = "Threshold", min = "1", max = "255", stepSize = "1") + private int threshold = 1; + + @Override + public void run() { + Node active = sciView.getActiveNode(); + + if (!(active instanceof Volume)) { + ui.showDialog("The active node needs to be a volume.", DialogPrompt.MessageType.ERROR_MESSAGE); + return; + } + + Volume volume = (Volume) active; + + // Use the new extension methods to get the current view of the volume + IntervalView view = VolumeExtensionsKt.getCurrentView(volume); + + if (view == null) { + ui.showDialog("Could not access volume data. Make sure this is a valid volume.", + DialogPrompt.MessageType.ERROR_MESSAGE); + return; + } + + log.info("Creating mesh from volume at timepoint: " + volume.getCurrentTimepoint()); + + // Use the threshold value to create a binary image + Img bitImg = (Img) ops.threshold().apply(view, new UnsignedByteType(threshold)); + + // Apply marching cubes algorithm + net.imagej.mesh.Mesh mesh = (net.imagej.mesh.Mesh) ops.geom().marchingCubes( + bitImg, (double) threshold, new BitTypeVertexInterpolator()); + + // Process the mesh + net.imagej.mesh.Mesh meshes = RemoveDuplicateVertices.calculate(mesh, 0); + + // Create a group to hold the mesh components + Group group = new Group(); + group.setName("Mesh from volume at timepoint: " + volume.getCurrentTimepoint()); + + // Add each connected component as a separate mesh + for (net.imagej.mesh.Mesh m : MeshConnectedComponents.iterable(meshes)) { + Mesh ready = MeshConverter.toScenery(m); + ready.material().setWireframe(true); + ready.material().setDiffuse(new Vector3f(1f, 0.7f, 0.5f)); + group.addChild(ready); + } + + // Add the group to the scene + sciView.addNode(group, volume); + } +} From e4de8e84839cdf24bb7cc9992be687568f045261 Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Sun, 6 Apr 2025 08:26:06 -0400 Subject: [PATCH 4/4] Add documentation for Volume data access utilities --- src/main/kotlin/sc/iview/process/README.md | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/main/kotlin/sc/iview/process/README.md diff --git a/src/main/kotlin/sc/iview/process/README.md b/src/main/kotlin/sc/iview/process/README.md new file mode 100644 index 00000000..e1b4d3e8 --- /dev/null +++ b/src/main/kotlin/sc/iview/process/README.md @@ -0,0 +1,41 @@ +# SciView Volume Data Access Utilities + +This module provides utilities for accessing the original data stored in Volume objects. + +## VolumeUtils + +The `VolumeUtils` class provides static methods for accessing volume data: + +- `getOriginalRandomAccessibleInterval(volume)`: Returns the original RandomAccessibleInterval data stored in a Volume. +- `getCurrentView(volume)`: Returns an IntervalView representing the current timepoint of a volume. + +## Volume Extensions + +For more convenient usage, extension methods are provided for the Volume class: + +- `volume.getOriginalRandomAccessibleInterval()`: Extension method to get the original data. +- `volume.getCurrentView()`: Extension method to get the current view based on the timepoint. + +## Usage Example + +Here's a simple example of using these utilities to apply a marching cubes algorithm to a Volume: + +```kotlin +// Get the current Volume +val volume = sciView.activeNode as Volume + +// Get the current view based on the timepoint +val view = volume.getCurrentView() + +// Apply your processing +val bitImg = ops.threshold().apply(view, UnsignedByteType(1)) as Img +val mesh = ops.geom().marchingCubes(bitImg, 1.0, BitTypeVertexInterpolator()) + +// Convert to scenery mesh +val sceneryMesh = MeshConverter.toScenery(mesh) + +// Add to scene +sciView.addNode(sceneryMesh) +``` + +See the `VolumeMarchingCubes` command for a complete example.