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

Fix volume rendering scaling #576

Merged
merged 12 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ dependencies {
exclude("org.lwjgl")
}

val sceneryVersion = "0.11.0"
val sceneryVersion = "4e8f7ed"
api("graphics.scenery:scenery:$sceneryVersion") {
version { strictly(sceneryVersion) }
exclude("org.biojava.thirdparty", "forester")
Expand Down
29 changes: 11 additions & 18 deletions src/main/kotlin/sc/iview/SciView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,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
Expand All @@ -81,12 +80,9 @@ 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
Expand Down Expand Up @@ -811,19 +807,14 @@ class SciView : SceneryBase, CalibratedRealInterval<CalibratedAxis> {
is Mesh -> addMesh(data, name = name)
is PointCloud -> addPointCloud(data, name)
is graphics.scenery.Mesh -> addMesh(data, name = name)
is Dataset -> addVolume(data, floatArrayOf(1.0f, 1.0f, 1.0f))
// is RandomAccessibleInterval<*> -> {
// val t = data.randomAccess().get()
// addVolume(data, source, floatArrayOf(1.0f, 1.0f, 1.0f))
// }
is Dataset -> addVolume(data)
is List<*> -> {
val list = data
require(!list.isEmpty()) { "Data source '$source' appears empty." }
val element = list[0]
require(data.isNotEmpty()) { "Data source '$source' appears empty." }
val element = data[0]
if(element is RealLocalizable) {
// NB: For now, we assume all elements will be RealLocalizable.
// Highly likely to be the case, barring antagonistic importers.
val points = list as List<RealLocalizable>
val points = data as List<RealLocalizable>
addPointCloud(points, name)
} else {
val type = if(element == null) "<null>" else element.javaClass.name
Expand Down Expand Up @@ -1386,17 +1377,19 @@ class SciView : SceneryBase, CalibratedRealInterval<CalibratedAxis> {
*/
fun getSciviewScale(image: Dataset): FloatArray {
val voxelDims = FloatArray(3)
var axisNames = arrayOf("X", "Y", "Z")
val axisNames = arrayOf("X", "Y", "Z")
for (axisIdx in voxelDims.indices) {
var d = (0 until image.numDimensions()).filter { idx -> image.axis(idx).type().label == axisNames[axisIdx] }.first()
val d = (0 until image.numDimensions()).filter { idx -> image.axis(idx).type().label == axisNames[axisIdx] }.first()
val inValue = image.axis(d).averageScale(0.0, 1.0)
if (image.axis(d).unit() == null || d >= axes.size) {
logger.debug("Dim for axis $d is ${inValue.toFloat()}")
voxelDims[axisIdx] = inValue.toFloat()
} else {
val imageAxisUnit = image.axis(d).unit().replace("µ", "u")
val sciviewAxisUnit = axis(axisIdx)!!.unit().replace("µ", "u")

voxelDims[axisIdx] = unitService.value(inValue, imageAxisUnit, sciviewAxisUnit).toFloat()
logger.debug("Dim for axis $d is ${voxelDims[axisIdx]}")
}
}
return voxelDims
Expand Down Expand Up @@ -1513,7 +1506,7 @@ class SciView : SceneryBase, CalibratedRealInterval<CalibratedAxis> {
image.min(minPt)
imageRA.setPosition(minPt)
val voxelType = imageRA.get()!!.createVariable() as T
println("addVolume " + image.numDimensions() + " interval " + image as Interval)
logger.info("Adding ${image.numDimensions()}-dimensional volume with type ${voxelType.javaClass.simpleName} (${image as Interval}) with voxel dimensions ${voxelDimensions.joinToString("/")}")

//int numTimepoints = 1;
if (image.numDimensions() > 3) {
Expand All @@ -1534,11 +1527,11 @@ class SciView : SceneryBase, CalibratedRealInterval<CalibratedAxis> {
val options = VolumeViewerOptions()
val v: Volume = RAIVolume(ds, options, hub)
// Note we override scenery's default scale of mm
// v.pixelToWorldRatio = 0.1f
// v.pixelToWorldRatio = 0.01f
v.name = name
v.metadata["sources"] = sources
v.metadata["VoxelDimensions"] = voxelDimensions
v.spatial().scale = Vector3f(voxelDimensions[0], voxelDimensions[1], voxelDimensions[2]) * v.pixelToWorldRatio
v.spatial().scale = Vector3f(voxelDimensions[0], voxelDimensions[1], voxelDimensions[2])
val tf = v.transferFunction
val rampMin = 0f
val rampMax = 0.1f
Expand Down
14 changes: 7 additions & 7 deletions src/main/kotlin/sc/iview/commands/add/AddVolume.kt
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,13 @@ class AddVolume : Command {

private fun <RealType> splitChannels(img: Img<RealType>, splitChannelDimension: Int): List<RandomAccessibleInterval<RealType>> {
// Ensure we're safe in the dimension we choose
var splitDim = max(min(img.numDimensions() - 1, splitChannelDimension), 0)
val splitDim = max(min(img.numDimensions() - 1, splitChannelDimension), 0)

val numChannels = img.dimension(splitDim)
val channelIntervals = mutableListOf<RandomAccessibleInterval<RealType>>()

for (channel in 0 until numChannels) {
val interval = Views.hyperSlice(img, splitDim, channel.toLong()) as RandomAccessibleInterval<RealType>
val interval = Views.hyperSlice(img, splitDim, channel) as RandomAccessibleInterval<RealType>
channelIntervals.add(interval)
}

Expand All @@ -101,13 +101,13 @@ class AddVolume : Command {
setVoxelDimension()

if (splitChannels && image.numDimensions() > 3) {
var splitDim = ((0 until image.numDimensions()).filter { d -> (image.imgPlus.axis(d) as CalibratedAxis).type().label == "Channel" }).first()
var channels = splitChannels(image, splitDim)
val splitDim = ((0 until image.numDimensions()).filter { d -> (image.imgPlus.axis(d) as CalibratedAxis).type().label == "Channel" }).first()
val channels = splitChannels(image, splitDim)

for (ch in channels.indices) {
var channel = channels[ch]
var v = sciView.addVolume(channel as RandomAccessibleInterval<out RealType<*>>, name = image.name + "-ch$ch", voxelDimensions = floatArrayOf(voxelWidth, voxelHeight, voxelDepth), block = {})
var lut = splitChannelLuts[ch % splitChannelLuts.size]
val channel = channels[ch]
val v = sciView.addVolume(channel as RandomAccessibleInterval<out RealType<*>>, name = image.name + "-ch$ch", voxelDimensions = floatArrayOf(voxelWidth, voxelHeight, voxelDepth), block = {})
val lut = splitChannelLuts[ch % splitChannelLuts.size]
sciView.setColormap(v, lut)
}
} else {
Expand Down
33 changes: 14 additions & 19 deletions src/main/kotlin/sc/iview/commands/edit/Properties.kt
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,8 @@ class Properties : InteractiveCommand() {
@Parameter(label = "Speed", min = "1", max = "10", style = NumberWidget.SCROLL_BAR_STYLE + ",group:Volume", persist = false)
private var playSpeed = 4

@Parameter(label = "Min", callback = "updateNodeProperties", style = "group:Volume")
private var min = 0

@Parameter(label = "Max", callback = "updateNodeProperties", style = "group:Volume")
private var max = 255
@Parameter(label = "Pixel-to-world ratio", min = "0.00001f", max = "1000000.0f", style = NumberWidget.SPINNER_STYLE + ",group:Volume", persist = false, callback = "updateNodeProperties")
private var pixelToWorldRatio: Float = 0.001f

@Parameter(label = "Color map", choices = [], callback = "updateNodeProperties", style = "group:Volume")
private var colormapName: String = "Fire.lut"
Expand Down Expand Up @@ -397,33 +394,30 @@ class Properties : InteractiveCommand() {
log.error("Could not load LUT $colormapName")
}

pixelToWorldRatio = node.pixelToWorldRatio
dimensions = "${node.getDimensions().x}x${node.getDimensions().y}x${node.getDimensions().z}"

min = node.converterSetups[0].displayRangeMin.toInt()
max = node.converterSetups[0].displayRangeMax.toInt()

val maxTimepoint = node.timepointCount - 1
if(maxTimepoint > 0) {
timepoint = node.currentTimepoint
val timepointInput = info.getMutableInput("timepoint", java.lang.Integer::class.java)
timepointInput.minimumValue = java.lang.Integer.valueOf(0) as java.lang.Integer
timepointInput.maximumValue = java.lang.Integer.valueOf(maxTimepoint) as java.lang.Integer
val timepointInput = info.getMutableInput("timepoint", Integer::class.java)
timepointInput.minimumValue = Integer.valueOf(0) as Integer
timepointInput.maximumValue = Integer.valueOf(maxTimepoint) as Integer
} else {
maybeRemoveInput("timepoint", java.lang.Integer::class.java)
maybeRemoveInput("timepoint", Integer::class.java)
maybeRemoveInput("playPauseButton", Button::class.java)
maybeRemoveInput("playSpeed", java.lang.Integer::class.java)
maybeRemoveInput("playSpeed", Integer::class.java)
}

maybeRemoveInput("colour", ColorRGB::class.java)
} else {
maybeRemoveInput("pixelToWorldRatio", Float::class.java)
maybeRemoveInput("slicingMode", String::class.java)
maybeRemoveInput("min", java.lang.Integer::class.java)
maybeRemoveInput("max", java.lang.Integer::class.java)
maybeRemoveInput("timepoint", java.lang.Integer::class.java)
maybeRemoveInput("timepoint", Integer::class.java)
maybeRemoveInput("colormap", ColorTable::class.java)
maybeRemoveInput("colormapName", String::class.java)
maybeRemoveInput("playPauseButton", Button::class.java)
maybeRemoveInput("playSpeed", java.lang.Integer::class.java)
maybeRemoveInput("playSpeed", Integer::class.java)
maybeRemoveInput("dimensions", java.lang.String::class.java)
}

Expand Down Expand Up @@ -489,7 +483,7 @@ class Properties : InteractiveCommand() {
if (node is Line){
edgeWidth = node.edgeWidth.toInt()
} else {
maybeRemoveInput("edgeWidth", java.lang.Integer::class.java)
maybeRemoveInput("edgeWidth", Integer::class.java)
}

name = node.name
Expand Down Expand Up @@ -541,11 +535,12 @@ class Properties : InteractiveCommand() {
log.error("Could not load LUT $colormapName")
}
node.goToTimepoint(timepoint)
node.converterSetups[0].setDisplayRange(min.toDouble(), max.toDouble())
val slicingModeIndex = slicingModeChoices.indexOf(Volume.SlicingMode.valueOf(slicingMode))
if (slicingModeIndex != -1) {
node.slicingMode = Volume.SlicingMode.values()[slicingModeIndex]
}
node.pixelToWorldRatio = pixelToWorldRatio
node.spatial().needsUpdateWorld = true
}
if (node is Camera) {
val scene = node.getScene()
Expand Down
34 changes: 32 additions & 2 deletions src/main/kotlin/sc/iview/ui/SwingMainWindow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import com.formdev.flatlaf.FlatLightLaf
import graphics.scenery.Node
import graphics.scenery.SceneryElement
import graphics.scenery.backends.Renderer
import graphics.scenery.utils.lazyLogger
import graphics.scenery.utils.SceneryJPanel
import graphics.scenery.utils.lazyLogger
import org.joml.Vector2f
import org.scijava.menu.MenuService
import org.scijava.ui.swing.menu.SwingJMenuBarCreator
Expand All @@ -42,14 +42,19 @@ import sc.iview.SciViewService
import sc.iview.SplashLabel
import sc.iview.Utils
import java.awt.*
import java.awt.datatransfer.DataFlavor
import java.awt.dnd.DnDConstants
import java.awt.dnd.DropTarget
import java.awt.dnd.DropTargetDropEvent
import java.awt.event.*
import java.io.File
import java.util.*
import javax.script.ScriptException
import javax.swing.*
import javax.swing.Timer
import kotlin.concurrent.thread
import kotlin.math.roundToInt


/**
* Class for Swing-based main window.
*
Expand Down Expand Up @@ -243,6 +248,31 @@ class SwingMainWindow(val sciview: SciView) : MainWindow {
glassPane.repaint()

sciview.sceneryPanel[0] = sceneryJPanel

// attach drag-and-drop handler to enable dropping of e.g. volume files onto the viewport
sceneryJPanel.dropTarget = object: DropTarget() {
override fun drop(evt: DropTargetDropEvent?) {
if(evt == null) {
return
}

try {
evt.acceptDrop(DnDConstants.ACTION_COPY)
val files = evt.transferable.getTransferData(DataFlavor.javaFileListFlavor) as? List<File>
?: return

files.forEach { file ->
logger.info("$file dropped onto sciview, trying to open 👍")
sciview.open(file.absolutePath)
}

evt.dropComplete(true)
} catch(ex: Exception) {
ex.printStackTrace()
}
}
}

val renderer = Renderer.createRenderer(
sciview.hub,
sciview.applicationName,
Expand Down
Loading