diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/control/actions/LabelActionType.java b/src/main/java/org/janelia/saalfeldlab/paintera/control/actions/LabelActionType.java index bd51a98d6..feb63ff62 100644 --- a/src/main/java/org/janelia/saalfeldlab/paintera/control/actions/LabelActionType.java +++ b/src/main/java/org/janelia/saalfeldlab/paintera/control/actions/LabelActionType.java @@ -4,7 +4,7 @@ public enum LabelActionType implements ActionType { Toggle(true), - Append, + Append(true ), CreateNew, Lock(true), Merge, diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/data/mask/MaskedSource.java b/src/main/java/org/janelia/saalfeldlab/paintera/data/mask/MaskedSource.java index ca42e97c2..83dabd03e 100644 --- a/src/main/java/org/janelia/saalfeldlab/paintera/data/mask/MaskedSource.java +++ b/src/main/java/org/janelia/saalfeldlab/paintera/data/mask/MaskedSource.java @@ -41,6 +41,7 @@ import javafx.util.Duration; import javafx.util.Pair; import mpicbg.spim.data.sequence.VoxelDimensions; +import net.imglib2.AbstractInterval; import net.imglib2.FinalInterval; import net.imglib2.FinalRealInterval; import net.imglib2.Interval; @@ -66,6 +67,7 @@ import net.imglib2.img.cell.AbstractCellImg; import net.imglib2.img.cell.CellGrid; import net.imglib2.interpolation.randomaccess.NearestNeighborInterpolatorFactory; +import net.imglib2.iterator.LocalizingIntervalIterator; import net.imglib2.loops.LoopBuilder; import net.imglib2.parallel.TaskExecutor; import net.imglib2.parallel.TaskExecutors; @@ -105,6 +107,8 @@ import org.janelia.saalfeldlab.paintera.data.n5.BlockSpec; import org.janelia.saalfeldlab.paintera.ui.PainteraAlerts; import org.janelia.saalfeldlab.paintera.util.IntervalHelpers; +import org.janelia.saalfeldlab.paintera.util.IntervalIterable; +import org.janelia.saalfeldlab.paintera.util.ReusableIntervalIterator; import org.janelia.saalfeldlab.util.TmpVolatileHelpers; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1187,36 +1191,51 @@ private static > Set downsample( steps ); - /* Views.tiles doesn't preserve intervals, so zeroMin prior to tiling */ final var zeroMinTarget = Views.zeroMin(target); final var sourceInterval = IntervalHelpers.scale(target, steps, true); final IntervalView zeroMinSource = Views.zeroMin(Views.interval(source, sourceInterval)); - final var tiledSource = Views.tiles(zeroMinSource, steps); - final HashSet labels = new HashSet<>(); - LoopBuilder.setImages(tiledSource, zeroMinTarget) - .forEachChunk(chunk -> { - final TLongLongHashMap maxCounts = new TLongLongHashMap(); - chunk.forEachPixel((sourceTile, lowResTarget) -> { - var maxCount = -1L; - var maxId = Label.INVALID; - for (T t : Views.iterable(sourceTile)) { - final long id = t.getIntegerLong(); - if (id == Label.INVALID) - continue; - final var curCount = maxCounts.adjustOrPutValue(id, 1, 1); - if (curCount > maxCount) { - maxCount = curCount; - maxId = id; - } - } - lowResTarget.setInteger(maxId); - if (maxId != Label.INVALID) - labels.add(maxId); - maxCounts.clear(); - }); - return null; - }); + + final RandomAccess zeroMinSourceRA = zeroMinSource.randomAccess(); + final RandomAccess zeroMinTargetRA = zeroMinTarget.randomAccess(); + + final ReusableIntervalIterator sourceIntervalIterator = new ReusableIntervalIterator(sourceInterval); + final long[] sourcePosMin = new long[zeroMinSource.numDimensions()]; + final long[] sourcePosMax = new long[zeroMinSource.numDimensions()]; + final var sourceTileInterval = new AbstractInterval(sourcePosMin, sourcePosMax, false) { + + }; + + final TLongLongHashMap maxCounts = new TLongLongHashMap(); + + var sourceTileIterable = new IntervalIterable(sourceIntervalIterator); + for (long[] targetPos : new IntervalIterable(new LocalizingIntervalIterator(zeroMinTarget))) { + for (int i = 0; i < sourcePosMin.length; i++) { + sourcePosMin[i] = targetPos[i] * steps[i]; + sourcePosMax[i] = sourcePosMin[i] + steps[i] - 1; + } + sourceIntervalIterator.resetInterval(sourceTileInterval); + + var maxCount = -1L; + var maxId = Label.INVALID; + + for (long[] sourcePos : sourceTileIterable) { + final long sourceVal = zeroMinSourceRA.setPositionAndGet(sourcePos).getIntegerLong(); + if (sourceVal != Label.INVALID) { + var curCount = maxCounts.adjustOrPutValue(sourceVal, 1, 1); + if (curCount > maxCount) { + maxCount++; + maxId = sourceVal; + } + } + } + + final T targetVal = zeroMinTargetRA.setPositionAndGet(targetPos); + targetVal.setInteger(maxId); + labels.add(maxId); + maxCounts.clear(); + } + labels.remove(Label.INVALID); return labels; } diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/ui/dialogs/opendialog/VolatileHelpers.java b/src/main/java/org/janelia/saalfeldlab/paintera/ui/dialogs/opendialog/VolatileHelpers.java index 1e2f621ec..72b19fa23 100644 --- a/src/main/java/org/janelia/saalfeldlab/paintera/ui/dialogs/opendialog/VolatileHelpers.java +++ b/src/main/java/org/janelia/saalfeldlab/paintera/ui/dialogs/opendialog/VolatileHelpers.java @@ -39,6 +39,6 @@ public Cell createInvalid(final Long key) throws Exc } } - private static final VolatileLabelMultisetArray EMPTY_ACCESS = new VolatileLabelMultisetArray(0, true, new long[]{Label.INVALID}); + private static final VolatileLabelMultisetArray EMPTY_ACCESS = new VolatileLabelMultisetArray(0, false, new long[]{Label.INVALID}); } diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/PainteraMainWindow.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/PainteraMainWindow.kt index 2f0420c8d..5ddc54a7f 100644 --- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/PainteraMainWindow.kt +++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/PainteraMainWindow.kt @@ -117,8 +117,8 @@ class PainteraMainWindow(val gateway: PainteraGateway = PainteraGateway()) { { projectDirectory.actualDirectory.absolutePath }, { indexToState[it] }) val gson = builder.create() - val json = projectDirectory.actualDirectory.absolutePath - .let { Paintera.n5Factory.openReader(it).getAttribute("/", PAINTERA_KEY, JsonElement::class.java) } + val json = projectDirectory.actualDirectory.toURI() + .let { Paintera.n5Factory.openReader(it.toString()).getAttribute("/", PAINTERA_KEY, JsonElement::class.java) } ?.takeIf { it.isJsonObject } ?.asJsonObject Paintera.n5Factory.gsonBuilder(builder) diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/cache/SamEmbeddingLoaderCache.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/cache/SamEmbeddingLoaderCache.kt index 256694bb9..a54364766 100644 --- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/cache/SamEmbeddingLoaderCache.kt +++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/cache/SamEmbeddingLoaderCache.kt @@ -81,7 +81,9 @@ object SamEmbeddingLoaderCache : AsyncCacheWithLoader - slice.opacityProperty().bind(baseConfig.opacityProperty()) - slice.shadingProperty().bind(baseConfig.shadingProperty()) + slice.opacityProperty().bindBidirectional(baseConfig.opacityProperty()) + slice.shadingProperty().bindBidirectional(baseConfig.shadingProperty()) } } } 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 bf83c2815..3e09125e6 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 @@ -26,8 +26,10 @@ import org.janelia.saalfeldlab.control.mcu.MCUButtonControl 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.DragActionSet import org.janelia.saalfeldlab.fx.actions.NamedKeyBinding import org.janelia.saalfeldlab.fx.actions.painteraActionSet +import org.janelia.saalfeldlab.fx.actions.painteraDragActionSet import org.janelia.saalfeldlab.fx.actions.painteraMidiActionSet import org.janelia.saalfeldlab.fx.midi.MidiButtonEvent import org.janelia.saalfeldlab.fx.midi.MidiToggleEvent @@ -89,7 +91,7 @@ class ShapeInterpolationMode>(val controller: ShapeInterpolat old?.viewer()?.apply { modeActions.forEach { removeActionSet(it) } } } - private val samNavigationRequestListener = ChangeListener { _, _, curViewer -> + private val samNavigationRequestListener = ChangeListener { _, _, curViewer -> SamEmbeddingLoaderCache.stopNavigationBasedRequests() curViewer?.let { SamEmbeddingLoaderCache.startNavigationBasedRequests(curViewer) @@ -221,6 +223,17 @@ class ShapeInterpolationMode>(val controller: ShapeInterpolat keyPressEditSelectionAction(EditSelectionChoice.Previous, SHAPE_INTERPOLATION__SELECT_PREVIOUS_SLICE) keyPressEditSelectionAction(EditSelectionChoice.Next, SHAPE_INTERPOLATION__SELECT_NEXT_SLICE) }, + painteraDragActionSet("drag activate SAM mode with box", PaintActionType.Paint, ignoreDisable = true, consumeMouseClicked = true) { + onDragDetected { + verify("can't trigger box prompt with active tool") { activeTool in listOf(NavigationTool, shapeInterpolationTool, samTool) } + switchTool(samTool) + } + onDrag { + (activeTool as? SamTool)?.apply { + requestBoxPromptPrediction(it) + } + } + }, DeviceManager.xTouchMini?.let { device -> activeViewerProperty.get()?.viewer()?.let { viewer -> painteraMidiActionSet("midi paint tool switch actions", device, viewer, PaintActionType.Paint) { diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/paint/FloodFill.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/paint/FloodFill.kt index 5becc69c8..966638103 100644 --- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/paint/FloodFill.kt +++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/paint/FloodFill.kt @@ -27,14 +27,15 @@ import org.janelia.saalfeldlab.paintera.util.IntervalHelpers.Companion.smallestC import org.janelia.saalfeldlab.util.extendValue import java.util.concurrent.CancellationException import java.util.concurrent.atomic.AtomicBoolean -import java.util.function.* +import java.util.function.BooleanSupplier +import java.util.function.Consumer import java.util.stream.Collectors class FloodFill>( private val activeViewerProperty: ObservableValue, - private val source: MaskedSource, + val source: MaskedSource, private val assignment: FragmentSegmentAssignment, - private val requestRepaint: Consumer, + val requestRepaint: Consumer, private val isVisible: BooleanSupplier ) { @@ -42,7 +43,7 @@ class FloodFill>( val fill = fillSupplier?.invoke() ?: let { return Job().apply { val reason = CancellationException("Received invalid label -- will not fill.") - LOG.debug(reason) { } + LOG.debug(reason) { } completeExceptionally(reason) } } @@ -55,7 +56,7 @@ class FloodFill>( if (!isVisible.asBoolean) { return Job().apply { val reason = CancellationException("Selected source is not visible -- will not fill") - LOG.debug(reason) { } + LOG.debug(reason) { } completeExceptionally(reason) } } @@ -96,7 +97,7 @@ class FloodFill>( val seedLabel = assignment?.getSegment(seedValue!!.integerLong) ?: seedValue!!.integerLong if (!Label.regular(seedLabel)) { val reason = CancellationException("Cannot fill at irregular label: $seedLabel (${Point(seed)})") - LOG.debug(reason) { } + LOG.debug(reason) { } return Job().apply { completeExceptionally(reason) } } @@ -115,11 +116,13 @@ class FloodFill>( .collect(Collectors.toList()) val triggerRefresh = AtomicBoolean(false) + var floodFillJob: Job? = null val accessTracker: AccessBoxRandomAccessible = object : AccessBoxRandomAccessible(mask.rai.extendValue(UnsignedLongType(1))) { val position: Point = Point(sourceAccess.numDimensions()) override fun get(): UnsignedLongType { - if (Thread.currentThread().isInterrupted) throw RuntimeException("Flood Fill Interrupted") + if (floodFillJob?.isCancelled == true || Thread.currentThread().isInterrupted) + throw CancellationException("Flood Fill Canceled") synchronized(this) { updateAccessBox() } @@ -136,18 +139,17 @@ class FloodFill>( } } - val floodFillJob = CoroutineScope(Dispatchers.Default).launch { + floodFillJob = CoroutineScope(Dispatchers.Default).launch { val fillContext = coroutineContext InvokeOnJavaFXApplicationThread { - delay(1000) while (fillContext.isActive) { + awaitPulse() if (triggerRefresh.get()) { val repaintInterval = globalToSource.inverse().estimateBounds(accessTracker.createAccessInterval()) requestRepaint.accept(repaintInterval.smallestContainingInterval) triggerRefresh.set(false) } awaitPulse() - awaitPulse() } } @@ -156,32 +158,16 @@ class FloodFill>( } else { fillPrimitiveType(data, accessTracker, seed, seedLabel, fill, assignment) } - }.also { - it.invokeOnCompletion { cause -> - val sourceInterval = accessTracker.createAccessInterval() - val globalInterval = globalToSource.inverse().estimateBounds(sourceInterval).smallestContainingInterval - - when (cause) { - null -> { - LOG.trace { "FloodFill has been completed" } - LOG.trace { - "Applying mask for interval ${Intervals.minAsLongArray(sourceInterval).contentToString()} ${Intervals.maxAsLongArray(sourceInterval).contentToString()}" - } - requestRepaint.accept(globalInterval) - source.applyMask(mask, sourceInterval, MaskedSource.VALID_LABEL_CHECK) - } - is CancellationException -> try { - LOG.debug { "FloodFill has been interrupted" } - source.resetMasks() - requestRepaint.accept(globalInterval) - } catch (e: MaskInUse) { - LOG.error(e) {} - } + val sourceInterval = accessTracker.createAccessInterval() + val globalInterval = globalToSource.inverse().estimateBounds(sourceInterval).smallestContainingInterval - else -> requestRepaint.accept(globalInterval) - } + LOG.trace { "FloodFill has been completed" } + LOG.trace { + "Applying mask for interval ${Intervals.minAsLongArray(sourceInterval).contentToString()} ${Intervals.maxAsLongArray(sourceInterval).contentToString()}" } + requestRepaint.accept(globalInterval) + source.applyMask(mask, sourceInterval, MaskedSource.VALID_LABEL_CHECK) } return floodFillJob } @@ -231,7 +217,7 @@ class FloodFill>( ) { source, target: UnsignedLongType -> predicate(source, target) } } - private suspend fun > fillPrimitiveType( + private fun > fillPrimitiveType( input: RandomAccessibleInterval, output: RandomAccessible, seed: Localizable, @@ -249,7 +235,7 @@ class FloodFill>( seed, UnsignedLongType(fillLabel), DiamondShape(1) - ) { source, target: UnsignedLongType -> runBlocking { predicate(source, target) } } + ) { source, target: UnsignedLongType -> predicate(source, target) } } private fun > makePredicate(seedLabel: Long, assignment: FragmentSegmentAssignment?): (T, UnsignedLongType) -> Boolean { diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/Fill3DTool.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/Fill3DTool.kt index 75525f49a..a3a8e8eca 100644 --- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/Fill3DTool.kt +++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/control/tools/paint/Fill3DTool.kt @@ -1,6 +1,7 @@ package org.janelia.saalfeldlab.paintera.control.tools.paint import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView +import io.github.oshai.kotlinlogging.KotlinLogging import javafx.beans.property.SimpleBooleanProperty import javafx.beans.property.SimpleObjectProperty import javafx.beans.value.ChangeListener @@ -22,6 +23,7 @@ import org.janelia.saalfeldlab.fx.ui.GlyphScaleView import org.janelia.saalfeldlab.fx.ui.ScaleView import org.janelia.saalfeldlab.fx.util.InvokeOnJavaFXApplicationThread import org.janelia.saalfeldlab.paintera.LabelSourceStateKeys +import org.janelia.saalfeldlab.paintera.Paintera import org.janelia.saalfeldlab.paintera.control.ControlUtils import org.janelia.saalfeldlab.paintera.control.actions.PaintActionType import org.janelia.saalfeldlab.paintera.control.modes.ToolMode @@ -32,6 +34,10 @@ import org.janelia.saalfeldlab.paintera.ui.overlays.CursorOverlayWithText class Fill3DTool(activeSourceStateProperty: SimpleObjectProperty?>, mode: ToolMode? = null) : PaintTool(activeSourceStateProperty, mode) { + companion object { + private val LOG = KotlinLogging.logger { } + } + override val graphic = { ScaleView().also { it.styleClass += "fill-3d" } } override val name = "Fill 3D" @@ -48,7 +54,7 @@ class Fill3DTool(activeSourceStateProperty: SimpleObjectProperty paintera.baseView.orthogonalViews().requestRepaint(interval) }, - { activeSourceStateProperty.value?.isVisibleProperty?.get() ?: false } + { activeSourceStateProperty.value?.isVisibleProperty?.get() == true } ) } } @@ -101,16 +107,23 @@ class Fill3DTool(activeSourceStateProperty: SimpleObjectProperty - floodFillTask = task paintera.baseView.isDisabledProperty.addListener(setFalseAndRemoveListener) paintera.baseView.disabledPropertyBindings[this] = fillIsRunningProperty - task.invokeOnCompletion { cause -> - fillIsRunningProperty.set(false) - paintera.baseView.disabledPropertyBindings -= this - statePaintContext?.refreshMeshes?.invoke() - floodFillTask = null + floodFillTask = task.apply { + invokeOnCompletion { cause -> + fillIsRunningProperty.set(false) + paintera.baseView.disabledPropertyBindings -= this + cause?.let { + LOG.debug(cause) { "Fill 3D cancelled, resetting mask. " } + statePaintContext?.dataSource?.resetMasks(true) + fill.source.resetMasks() + fill.requestRepaint.accept(null) + } + statePaintContext?.refreshMeshes?.invoke() + floodFillTask = null + } } } } 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 1b6ac8fa2..a14d95489 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 @@ -233,7 +233,7 @@ open class SamTool(activeSourceStateProperty: SimpleObjectProperty() + private var screenScale = Double.NaN private val predictionChannel = Channel>(1) @@ -635,23 +635,29 @@ open class SamTool(activeSourceStateProperty: SimpleObjectProperty - - val xInBounds = mouse.x.coerceIn(0.0, activeViewer!!.width) - val yInBounds = mouse.y.coerceIn(0.0, activeViewer!!.height) - - val (minX, maxX) = (if (startX < mouse.x) startX to xInBounds else xInBounds to startX) - val (minY, maxY) = (if (startY < mouse.y) startY to yInBounds else yInBounds to startY) - - val topLeft = SamPoint(minX * screenScale, minY * screenScale, SamPredictor.SparseLabel.TOP_LEFT_BOX) - val bottomRight = SamPoint(maxX * screenScale, maxY * screenScale, SamPredictor.SparseLabel.BOTTOM_RIGHT_BOX) - val points = setBoxPrompt(topLeft, bottomRight) - temporaryPrompt = false - requestPrediction(points) + requestBoxPromptPrediction(mouse) } } ) } + internal fun DragActionSet.requestBoxPromptPrediction(mouse: MouseEvent) { + val (width, height) = activeViewer?.run { width to height } ?: return + val scale = if (screenScale != Double.NaN) screenScale else return + + val xInBounds = mouse.x.coerceIn(0.0, width) + val yInBounds = mouse.y.coerceIn(0.0, height) + + val (minX, maxX) = (if (startX < mouse.x) startX to xInBounds else xInBounds to startX) + val (minY, maxY) = (if (startY < mouse.y) startY to yInBounds else yInBounds to startY) + + val topLeft = SamPoint(minX * scale, minY * scale, SamPredictor.SparseLabel.TOP_LEFT_BOX) + val bottomRight = SamPoint(maxX * scale, maxY * scale, SamPredictor.SparseLabel.BOTTOM_RIGHT_BOX) + val points = setBoxPrompt(topLeft, bottomRight) + temporaryPrompt = false + requestPrediction(points) + } + private fun resetPromptAndPrediction() { clearPromptDrawings() currentPredictionRequest = null diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/util/IntervalHelpers.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/util/IntervalHelpers.kt index 21a0e2f89..2febab484 100644 --- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/util/IntervalHelpers.kt +++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/util/IntervalHelpers.kt @@ -2,10 +2,14 @@ package org.janelia.saalfeldlab.paintera.util import net.imglib2.* import net.imglib2.algorithm.util.Grids +import net.imglib2.iterator.IntervalIterator +import net.imglib2.iterator.LocalizingIntervalIterator import net.imglib2.realtransform.RealTransform import net.imglib2.util.Intervals +import org.janelia.saalfeldlab.paintera.Paintera import org.janelia.saalfeldlab.paintera.util.IntervalHelpers.Companion.scale import org.janelia.saalfeldlab.util.shape +import java.io.File import java.util.* import kotlin.math.max import kotlin.math.min @@ -139,43 +143,101 @@ class IntervalHelpers { fun RealInterval.realCorner(d: Int, corner: Long) = if (corner == 0L) realMin(d) else realMax(d) } +} +class IntervalIterable(private val iterator: IntervalIterator) : Iterable { -} + private val pos: LongArray = iterator.positionAsLongArray() -fun main() { - //TODO Caleb: Move to test - val zeroMin = Intervals.createMinMaxReal(0.0, 0.0, 99.0, 99.0) - zeroMin.shape().contentEquals(doubleArrayOf(100.0, 100.0)) + override fun iterator(): Iterator { + return IntervalPositionIterator(iterator, pos) + } + private class IntervalPositionIterator( + private val interval: IntervalIterator, + private val pos: LongArray + ) : Iterator { - val zeroMinDoubleTrue = zeroMin.scale(2.0, scaleMin = true) - zeroMinDoubleTrue.shape().contentEquals(doubleArrayOf(200.0, 200.0)) + override fun hasNext(): Boolean { + return interval.hasNext() + } - val zeroMinDoubleFalse = zeroMin.scale(2.0, scaleMin = false) - zeroMinDoubleFalse.shape().contentEquals(doubleArrayOf(200.0, 200.0)) + override fun next(): LongArray { + interval.fwd() + interval.localize(pos) + return pos + } + } +} - val zeroMinDoubleFalse5 = zeroMin.scale(5.0, scaleMin = false) - zeroMinDoubleFalse5.shape().contentEquals(doubleArrayOf(500.0, 500.0)) +open class ReusableIntervalIterator(interval: Interval) : LocalizingIntervalIterator(interval) { - val zeroMinHalfTrue: RealInterval = zeroMin.scale(.5, scaleMin = true) - zeroMinHalfTrue.shape().contentEquals(doubleArrayOf(50.0, 50.0)) + protected var reusableLastIndex: Long = lastIndex - val zeroMinHalfFalse = zeroMin.scale(.5, scaleMin = false) - zeroMinHalfFalse.shape().contentEquals(doubleArrayOf(50.0, 50.0)) + fun resetInterval(interval: Interval) { + interval.min(min) + interval.max(max) - val min = Intervals.createMinMaxReal(50.0, 50.0, 99.0, 99.0) - zeroMinDoubleTrue.shape().contentEquals(doubleArrayOf(200.0, 200.0)) + val m = n - 1 + var k = 1L.also { steps[0] = it } + for (d in 0 until m) { + val dimd = interval.dimension(d) + dimensions[d] = dimd + k *= dimd + steps[d + 1] = k + } + val dimm = interval.dimension(m) + dimensions[m] = dimm + reusableLastIndex = k * dimm - 1 + reset() + } - val minDoubleTrue = min.scale(2.0, scaleMin = true) - minDoubleTrue.shape().contentEquals(doubleArrayOf(100.0, 100.0)) + override fun hasNext(): Boolean { + return index < reusableLastIndex + } +} - val minDoubleFalse = min.scale(2.0, scaleMin = false) - minDoubleFalse.shape().contentEquals(doubleArrayOf(100.0, 100.0)) - val minHalfTrue = min.scale(.5, scaleMin = true) - minHalfTrue.shape().contentEquals(doubleArrayOf(25.0, 25.0)) +fun main() { - val minHalfFalse = min.scale(.5, scaleMin = false) - minHalfFalse.shape().contentEquals(doubleArrayOf(25.0, 25.0)) + val it = LocalizingIntervalIterator(Intervals.createMinSize(0,0,4,4)) + + IntervalIterable(it).forEach { println(it.joinToString()) } + + + +// //TODO Caleb: Move to test +// val zeroMin = Intervals.createMinMaxReal(0.0, 0.0, 99.0, 99.0) +// zeroMin.shape().contentEquals(doubleArrayOf(100.0, 100.0)) +// +// +// val zeroMinDoubleTrue = zeroMin.scale(2.0, scaleMin = true) +// zeroMinDoubleTrue.shape().contentEquals(doubleArrayOf(200.0, 200.0)) +// +// val zeroMinDoubleFalse = zeroMin.scale(2.0, scaleMin = false) +// zeroMinDoubleFalse.shape().contentEquals(doubleArrayOf(200.0, 200.0)) +// +// val zeroMinDoubleFalse5 = zeroMin.scale(5.0, scaleMin = false) +// zeroMinDoubleFalse5.shape().contentEquals(doubleArrayOf(500.0, 500.0)) +// +// val zeroMinHalfTrue: RealInterval = zeroMin.scale(.5, scaleMin = true) +// zeroMinHalfTrue.shape().contentEquals(doubleArrayOf(50.0, 50.0)) +// +// val zeroMinHalfFalse = zeroMin.scale(.5, scaleMin = false) +// zeroMinHalfFalse.shape().contentEquals(doubleArrayOf(50.0, 50.0)) +// +// val min = Intervals.createMinMaxReal(50.0, 50.0, 99.0, 99.0) +// zeroMinDoubleTrue.shape().contentEquals(doubleArrayOf(200.0, 200.0)) +// +// val minDoubleTrue = min.scale(2.0, scaleMin = true) +// minDoubleTrue.shape().contentEquals(doubleArrayOf(100.0, 100.0)) +// +// val minDoubleFalse = min.scale(2.0, scaleMin = false) +// minDoubleFalse.shape().contentEquals(doubleArrayOf(100.0, 100.0)) +// +// val minHalfTrue = min.scale(.5, scaleMin = true) +// minHalfTrue.shape().contentEquals(doubleArrayOf(25.0, 25.0)) +// +// val minHalfFalse = min.scale(.5, scaleMin = false) +// minHalfFalse.shape().contentEquals(doubleArrayOf(25.0, 25.0)) }