From 1996a31a32cf9cdad98072b39b692095af1e9ce9 Mon Sep 17 00:00:00 2001 From: Thomas <thomas.musset@pasteur.fr> Date: Thu, 27 Jun 2024 16:15:11 +0200 Subject: [PATCH] updated pom to v2.0.0-a.1, fix classes accordingly to new architecture, added icon, updated .gitignore --- .gitignore | 41 ++++++++++++++++-- pom.xml | 9 ++-- .../java/plugins/adufour/quickhull/Face.java | 25 ++++++----- .../plugins/adufour/quickhull/FaceList.java | 4 +- .../plugins/adufour/quickhull/HalfEdge.java | 11 +++-- .../plugins/adufour/quickhull/QuickHull.java | 10 +++-- .../adufour/quickhull/QuickHull2D.java | 19 ++++---- .../adufour/quickhull/QuickHull3D.java | 39 +++++++++-------- .../plugins/adufour/quickhull/Vertex.java | 8 +++- .../plugins/adufour/quickhull/VertexList.java | 12 ++--- src/main/resources/quickhull.png | Bin 0 -> 8661 bytes 11 files changed, 116 insertions(+), 62 deletions(-) create mode 100644 src/main/resources/quickhull.png diff --git a/.gitignore b/.gitignore index b0a9905..57f16fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,41 @@ -.idea/ -.settings/ -build/ +/build* +/workspace +setting.xml +release/ target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +icy.log + +### IntelliJ IDEA ### +.idea/ +*.iws *.iml +*.ipr + +### Eclipse ### +.apt_generated .classpath +.factorypath .project -**/.DS_Store \ No newline at end of file +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +**/.DS_Store +Icon? \ No newline at end of file diff --git a/pom.xml b/pom.xml index b5454c5..b3cfb94 100644 --- a/pom.xml +++ b/pom.xml @@ -7,13 +7,11 @@ <parent> <groupId>org.bioimageanalysis.icy</groupId> <artifactId>pom-icy</artifactId> - <version>2.2.0</version> + <version>3.0.0-a.1</version> </parent> <artifactId>quickhull</artifactId> - <version>2.0.0</version> - - <packaging>jar</packaging> + <version>2.0.0-a.1</version> <name>QuickHull</name> <description> @@ -30,8 +28,7 @@ <repositories> <repository> <id>icy</id> - <url>https://icy-nexus.pasteur.fr/repository/Icy/</url> + <url>https://nexus-icy.pasteur.cloud/repository/icy/</url> </repository> </repositories> - </project> \ No newline at end of file diff --git a/src/main/java/plugins/adufour/quickhull/Face.java b/src/main/java/plugins/adufour/quickhull/Face.java index 042eea7..2afe11a 100644 --- a/src/main/java/plugins/adufour/quickhull/Face.java +++ b/src/main/java/plugins/adufour/quickhull/Face.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,6 +18,8 @@ package plugins.adufour.quickhull; +import org.jetbrains.annotations.NotNull; + import javax.vecmath.Vector3d; /** @@ -30,7 +32,7 @@ import javax.vecmath.Vector3d; * * @author John E. Lloyd, Fall 2004 */ -class Face { +public class Face { HalfEdge he0; private final Vector3d normal; double area; @@ -49,7 +51,7 @@ class Face { Vertex outside; - public void computeCentroid(final Vector3d centroid) { + public void computeCentroid(final @NotNull Vector3d centroid) { centroid.set(0, 0, 0); HalfEdge he = he0; do { @@ -95,7 +97,7 @@ class Face { } } - public void computeNormal(final Vector3d normal) { + public void computeNormal(final @NotNull Vector3d normal) { HalfEdge he1 = he0.next; HalfEdge he2 = he1.next; @@ -153,7 +155,7 @@ class Face { planeOffset = normal.dot(centroid); } - public static Face createTriangle(final Vertex v0, final Vertex v1, final Vertex v2) { + public static @NotNull Face createTriangle(final Vertex v0, final Vertex v1, final Vertex v2) { return createTriangle(v0, v1, v2, 0); } @@ -164,7 +166,7 @@ class Face { * @param v1 second vertex * @param v2 third vertex */ - public static Face createTriangle(final Vertex v0, final Vertex v1, final Vertex v2, final double minArea) { + public static @NotNull Face createTriangle(final Vertex v0, final Vertex v1, final Vertex v2, final double minArea) { final Face face = new Face(); final HalfEdge he0 = new HalfEdge(v0, face); final HalfEdge he1 = new HalfEdge(v1, face); @@ -184,7 +186,7 @@ class Face { return face; } - public static Face create(final Vertex[] vtxArray, final int[] indices) { + public static @NotNull Face create(final Vertex[] vtxArray, final int @NotNull [] indices) { final Face face = new Face(); HalfEdge hePrev = null; for (final int j : indices) { @@ -261,7 +263,7 @@ class Face { * @param p the point * @return distance from the point to the plane */ - public double distanceToPlane(final Vector3d p) { + public double distanceToPlane(final @NotNull Vector3d p) { return normal.x * p.x + normal.y * p.y + normal.z * p.z - planeOffset; } @@ -297,7 +299,7 @@ class Face { return s.toString(); } - public void getVertexIndices(final int[] idxs) { + public void getVertexIndices(final int @NotNull [] idxs) { HalfEdge he = he0; int i = 0; do { @@ -306,7 +308,7 @@ class Face { } while (he != he0); } - private Face connectHalfEdges(final HalfEdge hedgePrev, final HalfEdge hedge) { + private Face connectHalfEdges(final @NotNull HalfEdge hedgePrev, final @NotNull HalfEdge hedge) { Face discardedFace = null; if (hedgePrev.oppositeFace() == hedge.oppositeFace()) { // then there is a redundant edge that we can get rid off @@ -389,7 +391,7 @@ class Face { } - public int mergeAdjacentFace(final HalfEdge hedgeAdj, final Face[] discarded) { + public int mergeAdjacentFace(final @NotNull HalfEdge hedgeAdj, final Face @NotNull [] discarded) { final Face oppFace = hedgeAdj.oppositeFace(); int numDiscarded = 0; @@ -481,6 +483,5 @@ class Face { for (Face face = face0; face != null; face = face.next) { face.checkConsistency(); } - } } diff --git a/src/main/java/plugins/adufour/quickhull/FaceList.java b/src/main/java/plugins/adufour/quickhull/FaceList.java index 1713a12..be6d7b2 100644 --- a/src/main/java/plugins/adufour/quickhull/FaceList.java +++ b/src/main/java/plugins/adufour/quickhull/FaceList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -21,7 +21,7 @@ package plugins.adufour.quickhull; /** * Maintains a single-linked list of faces for use by QuickHull3D */ -class FaceList { +public class FaceList { private Face head; private Face tail; diff --git a/src/main/java/plugins/adufour/quickhull/HalfEdge.java b/src/main/java/plugins/adufour/quickhull/HalfEdge.java index 1290f57..e31d21e 100644 --- a/src/main/java/plugins/adufour/quickhull/HalfEdge.java +++ b/src/main/java/plugins/adufour/quickhull/HalfEdge.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,6 +18,9 @@ package plugins.adufour.quickhull; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + import javax.vecmath.Vector3d; /** @@ -25,7 +28,7 @@ import javax.vecmath.Vector3d; * * @author John E. Lloyd, Fall 2004 */ -class HalfEdge { +public class HalfEdge { /** * The vertex associated with the head of this half-edge. */ @@ -58,11 +61,13 @@ class HalfEdge { * @param v head vertex * @param f left-hand triangular face */ + @Contract(pure = true) public HalfEdge(final Vertex v, final Face f) { vertex = v; face = f; } + @Contract(pure = true) public HalfEdge() { } @@ -125,7 +130,7 @@ class HalfEdge { * * @param edge opposite half-edge */ - public void setOpposite(final HalfEdge edge) { + public void setOpposite(final @NotNull HalfEdge edge) { opposite = edge; edge.opposite = this; } diff --git a/src/main/java/plugins/adufour/quickhull/QuickHull.java b/src/main/java/plugins/adufour/quickhull/QuickHull.java index 1be8ced..5277b47 100644 --- a/src/main/java/plugins/adufour/quickhull/QuickHull.java +++ b/src/main/java/plugins/adufour/quickhull/QuickHull.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,8 +18,10 @@ package plugins.adufour.quickhull; -import icy.plugin.abstract_.Plugin; -import icy.plugin.interface_.PluginLibrary; +import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginIcon; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.bioimageanalysis.icy.extension.plugin.interface_.PluginLibrary; /** * Main class of the QuickHull library for Icy @@ -29,6 +31,8 @@ import icy.plugin.interface_.PluginLibrary; * * @author Alexandre Dufour */ +@IcyPluginName("QuickHull") +@IcyPluginIcon(path = "/quickhull.png") public class QuickHull extends Plugin implements PluginLibrary { } diff --git a/src/main/java/plugins/adufour/quickhull/QuickHull2D.java b/src/main/java/plugins/adufour/quickhull/QuickHull2D.java index 2f2542b..df97cf9 100644 --- a/src/main/java/plugins/adufour/quickhull/QuickHull2D.java +++ b/src/main/java/plugins/adufour/quickhull/QuickHull2D.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,21 +18,24 @@ package plugins.adufour.quickhull; -import icy.plugin.abstract_.Plugin; +import org.bioimageanalysis.icy.extension.plugin.abstract_.Plugin; +import org.bioimageanalysis.icy.extension.plugin.annotation_.IcyPluginName; +import org.jetbrains.annotations.NotNull; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.List; +@IcyPluginName("QuickHull 2D") public class QuickHull2D extends Plugin { - public static List<Point2D> computeConvexEnvelope(final List<Point2D> points) { + public static @NotNull List<Point2D> computeConvexEnvelope(final @NotNull List<Point2D> points) { final ArrayList<Point2D> envelope = new ArrayList<>(); // find two points: right (bottom) and left (top) - Point2D l = points.get(0); - Point2D r = points.get(0); + Point2D l = points.getFirst(); + Point2D r = points.getFirst(); for (int i = 1; i < points.size(); i++) { final Point2D p = points.get(i); @@ -69,8 +72,8 @@ public class QuickHull2D extends Plugin { return envelope; } - private static void quickhull(final ArrayList<Point2D> envelope, final Point2D a, final Point2D b, final ArrayList<Point2D> neighbors) { - if (neighbors.size() == 0) return; + private static void quickhull(final ArrayList<Point2D> envelope, final Point2D a, final Point2D b, final @NotNull ArrayList<Point2D> neighbors) { + if (neighbors.isEmpty()) return; final Point2D c = farthestpoint(a, b, neighbors); @@ -95,7 +98,7 @@ public class QuickHull2D extends Plugin { quickhull(envelope, c, b, al2); } - private static Point2D farthestpoint(final Point2D a, final Point2D b, final ArrayList<Point2D> points) { + private static Point2D farthestpoint(final Point2D a, final Point2D b, final @NotNull ArrayList<Point2D> points) { double maxD = -1; Point2D maxP = null; diff --git a/src/main/java/plugins/adufour/quickhull/QuickHull3D.java b/src/main/java/plugins/adufour/quickhull/QuickHull3D.java index dac0a2f..d370f79 100644 --- a/src/main/java/plugins/adufour/quickhull/QuickHull3D.java +++ b/src/main/java/plugins/adufour/quickhull/QuickHull3D.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -17,6 +17,10 @@ */ package plugins.adufour.quickhull; +import org.bioimageanalysis.icy.system.logging.IcyLogger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import javax.vecmath.Point3d; import javax.vecmath.Vector3d; import java.io.*; @@ -224,7 +228,7 @@ public class QuickHull3D { return explicitTolerance; } - private void addPointToFace(final Vertex vtx, final Face face) { + private void addPointToFace(final @NotNull Vertex vtx, final @NotNull Face face) { vtx.face = face; if (face.outside == null) { @@ -236,7 +240,7 @@ public class QuickHull3D { face.outside = vtx; } - private void removePointFromFace(final Vertex vtx, final Face face) { + private void removePointFromFace(final Vertex vtx, final @NotNull Face face) { if (vtx == face.outside) { if (vtx.next != null && vtx.next.face == face) { face.outside = vtx.next; @@ -248,7 +252,7 @@ public class QuickHull3D { claimed.delete(vtx); } - private Vertex removeAllPointsFromFace(final Face face) { + private @Nullable Vertex removeAllPointsFromFace(final @NotNull Face face) { if (face.outside != null) { Vertex end = face.outside; while (end.next != null && end.next.face == face) { @@ -293,7 +297,7 @@ public class QuickHull3D { build(points, points.length); } - private HalfEdge findHalfEdge(final Vertex tail, final Vertex head) { + private @Nullable HalfEdge findHalfEdge(final Vertex tail, final Vertex head) { // brute force ... OK, since setHull is not used much for (final Face face : faces) { final HalfEdge he = face.findEdge(tail, head); @@ -322,7 +326,7 @@ public class QuickHull3D { } } - private void printQhullErrors(final Process proc) throws IOException { + private void printQhullErrors(final @NotNull Process proc) throws IOException { boolean wrote = false; final InputStream es = proc.getErrorStream(); while (es.available() > 0) { @@ -340,7 +344,7 @@ public class QuickHull3D { commandStr += " -Qt"; } try { - final Process proc = Runtime.getRuntime().exec(commandStr); + final Process proc = Runtime.getRuntime().exec(commandStr); // FIXME change command from String to String[] final PrintStream ps = new PrintStream(proc.getOutputStream()); final StreamTokenizer stok = new StreamTokenizer(new InputStreamReader(proc.getInputStream())); @@ -374,7 +378,7 @@ public class QuickHull3D { System.out.println("Expecting face index"); System.exit(1); } - indexList.add(0, (int) stok.nval); + indexList.addFirst((int) stok.nval); } faceIndices[i] = new int[indexList.size()]; int k = 0; @@ -385,8 +389,9 @@ public class QuickHull3D { setHull(coords, nump, faceIndices, numf); } catch (final Exception e) { - e.printStackTrace(); - System.exit(1); + IcyLogger.fatal(this.getClass(), e, e.getLocalizedMessage()); + //System.exit(1); + throw new RuntimeException(e); } } @@ -878,7 +883,7 @@ public class QuickHull3D { } } - private void getFaceIndices(final int[] indices, final Face face, final int flags) { + private void getFaceIndices(final int[] indices, final @NotNull Face face, final int flags) { final boolean ccw = ((flags & CLOCKWISE) == 0); final boolean indexedFromOne = ((flags & INDEXED_FROM_ONE) != 0); final boolean pointRelative = ((flags & POINT_RELATIVE) != 0); @@ -956,11 +961,11 @@ public class QuickHull3D { private static final int NONCONVEX_WRT_LARGER_FACE = 1; private static final int NONCONVEX = 2; - protected double oppFaceDistance(final HalfEdge he) { + protected double oppFaceDistance(final @NotNull HalfEdge he) { return he.face.distanceToPlane(he.opposite.face.getCentroid()); } - private boolean doAdjacentMerge(final Face face, final int mergeType) { + private boolean doAdjacentMerge(final @NotNull Face face, final int mergeType) { HalfEdge hedge = face.he0; boolean convex = true; @@ -1050,14 +1055,14 @@ public class QuickHull3D { } while (edge != edge0); } - private HalfEdge addAdjoiningFace(final Vertex eyeVtx, final HalfEdge he) { + private HalfEdge addAdjoiningFace(final Vertex eyeVtx, final @NotNull HalfEdge he) { final Face face = Face.createTriangle(eyeVtx, he.tail(), he.head()); faces.add(face); face.getEdge(-1).setOpposite(he.getOpposite()); return face.getEdge(0); } - protected void addNewFaces(final FaceList newFaces, final Vertex eyeVtx, final Vector<HalfEdge> horizon) { + protected void addNewFaces(final @NotNull FaceList newFaces, final Vertex eyeVtx, final @NotNull Vector<HalfEdge> horizon) { newFaces.clear(); HalfEdge hedgeSidePrev = null; @@ -1153,7 +1158,7 @@ public class QuickHull3D { } } - private void markFaceVertices(final Face face, final int mark) { + private void markFaceVertices(final @NotNull Face face, final int mark) { final HalfEdge he0 = face.getFirstEdge(); HalfEdge he = he0; do { @@ -1189,7 +1194,7 @@ public class QuickHull3D { } } - protected boolean checkFaceConvexity(final Face face, final double tol, final PrintStream ps) { + protected boolean checkFaceConvexity(final @NotNull Face face, final double tol, final PrintStream ps) { double dist; HalfEdge he = face.he0; do { diff --git a/src/main/java/plugins/adufour/quickhull/Vertex.java b/src/main/java/plugins/adufour/quickhull/Vertex.java index b652810..cdbbfc3 100644 --- a/src/main/java/plugins/adufour/quickhull/Vertex.java +++ b/src/main/java/plugins/adufour/quickhull/Vertex.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,6 +18,8 @@ package plugins.adufour.quickhull; +import org.jetbrains.annotations.Contract; + import javax.vecmath.Vector3d; /** @@ -26,7 +28,7 @@ import javax.vecmath.Vector3d; * * @author John E. Lloyd, Fall 2004 */ -class Vertex { +public class Vertex { /** * Spatial point associated with this vertex. */ @@ -55,6 +57,7 @@ class Vertex { /** * Constructs a vertex and sets its coordinates to 0. */ + @Contract(pure = true) public Vertex() { pnt = new Vector3d(); } @@ -63,6 +66,7 @@ class Vertex { * Constructs a vertex with the specified coordinates * and index. */ + @Contract(pure = true) public Vertex(final double x, final double y, final double z, final int idx) { pnt = new Vector3d(x, y, z); index = idx; diff --git a/src/main/java/plugins/adufour/quickhull/VertexList.java b/src/main/java/plugins/adufour/quickhull/VertexList.java index f6d6247..2b6816f 100644 --- a/src/main/java/plugins/adufour/quickhull/VertexList.java +++ b/src/main/java/plugins/adufour/quickhull/VertexList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2023. Institut Pasteur. + * Copyright (c) 2010-2024. Institut Pasteur. * * This file is part of Icy. * Icy is free software: you can redistribute it and/or modify @@ -18,10 +18,12 @@ package plugins.adufour.quickhull; +import org.jetbrains.annotations.NotNull; + /** * Maintains a double-linked list of vertices for use by QuickHull3D */ -class VertexList { +public class VertexList { private Vertex head; private Vertex tail; @@ -67,7 +69,7 @@ class VertexList { /** * Deletes a vertex from this list. */ - public void delete(final Vertex vtx) { + public void delete(final @NotNull Vertex vtx) { if (vtx.prev == null) { head = vtx.next; } @@ -85,7 +87,7 @@ class VertexList { /** * Deletes a chain of vertices from this list. */ - public void delete(final Vertex vtx1, final Vertex vtx2) { + public void delete(final @NotNull Vertex vtx1, final Vertex vtx2) { if (vtx1.prev == null) { head = vtx2.next; } @@ -103,7 +105,7 @@ class VertexList { /** * Inserts a vertex into this list before another specificed vertex. */ - public void insertBefore(final Vertex vtx, final Vertex next) { + public void insertBefore(final @NotNull Vertex vtx, final @NotNull Vertex next) { vtx.prev = next.prev; if (next.prev == null) { head = vtx; diff --git a/src/main/resources/quickhull.png b/src/main/resources/quickhull.png new file mode 100644 index 0000000000000000000000000000000000000000..9c8c783a833888f37d0123b8de62ecbe8cdd7ea6 GIT binary patch literal 8661 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 literal 0 HcmV?d00001 -- GitLab