Skip to content

Commit

Permalink
Merge pull request #538 from saalfeldlab/feat/sam_si_connected_compon…
Browse files Browse the repository at this point in the history
…ents

Multi-point prompt for auto-sam, create scalar label source, fixes
  • Loading branch information
cmhulbert authored Jun 10, 2024
2 parents 4068102 + 37c06c5 commit de66d74
Show file tree
Hide file tree
Showing 14 changed files with 171 additions and 71 deletions.
5 changes: 3 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<groupId>org.janelia.saalfeldlab</groupId>
<artifactId>paintera</artifactId>
<version>1.2.4-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>

<name>Paintera</name>
<description>New Era Painting and annotation tool</description>
Expand Down Expand Up @@ -66,7 +66,8 @@

<main-class>org.janelia.saalfeldlab.paintera.Paintera</main-class>
<app.name>Paintera</app.name>
<app.version>1.2.4</app.version>
<app.package>paintera</app.package>
<app.version>1.3.0</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 Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

public class MipMapLevel {

final public SimpleBooleanProperty isLabelMultisetProperty = new SimpleBooleanProperty(true);

final SpatialField<LongProperty> baseDimensions;
final SpatialField<IntegerProperty> relativeDownsamplingFactors;
Expand Down Expand Up @@ -80,7 +81,14 @@ public Node makeNode() {
mipMapRow.getChildren().add(new VBox(resolutionHeader, resolution.getNode()));
mipMapRow.getChildren().add(new VBox(dimensionHeader, dimensions.getNode()));
}
mipMapRow.getChildren().add(new VBox(maxEntriesHeader, maxNumberOfEntriesPerSet.getTextField()));
final VBox numEntriesRow = new VBox(maxEntriesHeader, maxNumberOfEntriesPerSet.getTextField());

isLabelMultisetProperty.subscribe(isLabelMultiset -> {
if (isLabelMultiset)
mipMapRow.getChildren().add(numEntriesRow);
else
mipMapRow.getChildren().remove(numEntriesRow);
});

mipMapRow.setPadding(new Insets(0, 10.0, 0, 10.0));
mipMapRow.spacingProperty().setValue(10.0);
Expand Down
19 changes: 12 additions & 7 deletions src/main/java/org/janelia/saalfeldlab/util/n5/N5Data.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
Expand Down Expand Up @@ -749,9 +750,10 @@ public static void createEmptyLabelDataset(
final double[] resolution,
final double[] offset,
final double[][] relativeScaleFactors,
final int[] maxNumEntries) throws IOException {
@Nullable final int[] maxNumEntries,
final boolean labelMultiset) throws IOException {

createEmptyLabelDataset(container, group, dimensions, blockSize, resolution, offset, relativeScaleFactors, maxNumEntries, false);
createEmptyLabelDataset(container, group, dimensions, blockSize, resolution, offset, relativeScaleFactors, maxNumEntries, labelMultiset, false);
}

/**
Expand All @@ -776,7 +778,8 @@ public static void createEmptyLabelDataset(
final double[] resolution,
final double[] offset,
final double[][] relativeScaleFactors,
final int[] maxNumEntries,
@Nullable final int[] maxNumEntries,
final boolean labelMultisetType,
final boolean ignoreExisiting) throws IOException {

final Map<String, String> pd = new HashMap<>();
Expand Down Expand Up @@ -806,7 +809,7 @@ public static void createEmptyLabelDataset(
n5.setAttribute(dataGroup, N5Helpers.MULTI_SCALE_KEY, true);
n5.setAttribute(dataGroup, N5Helpers.OFFSET_KEY, offset);
n5.setAttribute(dataGroup, N5Helpers.RESOLUTION_KEY, resolution);
n5.setAttribute(dataGroup, N5Helpers.IS_LABEL_MULTISET_KEY, true);
n5.setAttribute(dataGroup, N5Helpers.IS_LABEL_MULTISET_KEY, labelMultisetType);

n5.createGroup(uniqueLabelsGroup);
n5.setAttribute(uniqueLabelsGroup, N5Helpers.MULTI_SCALE_KEY, true);
Expand All @@ -825,12 +828,14 @@ public static void createEmptyLabelDataset(

final String dataset = String.format(scaleDatasetPattern, scaleLevel);
final String uniqeLabelsDataset = String.format(scaleUniqueLabelsPattern, scaleLevel);
final int maxNum = downscaledLevel < 0 ? -1 : maxNumEntries[downscaledLevel];
n5.createDataset(dataset, scaledDimensions, blockSize, DataType.UINT8, new GzipCompression());
n5.createDataset(uniqeLabelsDataset, scaledDimensions, blockSize, DataType.UINT64, new GzipCompression());

n5.setAttribute(dataset, N5Helpers.MAX_NUM_ENTRIES_KEY, maxNum);
n5.setAttribute(dataset, N5Helpers.IS_LABEL_MULTISET_KEY, true);
if (labelMultisetType) {
final int maxNum = downscaledLevel < 0 ? -1 : maxNumEntries[downscaledLevel];
n5.setAttribute(dataset, N5Helpers.MAX_NUM_ENTRIES_KEY, maxNum);
n5.setAttribute(dataset, N5Helpers.IS_LABEL_MULTISET_KEY, true);
}

if (scaleLevel != 0) {
n5.setAttribute(dataset, N5Helpers.DOWNSAMPLING_FACTORS_KEY, accumulatedFactors);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.janelia.saalfeldlab.paintera

import org.janelia.saalfeldlab.bdv.fx.viewer.ViewerPanelFX
import bdv.viewer.ViewerOptions
import com.google.gson.Gson
import com.google.gson.JsonElement
Expand All @@ -16,6 +15,7 @@ import javafx.scene.image.Image
import javafx.stage.Stage
import net.imglib2.realtransform.AffineTransform3D
import org.controlsfx.control.Notifications
import org.janelia.saalfeldlab.bdv.fx.viewer.ViewerPanelFX
import org.janelia.saalfeldlab.fx.event.KeyTracker
import org.janelia.saalfeldlab.fx.event.MouseTracker
import org.janelia.saalfeldlab.fx.extensions.createNullableValueBinding
Expand Down Expand Up @@ -239,7 +239,13 @@ class PainteraMainWindow(val gateway: PainteraGateway = PainteraGateway()) {
.toFile()

private fun deserialize(json: JsonObject?, gson: Gson, indexToState: MutableMap<Int, SourceState<*, *>>) {
initProperties(gson.get<Properties>(json) ?: Properties())
/* Clear any errant null values from the properties. It shouldn't happen, but also is recoverable in case is does (did). */
json?.entrySet()
?.filter { (_, value) -> value == null || value.isJsonNull }?.toList()
?.forEach { (key, _) -> json.remove(key) }

val properties = gson.get<Properties>(json) ?: Properties()
initProperties(properties)
json?.get<JsonObject>(SOURCES_KEY)?.let { sourcesJson ->
SourceInfoSerializer.populate(
{ baseView.addState(it) },
Expand Down Expand Up @@ -273,7 +279,7 @@ class PainteraMainWindow(val gateway: PainteraGateway = PainteraGateway()) {
stage.onHiding = EventHandler { if (!doSaveAndQuit()) it.consume() }
}

internal fun askSaveAndQuit() : Boolean {
internal fun askSaveAndQuit(): Boolean {
return when {
wasQuit -> false
!isSaveNecessary() -> true
Expand Down Expand Up @@ -337,6 +343,7 @@ class PainteraMainWindow(val gateway: PainteraGateway = PainteraGateway()) {
class Serializer : PainteraSerialization.PainteraSerializer<PainteraMainWindow> {
override fun serialize(mainWindow: PainteraMainWindow, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
val map = context[mainWindow.properties].asJsonObject
map.entrySet().filter { (_, value) -> value == null || value.isJsonNull }.toList().forEach { (key, _) -> map.remove(key) }
map.add(SOURCES_KEY, context[mainWindow.baseView.sourceInfo()])
map.addProperty(VERSION_KEY, VERSION_STRING)
map.add(GLOBAL_TRANSFORM_KEY, context[AffineTransform3D().also { mainWindow.baseView.manager().getTransform(it) }])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,16 @@ open class AsyncCacheWithLoader<K : Any, V>(
cache.get(key) {
if (clear) cancelUnsubmittedLoadRequests()
async(loaderContext) { loader(key) }
}
}.also { if (it.isCancelled) cache.invalidate(key) }
}

open fun load(key: K) : Job {
/* If it's already in the cache, deferred or not, just return a completed job and skipp the loader queue */
if (cache.getIfPresent(key) != null) return Job().apply { complete() }
/* If it's already in the cache, deferred or not, just return a completed job and skip the loader queue */
cache.getIfPresent(key)?.let {
if (it.isCancelled)
cache.invalidate(key)
else return Job().apply { complete() }
}

return runBlocking {
async(loaderQueueContext) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.janelia.saalfeldlab.paintera.control

import org.janelia.saalfeldlab.bdv.fx.viewer.ViewerPanelFX
import bdv.viewer.TransformListener
import io.github.oshai.kotlinlogging.KotlinLogging
import javafx.application.Platform
Expand All @@ -25,7 +24,6 @@ import net.imglib2.img.array.ArrayImgFactory
import net.imglib2.img.array.ArrayImgs
import net.imglib2.interpolation.randomaccess.NLinearInterpolatorFactory
import net.imglib2.loops.LoopBuilder
import org.janelia.saalfeldlab.net.imglib2.outofbounds.RealOutOfBoundsConstantValueFactory
import net.imglib2.realtransform.AffineTransform3D
import net.imglib2.realtransform.Translation3D
import net.imglib2.type.BooleanType
Expand All @@ -41,10 +39,12 @@ import net.imglib2.util.*
import net.imglib2.view.ExtendedRealRandomAccessibleRealInterval
import net.imglib2.view.IntervalView
import net.imglib2.view.Views
import org.checkerframework.common.reflection.qual.Invoke
import org.janelia.saalfeldlab.bdv.fx.viewer.ViewerPanelFX
import org.janelia.saalfeldlab.fx.Tasks
import org.janelia.saalfeldlab.fx.extensions.*
import org.janelia.saalfeldlab.fx.util.InvokeOnJavaFXApplicationThread
import org.janelia.saalfeldlab.net.imglib2.outofbounds.RealOutOfBoundsConstantValueFactory
import org.janelia.saalfeldlab.net.imglib2.view.BundleView
import org.janelia.saalfeldlab.paintera.Paintera
import org.janelia.saalfeldlab.paintera.PainteraBaseView
import org.janelia.saalfeldlab.paintera.control.assignment.FragmentSegmentAssignment
Expand All @@ -61,7 +61,6 @@ import org.janelia.saalfeldlab.paintera.util.IntervalHelpers
import org.janelia.saalfeldlab.paintera.util.IntervalHelpers.Companion.extendBy
import org.janelia.saalfeldlab.paintera.util.IntervalHelpers.Companion.smallestContainingInterval
import org.janelia.saalfeldlab.util.*
import org.janelia.saalfeldlab.net.imglib2.view.BundleView
import java.math.BigDecimal
import java.math.RoundingMode
import java.util.Collections
Expand Down Expand Up @@ -192,19 +191,16 @@ class ShapeInterpolationController<D : IntegerType<D>>(
@JvmOverloads
fun addSelection(
maskIntervalOverSelection: Interval,
keepInterpolation: Boolean = true,
replaceExistingSlice: Boolean = false,
globalTransform: AffineTransform3D,
viewerMask: ViewerMask
): SliceInfo? {
if (controllerState == ControllerState.Off) return null
isBusy = true
val selectionDepth = depthAt(globalTransform)
if (!keepInterpolation && slicesAndInterpolants.getSliceAtDepth(selectionDepth) != null) {
if (replaceExistingSlice && slicesAndInterpolants.getSliceAtDepth(selectionDepth) != null)
slicesAndInterpolants.removeSliceAtDepth(selectionDepth)
updateSliceAndInterpolantsCompositeMask()
val slice = SliceInfo(viewerMask, globalTransform, maskIntervalOverSelection)
slicesAndInterpolants.add(selectionDepth, slice)
}

if (slicesAndInterpolants.getSliceAtDepth(selectionDepth) == null) {
val slice = SliceInfo(viewerMask, globalTransform, maskIntervalOverSelection)
slicesAndInterpolants.add(selectionDepth, slice)
Expand Down Expand Up @@ -280,6 +276,7 @@ class ShapeInterpolationController<D : IntegerType<D>>(
}

fun togglePreviewMode() {
freezeInterpolation = false
preview = !preview
if (preview) interpolateBetweenSlices(false)
else updateSliceAndInterpolantsCompositeMask()
Expand All @@ -302,6 +299,7 @@ class ShapeInterpolationController<D : IntegerType<D>>(

@Synchronized
fun interpolateBetweenSlices(replaceExistingInterpolants: Boolean) {
if (freezeInterpolation) return
if (slicesAndInterpolants.slices.size < 2) {
updateSliceAndInterpolantsCompositeMask()
isBusy = false
Expand Down Expand Up @@ -336,7 +334,7 @@ class ShapeInterpolationController<D : IntegerType<D>>(
.reduceOrNull(Intervals::union)
}
updateSliceAndInterpolantsCompositeMask()
requestRepaintAfterTasks(updateInterval)
requestRepaintAfterTasks(updateInterval)
}
}
.onCancelled { _, _ -> LOG.debug { "Interpolation Cancelled" } }
Expand Down Expand Up @@ -472,9 +470,13 @@ class ShapeInterpolationController<D : IntegerType<D>>(
refreshMeshes()
}

private val freezeInterpolationProperty = SimpleBooleanProperty(false)
var freezeInterpolation: Boolean by freezeInterpolationProperty.nonnull()


@Throws(MaskInUse::class)
private fun setCompositeMask(includeInterpolant: Boolean = preview) {
if (freezeInterpolation) return
synchronized(source) {
source.resetMasks(false)
/* If preview is on, hide all except the first and last fill mask */
Expand Down Expand Up @@ -620,10 +622,10 @@ class ShapeInterpolationController<D : IntegerType<D>>(
activeViewer!!.state.getViewerTransform(it)
}

fun getMask(targetMipMapLevel: Int = currentBestMipMapLevel): ViewerMask {
fun getMask(targetMipMapLevel: Int = currentBestMipMapLevel, ignoreExisting: Boolean = false): ViewerMask {

/* If we have a mask, get it; else create a new one */
currentViewerMask = sliceAtCurrentDepth?.let { oldSlice ->
currentViewerMask = (if (ignoreExisting) null else sliceAtCurrentDepth)?.let { oldSlice ->
val oldSliceBoundingBox = oldSlice.maskBoundingBox ?: let {
deleteSliceAt()
return@let null
Expand Down Expand Up @@ -732,11 +734,13 @@ class ShapeInterpolationController<D : IntegerType<D>>(

/* get union of adjacent slices bounding boxes */
val unionInterval = let {

val sourceIntervalInMaskSpace = viewerMask.run { currentMaskToSourceTransform.inverse().estimateBounds(source.getSource(0, info.level)) }
val interpolantIntervalInMaskSpace = viewerMask.currentGlobalToMaskTransform.estimateBounds(interpolantInterval)

val minZSlice = interpolantIntervalInMaskSpace.minAsDoubleArray().also { it[2] = 0.0 }
val maxZSlice = interpolantIntervalInMaskSpace.maxAsDoubleArray().also { it[2] = 0.0 }
val interpolantIntervalSliceInMaskSpace = FinalRealInterval(minZSlice, maxZSlice)
val interpolantIntervalSliceInMaskSpace = FinalRealInterval(minZSlice, maxZSlice) intersect sourceIntervalInMaskSpace


val interpolatedMaskView = interpolant.dataInterpolant
Expand Down Expand Up @@ -773,7 +777,7 @@ class ShapeInterpolationController<D : IntegerType<D>>(
}

companion object {
private val LOG = KotlinLogging.logger { }
private val LOG = KotlinLogging.logger { }
private fun paintera(): PainteraBaseView = Paintera.getPaintera().baseView

private val Long.isInterpolationLabel
Expand Down Expand Up @@ -1215,8 +1219,9 @@ class ShapeInterpolationController<D : IntegerType<D>>(

}

val sourceInMaskInterval = mask.initialMaskToSourceTransform.inverse().estimateBounds(mask.source.getSource(0, mask.info.level))
selectionIntervals
.map { BundleView(mask.viewerImg).interval(it) }
.map { BundleView(mask.viewerImg).interval(it intersect sourceInMaskInterval) }
.map {
val shrinkingInterval = ShrinkingInterval(it.numDimensions())
LoopBuilder.setImages(it).forEachPixel { access ->
Expand Down
Loading

0 comments on commit de66d74

Please sign in to comment.