From 6188279dd81305d42f737ff651617d8caecda0b7 Mon Sep 17 00:00:00 2001 From: Ulrik Guenther Date: Thu, 6 Jun 2024 15:53:26 +0200 Subject: [PATCH 01/11] OpenN5: Refactor (WIP) --- build.gradle.kts | 8 +- .../java/sc/iview/commands/file/OpenN5.kt | 131 ++++++++++++------ 2 files changed, 96 insertions(+), 43 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 55fbfae9..8edd3ee8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ +import groovy.xml.XmlSlurper import org.gradle.kotlin.dsl.implementation import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.net.URL @@ -60,6 +61,9 @@ dependencies { implementation("net.java.dev.jna:jna-platform:5.11.0") implementation("org.janelia.saalfeldlab:n5") implementation("org.janelia.saalfeldlab:n5-imglib2") + implementation("org.janelia.saalfeldlab:n5-blosc") + implementation("org.janelia.saalfeldlab:n5-universe:1.3.2") + implementation("org.janelia.saalfeldlab:n5-viewer_fiji:6.0.1") implementation("org.apache.logging.log4j:log4j-api:2.20.0") implementation("org.apache.logging.log4j:log4j-1.2-api:2.20.0") @@ -67,9 +71,7 @@ dependencies { // SciJava dependencies - implementation("org.yaml:snakeyaml") { - version { strictly("1.33") } - } + implementation("org.yaml:snakeyaml") implementation("org.scijava:scijava-common") implementation("org.scijava:ui-behaviour") implementation("org.scijava:script-editor") diff --git a/src/main/java/sc/iview/commands/file/OpenN5.kt b/src/main/java/sc/iview/commands/file/OpenN5.kt index 42d3a220..9420f003 100644 --- a/src/main/java/sc/iview/commands/file/OpenN5.kt +++ b/src/main/java/sc/iview/commands/file/OpenN5.kt @@ -28,7 +28,14 @@ */ package sc.iview.commands.file +import bdv.cache.SharedQueue +import bdv.tools.brightness.ConverterSetup +import bdv.util.BdvOptions import bdv.util.volatiles.VolatileViews +import bdv.viewer.SourceAndConverter +import net.imagej.Dataset +import net.imglib2.realtransform.AffineTransform3D +import net.imglib2.type.numeric.RealType import net.imglib2.type.numeric.integer.* import net.imglib2.type.numeric.real.DoubleType import net.imglib2.type.numeric.real.FloatType @@ -36,7 +43,12 @@ import org.janelia.saalfeldlab.n5.DataType import org.janelia.saalfeldlab.n5.N5FSReader import org.janelia.saalfeldlab.n5.N5Reader import org.janelia.saalfeldlab.n5.N5URI +import org.janelia.saalfeldlab.n5.bdv.N5Viewer import org.janelia.saalfeldlab.n5.imglib2.N5Utils +import org.janelia.saalfeldlab.n5.universe.N5Factory +import org.janelia.saalfeldlab.n5.universe.N5MetadataUtils +import org.janelia.saalfeldlab.n5.universe.metadata.N5Metadata +import org.scijava.ItemVisibility import org.scijava.command.DynamicCommand import org.scijava.log.LogService import org.scijava.plugin.Menu @@ -44,12 +56,16 @@ import org.scijava.plugin.Parameter import org.scijava.plugin.Plugin import org.scijava.widget.ChoiceWidget import org.scijava.widget.NumberWidget -import org.scijava.widget.TextWidget import sc.iview.SciView import sc.iview.commands.MenuWeights.FILE import sc.iview.commands.MenuWeights.FILE_OPEN +import ucar.units.StandardUnitFormatConstants.T import java.io.File import java.io.IOException +import java.util.* +import kotlin.collections.ArrayList +import kotlin.math.max + /** * Command to open a file in SciView @@ -70,35 +86,47 @@ class OpenN5 : DynamicCommand() { private lateinit var sciView: SciView // TODO: Find a more extensible way than hard-coding the extensions. - @Parameter(style = "directory", callback = "refreshDatasets", required = true, persist = false) + @Parameter(style = "directory", callback = "refreshDatasets", required = true, persist = true) private lateinit var file: File - @Parameter(required = true, style = ChoiceWidget.LIST_BOX_STYLE, callback = "refreshVoxelSize", persist = false) + @Parameter(required = true, style = ChoiceWidget.LIST_BOX_STYLE, choices = ["(none)"], callback = "refreshVoxelSize", persist = false) private lateinit var dataset: String private lateinit var reader: N5Reader - @Parameter(style = NumberWidget.SPINNER_STYLE + ",format:0.000", stepSize = "0.1") + @Parameter(style = NumberWidget.SPINNER_STYLE + ",format:0.000", stepSize = "0.1", persist = false) private var voxelSizeX = 1.0f - @Parameter(style = NumberWidget.SPINNER_STYLE + ",format:0.000", stepSize = "0.1") + @Parameter(style = NumberWidget.SPINNER_STYLE + ",format:0.000", stepSize = "0.1", persist = false) private var voxelSizeY = 1.0f - @Parameter(style = NumberWidget.SPINNER_STYLE + ",format:0.000", stepSize = "0.1") + @Parameter(style = NumberWidget.SPINNER_STYLE + ",format:0.000", stepSize = "0.1", persist = false) private var voxelSizeZ = 1.0f - @Parameter(style = NumberWidget.SPINNER_STYLE + ",format:0.000", stepSize = "0.1") + @Parameter(style = NumberWidget.SPINNER_STYLE + ",format:0.000", stepSize = "0.1", persist = false) private var unitScaling = 1.0f - @Parameter(style = TextWidget.AREA_STYLE) + @Parameter(visibility = ItemVisibility.MESSAGE, persist = false) private var unitMessage = "" + private var multiscaleDatasets = mutableListOf() + @Suppress("unused") private fun refreshDatasets() { reader = N5FSReader(file.absolutePath) val includedDatasets = reader.deepListDatasets("/") - info.getMutableInput("dataset", String::class.java).choices = includedDatasets.toMutableList() - dataset = includedDatasets.first() + + val isMultiscale = includedDatasets.map { it.split("/").last() }.all { it.startsWith("s") } + if(isMultiscale) { + log.info("Discovered dataset: ${includedDatasets.first()} (multiscale)") + multiscaleDatasets = includedDatasets.toMutableList() + info.getMutableInput("dataset", String::class.java).choices = listOf(includedDatasets.first().split("/").first()) + dataset = includedDatasets.first().split("/").first() + } else { + log.info("Discovered dataset: ${includedDatasets.joinToString(", ")}") + info.getMutableInput("dataset", String::class.java).choices = includedDatasets.toMutableList() + dataset = includedDatasets.first() + } refreshVoxelSize() } @@ -106,43 +134,66 @@ class OpenN5 : DynamicCommand() { private fun refreshVoxelSize() { log.info("dataset is $dataset") reader = N5FSReader(file.absolutePath) - val resolution = reader.getAttribute("volume", "resolution", FloatArray::class.java) ?: return - voxelSizeX = resolution[0] - voxelSizeY = resolution[1] - voxelSizeZ = resolution[2] - - val units = reader.getAttribute("volume", "units", Array::class.java) ?: return - - unitScaling = when(units.first()) { - "nm" -> 0.001f - "µm" -> 1.0f - "mm" -> 1000.0f - else -> 1.0f + val resolution = reader.getAttribute(dataset, "resolution", FloatArray::class.java) + if(resolution != null) { + voxelSizeX = resolution[0] + voxelSizeY = resolution[1] + voxelSizeZ = resolution[2] + } + + val units = reader.getAttribute(dataset, "units", Array::class.java) + + if(units != null) { + unitScaling = when(units.first()) { + "nm" -> 0.001f + "µm" -> 1.0f + "mm" -> 1000.0f + else -> 1.0f + } } - unitMessage = "Individual voxels will appear in the scene as ${voxelSizeX*unitScaling} m (world units) in size." + unitMessage = if(units == null || resolution == null) { + "Dataset is missing resolution or unit information.\nOne voxel will occupy ${voxelSizeX*unitScaling}m in world space." + } else { + "Individual voxels will appear in the scene as ${voxelSizeX * unitScaling} m (world units) in size." + } } + + override fun run() { try { - val attributes = reader.getDatasetAttributes(dataset) - val img = when(attributes.dataType) { - DataType.UINT8 -> N5Utils.openVolatile(reader, dataset) - DataType.UINT16 -> N5Utils.openVolatile(reader, dataset) - DataType.UINT32 -> N5Utils.openVolatile(reader, dataset) - DataType.UINT64 -> N5Utils.openVolatile(reader, dataset) - DataType.INT8 -> N5Utils.openVolatile(reader, dataset) - DataType.INT16 -> N5Utils.openVolatile(reader, dataset) - DataType.INT32 -> N5Utils.openVolatile(reader, dataset) - DataType.INT64 -> N5Utils.openVolatile(reader, dataset) - DataType.FLOAT32 -> N5Utils.openVolatile(reader, dataset) - DataType.FLOAT64 -> N5Utils.openVolatile(reader, dataset) - DataType.OBJECT -> TODO() - null -> TODO() + if(multiscaleDatasets.size == 0) { + val attributes = reader.getDatasetAttributes(dataset) + val img = when(attributes.dataType) { + DataType.UINT8 -> N5Utils.openVolatile(reader, dataset) + DataType.UINT16 -> N5Utils.openVolatile(reader, dataset) + DataType.UINT32 -> N5Utils.openVolatile(reader, dataset) + DataType.UINT64 -> N5Utils.openVolatile(reader, dataset) + DataType.INT8 -> N5Utils.openVolatile(reader, dataset) + DataType.INT16 -> N5Utils.openVolatile(reader, dataset) + DataType.INT32 -> N5Utils.openVolatile(reader, dataset) + DataType.INT64 -> N5Utils.openVolatile(reader, dataset) + DataType.FLOAT32 -> N5Utils.openVolatile(reader, dataset) + DataType.FLOAT64 -> N5Utils.openVolatile(reader, dataset) + DataType.OBJECT -> TODO() + null -> TODO() + DataType.STRING -> TODO() + } + + val wrapped = VolatileViews.wrapAsVolatile(img) + sciView.addVolume( + wrapped, + dataset, + voxelDimensions = floatArrayOf( + voxelSizeX * unitScaling * 1000.0f, + voxelSizeY * unitScaling * 1000.0f, + voxelSizeZ * unitScaling * 1000.0f + ) + ) + } else { + N5Opener.openN5(sciView, file.absolutePath) } - - val wrapped = VolatileViews.wrapAsVolatile(img) - sciView.addVolume(wrapped, dataset, voxelDimensions = floatArrayOf(voxelSizeX*unitScaling*1000.0f, voxelSizeY*unitScaling*1000.0f, voxelSizeZ*unitScaling*1000.0f)) } catch(exc: IOException) { log.error(exc) } catch(exc: IllegalArgumentException) { From 7a24fc12ffb6911a3dd51c39545964099d9e0786 Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 20 Jun 2024 15:23:51 -0400 Subject: [PATCH 02/11] [wip] mandelbulb --- build.gradle.kts | 5 + .../MandelbulbCacheArrayLoader.java | 103 +++++++++++ .../iview/mandelbulb/MandelbulbImgLoader.java | 147 +++++++++++++++ .../mandelbulb/MultiResolutionMandelbulb.java | 173 ++++++++++++++++++ src/main/kotlin/sc/iview/SciView.kt | 67 ++++++- 5 files changed, 487 insertions(+), 8 deletions(-) create mode 100644 src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java create mode 100644 src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java create mode 100644 src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java diff --git a/build.gradle.kts b/build.gradle.kts index 8edd3ee8..de016a71 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -123,6 +123,11 @@ dependencies { implementation("sc.fiji:spim_data") implementation("org.slf4j:slf4j-simple") + implementation("software.amazon.awssdk:s3:2.20.1") + implementation("org.apache.commons:commons-compress:1.21") + // implementation("com.scalableminds:blosc-java:0.1-1.21.4") + implementation("org.lasersonlab:jblosc:1.0.1") + implementation(platform(kotlin("bom"))) implementation(kotlin("stdlib-jdk8")) testImplementation(kotlin("test-junit")) diff --git a/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java b/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java new file mode 100644 index 00000000..b480dfca --- /dev/null +++ b/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java @@ -0,0 +1,103 @@ +package sc.iview.mandelbulb; + +import bdv.img.cache.CacheArrayLoader; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray; +import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.view.Views; + +public class MandelbulbCacheArrayLoader implements CacheArrayLoader +{ + private final int maxIter; + private final int order; + + // Static variables for grid sizes, base grid size, and desired finest grid size + public static int[] gridSizes; + public static int baseGridSize; + public static int desiredFinestGridSize; + + public MandelbulbCacheArrayLoader(int maxIter, int order) + { + this.maxIter = maxIter; + this.order = order; + } + + @Override + public VolatileShortArray loadArray(final int timepoint, final int setup, final int level, final int[] cellDims, final long[] cellMin) throws InterruptedException + { + // Generate Mandelbulb for the specific cell region + final RandomAccessibleInterval img = generateMandelbulbForCell(cellDims, cellMin, level, maxIter, order); + + // Create a VolatileShortArray to hold the generated data + final VolatileShortArray shortArray = new VolatileShortArray(cellDims[0] * cellDims[1] * cellDims[2], true); + + // Extract the data into the short array + final short[] data = shortArray.getCurrentStorageArray(); + Views.flatIterable(img).forEach(pixel -> data[(int) pixel.index().get()] = (short) pixel.get()); + + return shortArray; + } + + @Override + public int getBytesPerElement() + { + return 2; // Each element is 2 bytes (16 bits) + } + + public static RandomAccessibleInterval generateMandelbulbForCell(int[] cellDims, long[] cellMin, int level, int maxIter, int order) + { + final RandomAccessibleInterval img = ArrayImgs.unsignedShorts(new long[]{cellDims[0], cellDims[1], cellDims[2]}); + + // Calculate the scaling factor based on the desired finest grid size + double scale = (double) desiredFinestGridSize / gridSizes[level]; + + // Calculate center offset for normalization + double centerOffset = desiredFinestGridSize / 2.0; + + for (long z = 0; z < cellDims[2]; z++) + { + for (long y = 0; y < cellDims[1]; y++) + { + for (long x = 0; x < cellDims[0]; x++) + { + // Normalize and center coordinates to range from -1 to 1 + double[] coordinates = new double[]{ + ((x + cellMin[0]) * scale - centerOffset) / centerOffset, + ((y + cellMin[1]) * scale - centerOffset) / centerOffset, + ((z + cellMin[2]) * scale - centerOffset) / centerOffset + }; + int iterations = mandelbulbIter(coordinates, maxIter, order); + img.getAt(x, y, z).set((int) (iterations * 65535.0 / maxIter)); // Scale to 16-bit range + } + } + } + return img; + } + + private static int mandelbulbIter(double[] coord, int maxIter, int order) + { + double x = coord[0]; + double y = coord[1]; + double z = coord[2]; + double xn = 0, yn = 0, zn = 0; + int iter = 0; + while (iter < maxIter && xn * xn + yn * yn + zn * zn < 4) + { + double r = Math.sqrt(xn * xn + yn * yn + zn * zn); + double theta = Math.atan2(Math.sqrt(xn * xn + yn * yn), zn); + double phi = Math.atan2(yn, xn); + + double newR = Math.pow(r, order); + double newTheta = theta * order; + double newPhi = phi * order; + + xn = newR * Math.sin(newTheta) * Math.cos(newPhi) + x; + yn = newR * Math.sin(newTheta) * Math.sin(newPhi) + y; + zn = newR * Math.cos(newTheta) + z; + + iter++; + } + return iter; + } +} diff --git a/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java b/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java new file mode 100644 index 00000000..861cee44 --- /dev/null +++ b/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java @@ -0,0 +1,147 @@ +package sc.iview.mandelbulb; + +import bdv.AbstractViewerSetupImgLoader; +import bdv.ViewerImgLoader; +import bdv.cache.CacheControl; +import bdv.img.cache.CacheArrayLoader; +import bdv.img.cache.VolatileCachedCellImg; +import bdv.img.cache.VolatileGlobalCellCache; +import bdv.img.hdf5.MipmapInfo; +import bdv.img.hdf5.ViewLevelId; +import mpicbg.spim.data.generic.sequence.ImgLoaderHint; +import net.imglib2.Dimensions; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.cache.volatiles.CacheHints; +import net.imglib2.cache.volatiles.LoadingStrategy; +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray; +import net.imglib2.img.cell.CellGrid; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.type.volatiles.VolatileUnsignedShortType; +import net.imglib2.util.Intervals; + +import java.util.HashMap; + +public class MandelbulbImgLoader implements ViewerImgLoader +{ + private final int[] gridSizes; + private final int maxIter; + private final int order; + + private MipmapInfo mipmapInfo; + private long[][] mipmapDimensions; + private VolatileGlobalCellCache cache; + private CacheArrayLoader loader; + private final HashMap setupImgLoaders; + + public MandelbulbImgLoader(int[] gridSizes, int maxIter, int order) + { + this.gridSizes = gridSizes; + this.maxIter = maxIter; + this.order = order; + this.setupImgLoaders = new HashMap<>(); + initialize(); + } + + private void initialize() + { + // Set up mipmap dimensions and info + mipmapDimensions = new long[gridSizes.length][]; + final double[][] resolutions = new double[gridSizes.length][]; + final int[][] subdivisions = new int[gridSizes.length][]; + final AffineTransform3D[] transforms = new AffineTransform3D[gridSizes.length]; + + for (int level = 0; level < gridSizes.length; level++) + { + int gridSize = gridSizes[level]; + mipmapDimensions[level] = new long[]{gridSize, gridSize, gridSize}; + resolutions[level] = new double[]{1.0 / (1 << level), 1.0 / (1 << level), 1.0 / (1 << level)}; + subdivisions[level] = new int[]{16, 16, 16}; // arbitrary cell size + transforms[level] = new AffineTransform3D(); + transforms[level].scale(resolutions[level][0], resolutions[level][1], resolutions[level][2]); + } + + mipmapInfo = new MipmapInfo(resolutions, transforms, subdivisions); + loader = new MandelbulbCacheArrayLoader(maxIter, order); + cache = new VolatileGlobalCellCache(gridSizes.length, 1); + + for (int setupId = 0; setupId < 1; setupId++) + { + setupImgLoaders.put(setupId, new SetupImgLoader(setupId)); + } + } + + protected > VolatileCachedCellImg prepareCachedImage(final ViewLevelId id, final LoadingStrategy loadingStrategy, final T type) + { + final int level = id.getLevel(); + final long[] dimensions = mipmapDimensions[level]; + final int[] cellDimensions = mipmapInfo.getSubdivisions()[level]; + final CellGrid grid = new CellGrid(dimensions, cellDimensions); + + final int priority = mipmapInfo.getMaxLevel() - level; + final CacheHints cacheHints = new CacheHints(loadingStrategy, priority, false); + return cache.createImg(grid, id.getTimePointId(), id.getViewSetupId(), level, cacheHints, loader, type); + } + + @Override + public CacheControl getCacheControl() + { + return cache; + } + + @Override + public SetupImgLoader getSetupImgLoader(final int setupId) + { + return setupImgLoaders.get(setupId); + } + + public class SetupImgLoader extends AbstractViewerSetupImgLoader + { + private final int setupId; + + protected SetupImgLoader(final int setupId) + { + super(new UnsignedShortType(), new VolatileUnsignedShortType()); + this.setupId = setupId; + } + + @Override + public RandomAccessibleInterval getImage(final int timepointId, final int level, final ImgLoaderHint... hints) + { + final ViewLevelId id = new ViewLevelId(timepointId, setupId, level); + return prepareCachedImage(id, LoadingStrategy.BLOCKING, new UnsignedShortType()); + } + + @Override + public RandomAccessibleInterval getVolatileImage(final int timepointId, final int level, final ImgLoaderHint... hints) + { + final ViewLevelId id = new ViewLevelId(timepointId, setupId, level); + return prepareCachedImage(id, LoadingStrategy.BUDGETED, new VolatileUnsignedShortType()); + } + + @Override + public double[][] getMipmapResolutions() + { + return mipmapInfo.getResolutions(); + } + + @Override + public AffineTransform3D[] getMipmapTransforms() + { + return mipmapInfo.getTransforms(); + } + + @Override + public int numMipmapLevels() + { + return mipmapInfo.getNumLevels(); + } + } + + public Dimensions dimensions() + { + // Return dimensions of the highest resolution + return Intervals.createMinSize(mipmapDimensions[0][0], mipmapDimensions[0][1], mipmapDimensions[0][2]); + } +} diff --git a/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java b/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java new file mode 100644 index 00000000..348d3e0f --- /dev/null +++ b/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java @@ -0,0 +1,173 @@ +package sc.iview.mandelbulb; + +import bdv.BigDataViewer; +import bdv.spimdata.SequenceDescriptionMinimal; +import bdv.spimdata.SpimDataMinimal; +import bdv.spimdata.WrapBasicImgLoader; +import bdv.tools.brightness.ConverterSetup; +import bdv.viewer.SourceAndConverter; +import bvv.core.VolumeViewerPanel; +import bvv.vistools.BvvFunctions; +import bvv.vistools.BvvOptions; +import bvv.vistools.BvvStackSource; +import ij.process.LUT; +import mpicbg.spim.data.generic.sequence.BasicViewSetup; +import mpicbg.spim.data.registration.ViewRegistration; +import mpicbg.spim.data.registration.ViewRegistrations; +import mpicbg.spim.data.sequence.DefaultVoxelDimensions; +import mpicbg.spim.data.sequence.TimePoint; +import mpicbg.spim.data.sequence.TimePoints; +import mpicbg.spim.data.sequence.ViewId; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.numeric.ARGBType; +import net.imglib2.type.numeric.RealType; +import org.scijava.listeners.Listeners; +import sc.iview.SciView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +public class MultiResolutionMandelbulb { + + public static void main(String[] args) throws Exception { + // Define max scale level + int maxScale = 18; // Example maxScale value + + // Desired grid size at the finest resolution level + final int desiredFinestGridSize = 2; // Define as per your requirement + + // Compute the base grid size + final int baseGridSize = desiredFinestGridSize * (int) Math.pow(2, maxScale - 1); + + // Generate resolutions and corresponding grid sizes + final double[][] resolutions = new double[maxScale][3]; + final int[] gridSizes = new int[maxScale]; + for (int i = 0; i < maxScale; i++) { + double scaleFactor = Math.pow(2, i); + resolutions[i][0] = scaleFactor; + resolutions[i][1] = scaleFactor; + resolutions[i][2] = scaleFactor; + gridSizes[i] = baseGridSize / (int) scaleFactor; + System.out.println("Grid size for " + i + " grid size: " + gridSizes[i]); + } + + MandelbulbCacheArrayLoader.gridSizes = gridSizes; + MandelbulbCacheArrayLoader.baseGridSize = baseGridSize; + MandelbulbCacheArrayLoader.desiredFinestGridSize = desiredFinestGridSize; + + // Mandelbulb parameters + final int maxIter = 255; + final int order = 8; + + // Create Mandelbulb ImgLoader + MandelbulbImgLoader imgLoader = new MandelbulbImgLoader(gridSizes, maxIter, order); + + // Create a list of TimePoints (assuming single timepoint) + final List timepoints = Collections.singletonList(new TimePoint(0)); + + // Create BasicViewSetup + final BasicViewSetup viewSetup = new BasicViewSetup(0, "setup0", imgLoader.dimensions(), new DefaultVoxelDimensions(3)); + + // Create SequenceDescriptionMinimal + final SequenceDescriptionMinimal seq = new SequenceDescriptionMinimal(new TimePoints(timepoints), Collections.singletonMap(viewSetup.getId(), viewSetup), imgLoader, null); + + // Define voxel size + final double[] voxelSize = {1.0, 1.0, 1.0}; + + // Create ViewRegistrations + final HashMap registrations = new HashMap<>(); + for (final BasicViewSetup setup : seq.getViewSetupsOrdered()) { + final int setupId = setup.getId(); + for (final TimePoint timepoint : seq.getTimePoints().getTimePointsOrdered()) { + final int timepointId = timepoint.getId(); + for (int level = 0; level < resolutions.length; level++) { + AffineTransform3D transform = new AffineTransform3D(); + transform.set( + voxelSize[0] * resolutions[level][0], 0, 0, 0, + 0, voxelSize[1] * resolutions[level][1], 0, 0, + 0, 0, voxelSize[2] * resolutions[level][2], 0); + registrations.put(new ViewId(timepointId, setupId), new ViewRegistration(timepointId, setupId, transform)); + } + } + } + + // Create SpimDataMinimal + final SpimDataMinimal spimData = new SpimDataMinimal(null, seq, new ViewRegistrations(registrations)); + + // Obtain sources and setups + List> sources = getSourceAndConverters(spimData); + ArrayList converterSetups = getConverterSetups(sources); + + // Define voxel dimensions as a float array + float[] voxelDimensions = {1.0f, 1.0f, 1.0f}; + + @SuppressWarnings("unchecked") + List>> typedSources = (List>>) (List) sources; + + + // Create and add volume to SciView + SciView sciview = SciView.create(); + sciview.addSpimVolume(typedSources, converterSetups, timepoints.size(), "Mandelbulb Volume", voxelDimensions); + } + + private static List> getSourceAndConverters(SpimDataMinimal spimData) { + WrapBasicImgLoader.wrapImgLoaderIfNecessary(spimData); + final ArrayList> sources = new ArrayList<>(); + BigDataViewer.initSetups(spimData, new ArrayList<>(), sources); + WrapBasicImgLoader.removeWrapperIfPresent(spimData); + return sources; + } + + private static ArrayList getConverterSetups(List> sources) { + ArrayList converterSetups = new ArrayList<>(); + for (SourceAndConverter source : sources) { + // Placeholder logic for converter setup; customize as needed + converterSetups.add(new ConverterSetup() { + @Override + public void setDisplayRange(double min, double max) { + // Implement this method based on actual display range logic + } + + @Override + public void setColor(ARGBType color) { + + } + + @Override + public boolean supportsColor() { + return false; + } + + @Override + public double getDisplayRangeMin() { + return 0; + } + + @Override + public double getDisplayRangeMax() { + return 0; + } + + @Override + public ARGBType getColor() { + return null; + } + + + @Override + public Listeners setupChangeListeners() { + return null; + } + + @Override + public int getSetupId() { + return 0; // Implement unique ID retrieval logic + } + + }); + } + return converterSetups; + } +} diff --git a/src/main/kotlin/sc/iview/SciView.kt b/src/main/kotlin/sc/iview/SciView.kt index 014902ac..06b0c26d 100644 --- a/src/main/kotlin/sc/iview/SciView.kt +++ b/src/main/kotlin/sc/iview/SciView.kt @@ -30,6 +30,7 @@ package sc.iview import bdv.BigDataViewer import bdv.cache.CacheControl +import bdv.spimdata.SpimDataMinimal import bdv.tools.brightness.ConverterSetup import bdv.util.AxisOrder import bdv.util.RandomAccessibleIntervalSource @@ -62,7 +63,6 @@ import graphics.scenery.volumes.Volume.Companion.fromXML import graphics.scenery.volumes.Volume.Companion.setupId import graphics.scenery.volumes.Volume.VolumeDataSource.RAISource import io.scif.SCIFIOService -import io.scif.services.DatasetIOService import net.imagej.Dataset import net.imagej.ImageJService import net.imagej.axis.CalibratedAxis @@ -80,16 +80,12 @@ import net.imglib2.img.Img import net.imglib2.img.array.ArrayImgs import net.imglib2.realtransform.AffineTransform3D import net.imglib2.type.numeric.ARGBType -import net.imglib2.type.numeric.NumericType import net.imglib2.type.numeric.RealType import net.imglib2.type.numeric.integer.UnsignedByteType import net.imglib2.view.Views -import org.janelia.saalfeldlab.n5.N5FSReader -import org.janelia.saalfeldlab.n5.imglib2.N5Utils import org.joml.Quaternionf import org.joml.Vector3f import org.scijava.Context -import org.scijava.`object`.ObjectService import org.scijava.display.Display import org.scijava.event.EventHandler import org.scijava.event.EventService @@ -97,6 +93,7 @@ import org.scijava.io.IOService import org.scijava.log.LogLevel import org.scijava.log.LogService import org.scijava.menu.MenuService +import org.scijava.`object`.ObjectService import org.scijava.plugin.Parameter import org.scijava.service.SciJavaService import org.scijava.thread.ThreadService @@ -125,13 +122,11 @@ import java.util.function.Consumer import java.util.function.Function import java.util.function.Predicate import java.util.stream.Collectors -import kotlin.collections.ArrayList -import kotlin.collections.HashMap -import kotlin.collections.LinkedHashMap import javax.swing.JOptionPane import kotlin.math.cos import kotlin.math.sin + /** * Main SciView class. * @@ -1472,6 +1467,62 @@ class SciView : SceneryBase, CalibratedRealInterval { return v } + fun > addSpimVolume( + sources: List>>, + converterSetups: ArrayList, + numTimepoints: Int, + name: String, + voxelDimensions: FloatArray + ): Volume? { + // Cast sources to match the expected type for addVolume + @Suppress("UNCHECKED_CAST") + val typedSources = sources as List> + + // Call addVolume with the casted list + return addVolume( + typedSources, + converterSetups, + numTimepoints, + name, + voxelDimensions + ) + } + + fun addSpimVolume( + spimData: SpimDataMinimal, + name: String, + voxelDimensions: FloatArray, + block: Volume.() -> Unit = {}, + colormapName: String = "Fire.lut" + ): Volume? { + // Create the volume using the companion object's fromSpimData method + val volume = Volume.fromSpimData(spimData, hub, VolumeViewerOptions()) + + // Set properties + volume.name = name + volume.metadata["VoxelDimensions"] = voxelDimensions + volume.spatial().scale = Vector3f(voxelDimensions[0], voxelDimensions[1], voxelDimensions[2]) * volume.pixelToWorldRatio + + // Configure the transfer function + val tf = volume.transferFunction + val rampMin = 0f + val rampMax = 0.1f + tf.clear() + tf.addControlPoint(0.0f, 0.0f) + tf.addControlPoint(rampMin, 0.0f) + tf.addControlPoint(1.0f, rampMax) + + // Set default colormap + volume.metadata["sciview.colormapName"] = colormapName + volume.colormap = Colormap.fromColorTable(getLUT(colormapName)) + + // Add the volume node + return addNode(volume, block = block) + } + + + + /** * Adds a SourceAndConverter to the scene. * From 100e72605ac0b60eca5f4d742bbcb70736bccb4a Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 20 Jun 2024 17:22:15 -0400 Subject: [PATCH 03/11] [wip] works up to 5 scales --- .../iview/mandelbulb/MandelbulbImgLoader.java | 58 ++++++++----------- .../mandelbulb/MultiResolutionMandelbulb.java | 38 ++++++------ src/main/kotlin/sc/iview/SciView.kt | 9 ++- 3 files changed, 48 insertions(+), 57 deletions(-) diff --git a/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java b/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java index 861cee44..4fedb92b 100644 --- a/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java +++ b/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java @@ -23,8 +23,7 @@ import java.util.HashMap; -public class MandelbulbImgLoader implements ViewerImgLoader -{ +public class MandelbulbImgLoader implements ViewerImgLoader { private final int[] gridSizes; private final int maxIter; private final int order; @@ -35,8 +34,7 @@ public class MandelbulbImgLoader implements ViewerImgLoader private CacheArrayLoader loader; private final HashMap setupImgLoaders; - public MandelbulbImgLoader(int[] gridSizes, int maxIter, int order) - { + public MandelbulbImgLoader(int[] gridSizes, int maxIter, int order) { this.gridSizes = gridSizes; this.maxIter = maxIter; this.order = order; @@ -44,20 +42,26 @@ public MandelbulbImgLoader(int[] gridSizes, int maxIter, int order) initialize(); } - private void initialize() - { + private void initialize() { // Set up mipmap dimensions and info mipmapDimensions = new long[gridSizes.length][]; final double[][] resolutions = new double[gridSizes.length][]; final int[][] subdivisions = new int[gridSizes.length][]; final AffineTransform3D[] transforms = new AffineTransform3D[gridSizes.length]; - for (int level = 0; level < gridSizes.length; level++) - { + for (int level = 0; level < gridSizes.length; level++) { int gridSize = gridSizes[level]; mipmapDimensions[level] = new long[]{gridSize, gridSize, gridSize}; - resolutions[level] = new double[]{1.0 / (1 << level), 1.0 / (1 << level), 1.0 / (1 << level)}; + + // Ensure resolution stays above a minimum value (0.5) to avoid zero scales + resolutions[level] = new double[]{ + Math.max(1.0 / (1 << level), 0.5), + Math.max(1.0 / (1 << level), 0.5), + Math.max(1.0 / (1 << level), 0.5) + }; + subdivisions[level] = new int[]{16, 16, 16}; // arbitrary cell size + transforms[level] = new AffineTransform3D(); transforms[level].scale(resolutions[level][0], resolutions[level][1], resolutions[level][2]); } @@ -66,14 +70,12 @@ private void initialize() loader = new MandelbulbCacheArrayLoader(maxIter, order); cache = new VolatileGlobalCellCache(gridSizes.length, 1); - for (int setupId = 0; setupId < 1; setupId++) - { + for (int setupId = 0; setupId < 1; setupId++) { setupImgLoaders.put(setupId, new SetupImgLoader(setupId)); } } - protected > VolatileCachedCellImg prepareCachedImage(final ViewLevelId id, final LoadingStrategy loadingStrategy, final T type) - { + protected > VolatileCachedCellImg prepareCachedImage(final ViewLevelId id, final LoadingStrategy loadingStrategy, final T type) { final int level = id.getLevel(); final long[] dimensions = mipmapDimensions[level]; final int[] cellDimensions = mipmapInfo.getSubdivisions()[level]; @@ -85,62 +87,52 @@ protected > VolatileCachedCellImg } @Override - public CacheControl getCacheControl() - { + public CacheControl getCacheControl() { return cache; } @Override - public SetupImgLoader getSetupImgLoader(final int setupId) - { + public SetupImgLoader getSetupImgLoader(final int setupId) { return setupImgLoaders.get(setupId); } - public class SetupImgLoader extends AbstractViewerSetupImgLoader - { + public class SetupImgLoader extends AbstractViewerSetupImgLoader { private final int setupId; - protected SetupImgLoader(final int setupId) - { + protected SetupImgLoader(final int setupId) { super(new UnsignedShortType(), new VolatileUnsignedShortType()); this.setupId = setupId; } @Override - public RandomAccessibleInterval getImage(final int timepointId, final int level, final ImgLoaderHint... hints) - { + public RandomAccessibleInterval getImage(final int timepointId, final int level, final ImgLoaderHint... hints) { final ViewLevelId id = new ViewLevelId(timepointId, setupId, level); return prepareCachedImage(id, LoadingStrategy.BLOCKING, new UnsignedShortType()); } @Override - public RandomAccessibleInterval getVolatileImage(final int timepointId, final int level, final ImgLoaderHint... hints) - { + public RandomAccessibleInterval getVolatileImage(final int timepointId, final int level, final ImgLoaderHint... hints) { final ViewLevelId id = new ViewLevelId(timepointId, setupId, level); return prepareCachedImage(id, LoadingStrategy.BUDGETED, new VolatileUnsignedShortType()); } @Override - public double[][] getMipmapResolutions() - { + public double[][] getMipmapResolutions() { return mipmapInfo.getResolutions(); } @Override - public AffineTransform3D[] getMipmapTransforms() - { + public AffineTransform3D[] getMipmapTransforms() { return mipmapInfo.getTransforms(); } @Override - public int numMipmapLevels() - { + public int numMipmapLevels() { return mipmapInfo.getNumLevels(); } } - public Dimensions dimensions() - { + public Dimensions dimensions() { // Return dimensions of the highest resolution return Intervals.createMinSize(mipmapDimensions[0][0], mipmapDimensions[0][1], mipmapDimensions[0][2]); } diff --git a/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java b/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java index 348d3e0f..ceaaa369 100644 --- a/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java +++ b/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java @@ -6,11 +6,6 @@ import bdv.spimdata.WrapBasicImgLoader; import bdv.tools.brightness.ConverterSetup; import bdv.viewer.SourceAndConverter; -import bvv.core.VolumeViewerPanel; -import bvv.vistools.BvvFunctions; -import bvv.vistools.BvvOptions; -import bvv.vistools.BvvStackSource; -import ij.process.LUT; import mpicbg.spim.data.generic.sequence.BasicViewSetup; import mpicbg.spim.data.registration.ViewRegistration; import mpicbg.spim.data.registration.ViewRegistrations; @@ -33,10 +28,10 @@ public class MultiResolutionMandelbulb { public static void main(String[] args) throws Exception { // Define max scale level - int maxScale = 18; // Example maxScale value + int maxScale = 4; // Adjust this value to test rendering at different scales // Desired grid size at the finest resolution level - final int desiredFinestGridSize = 2; // Define as per your requirement + final int desiredFinestGridSize = 8; // Define as per your requirement // Compute the base grid size final int baseGridSize = desiredFinestGridSize * (int) Math.pow(2, maxScale - 1); @@ -44,13 +39,19 @@ public static void main(String[] args) throws Exception { // Generate resolutions and corresponding grid sizes final double[][] resolutions = new double[maxScale][3]; final int[] gridSizes = new int[maxScale]; + for (int i = 0; i < maxScale; i++) { double scaleFactor = Math.pow(2, i); - resolutions[i][0] = scaleFactor; - resolutions[i][1] = scaleFactor; - resolutions[i][2] = scaleFactor; + + // Ensure resolution stays above a minimum value (0.5) to avoid zero scales + resolutions[i][0] = Math.max(1.0 / scaleFactor, 0.5); + resolutions[i][1] = Math.max(1.0 / scaleFactor, 0.5); + resolutions[i][2] = Math.max(1.0 / scaleFactor, 0.5); + gridSizes[i] = baseGridSize / (int) scaleFactor; - System.out.println("Grid size for " + i + " grid size: " + gridSizes[i]); + + System.out.println("Grid size for level " + i + ": " + gridSizes[i]); + System.out.println("Resolution for level " + i + ": " + resolutions[i][0] + " / " + resolutions[i][1] + " / " + resolutions[i][2]); } MandelbulbCacheArrayLoader.gridSizes = gridSizes; @@ -84,10 +85,8 @@ public static void main(String[] args) throws Exception { final int timepointId = timepoint.getId(); for (int level = 0; level < resolutions.length; level++) { AffineTransform3D transform = new AffineTransform3D(); - transform.set( - voxelSize[0] * resolutions[level][0], 0, 0, 0, - 0, voxelSize[1] * resolutions[level][1], 0, 0, - 0, 0, voxelSize[2] * resolutions[level][2], 0); + // Apply level-specific resolutions + transform.scale(1 / resolutions[level][0], 1 / resolutions[level][1], 1 / resolutions[level][2]); registrations.put(new ViewId(timepointId, setupId), new ViewRegistration(timepointId, setupId, transform)); } } @@ -101,15 +100,14 @@ public static void main(String[] args) throws Exception { ArrayList converterSetups = getConverterSetups(sources); // Define voxel dimensions as a float array - float[] voxelDimensions = {1.0f, 1.0f, 1.0f}; + float[] voxelDimensions = {10000.0f, 10000.0f, 10000.0f}; @SuppressWarnings("unchecked") List>> typedSources = (List>>) (List) sources; - // Create and add volume to SciView SciView sciview = SciView.create(); - sciview.addSpimVolume(typedSources, converterSetups, timepoints.size(), "Mandelbulb Volume", voxelDimensions); + sciview.addSpimVolume(spimData, "test", voxelDimensions); } private static List> getSourceAndConverters(SpimDataMinimal spimData) { @@ -132,7 +130,6 @@ public void setDisplayRange(double min, double max) { @Override public void setColor(ARGBType color) { - } @Override @@ -155,7 +152,6 @@ public ARGBType getColor() { return null; } - @Override public Listeners setupChangeListeners() { return null; @@ -165,9 +161,9 @@ public Listeners setupChangeListeners() { public int getSetupId() { return 0; // Implement unique ID retrieval logic } - }); } return converterSetups; } + } diff --git a/src/main/kotlin/sc/iview/SciView.kt b/src/main/kotlin/sc/iview/SciView.kt index 06b0c26d..d04e1a2a 100644 --- a/src/main/kotlin/sc/iview/SciView.kt +++ b/src/main/kotlin/sc/iview/SciView.kt @@ -1491,10 +1491,13 @@ class SciView : SceneryBase, CalibratedRealInterval { fun addSpimVolume( spimData: SpimDataMinimal, name: String, - voxelDimensions: FloatArray, - block: Volume.() -> Unit = {}, - colormapName: String = "Fire.lut" + voxelDimensions: FloatArray ): Volume? { + + val block: Volume.() -> Unit = {} + + val colormapName = "Fire.lut" + // Create the volume using the companion object's fromSpimData method val volume = Volume.fromSpimData(spimData, hub, VolumeViewerOptions()) From 58cbb4438470bcc966ee8b1da301cc7ba71b3839 Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Sun, 23 Jun 2024 17:27:16 -0400 Subject: [PATCH 04/11] Add single scale zarr demo for zebrahub --- .../sc/iview/zebrahub/RemoteZarrLoader.java | 175 ++++++++++++++++++ .../sc/iview/zebrahub/ZebrahubImgLoader.java | 154 +++++++++++++++ .../demo/advanced/LoadZebrahubSingleScale.kt | 101 ++++++++++ 3 files changed, 430 insertions(+) create mode 100644 src/main/java/sc/iview/zebrahub/RemoteZarrLoader.java create mode 100644 src/main/java/sc/iview/zebrahub/ZebrahubImgLoader.java create mode 100644 src/main/kotlin/sc/iview/commands/demo/advanced/LoadZebrahubSingleScale.kt diff --git a/src/main/java/sc/iview/zebrahub/RemoteZarrLoader.java b/src/main/java/sc/iview/zebrahub/RemoteZarrLoader.java new file mode 100644 index 00000000..da375f69 --- /dev/null +++ b/src/main/java/sc/iview/zebrahub/RemoteZarrLoader.java @@ -0,0 +1,175 @@ +package sc.iview.zebrahub; + +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.blosc.JBlosc; +import org.blosc.PrimitiveSizes; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Map; + +public class RemoteZarrLoader { + + private final String zarrUrl; + private final Map scaleUrls; + private final int[][] dimensions; // Spatial dimensions for each scale level + private final int[][] chunkSizes; // Chunk sizes for each scale level + + public RemoteZarrLoader(String zarrUrl) throws IOException { + this.zarrUrl = zarrUrl; + this.scaleUrls = fetchScaleUrls(); + this.dimensions = fetchDimensions(); + this.chunkSizes = fetchChunkSizes(); + } + + private Map fetchScaleUrls() throws IOException { + Map scaleUrls = new HashMap<>(); + for (int i = 0; i < 3; i++) { // Assuming 3 scales + scaleUrls.put(i, zarrUrl + "/" + i); + } + return scaleUrls; + } + + private int[][] fetchDimensions() throws IOException { + ObjectMapper mapper = new ObjectMapper(); + int[][] dims = new int[scaleUrls.size()][]; + + for (Map.Entry entry : scaleUrls.entrySet()) { + URL url = new URL(entry.getValue() + "/.zarray"); + JsonNode root = mapper.readTree(openStream(url)); + JsonNode shape = root.get("shape"); + // Extract only the spatial dimensions: z, y, x + dims[entry.getKey()] = new int[]{shape.get(2).asInt(), shape.get(3).asInt(), shape.get(4).asInt()}; + } + return dims; + } + + private int[][] fetchChunkSizes() throws IOException { + ObjectMapper mapper = new ObjectMapper(); + int[][] sizes = new int[scaleUrls.size()][]; + + for (Map.Entry entry : scaleUrls.entrySet()) { + URL url = new URL(entry.getValue() + "/.zarray"); + JsonNode root = mapper.readTree(openStream(url)); + JsonNode chunks = root.get("chunks"); + // Extract only the spatial chunk sizes: z, y, x + sizes[entry.getKey()] = new int[]{chunks.get(2).asInt(), chunks.get(3).asInt(), chunks.get(4).asInt()}; + } + return sizes; + } + + public int[][] getDimensions() { + return dimensions; + } + + public int[][] getChunkSizes() { + return chunkSizes; + } + + public VolatileShortArray loadData(int scale, int[] cellDims, long[] cellMin) throws IOException { + String scaleUrl = scaleUrls.get(scale); + if (scaleUrl == null) throw new IllegalArgumentException("Invalid scale level"); + + // Convert cell dimensions and cell minimums to 5D, with hardcoded timepoint and channel values + long[] chunkCoords = calculateChunkCoordinates(scale, cellMin); + String chunkUrl = constructChunkUrl(scaleUrl, chunkCoords); + byte[] compressedData = fetchChunkData(chunkUrl); + + // Decompress data using JBLosc + byte[] dataBytes = decompressData(compressedData, cellDims); + + int size = cellDims[0] * cellDims[1] * cellDims[2]; + int expectedSize = size * 2; // 2 bytes per short + + if (dataBytes.length != expectedSize) { + throw new IOException("Fetched data size does not match expected size. Expected: " + expectedSize + " but got: " + dataBytes.length); + } + + VolatileShortArray shortArray = new VolatileShortArray(size, true); + short[] data = shortArray.getCurrentStorageArray(); + + ByteBuffer byteBuffer = ByteBuffer.wrap(dataBytes).order(ByteOrder.LITTLE_ENDIAN); // Assuming little-endian + byteBuffer.asShortBuffer().get(data); + + return shortArray; + } + + private long[] calculateChunkCoordinates(int scale, long[] cellMin) { + int[] chunkSizes = this.chunkSizes[scale]; + long[] chunkCoords = new long[5]; + chunkCoords[0] = 0; // Assuming single timepoint + chunkCoords[1] = 0; // Assuming single channel + chunkCoords[2] = cellMin[0] / chunkSizes[0]; + chunkCoords[3] = cellMin[1] / chunkSizes[1]; + chunkCoords[4] = cellMin[2] / chunkSizes[2]; + return chunkCoords; + } + + private String constructChunkUrl(String scaleUrl, long[] chunkCoords) { + // Construct URL based on the chunk coordinates + // This logic depends on the Zarr chunking scheme and dimension separator "/" + StringBuilder url = new StringBuilder(scaleUrl); + for (long coord : chunkCoords) { + url.append("/").append(coord); + } + return url.toString(); + } + + private byte[] fetchChunkData(String chunkUrl) throws IOException { + URL url = new URL(chunkUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestProperty("Accept", "application/octet-stream"); + connection.setDoInput(true); + connection.connect(); + + try (InputStream inputStream = connection.getInputStream(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { + byte[] data = new byte[1024]; + int bytesRead; + while ((bytesRead = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, bytesRead); + } + return buffer.toByteArray(); + } finally { + connection.disconnect(); + } + } + + private byte[] decompressData(byte[] compressedData, int[] cellDims) throws IOException { + JBlosc jb = new JBlosc(); + try { + int decompressedSize = cellDims[0] * cellDims[1] * cellDims[2] * PrimitiveSizes.SHORT_FIELD_SIZE; // 2 bytes per short + ByteBuffer compressedBuffer = ByteBuffer.allocateDirect(compressedData.length); + compressedBuffer.put(compressedData); + compressedBuffer.flip(); + + ByteBuffer decompressedBuffer = ByteBuffer.allocateDirect(decompressedSize); + + jb.decompress(compressedBuffer, decompressedBuffer, decompressedSize); + jb.destroy(); + + byte[] decompressedData = new byte[decompressedSize]; + decompressedBuffer.get(decompressedData); + return decompressedData; + } catch (Exception e) { + jb.destroy(); + throw new IOException("Failed to decompress data", e); + } + } + + private InputStream openStream(URL url) throws IOException { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestProperty("Accept", "application/json"); + connection.setDoInput(true); + connection.connect(); + return connection.getInputStream(); + } +} diff --git a/src/main/java/sc/iview/zebrahub/ZebrahubImgLoader.java b/src/main/java/sc/iview/zebrahub/ZebrahubImgLoader.java new file mode 100644 index 00000000..563ec21f --- /dev/null +++ b/src/main/java/sc/iview/zebrahub/ZebrahubImgLoader.java @@ -0,0 +1,154 @@ +package sc.iview.zebrahub; + +import bdv.AbstractViewerSetupImgLoader; +import bdv.ViewerImgLoader; +import bdv.cache.CacheControl; +import bdv.img.cache.CacheArrayLoader; +import bdv.img.cache.VolatileCachedCellImg; +import bdv.img.cache.VolatileGlobalCellCache; +import bdv.img.hdf5.MipmapInfo; +import bdv.img.hdf5.ViewLevelId; +import mpicbg.spim.data.generic.sequence.ImgLoaderHint; +import net.imglib2.Dimensions; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.cache.volatiles.CacheHints; +import net.imglib2.cache.volatiles.LoadingStrategy; +import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray; +import net.imglib2.img.cell.CellGrid; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.type.volatiles.VolatileUnsignedShortType; +import net.imglib2.util.Intervals; + +import java.io.IOException; +import java.util.HashMap; + +public class ZebrahubImgLoader implements ViewerImgLoader { + private final RemoteZarrLoader zarrLoader; + public int[] gridSizes; + + public MipmapInfo mipmapInfo; + private long[][] mipmapDimensions; + private VolatileGlobalCellCache cache; + private HashMap setupImgLoaders; + + public ZebrahubImgLoader(String zarrUrl) throws IOException { + this.zarrLoader = new RemoteZarrLoader(zarrUrl); + this.setupImgLoaders = new HashMap<>(); + initialize(); + } + + private void initialize() throws IOException { + // Fetch dimensions and chunk sizes from Zarr metadata + int[][] zarrDimensions = zarrLoader.getDimensions(); + int[][] chunkSizes = zarrLoader.getChunkSizes(); + this.gridSizes = new int[zarrDimensions.length]; + + mipmapDimensions = new long[gridSizes.length][]; + final double[][] resolutions = new double[gridSizes.length][]; + final int[][] subdivisions = new int[gridSizes.length][]; + final AffineTransform3D[] transforms = new AffineTransform3D[gridSizes.length]; + + for (int level = 0; level < zarrDimensions.length; level++) { + int[] dim = zarrDimensions[level]; + int[] chunkSize = chunkSizes[level]; + mipmapDimensions[level] = new long[]{dim[0], dim[1], dim[2]}; + gridSizes[level] = Math.max(dim[0], Math.max(dim[1], dim[2])); // Use the maximum dimension for grid size + + resolutions[level] = new double[]{ + Math.max(1.0 / (1 << level), 0.5), + Math.max(1.0 / (1 << level), 0.5), + Math.max(1.0 / (1 << level), 0.5) + }; + + subdivisions[level] = new int[]{chunkSize[0], chunkSize[1], chunkSize[2]}; // Use chunk size for cell dimensions + + transforms[level] = new AffineTransform3D(); + transforms[level].scale(resolutions[level][0], resolutions[level][1], resolutions[level][2]); + } + + mipmapInfo = new MipmapInfo(resolutions, transforms, subdivisions); + cache = new VolatileGlobalCellCache(gridSizes.length, 1); + + for (int setupId = 0; setupId < 1; setupId++) { + setupImgLoaders.put(setupId, new SetupImgLoader(setupId)); + } + } + + protected > VolatileCachedCellImg prepareCachedImage(final ViewLevelId id, final LoadingStrategy loadingStrategy, final T type) { + final int level = id.getLevel(); + final long[] dimensions = mipmapDimensions[level]; + final int[] cellDimensions = mipmapInfo.getSubdivisions()[level]; + final CellGrid grid = new CellGrid(dimensions, cellDimensions); + + final int priority = mipmapInfo.getMaxLevel() - level; + final CacheHints cacheHints = new CacheHints(loadingStrategy, priority, false); + return cache.createImg(grid, id.getTimePointId(), id.getViewSetupId(), level, cacheHints, new CacheArrayLoader() { + @Override + public VolatileShortArray loadArray(int timepoint, int setup, int level, int[] cellDims, long[] cellMin) throws InterruptedException { + try { + return zarrLoader.loadData(level, cellDims, cellMin); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public int getBytesPerElement() { + return 2; // Each element is 2 bytes (16 bits) + } + }, type); + } + + @Override + public CacheControl getCacheControl() { + return cache; + } + + @Override + public SetupImgLoader getSetupImgLoader(final int setupId) { + return setupImgLoaders.get(setupId); + } + + public class SetupImgLoader extends AbstractViewerSetupImgLoader { + private final int setupId; + + protected SetupImgLoader(final int setupId) { + super(new UnsignedShortType(), new VolatileUnsignedShortType()); + this.setupId = setupId; + } + + @Override + public RandomAccessibleInterval getImage(final int timepointId, final int level, final ImgLoaderHint... hints) { + final ViewLevelId id = new ViewLevelId(timepointId, setupId, level); + return prepareCachedImage(id, LoadingStrategy.BLOCKING, new UnsignedShortType()); + } + + @Override + public RandomAccessibleInterval getVolatileImage(final int timepointId, final int level, final ImgLoaderHint... hints) { + final ViewLevelId id = new ViewLevelId(timepointId, setupId, level); + return prepareCachedImage(id, LoadingStrategy.BUDGETED, new VolatileUnsignedShortType()); + } + + @Override + public double[][] getMipmapResolutions() { + return mipmapInfo.getResolutions(); + } + + @Override + public AffineTransform3D[] getMipmapTransforms() { + return mipmapInfo.getTransforms(); + } + + @Override + public int numMipmapLevels() { + return mipmapInfo.getNumLevels(); + } + } + + public Dimensions dimensions() { + // Return dimensions of the highest resolution + return Intervals.createMinSize(mipmapDimensions[0][0], mipmapDimensions[0][1], mipmapDimensions[0][2]); + } +} diff --git a/src/main/kotlin/sc/iview/commands/demo/advanced/LoadZebrahubSingleScale.kt b/src/main/kotlin/sc/iview/commands/demo/advanced/LoadZebrahubSingleScale.kt new file mode 100644 index 00000000..6be43505 --- /dev/null +++ b/src/main/kotlin/sc/iview/commands/demo/advanced/LoadZebrahubSingleScale.kt @@ -0,0 +1,101 @@ +package sc.iview.commands.demo.advanced + +import graphics.scenery.* +import graphics.scenery.volumes.TransferFunction +import graphics.scenery.volumes.Volume +import net.imglib2.RandomAccessibleInterval +import net.imglib2.converter.Converter +import net.imglib2.realtransform.AffineTransform3D +import net.imglib2.type.numeric.integer.UnsignedShortType +import net.imglib2.util.Intervals +import net.imglib2.view.Views +import org.joml.Vector3f +import org.scijava.command.Command +import org.scijava.command.CommandService +import org.scijava.log.LogService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights +import sc.iview.zebrahub.ZebrahubImgLoader + +@Plugin(type = Command::class, + label = "Load Zebrahub Single Scale", + menuRoot = "SciView", + menu = [Menu(label = "Demo", weight = MenuWeights.DEMO), + Menu(label = "Advanced", weight = MenuWeights.DEMO_ADVANCED), + Menu(label = "Load Zebrahub Single Scale", weight = MenuWeights.DEMO_ADVANCED_FLYBRAIN)]) +class LoadZebrahubSingleScale : Command { + @Parameter + private lateinit var sciview: SciView + + @Parameter + private lateinit var log: LogService + + override fun run() { + log.info("Loading Zarr dataset...") + + // URL of the remote Zarr dataset + val zarrUrl = "https://public.czbiohub.org/royerlab/zebrahub/imaging/single-objective/ZSNS001.ome.zarr/" + + // Create Zarr ImgLoader + val imgLoader = ZebrahubImgLoader(zarrUrl) + + // Fetch a specific chunk by specifying a region + val chunkMin = longArrayOf(100, 250, 250) // Minimum coordinates of the chunk + // val chunkMax = longArrayOf(99, 99, 99) // Maximum coordinates of the chunk (example values) + val chunkMax = longArrayOf(299, 1750, 1750) // Maximum coordinates of the chunk (example values) + + // s: 448, 2174, 2423 + + // Load the dataset from the first scale and crop it to the specific chunk + val fullDataset: RandomAccessibleInterval = imgLoader.getSetupImgLoader(0) + .getImage(250, 0) // Single timepoint (0) and level (0) + + // Crop the dataset to the desired chunk + val croppedDataset = Views.interval(fullDataset, chunkMin, chunkMax) + // val croppedDataset = fullDataset + + + println("Pixel check: " + croppedDataset.getAt(50, 50, 50).get()) + + // Display cropped dataset + val dimensions = Intervals.dimensionsAsLongArray(croppedDataset) + log.info("Cropped dataset dimensions: ${dimensions.joinToString(", ")}") + + // Apply any desired transformations (e.g., scaling) + val transform = AffineTransform3D() + val scaleFactor = 40.0 + transform.scale(scaleFactor, scaleFactor, scaleFactor) + + // Create the volume node + val volume = Volume.fromRAI( + croppedDataset, + UnsignedShortType(), // Use UnsignedShortType directly + name = "Zarr Dataset (Cropped)", + hub = sciview.hub + ) + + // Apply the transfer function to the volume + volume.transferFunction = TransferFunction.ramp(0.001f, 0.4f) + volume.converterSetups.first().setDisplayRange(0.0, 65535.0) // Adjust range to 16-bit + volume.spatial().position = Vector3f(0f, 0f, 0.0f) + volume.spatial().scale = Vector3f(scaleFactor.toFloat(), scaleFactor.toFloat(), scaleFactor.toFloat()) + + // Add the volume to the scene + sciview.addNode(volume) + + log.info("Zarr dataset loaded and cropped successfully.") + } + + companion object { + @JvmStatic + fun main(args: Array) { + val sv = SciView.create() + val command = sv.scijavaContext!!.getService(CommandService::class.java) + val argmap = HashMap() + command.run(LoadZebrahubSingleScale::class.java, true, argmap) + } + } +} From ca1b64793edd79c8bbbf52ec29872460a62e961e Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Mon, 1 Jul 2024 17:29:40 -0400 Subject: [PATCH 05/11] wip: demo starting from OutOfCoreRAIExample from scenery --- .../MandelbulbCacheArrayLoader.java | 33 +++++ .../demo/advanced/LoadMandelbulbOutOfCore.kt | 125 ++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 src/main/kotlin/sc/iview/commands/demo/advanced/LoadMandelbulbOutOfCore.kt diff --git a/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java b/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java index b480dfca..3b26b491 100644 --- a/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java +++ b/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java @@ -1,9 +1,16 @@ package sc.iview.mandelbulb; import bdv.img.cache.CacheArrayLoader; +import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.Img; +import net.imglib2.img.array.ArrayImg; +import net.imglib2.img.array.ArrayImgFactory; import net.imglib2.img.array.ArrayImgs; import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray; +import net.imglib2.img.cell.CellGrid; +import net.imglib2.img.cell.CellImg; +import net.imglib2.img.cell.CellImgFactory; import net.imglib2.type.numeric.integer.UnsignedShortType; import net.imglib2.view.Views; @@ -100,4 +107,30 @@ private static int mandelbulbIter(double[] coord, int maxIter, int order) } return iter; } + + public static ArrayImg generateFullMandelbulb(int level, int maxIter, int order) + { + long[] dimensions = { (long) gridSizes[level], (long) gridSizes[level], (long) gridSizes[level] }; + + ArrayImgFactory factory = new ArrayImgFactory<>(new UnsignedShortType()); + ArrayImg img = factory.create(dimensions); + + RandomAccess imgRA = img.randomAccess(); + for (long z = 0; z < dimensions[2]; z++) { + for (long y = 0; y < dimensions[1]; y++) { + for (long x = 0; x < dimensions[0]; x++) { + double[] coordinates = new double[]{ + ((double)x / dimensions[0] * 2 - 1), + ((double)y / dimensions[1] * 2 - 1), + ((double)z / dimensions[2] * 2 - 1) + }; + int iterations = mandelbulbIter(coordinates, maxIter, order); + imgRA.setPosition(new long[]{x, y, z}); + imgRA.get().set((int) (iterations * 65535.0 / maxIter)); + } + } + } + + return img; + } } diff --git a/src/main/kotlin/sc/iview/commands/demo/advanced/LoadMandelbulbOutOfCore.kt b/src/main/kotlin/sc/iview/commands/demo/advanced/LoadMandelbulbOutOfCore.kt new file mode 100644 index 00000000..0f825870 --- /dev/null +++ b/src/main/kotlin/sc/iview/commands/demo/advanced/LoadMandelbulbOutOfCore.kt @@ -0,0 +1,125 @@ +/*- + * #%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.demo.advanced + +import bdv.util.AxisOrder +import bdv.util.volatiles.VolatileViews +import graphics.scenery.* +import graphics.scenery.volumes.* +import net.imglib2.* +import net.imglib2.img.array.ArrayImgs +import net.imglib2.type.numeric.integer.UnsignedShortType +import net.imglib2.type.volatiles.VolatileUnsignedShortType +import net.imglib2.view.Views +import org.janelia.saalfeldlab.n5.GzipCompression +import org.janelia.saalfeldlab.n5.N5FSReader +import org.janelia.saalfeldlab.n5.N5FSWriter +import org.janelia.saalfeldlab.n5.imglib2.N5Utils +import org.joml.Vector3f +import org.scijava.command.Command +import org.scijava.command.CommandService +import org.scijava.log.LogService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights +import sc.iview.mandelbulb.MandelbulbCacheArrayLoader.* +import java.nio.file.Files + +@Plugin(type = Command::class, + label = "Load Mandelbulb Out-of-Core demo", + menuRoot = "SciView", + menu = [Menu(label = "Demo", weight = MenuWeights.DEMO), + Menu(label = "Advanced", weight = MenuWeights.DEMO_ADVANCED), + Menu(label = "Load Mandelbulb Out-of-Core demo", weight = MenuWeights.DEMO_ADVANCED)]) +class LoadMandelbulbOutOfCore : Command { + @Parameter + private lateinit var sciview: SciView + + @Parameter + private lateinit var log: LogService + + override fun run() { + gridSizes = intArrayOf(64, 128, 256, 512) // Example grid sizes for different levels + baseGridSize = 64 // Example base grid size + desiredFinestGridSize = 512 // Example desired finest grid size + + val level = 2 // Example level + val maxIter = 32 // Example maximum iterations + val order = 8 // Example Mandelbulb order + + log.info("Generating mandelbulb") + val img = generateFullMandelbulb(level, maxIter, order) + log.info("Generated") + + val datasetName = "mandelbulbDataset" + val n5path = Files.createTempDirectory("scenery-mandelbulb-n5") + val n5 = N5FSWriter(n5path.toString()) + N5Utils.save(img, n5, datasetName, intArrayOf(img.dimension(0).toInt(), img.dimension(1).toInt(), img.dimension(2).toInt()), GzipCompression()) + log.info("Dataset saved as N5") + + val ooc: RandomAccessibleInterval = N5Utils.openVolatile(N5FSReader(n5path.toString()), datasetName) + val wrapped = VolatileViews.wrapAsVolatile(ooc) + + @Suppress("UNCHECKED_CAST") + val volume = Volume.fromRAI( + wrapped as RandomAccessibleInterval, + VolatileUnsignedShortType(), + AxisOrder.DEFAULT, + "Mandelbulb OOC", + sciview.hub + ) + volume.converterSetups.first().setDisplayRange(25.0, 512.0) + volume.transferFunction = TransferFunction.ramp(0.01f, 0.03f) + volume.spatial().scale = Vector3f(20.0f) + sciview.addNode(volume) + + val lights = (0 until 3).map { + PointLight(radius = 15.0f) + } + + lights.mapIndexed { i, light -> + light.spatial().position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) + light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) + light.intensity = 50.0f + sciview.addNode(light) + } + } + + companion object { + @JvmStatic + fun main(args: Array) { + val sv = SciView.create() + val command = sv.scijavaContext!!.getService(CommandService::class.java) + val argmap = HashMap() + command.run(LoadMandelbulbOutOfCore::class.java, true, argmap) + } + } +} From f81dcb095c5fd55da7e0aa83f0dec4cb08af506e Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Mon, 1 Jul 2024 18:10:49 -0400 Subject: [PATCH 06/11] wip: some resolutions are loading as expected, levels 8+ = problems --- .../java/sc/iview/commands/file/OpenN5.kt | 2 +- .../iview/mandelbulb/MandelbulbImgLoader.java | 3 + .../demo/advanced/LoadMandelbulbOutOfCore.kt | 84 ++++++++++++------- 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/src/main/java/sc/iview/commands/file/OpenN5.kt b/src/main/java/sc/iview/commands/file/OpenN5.kt index 9420f003..33cdbc5e 100644 --- a/src/main/java/sc/iview/commands/file/OpenN5.kt +++ b/src/main/java/sc/iview/commands/file/OpenN5.kt @@ -192,7 +192,7 @@ class OpenN5 : DynamicCommand() { ) ) } else { - N5Opener.openN5(sciView, file.absolutePath) + //N5Opener.openN5(sciView, file.absolutePath) } } catch(exc: IOException) { log.error(exc) diff --git a/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java b/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java index 4fedb92b..ec49951e 100644 --- a/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java +++ b/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java @@ -11,6 +11,7 @@ import mpicbg.spim.data.generic.sequence.ImgLoaderHint; import net.imglib2.Dimensions; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.cache.img.ReadOnlyCachedCellImgFactory; import net.imglib2.cache.volatiles.CacheHints; import net.imglib2.cache.volatiles.LoadingStrategy; import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray; @@ -23,6 +24,8 @@ import java.util.HashMap; +import static sc.iview.mandelbulb.MandelbulbCacheArrayLoader.baseGridSize; + public class MandelbulbImgLoader implements ViewerImgLoader { private final int[] gridSizes; private final int maxIter; diff --git a/src/main/kotlin/sc/iview/commands/demo/advanced/LoadMandelbulbOutOfCore.kt b/src/main/kotlin/sc/iview/commands/demo/advanced/LoadMandelbulbOutOfCore.kt index 0f825870..207f299d 100644 --- a/src/main/kotlin/sc/iview/commands/demo/advanced/LoadMandelbulbOutOfCore.kt +++ b/src/main/kotlin/sc/iview/commands/demo/advanced/LoadMandelbulbOutOfCore.kt @@ -28,19 +28,13 @@ */ package sc.iview.commands.demo.advanced -import bdv.util.AxisOrder import bdv.util.volatiles.VolatileViews -import graphics.scenery.* -import graphics.scenery.volumes.* -import net.imglib2.* -import net.imglib2.img.array.ArrayImgs -import net.imglib2.type.numeric.integer.UnsignedShortType +import graphics.scenery.PointLight +import graphics.scenery.volumes.TransferFunction +import graphics.scenery.volumes.Volume +import net.imglib2.RandomAccessibleInterval import net.imglib2.type.volatiles.VolatileUnsignedShortType import net.imglib2.view.Views -import org.janelia.saalfeldlab.n5.GzipCompression -import org.janelia.saalfeldlab.n5.N5FSReader -import org.janelia.saalfeldlab.n5.N5FSWriter -import org.janelia.saalfeldlab.n5.imglib2.N5Utils import org.joml.Vector3f import org.scijava.command.Command import org.scijava.command.CommandService @@ -50,8 +44,8 @@ import org.scijava.plugin.Parameter import org.scijava.plugin.Plugin import sc.iview.SciView import sc.iview.commands.MenuWeights -import sc.iview.mandelbulb.MandelbulbCacheArrayLoader.* -import java.nio.file.Files +import sc.iview.mandelbulb.MandelbulbCacheArrayLoader +import sc.iview.mandelbulb.MandelbulbImgLoader @Plugin(type = Command::class, label = "Load Mandelbulb Out-of-Core demo", @@ -67,34 +61,66 @@ class LoadMandelbulbOutOfCore : Command { private lateinit var log: LogService override fun run() { - gridSizes = intArrayOf(64, 128, 256, 512) // Example grid sizes for different levels - baseGridSize = 64 // Example base grid size - desiredFinestGridSize = 512 // Example desired finest grid size - - val level = 2 // Example level val maxIter = 32 // Example maximum iterations val order = 8 // Example Mandelbulb order + + // Define max scale level + val maxScale = 7 // Adjust this value to test rendering at different scales + + + // Desired grid size at the finest resolution level + + // Desired grid size at the finest resolution level + val desiredFinestGridSize = 8 // Define as per your requirement + + + // Compute the base grid size + + // Compute the base grid size + val baseGridSize = desiredFinestGridSize * Math.pow(2.0, (maxScale - 1).toDouble()).toInt() + + // Generate resolutions and corresponding grid sizes + + // Generate resolutions and corresponding grid sizes + val resolutions = Array(maxScale) { DoubleArray(3) } + val gridSizes = IntArray(maxScale) + + for (i in 0 until maxScale) { + val scaleFactor = Math.pow(2.0, i.toDouble()) + + // Ensure resolution stays above a minimum value (0.5) to avoid zero scales + resolutions[i][0] = 1.0 / scaleFactor + resolutions[i][1] = 1.0 / scaleFactor + resolutions[i][2] = 1.0 / scaleFactor + gridSizes[i] = baseGridSize / scaleFactor.toInt() + println("Grid size for level " + i + ": " + gridSizes[i]) + println("Resolution for level " + i + ": " + resolutions[i][0] + " / " + resolutions[i][1] + " / " + resolutions[i][2]) + } + + MandelbulbCacheArrayLoader.gridSizes = gridSizes + MandelbulbCacheArrayLoader.baseGridSize = baseGridSize + MandelbulbCacheArrayLoader.desiredFinestGridSize = desiredFinestGridSize + log.info("Generating mandelbulb") - val img = generateFullMandelbulb(level, maxIter, order) - log.info("Generated") - val datasetName = "mandelbulbDataset" - val n5path = Files.createTempDirectory("scenery-mandelbulb-n5") - val n5 = N5FSWriter(n5path.toString()) - N5Utils.save(img, n5, datasetName, intArrayOf(img.dimension(0).toInt(), img.dimension(1).toInt(), img.dimension(2).toInt()), GzipCompression()) - log.info("Dataset saved as N5") + val imgLoader = MandelbulbImgLoader(gridSizes, maxIter, order) + + // val img = imgLoader.getSetupImgLoader(0).getImage(0, maxScale - 1) + val img = imgLoader.getSetupImgLoader(0).getVolatileImage(0, 0) + + log.info("Generated") - val ooc: RandomAccessibleInterval = N5Utils.openVolatile(N5FSReader(n5path.toString()), datasetName) - val wrapped = VolatileViews.wrapAsVolatile(ooc) + // val wrapped = VolatileViews.wrapAsVolatile(img) + // val wrapped = VolatileViews.wrapAsVolatile(Views.extendZero(img)) + val wrapped = img @Suppress("UNCHECKED_CAST") val volume = Volume.fromRAI( wrapped as RandomAccessibleInterval, VolatileUnsignedShortType(), - AxisOrder.DEFAULT, - "Mandelbulb OOC", - sciview.hub + name ="Mandelbulb OOC", + hub = sciview.hub ) volume.converterSetups.first().setDisplayRange(25.0, 512.0) volume.transferFunction = TransferFunction.ramp(0.01f, 0.03f) From 2c72d3f8c0a26cd9b7bec67b8ff4cd9d9e1a9e9b Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Tue, 2 Jul 2024 12:30:13 -0400 Subject: [PATCH 07/11] wip: working toward figure --- .../demo/advanced/LoadMandelbulbOutOfCore.kt | 13 +-- .../commands/demo/advanced/LoadN5OutOfCore.kt | 89 +++++++++++++++++++ 2 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/sc/iview/commands/demo/advanced/LoadN5OutOfCore.kt diff --git a/src/main/kotlin/sc/iview/commands/demo/advanced/LoadMandelbulbOutOfCore.kt b/src/main/kotlin/sc/iview/commands/demo/advanced/LoadMandelbulbOutOfCore.kt index 207f299d..5bbd15c0 100644 --- a/src/main/kotlin/sc/iview/commands/demo/advanced/LoadMandelbulbOutOfCore.kt +++ b/src/main/kotlin/sc/iview/commands/demo/advanced/LoadMandelbulbOutOfCore.kt @@ -64,24 +64,15 @@ class LoadMandelbulbOutOfCore : Command { val maxIter = 32 // Example maximum iterations val order = 8 // Example Mandelbulb order - // Define max scale level - val maxScale = 7 // Adjust this value to test rendering at different scales - + val maxScale = 8 // Adjust this value to test rendering at different scales // Desired grid size at the finest resolution level - - // Desired grid size at the finest resolution level - val desiredFinestGridSize = 8 // Define as per your requirement - - - // Compute the base grid size + val desiredFinestGridSize = 8 // Compute the base grid size val baseGridSize = desiredFinestGridSize * Math.pow(2.0, (maxScale - 1).toDouble()).toInt() - // Generate resolutions and corresponding grid sizes - // Generate resolutions and corresponding grid sizes val resolutions = Array(maxScale) { DoubleArray(3) } val gridSizes = IntArray(maxScale) diff --git a/src/main/kotlin/sc/iview/commands/demo/advanced/LoadN5OutOfCore.kt b/src/main/kotlin/sc/iview/commands/demo/advanced/LoadN5OutOfCore.kt new file mode 100644 index 00000000..42ff567b --- /dev/null +++ b/src/main/kotlin/sc/iview/commands/demo/advanced/LoadN5OutOfCore.kt @@ -0,0 +1,89 @@ +package sc.iview.commands.demo.advanced + +import bdv.util.volatiles.VolatileViews +import graphics.scenery.BoundingGrid +import graphics.scenery.PointLight +import graphics.scenery.volumes.TransferFunction +import graphics.scenery.volumes.Volume +import net.imglib2.RandomAccessibleInterval +import net.imglib2.type.volatiles.VolatileUnsignedShortType +import net.imglib2.type.numeric.integer.UnsignedShortType +import org.janelia.saalfeldlab.n5.N5FSReader +import org.janelia.saalfeldlab.n5.imglib2.N5Utils +import org.joml.Vector3f +import org.scijava.command.Command +import org.scijava.command.CommandService +import org.scijava.log.LogService +import org.scijava.plugin.Menu +import org.scijava.plugin.Parameter +import org.scijava.plugin.Plugin +import sc.iview.SciView +import sc.iview.commands.MenuWeights + +@Plugin(type = Command::class, + label = "Load N5 Out-of-Core demo", + menuRoot = "SciView", + menu = [Menu(label = "Demo", weight = MenuWeights.DEMO), + Menu(label = "Advanced", weight = MenuWeights.DEMO_ADVANCED), + Menu(label = "Load Mandelbulb Out-of-Core demo", weight = MenuWeights.DEMO_ADVANCED)]) +class LoadN5OutOfCore : Command { + @Parameter + private lateinit var sciview: SciView + + @Parameter + private lateinit var log: LogService + + override fun run() { + // Load data from N5 + val n5path = "/Users/kharrington/Library/CloudStorage/Dropbox/sciview_data/data/test_n5_error/input-multiscale.n5" + val datasetName = "setup0/timepoint0/s0" + + log.info("Loading N5 data from $n5path with dataset $datasetName") + + val ooc: RandomAccessibleInterval = N5Utils.openVolatile(N5FSReader(n5path), datasetName) + val wrapped = VolatileViews.wrapAsVolatile(ooc) + + // When loading datasets with multiple resolution levels, it's important to use Volatile types + // here, such as VolatileUnsignedShortType, otherwise loading volume blocks will not work correctly. + @Suppress("UNCHECKED_CAST") + val volume = Volume.fromRAI( + wrapped as RandomAccessibleInterval, + VolatileUnsignedShortType(), + name = "N5 OOC", + hub = sciview.hub + ) + volume.converterSetups.first().setDisplayRange(25.0, 512.0) + volume.transferFunction = TransferFunction.ramp(0.01f, 0.03f) + volume.spatial().scale = Vector3f(20.0f) + sciview.addNode(volume) + + log.info("Volume added to the scene") + + val lights = (0 until 3).map { + PointLight(radius = 15.0f) + } + + lights.mapIndexed { i, light -> + light.spatial().position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) + light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) + light.intensity = 50.0f + sciview.addNode(light) + } + + val newBg = BoundingGrid() + newBg.node = volume + sciview.publishNode(newBg) + + log.info("Lights added to the scene") + } + + companion object { + @JvmStatic + fun main(args: Array) { + val sv = SciView.create() + val command = sv.scijavaContext!!.getService(CommandService::class.java) + val argmap = HashMap() + command.run(LoadN5OutOfCore::class.java, true, argmap) + } + } +} \ No newline at end of file From 687e7709b6af29df255cbf8a80fc0f3615534642 Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Mon, 5 Aug 2024 21:00:21 -0400 Subject: [PATCH 08/11] Tweak parameters --- .../java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java | 1 + src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java | 2 +- .../java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java b/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java index 3b26b491..895fe2d5 100644 --- a/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java +++ b/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java @@ -74,6 +74,7 @@ public static RandomAccessibleInterval generateMandelbulbForC ((y + cellMin[1]) * scale - centerOffset) / centerOffset, ((z + cellMin[2]) * scale - centerOffset) / centerOffset }; + // int iterations = (int) (( x + y + z ) % 2); int iterations = mandelbulbIter(coordinates, maxIter, order); img.getAt(x, y, z).set((int) (iterations * 65535.0 / maxIter)); // Scale to 16-bit range } diff --git a/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java b/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java index ec49951e..41226de5 100644 --- a/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java +++ b/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java @@ -63,7 +63,7 @@ private void initialize() { Math.max(1.0 / (1 << level), 0.5) }; - subdivisions[level] = new int[]{16, 16, 16}; // arbitrary cell size + subdivisions[level] = new int[]{128, 128, 128}; // arbitrary cell size transforms[level] = new AffineTransform3D(); transforms[level].scale(resolutions[level][0], resolutions[level][1], resolutions[level][2]); diff --git a/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java b/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java index 9cb48c38..a2f71ed1 100644 --- a/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java +++ b/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java @@ -28,7 +28,7 @@ public class MultiResolutionMandelbulb { public static void main(String[] args) throws Exception { // Define max scale level - int maxScale = 6; // Adjust this value to test rendering at different scales + int maxScale = 3; // Adjust this value to test rendering at different scales // Desired grid size at the finest resolution level final int desiredFinestGridSize = 8; // Define as per your requirement From 82957ce96b1bfc99a188facdfaa39a35e5a6120b Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Mon, 5 Aug 2024 21:40:38 -0400 Subject: [PATCH 09/11] Kinda making arcball better, but initial rotation is annoying --- .../AnimatedCenteringBeforeArcBallControl.kt | 144 +++++++++++++----- 1 file changed, 110 insertions(+), 34 deletions(-) diff --git a/src/main/kotlin/sc/iview/AnimatedCenteringBeforeArcBallControl.kt b/src/main/kotlin/sc/iview/AnimatedCenteringBeforeArcBallControl.kt index 423e3795..13860d29 100644 --- a/src/main/kotlin/sc/iview/AnimatedCenteringBeforeArcBallControl.kt +++ b/src/main/kotlin/sc/iview/AnimatedCenteringBeforeArcBallControl.kt @@ -6,13 +6,13 @@ * %% * 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 @@ -36,6 +36,9 @@ import graphics.scenery.utils.extensions.times import org.joml.Quaternionf import org.joml.Vector3f import java.util.function.Supplier +import org.joml.Matrix4f +import kotlin.math.max +import kotlin.math.min /* * A wrapping class for the {@ArcballCameraControl} that calls {@link CenterOnPosition()} @@ -46,9 +49,26 @@ import java.util.function.Supplier * @author Vladimir Ulman * @author Ulrik Guenther */ -class AnimatedCenteringBeforeArcBallControl(val initAction: (Int, Int) -> Any, val scrollAction: (Double, Boolean, Int, Int) -> Any, name: String, n: () -> Camera?, w: Int, h: Int, target: () -> Vector3f) : ArcballCameraControl(name, n, w, h, target) { - protected var lastX = -1 - protected var lastY = -1 + + +class AnimatedCenteringBeforeArcBallControl( + val initAction: (Int, Int) -> Any, + val scrollAction: (Double, Boolean, Int, Int) -> Any, + name: String, + n: () -> Camera?, + w: Int, + h: Int, + target: () -> Vector3f +) : ArcballCameraControl(name, n, w, h, target) { + private var lastX = -1 + private var lastY = -1 + private val rotationSpeed = 1.0f + private val dampingFactor = 0.8f + private var spherical = Spherical() + private var sphericalDelta = Spherical() + private var zoomFactor = 0.0f + private val zoomSpeed = 0.2f + private var isInitialClick = true override fun init(x: Int, y: Int) { initAction.invoke(x, y) @@ -58,6 +78,17 @@ class AnimatedCenteringBeforeArcBallControl(val initAction: (Int, Int) -> Any, v cam?.targeted = true cam?.target = target.invoke() + + if (isInitialClick) { + updateSpherical() + isInitialClick = false + } else { + // For subsequent clicks, we'll set a small delta to trigger a smooth update + sphericalDelta.theta = 0.001f + sphericalDelta.phi = 0.001f + } + + update() } override fun drag(x: Int, y: Int) { @@ -66,31 +97,16 @@ class AnimatedCenteringBeforeArcBallControl(val initAction: (Int, Int) -> Any, v return } - val xoffset: Float = (x - lastX).toFloat() * mouseSpeedMultiplier - val yoffset: Float = (lastY - y).toFloat() * mouseSpeedMultiplier + val deltaX = (x - lastX) * rotationSpeed * mouseSpeedMultiplier + val deltaY = (y - lastY) * rotationSpeed * mouseSpeedMultiplier + + sphericalDelta.theta -= (2 * Math.PI * deltaX / cam!!.width).toFloat() + sphericalDelta.phi -= (2 * Math.PI * deltaY / cam!!.height).toFloat() lastX = x lastY = y - val frameYaw = (xoffset) / 180.0f * Math.PI.toFloat() - val framePitch = yoffset / 180.0f * Math.PI.toFloat() * -1f - - // first calculate the total rotation quaternion to be applied to the camera - val yawQ = Quaternionf().rotateXYZ(0.0f, frameYaw, 0.0f).normalize() - val pitchQ = Quaternionf().rotateXYZ(framePitch, 0.0f, 0.0f).normalize() - - node.ifSpatial { - distance = (target.invoke() - position).length() - node.target = target.invoke() - val currentRotation = rotation - - // Rotate pitch first, then yaw to ensure proper axis alignment - rotation = pitchQ.mul(currentRotation).normalize() - rotation = yawQ.mul(rotation).normalize() - - // Update position based on new rotation - position = target.invoke() + node.forward * distance * (-1.0f) - } + update() node.lock.unlock() } @@ -103,15 +119,75 @@ class AnimatedCenteringBeforeArcBallControl(val initAction: (Int, Int) -> Any, v return } - val sign = if (wheelRotation.toFloat() > 0) 1 else -1 + zoomFactor -= wheelRotation.toFloat() * zoomSpeed + update() + } + + private fun updateSpherical() { + cam?.let { camera -> + val offset = camera.spatial().position - target.invoke() + spherical.setFromVector3f(offset) + } + } - distance = (target.invoke() - cam!!.spatial().position).length() - // This is the difference from scenery's scroll: we use a quadratic speed - distance += sign * wheelRotation.toFloat() * wheelRotation.toFloat() * scrollSpeedMultiplier + private fun update() { + spherical.theta += sphericalDelta.theta * dampingFactor + spherical.phi += sphericalDelta.phi * dampingFactor - if (distance >= maximumDistance) distance = maximumDistance - if (distance <= minimumDistance) distance = minimumDistance + spherical.phi = max(0.000001f, min(Math.PI.toFloat() - 0.000001f, spherical.phi)) + spherical.makeSafe() - cam?.let { node -> node.spatialOrNull()?.position = target.invoke() + node.forward * distance * (-1.0f) } + // Apply zoom + spherical.radius *= Math.pow(0.95, zoomFactor.toDouble()).toFloat() + spherical.radius = max(minimumDistance, min(maximumDistance, spherical.radius)) + + val offset = spherical.toVector3f() + val position = target.invoke() + offset + + cam?.let { camera -> + // Smooth transition for position + camera.spatial().position = camera.spatial().position.lerp(position, dampingFactor) + + // Smooth transition for rotation + val targetRotation = calculateRotation(offset) + camera.spatial().rotation = camera.spatial().rotation.slerp(targetRotation, dampingFactor) + } + + sphericalDelta.theta *= (1f - dampingFactor) + sphericalDelta.phi *= (1f - dampingFactor) + zoomFactor *= (1f - dampingFactor) + + cam?.spatialOrNull()?.needsUpdateWorld = true + } + + private fun calculateRotation(offset: Vector3f): Quaternionf { + val targetToCamera = offset.normalize() + val up = Vector3f(0f, 1f, 0f) + val right = up.cross(targetToCamera, Vector3f()) + up.set(targetToCamera).cross(right) + + val rotationMatrix = Matrix4f().setLookAt(targetToCamera, Vector3f(0f), up) + return Quaternionf().setFromNormalized(rotationMatrix) + } + + private class Spherical(var radius: Float = 1f, var phi: Float = 0f, var theta: Float = 0f) { + fun setFromVector3f(v: Vector3f) { + radius = v.length() + phi = Math.acos(v.y.toDouble() / radius).toFloat() + theta = Math.atan2(v.z.toDouble(), v.x.toDouble()).toFloat() + } + + fun makeSafe() { + theta = max(-Math.PI.toFloat(), min(Math.PI.toFloat(), theta)) + } + + fun toVector3f(): Vector3f { + val sinPhiRadius = Math.sin(phi.toDouble()) * radius + return Vector3f( + (sinPhiRadius * Math.sin(theta.toDouble())).toFloat(), + (Math.cos(phi.toDouble()) * radius).toFloat(), + (sinPhiRadius * Math.cos(theta.toDouble())).toFloat() + ) + } } -} +} \ No newline at end of file From 773f329db376cc50af5b7960f194b30685b5e127 Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Tue, 13 Aug 2024 08:54:36 -0400 Subject: [PATCH 10/11] Update to match BVV version --- .../MandelbulbCacheArrayLoader.java | 5 ++++- .../iview/mandelbulb/MandelbulbImgLoader.java | 19 ++++++---------- .../mandelbulb/MultiResolutionMandelbulb.java | 22 +++++++++++++------ 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java b/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java index 895fe2d5..e3172e2b 100644 --- a/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java +++ b/src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java @@ -62,6 +62,8 @@ public static RandomAccessibleInterval generateMandelbulbForC // Calculate center offset for normalization double centerOffset = desiredFinestGridSize / 2.0; + System.out.println("Generating cell level=" + level + " at " + cellMin[0] + ", " + cellMin[1] + ", " + cellMin[2] + " scale " + scale + " centerOffset " + centerOffset); + for (long z = 0; z < cellDims[2]; z++) { for (long y = 0; y < cellDims[1]; y++) @@ -75,7 +77,8 @@ public static RandomAccessibleInterval generateMandelbulbForC ((z + cellMin[2]) * scale - centerOffset) / centerOffset }; // int iterations = (int) (( x + y + z ) % 2); - int iterations = mandelbulbIter(coordinates, maxIter, order); + // int iterations = mandelbulbIter(coordinates, maxIter, order); + int iterations = (int) (255 * level / gridSizes.length); img.getAt(x, y, z).set((int) (iterations * 65535.0 / maxIter)); // Scale to 16-bit range } } diff --git a/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java b/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java index 41226de5..44d5be6d 100644 --- a/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java +++ b/src/main/java/sc/iview/mandelbulb/MandelbulbImgLoader.java @@ -45,26 +45,20 @@ public MandelbulbImgLoader(int[] gridSizes, int maxIter, int order) { initialize(); } - private void initialize() { + private void initialize() + { // Set up mipmap dimensions and info mipmapDimensions = new long[gridSizes.length][]; final double[][] resolutions = new double[gridSizes.length][]; final int[][] subdivisions = new int[gridSizes.length][]; final AffineTransform3D[] transforms = new AffineTransform3D[gridSizes.length]; - for (int level = 0; level < gridSizes.length; level++) { + for (int level = 0; level < gridSizes.length; level++) + { int gridSize = gridSizes[level]; mipmapDimensions[level] = new long[]{gridSize, gridSize, gridSize}; - - // Ensure resolution stays above a minimum value (0.5) to avoid zero scales - resolutions[level] = new double[]{ - Math.max(1.0 / (1 << level), 0.5), - Math.max(1.0 / (1 << level), 0.5), - Math.max(1.0 / (1 << level), 0.5) - }; - + resolutions[level] = new double[]{1.0 / (1 << level), 1.0 / (1 << level), 1.0 / (1 << level)}; subdivisions[level] = new int[]{128, 128, 128}; // arbitrary cell size - transforms[level] = new AffineTransform3D(); transforms[level].scale(resolutions[level][0], resolutions[level][1], resolutions[level][2]); } @@ -73,7 +67,8 @@ private void initialize() { loader = new MandelbulbCacheArrayLoader(maxIter, order); cache = new VolatileGlobalCellCache(gridSizes.length, 1); - for (int setupId = 0; setupId < 1; setupId++) { + for (int setupId = 0; setupId < 1; setupId++) + { setupImgLoaders.put(setupId, new SetupImgLoader(setupId)); } } diff --git a/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java b/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java index a2f71ed1..640c7bba 100644 --- a/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java +++ b/src/main/java/sc/iview/mandelbulb/MultiResolutionMandelbulb.java @@ -6,6 +6,8 @@ import bdv.spimdata.WrapBasicImgLoader; import bdv.tools.brightness.ConverterSetup; import bdv.viewer.SourceAndConverter; +import graphics.scenery.volumes.Volume; +import kotlin.Pair; import mpicbg.spim.data.generic.sequence.BasicViewSetup; import mpicbg.spim.data.registration.ViewRegistration; import mpicbg.spim.data.registration.ViewRegistrations; @@ -16,6 +18,7 @@ import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.type.numeric.ARGBType; import net.imglib2.type.numeric.RealType; +import org.joml.Vector3f; import org.scijava.listeners.Listeners; import sc.iview.SciView; @@ -28,7 +31,7 @@ public class MultiResolutionMandelbulb { public static void main(String[] args) throws Exception { // Define max scale level - int maxScale = 3; // Adjust this value to test rendering at different scales + int maxScale = 6; // Adjust this value to test rendering at different scales // Desired grid size at the finest resolution level final int desiredFinestGridSize = 8; // Define as per your requirement @@ -44,9 +47,9 @@ public static void main(String[] args) throws Exception { double scaleFactor = Math.pow(2, i); // Ensure resolution stays above a minimum value (0.5) to avoid zero scales - resolutions[i][0] = 1.0 / scaleFactor; - resolutions[i][1] = 1.0 / scaleFactor; - resolutions[i][2] = 1.0 / scaleFactor; + resolutions[i][0] = scaleFactor; + resolutions[i][1] = scaleFactor; + resolutions[i][2] = scaleFactor; gridSizes[i] = baseGridSize / (int) scaleFactor; @@ -85,8 +88,10 @@ public static void main(String[] args) throws Exception { final int timepointId = timepoint.getId(); for (int level = 0; level < resolutions.length; level++) { AffineTransform3D transform = new AffineTransform3D(); - // Apply level-specific resolutions - transform.scale(1 / resolutions[level][0], 1 / resolutions[level][1], 1 / resolutions[level][2]); + transform.set( + voxelSize[0] * resolutions[level][0], 0, 0, 0, + 0, voxelSize[1] * resolutions[level][1], 0, 0, + 0, 0, voxelSize[2] * resolutions[level][2], 0); registrations.put(new ViewId(timepointId, setupId), new ViewRegistration(timepointId, setupId, transform)); } } @@ -107,7 +112,10 @@ public static void main(String[] args) throws Exception { // Create and add volume to SciView SciView sciview = SciView.create(); - sciview.addSpimVolume(spimData, "test", voxelDimensions); + sciview.getCamera().spatial().setPosition(new Vector3f(30, 30, 30)); + Volume volume = sciview.addSpimVolume(spimData, "test", voxelDimensions); + volume.setMultiResolutionLevelLimits(new Pair<>(1,4)); + volume.spatial().setScale(new Vector3f(10, 10, 10)); } private static List> getSourceAndConverters(SpimDataMinimal spimData) { From 3103941d856c2a72540e90c69889b5e2f37942b7 Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Tue, 13 Aug 2024 10:05:54 -0400 Subject: [PATCH 11/11] Switch to jitpack BVV for singular matrix issue --- build.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 83670532..3c9310d8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -134,7 +134,8 @@ dependencies { implementation("sc.fiji:bigdataviewer-core") implementation("sc.fiji:bigdataviewer-vistools") - implementation("sc.fiji:bigvolumeviewer:0.3.3") { + //implementation("sc.fiji:bigvolumeviewer:0.3.3") { + implementation("com.github.kephale:bigvolumeviewer-core:1bc0f83") { exclude("org.jogamp.jogl","jogl-all") exclude("org.jogamp.gluegen", "gluegen-rt") }