- * Adapted from Yury Petrov's Ellipsoid
- * Fit MATLAB function and KalebKE ellipsoidfit.
- *
- * @author Jean-Yves Tinevez
- */
-public class EllipsoidFitter
-{
-
- /**
- * The results of fitting an ellpsoid to a mesh or a collection of points.
- */
- public static final class EllipsoidFit
- {
- /** The ellipsoid center. */
- public final RealLocalizable center;
-
- /** The eigenvector of the smallest axis of the ellipsoid. */
- public final RealLocalizable ev1;
-
- /** The eigenvector of the middle axis of the ellipsoid. */
- public final RealLocalizable ev2;
-
- /** The eigenvector of the largest axis of the ellipsoid. */
- public final RealLocalizable ev3;
-
- /** The radius of the smallest axis of the ellipsoid. */
- public final double r1;
-
- /** The radius of the middle axis of the ellipsoid. */
- public final double r2;
-
- /** The radius of the largest axis of the ellipsoid. */
- public final double r3;
-
- private EllipsoidFit( final RealLocalizable center,
- final RealLocalizable ev1,
- final RealLocalizable ev2,
- final RealLocalizable ev3,
- final double r1,
- final double r2,
- final double r3 )
- {
- this.center = center;
- this.ev1 = ev1;
- this.ev2 = ev2;
- this.ev3 = ev3;
- this.r1 = r1;
- this.r2 = r2;
- this.r3 = r3;
- }
-
- @Override
- public String toString()
- {
- final StringBuilder str = new StringBuilder( super.toString() );
- str.append( "\n - center: " + Util.printCoordinates( center ) );
- str.append( String.format( "\n - axis 1: radius = %.2f, vector = %s", r1, ev1 ) );
- str.append( String.format( "\n - axis 2: radius = %.2f, vector = %s", r2, ev2 ) );
- str.append( String.format( "\n - axis 3: radius = %.2f, vector = %s", r3, ev3 ) );
- return str.toString();
- }
- }
-
- private static final DefaultConvexHull3D cHull = new DefaultConvexHull3D();
-
- /**
- * Fit an ellipsoid to the convex-Hull of a 3D mesh.
- *
- * @param mesh
- * the mesh to fit.
- * @return the fit results.
- */
- public static final EllipsoidFit fit( final Mesh mesh )
- {
- final Mesh ch = cHull.calculate( mesh );
- return fitOnConvexHull( ch );
- }
-
- /**
- * Fit an ellipsoid to a 3D mesh, assuming it is the convex-Hull.
- *
- * @param mesh
- * the convex-Hull of the mesh to fit.
- * @return the fit results.
- */
- public static final EllipsoidFit fitOnConvexHull( final Mesh mesh )
- {
- return fit( mesh.vertices(), ( int ) mesh.vertices().size() );
- }
-
- /**
- * Fit an ellipsoid to a collection of 3D points.
- *
- * @param points
- * an iterable over the points to fit.
- * @param nPoints
- * the number of points to include in the fit. The fit will
- * consider at most the first nPoints of the iterable, or all the
- * points in the iterable, whatever comes first.
- * @return the fit results.
- */
- public static EllipsoidFit fit( final Iterable< ? extends RealLocalizable > points, final int nPoints )
- {
- final RealVector V = solve( points, nPoints );
-
- // To algebraix form.
- final RealMatrix A = toAlgebraicForm( V );
-
- // Find the center of the ellipsoid.
- final RealVector C = findCenter( A );
-
- // Translate the algebraic form of the ellipsoid to the center.
- final RealMatrix R = translateToCenter( C, A );
-
- // Ellipsoid eigenvectors and eigenvalues.
- final EllipsoidFit fit = getFit( R, C );
- return fit;
- }
-
- private static EllipsoidFit getFit( final RealMatrix R, final RealVector C )
- {
- final RealMatrix subr = R.getSubMatrix( 0, 2, 0, 2 );
-
- // subr[i][j] = subr[i][j] / -r[3][3]).
- final double divr = -R.getEntry( 3, 3 );
- for ( int i = 0; i < subr.getRowDimension(); i++ )
- for ( int j = 0; j < subr.getRowDimension(); j++ )
- subr.setEntry( i, j, subr.getEntry( i, j ) / divr );
-
- // Get the eigenvalues and eigenvectors.
- final EigenDecomposition ed = new EigenDecomposition( subr );
- final double[] eigenvalues = ed.getRealEigenvalues();
- final RealVector e1 = ed.getEigenvector( 0 );
- final RealVector e2 = ed.getEigenvector( 1 );
- final RealVector e3 = ed.getEigenvector( 2 );
-
- // Semi-axis length (radius).
- final RealVector SAL = new ArrayRealVector( eigenvalues.length );
- for ( int i = 0; i < eigenvalues.length; i++ )
- SAL.setEntry( i, Math.sqrt( 1. / eigenvalues[ i ] ) );
-
- // Put everything in a fit object.
- final RealPoint center = new RealPoint( C.getEntry( 0 ), C.getEntry( 1 ), C.getEntry( 2 ) );
- final RealPoint ev1 = new RealPoint( e1.getEntry( 0 ), e1.getEntry( 1 ), e1.getEntry( 2 ) );
- final RealPoint ev2 = new RealPoint( e2.getEntry( 0 ), e2.getEntry( 1 ), e2.getEntry( 2 ) );
- final RealPoint ev3 = new RealPoint( e3.getEntry( 0 ), e3.getEntry( 1 ), e3.getEntry( 2 ) );
- return new EllipsoidFit( center, ev1, ev2, ev3, SAL.getEntry( 0 ), SAL.getEntry( 1 ), SAL.getEntry( 2 ) );
- }
-
- /**
- * Translate the algebraic form of the ellipsoid to the center.
- *
- * @param C
- * the center of the ellipsoid.
- * @param A
- * the ellipsoid matrix.
- * @return the center translated form of the algebraic ellipsoid.
- */
- private static final RealMatrix translateToCenter( final RealVector C, final RealMatrix A )
- {
- final RealMatrix T = MatrixUtils.createRealIdentityMatrix( 4 );
- final RealMatrix centerMatrix = new Array2DRowRealMatrix( 1, 3 );
- centerMatrix.setRowVector( 0, C );
- T.setSubMatrix( centerMatrix.getData(), 3, 0 );
- final RealMatrix R = T.multiply( A ).multiply( T.transpose() );
- return R;
- }
-
- /**
- * Find the center of the ellipsoid.
- *
- * @param a
- * the algebraic from of the polynomial.
- * @return a vector containing the center of the ellipsoid.
- */
- private static final RealVector findCenter( final RealMatrix A )
- {
- final RealMatrix subA = A.getSubMatrix( 0, 2, 0, 2 );
-
- for ( int q = 0; q < subA.getRowDimension(); q++ )
- for ( int s = 0; s < subA.getColumnDimension(); s++ )
- subA.multiplyEntry( q, s, -1.0 );
-
- final RealVector subV = A.getRowVector( 3 ).getSubVector( 0, 3 );
-
- final DecompositionSolver solver = new SingularValueDecomposition( subA ).getSolver();
- final RealMatrix subAi = solver.getInverse();
- return subAi.operate( subV );
- }
-
- /**
- * Solve for Ax^2 + By^2 + Cz^2 + 2Dxy + 2Exz + 2Fyz + 2Gx + 2Hy +
- * 2Iz = 1
.
- *
- * @param points
- * an iterable over 3D points.
- * @param nPoints
- * the number of points in the iterable.
- * @return
- */
- private static final RealVector solve( final Iterable< ? extends RealLocalizable > points, final int nPoints )
- {
- final RealMatrix M0 = new Array2DRowRealMatrix( nPoints, 9 );
- int i = 0;
- for ( final RealLocalizable point : points )
- {
- final double x = point.getDoublePosition( 0 );
- final double y = point.getDoublePosition( 1 );
- final double z = point.getDoublePosition( 2 );
-
- final double xx = x * x;
- final double yy = y * y;
- final double zz = z * z;
-
- final double xy = 2. * x * y;
- final double xz = 2. * x * z;
- final double yz = 2. * y * z;
-
- M0.setEntry( i, 0, xx );
- M0.setEntry( i, 1, yy );
- M0.setEntry( i, 2, zz );
- M0.setEntry( i, 3, xy );
- M0.setEntry( i, 4, xz );
- M0.setEntry( i, 5, yz );
- M0.setEntry( i, 6, 2. * x );
- M0.setEntry( i, 7, 2. * y );
- M0.setEntry( i, 8, 2. * z );
-
- i++;
- if ( i >= nPoints )
- break;
- }
- final RealMatrix M;
- if ( i == nPoints )
- M = M0;
- else
- M = M0.getSubMatrix( 0, i, 0, 9 );
-
- final RealMatrix M2 = M.transpose().multiply( M );
-
- final RealVector O = new ArrayRealVector( nPoints );
- O.mapAddToSelf( 1 );
-
- final RealVector MO = M.transpose().operate( O );
-
- final DecompositionSolver solver = new SingularValueDecomposition( M2 ).getSolver();
- final RealMatrix I = solver.getInverse();
-
- final RealVector V = I.operate( MO );
- return V;
- }
-
- /**
- * Reshape the fit result vector in the shape of an algebraic matrix.
- *
- *
- * A = [ Ax2 2Dxy 2Exz 2Gx ] - * [ 2Dxy By2 2Fyz 2Hy ] - * [ 2Exz 2Fyz Cz2 2Iz ] - * [ 2Gx 2Hy 2Iz -1 ] ] - *- * - * @param V the fit result. - * @return a new 4x4 real matrix. - */ - private static final RealMatrix toAlgebraicForm( final RealVector V ) - { - final RealMatrix A = new Array2DRowRealMatrix( 4, 4 ); - - A.setEntry( 0, 0, V.getEntry( 0 ) ); - A.setEntry( 0, 1, V.getEntry( 3 ) ); - A.setEntry( 0, 2, V.getEntry( 4 ) ); - A.setEntry( 0, 3, V.getEntry( 6 ) ); - A.setEntry( 1, 0, V.getEntry( 3 ) ); - A.setEntry( 1, 1, V.getEntry( 1 ) ); - A.setEntry( 1, 2, V.getEntry( 5 ) ); - A.setEntry( 1, 3, V.getEntry( 7 ) ); - A.setEntry( 2, 0, V.getEntry( 4 ) ); - A.setEntry( 2, 1, V.getEntry( 5 ) ); - A.setEntry( 2, 2, V.getEntry( 2 ) ); - A.setEntry( 2, 3, V.getEntry( 8 ) ); - A.setEntry( 3, 0, V.getEntry( 6 ) ); - A.setEntry( 3, 1, V.getEntry( 7 ) ); - A.setEntry( 3, 2, V.getEntry( 8 ) ); - A.setEntry( 3, 3, -1 ); - return A; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java index 1dd49e9d5..3f384f969 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java @@ -2,10 +2,9 @@ import fiji.plugin.trackmate.SpotMesh; import gnu.trove.list.array.TDoubleArrayList; -import net.imagej.mesh.alg.zslicer.Slice; import net.imglib2.Cursor; import net.imglib2.RandomAccess; -import net.imglib2.Sampler; +import net.imglib2.mesh.alg.zslicer.Slice; /** * A {@link Cursor} that iterates over the pixels inside a mesh. @@ -197,13 +196,13 @@ public long getLongPosition( final int d ) public Cursor< T > copyCursor() { return new SpotMeshCursor<>( - ra.copyRandomAccess(), + ra.copy(), sm.copy(), cal.clone() ); } @Override - public Sampler< T > copy() + public Cursor< T > copy() { return copyCursor(); } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java new file mode 100644 index 000000000..d3ff277bd --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java @@ -0,0 +1,122 @@ +package fiji.plugin.trackmate.visualization.bvv; + +import bvv.vistools.Bvv; +import bvv.vistools.BvvFunctions; +import bvv.vistools.BvvHandle; +import bvv.vistools.BvvSource; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotMesh; +import fiji.plugin.trackmate.util.TMUtils; +import ij.CompositeImage; +import ij.ImagePlus; +import ij.process.ImageProcessor; +import ij.process.LUT; +import net.imagej.ImgPlus; +import net.imagej.axis.Axes; +import net.imglib2.img.display.imagej.ImgPlusViews; +import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.obj.Mesh; +import net.imglib2.mesh.obj.nio.BufferMesh; +import net.imglib2.mesh.obj.transform.TranslateMesh; +import net.imglib2.type.Type; +import net.imglib2.type.numeric.ARGBType; + +public class BVVUtils +{ + + public static final StupidMesh createMesh( final Spot spot ) + { + if ( spot instanceof SpotMesh ) + { + final SpotMesh sm = ( SpotMesh ) spot; + final Mesh mesh = TranslateMesh.translate( sm.getMesh(), spot ); + final BufferMesh bm = new BufferMesh( ( int ) mesh.vertices().size(), ( int ) mesh.triangles().size() ); + Meshes.copy( mesh, bm ); + return new StupidMesh( bm ); + } + return new StupidMesh( Icosahedron.sphere( spot ) ); + } + + public static final < T extends Type< T > > BvvHandle createViewer( final ImagePlus imp ) + { + final double[] cal = TMUtils.getSpatialCalibration( imp ); + + // Convert and split by channels. + final ImgPlus< T > img = TMUtils.rawWraps( imp ); + final int cAxis = img.dimensionIndex( Axes.CHANNEL ); + final BvvHandle bvvHandle; + if ( cAxis < 0 ) + { + final BvvSource source = BvvFunctions.show( img, imp.getShortTitle(), + Bvv.options() + .maxAllowedStepInVoxels( 0 ) + .renderWidth( 1024 ) + .renderHeight( 1024 ) + .preferredSize( 512, 512 ) + .frameTitle( "3D view " + imp.getShortTitle() ) + .sourceTransform( cal ) ); + source.setDisplayRange( imp.getDisplayRangeMin(), imp.getDisplayRangeMax() ); + if ( imp.getLuts().length > 0 ) + { + final LUT lut = imp.getLuts()[ 0 ]; + final int rgb = lut.getColorModel().getRGB( ( int ) imp.getDisplayRangeMax() ); + source.setColor( new ARGBType( rgb ) ); + } + bvvHandle = source.getBvvHandle(); + } + else + { + BvvHandle h = null; + final long nChannels = img.dimension( cAxis ); + final String st = imp.getShortTitle(); + for ( int c = 0; c < nChannels; c++ ) + { + final ImgPlus< T > channel = ImgPlusViews.hyperSlice( img, cAxis, c ); + final BvvSource source; + if ( h == null ) + { + source = BvvFunctions.show( channel, st + "_c" + ( c + 1 ), + Bvv.options() + .maxAllowedStepInVoxels( 0 ) + .renderWidth( 1024 ) + .renderHeight( 1024 ) + .preferredSize( 512, 512 ) + .frameTitle( "3D view " + imp.getShortTitle() ) + .sourceTransform( cal ) ); + h = source.getBvvHandle(); + } + else + { + source = BvvFunctions.show( channel, st + "_c" + ( c + 1 ), + Bvv.options() + .maxAllowedStepInVoxels( 0 ) + .renderWidth( 1024 ) + .renderHeight( 1024 ) + .preferredSize( 512, 512 ) + .sourceTransform( cal ) + .addTo( h ) ); + + } + final int i = imp.getStackIndex( c + 1, 1, 1 ); + if ( imp instanceof CompositeImage ) + { + final CompositeImage cp = ( CompositeImage ) imp; + source.setDisplayRange( cp.getChannelLut( c + 1 ).min, cp.getChannelLut( c + 1 ).max ); + } + else + { + final ImageProcessor ip = imp.getStack().getProcessor( i ); + source.setDisplayRange( ip.getMin(), ip.getMax() ); + } + if ( imp.getLuts().length > 0 ) + { + final LUT lut = imp.getLuts()[ c ]; + final int rgb = lut.getColorModel().getRGB( ( int ) imp.getDisplayRangeMax() ); + source.setColor( new ARGBType( rgb ) ); + } + } + bvvHandle = h; + } + return bvvHandle; + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java new file mode 100644 index 000000000..d77d407d1 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java @@ -0,0 +1,154 @@ +package fiji.plugin.trackmate.visualization.bvv; + +import fiji.plugin.trackmate.Spot; +import net.imglib2.RealLocalizable; +import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.obj.Mesh; +import net.imglib2.mesh.obj.Triangle; +import net.imglib2.mesh.obj.naive.NaiveDoubleMesh; +import net.imglib2.mesh.obj.nio.BufferMesh; + +/** + * Icosahedron spheres. + *+ * Based on https://github.com/caosdoar/spheres + * + * @author Jean-Yves Tinevez + */ +public class Icosahedron +{ + + public static final Mesh core() + { + final NaiveDoubleMesh mesh = new NaiveDoubleMesh(); + + // Vertices + final double t = ( 1. + Math.sqrt( 5. ) ) / 2.; + final double[][] vs = new double[][] { + { -1.0, t, 0.0 }, + { 1.0, t, 0.0 }, + { -1.0, -t, 0.0 }, + { 1.0, -t, 0.0 }, + { 0.0, -1.0, t }, + { 0.0, 1.0, t }, + { 0.0, -1.0, -t }, + { 0.0, 1.0, -t }, + { t, 0.0, -1.0 }, + { t, 0.0, 1.0 }, + { -t, 0.0, -1.0 }, + { -t, 0.0, 1.0 } + }; + final double[] tmp = new double[ 3 ]; + for ( final double[] v : vs ) + { + normalize( v, tmp ); + mesh.vertices().add( tmp[ 0 ], tmp[ 1 ], tmp[ 2 ] ); + } + + // Faces + mesh.triangles().add( 0, 11, 5 ); + mesh.triangles().add( 0, 5, 1 ); + mesh.triangles().add( 0, 1, 7 ); + mesh.triangles().add( 0, 7, 10 ); + mesh.triangles().add( 0, 10, 11 ); + mesh.triangles().add( 1, 5, 9 ); + mesh.triangles().add( 5, 11, 4 ); + mesh.triangles().add( 11, 10, 2 ); + mesh.triangles().add( 10, 7, 6 ); + mesh.triangles().add( 7, 1, 8 ); + mesh.triangles().add( 3, 9, 4 ); + mesh.triangles().add( 3, 4, 2 ); + mesh.triangles().add( 3, 2, 6 ); + mesh.triangles().add( 3, 6, 8 ); + mesh.triangles().add( 3, 8, 9 ); + mesh.triangles().add( 4, 9, 5 ); + mesh.triangles().add( 2, 4, 11 ); + mesh.triangles().add( 6, 2, 10 ); + mesh.triangles().add( 8, 6, 7 ); + mesh.triangles().add( 9, 8, 1 ); + return mesh; + } + + public static final BufferMesh refine( final Mesh core ) + { + final int nVerticesOut = ( int ) ( 6 * core.triangles().size() ); + final int nTrianglesOut = ( int ) ( 4 * core.triangles().size() ); + final BufferMesh out = new BufferMesh( nVerticesOut, nTrianglesOut ); + + final double[] tmpIn = new double[ 3 ]; + final double[] tmpOut = new double[ 3 ]; + for ( final Triangle t : core.triangles() ) + { + final long v0 = out.vertices().add( t.v0x(), t.v0y(), t.v0z() ); + final long v1 = out.vertices().add( t.v1x(), t.v1y(), t.v1z() ); + final long v2 = out.vertices().add( t.v2x(), t.v2y(), t.v2z() ); + + tmpIn[ 0 ] = 0.5 * ( t.v0xf() + t.v1xf() ); + tmpIn[ 1 ] = 0.5 * ( t.v0yf() + t.v1yf() ); + tmpIn[ 2 ] = 0.5 * ( t.v0zf() + t.v1zf() ); + normalize( tmpIn, tmpOut ); + final long v3 = out.vertices().add( tmpOut[ 0 ], tmpOut[ 1 ], tmpOut[ 2 ] ); + + tmpIn[ 0 ] = 0.5 * ( t.v2xf() + t.v1xf() ); + tmpIn[ 1 ] = 0.5 * ( t.v2yf() + t.v1yf() ); + tmpIn[ 2 ] = 0.5 * ( t.v2zf() + t.v1zf() ); + normalize( tmpIn, tmpOut ); + final long v4 = out.vertices().add( tmpOut[ 0 ], tmpOut[ 1 ], tmpOut[ 2 ] ); + + tmpIn[ 0 ] = 0.5 * ( t.v0xf() + t.v2xf() ); + tmpIn[ 1 ] = 0.5 * ( t.v0yf() + t.v2yf() ); + tmpIn[ 2 ] = 0.5 * ( t.v0zf() + t.v2zf() ); + normalize( tmpIn, tmpOut ); + final long v5 = out.vertices().add( tmpOut[ 0 ], tmpOut[ 1 ], tmpOut[ 2 ] ); + + out.triangles().add( v0, v3, v5 ); + out.triangles().add( v3, v1, v4 ); + out.triangles().add( v5, v4, v2 ); + out.triangles().add( v3, v4, v5 ); + } + + return out; + } + + public static BufferMesh sphere( final Spot spot ) + { + return sphere( spot, spot.getFeature( Spot.RADIUS ) ); + } + + public static BufferMesh sphere( final RealLocalizable center, final double radius ) + { + return sphere( center, radius, 3 ); + } + + public static BufferMesh sphere( final RealLocalizable center, final double radius, final int nSubdivisions ) + { + Mesh mesh = core(); + for ( int i = 0; i < nSubdivisions; i++ ) + mesh = refine( mesh ); + + scale( mesh, center, radius ); + final BufferMesh out = new BufferMesh( ( int ) mesh.vertices().size(), ( int ) mesh.triangles().size() ); + Meshes.calculateNormals( mesh, out ); + return out; + } + + private static void scale( final Mesh mesh, final RealLocalizable center, final double radius ) + { + final long nV = mesh.vertices().size(); + for ( int i = 0; i < nV; i++ ) + { + final double x = mesh.vertices().x( i ) * radius + center.getDoublePosition( 0 ); + final double y = mesh.vertices().y( i ) * radius + center.getDoublePosition( 1 ); + final double z = mesh.vertices().z( i ) * radius + center.getDoublePosition( 2 ); + mesh.vertices().set( i, x, y, z ); + } + } + + private static void normalize( final double[] v, final double[] tmp ) + { + final double l = Math.sqrt( v[ 0 ] * v[ 0 ] + v[ 1 ] * v[ 1 ] + v[ 2 ] * v[ 2 ] ); + tmp[ 0 ] = v[ 0 ] / l; + tmp[ 1 ] = v[ 1 ] / l; + tmp[ 2 ] = v[ 2 ] / l; + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java new file mode 100644 index 000000000..46da5742e --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java @@ -0,0 +1,155 @@ +/*- + * #%L + * Volume rendering of bdv datasets + * %% + * Copyright (C) 2018 - 2021 Tobias Pietzsch + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package fiji.plugin.trackmate.visualization.bvv; + +import static com.jogamp.opengl.GL.GL_FLOAT; +import static com.jogamp.opengl.GL.GL_TRIANGLES; +import static com.jogamp.opengl.GL.GL_UNSIGNED_INT; + +import java.awt.Color; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +import org.joml.Matrix3f; +import org.joml.Matrix4f; +import org.joml.Matrix4fc; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL3; + +import bvv.core.backend.jogl.JoglGpuContext; +import bvv.core.shadergen.DefaultShader; +import bvv.core.shadergen.Shader; +import bvv.core.shadergen.generate.Segment; +import bvv.core.shadergen.generate.SegmentTemplate; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import net.imglib2.mesh.obj.nio.BufferMesh; + +public class StupidMesh +{ + private final Shader prog; + + private final BufferMesh mesh; + + private boolean initialized; + + private int vao; + + private final float[] carr = new float[ 4 ]; + + private final float[] scarr = new float[ 4 ]; + + public StupidMesh( final BufferMesh mesh ) + { + this.mesh = mesh; + final Segment meshVp = new SegmentTemplate( StupidMesh.class, "mesh.vp" ).instantiate(); + final Segment meshFp = new SegmentTemplate( StupidMesh.class, "mesh.fp" ).instantiate(); + prog = new DefaultShader( meshVp.getCode(), meshFp.getCode() ); + DisplaySettings.defaultStyle().getSpotUniformColor().getColorComponents( carr ); + DisplaySettings.defaultStyle().getHighlightColor().getColorComponents( scarr ); + } + + private void init( final GL3 gl ) + { + initialized = true; + + final int[] tmp = new int[ 3 ]; + gl.glGenBuffers( 3, tmp, 0 ); + final int meshPosVbo = tmp[ 0 ]; + final int meshNormalVbo = tmp[ 1 ]; + final int meshEbo = tmp[ 2 ]; + + final FloatBuffer vertices = mesh.vertices().verts(); + vertices.rewind(); + gl.glBindBuffer( GL.GL_ARRAY_BUFFER, meshPosVbo ); + gl.glBufferData( GL.GL_ARRAY_BUFFER, vertices.limit() * Float.BYTES, vertices, GL.GL_STATIC_DRAW ); + gl.glBindBuffer( GL.GL_ARRAY_BUFFER, 0 ); + + final FloatBuffer normals = mesh.vertices().normals(); + normals.rewind(); + gl.glBindBuffer( GL.GL_ARRAY_BUFFER, meshNormalVbo ); + gl.glBufferData( GL.GL_ARRAY_BUFFER, normals.limit() * Float.BYTES, normals, GL.GL_STATIC_DRAW ); + gl.glBindBuffer( GL.GL_ARRAY_BUFFER, 0 ); + + final IntBuffer indices = mesh.triangles().indices(); + indices.rewind(); + gl.glBindBuffer( GL.GL_ELEMENT_ARRAY_BUFFER, meshEbo ); + gl.glBufferData( GL.GL_ELEMENT_ARRAY_BUFFER, indices.limit() * Integer.BYTES, indices, GL.GL_STATIC_DRAW ); + gl.glBindBuffer( GL.GL_ELEMENT_ARRAY_BUFFER, 0 ); + + gl.glGenVertexArrays( 1, tmp, 0 ); + vao = tmp[ 0 ]; + gl.glBindVertexArray( vao ); + gl.glBindBuffer( GL.GL_ARRAY_BUFFER, meshPosVbo ); + gl.glVertexAttribPointer( 0, 3, GL_FLOAT, false, 3 * Float.BYTES, 0 ); + gl.glEnableVertexAttribArray( 0 ); + gl.glBindBuffer( GL.GL_ARRAY_BUFFER, meshNormalVbo ); + gl.glVertexAttribPointer( 1, 3, GL_FLOAT, false, 3 * Float.BYTES, 0 ); + gl.glEnableVertexAttribArray( 1 ); + gl.glBindBuffer( GL.GL_ELEMENT_ARRAY_BUFFER, meshEbo ); + gl.glBindVertexArray( 0 ); + } + + public void setColor( final Color color, final float alpha ) + { + color.getComponents( carr ); + carr[ 3 ] = alpha; + } + + public void setSelectionColor( final Color selectionColor, final float alpha ) + { + selectionColor.getComponents( scarr ); + scarr[ 3 ] = alpha; + } + + public void draw( final GL3 gl, final Matrix4fc pvm, final Matrix4fc vm, final boolean isSelected ) + { + if ( !initialized ) + init( gl ); + + final JoglGpuContext context = JoglGpuContext.get( gl ); + final Matrix4f itvm = vm.invert( new Matrix4f() ).transpose(); + + prog.getUniformMatrix4f( "pvm" ).set( pvm ); + prog.getUniformMatrix4f( "vm" ).set( vm ); + prog.getUniformMatrix3f( "itvm" ).set( itvm.get3x3( new Matrix3f() ) ); + prog.getUniform4f( "ObjectColor" ).set( carr[ 0 ], carr[ 1 ], carr[ 2 ], carr[ 3 ] ); + prog.getUniform1f( "IsSelected" ).set( isSelected ? 1f : 0f ); + prog.getUniform4f( "SelectionColor" ).set( scarr[ 0 ], scarr[ 1 ], scarr[ 2 ], scarr[ 3 ] ); + prog.setUniforms( context ); + prog.use( context ); + + gl.glBindVertexArray( vao ); + gl.glEnable( GL.GL_CULL_FACE ); + gl.glCullFace( GL.GL_BACK ); + gl.glFrontFace( GL.GL_CCW ); + gl.glDrawElements( GL_TRIANGLES, ( int ) mesh.triangles().size() * 3, GL_UNSIGNED_INT, 0 ); + gl.glBindVertexArray( 0 ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java new file mode 100644 index 000000000..a7c024b5b --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -0,0 +1,189 @@ +package fiji.plugin.trackmate.visualization.bvv; + +import java.awt.Color; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.joml.Matrix4f; + +import bdv.viewer.animate.TranslationAnimator; +import bvv.core.VolumeViewerPanel; +import bvv.core.util.MatrixMath; +import bvv.vistools.BvvHandle; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.ModelChangeEvent; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.features.FeatureUtils; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.visualization.AbstractTrackMateModelView; +import fiji.plugin.trackmate.visualization.FeatureColorGenerator; +import ij.ImagePlus; +import net.imglib2.RealLocalizable; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.Type; + +public class TrackMateBVV< T extends Type< T > > extends AbstractTrackMateModelView +{ + + private static final String KEY = "BIGVOLUMEVIEWER"; + + private final ImagePlus imp; + + private BvvHandle handle; + + private final Map< Spot, StupidMesh > meshMap; + + public TrackMateBVV( final Model model, final SelectionModel selectionModel, final ImagePlus imp, final DisplaySettings displaySettings ) + { + super( model, selectionModel, displaySettings ); + this.imp = imp; + this.meshMap = new HashMap<>(); + final Iterable< Spot > it = model.getSpots().iterable( true ); + it.forEach( s -> meshMap.computeIfAbsent( s, BVVUtils::createMesh ) ); + updateColor(); + displaySettings.listeners().add( this::updateColor ); + selectionModel.addSelectionChangeListener( e -> refresh() ); + } + + /** + * Returns the {@link BvvHandle} that contains this view. Returns + *
null
if this view has not been rendered yet. + * + * @return the BVV handle, ornull
. + */ + public BvvHandle getBvvHandle() + { + return handle; + } + + @Override + public void render() + { + this.handle = BVVUtils.createViewer( imp ); + final VolumeViewerPanel viewer = handle.getViewerPanel(); + viewer.setRenderScene( ( gl, data ) -> { + if ( displaySettings.isSpotVisible() ) + { + final Matrix4f pvm = new Matrix4f( data.getPv() ); + final Matrix4f view = MatrixMath.affine( data.getRenderTransformWorldToScreen(), new Matrix4f() ); + final Matrix4f vm = MatrixMath.screen( data.getDCam(), data.getScreenWidth(), data.getScreenHeight(), new Matrix4f() ).mul( view ); + + final int t = data.getTimepoint(); + final Iterable< Spot > it = model.getSpots().iterable( t, true ); + it.forEach( s -> meshMap.computeIfAbsent( s, BVVUtils::createMesh ).draw( gl, pvm, vm, selectionModel.getSpotSelection().contains( s ) ) ); + } + } ); + } + + @Override + public void refresh() + { + if ( handle != null ) + handle.getViewerPanel().requestRepaint(); + } + + @Override + public void clear() + { + // TODO Auto-generated method stub + + } + + @Override + public void centerViewOn( final Spot spot ) + { + if ( handle == null ) + return; + + final VolumeViewerPanel panel = handle.getViewerPanel(); + panel.setTimepoint( spot.getFeature( Spot.FRAME ).intValue() ); + + final AffineTransform3D c = panel.state().getViewerTransform(); + final double[] translation = getTranslation( c, spot, panel.getWidth(), panel.getHeight() ); + if ( translation != null ) + { + final TranslationAnimator animator = new TranslationAnimator( c, translation, 300 ); + animator.setTime( System.currentTimeMillis() ); + panel.setTransformAnimator( animator ); + } + } + + /** + * Returns a translation vector that will put the specified position at the + * center of the panel when used with a TranslationAnimator. + * + * @param t + * the viewer panel current view transform. + * @param target + * the position to focus on. + * @param width + * the width of the panel. + * @param height + * the height of the panel. + * @return a newdouble[]
array with 3 elements containing the + * translation to use. + */ + private static final double[] getTranslation( final AffineTransform3D t, final RealLocalizable target, final int width, final int height ) + { + final double[] pos = new double[ 3 ]; + final double[] vPos = new double[ 3 ]; + target.localize( pos ); + t.apply( pos, vPos ); + + final double dx = width / 2 - vPos[ 0 ] + t.get( 0, 3 ); + final double dy = height / 2 - vPos[ 1 ] + t.get( 1, 3 ); + final double dz = -vPos[ 2 ] + t.get( 2, 3 ); + + return new double[] { dx, dy, dz }; + } + + @Override + public String getKey() + { + return KEY; + } + + @Override + public void modelChanged( final ModelChangeEvent event ) + { + switch ( event.getEventID() ) + { + case ModelChangeEvent.SPOTS_FILTERED: + case ModelChangeEvent.SPOTS_COMPUTED: + case ModelChangeEvent.TRACKS_VISIBILITY_CHANGED: + case ModelChangeEvent.TRACKS_COMPUTED: + refresh(); + break; + case ModelChangeEvent.MODEL_MODIFIED: + { + for ( final Spot spot : event.getSpots() ) + { + final StupidMesh mesh = BVVUtils.createMesh( spot ); + meshMap.put( spot, mesh ); + } + updateColor(); + refresh(); + break; + } + } + } + + private void updateColor() + { + final FeatureColorGenerator< Spot > spotColorGenerator = FeatureUtils.createSpotColorGenerator( model, displaySettings ); + for ( final Entry< Spot, StupidMesh > entry : meshMap.entrySet() ) + { + final StupidMesh sm = entry.getValue(); + if ( sm == null ) + continue; + + final Color color = spotColorGenerator.color( entry.getKey() ); + final float alpha = ( float ) displaySettings.getSpotTransparencyAlpha(); + sm.setColor( color, alpha ); + sm.setSelectionColor( displaySettings.getHighlightColor(), alpha ); + } + refresh(); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp b/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp new file mode 100644 index 000000000..fb732ddd4 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp @@ -0,0 +1,48 @@ +out vec4 fragColor; + +in vec3 Normal; +in vec3 FragPos; + +uniform vec4 ObjectColor; +uniform float IsSelected; +uniform vec4 SelectionColor; + +const vec3 lightColor1 = 0.5 * vec3(0.9, 0.9, 1); +const vec3 lightDir1 = normalize(vec3(0, -0.2, -1)); + +const vec3 lightColor2 = 0.5 * vec3(0.1, 0.1, 1); +const vec3 lightDir2 = normalize(vec3(1, 1, 0.5)); + +const vec3 ambient = vec3(0.7, 0.7, 0.7); + +const float specularStrength = 5; + +vec3 phong(vec3 norm, vec3 viewDir, vec3 lightDir, vec3 lightColor, float shininess, float specularStrength) +{ + float diff = max(dot(norm, lightDir), 0.0); + vec3 diffuse = diff * lightColor; + + vec3 reflectDir = reflect(-lightDir, norm); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess); + vec3 specular = specularStrength * spec * lightColor; + + return diffuse + specular; +} + +void main() +{ +// fragColor = vec4(ObjectColor, 1); + vec3 norm = normalize(Normal); + vec3 viewDir = normalize(-FragPos); + + vec3 l1 = phong( norm, viewDir, lightDir1, lightColor1, 32, 0.1 ); + vec3 l2 = phong( norm, viewDir, lightDir2, lightColor2, 32, 0.5 ); + + if (IsSelected > 0.5) { + fragColor = vec4( (ambient + l1 + l2), SelectionColor[3]) * SelectionColor; + } else { + float it = dot(norm, viewDir); + fragColor = vec4( it * (ambient + l1 + l2), 1) * ObjectColor + (1-it) * vec4(1,1,1,ObjectColor[3]); + } + +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.vp b/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.vp new file mode 100644 index 000000000..e86810409 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.vp @@ -0,0 +1,16 @@ +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec3 aNormal; + +out vec3 FragPos; +out vec3 Normal; + +uniform mat4 pvm; +uniform mat4 vm; +uniform mat3 itvm; + +void main() +{ + gl_Position = pvm * vec4( aPos, 1.0 ); + FragPos = vec3(vm * vec4(aPos, 1.0)); + Normal = itvm * aNormal; +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java index dfc83d4ed..fded6eef5 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java @@ -17,7 +17,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.SpotShape; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.detection.semiauto.SemiAutoTracker; import fiji.plugin.trackmate.util.ModelTools; import fiji.plugin.trackmate.util.TMUtils; @@ -76,7 +76,7 @@ private Spot makeSpot( Point mouseLocation ) SwingUtilities.convertPointFromScreen( mouseLocation, canvas ); } final double[] calibration = TMUtils.getSpatialCalibration( imp ); - return new Spot( + return new SpotBase( ( -0.5 + canvas.offScreenXD( mouseLocation.x ) ) * calibration[ 0 ], ( -0.5 + canvas.offScreenYD( mouseLocation.y ) ) * calibration[ 1 ], ( imp.getSlice() - 1 ) * calibration[ 2 ], @@ -265,24 +265,14 @@ public void changeSpotRadius( final boolean increase, final boolean fast ) ? radius + factor * dx * COARSE_STEP : radius + factor * dx * FINE_STEP; - if ( newRadius <= dx ) return; // Store new value of radius for next spot creation. previousRadius = newRadius; - final SpotShape shape = target.getShape(); - if ( null == shape ) - { - target.putFeature( Spot.RADIUS, newRadius ); - } - else - { - final double alpha = newRadius / radius; - shape.scale( alpha ); - target.putFeature( Spot.RADIUS, shape.radius() ); - } + // Actually scale the spot. + target.scale( radius / newRadius ); model.beginUpdate(); try diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index af1cc0e7d..158260da4 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -11,9 +11,9 @@ import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import ij.ImagePlus; -import net.imagej.mesh.alg.zslicer.Contour; -import net.imagej.mesh.alg.zslicer.Slice; import net.imglib2.RealLocalizable; +import net.imglib2.mesh.alg.zslicer.Contour; +import net.imglib2.mesh.alg.zslicer.Slice; /** * Utility class to paint the {@link SpotMesh} component of spots. diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java index 2b92ea64f..baedb0394 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java @@ -403,7 +403,6 @@ public void keyPressed( final KeyEvent e ) break; } } - } @Override diff --git a/src/main/resources/fiji/plugin/trackmate/gui/images/TrackMateBVV-logo-16x16.png b/src/main/resources/fiji/plugin/trackmate/gui/images/TrackMateBVV-logo-16x16.png new file mode 100644 index 000000000..2e5b7d5fa Binary files /dev/null and b/src/main/resources/fiji/plugin/trackmate/gui/images/TrackMateBVV-logo-16x16.png differ diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java index 94456b577..9d28c8e35 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java @@ -13,9 +13,9 @@ import ij.CompositeImage; import ij.ImageJ; import ij.ImagePlus; -import net.imagej.mesh.alg.zslicer.Contour; -import net.imagej.mesh.alg.zslicer.Slice; -import net.imagej.mesh.alg.zslicer.ZSlicer; +import net.imglib2.mesh.alg.zslicer.Contour; +import net.imglib2.mesh.alg.zslicer.Slice; +import net.imglib2.mesh.alg.zslicer.ZSlicer; public class DebugZSlicer { diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java index 8ce097ad2..37e6f9961 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java @@ -16,8 +16,8 @@ import ij.measure.Calibration; import net.imagej.ImgPlus; import net.imagej.axis.Axes; -import net.imagej.mesh.Mesh; import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.mesh.obj.Mesh; import net.imglib2.type.logic.BitType; public class DefaultMesh diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index 84b9912f9..766d92e4b 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -14,21 +14,20 @@ import ij.gui.PolygonRoi; import net.imagej.ImgPlus; import net.imagej.axis.Axes; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.Meshes; -import net.imagej.mesh.Vertices; -import net.imagej.mesh.alg.zslicer.Contour; -import net.imagej.mesh.alg.zslicer.Slice; -import net.imagej.mesh.alg.zslicer.ZSlicer; -import net.imagej.mesh.io.ply.PLYMeshIO; -import net.imagej.mesh.io.stl.STLMeshIO; -import net.imagej.mesh.naive.NaiveDoubleMesh; -import net.imagej.mesh.naive.NaiveDoubleMesh.Triangles; import net.imglib2.RandomAccessibleInterval; import net.imglib2.converter.RealTypeConverters; import net.imglib2.img.ImgView; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.img.display.imagej.ImgPlusViews; +import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.alg.zslicer.Contour; +import net.imglib2.mesh.alg.zslicer.Slice; +import net.imglib2.mesh.alg.zslicer.ZSlicer; +import net.imglib2.mesh.io.ply.PLYMeshIO; +import net.imglib2.mesh.io.stl.STLMeshIO; +import net.imglib2.mesh.obj.Mesh; +import net.imglib2.mesh.obj.Vertices; +import net.imglib2.mesh.obj.naive.NaiveDoubleMesh; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegions; @@ -112,8 +111,8 @@ public static void main( final String[] args ) static Mesh debugMesh( final long[] min, final long[] max ) { final NaiveDoubleMesh mesh = new NaiveDoubleMesh(); - final net.imagej.mesh.naive.NaiveDoubleMesh.Vertices vertices = mesh.vertices(); - final Triangles triangles = mesh.triangles(); + final net.imglib2.mesh.obj.naive.NaiveDoubleMesh.Vertices vertices = mesh.vertices(); + final net.imglib2.mesh.obj.naive.NaiveDoubleMesh.Triangles triangles = mesh.triangles(); // Coords as X Y Z @@ -198,11 +197,10 @@ private static void testIO( final Mesh mesh, final int j ) // Serialize to disk. try { - new STLMeshIO().save( mesh, String.format( "samples/mesh/io/STL_%02d.stl", j ) ); + STLMeshIO.save( mesh, String.format( "samples/mesh/io/STL_%02d.stl", j ) ); - final PLYMeshIO plyio = new PLYMeshIO(); - plyio.save( mesh, String.format( "samples/mesh/io/PLY_%02d.ply", j ) ); - final byte[] bs = plyio.writeAscii( mesh ); + PLYMeshIO.save( mesh, String.format( "samples/mesh/io/PLY_%02d.ply", j ) ); + final byte[] bs = PLYMeshIO.writeAscii( mesh ); final String str = new String( bs ); try (final FileWriter writer = new FileWriter( String.format( "samples/mesh/io/PLYTEXT_%02d.txt", j ) )) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java b/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java index 44b560382..890b4a553 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java @@ -12,7 +12,7 @@ import fiji.plugin.trackmate.detection.ThresholdDetectorFactory; import ij.IJ; import ij.ImagePlus; -import net.imagej.mesh.io.stl.STLMeshIO; +import net.imglib2.mesh.io.stl.STLMeshIO; public class ExportMeshForDemo { @@ -42,7 +42,6 @@ public static void main( final String[] args ) if ( !file.isDirectory() ) file.delete(); - final STLMeshIO io = new STLMeshIO(); for ( final Spot spot : spots.iterable( true ) ) { final int t = spot.getFeature( Spot.FRAME ).intValue(); @@ -51,7 +50,7 @@ public static void main( final String[] args ) if ( spot instanceof SpotMesh ) { final SpotMesh mesh = ( SpotMesh ) spot; - io.save( mesh.getMesh(), savePath ); + STLMeshIO.save( mesh.getMesh(), savePath ); } } System.out.println( "Export done." ); diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java new file mode 100644 index 000000000..0c2cb85e7 --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java @@ -0,0 +1,114 @@ +package fiji.plugin.trackmate.mesh; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.joml.Matrix4f; +import org.scijava.ui.behaviour.io.InputTriggerConfig; +import org.scijava.ui.behaviour.util.Actions; + +import bvv.core.VolumeViewerPanel; +import bvv.core.util.MatrixMath; +import bvv.vistools.Bvv; +import bvv.vistools.BvvFunctions; +import bvv.vistools.BvvSource; +import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.visualization.bvv.StupidMesh; +import ij.IJ; +import ij.ImagePlus; +import net.imagej.ImgPlus; +import net.imagej.axis.Axes; +import net.imglib2.img.display.imagej.ImgPlusViews; +import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.obj.Mesh; +import net.imglib2.mesh.obj.naive.NaiveDoubleMesh; +import net.imglib2.mesh.obj.nio.BufferMesh; +import net.imglib2.type.Type; +import net.imglib2.type.numeric.ARGBType; + +public class MeshPlayground +{ + public static < T extends Type< T > > void main( final String[] args ) + { + final String filePath = "samples/mesh/CElegansMask3D.tif"; + final ImagePlus imp = IJ.openImage( filePath ); + + final ImgPlus< T > img = TMUtils.rawWraps( imp ); + final ImgPlus< T > c1 = ImgPlusViews.hyperSlice( img, img.dimensionIndex( Axes.CHANNEL ), 1 ); + final ImgPlus< T > t1 = ImgPlusViews.hyperSlice( c1, c1.dimensionIndex( Axes.TIME ), 0 ); + final double[] cal = TMUtils.getSpatialCalibration( t1 ); + + final BvvSource source = BvvFunctions.show( c1, "t1", + Bvv.options() + .maxAllowedStepInVoxels( 0 ) + .renderWidth( 1024 ) + .renderHeight( 1024 ) + .preferredSize( 512, 512 ) + .sourceTransform( cal ) ); + + source.setDisplayRangeBounds( 0, 1024 ); + source.setColor( new ARGBType( 0xaaffaa ) ); + + + final List< StupidMesh > meshes = new ArrayList<>(); + for ( int j = 1; j <= 3; ++j ) + { + final String fn = String.format( "samples/mesh/CElegansMask3D_%02d.stl", j ); + meshes.add( new StupidMesh( load( fn ) ) ); + } + + final VolumeViewerPanel viewer = source.getBvvHandle().getViewerPanel(); + + final AtomicBoolean showMeshes = new AtomicBoolean( true ); + viewer.setRenderScene( ( gl, data ) -> { + if ( showMeshes.get() ) + { + final Matrix4f pvm = new Matrix4f( data.getPv() ); + final Matrix4f view = MatrixMath.affine( data.getRenderTransformWorldToScreen(), new Matrix4f() ); + final Matrix4f vm = MatrixMath.screen( data.getDCam(), data.getScreenWidth(), data.getScreenHeight(), new Matrix4f() ).mul( view ); + meshes.forEach( mesh -> mesh.draw( gl, pvm, vm, false ) ); + } + } ); + + final Actions actions = new Actions( new InputTriggerConfig() ); + actions.install( source.getBvvHandle().getKeybindings(), "my-new-actions" ); + actions.runnableAction( () -> { + showMeshes.set( !showMeshes.get() ); + viewer.requestRepaint(); + }, "toggle meshes", "G" ); + + viewer.requestRepaint(); + } + + + private static BufferMesh load( final String fn ) + { + BufferMesh mesh = null; + try + { + final NaiveDoubleMesh nmesh = new NaiveDoubleMesh(); + net.imglib2.mesh.io.stl.STLMeshIO.read( nmesh, new File( fn ) ); + mesh = calculateNormals( + nmesh +// Meshes.removeDuplicateVertices( nmesh, 5 ) + ); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + return mesh; + } + + private static BufferMesh calculateNormals( final Mesh mesh ) + { + final int nvertices = ( int ) mesh.vertices().size(); + final int ntriangles = ( int ) mesh.triangles().size(); + final BufferMesh bufferMesh = new BufferMesh( nvertices, ntriangles, true ); + Meshes.calculateNormals( mesh, bufferMesh ); + return bufferMesh; + } +} diff --git a/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java b/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java index c5fabdd62..f5172a51c 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java @@ -5,10 +5,10 @@ import org.junit.Test; -import fiji.plugin.trackmate.util.mesh.EllipsoidFitter; -import fiji.plugin.trackmate.util.mesh.EllipsoidFitter.EllipsoidFit; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.naive.NaiveDoubleMesh; +import net.imglib2.mesh.alg.EllipsoidFitter; +import net.imglib2.mesh.alg.EllipsoidFitter.EllipsoidFit; +import net.imglib2.mesh.obj.Mesh; +import net.imglib2.mesh.obj.naive.NaiveDoubleMesh; public class TestEllipsoidFit {