Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/mesh work #520

Merged
merged 6 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ canvases
*.class
*.java-version
*dockerfile
/meshes
14 changes: 12 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@

<!-- kotlin -->
<kotlin.compiler.jvmTarget>11</kotlin.compiler.jvmTarget>
<kotlin.version>1.8.0</kotlin.version>
<kotlin.version>1.9.20</kotlin.version>
<kotlin.compiler.incremental>true</kotlin.compiler.incremental>

<!-- NB: Convert README.md to html during compilation. -->
Expand Down Expand Up @@ -88,6 +88,11 @@


<dependencies>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>dev.dirs</groupId>
<artifactId>directories</artifactId>
Expand Down Expand Up @@ -151,6 +156,11 @@
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</dependency>
<dependency>
<groupId>io.github.oshai</groupId>
<artifactId>kotlin-logging-jvm</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.scijava</groupId>
<artifactId>scijava-common</artifactId>
Expand Down Expand Up @@ -541,7 +551,7 @@
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>1.6.4</version>
<version>1.7.3</version>
<scope>compile</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class ManagedMeshSettings {
public class ManagedMeshSettings<K> {

public static final String MESH_SETTINGS_KEY = "meshSettings";

Expand All @@ -35,9 +35,9 @@ public class ManagedMeshSettings {

private final MeshSettings globalSettings;

private final Map<Long, MeshSettings> individualSettings = new HashMap<>();
private final Map<K, MeshSettings> individualSettings = new HashMap<>();

private final HashMap<Long, SimpleBooleanProperty> isManagedProperties = new HashMap<>();
private final HashMap<K, SimpleBooleanProperty> isManagedProperties = new HashMap<>();

private final SimpleBooleanProperty isMeshListEnabled = new SimpleBooleanProperty(DEFAULT_IS_MESH_LIST_ENABLED);

Expand All @@ -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.
* <p>
* 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);
Expand All @@ -69,25 +80,44 @@ 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() {

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);
}
Expand All @@ -108,9 +138,9 @@ public void clearSettings() {
this.individualSettings.clear();
}

public void keepOnlyMatching(final Predicate<Long> filter) {
public void keepOnlyMatching(final Predicate<K> filter) {

final Set<Long> toBeRemoved = isManagedProperties
final Set<K> toBeRemoved = isManagedProperties
.keySet()
.stream()
.filter(filter.negate())
Expand All @@ -125,16 +155,16 @@ public static Serializer jsonSerializer() {
return new Serializer();
}

public void set(final ManagedMeshSettings that) {
public void set(final ManagedMeshSettings<K> that) {

clearSettings();
globalSettings.setTo(that.globalSettings);
isMeshListEnabled.set(that.isMeshListEnabled.get());
meshesEnabled.set(that.meshesEnabled.get());
for (final Entry<Long, MeshSettings> entry : that.individualSettings.entrySet()) {
final Long id = entry.getKey();
for (final Entry<K, MeshSettings> 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());
}
Expand Down Expand Up @@ -188,24 +218,26 @@ 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))
.map(JsonElement::getAsBoolean)
.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);
}
}

Expand All @@ -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<Object> managedMeshSettings = (ManagedMeshSettings<Object>) 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<Long, MeshSettings> 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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/
Expand Down Expand Up @@ -381,67 +377,59 @@ 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];

final boolean[] vertexVals = new boolean[8];
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<B> bundledInput = new BundleView<>(Views.interval(input, interval));
final IntervalView<RandomAccess<B>> 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();
}

Expand Down
11 changes: 8 additions & 3 deletions src/main/java/org/janelia/saalfeldlab/paintera/meshes/Mesh.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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},
Expand Down
Loading
Loading