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 LUT display and persistence for SetLUT and Properties panel #499

Merged
merged 6 commits into from
Jun 15, 2023
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
53 changes: 52 additions & 1 deletion src/main/kotlin/sc/iview/SciView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ import sc.iview.ui.TaskManager
import tpietzsch.example2.VolumeViewerOptions
import java.awt.event.WindowListener
import java.io.IOException
import java.net.URL
import java.nio.ByteBuffer
import java.nio.FloatBuffer
import java.time.LocalDate
Expand All @@ -122,6 +123,9 @@ 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
Expand All @@ -134,6 +138,7 @@ import kotlin.math.sin
// we suppress unused warnings here because @Parameter-annotated fields
// get updated automatically by SciJava.
class SciView : SceneryBase, CalibratedRealInterval<CalibratedAxis> {

val sceneryPanel = arrayOf<SceneryPanel?>(null)

/*
Expand Down Expand Up @@ -216,6 +221,11 @@ class SciView : SceneryBase, CalibratedRealInterval<CalibratedAxis> {
* Set the SciJava Display
*/ var display: Display<*>? = null

/**
* List of available LUTs for caching
*/
private var availableLUTs = LinkedHashMap<String, URL>()

/**
* Return the current SceneryJPanel. This is necessary for custom context menus
* @return panel the current SceneryJPanel
Expand Down Expand Up @@ -899,6 +909,7 @@ class SciView : SceneryBase, CalibratedRealInterval<CalibratedAxis> {
n.let {
objectService.addObject(n)
eventService.publish(NodeAddedEvent(n))
(mainWindow as SwingMainWindow).nodePropertyEditor.updateProperties(n, rebuild = true)
}
return n
}
Expand Down Expand Up @@ -1418,7 +1429,8 @@ fun deleteNode(node: Node?, activePublish: Boolean = true) {
numTimepoints: Int,
name: String = "Volume",
vararg voxelDimensions: Float,
block: Volume.() -> Unit = {}): Volume {
block: Volume.() -> Unit = {},
colormapName: String = "Fire.lut"): Volume {
var timepoints = numTimepoints
var cacheControl: CacheControl? = null

Expand Down Expand Up @@ -1462,6 +1474,10 @@ fun deleteNode(node: Node?, activePublish: Boolean = true) {
val bg = BoundingGrid()
bg.node = v

// Set default colormap
v.metadata["sciview.colormapName"] = colormapName
v.colormap = Colormap.fromColorTable(getLUT(colormapName))

imageToVolumeMap[image] = v
return addNode(v, block = block)
}
Expand Down Expand Up @@ -1745,6 +1761,41 @@ fun deleteNode(node: Node?, activePublish: Boolean = true) {
println(scijavaContext!!.serviceIndex)
}

/**
* Return the color table corresponding to the [lutName]
* @param lutName a String represening an ImageJ style LUT name, like Fire.lut
* @return a [ColorTable] corresponding to the LUT or null if LUT not available
*/
fun getLUT(lutName: String = "Fire.lut"): ColorTable {
try {
refreshLUTs()
var lutResult = availableLUTs[lutName]
return lutService.loadLUT(lutResult)
} catch (e: IOException) {
log.error("LUT $lutName not available")
e.printStackTrace()
throw e
}
}

/**
* Refresh the cache of available LUTs fetched from LutService
*/
private fun refreshLUTs() {
if(availableLUTs.isEmpty()) {
availableLUTs.putAll(lutService.findLUTs().entries.sortedBy { it.key }.map { it.key to it.value })
}
}

/**
* Return a list of available LUTs/colormaps
* @return a list of LUT names
*/
fun getAvailableLUTs(): List<String> {
refreshLUTs()
return availableLUTs.map {entry -> entry.key}
}

companion object {
//bounds for the controls
const val FPSSPEED_MINBOUND_SLOW = 0.01f
Expand Down
36 changes: 17 additions & 19 deletions src/main/kotlin/sc/iview/commands/edit/Properties.kt
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,6 @@ class Properties : InteractiveCommand() {
@Parameter
private lateinit var log: LogService

private var availableLUTs = LinkedHashMap<String, URL>()

/**
* Nothing happens here, as cancelling the dialog is not possible.
*/
Expand All @@ -242,13 +240,6 @@ class Properties : InteractiveCommand() {
}

protected fun initValues() {
if (colormap == null) {
try {
lutService.loadLUT(availableLUTs["Red.lut"])
} catch (ioe: IOException) {
System.err.println("IOException while loading Red.lut")
}
}
rebuildSceneObjectChoiceList()
refreshSceneNodeInDialog()
updateCommandFields()
Expand Down Expand Up @@ -345,10 +336,6 @@ class Properties : InteractiveCommand() {
/** Updates command fields to match current scene node properties. */
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
fun updateCommandFields() {
if(availableLUTs.isEmpty()) {
availableLUTs.putAll(lutService.findLUTs().entries.sortedBy { it.key }.map { it.key to it.value })
}

val node = currentSceneNode ?: return

fieldsUpdating = true
Expand Down Expand Up @@ -397,13 +384,24 @@ class Properties : InteractiveCommand() {
slicingMode = slicingModeChoices[Volume.SlicingMode.values().indexOf(node .slicingMode)].toString()

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

if(cachedColormapName != null && availableLUTs[cachedColormapName] != null) {
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")
}

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

Expand Down Expand Up @@ -538,9 +536,9 @@ class Properties : InteractiveCommand() {
node.renderingMethod = Volume.RenderingMethod.values()[mode]
}
try {
val cm = lutService.loadLUT(availableLUTs[colormapName])
node.colormap = fromColorTable(cm)
node.metadata["sciview.colormap-name"] = colormapName
val cm = sciView.getLUT(colormapName)
node.colormap = fromColorTable(cm!!)
node.metadata["sciview.colormapName"] = colormapName
log.info("Setting new colormap to $colormapName / $cm")

colormap = cm
Expand Down
36 changes: 24 additions & 12 deletions src/main/kotlin/sc/iview/commands/view/SetLUT.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,22 @@
package sc.iview.commands.view

import graphics.scenery.Node
import net.imagej.lut.LUTService
import net.imglib2.display.AbstractArrayColorTable
import net.imglib2.display.ColorTable
import org.scijava.command.Command
import org.scijava.command.CommandService
import org.scijava.command.DynamicCommand
import org.scijava.plugin.Menu
import org.scijava.plugin.Parameter
import org.scijava.plugin.Plugin
import org.scijava.prefs.PrefService
import sc.iview.SciView
import sc.iview.commands.MenuWeights.VIEW
import sc.iview.commands.MenuWeights.VIEW_SET_LUT
import sc.iview.commands.demo.basic.VolumeRenderDemo
import java.io.IOException
import java.net.URL
import java.util.*


/**
* Command to set the currently used Look Up Table (LUT). This is a colormap for the volume.
*
Expand All @@ -55,38 +56,49 @@ class SetLUT : DynamicCommand() {
@Parameter
private lateinit var sciView: SciView

@Parameter
private lateinit var lutService: LUTService

@Parameter(label = "Node")
private lateinit var node: Node

@Parameter(label = "Selected LUT", choices = [], callback = "lutNameChanged")
@Parameter(label = "Selected LUT", choices = [], callback = "lutNameChanged", initializer = "initLutName")
private lateinit var lutName: String

@Parameter(label = "LUT Selection")
private lateinit var colorTable: ColorTable

@Parameter
private lateinit var prefs: PrefService

protected fun initLutName() {
lutName = "Fire.lut"
}

protected fun lutNameChanged() {
val lutNameItem = info.getMutableInput("lutName", String::class.java)
try {
colorTable = lutService.loadLUT(lutService.findLUTs()[lutNameItem.toString()])
colorTable = sciView.getLUT(lutName)!!
} catch (e: IOException) {
e.printStackTrace()
} catch (e: Exception) {
e.printStackTrace()
}
}

override fun initialize() {
val lutNameItem = info.getMutableInput("lutName", String::class.java)
lutNameItem.choices = sciView.getAvailableLUTs()

var persistLutName = prefs.get(SetLUT::class.java, "lutName");

try {
colorTable = lutService.loadLUT(lutService.findLUTs()["Red.lut"])
colorTable = sciView.getLUT(persistLutName)!!
} catch (e: IOException) {
e.printStackTrace()
}
val lutNameItem = info.getMutableInput("lutName", String::class.java)
lutNameItem.choices = ArrayList(lutService.findLUTs().keys)
}

override fun run() {
node.metadata["sciview.colormapName"] = lutName
sciView.setColormap(node, colorTable)
// Trigger an update to the UI for the colormap
sciView.publishNode(node)
}
}
9 changes: 4 additions & 5 deletions src/main/kotlin/sc/iview/ui/SwingNodePropertyEditor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ class SwingNodePropertyEditor(private val sciView: SciView) : UIComponent<JPanel
private val updateLock = ReentrantLock()

/** Generates a properties panel for the given node. */
fun updateProperties(sceneNode: Node?) {
fun updateProperties(sceneNode: Node?, rebuild: Boolean = false) {
if (sceneNode == null) {
try {
if (updateLock.tryLock() || updateLock.tryLock(200, TimeUnit.MILLISECONDS)) {
Expand All @@ -295,7 +295,7 @@ class SwingNodePropertyEditor(private val sciView: SciView) : UIComponent<JPanel
}
try {
if (updateLock.tryLock() || updateLock.tryLock(200, TimeUnit.MILLISECONDS)) {
if (currentNode === sceneNode && currentProperties != null) {
if (!rebuild && currentNode === sceneNode && currentProperties != null) {
currentProperties!!.updateCommandFields()
inputPanel.refresh()
updateLock.unlock()
Expand Down Expand Up @@ -347,6 +347,7 @@ class SwingNodePropertyEditor(private val sciView: SciView) : UIComponent<JPanel
textArea.text = "<html><pre>$stackTrace</pre>"
updatePropertiesPanel(textArea)
}

updateLock.unlock()
}
} catch (e: InterruptedException) {
Expand Down Expand Up @@ -418,9 +419,7 @@ class SwingNodePropertyEditor(private val sciView: SciView) : UIComponent<JPanel
if (newPath != null) {
tree.selectionPath = newPath
tree.scrollPathToVisible(newPath)
if (node !== sciView.activeNode) {
updateProperties(node)
}
updateProperties(node)
}
}

Expand Down