Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generative multiresolution demo #597

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
16 changes: 12 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -60,16 +61,17 @@ dependencies {
implementation("net.java.dev.jna:jna-platform:5.14.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")

implementation("com.formdev:flatlaf:3.4.1")

// 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")
Expand Down Expand Up @@ -120,14 +122,20 @@ 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"))
testImplementation("org.slf4j:slf4j-simple")

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")
}
Expand Down
131 changes: 91 additions & 40 deletions src/main/java/sc/iview/commands/file/OpenN5.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,44 @@
*/
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
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
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
Expand All @@ -70,79 +86,114 @@ 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<String>()

@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()
}

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<String>::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<String>::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<UnsignedByteType>(reader, dataset)
DataType.UINT16 -> N5Utils.openVolatile<UnsignedShortType>(reader, dataset)
DataType.UINT32 -> N5Utils.openVolatile<UnsignedIntType>(reader, dataset)
DataType.UINT64 -> N5Utils.openVolatile<UnsignedLongType>(reader, dataset)
DataType.INT8 -> N5Utils.openVolatile<ByteType>(reader, dataset)
DataType.INT16 -> N5Utils.openVolatile<ShortType>(reader, dataset)
DataType.INT32 -> N5Utils.openVolatile<IntType>(reader, dataset)
DataType.INT64 -> N5Utils.openVolatile<LongType>(reader, dataset)
DataType.FLOAT32 -> N5Utils.openVolatile<FloatType>(reader, dataset)
DataType.FLOAT64 -> N5Utils.openVolatile<DoubleType>(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<UnsignedByteType>(reader, dataset)
DataType.UINT16 -> N5Utils.openVolatile<UnsignedShortType>(reader, dataset)
DataType.UINT32 -> N5Utils.openVolatile<UnsignedIntType>(reader, dataset)
DataType.UINT64 -> N5Utils.openVolatile<UnsignedLongType>(reader, dataset)
DataType.INT8 -> N5Utils.openVolatile<ByteType>(reader, dataset)
DataType.INT16 -> N5Utils.openVolatile<ShortType>(reader, dataset)
DataType.INT32 -> N5Utils.openVolatile<IntType>(reader, dataset)
DataType.INT64 -> N5Utils.openVolatile<LongType>(reader, dataset)
DataType.FLOAT32 -> N5Utils.openVolatile<FloatType>(reader, dataset)
DataType.FLOAT64 -> N5Utils.openVolatile<DoubleType>(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) {
Expand Down
140 changes: 140 additions & 0 deletions src/main/java/sc/iview/mandelbulb/MandelbulbCacheArrayLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
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;

public class MandelbulbCacheArrayLoader implements CacheArrayLoader<VolatileShortArray>
{
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<UnsignedShortType> 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<UnsignedShortType> generateMandelbulbForCell(int[] cellDims, long[] cellMin, int level, int maxIter, int order)
{
final RandomAccessibleInterval<UnsignedShortType> 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;

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++)
{
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 = (int) (( x + y + z ) % 2);
// 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
}
}
}
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;
}

public static ArrayImg<UnsignedShortType, ?> generateFullMandelbulb(int level, int maxIter, int order)
{
long[] dimensions = { (long) gridSizes[level], (long) gridSizes[level], (long) gridSizes[level] };

ArrayImgFactory<UnsignedShortType> factory = new ArrayImgFactory<>(new UnsignedShortType());
ArrayImg<UnsignedShortType, ?> img = factory.create(dimensions);

RandomAccess<UnsignedShortType> 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;
}
}
Loading