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 all 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 = "0.11.1"
api("graphics.scenery:scenery:$sceneryVersion") {
version { strictly(sceneryVersion) }
exclude("org.biojava.thirdparty", "forester")
Expand Down
42 changes: 21 additions & 21 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 All @@ -113,6 +109,7 @@ import sc.iview.ui.CustomPropertyUI
import sc.iview.ui.MainWindow
import sc.iview.ui.SwingMainWindow
import sc.iview.ui.TaskManager
import ucar.units.ConversionException
import java.awt.event.WindowListener
import java.io.IOException
import java.net.URL
Expand Down Expand Up @@ -811,19 +808,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,18 +1378,26 @@ 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()).first { idx -> image.axis(idx).type().label == axisNames[axisIdx] }
val inValue = image.axis(d).averageScale(0.0, 1.0)
if (image.axis(d).unit() == null || d >= axes.size) {
voxelDims[axisIdx] = inValue.toFloat()
} else {
val imageAxisUnit = image.axis(d).unit().replace("µ", "u")
val sciviewAxisUnit = axis(axisIdx)!!.unit().replace("µ", "u")
try {
val imageAxisUnit = image.axis(d).unit().replace("µ", "u")
val sciviewAxisUnit = axis(axisIdx)!!.unit().replace("µ", "u")

voxelDims[axisIdx] = unitService.value(inValue, imageAxisUnit, sciviewAxisUnit).toFloat()
} catch(e: IllegalArgumentException) {
logger.warn(e.message + " - setting voxel scale for dimension $axisIdx to 1.0")
voxelDims[axisIdx] = 1.0f
}

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 +1513,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 +1534,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
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ class LoadCremiDatasetAndNeurons : Command {

val v = sciview.addVolume(nai.third, file.name) {
origin = Origin.FrontBottomLeft
this.spatialOrNull()?.scale = Vector3f(0.08f, 0.08f, 5.0f)
this.spatialOrNull()?.scale = Vector3f(8f, 8f, 8f)
transferFunction = TransferFunction.ramp(0.3f, 0.1f, 0.1f)
// min 20, max 180, color map fire

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,10 @@ class LoadFlybrainOutOfCore : Command {
log.info("Done.")

val transform = AffineTransform3D()
val sx = imp.calibration.pixelWidth
val sy = imp.calibration.pixelHeight
val sz = imp.calibration.pixelDepth * 10
val scaleFactor = 40
val sx = imp.calibration.pixelWidth * 40
val sy = imp.calibration.pixelHeight * 40
val sz = imp.calibration.pixelDepth * 40
transform[sx, 0.0, 0.0, 0.0, 0.0, sy, 0.0, 0.0, 0.0, 0.0, sz] = 0.0
val type = UnsignedShortType()

Expand Down
94 changes: 19 additions & 75 deletions src/main/kotlin/sc/iview/commands/edit/Properties.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import graphics.scenery.*
import graphics.scenery.attribute.material.HasMaterial
import graphics.scenery.primitives.Line
import graphics.scenery.primitives.TextBoard
import graphics.scenery.volumes.Colormap
import graphics.scenery.volumes.Colormap.Companion.fromColorTable
import graphics.scenery.volumes.SlicingPlane
import graphics.scenery.volumes.Volume
Expand Down Expand Up @@ -138,17 +139,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 = "Color map", choices = [], callback = "updateNodeProperties", style = "group:Volume")
private var colormapName: String = "Fire.lut"

@Parameter(label = " ", style = "group:Volume")
private var colormap = dummyColorTable
@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 = "AO steps", style = NumberWidget.SPINNER_STYLE+"group:Volume", callback = "updateNodeProperties")
private val occlusionSteps = 0
Expand Down Expand Up @@ -234,6 +226,8 @@ class Properties : InteractiveCommand() {
*/
override fun run() {}
fun setSceneNode(node: Node?) {
Colormap.Companion.lutService = sciView.scijavaContext?.getService(LUTService::class.java)

currentSceneNode = node
updateCommandFields()
}
Expand Down Expand Up @@ -378,47 +372,31 @@ class Properties : InteractiveCommand() {
slicingModeInput.setChoices(slicingMethods)
slicingMode = slicingModeChoices[Volume.SlicingMode.values().indexOf(node .slicingMode)].toString()

val lutNameItem = info.getMutableInput("colormapName", String::class.java)
lutNameItem.choices = sciView.getAvailableLUTs()
val cachedColormapName = node.metadata["sciview.colormapName"] as? String

if(cachedColormapName != null && sciView.getLUT(cachedColormapName) != null) {
colormapName = cachedColormapName
}

try {
val cm = sciView.getLUT(colormapName)
// Ensure the node matches
node.colormap = fromColorTable(cm)
node.metadata["sciview.colormapName"] = colormapName

colormap = cm
} catch (ioe: IOException) {
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 {
// Warning! The java.lang prefix needs to be here, otherwise the compiler
// reverts to the Kotlin types and you'll end up with interesting error messages
// like "float does not match type float" ;-)
maybeRemoveInput("timepoint", java.lang.Integer::class.java)
maybeRemoveInput("playPauseButton", Button::class.java)
maybeRemoveInput("playSpeed", java.lang.Integer::class.java)
}

maybeRemoveInput("colour", ColorRGB::class.java)
} else {
// Warning! The java.lang prefix needs to be here, otherwise the compiler
// reverts to the Kotlin types and you'll end up with interesting error messages
// like "float does not match type float" ;-)
maybeRemoveInput("pixelToWorldRatio", java.lang.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("colormap", ColorTable::class.java)
maybeRemoveInput("colormapName", String::class.java)
Expand Down Expand Up @@ -489,7 +467,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 @@ -530,22 +508,13 @@ class Properties : InteractiveCommand() {
node.intensity = intensity
}
if (node is Volume) {
try {
val cm = sciView.getLUT(colormapName)
node.colormap = fromColorTable(cm!!)
node.metadata["sciview.colormapName"] = colormapName
log.info("Setting new colormap to $colormapName / $cm")

colormap = cm
} catch (ioe: IOException) {
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 Expand Up @@ -624,32 +593,7 @@ class Properties : InteractiveCommand() {
companion object {
private const val PI_NEG = "-3.142"
private const val PI_POS = "3.142"
private var dummyColorTable: ColorTable? = null

private val inputModuleMaps = ConcurrentHashMap<ModuleItem<*>, Module>()

init {
dummyColorTable = object : ColorTable {
override fun lookupARGB(v: Double, v1: Double, v2: Double): Int {
return 0
}

override fun getComponentCount(): Int {
return 0
}

override fun getLength(): Int {
return 0
}

override fun get(i: Int, i1: Int): Int {
return 0
}

override fun getResampled(i: Int, i1: Int, i2: Int): Int {
return 0
}
}
}
}
}
Loading
Loading