From c5401e637d2d111bbdfc8f3de76ac54c7a09b5d2 Mon Sep 17 00:00:00 2001 From: Pete Date: Wed, 8 Jun 2022 10:13:55 +0100 Subject: [PATCH 1/4] Update version Include gradle upgrade, so it will build with Java 17 --- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- src/main/java/qupath/ext/stardist/StarDistExtension.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 8798aba..365318c 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { // To create a shadow/fat jar, including dependencies id 'com.github.johnrengelman.shadow' version '7.0.0' // To manage included native libraries - id 'org.bytedeco.gradle-javacpp-platform' version '1.5.6' + id 'org.bytedeco.gradle-javacpp-platform' version '1.5.7' } repositories { @@ -26,7 +26,7 @@ ext.moduleName = 'qupath.extension.stardist' description = 'QuPath extension to use StarDist' -version = "0.3.0" +version = "0.3.1-SNAPSHOT" dependencies { def qupathVersion = "0.3.0" // For now diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0f80bbf..aa991fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/qupath/ext/stardist/StarDistExtension.java b/src/main/java/qupath/ext/stardist/StarDistExtension.java index f18db05..8687425 100644 --- a/src/main/java/qupath/ext/stardist/StarDistExtension.java +++ b/src/main/java/qupath/ext/stardist/StarDistExtension.java @@ -51,7 +51,7 @@ public String getDescription() { @Override public Version getQuPathVersion() { - return Version.parse("0.3.0-rc2"); + return Version.parse("0.3.0"); } @Override From 3d5d3821a0cd6b0e24b426867173a82ce22ec7e6 Mon Sep 17 00:00:00 2001 From: Pete Date: Wed, 8 Jun 2022 10:14:27 +0100 Subject: [PATCH 2/4] Fix invalid scaling operation Fixes https://github.com/qupath/qupath-extension-stardist/issues/17 --- src/main/java/qupath/ext/stardist/StarDist2D.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/qupath/ext/stardist/StarDist2D.java b/src/main/java/qupath/ext/stardist/StarDist2D.java index d443345..e8631b0 100644 --- a/src/main/java/qupath/ext/stardist/StarDist2D.java +++ b/src/main/java/qupath/ext/stardist/StarDist2D.java @@ -537,7 +537,7 @@ public Builder inputAdd(double... values) { * @return this builder */ public Builder inputScale(double... values) { - this.ops.add(ImageOps.Core.subtract(values)); + this.ops.add(ImageOps.Core.multiply(values)); return this; } From a07a10d57c4164a6c041bb223c40710644ac227b Mon Sep 17 00:00:00 2001 From: Pete Date: Thu, 9 Jun 2022 10:39:29 +0100 Subject: [PATCH 3/4] Make StarDist2D AutoCloseable This makes it easier to free up resources, addressing https://github.com/qupath/qupath-extension-stardist/issues/11 (an explicit 'close' call is still needed) --- .../java/qupath/ext/stardist/StarDist2D.java | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/main/java/qupath/ext/stardist/StarDist2D.java b/src/main/java/qupath/ext/stardist/StarDist2D.java index e8631b0..2cfd736 100644 --- a/src/main/java/qupath/ext/stardist/StarDist2D.java +++ b/src/main/java/qupath/ext/stardist/StarDist2D.java @@ -17,6 +17,7 @@ package qupath.ext.stardist; import java.awt.image.BufferedImage; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -47,6 +48,7 @@ import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.Location; import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.index.strtree.STRtree; @@ -97,7 +99,7 @@ * * @author Pete Bankhead (this implementation, but based on the others) */ -public class StarDist2D { +public class StarDist2D implements AutoCloseable { private final static Logger logger = LoggerFactory.getLogger(StarDist2D.class); @@ -922,10 +924,14 @@ private PathObject convertToObject(PotentialNucleus nucleus, ImagePlane plane, d PathObject pathObject; if (cellExpansion > 0) { var geomCell = CellTools.estimateCellBoundary(geomNucleus, cellExpansion, cellConstrainScale); - if (mask != null) + if (mask != null) { geomCell = GeometryTools.attemptOperation(geomCell, g -> g.intersection(mask)); + } geomCell = simplify(geomCell); + // Intersection with complex mask could give linestrings - which we want to remove + geomCell = GeometryTools.ensurePolygonal(geomCell); + if (geomCell.isEmpty()) { logger.warn("Empty cell boundary at {} will be skipped", nucleus.geometry.getCentroid()); return null; @@ -1069,6 +1075,13 @@ private List detectObjectsForTile(ImageDataOp op, DnnModel requestPadded = RegionRequest.createInstance(server.getPath(), downsample, x1, y1, x2-x1, y2-y1); } +// // Hack to visualize the tiles that are computed (for debugging) +// imageData.getHierarchy().addPathObject( +// PathObjects.createAnnotationObject( +// ROIs.createRectangleROI(request), +// PathClassFactory.getPathClass("Temporary") +// )); + try (@SuppressWarnings("unchecked") var scope = new PointerScope()) { Mat mat; @@ -1350,6 +1363,11 @@ private List filterNuclei(List potentialNucl if (envelope.intersects(env) && nucleus.geometry.intersects(nucleus2.geometry)) { // Retain the nucleus only if it is not fragmented, or less than half its original area var difference = nucleus2.geometry.difference(nucleus.geometry); + + // Discard linestrings + if (difference instanceof GeometryCollection) + difference = GeometryTools.ensurePolygonal(difference); + if (difference instanceof Polygon && difference.getArea() > nucleus2.fullArea / 2.0) nucleus2.geometry = difference; else { @@ -1406,5 +1424,21 @@ int getClassification() { } } + + + /** + * Close and cleanup resources. + * + * @implNote In practice, this means close any {@link DnnModel} stored if it is an instance of + * {@link Closeable} or {@link AutoCloseable}. + * This can be important to avoid memory leaks, particularly if using a GPU. + */ + @Override + public void close() throws Exception { + if (dnn instanceof Closeable) { + ((Closeable) dnn).close(); + } else if (dnn instanceof AutoCloseable) + ((AutoCloseable) dnn).close(); + } } From f03ea050e2e4f2e3089deb631ddebad8baa261b8 Mon Sep 17 00:00:00 2001 From: Pete Date: Thu, 9 Jun 2022 12:07:32 +0100 Subject: [PATCH 4/4] Fix broken geometries created by cell expansion --- src/main/java/qupath/ext/stardist/StarDist2D.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/qupath/ext/stardist/StarDist2D.java b/src/main/java/qupath/ext/stardist/StarDist2D.java index 2cfd736..9b88a40 100644 --- a/src/main/java/qupath/ext/stardist/StarDist2D.java +++ b/src/main/java/qupath/ext/stardist/StarDist2D.java @@ -51,6 +51,7 @@ import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.Location; import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.geom.util.GeometryFixer; import org.locationtech.jts.index.strtree.STRtree; import org.locationtech.jts.simplify.VWSimplifier; import org.slf4j.Logger; @@ -923,7 +924,17 @@ private PathObject convertToObject(PotentialNucleus nucleus, ImagePlane plane, d var geomNucleus = simplify(nucleus.geometry); PathObject pathObject; if (cellExpansion > 0) { +// cellExpansion = geomNucleus.getPrecisionModel().makePrecise(cellExpansion); +// cellExpansion = Math.round(cellExpansion); var geomCell = CellTools.estimateCellBoundary(geomNucleus, cellExpansion, cellConstrainScale); + if (geomCell instanceof GeometryCollection && geomNucleus instanceof Polygon) { + if (!geomCell.isValid()) { + // Sometimes buffer creates invalid geometries + // (Note that the fix should already be applied by estimateCellBoundary in v0.4.0) + geomCell = GeometryFixer.fix(geomCell); + logger.debug("Used GeometryFixer to fix an invalid cell boundary geometry"); + } + } if (mask != null) { geomCell = GeometryTools.attemptOperation(geomCell, g -> g.intersection(mask)); }