isManagedProperties = new HashMap<>();
private final SimpleBooleanProperty isMeshListEnabled = new SimpleBooleanProperty(DEFAULT_IS_MESH_LIST_ENABLED);
@@ -53,7 +53,18 @@ public ManagedMeshSettings(final MeshSettings globalSettings) {
this.globalSettings = globalSettings;
}
- public MeshSettings getOrAddMesh(final Long id, final boolean isManaged) {
+ /**
+ * Retrieves a MeshSettings object for the given id.
+ * If the mesh settings are managed, the existing managed settings will be returned.
+ * Otherwise, a new MeshSettings object will be created based on the global settings.
+ *
+ * The resulting MeshSettings will be added to the `individualSettings` for this `id`, either `isManaged` or not.
+ *
+ * @param id The id of the mesh to retrieve the settings for.
+ * @param isManaged True if the individual mesh settings should be used.
+ * @return The mesh settings for the given id, or a new MeshSettings object if the id is not found.
+ */
+ public MeshSettings getMeshSettings(final K id, final boolean isManaged) {
if (!isManagedProperties.containsKey(id)) {
final SimpleBooleanProperty isManagedProperty = new SimpleBooleanProperty(isManaged);
@@ -69,12 +80,25 @@ public MeshSettings getOrAddMesh(final Long id, final boolean isManaged) {
return individualSettings.get(id);
}
- public MeshSettings getOrAddMesh(final Long id) {
+ /**
+ * Retrieves a MeshSettings for the given key.
+ * If the mesh settings are managed, then the managed settings will be returned.
+ * Otherwise, a new MeshSettings object will be created based on global settings.
+ *
+ * @param meshKey the key of the mesh to retrieve the settings for
+ * @return the mesh settings for the given mesh key, or a new MeshSettings object if the key is not found
+ */
+ public MeshSettings getMeshSettings(final K meshKey) {
+
+ final var isManagedProperty = isManagedProperties.get(meshKey);
+
+ final boolean isManaged;
+ if (isManagedProperty != null)
+ isManaged = isManagedProperty.get();
+ else
+ isManaged = false;
- if (!isManagedProperties.containsKey(id)) {
- return new MeshSettings(globalSettings.getNumScaleLevels());
- }
- return individualSettings.get(id);
+ return getMeshSettings(meshKey, isManaged);
}
public MeshSettings getGlobalSettings() {
@@ -82,12 +106,18 @@ public MeshSettings getGlobalSettings() {
return globalSettings;
}
- public boolean isPresent(final Long t) {
+ /**
+ * Checks if the given mesh key is managed.
+ *
+ * @param meshKey the mesh key
+ * @return true if the mesh key is managed, false otherwise
+ */
+ public boolean isManaged(final K meshKey) {
- return this.isManagedProperties.containsKey(t);
+ return this.isManagedProperties.containsKey(meshKey);
}
- public BooleanProperty isManagedProperty(final Long t) {
+ public BooleanProperty isManagedProperty(final K t) {
return this.isManagedProperties.get(t);
}
@@ -108,9 +138,9 @@ public void clearSettings() {
this.individualSettings.clear();
}
- public void keepOnlyMatching(final Predicate filter) {
+ public void keepOnlyMatching(final Predicate filter) {
- final Set toBeRemoved = isManagedProperties
+ final Set toBeRemoved = isManagedProperties
.keySet()
.stream()
.filter(filter.negate())
@@ -125,16 +155,16 @@ public static Serializer jsonSerializer() {
return new Serializer();
}
- public void set(final ManagedMeshSettings that) {
+ public void set(final ManagedMeshSettings that) {
clearSettings();
globalSettings.setTo(that.globalSettings);
isMeshListEnabled.set(that.isMeshListEnabled.get());
meshesEnabled.set(that.meshesEnabled.get());
- for (final Entry entry : that.individualSettings.entrySet()) {
- final Long id = entry.getKey();
+ for (final Entry entry : that.individualSettings.entrySet()) {
+ final K id = entry.getKey();
final boolean isManaged = that.isManagedProperties.get(id).get();
- this.getOrAddMesh(id, isManaged);
+ this.getMeshSettings(id, isManaged);
if (!isManaged)
this.individualSettings.get(id).setTo(entry.getValue());
}
@@ -188,10 +218,12 @@ public ManagedMeshSettings deserialize(
if (!settingsMap.has(ID_KEY)) {
continue;
}
+ //TODO Caleb: This needs to store and deserialize based on generic key type, not just Long
+ // Necessary to share logic for meshes across segment and virtual sources
final Long id = context.deserialize(settingsMap.get(ID_KEY), Long.class);
final MeshSettings settings = Optional
.ofNullable(settingsMap.get(SETTINGS_KEY))
- .map(el -> (MeshSettings)context.deserialize(el, MeshSettings.class))
+ .map(el -> (MeshSettings) context.deserialize(el, MeshSettings.class))
.orElseGet(globalSettings::copy);
final boolean isManaged = Optional
.ofNullable(settingsMap.get(IS_MANAGED_KEY))
@@ -199,13 +231,13 @@ public ManagedMeshSettings deserialize(
.orElse(true);
LOG.debug("{} is managed? {}", id, isManaged);
if (!isManaged)
- managedSettings.getOrAddMesh(id, false).setTo(settings);
+ managedSettings.getMeshSettings(id, false).setTo(settings);
else
- managedSettings.getOrAddMesh(id, true);
+ managedSettings.getMeshSettings(id, true);
}
return managedSettings;
} catch (final Exception e) {
- throw e instanceof JsonParseException ? (JsonParseException)e : new JsonParseException(e);
+ throw e instanceof JsonParseException ? (JsonParseException) e : new JsonParseException(e);
}
}
@@ -215,19 +247,22 @@ public JsonElement serialize(
final Type typeOfSrc,
final JsonSerializationContext context) {
+ //TODO Caleb: This also needs to serialize the generic type, instead of casting to Object
+ ManagedMeshSettings managedMeshSettings = (ManagedMeshSettings) src;
+
final JsonObject map = new JsonObject();
- map.add(GLOBAL_SETTINGS_KEY, context.serialize(src.globalSettings));
+ map.add(GLOBAL_SETTINGS_KEY, context.serialize(managedMeshSettings.globalSettings));
- if (DEFAULT_IS_MESH_LIST_ENABLED != src.isMeshListEnabledProperty().get())
- map.addProperty(IS_MESH_LIST_ENABLED_KEY, src.isMeshListEnabledProperty().get());
+ if (DEFAULT_IS_MESH_LIST_ENABLED != managedMeshSettings.isMeshListEnabledProperty().get())
+ map.addProperty(IS_MESH_LIST_ENABLED_KEY, managedMeshSettings.isMeshListEnabledProperty().get());
- if (DEFAULT_ARE_MESHES_ENABLED != src.getMeshesEnabledProperty().get())
- map.addProperty(MESHES_ENABLED_KEY, src.getMeshesEnabledProperty().get());
+ if (DEFAULT_ARE_MESHES_ENABLED != managedMeshSettings.getMeshesEnabledProperty().get())
+ map.addProperty(MESHES_ENABLED_KEY, managedMeshSettings.getMeshesEnabledProperty().get());
final JsonArray meshSettingsList = new JsonArray();
- for (final Entry entry : src.individualSettings.entrySet()) {
- final Long id = entry.getKey();
- final Boolean isManaged = Optional.ofNullable(src.isManagedProperty(id)).map(BooleanProperty::get).orElse(true);
+ for (final Entry, MeshSettings> entry : managedMeshSettings.individualSettings.entrySet()) {
+ final Object id = entry.getKey();
+ final Boolean isManaged = Optional.ofNullable(managedMeshSettings.isManagedProperty(id)).map(BooleanProperty::get).orElse(true);
if (!isManaged) {
final JsonObject settingsMap = new JsonObject();
settingsMap.addProperty(IS_MANAGED_KEY, false);
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MarchingCubes.java b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MarchingCubes.java
index 8a3d009cd..3f35459cf 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MarchingCubes.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MarchingCubes.java
@@ -1,18 +1,14 @@
package org.janelia.saalfeldlab.paintera.meshes;
import gnu.trove.list.array.TFloatArrayList;
-import net.imglib2.FinalInterval;
import net.imglib2.Interval;
-import net.imglib2.RandomAccess;
import net.imglib2.RandomAccessible;
import net.imglib2.loops.LoopBuilder;
import net.imglib2.type.BooleanType;
-import net.imglib2.util.Intervals;
-import paintera.net.imglib2.view.BundleView;
-import net.imglib2.view.IntervalView;
import net.imglib2.view.Views;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import paintera.net.imglib2.view.BundleView;
/**
* This class implements the marching cubes algorithm. Based on http://paulbourke.net/geometry/polygonise/
@@ -381,13 +377,6 @@ public float[] generateMesh() {
// This way, we need to remap the cube vertices.
// @formatter:on
- final FinalInterval expandedInterval = new FinalInterval(
- new long[]{
- interval.min(0) - 1,
- interval.min(1) - 1,
- interval.min(2) - 1},
- Intervals.maxAsLongArray(interval));
-
final TFloatArrayList vertices = new TFloatArrayList();
final float[][] interpolationPoints = new float[12][3];
@@ -395,53 +384,52 @@ public float[] generateMesh() {
final int[] idxRemap = new int[]{5, 7, 3, 1, 4, 6, 2, 0};
final long[] prevPos = new long[]{Long.MIN_VALUE, 0, 0};
final long[] curPos = new long[3];
- final BundleView bundledInput = new BundleView<>(Views.interval(input, interval));
- final IntervalView> expandedBundle = Views.interval(bundledInput, expandedInterval);
-
- LoopBuilder.setImages(expandedBundle).forEachPixel(pixelAccess -> {
- pixelAccess.localize(curPos);
- if (prevPos[0] != Long.MIN_VALUE && curPos[0] - prevPos[0] == 1 && curPos[1] - prevPos[1] == 0 && curPos[2] - prevPos[2] == 0) {
- vertexVals[0] = vertexVals[1];
- vertexVals[2] = vertexVals[3];
- vertexVals[4] = vertexVals[5];
- vertexVals[6] = vertexVals[7];
- } else {
- vertexVals[0] = pixelAccess.get().get();
- pixelAccess.move(1, 1); //move to offset (0, 1, 0)
- vertexVals[2] = pixelAccess.get().get();
- pixelAccess.move(1, 2); //move to offset (0, 1, 1)
- vertexVals[6] = pixelAccess.get().get();
- pixelAccess.move(-1, 1); //move to offset (0, 0, 1)
- vertexVals[4] = pixelAccess.get().get();
- pixelAccess.move(-1, 2);
- }
- pixelAccess.move(1, 0); //move to offset (1, 0, 0)
- vertexVals[1] = pixelAccess.get().get();
- pixelAccess.move(1, 1); //move to offset (1, 1, 0)
- vertexVals[3] = pixelAccess.get().get();
- pixelAccess.move(1, 2); //move to offset (1, 1, 1)
- vertexVals[7] = pixelAccess.get().get();
- pixelAccess.move(-1, 1); //move to offset (1, 0, 1)
- vertexVals[5] = pixelAccess.get().get();
-
- /* setup for next loop*/
- pixelAccess.setPosition(curPos);
- System.arraycopy(curPos, 0, prevPos, 0, 3);
-
- var vertexIn = 0b00000000;
- for (int i = 0; i < vertexVals.length; i++) {
- vertexIn |= (vertexVals[idxRemap[i]] ? 1 : 0) << i;
- }
- triangulation(
- vertexIn,
- curPos[0], //cursor0.getLongPosition(0),
- curPos[1], //cursor0.getLongPosition(1),
- curPos[2], //cursor0.getLongPosition(2),
- vertices,
- interpolationPoints
- );
- });
+ LoopBuilder.setImages(Views.interval(new BundleView<>(input), interval))
+ .forEachPixel(pixelAccess -> {
+ pixelAccess.localize(curPos);
+ if (prevPos[0] != Long.MIN_VALUE && curPos[0] - prevPos[0] == 1 && curPos[1] - prevPos[1] == 0 && curPos[2] - prevPos[2] == 0) {
+ vertexVals[0] = vertexVals[1];
+ vertexVals[2] = vertexVals[3];
+ vertexVals[4] = vertexVals[5];
+ vertexVals[6] = vertexVals[7];
+ } else {
+ vertexVals[0] = pixelAccess.get().get();
+ pixelAccess.move(1, 1); //move to offset (0, 1, 0)
+ vertexVals[2] = pixelAccess.get().get();
+ pixelAccess.move(1, 2); //move to offset (0, 1, 1)
+ vertexVals[6] = pixelAccess.get().get();
+ pixelAccess.move(-1, 1); //move to offset (0, 0, 1)
+ vertexVals[4] = pixelAccess.get().get();
+ pixelAccess.move(-1, 2);
+ }
+ pixelAccess.move(1, 0); //move to offset (1, 0, 0)
+ vertexVals[1] = pixelAccess.get().get();
+ pixelAccess.move(1, 1); //move to offset (1, 1, 0)
+ vertexVals[3] = pixelAccess.get().get();
+ pixelAccess.move(1, 2); //move to offset (1, 1, 1)
+ vertexVals[7] = pixelAccess.get().get();
+ pixelAccess.move(-1, 1); //move to offset (1, 0, 1)
+ vertexVals[5] = pixelAccess.get().get();
+
+ /* setup for next loop*/
+ pixelAccess.setPosition(curPos);
+ System.arraycopy(curPos, 0, prevPos, 0, 3);
+
+ var vertexIn = 0b00000000;
+ for (int i = 0; i < vertexVals.length; i++) {
+ vertexIn |= (vertexVals[idxRemap[i]] ? 1 : 0) << i;
+ }
+
+ triangulation(
+ vertexIn,
+ curPos[0],
+ curPos[1],
+ curPos[2],
+ vertices,
+ interpolationPoints
+ );
+ });
return vertices.toArray();
}
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/Mesh.java b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/Mesh.java
index 559c4bf6c..a937135b4 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/Mesh.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/Mesh.java
@@ -91,6 +91,10 @@ public FloatLocalizable(float[] position) {
* Of the form [ t1_v0_x, t1_v1_y, t1_v2_z, t2_v0_x, t2_v1_y, t2_v2_z, ..., tn_v0_x, tn_v1_y, tn_v2_z]
*/
public Mesh(final float[] flatTrianglesAndVertices, final Interval interval, final AffineTransform3D transform) {
+ this(flatTrianglesAndVertices, interval, transform, true);
+ }
+
+ public Mesh(final float[] flatTrianglesAndVertices, final Interval interval, final AffineTransform3D transform, final boolean overlap) {
assert flatTrianglesAndVertices.length % 9 == 0;
@@ -103,9 +107,10 @@ public Mesh(final float[] flatTrianglesAndVertices, final Interval interval, fin
final double minX = interval.min(0) - 1;
final double minZ = interval.min(2) - 1;
- final double maxX = interval.max(0) + 1; // overlap 1
- final double maxY = interval.max(1) + 1; // overlap 1
- final double maxZ = interval.max(2) + 1; // overlap 1
+ final int overlapOffset = overlap ? 1 : 0;
+ final double maxX = interval.max(0) + overlapOffset;
+ final double maxY = interval.max(1) + overlapOffset;
+ final double maxZ = interval.max(2) + overlapOffset;
final RealInterval vertexBounds = new FinalRealInterval(
new double[]{minX, minY, minZ},
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshExporter.java b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshExporter.java
index 4d2c12490..b688ab759 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshExporter.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshExporter.java
@@ -62,6 +62,7 @@ public void exportMesh(
meshSettings.getSmoothingLambda(),
meshSettings.getSmoothingIterations(),
meshSettings.getMinLabelRatio(),
+ meshSettings.getOverlap(),
Intervals.minAsLongArray(block),
Intervals.maxAsLongArray(block)
));
@@ -94,4 +95,7 @@ public void exportMesh(
protected abstract void save(String path, String id, float[] vertices, float[] normals, int[] indices, boolean append) throws IOException;
+ public enum MeshFileFormat {
+ Obj, Binary;
+ }
}
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshExporterObj.java b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshExporterObj.java
index 13fa81dd2..e81d99c68 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshExporterObj.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshExporterObj.java
@@ -15,12 +15,12 @@ public class MeshExporterObj extends MeshExporter {
private long numVertices = 0;
- public void exportMaterial(String path, final long[] ids, final Color[] colors) {
+ public void exportMaterial(String path, final String[] ids, final Color[] colors) {
final String materialPath = path + ".mtl";
try (final FileWriter writer = new FileWriter(materialPath, false)) {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < ids.length; i++) {
- final long id = ids[i];
+ final String id = ids[i];
final Color specularColor = colors[i];
final double red = specularColor.getRed();
final double green = specularColor.getGreen();
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshGenerator.java b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshGenerator.java
index df908c0d9..9df465c3a 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshGenerator.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshGenerator.java
@@ -93,7 +93,6 @@ public void setShowBlockBoundaries(final boolean isShowBlockBoundaries) {
showBlockBoundaries.set(isShowBlockBoundaries);
}
-
}
private static class SceneUpdateParameters {
@@ -104,6 +103,7 @@ private static class SceneUpdateParameters {
final double smoothingLambda;
final int smoothingIterations;
final double minLabelRatio;
+ final boolean overlap;
SceneUpdateParameters(
final BlockTree> sceneBlockTree,
@@ -111,7 +111,8 @@ private static class SceneUpdateParameters {
final int meshSimplificationIterations,
final double smoothingLambda,
final int smoothingIterations,
- final double minLabelRatio) {
+ final double minLabelRatio,
+ final boolean overlap) {
this.sceneBlockTree = sceneBlockTree;
this.rendererGrids = rendererGrids;
@@ -119,6 +120,7 @@ private static class SceneUpdateParameters {
this.smoothingLambda = smoothingLambda;
this.smoothingIterations = smoothingIterations;
this.minLabelRatio = minLabelRatio;
+ this.overlap = overlap;
}
}
@@ -186,10 +188,11 @@ public MeshGenerator(
sceneUpdateParameters = new SceneUpdateParameters(
sceneUpdateParameters != null ? sceneUpdateParameters.sceneBlockTree : null,
sceneUpdateParameters != null ? sceneUpdateParameters.rendererGrids : null,
- this.state.settings.getSimplificationIterations(),// this.meshSimplificationIterations.get(),
- this.state.settings.getSmoothingLambda(),//this.smoothingLambda.get(),
- this.state.settings.getSmoothingIterations(),//this.smoothingIterations.get(),
- this.state.settings.getMinLabelRatio()//this.minLabelRatio.get()
+ this.state.settings.getSimplificationIterations(),
+ this.state.settings.getSmoothingLambda(),
+ this.state.settings.getSmoothingIterations(),
+ this.state.settings.getMinLabelRatio(),
+ this.state.settings.getOverlap()
);
updateMeshes();
}
@@ -199,6 +202,7 @@ public MeshGenerator(
this.state.settings.getSmoothingLambdaProperty().addListener(updateInvalidationListener);
this.state.settings.getSmoothingIterationsProperty().addListener(updateInvalidationListener);
this.state.settings.getMinLabelRatioProperty().addListener(updateInvalidationListener);
+ this.state.settings.getOverlapProperty().addListener(updateInvalidationListener);
// initialize
updateInvalidationListener.invalidated(null);
@@ -232,12 +236,12 @@ public MeshGenerator(
workers,
state.progress);
- this.meshesAndBlocks.addListener((MapChangeListener, Pair>)change ->
+ this.meshesAndBlocks.addListener((MapChangeListener, Pair>) change ->
{
if (change.wasRemoved()) {
if (change.getValueRemoved().getA() != null) {
final MeshView meshRemoved = change.getValueRemoved().getA();
- ((PhongMaterial)meshRemoved.getMaterial()).diffuseColorProperty().unbind();
+ ((PhongMaterial) meshRemoved.getMaterial()).diffuseColorProperty().unbind();
meshRemoved.drawModeProperty().unbind();
meshRemoved.cullFaceProperty().unbind();
meshRemoved.scaleXProperty().unbind();
@@ -249,13 +253,13 @@ public MeshGenerator(
final Node blockOutlineRemoved = change.getValueRemoved().getB();
final Material material;
if (blockOutlineRemoved instanceof PolygonMeshView)
- material = ((PolygonMeshView)blockOutlineRemoved).getMaterial();
+ material = ((PolygonMeshView) blockOutlineRemoved).getMaterial();
else if (blockOutlineRemoved instanceof Shape3D)
- material = ((Shape3D)blockOutlineRemoved).getMaterial();
+ material = ((Shape3D) blockOutlineRemoved).getMaterial();
else
material = null;
if (material instanceof PhongMaterial)
- ((PhongMaterial)material).diffuseColorProperty().unbind();
+ ((PhongMaterial) material).diffuseColorProperty().unbind();
blockOutlineRemoved.scaleXProperty().unbind();
blockOutlineRemoved.scaleYProperty().unbind();
blockOutlineRemoved.scaleZProperty().unbind();
@@ -265,7 +269,7 @@ else if (blockOutlineRemoved instanceof Shape3D)
if (change.wasAdded()) {
if (change.getValueAdded().getA() != null) {
final MeshView meshAdded = change.getValueAdded().getA();
- ((PhongMaterial)meshAdded.getMaterial()).diffuseColorProperty().bind(this.state.premultipliedColor);
+ ((PhongMaterial) meshAdded.getMaterial()).diffuseColorProperty().bind(this.state.premultipliedColor);
meshAdded.drawModeProperty().bind(this.state.settings.getDrawModeProperty());
meshAdded.cullFaceProperty().bind(this.state.settings.getCullFaceProperty());
}
@@ -274,13 +278,13 @@ else if (blockOutlineRemoved instanceof Shape3D)
final Node blockOutlineAdded = change.getValueAdded().getB();
final Material material;
if (blockOutlineAdded instanceof PolygonMeshView)
- material = ((PolygonMeshView)blockOutlineAdded).getMaterial();
+ material = ((PolygonMeshView) blockOutlineAdded).getMaterial();
else if (blockOutlineAdded instanceof Shape3D)
- material = ((Shape3D)blockOutlineAdded).getMaterial();
+ material = ((Shape3D) blockOutlineAdded).getMaterial();
else
material = null;
if (material instanceof PhongMaterial)
- ((PhongMaterial)material).diffuseColorProperty().bind(this.state.premultipliedColor);
+ ((PhongMaterial) material).diffuseColorProperty().bind(this.state.premultipliedColor);
blockOutlineAdded.setDisable(true);
}
}
@@ -306,7 +310,8 @@ public synchronized void update(final BlockTree> sceneBlockTree,
@@ -78,7 +79,8 @@ private static final class SceneUpdateParameters {
final int simplificationIterations,
final double smoothingLambda,
final int smoothingIterations,
- final double minLabelRatio) {
+ final double minLabelRatio,
+ final boolean overlap) {
this.sceneBlockTree = sceneBlockTree;
this.rendererGrids = rendererGrids;
@@ -86,6 +88,7 @@ private static final class SceneUpdateParameters {
this.smoothingLambda = smoothingLambda;
this.smoothingIterations = smoothingIterations;
this.minLabelRatio = minLabelRatio;
+ this.overlap = overlap;
}
@Override
@@ -95,7 +98,7 @@ public boolean equals(final Object obj) {
return true;
if (obj instanceof SceneUpdateParameters) {
- final SceneUpdateParameters other = (SceneUpdateParameters)obj;
+ final SceneUpdateParameters other = (SceneUpdateParameters) obj;
if (rendererGrids.length != other.rendererGrids.length)
return false;
@@ -104,8 +107,8 @@ public boolean equals(final Object obj) {
for (int i = 0; i < rendererGrids.length; ++i) {
//noinspection RedundantCast
sameBlockSize &= Intervals.equalDimensions(
- (Dimensions)Grids.getCellInterval(rendererGrids[i], 0),
- (Dimensions)Grids.getCellInterval(other.rendererGrids[i], 0));
+ (Dimensions) Grids.getCellInterval(rendererGrids[i], 0),
+ (Dimensions) Grids.getCellInterval(other.rendererGrids[i], 0));
}
return
@@ -113,7 +116,8 @@ public boolean equals(final Object obj) {
simplificationIterations == other.simplificationIterations &&
smoothingLambda == other.smoothingLambda &&
smoothingIterations == other.smoothingIterations &&
- minLabelRatio == other.minLabelRatio;
+ minLabelRatio == other.minLabelRatio &&
+ overlap == other.overlap;
}
return false;
@@ -294,7 +298,8 @@ public void submit(
final int simplificationIterations,
final double smoothingLambda,
final int smoothingIterations,
- final double minLabelRatio) {
+ final double minLabelRatio,
+ final boolean overlap) {
if (isInterrupted.get())
return;
@@ -305,7 +310,8 @@ public void submit(
simplificationIterations,
smoothingLambda,
smoothingIterations,
- minLabelRatio
+ minLabelRatio,
+ overlap
);
synchronized (sceneUpdateParametersProperty) {
@@ -398,7 +404,7 @@ private synchronized void updateScene() {
// calculate how many tasks are already completed
final int numCompletedBlocks = numTotalBlocks - blocksToRender.size() - tasks.size();
meshProgress.set(numTotalBlocks, numCompletedBlocks);
- final int numExistingNonEmptyMeshes = (int)meshesAndBlocks.values().stream().filter(pair -> pair.getA() != null).count();
+ final int numExistingNonEmptyMeshes = (int) meshesAndBlocks.values().stream().filter(pair -> pair.getA() != null).count();
LOG.debug("ID {}: numTasks={}, numCompletedTasks={}, numActualBlocksToRender={}. Number of meshes in the scene: {} ({} of them are non-empty)",
identifier,
numTotalBlocks, numCompletedBlocks, blocksToRender.size(), meshesAndBlocks.size(), numExistingNonEmptyMeshes);
@@ -611,11 +617,11 @@ private synchronized void onMeshGenerated(final ShapeKey key, final PainteraT
"Mesh for block has been generated but it already exists in the current set of generated/visible meshes: " + key;
LOG.trace("ID {}: block {} has been generated", identifier, key);
- final boolean nonEmptyMesh = triangleMesh.isNotEmpty();
- final MeshView mv = nonEmptyMesh ? makeMeshView(triangleMesh) : null;
- final Node blockShape = nonEmptyMesh ? createBlockShape(key) : null;
+ final boolean meshIsEmpty = triangleMesh.isEmpty();
+ final MeshView mv = meshIsEmpty ? null : makeMeshView(triangleMesh);
+ final Node blockShape = meshIsEmpty ? null : createBlockShape(key);
final Pair meshAndBlock = new ValuePair<>(mv, blockShape);
- LOG.trace("Found {}/3 vertices and {}/3 normals", triangleMesh.getVertices(), triangleMesh.getNormals());
+ LOG.trace("Found {}/3 vertices and {}/3 normals", triangleMesh.getVertices().length, triangleMesh.getNormals().length);
final StatefulBlockTreeNode> treeNode = blockTree.nodes.get(key);
treeNode.state = BlockTreeNodeState.RENDERED;
@@ -1015,6 +1021,7 @@ private ShapeKey createShapeKey(
sceneUpdateParameters.smoothingLambda,
sceneUpdateParameters.smoothingIterations,
sceneUpdateParameters.minLabelRatio,
+ sceneUpdateParameters.overlap,
Intervals.minAsLongArray(blockInterval),
Intervals.maxAsLongArray(blockInterval)
);
@@ -1071,9 +1078,9 @@ private Node createBlockShape(final ShapeKey key) {
// blockWorldSize[2]
// );
final PolygonMeshView box = new PolygonMeshView(Meshes.createQuadrilateralMesh(
- (float)blockWorldSize[0],
- (float)blockWorldSize[1],
- (float)blockWorldSize[2]
+ (float) blockWorldSize[0],
+ (float) blockWorldSize[1],
+ (float) blockWorldSize[2]
));
final double[] blockWorldTranslation = new double[blockWorldInterval.numDimensions()];
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshInfo.java b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshInfo.java
deleted file mode 100644
index c420fb0e9..000000000
--- a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/MeshInfo.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.janelia.saalfeldlab.paintera.meshes;
-
-import org.janelia.saalfeldlab.paintera.meshes.managed.MeshManagerWithSingleMesh;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.lang.invoke.MethodHandles;
-
-public class MeshInfo {
-
- private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
- private final MeshManagerWithSingleMesh meshManager;
-
- public MeshInfo(final MeshManagerWithSingleMesh meshManager) {
-
- this.meshManager = meshManager;
- }
-
- public MeshSettings getMeshSettings() {
-
- return meshManager.getSettings();
- }
-
- public MeshManagerWithSingleMesh meshManager() {
-
- return this.meshManager;
- }
-
- public T getKey() {
-
- return this.meshManager.getMeshKey();
- }
-}
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/SegmentMeshInfo.java b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/SegmentMeshInfo.java
deleted file mode 100644
index aa943cc4e..000000000
--- a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/SegmentMeshInfo.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package org.janelia.saalfeldlab.paintera.meshes;
-
-import gnu.trove.set.hash.TLongHashSet;
-import javafx.beans.property.BooleanProperty;
-import org.janelia.saalfeldlab.paintera.control.assignment.FragmentSegmentAssignment;
-import org.janelia.saalfeldlab.paintera.meshes.managed.MeshManagerWithAssignmentForSegments;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.lang.invoke.MethodHandles;
-import java.util.Objects;
-
-public class SegmentMeshInfo {
-
- private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
- private final Long segmentId;
-
- private final MeshSettings meshSettings;
-
- private final FragmentSegmentAssignment assignment;
-
- private final MeshManagerWithAssignmentForSegments meshManager;
-
- private final ObservableMeshProgress meshProgress;
-
- private final BooleanProperty isManaged;
-
- public SegmentMeshInfo(
- final Long segmentId,
- final MeshSettings meshSettings,
- final BooleanProperty isManaged,
- final FragmentSegmentAssignment assignment,
- final MeshManagerWithAssignmentForSegments meshManager) {
-
- this.segmentId = segmentId;
- this.meshSettings = meshSettings;
- this.isManaged = isManaged;
- this.assignment = assignment;
- this.meshManager = meshManager;
-
- final MeshGenerator.State meshGeneratorState = meshManager.getStateFor(segmentId);
- this.meshProgress = meshGeneratorState == null ? null : meshGeneratorState.getProgress();
- }
-
- public Long segmentId() {
-
- return this.segmentId;
- }
-
- public MeshSettings getMeshSettings() {
-
- return this.meshSettings;
- }
-
- @Override
- public int hashCode() {
-
- return segmentId.hashCode();
- }
-
- @Override
- public boolean equals(final Object o) {
-
- return o instanceof SegmentMeshInfo && Objects.equals(((SegmentMeshInfo)o).segmentId, segmentId);
- }
-
- public ObservableMeshProgress meshProgress() {
-
- return this.meshProgress;
- }
-
- public MeshManagerWithAssignmentForSegments meshManager() {
-
- return this.meshManager;
- }
-
- public long[] containedFragments() {
-
- final TLongHashSet fragments = meshManager.getContainedFragmentsFor(segmentId);
- return fragments == null ? new long[]{} : fragments.toArray();
- }
-
- public BooleanProperty isManagedProperty() {
-
- return this.isManaged;
- }
-
-}
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/SegmentMeshInfos.java b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/SegmentMeshInfos.java
deleted file mode 100644
index 8604e5f7e..000000000
--- a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/SegmentMeshInfos.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package org.janelia.saalfeldlab.paintera.meshes;
-
-import javafx.beans.InvalidationListener;
-import javafx.beans.property.ReadOnlyListProperty;
-import javafx.beans.property.ReadOnlyListWrapper;
-import javafx.collections.FXCollections;
-import javafx.collections.ObservableList;
-import javafx.scene.paint.Color;
-import org.janelia.saalfeldlab.paintera.control.selection.SelectedSegments;
-import org.janelia.saalfeldlab.paintera.meshes.managed.MeshManagerWithAssignmentForSegments;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-public class SegmentMeshInfos {
-
- private final ReadOnlyListWrapper readOnlyInfos = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
- private final ManagedMeshSettings meshSettings;
-
- private final int numScaleLevels;
-
- public SegmentMeshInfos(
- final SelectedSegments selectedSegments,
- final MeshManagerWithAssignmentForSegments meshManager,
- final ManagedMeshSettings meshSettings,
- final int numScaleLevels) {
-
- super();
-
- this.meshSettings = meshSettings;
- this.numScaleLevels = numScaleLevels;
-
- final InvalidationListener updateMeshInfosHandler = obs -> {
- final long[] segments = selectedSegments.getSelectedSegmentsCopyAsArray();
- this.infos().clear();
- final var addInfos = new ArrayList();
- Arrays.stream(segments).forEach(id -> {
- final MeshSettings settings = meshSettings.getOrAddMesh(id, true);
- addInfos.add(new SegmentMeshInfo(
- id,
- settings,
- meshSettings.isManagedProperty(id),
- selectedSegments.getAssignment(),
- meshManager));
- });
- this.infos().addAll(addInfos);
- };
-
- meshManager.getMeshUpdateObservable().addListener(updateMeshInfosHandler);
- meshSettings.isMeshListEnabledProperty().addListener(updateMeshInfosHandler);
- }
-
- public Color getColor(long id) {
- return readOnlyInfos.stream()
- .filter(it -> it.segmentId() == id)
- .map(it -> it.meshManager().getStateFor(it.segmentId()).getColor())
- .findFirst()
- .orElse(Color.WHITE);
- }
-
- private ObservableList infos() {
-
- return readOnlyInfos.get();
- }
-
- public ReadOnlyListProperty readOnlyInfos() {
-
- return this.readOnlyInfos.getReadOnlyProperty();
- }
-
- public ManagedMeshSettings meshSettings() {
-
- return meshSettings;
- }
-
- public int getNumScaleLevels() {
-
- return this.numScaleLevels;
- }
-}
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/ShapeKey.java b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/ShapeKey.java
index 486a94318..1ff219c76 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/ShapeKey.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/ShapeKey.java
@@ -22,6 +22,8 @@ public class ShapeKey {
private final double minLabelRatio;
+ private final boolean overlap;
+
private final long[] min;
private final long[] max;
@@ -37,6 +39,7 @@ public ShapeKey(
final double smoothingLambda,
final int smoothingIterations,
final double minLabelRatio,
+ final boolean overlap,
final long[] min,
final long[] max) {
@@ -47,6 +50,7 @@ public ShapeKey(
smoothingLambda,
smoothingIterations,
minLabelRatio,
+ overlap,
min,
max,
Objects::hashCode,
@@ -60,6 +64,7 @@ public ShapeKey(
final double smoothingLambda,
final int smoothingIterations,
final double minLabelRatio,
+ final boolean overlap,
final long[] min,
final long[] max,
final ToIntFunction shapeIdHashCode,
@@ -71,6 +76,7 @@ public ShapeKey(
this.smoothingLambda = smoothingLambda;
this.smoothingIterations = smoothingIterations;
this.minLabelRatio = minLabelRatio;
+ this.overlap = overlap;
this.min = min;
this.max = max;
this.shapeIdHashCode = shapeIdHashCode;
@@ -81,13 +87,14 @@ public ShapeKey(
public String toString() {
return String.format(
- "{shapeId=%s, scaleIndex=%d, simplifications=%d, smoothingLambda=%.2f, smoothings=%d, minLabelRatio=%.2f, min=%s, max=%s}",
+ "{shapeId=%s, scaleIndex=%d, simplifications=%d, smoothingLambda=%.2f, smoothings=%d, minLabelRatio=%.2f, overlap=%b, min=%s, max=%s}",
shapeId,
scaleIndex,
simplificationIterations,
smoothingLambda,
smoothingIterations,
minLabelRatio,
+ overlap,
Arrays.toString(min),
Arrays.toString(max));
}
@@ -102,6 +109,7 @@ public int hashCode() {
result = 31 * result + Double.hashCode(smoothingLambda);
result = 31 * result + smoothingIterations;
result = 31 * result + Double.hashCode(minLabelRatio);
+ result = 31 * result + Boolean.hashCode(overlap);
result = 31 * result + Arrays.hashCode(this.min);
result = 31 * result + Arrays.hashCode(this.max);
return result;
@@ -122,6 +130,7 @@ public boolean equals(final Object obj) {
smoothingLambda == other.smoothingLambda &&
smoothingIterations == other.smoothingIterations &&
minLabelRatio == other.minLabelRatio &&
+ overlap == other.overlap &&
Arrays.equals(min, other.min) &&
Arrays.equals(max, other.max);
}
@@ -184,4 +193,8 @@ public Interval interval() {
return new FinalInterval(min, max);
}
+ public boolean overlap() {
+ return overlap;
+ }
+
}
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/AbstractMeshCacheLoader.java b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/AbstractMeshCacheLoader.java
index 034d0ed66..4d8960d0f 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/AbstractMeshCacheLoader.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/AbstractMeshCacheLoader.java
@@ -64,7 +64,7 @@ public PainteraTriangleMesh get(final ShapeKey key) throws Exception {
Intervals.expand(key.interval(), smoothingIterations + 2)
).generateMesh();
- Mesh meshMesh = new Mesh(vertices, key.interval(), transform);
+ Mesh meshMesh = new Mesh(vertices, key.interval(), transform, key.overlap());
if (smoothingIterations > 0)
meshMesh.smooth(key.smoothingLambda(), smoothingIterations);
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/GenericMeshCacheLoader.java b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/GenericMeshCacheLoader.java
index ea43bf93f..6d2ff6a91 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/GenericMeshCacheLoader.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/GenericMeshCacheLoader.java
@@ -32,7 +32,6 @@ public GenericMeshCacheLoader(
LOG.debug("Constructing {}", getClass().getName());
this.data = data;
this.transform = transform;
- // this.containedLabelsInBlock = containedLabelsInBlock;
}
@Override
@@ -49,7 +48,7 @@ public PainteraTriangleMesh get(final ShapeKey key) throws Exception {
Intervals.expand(key.interval(), smoothingIterations + 2)
).generateMesh();
- final Mesh meshMesh = new Mesh(vertices, key.interval(), transform);
+ final Mesh meshMesh = new Mesh(vertices, key.interval(), transform, key.overlap());
if (smoothingIterations > 0)
meshMesh.smooth(key.smoothingLambda(), smoothingIterations);
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/SegmentMaskGenerators.java b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/SegmentMaskGenerators.java
index 42c374414..fa38c1a15 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/SegmentMaskGenerators.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/SegmentMaskGenerators.java
@@ -29,18 +29,22 @@ public class SegmentMaskGenerators {
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- public static > BiFunction> create(
+ public static > BiFunction> create(
final DataSource source,
- final int level) {
+ final int level,
+ final Function fragmentsForSegment
+ ) {
final T t = source.getDataType();
- if (t instanceof LabelMultisetType)
- return new LabelMultisetTypeMaskGenerator(source, level);
+ if (t instanceof LabelMultisetType) {
+ final LabelMultisetTypeMaskGenerator labelMultisetTypeMaskGenerator = new LabelMultisetTypeMaskGenerator(source, level);
+ return (l, minLabelRatio) -> labelMultisetTypeMaskGenerator.apply(fragmentsForSegment.apply(l), minLabelRatio);
+ }
if (t instanceof IntegerType>) {
final IntegerTypeMaskGenerator integerTypeMaskGenerator = new IntegerTypeMaskGenerator();
- return (l, minLabelRatio) -> integerTypeMaskGenerator.apply(l);
+ return (l, minLabelRatio) -> integerTypeMaskGenerator.apply(fragmentsForSegment.apply(l));
}
return null;
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/SegmentMeshCacheLoader.java b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/SegmentMeshCacheLoader.java
index a47c2ebdc..613590a03 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/SegmentMeshCacheLoader.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/meshes/cache/SegmentMeshCacheLoader.java
@@ -9,11 +9,11 @@
import java.util.function.BiFunction;
import java.util.function.Supplier;
-public class SegmentMeshCacheLoader extends AbstractMeshCacheLoader {
+public class SegmentMeshCacheLoader extends AbstractMeshCacheLoader {
public SegmentMeshCacheLoader(
final Supplier> data,
- final BiFunction> getMaskGenerator,
+ final BiFunction> getMaskGenerator,
final AffineTransform3D transform) {
super(data, getMaskGenerator, transform);
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/serialization/config/MeshSettingsSerializer.java b/src/main/java/org/janelia/saalfeldlab/paintera/serialization/config/MeshSettingsSerializer.java
index f3ee04180..1f13226ba 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/serialization/config/MeshSettingsSerializer.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/serialization/config/MeshSettingsSerializer.java
@@ -33,6 +33,8 @@ public class MeshSettingsSerializer implements PainteraSerialization.PainteraAda
private static final String MIN_LABEL_RATIO_KEY = "minLabelRatio";
+ private static final String OVERLAP_KEY = "overlap";
+
private static final String OPACITY_KEY = "opacity";
private static final String DRAW_MODE_KEY = "drawMode";
@@ -56,6 +58,7 @@ public static MeshSettings deserializeInto(
Optional.ofNullable(map.get(CULL_FACE_KEY)).map(el -> (CullFace)context.deserialize(el, CullFace.class)).ifPresent(settings::setCullFace);
Optional.ofNullable(map.get(IS_VISIBLE_KEY)).map(JsonElement::getAsBoolean).ifPresent(settings::setVisible);
Optional.ofNullable(map.get(MIN_LABEL_RATIO_KEY)).map(JsonElement::getAsDouble).ifPresent(settings::setMinLabelRatio);
+ Optional.ofNullable(map.get(OVERLAP_KEY)).map(JsonElement::getAsBoolean).ifPresent(settings::setOverlap);
Optional.ofNullable(map.get(LEVEL_OF_DETAIL_KEY)).map(JsonElement::getAsInt).ifPresent(settings::setLevelOfDetail);
return settings;
}
@@ -106,6 +109,9 @@ public JsonElement serialize(final MeshSettings src, final Type typeOfSrc, final
if (defaults.getMinLabelRatio() != src.getMinLabelRatioProperty().get())
map.addProperty(MIN_LABEL_RATIO_KEY, src.getMinLabelRatioProperty().get());
+ if (defaults.getOverlap() != src.getOverlapProperty().get())
+ map.addProperty(OVERLAP_KEY, src.getOverlapProperty().get());
+
if (defaults.getOpacity() != src.getOpacityProperty().get())
map.addProperty(OPACITY_KEY, src.getOpacityProperty().get());
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/state/IntersectingSourceState.java b/src/main/java/org/janelia/saalfeldlab/paintera/state/IntersectingSourceState.java
index 40c56ed0c..b86674090 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/state/IntersectingSourceState.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/state/IntersectingSourceState.java
@@ -10,6 +10,8 @@
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import javafx.scene.paint.Color;
+import kotlin.coroutines.EmptyCoroutineContext;
+import kotlinx.coroutines.BuildersKt;
import net.imglib2.Cursor;
import net.imglib2.FinalInterval;
import net.imglib2.Interval;
@@ -183,7 +185,14 @@ private IntersectingSourceState(
INTERSECTION_FILL_SERVICE.submit(() -> {
getDataSource().invalidateAll();
requestRepaint();
- getMeshManager().createMeshFor(newv);
+ try {
+ BuildersKt.runBlocking(
+ EmptyCoroutineContext.INSTANCE,
+ (scope, continuation) -> getMeshManager().createMeshFor(newv, continuation)
+ );
+ } catch (InterruptedException e) {
+ LOG.debug("create mesh interrupted");
+ }
});
}
});
@@ -232,17 +241,20 @@ private CacheLoader>, Paint
);
}
- @Override public DataSource> getIntersectableMask() {
+ @Override
+ public DataSource> getIntersectableMask() {
return predicateDataSource;
}
- @Override public ObjectBinding> getMeshCacheKeyBinding() {
+ @Override
+ public ObjectBinding> getMeshCacheKeyBinding() {
return this.intersectionMeshCacheKeyBinding;
}
- @Override public GetBlockListFor> getGetBlockListFor() {
+ @Override
+ public GetBlockListFor> getGetBlockListFor() {
return this.getGetUnionBlockListFor;
}
@@ -282,7 +294,8 @@ public Interval[] getBlocksFor(int level, IntersectingSourceStateMeshCacheKey this.requestRepaint());
- getMeshManager().createMeshFor(getMeshCacheKeyBinding().get());
+ try {
+ BuildersKt.runBlocking(
+ EmptyCoroutineContext.INSTANCE,
+ (scope, continuation) -> getMeshManager().createMeshFor(getMeshCacheKeyBinding().get(), continuation)
+ );
+ } catch (InterruptedException e) {
+ LOG.debug("create mesh interrupted");
+ }
}
private void requestRepaint() {
@@ -310,7 +330,8 @@ private void requestRepaint() {
requestRepaintProperty.set(true);
}
- @Override public void onRemoval(SourceInfo paintera) {
+ @Override
+ public void onRemoval(SourceInfo paintera) {
LOG.info("Removed IntersectingSourceState {}", nameProperty().get());
meshManager.removeAllMeshes();
@@ -328,7 +349,7 @@ public void setMeshesEnabled(final boolean enabled) {
public void refreshMeshes() {
- this.meshManager.refreshMeshes();
+ meshManager.refreshMeshes();
}
public MeshManagerWithSingleMesh> getMeshManager() {
@@ -385,9 +406,9 @@ private static > ObservableDataSource)seedRAI).getSource() instanceof CachedCellImg) {
- var crai2 = (ConvertedRandomAccessibleInterval, ?>)seedRAI;
- var cachedCellImg = (CachedCellImg, ?>)crai2.getSource();
+ && ((ConvertedRandomAccessibleInterval, ?>) seedRAI).getSource() instanceof CachedCellImg) {
+ var crai2 = (ConvertedRandomAccessibleInterval, ?>) seedRAI;
+ var cachedCellImg = (CachedCellImg, ?>) crai2.getSource();
var grid = cachedCellImg.getCellGrid();
cellDimensions = new int[grid.numDimensions()];
grid.cellDimensions(cellDimensions);
@@ -407,7 +428,7 @@ private static > ObservableDataSource vimg = TmpVolatileHelpers.createVolatileCachedCellImgWithInvalidate(
- (CachedCellImg)img,
+ (CachedCellImg) img,
queue,
new CacheHints(LoadingStrategy.VOLATILE, priority, true));
@@ -474,7 +495,7 @@ private static > void addFillFromSeedsListener(
}
private static > boolean fillFromSeedPoints(RandomAccessibleInterval fillRAI, RandomAccessibleInterval img,
- Point[] seedPointsCopy) {
+ Point[] seedPointsCopy) {
LOG.debug("Filling from seed points");
boolean filledFromSeed = false;
@@ -487,7 +508,7 @@ private static > boolean fillFromSeedPoints(RandomAcces
seed,
new UnsignedByteType(1),
new DiamondShape(1),
- (BiPredicate)(source, target) -> source.get() && target.get() == 0
+ (BiPredicate) (source, target) -> source.get() && target.get() == 0
);
filledFromSeed = true;
}
@@ -496,7 +517,7 @@ private static > boolean fillFromSeedPoints(RandomAcces
}
private static > HashSet detectIntersectionPoints(RandomAccessibleInterval seedRAI, RandomAccessibleInterval fillRAI,
- RandomAccessibleInterval cell) {
+ RandomAccessibleInterval cell) {
LOG.trace(
"Detecting seed point in cell {} {}",
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/state/ThresholdingSourceState.java b/src/main/java/org/janelia/saalfeldlab/paintera/state/ThresholdingSourceState.java
index 66247781c..22faef565 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/state/ThresholdingSourceState.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/state/ThresholdingSourceState.java
@@ -11,6 +11,8 @@
import javafx.beans.value.ObservableDoubleValue;
import javafx.scene.Node;
import javafx.scene.paint.Color;
+import kotlin.coroutines.EmptyCoroutineContext;
+import kotlinx.coroutines.BuildersKt;
import net.imglib2.Interval;
import net.imglib2.Volatile;
import net.imglib2.algorithm.util.Grids;
@@ -165,17 +167,20 @@ public Threshold getThreshold() {
return this.threshold;
}
- @Override public ObjectBinding getMeshCacheKeyBinding() {
+ @Override
+ public ObjectBinding getMeshCacheKeyBinding() {
return meshCacheKeyProperty;
}
- @Override public GetBlockListFor getGetBlockListFor() {
+ @Override
+ public GetBlockListFor getGetBlockListFor() {
return getGetBlockListForMeshCaheKey();
}
- @Override public DataSource> getIntersectableMask() {
+ @Override
+ public DataSource> getIntersectableMask() {
return getDataSource();
}
@@ -217,7 +222,7 @@ public DoubleProperty maxProperty() {
public MeshSettings getMeshSettings() {
- return this.meshes.getSettings();
+ return this.meshes.getGlobalSettings();
}
public void refreshMeshes() {
@@ -266,7 +271,14 @@ private void setMeshId() {
private void setMeshId(final MeshManagerWithSingleMesh meshes) {
- meshes.createMeshFor(getThresholdBounds());
+ try {
+ BuildersKt.runBlocking(
+ EmptyCoroutineContext.INSTANCE,
+ (scope, continuation) -> meshes.createMeshFor(getThresholdBounds(), continuation)
+ );
+ } catch (InterruptedException e) {
+ LOG.debug("create mesh interrupted");
+ }
}
public Bounds getThresholdBounds() {
@@ -289,17 +301,20 @@ public ThresholdMeshCacheKey(final Bounds bounds) {
this.bounds = bounds;
}
- @Override public int hashCode() {
+ @Override
+ public int hashCode() {
return new HashCodeBuilder().append(bounds).toHashCode();
}
- @Override public boolean equals(Object obj) {
+ @Override
+ public boolean equals(Object obj) {
- return obj instanceof ThresholdMeshCacheKey && ((ThresholdMeshCacheKey)obj).bounds.equals(this.bounds);
+ return obj instanceof ThresholdMeshCacheKey && ((ThresholdMeshCacheKey) obj).bounds.equals(this.bounds);
}
- @Override public String toString() {
+ @Override
+ public String toString() {
return "ThresholdMeshCacheKey: (" + bounds.toString() + ")";
}
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/MeshExportResult.java b/src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/MeshExportResult.java
index b992d7394..e6f1d6244 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/MeshExportResult.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/MeshExportResult.java
@@ -2,9 +2,12 @@
import org.janelia.saalfeldlab.paintera.meshes.MeshExporter;
+import java.util.List;
+
public class MeshExportResult {
private final MeshExporter meshExporter;
+ private final List meshKeys;
private final String filePath;
@@ -13,9 +16,19 @@ public class MeshExportResult {
public MeshExportResult(
final MeshExporter meshExporter,
final String filePath,
- final int scale) {
+ final int scale,
+ final T meshKey) {
+ this(meshExporter, filePath, scale, List.of(meshKey));
+ }
+
+ public MeshExportResult(
+ final MeshExporter meshExporter,
+ final String filePath,
+ final int scale,
+ final List meshKeys) {
this.meshExporter = meshExporter;
+ this.meshKeys = meshKeys;
this.filePath = filePath;
this.scale = scale;
}
@@ -34,4 +47,8 @@ public int getScale() {
return scale;
}
+
+ public List getMeshKeys() {
+ return meshKeys;
+ }
}
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/SegmentMeshExporterDialog.java b/src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/MeshExporterDialog.java
similarity index 80%
rename from src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/SegmentMeshExporterDialog.java
rename to src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/MeshExporterDialog.java
index 631e1d5b7..b4dab541c 100644
--- a/src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/SegmentMeshExporterDialog.java
+++ b/src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/MeshExporterDialog.java
@@ -4,23 +4,33 @@
import javafx.beans.binding.BooleanBinding;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
-import javafx.scene.control.*;
+import javafx.scene.control.Button;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.Label;
+import javafx.scene.control.Spinner;
+import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.DirectoryChooser;
import org.controlsfx.control.CheckListView;
-import org.janelia.saalfeldlab.paintera.meshes.*;
+import org.janelia.saalfeldlab.paintera.meshes.MeshExporter;
+import org.janelia.saalfeldlab.paintera.meshes.MeshExporterBinary;
+import org.janelia.saalfeldlab.paintera.meshes.MeshExporterObj;
+import org.janelia.saalfeldlab.paintera.meshes.MeshInfo;
+import org.janelia.saalfeldlab.paintera.meshes.MeshSettings;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.nio.file.Path;
-import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-public class SegmentMeshExporterDialog extends Dialog> {
+public class MeshExporterDialog extends Dialog> {
public enum MeshFileFormat {
@@ -29,39 +39,32 @@ public enum MeshFileFormat {
final int LIST_CELL_HEIGHT = 25;
- private final Spinner scale;
+ protected final Spinner scale;
private final TextField filePathField;
private final TextField meshFileNameField;
- private MeshExporter meshExporter;
-
- private long[][] fragmentIds;
-
- private long[] segmentIds;
+ private MeshExporter meshExporter;
private ComboBox fileFormats;
- private CheckListView checkListView;
+ private CheckListView checkListView;
private CheckBox selectAll;
private final BooleanBinding isError;
private static String previousFilePath = null;
- public SegmentMeshExporterDialog(final SegmentMeshInfo meshInfo) {
-
+ public MeshExporterDialog(MeshInfo meshInfo) {
super();
- this.segmentIds = new long[]{meshInfo.segmentId()};
- this.fragmentIds = new long[][]{meshInfo.containedFragments()};
this.filePathField = new TextField();
if (previousFilePath != null)
filePathField.setText(previousFilePath);
this.meshFileNameField = new TextField();
- meshFileNameField.setText(meshInfo.segmentId().toString());
- this.setTitle("Export Mesh " + Arrays.toString(segmentIds));
+ this.setTitle("Export Mesh ");
this.isError = (Bindings.createBooleanBinding(() -> filePathField.getText().isEmpty() || meshFileNameField.getText().isEmpty() || pathExists(), filePathField.textProperty(), meshFileNameField.textProperty()));
- final MeshSettings settings = meshInfo.getMeshSettings();
+ final K meshKey = meshInfo.getKey();
+ final MeshSettings settings = meshInfo.getManager().getManagedSettings().getMeshSettings(meshKey);
this.scale = new Spinner<>(0, settings.getNumScaleLevels() - 1, settings.getFinestScaleLevel());
this.scale.setEditable(true);
@@ -70,32 +73,19 @@ public SegmentMeshExporterDialog(final SegmentMeshInfo meshInfo) {
return null;
}
previousFilePath = filePathField.getText();
- return new SegmentMeshExportResult<>(
+
+ return new MeshExportResult<>(
meshExporter,
- fragmentIds,
- segmentIds,
+ resolveFilePath(),
scale.getValue(),
- resolveFilePath()
+ meshKey
);
});
createDialog();
-
- }
-
- private boolean pathExists() {
-
- return Path.of(resolveFilePath()).toFile().exists();
- }
-
- @NotNull
- private String resolveFilePath() {
- final String file = Path.of(filePathField.getText(), meshFileNameField.getText()).toString();
- final String path = file.replace("~", System.getProperty("user.home"));
- return path;
}
- public SegmentMeshExporterDialog(ObservableList meshInfoList) {
+ public MeshExporterDialog(ObservableList> meshInfoList) {
super();
this.filePathField = new TextField();
@@ -103,8 +93,6 @@ public SegmentMeshExporterDialog(ObservableList meshInfoList) {
filePathField.setText(previousFilePath);
this.meshFileNameField = new TextField();
this.setTitle("Export mesh ");
- this.segmentIds = new long[meshInfoList.size()];
- this.fragmentIds = new long[meshInfoList.size()][];
this.checkListView = new CheckListView<>();
this.selectAll = new CheckBox("Select All");
selectAll.selectedProperty().addListener((obs, oldv, selected) -> {
@@ -124,15 +112,13 @@ public SegmentMeshExporterDialog(ObservableList meshInfoList) {
checkListView.itemsProperty()
));
- int minCommonScaleLevels = Integer.MAX_VALUE;
+ int minCommonScaleLevels = Integer.MAX_VALUE;
int minCommonScale = Integer.MAX_VALUE;
- final ObservableList ids = FXCollections.observableArrayList();
+ final ObservableList ids = FXCollections.observableArrayList();
for (int i = 0; i < meshInfoList.size(); i++) {
- final SegmentMeshInfo info = meshInfoList.get(i);
- this.segmentIds[i] = info.segmentId();
- this.fragmentIds[i] = info.containedFragments();
+ final MeshInfo info = meshInfoList.get(i);
final MeshSettings settings = info.getMeshSettings();
- ids.add(info.segmentId());
+ ids.add(info.getKey());
if (minCommonScaleLevels > settings.getNumScaleLevels()) {
minCommonScaleLevels = settings.getNumScaleLevels();
@@ -151,16 +137,30 @@ public SegmentMeshExporterDialog(ObservableList meshInfoList) {
return null;
}
previousFilePath = filePathField.getText();
- return new SegmentMeshExportResult<>(
+
+ return new MeshExportResult<>(
meshExporter,
- fragmentIds,
- segmentIds,
+ resolveFilePath(),
scale.getValue(),
- resolveFilePath()
+ checkListView.getCheckModel().getCheckedItems()
);
});
- createMultiIdsDialog(ids);
+ createMultiKeyDialog(ids);
+
+
+ }
+
+ private boolean pathExists() {
+
+ return Path.of(resolveFilePath()).toFile().exists();
+ }
+
+ @NotNull
+ private String resolveFilePath() {
+ final String file = Path.of(filePathField.getText(), meshFileNameField.getText()).toString();
+ final String path = file.replace("~", System.getProperty("user.home"));
+ return path;
}
private void createDialog() {
@@ -186,13 +186,13 @@ private void createDialog() {
this.getDialogPane().setContent(vbox);
}
- private void createMultiIdsDialog(final ObservableList ids) {
+ private void createMultiKeyDialog(final ObservableList keys) {
final VBox vbox = new VBox();
final GridPane contents = new GridPane();
int row = createCommonDialog(contents);
- checkListView.setItems(ids);
+ checkListView.setItems(keys);
checkListView.getSelectionModel().selectAll();
selectAll.setSelected(true);
checkListView.prefHeightProperty().bind(Bindings.size(checkListView.itemsProperty().get()).multiply(LIST_CELL_HEIGHT));
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/SegmentMeshExportResult.java b/src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/SegmentMeshExportResult.java
deleted file mode 100644
index 97fe144ad..000000000
--- a/src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/SegmentMeshExportResult.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.janelia.saalfeldlab.paintera.ui.source.mesh;
-
-import org.janelia.saalfeldlab.paintera.meshes.MeshExporter;
-
-public class SegmentMeshExportResult {
-
- private final MeshExporter meshExporter;
-
- private final long[][] fragmentIds;
-
- private final long[] segmentId;
-
- private final String filePath;
-
- private final int scale;
-
- // TODO: change scale parameter when the interface allows to export
- // different scales for different meshes at the same time
- public SegmentMeshExportResult(
- final MeshExporter meshExporter,
- final long[][] fragmentIds,
- final long[] segmentId,
- final int scale,
- final String filePath) {
-
- this.meshExporter = meshExporter;
- this.fragmentIds = fragmentIds;
- this.segmentId = segmentId;
- this.filePath = filePath;
- this.scale = scale;
- }
-
- public MeshExporter getMeshExporter() {
-
- return meshExporter;
- }
-
- public long[][] getFragmentIds() {
-
- return fragmentIds;
- }
-
- public long[] getSegmentId() {
-
- return segmentId;
- }
-
- public String getFilePath() {
-
- return filePath;
- }
-
- public int getScale() {
-
- return scale;
- }
-}
diff --git a/src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/SegmentMeshInfoNode.java b/src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/SegmentMeshInfoNode.java
deleted file mode 100644
index 7900d5391..000000000
--- a/src/main/java/org/janelia/saalfeldlab/paintera/ui/source/mesh/SegmentMeshInfoNode.java
+++ /dev/null
@@ -1,152 +0,0 @@
-package org.janelia.saalfeldlab.paintera.ui.source.mesh;
-
-import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.SimpleBooleanProperty;
-import javafx.geometry.VPos;
-import javafx.scene.Node;
-import javafx.scene.control.*;
-import javafx.scene.layout.*;
-import javafx.scene.paint.Color;
-import net.imglib2.type.label.LabelMultisetType;
-import org.fxmisc.flowless.VirtualizedScrollPane;
-import org.fxmisc.richtext.InlineCssTextArea;
-import org.janelia.saalfeldlab.fx.ui.NamedNode;
-import org.janelia.saalfeldlab.fx.util.InvokeOnJavaFXApplicationThread;
-import org.janelia.saalfeldlab.paintera.data.DataSource;
-import org.janelia.saalfeldlab.paintera.meshes.MeshExporter;
-import org.janelia.saalfeldlab.paintera.meshes.MeshExporterObj;
-import org.janelia.saalfeldlab.paintera.meshes.MeshSettings;
-import org.janelia.saalfeldlab.paintera.meshes.SegmentMeshInfo;
-import org.janelia.saalfeldlab.paintera.meshes.ui.MeshSettingsController;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.lang.invoke.MethodHandles;
-import java.util.Arrays;
-import java.util.Optional;
-
-public class SegmentMeshInfoNode {
-
- private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
- private final DataSource, ?> source;
-
- private final SegmentMeshInfo meshInfo;
-
- private final Region contents;
-
- private final CheckBox hasIndividualSettings = new CheckBox("Individual Settings");
-
- private final BooleanProperty isManaged = new SimpleBooleanProperty();
-
- private final MeshSettingsController controller;
-
- {
- hasIndividualSettings.selectedProperty().addListener((obs, oldv, newv) -> isManaged.set(!newv));
- isManaged.addListener((obs, oldv, newv) -> hasIndividualSettings.setSelected(!newv));
- isManaged.set(!hasIndividualSettings.isSelected());
- }
-
- private final MeshProgressBar progressBar = new MeshProgressBar();
-
- private final MeshSettings settings;
-
- public SegmentMeshInfoNode(final DataSource, ?> source, final SegmentMeshInfo meshInfo) {
-
- this.source = source;
- this.meshInfo = meshInfo;
- this.settings = meshInfo.getMeshSettings();
- this.controller = new MeshSettingsController(this.settings);
-
- LOG.debug("Initializing MeshinfoNode with draw mode {}", settings.getDrawModeProperty());
- this.contents = createContents();
- }
-
- public Region getNode() {
-
- return contents;
- }
-
- private Region createContents() {
-
- final TitledPane pane = new TitledPane(null, null);
- pane.setExpanded(false);
- pane.expandedProperty().addListener((obs, wasExpanded, isExpanded) -> {
- if (isExpanded && pane.getContent() == null) {
- InvokeOnJavaFXApplicationThread.invoke(() -> pane.setContent(getMeshInfoGrid()));
- }
- });
-
- // TODO come up with better way to ensure proper size of this!
- progressBar.setPrefWidth(200);
- progressBar.setMinWidth(Control.USE_PREF_SIZE);
- progressBar.setMaxWidth(Control.USE_PREF_SIZE);
- progressBar.setText("" + meshInfo.segmentId());
- pane.setGraphic(progressBar);
-
- return pane;
- }
-
- private GridPane getMeshInfoGrid() {
-
- final var grid = new GridPane();
-
- final GridPane settingsGrid = controller.createContents(source.getDataType() instanceof LabelMultisetType);
- final VBox individualSettingsBox = new VBox(hasIndividualSettings, settingsGrid);
- individualSettingsBox.setSpacing(5.0);
- settingsGrid.visibleProperty().bind(hasIndividualSettings.selectedProperty());
- settingsGrid.managedProperty().bind(settingsGrid.visibleProperty());
- hasIndividualSettings.setSelected(!meshInfo.isManagedProperty().get());
- isManaged.bindBidirectional(meshInfo.isManagedProperty());
- progressBar.bindTo(meshInfo.meshProgress());
-
- final var ids = new InlineCssTextArea(Arrays.toString(meshInfo.containedFragments()));
- final var virtualPane = new VirtualizedScrollPane<>(ids);
- ids.setWrapText(true);
-
- final Label idsLabel = new Label("ids: ");
- idsLabel.setMinWidth(30);
- idsLabel.setMaxWidth(30);
- final Node spacer = NamedNode.bufferNode();
- final HBox idsHeader = new HBox(idsLabel, spacer);
-
- final Button exportMeshButton = new Button("Export");
- exportMeshButton.setOnAction(event -> {
- final SegmentMeshExporterDialog exportDialog = new SegmentMeshExporterDialog<>(meshInfo);
- final Optional> result = exportDialog.showAndWait();
- if (result.isPresent()) {
- final SegmentMeshExportResult parameters = result.get();
- final MeshExporter meshExporter = parameters.getMeshExporter();
-
- final long id = parameters.getSegmentId()[0];
- final String filePath = parameters.getFilePath();
- final int scale = parameters.getScale();
-
- if (meshExporter instanceof MeshExporterObj) {
- ((MeshExporterObj>) meshExporter).exportMaterial(
- filePath,
- new long[]{id},
- new Color[]{meshInfo.meshManager().getStateFor(id).getColor()});
- }
-
- meshExporter.exportMesh(
- meshInfo.meshManager().getGetBlockListForLongKey(),
- meshInfo.meshManager().getGetMeshForLongKey(),
- meshInfo.getMeshSettings(),
- id,
- scale,
- filePath,
- false);
- }
- });
-
- grid.add(idsHeader, 0, 0);
- GridPane.setValignment(idsHeader, VPos.CENTER);
- grid.add(virtualPane, 1, 0);
- GridPane.setHgrow(virtualPane, Priority.ALWAYS);
- grid.add(exportMeshButton, 0, 1, 2, 1);
- grid.add(individualSettingsBox, 0, 2, 2, 1);
-
- return grid;
- }
-}
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/MeshInfo.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/MeshInfo.kt
new file mode 100644
index 000000000..5fa7f5ef0
--- /dev/null
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/MeshInfo.kt
@@ -0,0 +1,23 @@
+package org.janelia.saalfeldlab.paintera.meshes
+
+import javafx.beans.property.BooleanProperty
+import org.janelia.saalfeldlab.paintera.meshes.managed.MeshManager
+
+open class MeshInfo(val key: T, open val manager: MeshManager) {
+ val progressProperty: ObservableMeshProgress?
+ get() = manager.getStateFor(key)?.progress
+
+
+ override fun hashCode() = key.hashCode()
+
+ override fun equals(obj: Any?): Boolean {
+ val aClass: Class?> = this.javaClass
+ return aClass == obj!!.javaClass && key == aClass.cast(obj)!!.key
+ }
+
+ val meshSettings: MeshSettings
+ get() = manager.getSettings(key)
+
+ val isManagedProperty: BooleanProperty
+ get() = manager.managedSettings.isManagedProperty(key)
+}
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/MeshSettings.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/MeshSettings.kt
index a6c7a7cc6..c92135785 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/MeshSettings.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/MeshSettings.kt
@@ -22,6 +22,7 @@ class MeshSettings @JvmOverloads constructor(
val cullFace: CullFace
val isVisible: Boolean
val minLabelRatio: Double
+ val overlap: Boolean
val levelOfDetail: Int
@@ -47,6 +48,9 @@ class MeshSettings @JvmOverloads constructor(
@JvmStatic
val isVisible = true
+ @JvmStatic
+ val overlap = true
+
@JvmStatic
val minLabelRatio = 0.25
@@ -81,6 +85,7 @@ class MeshSettings @JvmOverloads constructor(
override var cullFace: CullFace = Defaults.Values.cullFace
override var isVisible: Boolean = Defaults.Values.isVisible
override var minLabelRatio: Double = Defaults.Values.minLabelRatio
+ override var overlap: Boolean = Defaults.Values.overlap
override var levelOfDetail: Int = Defaults.Values.levelOfDetail
val asImmutable: Defaults
@@ -100,6 +105,7 @@ class MeshSettings @JvmOverloads constructor(
val cullFaceProperty: ObjectProperty = SimpleObjectProperty(defaults.cullFace)
val isVisibleProperty: BooleanProperty = SimpleBooleanProperty(defaults.isVisible)
val minLabelRatioProperty: DoubleProperty = SimpleDoubleProperty(defaults.minLabelRatio)
+ val overlapProperty: BooleanProperty = SimpleBooleanProperty(defaults.overlap)
val levelOfDetailProperty: IntegerProperty = SimpleIntegerProperty(defaults.levelOfDetail)
var coarsestScaleLevel by coarsestScaleLevelProperty.nonnull()
@@ -112,6 +118,7 @@ class MeshSettings @JvmOverloads constructor(
var cullFace by cullFaceProperty.nonnull()
var isVisible by isVisibleProperty.nonnull()
var minLabelRatio by minLabelRatioProperty.nonnull()
+ var overlap by overlapProperty.nonnull()
var levelOfDetail by levelOfDetailProperty.nonnull()
init {
@@ -137,6 +144,7 @@ class MeshSettings @JvmOverloads constructor(
smoothingLambda = that.smoothingLambda
smoothingIterations = that.smoothingIterations
minLabelRatio = that.minLabelRatio
+ overlap = that.overlap
opacity = that.opacity
drawMode = that.drawMode
cullFace = that.cullFace
@@ -160,6 +168,7 @@ class MeshSettings @JvmOverloads constructor(
smoothingLambdaProperty.bind(that.smoothingLambdaProperty)
smoothingIterationsProperty.bind(that.smoothingIterationsProperty)
minLabelRatioProperty.bind(that.minLabelRatioProperty)
+ overlapProperty.bind(that.overlapProperty)
opacityProperty.bind(that.opacityProperty)
drawModeProperty.bind(that.drawModeProperty)
cullFaceProperty.bind(that.cullFaceProperty)
@@ -174,6 +183,7 @@ class MeshSettings @JvmOverloads constructor(
smoothingLambdaProperty.unbind()
smoothingIterationsProperty.unbind()
minLabelRatioProperty.unbind()
+ overlapProperty.unbind()
opacityProperty.unbind()
drawModeProperty.unbind()
cullFaceProperty.unbind()
@@ -188,6 +198,7 @@ class MeshSettings @JvmOverloads constructor(
smoothingLambdaProperty.bindBidirectional(that.smoothingLambdaProperty)
smoothingIterationsProperty.bindBidirectional(that.smoothingIterationsProperty)
minLabelRatioProperty.bindBidirectional(that.minLabelRatioProperty)
+ overlapProperty.bindBidirectional(that.overlapProperty)
opacityProperty.bindBidirectional(that.opacityProperty)
drawModeProperty.bindBidirectional(that.drawModeProperty)
cullFaceProperty.bindBidirectional(that.cullFaceProperty)
@@ -202,6 +213,7 @@ class MeshSettings @JvmOverloads constructor(
smoothingLambdaProperty.unbindBidirectional(that.smoothingLambdaProperty)
smoothingIterationsProperty.unbindBidirectional(that.smoothingIterationsProperty)
minLabelRatioProperty.unbindBidirectional(that.minLabelRatioProperty)
+ overlapProperty.unbindBidirectional(that.overlapProperty)
opacityProperty.unbindBidirectional(that.opacityProperty)
drawModeProperty.unbindBidirectional(that.drawModeProperty)
cullFaceProperty.unbindBidirectional(that.cullFaceProperty)
@@ -214,6 +226,8 @@ class MeshSettings @JvmOverloads constructor(
&& simplificationIterations == defaults.simplificationIterations
&& smoothingLambda == defaults.smoothingLambda
&& smoothingIterations == defaults.smoothingIterations
+ && minLabelRatio == defaults.minLabelRatio
+ && overlap == defaults.overlap
&& opacity == defaults.opacity
&& defaults.drawMode == drawMode
&& defaults.cullFace == cullFace
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/SegmentMeshInfo.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/SegmentMeshInfo.kt
new file mode 100644
index 000000000..530ad52a6
--- /dev/null
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/SegmentMeshInfo.kt
@@ -0,0 +1,14 @@
+package org.janelia.saalfeldlab.paintera.meshes
+
+import org.janelia.saalfeldlab.paintera.meshes.managed.MeshManagerWithAssignmentForSegments
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.lang.invoke.MethodHandles
+
+class SegmentMeshInfo(segmentId: Long, override val manager: MeshManagerWithAssignmentForSegments) : MeshInfo(segmentId, manager) {
+
+ fun fragments(): LongArray {
+ return manager.getFragmentsFor(key).toArray()
+ }
+}
+
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/SegmentMeshInfoList.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/SegmentMeshInfoList.kt
new file mode 100644
index 000000000..3a2e47889
--- /dev/null
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/SegmentMeshInfoList.kt
@@ -0,0 +1,39 @@
+package org.janelia.saalfeldlab.paintera.meshes
+
+import javafx.collections.FXCollections
+import javafx.collections.ListChangeListener
+import javafx.collections.ObservableList
+import org.janelia.saalfeldlab.paintera.meshes.managed.MeshManagerWithAssignmentForSegments
+import org.janelia.saalfeldlab.paintera.meshes.ui.MeshInfoList
+import org.janelia.saalfeldlab.paintera.ui.source.mesh.SegmentMeshInfoNode
+
+class SegmentMeshInfoList(
+ selectedSegments: ObservableList,
+ val manager: MeshManagerWithAssignmentForSegments
+) : MeshInfoList(FXCollections.observableArrayList(), manager) {
+
+ init {
+ selectedSegments.addListener(ListChangeListener {change ->
+ if (manager.managedSettings.isMeshListEnabledProperty.get()) {
+ val toRemove = mutableSetOf()
+ val toAdd = mutableSetOf()
+ while (change.next()) {
+ val removed = change.removed
+ val added = change.addedSubList
+
+ /* remove the outdated things */
+ toRemove.removeAll(added)
+ toAdd.removeAll(removed)
+
+ /* add the new things */
+ toAdd.addAll(added)
+ toRemove.addAll(removed)
+ }
+ meshInfoList.removeIf { toRemove.contains(it.key) }
+ meshInfoList += toAdd.map { SegmentMeshInfo(it, manager) }.toSet()
+ }
+ })
+ }
+
+ override fun meshNodeFactory(meshInfo: SegmentMeshInfo) = SegmentMeshInfoNode(meshInfo)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/managed/MeshManagerWithAssignmentForSegments.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/managed/MeshManagerWithAssignmentForSegments.kt
index 8484e9202..3b1d9ebd8 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/managed/MeshManagerWithAssignmentForSegments.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/managed/MeshManagerWithAssignmentForSegments.kt
@@ -1,15 +1,14 @@
package org.janelia.saalfeldlab.paintera.meshes.managed
+import com.google.common.collect.HashBiMap
import gnu.trove.set.hash.TLongHashSet
import javafx.beans.InvalidationListener
-import javafx.beans.property.BooleanProperty
import javafx.beans.property.ObjectProperty
-import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
-import javafx.scene.Group
import javafx.scene.paint.Color
+import kotlinx.coroutines.*
import net.imglib2.FinalInterval
import net.imglib2.Interval
import net.imglib2.cache.Invalidate
@@ -26,7 +25,6 @@ import org.janelia.saalfeldlab.paintera.data.mask.MaskedSource
import org.janelia.saalfeldlab.paintera.meshes.*
import org.janelia.saalfeldlab.paintera.meshes.cache.SegmentMaskGenerators
import org.janelia.saalfeldlab.paintera.meshes.cache.SegmentMeshCacheLoader
-import org.janelia.saalfeldlab.paintera.meshes.managed.adaptive.AdaptiveResolutionMeshManager
import org.janelia.saalfeldlab.paintera.state.label.FragmentLabelMeshCacheKey
import org.janelia.saalfeldlab.paintera.stream.AbstractHighlightingARGBStream
import org.janelia.saalfeldlab.paintera.viewer3d.ViewFrustum
@@ -36,39 +34,49 @@ import org.janelia.saalfeldlab.util.NamedThreadFactory
import org.janelia.saalfeldlab.util.concurrent.HashPriorityQueueBasedTaskExecutor
import org.slf4j.LoggerFactory
import java.lang.invoke.MethodHandles
-import java.util.Arrays
+import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
+import kotlin.coroutines.coroutineContext
import kotlin.math.min
+private typealias Segments = TLongHashSet
+private typealias Fragments = TLongHashSet
+
/**
* @author Philipp Hanslovsky
* @author Igor Pisarev
*/
class MeshManagerWithAssignmentForSegments(
source: DataSource<*, *>,
- val labelBlockLookup: LabelBlockLookup,
- private val getMeshFor: GetMeshFor,
+ getMeshFor: GetMeshFor,
viewFrustumProperty: ObservableValue,
eyeToWorldTransformProperty: ObservableValue,
- private val selectedSegments: SelectedSegments,
- private val argbStream: AbstractHighlightingARGBStream,
- val managers: ExecutorService,
- val workers: HashPriorityQueueBasedTaskExecutor,
- val meshViewUpdateQueue: MeshViewUpdateQueue,
-) {
-
- private class CancelableTask(private val task: (() -> Boolean) -> Unit) : Runnable {
-
- private var isCanceled: Boolean = false
-
- fun cancel() {
- isCanceled = true
+ managers: ExecutorService,
+ workers: HashPriorityQueueBasedTaskExecutor,
+ meshViewUpdateQueue: MeshViewUpdateQueue,
+ val labelBlockLookup: LabelBlockLookup,
+ val selectedSegments: SelectedSegments,
+ val argbStream: AbstractHighlightingARGBStream,
+) : MeshManager(
+ source,
+ GetBlockListFor { level, segment ->
+ val intervals = mutableSetOf>()
+ val fragments = selectedSegments.assignment.getFragments(segment)
+ fragments.forEach { id ->
+ labelBlockLookup.read(level, id).map { HashWrapper.interval(it) }.let { intervals.addAll(it) }
+ true
}
+ intervals.map { it.data }.toTypedArray()
+ },
+ getMeshFor,
+ viewFrustumProperty,
+ eyeToWorldTransformProperty,
+ managers,
+ workers,
+ meshViewUpdateQueue
+) {
- override fun run() = task { isCanceled }
-
- }
private class RelevantBindingsAndProperties(private val key: Long, private val stream: AbstractHighlightingARGBStream) {
private val color: ObjectProperty = SimpleObjectProperty(Color.WHITE)
@@ -85,17 +93,8 @@ class MeshManagerWithAssignmentForSegments(
"meshmanager-with-assignment-update-%d",
true
)
- )
- private var currentTask: CancelableTask? = null
-
- private val getBlockList: GetBlockListFor = GetBlockListFor { level, key ->
- val intervals = mutableSetOf>()
- key.forEach { id ->
- labelBlockLookup.read(level, id).map { HashWrapper.interval(it) }.let { intervals.addAll(it) }
- true
- }
- intervals.map { it.data }.toTypedArray()
- }
+ ).asCoroutineDispatcher()
+ private var currentTask: Job? = null
// setMeshesCompleted is only visible to enclosing manager if
// _meshUpdateObservable is private
@@ -106,194 +105,134 @@ class MeshManagerWithAssignmentForSegments(
}
val meshUpdateObservable = _meshUpdateObservable
- private val segmentFragmentMap =
- FXCollections.synchronizedObservableMap(FXCollections.observableHashMap())
- private val fragmentSegmentMap =
- FXCollections.synchronizedObservableMap(FXCollections.observableHashMap())
- private val relevantBindingsAndPropertiesMap =
- FXCollections.synchronizedObservableMap(FXCollections.observableHashMap())
-
- private val viewerEnabled: SimpleBooleanProperty = SimpleBooleanProperty(false)
- var isViewerEnabled: Boolean
- get() = viewerEnabled.get()
- set(enabled) = viewerEnabled.set(enabled)
-
- fun viewerEnabledProperty(): BooleanProperty = viewerEnabled
-
- private val manager: AdaptiveResolutionMeshManager = AdaptiveResolutionMeshManager(
- source,
- getBlockList,
- getMeshFor,
- viewFrustumProperty,
- eyeToWorldTransformProperty,
- viewerEnabled,
- managers,
- workers,
- meshViewUpdateQueue
- )
-
- val rendererSettings get() = manager.rendererSettings
-
- val managedSettings = ManagedMeshSettings(source.numMipmapLevels).apply { rendererSettings.meshesEnabledProperty.bind(meshesEnabledProperty) }
-
- val settings: MeshSettings
- get() = managedSettings.globalSettings
-
- val meshesGroup: Group
- get() = manager.meshesGroup
-
- // TODO This listener is added to all mesh states. This is a problem if a lot of ids are selected
- // and all use global mesh settings. Whenever the global mesh settings are changed, the
- // managerCancelAndUpdate would be notified for each of the meshes, which can temporarily slow down
- // the UI for quite some time (tens of seconds). A smarter way might be a single thread executor that
- // executes only the last request and has a delay.
- // This may be fixed now by using manager.requestCancelAndUpdate(), which submits a task
- // to a LatestTaskExecutor with a delay of 100ms.
- private val managerCancelAndUpdate = InvalidationListener { manager.requestCancelAndUpdate() }
+ private val segmentFragmentBiMap = HashBiMap.create()
+ private val relevantBindingsAndPropertiesMap = FXCollections.synchronizedObservableMap(FXCollections.observableHashMap())
+
+ override fun releaseMeshState(key: Long, state: MeshGenerator.State) {
+ synchronized(this) {
+ segmentFragmentBiMap.remove(key)
+ relevantBindingsAndPropertiesMap.remove(key)?.release()
+ super.releaseMeshState(key, state)
+ }
+ }
- @Synchronized
fun setMeshesToSelection() {
+
currentTask?.cancel()
- currentTask = null
- val task = CancelableTask { setMeshesToSelectionImpl(it) }
- currentTask = task
- updateExecutors.submit(task)
+ runBlocking {
+ currentTask = launch(updateExecutors) { setMeshesToSelectionImpl() }
+ }
}
- private fun setMeshesToSelectionImpl(isCanceled: () -> Boolean) {
- if (isCanceled()) return
+ private suspend fun setMeshesToSelectionImpl() {
+ yield()
- val (selection, presentKeys) = synchronized(this) {
- val selection = TLongHashSet(selectedSegments.selectedSegments)
- val presentKeys = TLongHashSet().also { set -> segmentFragmentMap.keys.forEach { set.add(it) } }
- Pair(selection, presentKeys)
+ val (selectedSegments, presentSegments) = synchronized(this) {
+ val selectedSegments = Segments(selectedSegments.selectedSegments)
+ val presentSegments = Segments().also { set -> segmentFragmentBiMap.keys.forEach { set.add(it) } }
+ selectedSegments to presentSegments
}
// We need to collect all ids that are selected but not yet present in the 3d viewer and vice versa
// to generate a diff and only apply the diff to the current mesh selection.
// Additionally, segments that are inconsistent, i.e. if the set of fragments has changed for a segment
// we need to replace it as well.
- val presentButNotSelected = TLongHashSet()
- val selectedButNotPresent = TLongHashSet()
- val inconsistentIds = TLongHashSet()
+ val segmentsToRemove = Segments()
+ val segmentsToAdd = Segments()
- selection.forEach { id ->
- if (id !in presentKeys)
- selectedButNotPresent.add(id)
+ val inconsistentIds = Segments()
+
+ selectedSegments.forEach { segment ->
+ if (segment !in presentSegments)
+ segmentsToAdd.add(segment)
true
}
- presentKeys.forEach { id ->
- if (id !in selection)
- presentButNotSelected.add(id)
- else if (segmentFragmentMap[id]?.let { selectedSegments.assignment.isSegmentConsistent(id, it) } == false)
- inconsistentIds.add(id)
- true
+ synchronized(this) {
+ presentSegments.forEach { segment ->
+ if (segment !in selectedSegments)
+ segmentsToRemove.add(segment)
+ else if (segmentFragmentBiMap[segment]?.let { this.selectedSegments.assignment.isSegmentConsistent(segment, it) } == false)
+ inconsistentIds.add(segment)
+ true
+ }
}
- presentButNotSelected.addAll(inconsistentIds)
- selectedButNotPresent.addAll(inconsistentIds)
+
+ segmentsToRemove.addAll(inconsistentIds)
+ segmentsToAdd.addAll(inconsistentIds)
// remove meshes that are present but not in selection
- removeMeshesFor(presentButNotSelected.toArray().toList())
+ removeMeshes(segmentsToRemove.toArray().toList())
// add meshes for all selected ids that are not present yet
// removing mesh if is canceled is necessary because could be canceled between call to isCanceled.asBoolean and createMeshFor
// another option would be to synchronize on a lock object but that is not necessary
- for (id in selectedButNotPresent) {
- if (isCanceled()) break
- createMeshFor(id)
- if (isCanceled()) removeMeshFor(id)
- }
+ createMeshes(segmentsToAdd)
- if (!isCanceled())
- manager.requestCancelAndUpdate()
+ manager.requestCancelAndUpdate()
- if (!isCanceled())
- this._meshUpdateObservable.meshUpdateCompleted()
+ this._meshUpdateObservable.meshUpdateCompleted()
}
- private fun createMeshFor(key: Long) {
- if (key in segmentFragmentMap) return
- selectedSegments
- .assignment
- .getFragments(key)
- ?.takeUnless { it.isEmpty }
- ?.let { fragments ->
- segmentFragmentMap[key] = fragments
- fragmentSegmentMap[fragments] = key
- manager.createMeshFor(fragments, false) { setupGeneratorState(key, it) }
- }
+ private suspend fun createMeshes(segments: Segments) {
+ for (segment in segments) {
+ yield()
+ if (segment in segmentFragmentBiMap) return
+ selectedSegments.assignment.getFragments(segment)
+ ?.takeUnless { it.isEmpty }
+ ?.let { fragments ->
+ segmentFragmentBiMap[segment] = fragments
+ createMeshFor(segment)
+ }
+ }
+ }
+
+ override suspend fun createMeshFor(key: Long) {
+ super.createMeshFor(key)
+ if (!coroutineContext.isActive) {
+ removeMesh(key)
+ }
}
- private fun setupGeneratorState(key: Long, state: MeshGenerator.State) {
- state.settings.bindTo(managedSettings.getOrAddMesh(key, true))
+ override fun setupMeshState(key: Long, state: MeshGenerator.State) {
+ state.settings.bindTo(managedSettings.getMeshSettings(key, true))
val relevantBindingsAndProperties = relevantBindingsAndPropertiesMap.computeIfAbsent(key) {
RelevantBindingsAndProperties(key, argbStream)
}
state.colorProperty().bind(relevantBindingsAndProperties.colorProperty())
- state.settings.levelOfDetailProperty.addListener(managerCancelAndUpdate)
- state.settings.coarsestScaleLevelProperty.addListener(managerCancelAndUpdate)
- state.settings.finestScaleLevelProperty.addListener(managerCancelAndUpdate)
- }
+ super.setupMeshState(key, state)
- private fun MeshGenerator.State.release() {
- settings.levelOfDetailProperty.removeListener(managerCancelAndUpdate)
- settings.coarsestScaleLevelProperty.removeListener(managerCancelAndUpdate)
- settings.finestScaleLevelProperty.removeListener(managerCancelAndUpdate)
- settings.unbind()
- colorProperty().unbind()
}
- private fun removeMeshFor(key: Long) {
- segmentFragmentMap.remove(key)?.let { fragmentSet ->
- fragmentSegmentMap.remove(fragmentSet)
- manager.removeMeshFor(fragmentSet) {
- it.release()
- relevantBindingsAndPropertiesMap.remove(key)?.release()
- }
- }
- }
-
- private fun removeMeshesFor(keys: Iterable) {
- val fragmentSetKeys = keys.mapNotNull { key ->
- relevantBindingsAndPropertiesMap.remove(key)?.release()
- segmentFragmentMap.remove(key)?.also { fragmentSegmentMap.remove(it) }
- }
- manager.removeMeshesFor(fragmentSetKeys) { it.release() }
+ @Synchronized
+ override fun removeMesh(key: Long) {
+ super.removeMesh(key)
+ _meshUpdateObservable.meshUpdateCompleted()
}
@Synchronized
- fun removeAllMeshes() {
- currentTask?.cancel()
- currentTask = null
- val task = CancelableTask { removeAllMeshesImpl() }
- updateExecutors.submit(task)
- currentTask = task
+ override fun removeMeshes(keys: Iterable) {
+ super.removeMeshes(keys)
+ _meshUpdateObservable.meshUpdateCompleted()
}
- private fun removeAllMeshesImpl() {
- removeMeshesFor(segmentFragmentMap.keys.toList())
+ @Synchronized
+ override fun removeAllMeshes() {
+ super.removeAllMeshes()
_meshUpdateObservable.meshUpdateCompleted()
}
- @Synchronized
- fun refreshMeshes() {
- currentTask?.cancel()
- currentTask = null
- val task = CancelableTask { isCanceled ->
- this.removeAllMeshesImpl()
- if (labelBlockLookup is Invalidate<*>) labelBlockLookup.invalidateAll()
- if (getMeshFor is Invalidate<*>) getMeshFor.invalidateAll()
- this.setMeshesToSelectionImpl(isCanceled)
- }
- updateExecutors.submit(task)
- currentTask = task
+ override fun refreshMeshes() {
+ super.removeAllMeshes()
+ if (labelBlockLookup is Invalidate<*>) labelBlockLookup.invalidateAll()
+ if (getMeshFor is Invalidate<*>) getMeshFor.invalidateAll()
+ setMeshesToSelection()
}
companion object {
private val LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass())
- fun LabelBlockLookup.read(level: Int, id: Long) = read(LabelBlockLookupKey(level, id))
+ fun LabelBlockLookup.read(level: Int, fragmentId: Long) = read(LabelBlockLookupKey(level, fragmentId))
@JvmStatic
fun > fromBlockLookup(
@@ -307,12 +246,12 @@ class MeshManagerWithAssignmentForSegments(
meshWorkersExecutors: HashPriorityQueueBasedTaskExecutor,
): MeshManagerWithAssignmentForSegments {
LOG.debug("Data source is type {}", dataSource.javaClass)
- val actualLookup = when (dataSource) {
- is MaskedSource -> LabeLBlockLookupWithMaskedSource.create(labelBlockLookup, dataSource)
- else -> labelBlockLookup
- }
+ val actualLookup = (dataSource as? MaskedSource)
+ ?.let { LabelBlockLookupWithMaskedSource.create(labelBlockLookup, it) }
+ ?: labelBlockLookup
+
// Set up mesh caches
- val segmentMaskGenerators = Array(dataSource.numMipmapLevels) { SegmentMaskGenerators.create(dataSource, it) }
+ val segmentMaskGenerators = Array(dataSource.numMipmapLevels) { SegmentMaskGenerators.create(dataSource, it) { segment -> selectedSegments.assignment.getFragments(segment) } }
val loaders = Array(dataSource.numMipmapLevels) {
SegmentMeshCacheLoader(
{ dataSource.getDataSource(0, it) },
@@ -324,27 +263,27 @@ class MeshManagerWithAssignmentForSegments(
return MeshManagerWithAssignmentForSegments(
dataSource,
- actualLookup,
getMeshFor,
viewFrustumProperty,
eyeToWorldTransformProperty,
- selectedSegments,
- argbStream,
meshManagerExecutors,
meshWorkersExecutors,
- MeshViewUpdateQueue()
+ MeshViewUpdateQueue(),
+ actualLookup,
+ selectedSegments,
+ argbStream
)
}
}
- private class CachedLabeLBlockLookupWithMaskedSource>(
+ private class CachedLabelBlockLookupWithMaskedSource>(
private val delegate: CachedLabelBlockLookup,
private val maskedSource: MaskedSource,
) :
- LabeLBlockLookupWithMaskedSource(delegate, maskedSource),
+ LabelBlockLookupWithMaskedSource(delegate, maskedSource),
Invalidate by delegate
- private open class LabeLBlockLookupWithMaskedSource>(
+ private open class LabelBlockLookupWithMaskedSource>(
private val delegate: LabelBlockLookup,
private val maskedSource: MaskedSource,
) : LabelBlockLookup by delegate {
@@ -359,8 +298,8 @@ class MeshManagerWithAssignmentForSegments(
private val LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass())
fun > create(delegate: LabelBlockLookup, maskedSource: MaskedSource) = when (delegate) {
- is CachedLabelBlockLookup -> CachedLabeLBlockLookupWithMaskedSource(delegate, maskedSource)
- else -> LabeLBlockLookupWithMaskedSource(delegate, maskedSource)
+ is CachedLabelBlockLookup -> CachedLabelBlockLookupWithMaskedSource(delegate, maskedSource)
+ else -> LabelBlockLookupWithMaskedSource(delegate, maskedSource)
}
private fun affectedBlocksForLabel(source: MaskedSource<*, *>, level: Int, id: Long): Array {
@@ -388,30 +327,34 @@ class MeshManagerWithAssignmentForSegments(
}
- @Synchronized
- fun getStateFor(key: Long) = segmentFragmentMap[key]?.let { manager.getStateFor(it) }
-
- fun getContainedFragmentsFor(key: Long) = segmentFragmentMap[key]
+ fun getFragmentsFor(segment: Long): Fragments = segmentFragmentBiMap[segment] ?: selectedSegments.assignment.getFragments(segment)
- val getBlockListForLongKey: GetBlockListFor
- get() = GetBlockListFor { level, key ->
- getBlockList.getBlocksFor(level, getContainedFragmentsFor(key) ?: TLongHashSet())
+ val getBlockListForSegment: GetBlockListFor
+ get() = GetBlockListFor { level, segment ->
+ getBlockListFor.getBlocksFor(level, segment)
}
- val getBlockListForMeshCacheKey: GetBlockListFor = GetBlockListFor { level, key ->
- getBlockList.getBlocksFor(level, key.fragments)
+ val getBlockListForFragments: GetBlockListFor = GetBlockListFor { level, key ->
+ val intervals = mutableSetOf>()
+ val fragments = key.fragments
+ fragments.forEach { id ->
+ labelBlockLookup.read(level, id).map { HashWrapper.interval(it) }.let { intervals.addAll(it) }
+ true
+ }
+ intervals.map { it.data }.toTypedArray()
}
val getMeshForLongKey: GetMeshFor
get() = object : GetMeshFor {
override fun getMeshFor(key: ShapeKey): PainteraTriangleMesh? = getMeshFor.getMeshFor(
ShapeKey(
- getContainedFragmentsFor(key.shapeId()) ?: TLongHashSet(),
+ key.shapeId(),
key.scaleIndex(),
key.simplificationIterations(),
key.smoothingLambda(),
key.smoothingIterations(),
key.minLabelRatio(),
+ key.overlap(),
key.min(),
key.max()
)
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/managed/MeshManagerWithSingleMesh.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/managed/MeshManagerWithSingleMesh.kt
index 60daea178..b0790b65d 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/managed/MeshManagerWithSingleMesh.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/managed/MeshManagerWithSingleMesh.kt
@@ -1,5 +1,6 @@
package org.janelia.saalfeldlab.paintera.meshes.managed
+import io.github.oshai.kotlinlogging.KotlinLogging
import javafx.beans.InvalidationListener
import javafx.beans.property.BooleanProperty
import javafx.beans.property.ObjectProperty
@@ -8,25 +9,25 @@ import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import javafx.scene.Group
import javafx.scene.paint.Color
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
import net.imglib2.cache.Invalidate
import net.imglib2.realtransform.AffineTransform3D
import org.janelia.saalfeldlab.fx.extensions.nonnull
import org.janelia.saalfeldlab.paintera.data.DataSource
-import org.janelia.saalfeldlab.paintera.meshes.*
+import org.janelia.saalfeldlab.paintera.meshes.ManagedMeshSettings
+import org.janelia.saalfeldlab.paintera.meshes.MeshGenerator.State
+import org.janelia.saalfeldlab.paintera.meshes.MeshSettings
+import org.janelia.saalfeldlab.paintera.meshes.MeshViewUpdateQueue
+import org.janelia.saalfeldlab.paintera.meshes.MeshWorkerPriority
import org.janelia.saalfeldlab.paintera.meshes.managed.adaptive.AdaptiveResolutionMeshManager
import org.janelia.saalfeldlab.paintera.viewer3d.ViewFrustum
import org.janelia.saalfeldlab.util.concurrent.HashPriorityQueueBasedTaskExecutor
-import org.slf4j.LoggerFactory
-import java.lang.invoke.MethodHandles
import java.util.concurrent.ExecutorService
-/**
- * @author Philipp Hanslovsky
- * @author Igor Pisarev
- */
-class MeshManagerWithSingleMesh(
- source: DataSource<*, *>,
- val getBlockList: GetBlockListFor,
+abstract class MeshManager(
+ val source: DataSource<*, *>,
+ val getBlockListFor: GetBlockListFor,
val getMeshFor: GetMeshFor,
viewFrustumProperty: ObservableValue,
eyeToWorldTransformProperty: ObservableValue,
@@ -35,19 +36,16 @@ class MeshManagerWithSingleMesh(
meshViewUpdateQueue: MeshViewUpdateQueue,
) {
- var meshKey: Key? = null
- @Synchronized get
- @Synchronized private set
+ companion object {
+ val LOG = KotlinLogging.logger { }
+ }
val viewerEnabledProperty: BooleanProperty = SimpleBooleanProperty(false)
var isViewerEnabled: Boolean by viewerEnabledProperty.nonnull()
- val colorProperty: ObjectProperty = SimpleObjectProperty(Color.WHITE)
- var color: Color by colorProperty.nonnull()
-
- private val manager: AdaptiveResolutionMeshManager = AdaptiveResolutionMeshManager(
+ protected val manager: AdaptiveResolutionMeshManager = AdaptiveResolutionMeshManager(
source,
- getBlockList,
+ getBlockListFor,
getMeshFor,
viewFrustumProperty,
eyeToWorldTransformProperty,
@@ -57,63 +55,142 @@ class MeshManagerWithSingleMesh(
meshViewUpdateQueue
)
-
val rendererSettings = manager.rendererSettings
- val settings: MeshSettings = MeshSettings(source.numMipmapLevels)
+ val managedSettings = ManagedMeshSettings(source.numMipmapLevels).apply { rendererSettings.meshesEnabledProperty.bind(meshesEnabledProperty) }
- val managedSettings = ManagedMeshSettings(source.numMipmapLevels).apply { rendererSettings.meshesEnabledProperty.bind(meshesEnabledProperty) }
+ val globalSettings: MeshSettings = managedSettings.globalSettings
val meshesGroup: Group = manager.meshesGroup
- private val managerCancelAndUpdate = InvalidationListener { manager.requestCancelAndUpdate() }
+ // TODO This listener is added to all mesh states. This is a problem if a lot of ids are selected
+ // and all use global mesh settings. Whenever the global mesh settings are changed, the
+ // managerCancelAndUpdate would be notified for each of the meshes, which can temporarily slow down
+ // the UI for quite some time (tens of seconds). A smarter way might be a single thread executor that
+ // executes only the last request and has a delay.
+ // This may be fixed now by using manager.requestCancelAndUpdate(), which submits a task
+ // to a LatestTaskExecutor with a delay of 100ms.
+ protected val managerCancelAndUpdate = InvalidationListener { manager.requestCancelAndUpdate() }
+
+ open fun getStateFor(key: Key): State? {
+ return manager.getStateFor(key)
+ }
+
+ fun getSettings(key: Key): MeshSettings {
+ return managedSettings.getMeshSettings(key)
+ }
+
+ open suspend fun createMeshFor(key: Key) {
+ manager.createMeshFor(key, true, stateSetup = ::setupMeshState)
+ }
+
+ protected open fun setupMeshState(key: Key, state: State) {
+ LOG.debug { "Setting up state for mesh key $key" }
+ with(globalSettings) {
+ levelOfDetailProperty.addListener(managerCancelAndUpdate)
+ coarsestScaleLevelProperty.addListener(managerCancelAndUpdate)
+ finestScaleLevelProperty.addListener(managerCancelAndUpdate)
+ }
+ }
@Synchronized
- fun createMeshFor(key: Key) {
- if (key == meshKey)
- return
- this.removeAllMeshes()
- meshKey = key
- manager.createMeshFor(key, true) { it.setup() }
+ protected open fun releaseMeshState(key: Key, state: State) {
+ state.colorProperty().unbind()
+ with(globalSettings) {
+ unbind()
+ levelOfDetailProperty.removeListener(managerCancelAndUpdate)
+ coarsestScaleLevelProperty.removeListener(managerCancelAndUpdate)
+ finestScaleLevelProperty.removeListener(managerCancelAndUpdate)
+ }
}
@Synchronized
- fun removeAllMeshes() {
- manager.removeAllMeshes { it.release() }
- meshKey = null
+ open fun removeAllMeshes() {
+ manager.removeAllMeshes { key, state -> releaseMeshState(key, state) }
}
@Synchronized
- fun refreshMeshes() {
- val key = meshKey
+ open fun removeMesh(key: Key) {
+ manager.removeMeshFor(key) { meshKey, state -> releaseMeshState(meshKey, state) }
+ }
+
+ @Synchronized
+ open fun removeMeshes(keys: Iterable) {
+ manager.removeMeshesFor(keys) { key, state -> releaseMeshState(key, state) }
+ }
+
+ open fun refreshMeshes() {
+ val currentMeshKeys = manager.allMeshKeys
this.removeAllMeshes()
if (getMeshFor is Invalidate<*>) getMeshFor.invalidateAll()
- key?.let { createMeshFor(it) }
+ runBlocking {
+ launch {
+ currentMeshKeys.forEach {
+ createMeshFor(it)
+ }
+ }
+ }
}
+}
- private fun MeshGenerator.State.setup() = setupGeneratorState(this)
- @Synchronized
- private fun setupGeneratorState(state: MeshGenerator.State) {
- LOG.debug("Setting up state for mesh key {}", meshKey)
- state.colorProperty().bind(colorProperty)
- state.settings.bindTo(settings)
- state.settings.levelOfDetailProperty.addListener(managerCancelAndUpdate)
- state.settings.coarsestScaleLevelProperty.addListener(managerCancelAndUpdate)
- state.settings.finestScaleLevelProperty.addListener(managerCancelAndUpdate)
+/**
+ * @author Philipp Hanslovsky
+ * @author Igor Pisarev
+ */
+class MeshManagerWithSingleMesh(
+ source: DataSource<*, *>,
+ getBlockList: GetBlockListFor,
+ getMeshFor: GetMeshFor,
+ viewFrustumProperty: ObservableValue,
+ eyeToWorldTransformProperty: ObservableValue,
+ managers: ExecutorService,
+ workers: HashPriorityQueueBasedTaskExecutor,
+ meshViewUpdateQueue: MeshViewUpdateQueue
+) : MeshManager(
+ source,
+ getBlockList,
+ getMeshFor,
+ viewFrustumProperty,
+ eyeToWorldTransformProperty,
+ managers,
+ workers,
+ meshViewUpdateQueue
+) {
+
+ var meshKey: Key? = null
+ @Synchronized get
+ @Synchronized private set
+
+ val colorProperty: ObjectProperty = SimpleObjectProperty(Color.WHITE)
+ var color: Color by colorProperty.nonnull()
+
+ override suspend fun createMeshFor(key: Key) {
+ if (key == meshKey)
+ return
+ this.removeAllMeshes()
+ meshKey = key
+ super.createMeshFor(key)
+
+ }
+
+ override fun setupMeshState(key: Key, state: State) {
+ with(state) {
+ colorProperty().bind(colorProperty)
+ settings.bindTo(this@MeshManagerWithSingleMesh.globalSettings)
+ }
+ super.setupMeshState(key, state)
}
@Synchronized
- private fun MeshGenerator.State.release() {
- colorProperty().unbind()
- settings.unbind()
- settings.levelOfDetailProperty.removeListener(managerCancelAndUpdate)
- settings.coarsestScaleLevelProperty.removeListener(managerCancelAndUpdate)
- settings.finestScaleLevelProperty.removeListener(managerCancelAndUpdate)
+ override fun removeAllMeshes() {
+ meshKey?.let { removeMesh(it) }
}
- companion object {
- private val LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass())
+ @Synchronized
+ override fun removeMesh(key: Key) {
+ meshKey = null
+ super.removeMesh(key)
}
}
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/managed/adaptive/AdaptiveResolutionMeshManager.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/managed/adaptive/AdaptiveResolutionMeshManager.kt
index fa4bd3a42..38efef920 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/managed/adaptive/AdaptiveResolutionMeshManager.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/managed/adaptive/AdaptiveResolutionMeshManager.kt
@@ -23,11 +23,12 @@ import org.janelia.saalfeldlab.util.concurrent.HashPriorityQueueBasedTaskExecuto
import org.janelia.saalfeldlab.util.concurrent.LatestTaskExecutor
import org.slf4j.LoggerFactory
import java.lang.invoke.MethodHandles
-import java.util.Collections
+import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.RejectedExecutionException
+import java.util.function.BiConsumer
import java.util.function.BooleanSupplier
import java.util.function.Consumer
@@ -35,7 +36,7 @@ import java.util.function.Consumer
* @author Philipp Hanslovsky
* @author Igor Pisarev
*/
-class AdaptiveResolutionMeshManager constructor(
+class AdaptiveResolutionMeshManager(
private val source: DataSource<*, *>,
private val getBlockListFor: GetBlockListFor,
private val getMeshFor: GetMeshFor,
@@ -115,25 +116,25 @@ class AdaptiveResolutionMeshManager constructor(
@Synchronized
private fun replaceMesh(key: ObjectKey, cancelAndUpdate: Boolean) {
- val state = removeMeshFor(key) { }
+ val state = removeMeshFor(key) { _, _ -> }
state
- ?.let { s -> createMeshFor(key, cancelAndUpdate = cancelAndUpdate, state = s, stateSetup = { }) }
- ?: createMeshFor(key, cancelAndUpdate = cancelAndUpdate, stateSetup = { })
+ ?.let { s -> createMeshFor(key, cancelAndUpdate = cancelAndUpdate, state = s, stateSetup = { _, _ -> }) }
+ ?: createMeshFor(key, cancelAndUpdate = cancelAndUpdate, stateSetup = { _, _ -> })
}
@Synchronized
private fun replaceAllMeshes() = allMeshKeys.map { replaceMesh(it, false) }.also { cancelAndUpdate() }
- fun removeMeshFor(key: ObjectKey, releaseState: (MeshGenerator.State) -> Unit) = removeMeshFor(key, Consumer { releaseState(it) })
+ fun removeMeshFor(key: ObjectKey, releaseState: (ObjectKey, MeshGenerator.State) -> Unit) = removeMeshFor(key, BiConsumer { key, state -> releaseState(key, state) })
@Synchronized
- fun removeMeshFor(key: ObjectKey, releaseState: Consumer): MeshGenerator.State? {
+ fun removeMeshFor(key: ObjectKey, releaseState: BiConsumer): MeshGenerator.State? {
return meshes.remove(key)?.let { generator ->
unbindService.submit {
generator.interrupt()
generator.unbindFromThis()
generator.root.visibleProperty().unbind()
- releaseState.accept(generator.state)
+ releaseState.accept(key, generator.state)
Platform.runLater {
generator.root.isVisible = false
meshesGroup.children -= generator.root
@@ -143,18 +144,20 @@ class AdaptiveResolutionMeshManager constructor(
}
}
- fun removeMeshesFor(keys: Iterable, releaseState: (MeshGenerator.State) -> Unit) = removeMeshesFor(keys, Consumer { releaseState(it) })
+ fun removeMeshesFor(keys: Iterable, releaseState: (ObjectKey, MeshGenerator.State) -> Unit) = removeMeshesFor(keys, BiConsumer { key, state -> releaseState(key, state) })
@Synchronized
- fun removeMeshesFor(keys: Iterable, releaseState: Consumer) {
- val generators = synchronized(this) { keys.map { meshes.remove(it) } }
+ fun removeMeshesFor(keys: Iterable, releaseState: BiConsumer) {
+ val keysAndGenerators = synchronized(this) { keys.map { it to meshes.remove(it) } }
unbindService.submit {
- val roots = generators.mapNotNull { generator ->
- generator?.interrupt()
- generator?.unbindFromThis()
- generator?.root?.visibleProperty()?.unbind()
- generator?.let { releaseState.accept(it.state) }
- generator?.root
+ val roots = keysAndGenerators.mapNotNull { (key, generator) ->
+ generator?.run {
+ interrupt()
+ unbindFromThis()
+ root?.visibleProperty()?.unbind()
+ let { releaseState.accept(key, state) }
+ root
+ }
}
Platform.runLater {
// The roots list has to be converted to array first and then passed as vararg
@@ -165,16 +168,16 @@ class AdaptiveResolutionMeshManager constructor(
}
}
- fun removeAllMeshes(releaseState: (MeshGenerator.State) -> Unit) = removeAllMeshes(Consumer { releaseState(it) })
+ fun removeAllMeshes(releaseState: (ObjectKey, MeshGenerator.State) -> Unit) = removeAllMeshes(BiConsumer { key, state -> releaseState(key, state) })
- fun removeAllMeshes(releaseState: Consumer) = removeMeshesFor(allMeshKeys, releaseState)
+ fun removeAllMeshes(releaseState: BiConsumer) = removeMeshesFor(allMeshKeys, releaseState)
fun createMeshFor(
key: ObjectKey,
cancelAndUpdate: Boolean,
state: MeshGenerator.State = MeshGenerator.State(),
- stateSetup: (MeshGenerator.State) -> Unit
- ) = createMeshFor(key, cancelAndUpdate, state, Consumer { stateSetup(it) })
+ stateSetup: (ObjectKey, MeshGenerator.State) -> Unit
+ ) = createMeshFor(key, cancelAndUpdate, state, Consumer { stateSetup(key, it) })
@JvmOverloads
fun createMeshFor(
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/ui/MeshSettingsController.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/ui/MeshSettingsController.kt
index 0537b709e..1f1c797e4 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/ui/MeshSettingsController.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/meshes/ui/MeshSettingsController.kt
@@ -1,27 +1,33 @@
package org.janelia.saalfeldlab.paintera.meshes.ui
-import javafx.beans.property.BooleanProperty
-import javafx.beans.property.DoubleProperty
-import javafx.beans.property.IntegerProperty
-import javafx.beans.property.Property
+import javafx.beans.property.*
import javafx.collections.FXCollections
+import javafx.collections.ObservableList
import javafx.event.EventHandler
import javafx.geometry.HPos
import javafx.geometry.Pos
import javafx.scene.Node
import javafx.scene.control.*
import javafx.scene.layout.*
+import javafx.scene.paint.Color
import javafx.scene.shape.CullFace
import javafx.scene.shape.DrawMode
import javafx.stage.Modality
+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.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.MeshManager
import org.janelia.saalfeldlab.paintera.ui.PainteraAlerts
import org.janelia.saalfeldlab.paintera.ui.RefreshButton
+import org.janelia.saalfeldlab.paintera.ui.source.mesh.MeshExporterDialog
+import org.janelia.saalfeldlab.paintera.ui.source.mesh.MeshProgressBar
import kotlin.math.max
import kotlin.math.min
@@ -34,6 +40,7 @@ class MeshSettingsController @JvmOverloads constructor(
private val smoothingLambda: DoubleProperty,
private val smoothingIterations: IntegerProperty,
private val minLabelRatio: DoubleProperty,
+ private val overlap: BooleanProperty,
private val drawMode: Property,
private val cullFace: Property,
private val isVisible: BooleanProperty,
@@ -50,15 +57,14 @@ class MeshSettingsController @JvmOverloads constructor(
meshSettings.smoothingLambdaProperty,
meshSettings.smoothingIterationsProperty,
meshSettings.minLabelRatioProperty,
+ meshSettings.overlapProperty,
meshSettings.drawModeProperty,
meshSettings.cullFaceProperty,
meshSettings.isVisibleProperty,
refreshMeshes
)
- fun createContents(
- addMinLabelRatioSlider: Boolean
- ): GridPane {
+ fun createContents(addMinLabelRatioSlider: Boolean): GridPane {
return GridPane().populateGridWithMeshSettings(
addMinLabelRatioSlider,
CheckBox().also { it.selectedProperty().bindBidirectional(isVisible) },
@@ -75,6 +81,7 @@ class MeshSettingsController @JvmOverloads constructor(
NumericSliderWithField(0.0, 1.00, 1.0).apply { slider.valueProperty().bindBidirectional(smoothingLambda) },
NumericSliderWithField(0, 10, 1).apply { slider.valueProperty().bindBidirectional(smoothingIterations) },
NumericSliderWithField(0.0, 1.0, 0.5).apply { slider.valueProperty().bindBidirectional(minLabelRatio) },
+ CheckBox().also { it.selectedProperty().bindBidirectional(overlap) },
ComboBox(FXCollections.observableArrayList(*DrawMode.values())).apply { valueProperty().bindBidirectional(drawMode) },
ComboBox(FXCollections.observableArrayList(*CullFace.values())).apply { valueProperty().bindBidirectional(cullFace) })
}
@@ -115,6 +122,7 @@ class MeshSettingsController @JvmOverloads constructor(
alignment = Pos.CENTER
}
+
return TitledPane("", contents).apply {
minWidthProperty().set(0.0)
isExpanded = false
@@ -151,6 +159,7 @@ class MeshSettingsController @JvmOverloads constructor(
smoothingLambdaSlider: NumericSliderWithField,
smoothingIterationsSlider: NumericSliderWithField,
minLabelRatioSlider: NumericSliderWithField,
+ overlapToggle: CheckBox,
drawModeChoice: ComboBox,
cullFaceChoice: ComboBox,
): GridPane {
@@ -180,10 +189,11 @@ 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)
}
+ addGridOption("Overlap", overlapToggle)
addGridOption("Draw Mode", drawModeChoice)
addGridOption("Cull Face", cullFaceChoice)
return this
@@ -245,5 +255,126 @@ class MeshSettingsController @JvmOverloads constructor(
}
}
}
+}
+
+open class MeshInfoPane(private val meshInfo: MeshInfo) : TitledPane(null, null) {
+
+ private val hasIndividualSettings = CheckBox("Individual Settings")
+ private var isManaged = SimpleBooleanProperty()
+ private var controller: MeshSettingsController = MeshSettingsController(meshInfo.meshSettings)
+ private var progressBar = MeshProgressBar()
+
+ init {
+ hasIndividualSettings.selectedProperty().addListener { _, _, newv -> isManaged.set(!newv) }
+ isManaged.addListener { _, _, newv -> hasIndividualSettings.isSelected = !newv }
+ isManaged.set(!hasIndividualSettings.isSelected)
+ isExpanded = false
+ expandedProperty().addListener { _, _, isExpanded ->
+ if (isExpanded && content == null) {
+ content = createMeshInfoGrid()
+ }
+ }
+ progressBar.prefWidth = 200.0
+ progressBar.minWidth = Control.USE_PREF_SIZE
+ progressBar.maxWidth = Control.USE_PREF_SIZE
+ progressBar.text = "" + meshInfo.key
+ progressBar.bindTo(meshInfo.progressProperty)
+ graphic = progressBar
+ }
+
+ protected open fun createMeshInfoGrid(grid: GridPane = GridPane()): GridPane {
+
+ val settingsGrid = controller.createContents(meshInfo.manager.source.getDataType() is LabelMultisetType)
+ val individualSettingsBox = VBox(hasIndividualSettings, settingsGrid)
+ individualSettingsBox.spacing = 5.0
+ settingsGrid.visibleProperty().bind(hasIndividualSettings.selectedProperty())
+ settingsGrid.managedProperty().bind(settingsGrid.visibleProperty())
+ hasIndividualSettings.isSelected = !meshInfo.isManagedProperty.get()
+ isManaged.bindBidirectional(meshInfo.isManagedProperty)
+ return grid.apply {
+ add(createExportMeshButton(), 0, rowCount, 2, 1)
+ add(individualSettingsBox, 0, rowCount, 2, 1)
+ }
+ }
+
+ private fun createExportMeshButton(): Button {
+ val exportMeshButton = Button("Export")
+ exportMeshButton.setOnAction { event ->
+ val exportDialog = MeshExporterDialog(meshInfo)
+ val result = exportDialog.showAndWait()
+ if (!result.isPresent) return@setOnAction
+
+ val parameters = result.get()
+ 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(
+ filePath,
+ arrayOf(ids[0].toString()),
+ 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
+ }
+}
+
+abstract class MeshInfoList, K>(
+ protected val meshInfoList: ObservableList = FXCollections.observableArrayList(),
+ manager: MeshManager
+) : ListView(meshInfoList) {
+
+
+ val meshInfos: ReadOnlyListWrapper = ReadOnlyListWrapper(meshInfoList)
+
+ init {
+ setCellFactory { MeshInfoListCell() }
+ manager.managedSettings.isMeshListEnabledProperty.addListener { _, _, enabled ->
+ if (!enabled) {
+ itemsProperty().set(FXCollections.emptyObservableList())
+ } else {
+ itemsProperty().set(meshInfoList)
+ }
+ }
+ }
+
+ open fun meshNodeFactory(meshInfo: T): Node = MeshInfoPane(meshInfo)
+
+ private inner class MeshInfoListCell : ListCell() {
+
+ init {
+ style = "-fx-padding: 0px"
+ }
+
+ override fun updateItem(item: T?, empty: Boolean) {
+ super.updateItem(item, empty)
+ text = null
+ if (empty || item == null) {
+ graphic = null
+ } else {
+ InvokeOnJavaFXApplicationThread {
+ graphic = meshNodeFactory(item).apply {
+ prefWidthProperty().addListener { _, _, pref -> prefWidth = pref.toDouble() }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/IntersectingSourceStatePreferencePaneNode.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/IntersectingSourceStatePreferencePaneNode.kt
index 5b09c4f9d..e05d39dca 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/IntersectingSourceStatePreferencePaneNode.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/IntersectingSourceStatePreferencePaneNode.kt
@@ -1,10 +1,18 @@
package org.janelia.saalfeldlab.paintera.state
+import javafx.geometry.Pos
import javafx.scene.Node
+import javafx.scene.control.Button
import javafx.scene.control.ColorPicker
+import javafx.scene.layout.HBox
+import javafx.scene.layout.VBox
import org.janelia.saalfeldlab.fx.extensions.createNullableValueBinding
+import org.janelia.saalfeldlab.paintera.meshes.MeshExporterObj
+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.ui.source.mesh.MeshExporterDialog
import org.janelia.saalfeldlab.util.Colors
class IntersectingSourceStatePreferencePaneNode(private val state: IntersectingSourceState<*, *>) {
@@ -12,7 +20,7 @@ class IntersectingSourceStatePreferencePaneNode(private val state: IntersectingS
val node: Node
get() {
val manager = state.meshManager
- val settings = manager.settings
+ val settings = manager.globalSettings
return SourceState.defaultPreferencePaneNode(state.compositeProperty()).apply {
children += MeshSettingsController(settings, state::refreshMeshes).createTitledPane(
@@ -29,7 +37,36 @@ class IntersectingSourceStatePreferencePaneNode(private val state: IntersectingS
}
addGridOption("Color", colorPicker)
+ }.also {
+ it.content = VBox(it.content).apply {
+ val exportMeshButton = Button("Export all")
+ exportMeshButton.setOnAction { _ ->
+ val manager = state.meshManager as MeshManagerWithSingleMesh>
+ val key = manager.meshKey!!
+ val exportDialog = MeshExporterDialog(MeshInfo(key, manager))
+ val result = exportDialog.showAndWait()
+ if (result.isPresent) {
+ result.get().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
+ )
+ }
+ }
+ }
+ val buttonBox = HBox(exportMeshButton).also { it.alignment = Pos.BOTTOM_RIGHT }
+ children += buttonBox
+
+ }
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/LabelSourceStateMeshPaneNode.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/LabelSourceStateMeshPaneNode.kt
index feabb2660..943b051dc 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/LabelSourceStateMeshPaneNode.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/LabelSourceStateMeshPaneNode.kt
@@ -2,6 +2,7 @@ package org.janelia.saalfeldlab.paintera.state
import javafx.beans.property.ReadOnlyListProperty
import javafx.collections.ListChangeListener
+import javafx.collections.ObservableList
import javafx.event.EventHandler
import javafx.geometry.Insets
import javafx.geometry.Orientation
@@ -15,19 +16,16 @@ import javafx.scene.paint.Color
import javafx.stage.Modality
import net.imglib2.type.label.LabelMultisetType
import org.janelia.saalfeldlab.fx.extensions.TitledPaneExtensions
+import org.janelia.saalfeldlab.fx.extensions.TitledPaneExtensions.Companion.expandIfEnabled
+import org.janelia.saalfeldlab.fx.extensions.TitledPaneExtensions.Companion.graphicsOnly
import org.janelia.saalfeldlab.fx.extensions.createNonNullValueBinding
-import org.janelia.saalfeldlab.fx.util.InvokeOnJavaFXApplicationThread
import org.janelia.saalfeldlab.paintera.data.DataSource
-import org.janelia.saalfeldlab.paintera.meshes.GlobalMeshProgress
-import org.janelia.saalfeldlab.paintera.meshes.MeshExporterObj
-import org.janelia.saalfeldlab.paintera.meshes.SegmentMeshInfo
-import org.janelia.saalfeldlab.paintera.meshes.SegmentMeshInfos
+import org.janelia.saalfeldlab.paintera.meshes.*
import org.janelia.saalfeldlab.paintera.meshes.managed.MeshManagerWithAssignmentForSegments
import org.janelia.saalfeldlab.paintera.meshes.ui.MeshSettingsController
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.janelia.saalfeldlab.paintera.ui.source.mesh.SegmentMeshExporterDialog
-import org.janelia.saalfeldlab.paintera.ui.source.mesh.SegmentMeshInfoNode
import org.slf4j.LoggerFactory
import java.lang.invoke.MethodHandles
import java.util.*
@@ -38,14 +36,14 @@ typealias TPE = TitledPaneExtensions
class LabelSourceStateMeshPaneNode(
private val source: DataSource<*, *>,
private val manager: MeshManagerWithAssignmentForSegments,
- private val meshInfos: SegmentMeshInfos,
+ private val meshInfos: SegmentMeshInfoList,
) {
val node: Node
get() = makeNode()
private fun makeNode(): Node {
- val settings = manager.settings
+ val settings = manager.globalSettings
val tp = MeshSettingsController(settings, manager::refreshMeshes).createTitledPane(
source.dataType is LabelMultisetType,
manager.managedSettings.meshesEnabledProperty,
@@ -54,78 +52,39 @@ class LabelSourceStateMeshPaneNode(
)
with(tp.content.asVBox()) {
tp.content = this
- children.add(MeshesList(source, manager, meshInfos).node)
+ children.add(MeshSettingsPane(manager, meshInfos))
return tp
}
}
- private class MeshesList(
- private val source: DataSource<*, *>,
+ private class MeshSettingsPane(
private val manager: MeshManagerWithAssignmentForSegments,
- private val meshInfos: SegmentMeshInfos,
- ) {
-
- private class Listener(
- private val meshInfos: ReadOnlyListProperty,
- private val totalProgressBar: MeshProgressBar,
- ) : ListChangeListener {
-
- override fun onChanged(change: ListChangeListener.Change) {
- updateTotalProgressBindings()
- }
-
- private fun updateTotalProgressBindings() {
- val individualProgresses = meshInfos.stream().map { it.meshProgress() }.filter { Objects.nonNull(it) }.collect(Collectors.toList())
- val globalProgress = GlobalMeshProgress(individualProgresses)
- this.totalProgressBar.bindTo(globalProgress)
- }
- }
-
- val node: Node
- get() = createNode()
+ private val meshInfoList: SegmentMeshInfoList,
+ ) : TitledPane("Mesh List", null) {
private val isMeshListEnabledCheckBox = CheckBox()
private val totalProgressBar = MeshProgressBar()
- private fun createNode(): TitledPane {
-
- val meshesInfoList = ListView(meshInfos.readOnlyInfos())
-
- meshesInfoList.setCellFactory {
- object : ListCell() {
-
- init {
- style = "-fx-padding: 0px"
- }
-
- override fun updateItem(item: SegmentMeshInfo?, empty: Boolean) {
- super.updateItem(item, empty)
- text = null
- InvokeOnJavaFXApplicationThread {
- graphic = if (empty || item == null) null else SegmentMeshInfoNode(source, item).node.also { cell ->
- cell.prefWidthProperty().bind(it.prefWidthProperty())
- }
- }
- }
- }
- }
-
+ init {
val exportMeshButton = Button("Export all")
exportMeshButton.setOnAction { _ ->
- val exportDialog = SegmentMeshExporterDialog(meshesInfoList.items)
+ val exportDialog = MeshExporterDialog(meshInfoList.meshInfos as ObservableList>)
val result = exportDialog.showAndWait()
if (result.isPresent) {
result.get().run {
- val ids = segmentId.map { it }.toTypedArray()
- val meshSettings = ids.map { meshInfos.meshSettings().getOrAddMesh(it) }.toTypedArray()
+ val ids = meshKeys.toTypedArray()
+ val meshSettings = ids.map { manager.getSettings(it) }.toTypedArray()
(meshExporter as? MeshExporterObj<*>)?.run {
- val colors : Array = ids.map { meshInfos.getColor(it) }.toTypedArray()
- exportMaterial(filePath, ids.toLongArray(), colors)
+ val colors: Array = 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.getBlockListForLongKey,
+ manager.getBlockListForSegment,
manager.getMeshForLongKey,
meshSettings,
ids,
@@ -137,10 +96,10 @@ class LabelSourceStateMeshPaneNode(
}
val buttonBox = HBox(exportMeshButton).also { it.alignment = Pos.BOTTOM_RIGHT }
- val meshesBox = VBox(meshesInfoList, Separator(Orientation.HORIZONTAL), buttonBox)
- listOf(meshesBox, meshesInfoList).forEach { it.padding = Insets.EMPTY }
+ val meshesBox = VBox(meshInfoList, Separator(Orientation.HORIZONTAL), buttonBox)
+ listOf(meshesBox, meshInfoList).forEach { it.padding = Insets.EMPTY }
- isMeshListEnabledCheckBox.also { it.selectedProperty().bindBidirectional(meshInfos.meshSettings().isMeshListEnabledProperty) }
+ isMeshListEnabledCheckBox.also { it.selectedProperty().bindBidirectional(manager.managedSettings.isMeshListEnabledProperty) }
val helpDialog = PainteraAlerts.alert(Alert.AlertType.INFORMATION, true).apply {
initModality(Modality.NONE)
@@ -160,17 +119,32 @@ class LabelSourceStateMeshPaneNode(
isFillHeight = true
}
- meshInfos.readOnlyInfos().addListener(Listener(meshInfos.readOnlyInfos(), totalProgressBar))
+ meshInfoList.meshInfos.addListener(Listener(meshInfoList.meshInfos, totalProgressBar))
- return TitledPane("Mesh List", meshesBox).apply {
- with(TPE) {
- expandIfEnabled(isMeshListEnabledCheckBox.selectedProperty())
- graphicsOnly(tpGraphics)
- alignment = Pos.CENTER_RIGHT
- meshesInfoList.prefWidthProperty().bind(layoutBoundsProperty().createNonNullValueBinding { it.width - 5 })
- }
+ expandIfEnabled(isMeshListEnabledCheckBox.selectedProperty())
+ graphicsOnly(tpGraphics)
+ alignment = Pos.CENTER_RIGHT
+ meshInfoList.prefWidthProperty().bind(layoutBoundsProperty().createNonNullValueBinding { it.width - 5 })
+ content = meshesBox
+ }
+
+ private class Listener(
+ private val meshInfos: ReadOnlyListProperty,
+ private val totalProgressBar: MeshProgressBar,
+ ) : ListChangeListener {
+
+ override fun onChanged(change: ListChangeListener.Change) {
+ updateTotalProgressBindings()
+ }
+
+ private fun updateTotalProgressBindings() {
+ val individualProgresses = meshInfos.stream().map { it.progressProperty }.filter { Objects.nonNull(it) }.collect(Collectors.toList())
+ val globalProgress = GlobalMeshProgress(individualProgresses)
+ this.totalProgressBar.bindTo(globalProgress)
}
}
+
+
}
diff --git a/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/LabelSourceStatePreferencePaneNode.kt b/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/LabelSourceStatePreferencePaneNode.kt
index f77158120..79febd827 100644
--- a/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/LabelSourceStatePreferencePaneNode.kt
+++ b/src/main/kotlin/org/janelia/saalfeldlab/paintera/state/LabelSourceStatePreferencePaneNode.kt
@@ -2,8 +2,10 @@ package org.janelia.saalfeldlab.paintera.state
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
import javafx.application.Platform
+import javafx.beans.Observable
import javafx.beans.property.ObjectProperty
import javafx.beans.property.SimpleObjectProperty
+import javafx.collections.FXCollections
import javafx.event.EventHandler
import javafx.geometry.Insets
import javafx.geometry.Pos
@@ -35,8 +37,7 @@ import org.janelia.saalfeldlab.paintera.control.selection.SelectedSegments
import org.janelia.saalfeldlab.paintera.data.DataSource
import org.janelia.saalfeldlab.paintera.data.mask.MaskedSource
import org.janelia.saalfeldlab.paintera.data.mask.exception.CannotClearCanvas
-import org.janelia.saalfeldlab.paintera.meshes.ManagedMeshSettings
-import org.janelia.saalfeldlab.paintera.meshes.SegmentMeshInfos
+import org.janelia.saalfeldlab.paintera.meshes.SegmentMeshInfoList
import org.janelia.saalfeldlab.paintera.meshes.managed.MeshManagerWithAssignmentForSegments
import org.janelia.saalfeldlab.paintera.stream.HighlightingStreamConverter
import org.janelia.saalfeldlab.paintera.stream.HighlightingStreamConverterConfigNode
@@ -53,7 +54,6 @@ class LabelSourceStatePreferencePaneNode(
private val composite: ObjectProperty>,
private val converter: HighlightingStreamConverter<*>,
private val meshManager: MeshManagerWithAssignmentForSegments,
- private val meshSettings: ManagedMeshSettings,
private val brushProperties: BrushProperties?
) {
@@ -65,10 +65,25 @@ class LabelSourceStatePreferencePaneNode(
val node: Node
get() {
val box = SourceState.defaultPreferencePaneNode(composite)
+
+ val observableSelectedSegmentsList = FXCollections.observableArrayList