Skip to content

Commit

Permalink
refactor!: abstract mesh export with progress bar
Browse files Browse the repository at this point in the history
  • Loading branch information
cmhulbert committed Jan 22, 2025
1 parent af3ee51 commit 86faf15
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import gnu.trove.list.array.TFloatArrayList;
import gnu.trove.list.array.TIntArrayList;
import javafx.beans.property.SimpleIntegerProperty;
import net.imglib2.Interval;
import net.imglib2.util.Intervals;
import org.janelia.saalfeldlab.fx.ui.Exceptions;
import org.janelia.saalfeldlab.fx.util.InvokeOnJavaFXApplicationThread;
import org.janelia.saalfeldlab.paintera.meshes.managed.GetBlockListFor;
import org.janelia.saalfeldlab.paintera.meshes.managed.GetMeshFor;
import org.janelia.saalfeldlab.util.HashWrapper;
Expand All @@ -14,24 +16,37 @@
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.*;
import java.util.concurrent.CancellationException;

public abstract class MeshExporter<T> {

public final SimpleIntegerProperty blocksProcessed = new SimpleIntegerProperty(0);
boolean cancelled = false;

private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public void exportMesh(
final GetBlockListFor<T> getBlockListFor,
final GetMeshFor<T> getMeshFor,
final MeshSettings[] meshSettings,
final T[] ids,
final List<T> ids,
final int scale,
final String path) {

for (int i = 0; i < ids.length; i++) {
exportMesh(getBlockListFor, getMeshFor, meshSettings[i], ids[i], scale, path, i != 0);
for (int i = 0; i < ids.size(); i++) {
exportMesh(getBlockListFor, getMeshFor, meshSettings[i], ids.get(i), scale, path, i != 0);
}
}

public void exportMesh(
public void cancel() {

cancelled = true;
}

public boolean isCancelled() {
return cancelled;
}

private void exportMesh(
final GetBlockListFor<T> getBlockListFor,
final GetMeshFor<T> getMeshFor,
final MeshSettings meshSettings,
Expand All @@ -53,8 +68,8 @@ public void exportMesh(
// generate keys from blocks, scaleIndex, and id
final List<ShapeKey<T>> keys = new ArrayList<>();
for (final Interval block : blocks) {
// ignoring simplification iterations parameter
// TODO consider smoothing parameters
if (cancelled)
throw new CancellationException("Mesh Export Cancelled");
keys.add(new ShapeKey<>(
id,
scaleIndex,
Expand All @@ -72,8 +87,11 @@ public void exportMesh(
final var normals = new TFloatArrayList();
final var indices = new TIntArrayList();
for (final ShapeKey<T> key : keys) {
if (cancelled)
throw new CancellationException("Mesh Export Cancelled");
PainteraTriangleMesh verticesAndNormals;
verticesAndNormals = getMeshFor.getMeshFor(key);
InvokeOnJavaFXApplicationThread.invoke(() -> blocksProcessed.set(blocksProcessed.get() + 1));
if (verticesAndNormals == null) {
continue;
}
Expand All @@ -86,6 +104,9 @@ public void exportMesh(
}
}

if (cancelled)
throw new CancellationException("Mesh Export Cancelled");

try {
save(path, id.toString(), vertices.toArray(), normals.toArray(), indices.toArray(), append);
} catch (final IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import net.imglib2.type.NativeType
import net.imglib2.type.numeric.IntegerType
import net.imglib2.type.numeric.integer.AbstractIntegerType
import org.janelia.saalfeldlab.fx.extensions.createObservableBinding
import org.janelia.saalfeldlab.fx.util.InvokeOnJavaFXApplicationThread
import org.janelia.saalfeldlab.n5.DataType
import org.janelia.saalfeldlab.n5.DatasetAttributes
import org.janelia.saalfeldlab.n5.GsonKeyValueN5Reader
Expand All @@ -32,7 +33,6 @@ import org.janelia.saalfeldlab.util.convertRAI
import org.janelia.saalfeldlab.util.interval
import org.janelia.saalfeldlab.util.n5.N5Helpers.MAX_ID_KEY
import org.janelia.saalfeldlab.util.n5.N5Helpers.forEachBlockExists
import java.util.concurrent.atomic.AtomicInteger

class ExportSourceState {

Expand Down Expand Up @@ -127,7 +127,7 @@ class ExportSourceState {
}
progressUpdater?.apply {
exportJob.invokeOnCompletion { finish() }
showAndStart()
InvokeOnJavaFXApplicationThread { showAndWait() }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.janelia.saalfeldlab.paintera.meshes.ui

import io.github.oshai.kotlinlogging.KotlinLogging
import javafx.beans.property.*
import javafx.collections.FXCollections
import javafx.collections.ObservableList
Expand All @@ -13,22 +14,33 @@ import javafx.scene.paint.Color
import javafx.scene.shape.CullFace
import javafx.scene.shape.DrawMode
import javafx.stage.Modality
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.javafx.awaitPulse
import net.imglib2.type.label.LabelMultisetType
import org.janelia.saalfeldlab.fx.Buttons
import org.janelia.saalfeldlab.fx.Labels
import org.janelia.saalfeldlab.fx.extensions.TitledPaneExtensions
import org.janelia.saalfeldlab.fx.extensions.createNonNullValueBinding
import org.janelia.saalfeldlab.fx.ui.NamedNode
import org.janelia.saalfeldlab.fx.ui.NumericSliderWithField
import org.janelia.saalfeldlab.fx.util.InvokeOnJavaFXApplicationThread
import org.janelia.saalfeldlab.paintera.meshes.MeshExporterObj
import org.janelia.saalfeldlab.paintera.meshes.MeshInfo
import org.janelia.saalfeldlab.paintera.meshes.MeshSettings
import org.janelia.saalfeldlab.paintera.meshes.managed.GetBlockListFor
import org.janelia.saalfeldlab.paintera.meshes.managed.GetMeshFor
import org.janelia.saalfeldlab.paintera.meshes.managed.MeshManager
import org.janelia.saalfeldlab.paintera.meshes.managed.MeshManagerWithAssignmentForSegments
import org.janelia.saalfeldlab.paintera.meshes.ui.MeshInfoPane.Companion.LOG
import org.janelia.saalfeldlab.paintera.ui.PainteraAlerts
import org.janelia.saalfeldlab.paintera.ui.RefreshButton
import org.janelia.saalfeldlab.paintera.ui.dialogs.AnimatedProgressBarAlert
import org.janelia.saalfeldlab.paintera.ui.source.mesh.MeshExportResult
import org.janelia.saalfeldlab.paintera.ui.source.mesh.MeshExporterDialog
import org.janelia.saalfeldlab.paintera.ui.source.mesh.MeshProgressBar
import java.util.concurrent.CancellationException
import kotlin.math.max
import kotlin.math.min

Expand Down Expand Up @@ -190,7 +202,7 @@ class MeshSettingsController @JvmOverloads constructor(
// min label ratio slider only makes sense for sources of label multiset type
if (addMinLabelratioSlider) {
val tooltipText = "Min label percentage for a pixel to be filled." + System.lineSeparator() +
"0.0 means that a pixel will always be filled if it contains the given label."
"0.0 means that a pixel will always be filled if it contains the given label."
addGridOption("Min label ratio", minLabelRatioSlider, tooltipText)
}

Expand Down Expand Up @@ -318,14 +330,14 @@ open class MeshInfoPane<T>(private val meshInfo: MeshInfo<T>) : TitledPane(null,
val result = exportDialog.showAndWait()
if (!result.isPresent) return@setOnAction

val manager: MeshManager<T> = meshInfo.manager
val parameters = result.get()
manager.exportMeshWithProgressPopup(parameters)

val meshExporter = parameters.meshExporter

val ids = parameters.meshKeys
if (ids.isEmpty()) return@setOnAction

val filePath = parameters.filePath
val scale = parameters.scale

if (meshExporter is MeshExporterObj) {
meshExporter.exportMaterial(
Expand All @@ -334,19 +346,72 @@ open class MeshInfoPane<T>(private val meshInfo: MeshInfo<T>) : TitledPane(null,
arrayOf(meshInfo.manager.getStateFor(ids[0])?.color ?: Color.WHITE)
)
}

meshExporter.exportMesh(
meshInfo.manager.getBlockListFor,
meshInfo.manager.getMeshFor,
meshInfo.meshSettings,
ids[0],
scale,
filePath,
false
)
}
return exportMeshButton
}

companion object {
private val LOG = KotlinLogging.logger { }
}
}

fun <T> MeshManager<T>.exportMeshWithProgressPopup(result : MeshExportResult<T>) {
val log = KotlinLogging.logger { }
val meshExporter = result.meshExporter
val blocksProcessed = meshExporter.blocksProcessed
val ids = result.meshKeys
if (ids.isEmpty()) return
val (getBlocks, getMesh) = when(this) {
is MeshManagerWithAssignmentForSegments -> getBlockListForSegment as GetBlockListFor<T> to getMeshForLongKey as GetMeshFor<T>
else -> getBlockListFor to getMeshFor
}
val totalBlocks = ids.sumOf { getBlocks.getBlocksFor(result.scale, it).count() }
val labelProp = SimpleStringProperty().apply {
bind(blocksProcessed.createNonNullValueBinding { "Blocks processed: ${it}/$totalBlocks" })
}
val progressProp = SimpleDoubleProperty(0.0).apply {
bind(blocksProcessed.createNonNullValueBinding { it.toDouble() / totalBlocks })
}
val progressUpdater = AnimatedProgressBarAlert(
"Export Mesh",
"Exporting Mesh",
labelProp,
progressProp
)
val exportJob = CoroutineScope(Dispatchers.IO).async {
meshExporter.exportMesh(
getBlocks,
getMesh,
ids.map { getSettings(it) }.toTypedArray(),
ids,
result.scale,
result.filePath
)
}
exportJob.invokeOnCompletion { cause ->
cause?.let {
if (it is CancellationException) {
log.info { "Export Mesh Cancelled by User" }
progressUpdater.stopAndClose()
return@invokeOnCompletion
}
log.error(it) { "Error exporting meshes" }
progressUpdater.stopAndClose()
InvokeOnJavaFXApplicationThread {
PainteraAlerts.alert(Alert.AlertType.ERROR, true).apply {
contentText = "Error exporting meshes\n${it.message}"
}.showAndWait()
}
} ?: progressUpdater.finish()
}
InvokeOnJavaFXApplicationThread {
if (exportJob.isActive) {
progressUpdater.showAndWait()
if (progressUpdater.cancelled)
meshExporter.cancel()
}

}
}

abstract class MeshInfoList<T : MeshInfo<K>, K>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.janelia.saalfeldlab.paintera.meshes.MeshInfo
import org.janelia.saalfeldlab.paintera.meshes.managed.MeshManagerWithSingleMesh
import org.janelia.saalfeldlab.paintera.meshes.ui.MeshSettingsController
import org.janelia.saalfeldlab.paintera.meshes.ui.MeshSettingsController.Companion.addGridOption
import org.janelia.saalfeldlab.paintera.meshes.ui.exportMeshWithProgressPopup
import org.janelia.saalfeldlab.paintera.ui.source.mesh.MeshExporterDialog
import org.janelia.saalfeldlab.util.Colors

Expand Down Expand Up @@ -46,19 +47,12 @@ class IntersectingSourceStatePreferencePaneNode(private val state: IntersectingS
val exportDialog = MeshExporterDialog(MeshInfo(key, manager))
val result = exportDialog.showAndWait()
if (result.isPresent) {
manager.exportMeshWithProgressPopup(result.get())
result.get().run {
if (meshExporter.isCancelled()) return@run
(meshExporter as? MeshExporterObj<*>)?.run {
exportMaterial(filePath, arrayOf(""), arrayOf(Colors.toColor(state.converter().color)))
}
meshExporter.exportMesh(
manager.getBlockListFor,
manager.getMeshFor,
manager.getSettings(key),
key,
scale,
filePath,
false
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.janelia.saalfeldlab.paintera.state

import io.github.oshai.kotlinlogging.KotlinLogging
import javafx.collections.ObservableList
import javafx.event.EventHandler
import javafx.geometry.Insets
Expand All @@ -18,14 +19,16 @@ import org.janelia.saalfeldlab.fx.extensions.TitledPaneExtensions.Companion.expa
import org.janelia.saalfeldlab.fx.extensions.TitledPaneExtensions.Companion.graphicsOnly
import org.janelia.saalfeldlab.fx.extensions.createNonNullValueBinding
import org.janelia.saalfeldlab.paintera.data.DataSource
import org.janelia.saalfeldlab.paintera.meshes.*
import org.janelia.saalfeldlab.paintera.meshes.GlobalMeshProgressState
import org.janelia.saalfeldlab.paintera.meshes.MeshExporterObj
import org.janelia.saalfeldlab.paintera.meshes.MeshInfo
import org.janelia.saalfeldlab.paintera.meshes.SegmentMeshInfoList
import org.janelia.saalfeldlab.paintera.meshes.managed.MeshManagerWithAssignmentForSegments
import org.janelia.saalfeldlab.paintera.meshes.ui.MeshSettingsController
import org.janelia.saalfeldlab.paintera.meshes.ui.exportMeshWithProgressPopup
import org.janelia.saalfeldlab.paintera.ui.PainteraAlerts
import org.janelia.saalfeldlab.paintera.ui.source.mesh.MeshExporterDialog
import org.janelia.saalfeldlab.paintera.ui.source.mesh.MeshProgressBar
import org.slf4j.LoggerFactory
import java.lang.invoke.MethodHandles

typealias TPE = TitledPaneExtensions

Expand Down Expand Up @@ -73,25 +76,19 @@ class LabelSourceStateMeshPaneNode(
val exportDialog = MeshExporterDialog(meshInfoList.meshInfos as ObservableList<MeshInfo<Long>>)
val result = exportDialog.showAndWait()
if (result.isPresent) {
manager.exportMeshWithProgressPopup(result.get())
result.get().run {
if (meshExporter.isCancelled()) return@run

val ids = meshKeys.toTypedArray()
val meshSettings = ids.map { manager.getSettings(it) }.toTypedArray()

(meshExporter as? MeshExporterObj<*>)?.run {
val colors: Array<Color> = ids.mapIndexed { idx, it ->
val color = manager.getStateFor(it)?.color ?: Color.WHITE
color.deriveColor(0.0, 1.0, 1.0, meshSettings[idx].opacity)
}.toTypedArray()
exportMaterial(filePath, ids.map { it.toString() }.toTypedArray(), colors)
}
meshExporter.exportMesh(
manager.getBlockListForSegment,
manager.getMeshForLongKey,
meshSettings,
ids,
scale,
filePath
)
}
}
}
Expand Down Expand Up @@ -131,7 +128,7 @@ class LabelSourceStateMeshPaneNode(

companion object {

private val LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass())
private val LOG = KotlinLogging.logger { }

private fun Node.asVBox() = if (this is VBox) this else VBox(this)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import org.janelia.saalfeldlab.fx.ui.ObjectField
import org.janelia.saalfeldlab.paintera.meshes.MeshExporterObj
import org.janelia.saalfeldlab.paintera.meshes.MeshInfo
import org.janelia.saalfeldlab.paintera.meshes.ui.MeshSettingsController
import org.janelia.saalfeldlab.paintera.meshes.ui.exportMeshWithProgressPopup
import org.janelia.saalfeldlab.paintera.ui.PainteraAlerts
import org.janelia.saalfeldlab.paintera.ui.source.mesh.MeshExporterDialog

Expand Down Expand Up @@ -93,19 +94,12 @@ class ThresholdingSourceStatePreferencePaneNode(private val state: ThresholdingS
val exportDialog = MeshExporterDialog(MeshInfo(state.meshManager.meshKey, state.meshManager))
val result = exportDialog.showAndWait()
if (result.isPresent) {
state.meshManager.exportMeshWithProgressPopup(result.get())
result.get().run {
if (meshExporter.isCancelled()) return@run
(meshExporter as? MeshExporterObj<*>)?.run {
exportMaterial(filePath, arrayOf(""), arrayOf(state.colorProperty().get()))
}
meshExporter.exportMesh(
state.meshManager.getBlockListFor,
state.meshManager.getMeshFor,
state.meshSettings,
state.thresholdBounds,
scale,
filePath,
false
)
}
}
}
Expand Down
Loading

0 comments on commit 86faf15

Please sign in to comment.