Skip to content

Commit

Permalink
Merge pull request #537 from saalfeldlab/feat/intensityThreshold
Browse files Browse the repository at this point in the history
Feat/intensity threshold
  • Loading branch information
cmhulbert authored Jun 7, 2024
2 parents 4f764b5 + 7db7dbf commit 4068102
Show file tree
Hide file tree
Showing 18 changed files with 513 additions and 82 deletions.
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@

<!-- JavaFx Version-->
<javafx.version>22.0.1</javafx.version>
<saalfx.version>1.4.1</saalfx.version>
<saalfx.version>1.4.2-SNAPSHOT</saalfx.version>
<testfx.version>4.0.16-alpha</testfx.version>

<alphanumeric-comparator.version>1.4.1</alphanumeric-comparator.version>
<dokka.version>1.4.30</dokka.version>

<main-class>org.janelia.saalfeldlab.paintera.Paintera</main-class>
<app.name>Paintera</app.name>
<app.version>1.2.3</app.version>
<app.version>1.2.4</app.version>
<jvm.modules>javafx.base,javafx.controls,javafx.fxml,javafx.media,javafx.swing,javafx.web,javafx.graphics,java.naming,java.management,java.sql</jvm.modules>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<windows.upgrade.uuid>f918b6f9-8685-4b50-9fbd-9be7a1209249</windows.upgrade.uuid>
Expand All @@ -81,7 +81,7 @@
<n5-zarr.version>1.3.3</n5-zarr.version>
<n5-imglib2.version>7.0.0</n5-imglib2.version>
<n5-universe.version>1.5.0</n5-universe.version>
<imglib2-label-multisets.version>0.13.2</imglib2-label-multisets.version>
<imglib2-label-multisets.version>0.14.0</imglib2-label-multisets.version>

<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
<enforcer.skip>true</enforcer.skip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ private void requestRepaint() {
}

@Override
public void onRemoval(SourceInfo paintera) {
public void onRemoval(SourceInfo sourceInfo) {

LOG.info("Removed IntersectingSourceState {}", nameProperty().get());
meshManager.removeAllMeshes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,11 @@ public synchronized <T> void removeSource(final Source<T> source, final boolean

this.states.remove(source);
this.sources.remove(source);
this.currentSource.set(this.sources.size() == 0 ? null : this.sources.get(Math.max(currentSourceIndex - 1, 0)));
this.currentSource.set(this.sources.isEmpty() ? null : this.sources.get(Math.max(currentSourceIndex - 1, 0)));
this.composites.remove(source);
this.removedSources.add(source);
state.onRemoval(this);
if (state != null)
state.onRemoval(this);
}

public SourceState<?, ?> getState(final Source<?> source) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ default void onAdd(PainteraBaseView paintera) {
LOG.debug("Running default onAdd");
}

default void onRemoval(SourceInfo paintera) {
default void onRemoval(SourceInfo sourceInfo) {

LOG.debug("Running default onRemoval");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ public void refreshMeshes() {
meshes.refreshMeshes();
}

@Override public void onRemoval(SourceInfo sourceInfo) {

getMeshManager().removeAllMeshes();
}

@Override
public void onAdd(final PainteraBaseView paintera) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ enum class LabelSourceStateKeys(lateInitNamedKeyCombo : LateInitNamedKeyCombinat
CANCEL ( ESCAPE, "cancel tool / exit mode"),
TOGGLE_NON_SELECTED_LABELS_VISIBILITY ( V + SHIFT_DOWN, "toggle non-selected labels visibility"),
SEGMENT_ANYTHING__TOGGLE_MODE ( A),
SEGMENT_ANYTHING__RESET_PROMPT ( BACK_SPACE),
SEGMENT_ANYTHING__ACCEPT_SEGMENTATION ( ENTER),
PAINT_BRUSH ( SPACE),
FILL_2D ( F),
FILL_3D ( SHIFT_DOWN + F),
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/org/janelia/saalfeldlab/paintera/Paintera.kt
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ class Paintera : Application() {
styleable.stylesheets.add("style/interpolation.css")
styleable.stylesheets.add("style/sam.css")
styleable.stylesheets.add("style/paint.css")
styleable.stylesheets.add("style/raw-source.css")
}

private fun registerPainteraStylesheets(styleable: Parent) {
Expand All @@ -335,6 +336,7 @@ class Paintera : Application() {
styleable.stylesheets.add("style/interpolation.css")
styleable.stylesheets.add("style/sam.css")
styleable.stylesheets.add("style/paint.css")
styleable.stylesheets.add("style/raw-source.css")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import org.janelia.saalfeldlab.fx.extensions.*
import org.janelia.saalfeldlab.fx.midi.MidiActionSet
import org.janelia.saalfeldlab.fx.midi.MidiButtonEvent
import org.janelia.saalfeldlab.fx.midi.MidiPotentiometerEvent
import org.janelia.saalfeldlab.fx.ui.GlyphScaleView
import org.janelia.saalfeldlab.fx.ui.ObjectField.SubmitOn
import org.janelia.saalfeldlab.fx.ui.SpatialField
import org.janelia.saalfeldlab.fx.ui.GlyphScaleView
import org.janelia.saalfeldlab.fx.util.InvokeOnJavaFXApplicationThread
import org.janelia.saalfeldlab.paintera.DeviceManager
import org.janelia.saalfeldlab.paintera.NavigationKeys
Expand Down Expand Up @@ -61,7 +61,9 @@ object NavigationControlMode : AbstractToolMode() {
* Intentianally empty. [NavigationControlMode] has only one tool, which contains all the Navigation actions.
* It will always be active when [NavigationControlMode] is the active mode.
*/
override val modeActions = listOf<ActionSet>()
override val modeActions = listOf<ActionSet>(

)

override val allowedActions = AllowedActions.NAVIGATION

Expand Down Expand Up @@ -378,7 +380,7 @@ object NavigationTool : ViewerTool() {
val delta = speed / 100
val potVal = it!!.value.toDouble()
val scale = 1 - delta * potVal
val (x,y) = targetPositionObservable!!.let { it.x to it.y }
val (x, y) = targetPositionObservable!!.let { it.x to it.y }
zoomController.zoomCenteredAt(scale, x, y)
}
}
Expand Down Expand Up @@ -478,7 +480,7 @@ object NavigationTool : ViewerTool() {
MidiRotationStruct(7, Axis.Z),
).map { (handle, axis) ->
painteraMidiActionSet("rotate", device, target, NavigationActionType.Rotate) {
MidiPotentiometerEvent.POTENTIOMETER_RELATIVE ( handle) {
MidiPotentiometerEvent.POTENTIOMETER_RELATIVE(handle) {
name = "midi_rotate_${axis.name.lowercase()}"
displayType = DisplayType.TRIM
verifyEventNotNull()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package org.janelia.saalfeldlab.paintera.control.modes

import javafx.beans.value.ChangeListener
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.scene.input.KeyCode
import javafx.scene.input.KeyEvent.KEY_PRESSED
import net.imglib2.RandomAccessibleInterval
import net.imglib2.histogram.Histogram1d
import net.imglib2.histogram.Real1dBinMapper
import net.imglib2.realtransform.AffineTransform3D
import net.imglib2.type.label.LabelMultisetType
import net.imglib2.type.label.VolatileLabelMultisetType
import net.imglib2.type.numeric.IntegerType
import net.imglib2.type.numeric.RealType
import net.imglib2.type.numeric.integer.IntType
import net.imglib2.type.numeric.integer.UnsignedLongType
import net.imglib2.type.numeric.real.DoubleType
import net.imglib2.type.volatiles.AbstractVolatileRealType
import net.imglib2.util.Intervals
import net.imglib2.util.Util
import net.imglib2.view.IntervalView
import org.janelia.saalfeldlab.bdv.fx.viewer.ViewerPanelFX
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.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.control.actions.AllowedActions
import org.janelia.saalfeldlab.paintera.control.tools.Tool
import org.janelia.saalfeldlab.paintera.paintera
import org.janelia.saalfeldlab.paintera.state.SourceStateBackendN5
import org.janelia.saalfeldlab.paintera.state.raw.ConnectomicsRawState
import org.janelia.saalfeldlab.util.*


object RawSourceMode : AbstractToolMode() {

override val defaultTool: Tool = NavigationTool

override val tools: ObservableList<Tool> = FXCollections.observableArrayList()

override val allowedActions = AllowedActions.NAVIGATION

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) {
graphic = { ScaleView().apply { styleClass += "intensity-reset-min-max" } }
onAction {
val rawSource = activeSourceStateProperty.get() as ConnectomicsRawState<*, *>
resetIntensityMinMax(rawSource)
}
}
KEY_PRESSED(KeyCode.Y) {
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 }
onAction {
val rawSource = activeSourceStateProperty.get() as ConnectomicsRawState<*, *>
autoIntensityMinMax(rawSource, viewer)
}
}
}

fun autoIntensityMinMax(rawSource: ConnectomicsRawState<*, *>, viewer: ViewerPanelFX) {
val globalToViewerTransform = AffineTransform3D().also { viewer.state.getViewerTransform(it) }
val viewerInterval = Intervals.createMinSize(0, 0, 0, viewer.width.toLong(), viewer.height.toLong(), 1L)

val scaleLevel = viewer.state.bestMipMapLevel
val dataSource = rawSource.getDataSource().getDataSource(0, scaleLevel) as RandomAccessibleInterval<RealType<*>>

val sourceToGlobalTransform = rawSource.getDataSource().getSourceTransformCopy(0, scaleLevel)


val extension = Util.getTypeFromInterval(dataSource).createVariable().let {
when (it) {
is VolatileLabelMultisetType, is LabelMultisetType -> UnsignedLongType(0)
else -> it
}
}


val screenSource = dataSource
.extendValue(extension)
.interpolateNearestNeighbor()
.affineReal(globalToViewerTransform.concatenate(sourceToGlobalTransform))
.raster()
.interval(viewerInterval)

val converter = rawSource.converter()
val curMin = converter.minProperty().get()
val curMax = converter.maxProperty().get()

if ( curMin == curMax) {
resetIntensityMinMax(rawSource)
return
}
if (converterAtDefault(rawSource)) {
estimateWithRange(screenSource, converter)
}

if (extension is IntegerType<*>)
estimateWithHistogram(IntType(), screenSource, rawSource, converter)
else
estimateWithHistogram(DoubleType(), screenSource, rawSource, converter)
}

private fun estimateWithRange(screenSource: IntervalView<RealType<*>>, converter: ARGBColorConverter<out AbstractVolatileRealType<*, *>>) {
var min = screenSource.cursor().get().realDouble
var max = min
screenSource.forEach {
val value = it.realDouble
if (value < min)
min = value
if (value > max)
max = value
}
converter.min = min
converter.max = max
}

private fun <T : RealType<T>> estimateWithHistogram(type: T, screenSource: IntervalView<RealType<*>>, rawSource: ConnectomicsRawState<*, *>, converter: ARGBColorConverter<out AbstractVolatileRealType<*, *>>) {
val binMapper = Real1dBinMapper<T>(converter.min, converter.max, 4, false)
val histogram = Histogram1d(binMapper)
val img = screenSource.convert(type) { src, target -> target.setReal(src.realDouble) }.asIterable()
histogram.countData(img)

val numPixels = screenSource.dimensionsAsLongArray().sum()


val minBinIdx = histogram.indexOfFirst { i -> i.get() > (numPixels / 5000) }
val maxBinIdx = histogram.indexOfLast { i -> i.get() > (numPixels / 5000) }

val updateOrResetConverter = { min : Double, max : Double ->
if (converter.minProperty().value == min && converter.maxProperty().value == max)
resetIntensityMinMax(rawSource)
else {
converter.min = min
converter.max = max
}
}

when {
minBinIdx == -1 && maxBinIdx == -1 -> resetIntensityMinMax(rawSource)
minBinIdx == maxBinIdx -> {
updateOrResetConverter(
histogram.getLowerBound(minBinIdx.toLong(), type).let { type.realDouble },
histogram.getUpperBound(maxBinIdx.toLong(), type).let { type.realDouble }
)
}

else -> {
updateOrResetConverter(
histogram.getCenterValue(minBinIdx.toLong(), type).let { type.realDouble },
histogram.getCenterValue(maxBinIdx.toLong(), type).let { type.realDouble }
)
}
}
}

private fun converterAtDefault(rawSource: ConnectomicsRawState<*, *>) : Boolean {
val converter = rawSource.converter()
(rawSource.backend as? SourceStateBackendN5<*, *>)?.getMetadataState()?.let {
return converter.min == it.minIntensity && converter.max == it.maxIntensity
}

val dataSource = rawSource.getDataSource().getDataSource(0, 0) as RandomAccessibleInterval<RealType<*>>
val extension = Util.getTypeFromInterval(dataSource).createVariable().let {
when (it) {
is VolatileLabelMultisetType, is LabelMultisetType -> UnsignedLongType(0)
else -> it
}
}

return converter.minProperty().get() == extension.minValue && converter.maxProperty().get() == extension.maxValue
}

fun resetIntensityMinMax(rawSource: ConnectomicsRawState<*, *>) {

(rawSource.backend as? SourceStateBackendN5<*, *>)?.getMetadataState()?.let {
rawSource.converter().min = it.minIntensity
rawSource.converter().max = it.maxIntensity
return
}

val dataSource = rawSource.getDataSource().getDataSource(0, 0) as RandomAccessibleInterval<RealType<*>>
val extension = Util.getTypeFromInterval(dataSource).createVariable().let {
when (it) {
is VolatileLabelMultisetType, is LabelMultisetType -> UnsignedLongType(0)
else -> it
}
}

rawSource.converter().minProperty().set(extension.minValue)
rawSource.converter().maxProperty().set(extension.maxValue)
}

override val modeActions: List<ActionSet> = listOf(minMaxIntensityThreshold)

private val moveToolTriggersToActiveViewer = ChangeListener<OrthogonalViews.ViewerAndTransforms?> { _, old, new ->
/* remove the tool triggers from old, add to new */
modeActions.forEach { actionSet ->
old?.viewer()?.removeActionSet(actionSet)
new?.viewer()?.installActionSet(actionSet)
}

/* set the currently activeTool for this viewer */
switchTool(activeTool ?: NavigationTool)
}

override fun enter() {
activeViewerProperty.addListener(moveToolTriggersToActiveViewer)
super.enter()
}

override fun exit() {
activeViewerProperty.removeListener(moveToolTriggersToActiveViewer)
super.exit()
}

}


Loading

0 comments on commit 4068102

Please sign in to comment.