diff --git a/pom.xml b/pom.xml
index 4b98a7566..dda35ddf4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,7 +58,7 @@
22.0.1
- 1.4.1
+ 1.4.2
4.0.16-alpha
1.4.1
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/BindingKeys.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/BindingKeys.kt
index b7650e120..3e17f071d 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/BindingKeys.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/BindingKeys.kt
@@ -156,6 +156,29 @@ enum class LabelSourceStateKeys(lateInitNamedKeyCombo : LateInitNamedKeyCombinat
}
}
+enum class RawSourceStateKeys(lateInitNamedKeyCombo : LateInitNamedKeyCombination) : NamedKeyBinding by lateInitNamedKeyCombo {
+ RESET_MIN_MAX_INTENSITY_THRESHOLD ( SHIFT_DOWN + Y, "Reset Min / Max Intensity Threshold"),
+ AUTO_MIN_MAX_INTENSITY_THRESHOLD ( Y, "Auto Min / Max Intensity Threshold"),
+ ;
+
+
+ private val formattedName = name.lowercase()
+ .replace("__", ": ")
+ .replace("_", " ")
+
+ constructor(keys : KeyCombination, name : String? = null) : this(LateInitNamedKeyCombination(keys, name))
+ constructor(key : KeyCode, name : String? = null) : this(LateInitNamedKeyCombination(key.asCombination(), name))
+ constructor(key : Modifier, name : String? = null) : this(LateInitNamedKeyCombination(key.asCombination(), name))
+
+ init {
+ lateInitNamedKeyCombo.setName(formattedName)
+ }
+
+ companion object {
+ fun namedCombinationsCopy() = NamedKeyCombination.CombinationMap(*entries.map { it.deepCopy }.toTypedArray())
+ }
+}
+
object NavigationKeys {
const val BUTTON_TRANSLATE_ALONG_NORMAL_FORWARD = "translate along normal forward"
const val BUTTON_TRANSLATE_ALONG_NORMAL_FORWARD_FAST = "translate along normal forward fast"
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/RawSourceMode.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/RawSourceMode.kt
index 1811287c5..da012eb3c 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/RawSourceMode.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/RawSourceMode.kt
@@ -28,6 +28,7 @@ import org.janelia.saalfeldlab.fx.actions.painteraActionSet
import org.janelia.saalfeldlab.fx.ortho.OrthogonalViews
import org.janelia.saalfeldlab.fx.ui.ScaleView
import org.janelia.saalfeldlab.net.imglib2.converter.ARGBColorConverter
+import org.janelia.saalfeldlab.paintera.RawSourceStateKeys
import org.janelia.saalfeldlab.paintera.control.actions.AllowedActions
import org.janelia.saalfeldlab.paintera.control.tools.Tool
import org.janelia.saalfeldlab.paintera.paintera
@@ -46,14 +47,14 @@ object RawSourceMode : AbstractToolMode() {
private val minMaxIntensityThreshold = painteraActionSet("Min/Max Intensity Threshold") {
verifyAll(KEY_PRESSED, "Source State is Raw Source State ") { activeSourceStateProperty.get() is ConnectomicsRawState<*, *> }
- KEY_PRESSED(KeyCode.SHIFT, KeyCode.Y) {
+ KEY_PRESSED(RawSourceStateKeys.RESET_MIN_MAX_INTENSITY_THRESHOLD) {
graphic = { ScaleView().apply { styleClass += "intensity-reset-min-max" } }
onAction {
val rawSource = activeSourceStateProperty.get() as ConnectomicsRawState<*, *>
resetIntensityMinMax(rawSource)
}
}
- KEY_PRESSED(KeyCode.Y) {
+ KEY_PRESSED(RawSourceStateKeys.AUTO_MIN_MAX_INTENSITY_THRESHOLD) {
lateinit var viewer: ViewerPanelFX
graphic = { ScaleView().apply { styleClass += "intensity-auto-min-max" } }
verify("Last focused viewer found") { paintera.baseView.lastFocusHolder.value?.viewer()?.also { viewer = it } != null }
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/ShapeInterpolationMode.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/ShapeInterpolationMode.kt
index 5aff3996a..0d9b0daa9 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/ShapeInterpolationMode.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/modes/ShapeInterpolationMode.kt
@@ -1,7 +1,6 @@
package org.janelia.saalfeldlab.paintera.control.modes
import bdv.fx.viewer.render.RenderUnitState
-import bdv.util.BdvFunctions
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
import io.github.oshai.kotlinlogging.KotlinLogging
import javafx.beans.value.ChangeListener
@@ -18,16 +17,18 @@ import net.imglib2.loops.LoopBuilder
import net.imglib2.realtransform.AffineTransform3D
import net.imglib2.type.logic.BoolType
import net.imglib2.type.numeric.IntegerType
-import net.imglib2.type.numeric.integer.UnsignedIntType
import net.imglib2.type.numeric.integer.UnsignedLongType
-import net.imglib2.type.volatiles.VolatileUnsignedIntType
import net.imglib2.util.Intervals
import net.imglib2.view.IntervalView
import net.imglib2.view.Views
import org.janelia.saalfeldlab.control.mcu.MCUButtonControl
-import org.janelia.saalfeldlab.fx.actions.*
+import org.janelia.saalfeldlab.fx.actions.ActionSet
import org.janelia.saalfeldlab.fx.actions.ActionSet.Companion.installActionSet
import org.janelia.saalfeldlab.fx.actions.ActionSet.Companion.removeActionSet
+import org.janelia.saalfeldlab.fx.actions.NamedKeyBinding
+import org.janelia.saalfeldlab.fx.actions.painteraActionSet
+import org.janelia.saalfeldlab.fx.actions.painteraMidiActionSet
+import org.janelia.saalfeldlab.fx.extensions.addTriggeredWithListener
import org.janelia.saalfeldlab.fx.midi.MidiButtonEvent
import org.janelia.saalfeldlab.fx.midi.MidiToggleEvent
import org.janelia.saalfeldlab.fx.midi.ToggleAction
@@ -35,9 +36,12 @@ import org.janelia.saalfeldlab.fx.ortho.OrthogonalViews
import org.janelia.saalfeldlab.fx.ui.GlyphScaleView
import org.janelia.saalfeldlab.fx.util.InvokeOnJavaFXApplicationThread
import org.janelia.saalfeldlab.labels.Label
+import org.janelia.saalfeldlab.net.imglib2.view.BundleView
import org.janelia.saalfeldlab.paintera.DeviceManager
import org.janelia.saalfeldlab.paintera.LabelSourceStateKeys.*
import org.janelia.saalfeldlab.paintera.cache.HashableTransform.Companion.hashable
+import org.janelia.saalfeldlab.paintera.cache.SamEmbeddingLoaderCache
+import org.janelia.saalfeldlab.paintera.cache.SamEmbeddingLoaderCache.calculateTargetSamScreenScaleFactor
import org.janelia.saalfeldlab.paintera.control.ShapeInterpolationController
import org.janelia.saalfeldlab.paintera.control.ShapeInterpolationController.ControllerState.Moving
import org.janelia.saalfeldlab.paintera.control.ShapeInterpolationController.EditSelectionChoice
@@ -45,12 +49,13 @@ import org.janelia.saalfeldlab.paintera.control.actions.AllowedActions
import org.janelia.saalfeldlab.paintera.control.actions.MenuActionType
import org.janelia.saalfeldlab.paintera.control.actions.NavigationActionType
import org.janelia.saalfeldlab.paintera.control.actions.PaintActionType
-import org.janelia.saalfeldlab.paintera.cache.SamEmbeddingLoaderCache
-import org.janelia.saalfeldlab.paintera.cache.SamEmbeddingLoaderCache.calculateTargetSamScreenScaleFactor
import org.janelia.saalfeldlab.paintera.control.paint.ViewerMask
import org.janelia.saalfeldlab.paintera.control.paint.ViewerMask.Companion.createViewerMask
import org.janelia.saalfeldlab.paintera.control.tools.Tool
-import org.janelia.saalfeldlab.paintera.control.tools.paint.*
+import org.janelia.saalfeldlab.paintera.control.tools.paint.Fill2DTool
+import org.janelia.saalfeldlab.paintera.control.tools.paint.PaintBrushTool
+import org.janelia.saalfeldlab.paintera.control.tools.paint.SamPredictor
+import org.janelia.saalfeldlab.paintera.control.tools.paint.SamTool
import org.janelia.saalfeldlab.paintera.control.tools.shapeinterpolation.ShapeInterpolationFillTool
import org.janelia.saalfeldlab.paintera.control.tools.shapeinterpolation.ShapeInterpolationPaintBrushTool
import org.janelia.saalfeldlab.paintera.control.tools.shapeinterpolation.ShapeInterpolationSAMTool
@@ -59,8 +64,6 @@ import org.janelia.saalfeldlab.paintera.data.mask.MaskInfo
import org.janelia.saalfeldlab.paintera.data.mask.MaskedSource
import org.janelia.saalfeldlab.paintera.paintera
import org.janelia.saalfeldlab.util.*
-import org.janelia.saalfeldlab.net.imglib2.view.BundleView
-import kotlin.collections.forEach
import kotlin.collections.set
class ShapeInterpolationMode>(val controller: ShapeInterpolationController, private val previousMode: ControlMode) : AbstractToolMode() {
@@ -165,7 +168,7 @@ class ShapeInterpolationMode>(val controller: ShapeInterpolat
with(controller) {
verifyAll(KEY_PRESSED, "Shape Interpolation Controller is Active ") { isControllerActive }
verifyAll(Event.ANY, "Shape Interpolation Tool is Active") { activeTool is ShapeInterpolationTool }
- val exitMode = { _ : Event? ->
+ val exitMode = { _: Event? ->
exitShapeInterpolation(false)
paintera.baseView.changeMode(previousMode)
}
@@ -190,7 +193,12 @@ class ShapeInterpolationMode>(val controller: ShapeInterpolat
filter = true
verify("Fill2DTool is active") { activeTool is Fill2DTool }
onAction {
- switchTool(shapeInterpolationTool)
+ fill2DTool.fillIsRunningProperty.addTriggeredWithListener { obs, _, isRunning ->
+ if (!isRunning) {
+ switchTool(shapeInterpolationTool)
+ obs?.removeListener(this)
+ }
+ }
}
}
},
@@ -202,7 +210,7 @@ class ShapeInterpolationMode>(val controller: ShapeInterpolat
}
KEY_PRESSED(fill2DTool.keyTrigger) {
name = "switch to fill2d tool"
- verify("Active source is MaskedSource") { activeSourceStateProperty.get()?.dataSource is MaskedSource<*, *> }
+ verify("Active source is MaskedSource") { activeSourceStateProperty.get()?.dataSource is MaskedSource<*, *> }
onAction { switchTool(fill2DTool) }
}
KEY_PRESSED(samTool.keyTrigger) {
@@ -343,7 +351,7 @@ class ShapeInterpolationMode>(val controller: ShapeInterpolat
private fun ActionSet.keyPressEditSelectionAction(choice: EditSelectionChoice, namedKey: NamedKeyBinding) =
with(controller) {
- KEY_PRESSED ( namedKey) {
+ KEY_PRESSED(namedKey) {
graphic = when (choice) {
EditSelectionChoice.First -> {
{ GlyphScaleView(FontAwesomeIconView().also { it.styleClass += "interpolation-first-slice" }) }
@@ -398,7 +406,7 @@ class ShapeInterpolationMode>(val controller: ShapeInterpolat
.toList()
val renderState = RenderUnitState(mask.initialGlobalToViewerTransform.copy(), mask.info.time, sources, width.toLong(), height.toLong())
- val predictionRequest = SamPredictor.SparsePrediction(maxDistancePositions.map { (x,y) -> renderState.getSamPoint(x,y, SamPredictor.SparseLabel.IN) })
+ val predictionRequest = SamPredictor.SparsePrediction(maxDistancePositions.map { (x, y) -> renderState.getSamPoint(x, y, SamPredictor.SparseLabel.IN) })
SamSliceInfo(renderState, mask, predictionRequest, null, false).also {
SamEmbeddingLoaderCache.load(renderState)
@@ -465,7 +473,7 @@ class ShapeInterpolationMode>(val controller: ShapeInterpolat
selectionIntervalOverMask: Interval,
globalTransform: AffineTransform3D = paintera.baseView.manager().transform,
viewerMask: ViewerMask = controller.currentViewerMask!!,
- replaceExistingSlice : Boolean = false
+ replaceExistingSlice: Boolean = false
): SamSliceInfo? {
val globalToViewerTransform = viewerMask.initialGlobalToMaskTransform
val sliceDepth = controller.depthAt(globalToViewerTransform)
@@ -527,7 +535,7 @@ internal fun IntervalView.getComponentMaxDistancePosition(): L
DistanceTransform.binaryTransform(invertedBinaryImg, distances, DistanceTransform.DISTANCE_TYPE.EUCLIDIAN)
- val distancePerComponent = mutableMapOf>()
+ val distancePerComponent = mutableMapOf>()
var backgroundId = -1;
@@ -601,7 +609,7 @@ internal data class SamSliceInfo(val renderState: RenderUnitState, val mask: Vie
prediction = SamPredictor.SparsePrediction(listOf(renderState.getSamPoint(viewerX, viewerY, label)))
}
- fun updatePrediction(viewerPositions : List, label: SamPredictor.SparseLabel = SamPredictor.SparseLabel.IN) {
- prediction = SamPredictor.SparsePrediction(viewerPositions.map { (x,y) -> renderState.getSamPoint(x,y, label) })
+ fun updatePrediction(viewerPositions: List, label: SamPredictor.SparseLabel = SamPredictor.SparseLabel.IN) {
+ prediction = SamPredictor.SparsePrediction(viewerPositions.map { (x, y) -> renderState.getSamPoint(x, y, label) })
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/Fill2DTool.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/Fill2DTool.kt
index 87a18086f..d85be0300 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/Fill2DTool.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/Fill2DTool.kt
@@ -8,6 +8,7 @@ import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import javafx.scene.Cursor
import javafx.scene.input.*
+import javafx.util.Subscription
import net.imglib2.Interval
import net.imglib2.util.Intervals
import org.janelia.saalfeldlab.fx.UtilityTask
@@ -110,14 +111,16 @@ open class Fill2DTool(activeSourceStateProperty: SimpleObjectProperty Unit = {}): UtilityTask<*>? {
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/SamTool.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/SamTool.kt
index ada02c540..f3a4b2d3e 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/SamTool.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/SamTool.kt
@@ -26,6 +26,7 @@ import javafx.scene.layout.Pane
import javafx.scene.shape.Circle
import javafx.scene.shape.Rectangle
import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.Channel
import net.imglib2.FinalInterval
import net.imglib2.Interval
import net.imglib2.RandomAccessibleInterval
@@ -92,7 +93,6 @@ import org.janelia.saalfeldlab.paintera.util.IntervalHelpers.Companion.smallestC
import org.janelia.saalfeldlab.paintera.util.algorithms.otsuThresholdPrediction
import org.janelia.saalfeldlab.util.*
import java.util.concurrent.CancellationException
-import java.util.concurrent.LinkedBlockingQueue
import kotlin.collections.List
import kotlin.collections.MutableList
import kotlin.collections.addAll
@@ -204,7 +204,7 @@ open class SamTool(activeSourceStateProperty: SimpleObjectProperty(null)
var lastPrediction by lastPredictionProperty.nullable()
@@ -232,13 +232,13 @@ open class SamTool(activeSourceStateProperty: SimpleObjectProperty()
- private val predictionQueue = LinkedBlockingQueue>(1)
+ private val predictionChannel = Channel>(1)
private var currentPredictionRequest: Pair? = null
- set(value) = synchronized(predictionQueue) {
- predictionQueue.clear()
+ set(value) = runBlocking {
+ predictionChannel.tryReceive() /* capacity 1, so this will always either do nothing, or empty the channel */
value?.let { (request, _) ->
- predictionQueue.put(value)
+ predictionChannel.send(value)
if (!temporaryPrompt)
request.drawPrompt()
}
@@ -297,6 +297,7 @@ open class SamTool(activeSourceStateProperty: SimpleObjectProperty, estimateThreshold: Boolean = true) {
+ if (promptPoints.isEmpty())
+ temporaryPrompt = true
+
if (!predictionJob.isActive) {
startPredictionJob()
}
@@ -845,9 +845,24 @@ open class SamTool(activeSourceStateProperty: SimpleObjectProperty? = null
private var currentPrediction: SamPredictor.SamPrediction? = null
+
+ private val resetSAMTaskOnException = CoroutineExceptionHandler { _, exception ->
+ LOG.error(exception) { "Error during SAM Prediction " }
+ isBusy = false
+ deactivate()
+ mode?.apply {
+ InvokeOnJavaFXApplicationThread {
+ switchTool(defaultTool)
+ }
+ }
+ SAM_TASK_SCOPE = CoroutineScope(Dispatchers.IO + Job())
+ }
+
+ protected open var currentDisplay = false
+
private fun startPredictionJob() {
val maskSource = maskedSource ?: return
- predictionJob = SAM_TASK_SCOPE.launch {
+ predictionJob = SAM_TASK_SCOPE.launch(resetSAMTaskOnException) {
val session = createOrtSessionTask.get()
val imageEmbedding = try {
runBlocking {
@@ -863,9 +878,8 @@ open class SamTool(activeSourceStateProperty: SimpleObjectProperty true
- !info.preGenerated -> false
- else -> temporaryPrompt
+ if (!info.locked && !info.preGenerated) {
+ temporaryPrompt = false
+ requestPrediction(info.prediction)
}
- requestPrediction(info.prediction)
}
- override fun cancelAction() {
- shapeInterpolationMode.switchTool(shapeInterpolationMode.defaultTool)
+ override fun deactivate() {
+ super.deactivate()
controller.setMaskOverlay()
}
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/raw/ConnectomicsRawState.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/raw/ConnectomicsRawState.kt
index 18b8a6679..72b9f231f 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/raw/ConnectomicsRawState.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/raw/ConnectomicsRawState.kt
@@ -22,8 +22,10 @@ import org.janelia.saalfeldlab.fx.TitledPanes
import org.janelia.saalfeldlab.fx.extensions.TitledPaneExtensions.Companion.graphicsOnly
import org.janelia.saalfeldlab.fx.ui.NamedNode
import org.janelia.saalfeldlab.paintera.PainteraBaseView
+import org.janelia.saalfeldlab.paintera.RawSourceStateKeys
import org.janelia.saalfeldlab.paintera.composition.Composite
import org.janelia.saalfeldlab.paintera.composition.CompositeCopy
+import org.janelia.saalfeldlab.paintera.config.input.KeyAndMouseBindings
import org.janelia.saalfeldlab.paintera.control.modes.ControlMode
import org.janelia.saalfeldlab.paintera.control.modes.RawSourceMode
import org.janelia.saalfeldlab.paintera.data.DataSource
@@ -78,6 +80,10 @@ open class ConnectomicsRawState(
private val source: DataSource = backend.createSource(queue, priority, name)
+ override fun createKeyAndMouseBindings(): KeyAndMouseBindings {
+ return KeyAndMouseBindings(RawSourceStateKeys.namedCombinationsCopy())
+ }
+
override fun getDataSource(): DataSource = source
override fun converter(): ARGBColorConverter = converter