From eabf5f8eac006b18547e3032b1883b5a7207bace Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Tue, 28 May 2024 14:46:06 +0200 Subject: [PATCH 01/81] Rename method getDistanceMatrix to getAverageDistanceMatrix --- .../mastodon/mamut/classification/util/ClassificationUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java b/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java index 95f87a561..00e882290 100644 --- a/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java +++ b/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java @@ -133,7 +133,7 @@ public static double[][] getDistanceMatrix( final List< Tree< Double > > trees, * @param similarityMeasure the similarity measure to be used * @return a symmetric quadratic distance matrix */ - public static double[][] getDistanceMatrix( final Tree< Double >[][] treeMatrix, final SimilarityMeasure similarityMeasure ) + public static double[][] getAverageDistanceMatrix( final Tree< Double >[][] treeMatrix, final SimilarityMeasure similarityMeasure ) { if ( treeMatrix.length == 0 ) return new double[ 0 ][ 0 ]; From e8d4e84dd00e3a824ec8950662d2053fb1a345db Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Tue, 28 May 2024 14:46:22 +0200 Subject: [PATCH 02/81] Add a unit test to getAverageDistanceMatrix --- .../util/ClassificationUtilsTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/test/java/org/mastodon/mamut/classification/util/ClassificationUtilsTest.java b/src/test/java/org/mastodon/mamut/classification/util/ClassificationUtilsTest.java index 5e69188af..310572b97 100644 --- a/src/test/java/org/mastodon/mamut/classification/util/ClassificationUtilsTest.java +++ b/src/test/java/org/mastodon/mamut/classification/util/ClassificationUtilsTest.java @@ -206,6 +206,38 @@ void testGetDistanceMatrix() assertArrayEquals( new double[] { t1t3, t2t3, 0 }, distanceMatrix[ 2 ], 0d ); } + @Test + @SuppressWarnings( "unchecked" ) + void testGetAverageDistanceMatrix() + { + Tree< Double > tree1 = SimpleTreeExamples.tree1(); + Tree< Double > tree2 = SimpleTreeExamples.tree2(); + Tree< Double > tree3 = SimpleTreeExamples.tree3(); + + Tree< Double >[] trees1 = new Tree[] { tree1, tree2, tree3 }; + Tree< Double >[] trees2 = new Tree[] { tree1, tree2, tree3 }; + + Tree< Double >[][] tree2DArray1 = new Tree[][] { trees1, trees2 }; + + double t1t2 = 20d; + double t1t3 = 100d; + double t2t3 = 104d; + + // trivial case - two identical set of trees + double[][] distanceMatrix = + ClassificationUtils.getAverageDistanceMatrix( tree2DArray1, SimilarityMeasure.ZHANG_DISTANCE ); + assertArrayEquals( new double[] { 0, t1t2, t1t3 }, distanceMatrix[ 0 ], 0d ); + assertArrayEquals( new double[] { t1t2, 0, t2t3 }, distanceMatrix[ 1 ], 0d ); + assertArrayEquals( new double[] { t1t3, t2t3, 0 }, distanceMatrix[ 2 ], 0d ); + + Tree< Double >[][] tree2DArray2 = new Tree[][] { { tree1, tree2, tree3 }, { tree2, tree1, tree3 } }; + distanceMatrix = ClassificationUtils.getAverageDistanceMatrix( tree2DArray2, SimilarityMeasure.ZHANG_DISTANCE ); + assertArrayEquals( new double[] { 0, t1t2, ( t1t3 + t2t3 ) / 2d }, distanceMatrix[ 0 ], 0d ); + assertArrayEquals( new double[] { t1t2, 0, ( t1t3 + t2t3 ) / 2d }, distanceMatrix[ 1 ], 0d ); + assertArrayEquals( new double[] { ( t1t3 + t2t3 ) / 2d, ( t1t3 + t2t3 ) / 2d, 0 }, distanceMatrix[ 2 ], 0d ); + + } + @Test void testExceptions() { From 65e45df84f4fb0c5bf3257cdd5b95f6f0a02b2ed Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 30 May 2024 09:51:48 +0200 Subject: [PATCH 03/81] Make ClassificationUtils getAverageDistanceMatrix use List of Lists instead of a 2D array * By this in other parts of the code generics can be used better --- .../util/ClassificationUtils.java | 15 ++++++++------- .../util/ClassificationUtilsTest.java | 17 ++++++++--------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java b/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java index 00e882290..8eb823d08 100644 --- a/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java +++ b/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java @@ -52,7 +52,6 @@ import javax.annotation.Nullable; import java.lang.invoke.MethodHandles; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -133,18 +132,20 @@ public static double[][] getDistanceMatrix( final List< Tree< Double > > trees, * @param similarityMeasure the similarity measure to be used * @return a symmetric quadratic distance matrix */ - public static double[][] getAverageDistanceMatrix( final Tree< Double >[][] treeMatrix, final SimilarityMeasure similarityMeasure ) + public static double[][] getAverageDistanceMatrix( final List< List< Tree< Double > > > treeMatrix, + final SimilarityMeasure similarityMeasure ) { - if ( treeMatrix.length == 0 ) + if ( treeMatrix.isEmpty() ) return new double[ 0 ][ 0 ]; - double[][] result = new double[ treeMatrix[ 0 ].length ][ treeMatrix[ 0 ].length ]; - for ( Tree< Double >[] trees : treeMatrix ) + int numberOfTrees = treeMatrix.get( 0 ).size(); + double[][] result = new double[ numberOfTrees ][ numberOfTrees ]; + for ( List< Tree< Double > > trees : treeMatrix ) { - double[][] temp = getDistanceMatrix( Arrays.asList( trees ), similarityMeasure ); + double[][] temp = getDistanceMatrix( trees, similarityMeasure ); LinAlgHelpers.add( result, temp, result ); } - LinAlgHelpers.scale( result, 1d / treeMatrix.length, result ); + LinAlgHelpers.scale( result, 1d / treeMatrix.size(), result ); return result; } diff --git a/src/test/java/org/mastodon/mamut/classification/util/ClassificationUtilsTest.java b/src/test/java/org/mastodon/mamut/classification/util/ClassificationUtilsTest.java index 310572b97..f328c4471 100644 --- a/src/test/java/org/mastodon/mamut/classification/util/ClassificationUtilsTest.java +++ b/src/test/java/org/mastodon/mamut/classification/util/ClassificationUtilsTest.java @@ -207,17 +207,15 @@ void testGetDistanceMatrix() } @Test - @SuppressWarnings( "unchecked" ) void testGetAverageDistanceMatrix() { Tree< Double > tree1 = SimpleTreeExamples.tree1(); Tree< Double > tree2 = SimpleTreeExamples.tree2(); Tree< Double > tree3 = SimpleTreeExamples.tree3(); - Tree< Double >[] trees1 = new Tree[] { tree1, tree2, tree3 }; - Tree< Double >[] trees2 = new Tree[] { tree1, tree2, tree3 }; - - Tree< Double >[][] tree2DArray1 = new Tree[][] { trees1, trees2 }; + List< List< Tree< Double > > > treeMatrix1 = new ArrayList<>(); + treeMatrix1.add( Arrays.asList( tree1, tree2, tree3 ) ); + treeMatrix1.add( Arrays.asList( tree1, tree2, tree3 ) ); double t1t2 = 20d; double t1t3 = 100d; @@ -225,17 +223,18 @@ void testGetAverageDistanceMatrix() // trivial case - two identical set of trees double[][] distanceMatrix = - ClassificationUtils.getAverageDistanceMatrix( tree2DArray1, SimilarityMeasure.ZHANG_DISTANCE ); + ClassificationUtils.getAverageDistanceMatrix( treeMatrix1, SimilarityMeasure.ZHANG_DISTANCE ); assertArrayEquals( new double[] { 0, t1t2, t1t3 }, distanceMatrix[ 0 ], 0d ); assertArrayEquals( new double[] { t1t2, 0, t2t3 }, distanceMatrix[ 1 ], 0d ); assertArrayEquals( new double[] { t1t3, t2t3, 0 }, distanceMatrix[ 2 ], 0d ); - Tree< Double >[][] tree2DArray2 = new Tree[][] { { tree1, tree2, tree3 }, { tree2, tree1, tree3 } }; - distanceMatrix = ClassificationUtils.getAverageDistanceMatrix( tree2DArray2, SimilarityMeasure.ZHANG_DISTANCE ); + List< List< Tree< Double > > > treeMatrix2 = new ArrayList<>(); + treeMatrix2.add( Arrays.asList( tree1, tree2, tree3 ) ); + treeMatrix2.add( Arrays.asList( tree2, tree1, tree3 ) ); + distanceMatrix = ClassificationUtils.getAverageDistanceMatrix( treeMatrix2, SimilarityMeasure.ZHANG_DISTANCE ); assertArrayEquals( new double[] { 0, t1t2, ( t1t3 + t2t3 ) / 2d }, distanceMatrix[ 0 ], 0d ); assertArrayEquals( new double[] { t1t2, 0, ( t1t3 + t2t3 ) / 2d }, distanceMatrix[ 1 ], 0d ); assertArrayEquals( new double[] { ( t1t3 + t2t3 ) / 2d, ( t1t3 + t2t3 ) / 2d, 0 }, distanceMatrix[ 2 ], 0d ); - } @Test From 0b6eced52c49ac2ad68b4129e824912622650b69 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 5 Jun 2024 16:04:19 +0200 Subject: [PATCH 04/81] Add some logging to getAverageDistanceMatrix method --- .../mastodon/mamut/classification/util/ClassificationUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java b/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java index 8eb823d08..2551257b2 100644 --- a/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java +++ b/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java @@ -138,6 +138,7 @@ public static double[][] getAverageDistanceMatrix( final List< List< Tree< Doubl if ( treeMatrix.isEmpty() ) return new double[ 0 ][ 0 ]; + logger.debug( "Computing average similarity matrix with {} sets of {} trees each.", treeMatrix.size(), treeMatrix.get( 0 ).size() ); int numberOfTrees = treeMatrix.get( 0 ).size(); double[][] result = new double[ numberOfTrees ][ numberOfTrees ]; for ( List< Tree< Double > > trees : treeMatrix ) From 26c66f9a16f1910f752906a6759700c196b71c20 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 30 May 2024 14:13:08 +0200 Subject: [PATCH 05/81] Make methods getDistanceMatrix and getAverageDistanceMatrix more generic --- .../mamut/classification/ClassifyLineagesController.java | 2 +- .../mamut/classification/util/ClassificationUtils.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 19607461c..efde9505a 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -174,7 +174,7 @@ private void showDendrogram() private Classification< BranchSpotTree > classifyLineageTrees( List< BranchSpotTree > roots ) { logger.debug( "Start computing similarity matrix for {} lineage trees.", roots.size() ); - double[][] distances = ClassificationUtils.getDistanceMatrix( new ArrayList<>( roots ), similarityMeasure ); + double[][] distances = ClassificationUtils.getDistanceMatrix( roots, similarityMeasure ); logger.debug( "Finished computing similarity matrix. Shape: {}x{}={} entries.", distances.length, distances[ 0 ].length, distances.length * distances[ 0 ].length diff --git a/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java b/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java index 2551257b2..1515c382c 100644 --- a/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java +++ b/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java @@ -82,7 +82,8 @@ private ClassificationUtils() * @param similarityMeasure the similarity measure to be used * @return a symmetric quadratic distance matrix */ - public static double[][] getDistanceMatrix( final List< Tree< Double > > trees, final SimilarityMeasure similarityMeasure ) + public static < T extends Tree< Double > > double[][] getDistanceMatrix( final List< T > trees, + final SimilarityMeasure similarityMeasure ) { int size = trees.size(); double[][] distances = new double[ size ][ size ]; @@ -132,7 +133,7 @@ public static double[][] getDistanceMatrix( final List< Tree< Double > > trees, * @param similarityMeasure the similarity measure to be used * @return a symmetric quadratic distance matrix */ - public static double[][] getAverageDistanceMatrix( final List< List< Tree< Double > > > treeMatrix, + public static < T extends Tree< Double > > double[][] getAverageDistanceMatrix( final List< List< T > > treeMatrix, final SimilarityMeasure similarityMeasure ) { if ( treeMatrix.isEmpty() ) @@ -141,7 +142,7 @@ public static double[][] getAverageDistanceMatrix( final List< List< Tree< Doubl logger.debug( "Computing average similarity matrix with {} sets of {} trees each.", treeMatrix.size(), treeMatrix.get( 0 ).size() ); int numberOfTrees = treeMatrix.get( 0 ).size(); double[][] result = new double[ numberOfTrees ][ numberOfTrees ]; - for ( List< Tree< Double > > trees : treeMatrix ) + for ( List< T > trees : treeMatrix ) { double[][] temp = getDistanceMatrix( trees, similarityMeasure ); LinAlgHelpers.add( result, temp, result ); From ecd5854c04b2e6f9667127471a6dc3385bde0832 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 29 May 2024 16:27:59 +0200 Subject: [PATCH 06/81] Extract 2 new methods in the ClassifyLineagesCommand: updateParams() and updateFeedback() --- .../mamut/classification/ui/ClassifyLineagesCommand.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index 5d606755d..5ff03d0d6 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -130,12 +130,21 @@ public void run() @SuppressWarnings("unused") private void update() + { + updateParams(); + updateFeedback(); + } + + private void updateParams() { controller.setInputParams( CropCriteria.getByName( cropCriterion ), start, end, numberOfCellDivisions ); controller.setComputeParams( SimilarityMeasure.getByName( similarityMeasure ), ClusteringMethod.getByName( clusteringMethod ), numberOfClasses ); controller.setVisualisationParams( showDendrogram ); + } + private void updateFeedback() + { paramFeedback = ""; if ( controller.isValidParams() ) paramFeedback += "Parameters are valid."; From ac621e537b8e3f199b61ea8e79457d86cf1b2ca2 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 29 May 2024 16:28:55 +0200 Subject: [PATCH 07/81] Add some missing final modifiers to method params in ClassifyLineagesController --- .../mamut/classification/ClassifyLineagesController.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index efde9505a..cdd99aac9 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -171,7 +171,7 @@ private void showDendrogram() dendrogramView.show(); } - private Classification< BranchSpotTree > classifyLineageTrees( List< BranchSpotTree > roots ) + private Classification< BranchSpotTree > classifyLineageTrees( final List< BranchSpotTree > roots ) { logger.debug( "Start computing similarity matrix for {} lineage trees.", roots.size() ); double[][] distances = ClassificationUtils.getDistanceMatrix( roots, similarityMeasure ); @@ -202,7 +202,8 @@ private List< Pair< String, Integer > > createTagsAndColors() return tagsAndColors; } - private void applyClassification( Classification< BranchSpotTree > classification, List< Pair< String, Integer > > tagsAndColors ) + private void applyClassification( final Classification< BranchSpotTree > classification, + final List< Pair< String, Integer > > tagsAndColors ) { Set< Classification.ObjectClassification< BranchSpotTree > > objectClassifications = classification.getObjectClassifications(); TagSetStructure.TagSet tagSet = TagSetUtils.addNewTagSetToModel( model, getTagSetName(), tagsAndColors ); @@ -279,7 +280,8 @@ public void setInputParams( final CropCriteria cropCriterion, final int cropStar logger.debug( "Crop criterion {}, start: {}, end: {}", cropCriterion.getName(), this.cropStart, this.cropEnd ); } - public void setComputeParams( SimilarityMeasure similarityMeasure, ClusteringMethod clusteringMethod, int numberOfClasses ) + public void setComputeParams( final SimilarityMeasure similarityMeasure, final ClusteringMethod clusteringMethod, + final int numberOfClasses ) { this.similarityMeasure = similarityMeasure; this.clusteringMethod = clusteringMethod; From 34b6460bf5f5745ab8d31cda099db6e4a36c3409 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 29 May 2024 16:29:37 +0200 Subject: [PATCH 08/81] Add the possibility to specify a list of projects to ClassifyLineagesCommand --- .../mamut/classification/ClassifyLineagesController.java | 4 ++-- .../mamut/classification/ui/ClassifyLineagesCommand.java | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index cdd99aac9..65fdd8db6 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -6,13 +6,13 @@ * %% * 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 diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index 5ff03d0d6..4c9ee66f8 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -41,6 +41,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; import java.lang.invoke.MethodHandles; import java.util.Arrays; import java.util.List; @@ -103,6 +104,10 @@ public class ClassifyLineagesCommand extends InteractiveCommand @Parameter(label = "Feature", choices = "Branch duration", callback = "update") private String branchDuration; + @SuppressWarnings( "unused" ) + @Parameter( label = "List of projects", style = "files,extensions:mastodon", persist = false, callback = "update" ) + private File[] projects = new File[ 0 ]; + @SuppressWarnings("unused") @Parameter(label = "Show dendrogram of clustering", callback = "update") private boolean showDendrogram = true; From 84ac31c60d70b2087b65f348e7178e3355e483c2 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 30 May 2024 13:09:06 +0200 Subject: [PATCH 09/81] Move time measurements logs for computing distance matrix from method classifyLineageTrees to method getDistanceMatrix() --- .../mamut/classification/ClassifyLineagesController.java | 8 +------- .../mamut/classification/util/ClassificationUtils.java | 3 +++ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 65fdd8db6..e360df33a 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -173,16 +173,10 @@ private void showDendrogram() private Classification< BranchSpotTree > classifyLineageTrees( final List< BranchSpotTree > roots ) { - logger.debug( "Start computing similarity matrix for {} lineage trees.", roots.size() ); double[][] distances = ClassificationUtils.getDistanceMatrix( roots, similarityMeasure ); - logger.debug( - "Finished computing similarity matrix. Shape: {}x{}={} entries.", distances.length, distances[ 0 ].length, - distances.length * distances[ 0 ].length - ); BranchSpotTree[] rootBranchSpots = roots.toArray( new BranchSpotTree[ 0 ] ); Classification< BranchSpotTree > result = ClassificationUtils.getClassificationByClassCount( rootBranchSpots, distances, - clusteringMethod.getLinkageStrategy(), numberOfClasses - ); + clusteringMethod.getLinkageStrategy(), numberOfClasses ); logger.debug( "Finished hierarchical clustering. Created {} object classifications.", result.getObjectClassifications().size() ); return result; diff --git a/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java b/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java index 1515c382c..2a9848a07 100644 --- a/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java +++ b/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java @@ -85,6 +85,7 @@ private ClassificationUtils() public static < T extends Tree< Double > > double[][] getDistanceMatrix( final List< T > trees, final SimilarityMeasure similarityMeasure ) { + logger.debug( "Start computing similarity matrix for {} lineage trees.", trees.size() ); int size = trees.size(); double[][] distances = new double[ size ][ size ]; List< Pair< Integer, Integer > > pairs = new ArrayList<>(); @@ -116,6 +117,8 @@ public static < T extends Tree< Double > > double[][] getDistanceMatrix( final L } ); stopWatch.stop(); logger.debug( "Computed all distances in {} s.", stopWatch.getTime() / 1000d ); + logger.debug( "Shape of similarity matrix: {}x{}={} entries.", distances.length, distances[ 0 ].length, + distances.length * distances[ 0 ].length ); return distances; } From a783e508d6b0ca58b87ae0a7d1fad3f5547d1321 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 30 May 2024 13:20:20 +0200 Subject: [PATCH 10/81] Add param ProjectModel to getRoots() method in ClassifyLineagesController * This allows that this method can be called with any project model in the future * For backwards compatibility a getRoots() method with the project model of the controller is introduced --- .../mamut/classification/ClassifyLineagesController.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index e360df33a..d7d473760 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -226,6 +226,12 @@ private void applyClassification( final Classification< BranchSpotTree > classif private List< BranchSpotTree > getRoots() { + return getRoots( projectModel ); + } + + private List< BranchSpotTree > getRoots( final ProjectModel projectModel ) + { + Model model = projectModel.getModel(); if ( !projectModel.getBranchGraphSync().isUptodate() ) model.getBranchGraph().graphRebuilt(); From add20918a9810f44a6118bfe2252630d08ba64ce Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 30 May 2024 13:23:00 +0200 Subject: [PATCH 11/81] Rename model and projectModel to referenceModel and referenceProjectModel in ClassifyLineagesController --- .../ClassifyLineagesController.java | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index d7d473760..8c22344d6 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -70,9 +70,9 @@ public class ClassifyLineagesController private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() ); - private final Model model; + private final Model referenceModel; - private final ProjectModel projectModel; + private final ProjectModel referenceProjectModel; private final PrefService prefs; @@ -98,22 +98,22 @@ public class ClassifyLineagesController /** * Create a new controller for classifying lineage trees. - * @param projectModel the project model + * @param referenceProjectModel the reference project model */ - public ClassifyLineagesController( final ProjectModel projectModel ) + public ClassifyLineagesController( final ProjectModel referenceProjectModel ) { - this( projectModel, null ); + this( referenceProjectModel, null ); } /** * Create a new controller for classifying lineage trees. - * @param projectModel the project model + * @param referenceProjectModel the reference project model * @param prefs the preference service */ - public ClassifyLineagesController( final ProjectModel projectModel, final PrefService prefs ) + public ClassifyLineagesController( final ProjectModel referenceProjectModel, final PrefService prefs ) { - this.projectModel = projectModel; - this.model = projectModel.getModel(); + this.referenceProjectModel = referenceProjectModel; + this.referenceModel = referenceProjectModel.getModel(); this.prefs = prefs; } @@ -126,7 +126,7 @@ public void createTagSet() return; if ( !isValidParams() ) throw new IllegalArgumentException( "Invalid parameters settings." ); - ReentrantReadWriteLock.WriteLock writeLock = model.getGraph().getLock().writeLock(); + ReentrantReadWriteLock.WriteLock writeLock = referenceModel.getGraph().getLock().writeLock(); writeLock.lock(); try { @@ -167,7 +167,7 @@ String getParameters() private void showDendrogram() { String header = "Dendrogram of hierarchical clustering of lineages
" + getParameters() + ""; - DendrogramView< BranchSpotTree > dendrogramView = new DendrogramView<>( classification, header, model, prefs ); + DendrogramView< BranchSpotTree > dendrogramView = new DendrogramView<>( classification, header, referenceModel, prefs ); dendrogramView.show(); } @@ -200,7 +200,7 @@ private void applyClassification( final Classification< BranchSpotTree > classif final List< Pair< String, Integer > > tagsAndColors ) { Set< Classification.ObjectClassification< BranchSpotTree > > objectClassifications = classification.getObjectClassifications(); - TagSetStructure.TagSet tagSet = TagSetUtils.addNewTagSetToModel( model, getTagSetName(), tagsAndColors ); + TagSetStructure.TagSet tagSet = TagSetUtils.addNewTagSetToModel( referenceModel, getTagSetName(), tagsAndColors ); int i = 0; for ( Classification.ObjectClassification< BranchSpotTree > objectClassification : objectClassifications ) { @@ -209,15 +209,16 @@ private void applyClassification( final Classification< BranchSpotTree > classif TagSetStructure.Tag tag = tagSet.getTags().get( i ); for ( BranchSpotTree tree : trees ) { - Spot rootSpot = model.getBranchGraph().getFirstLinkedVertex( tree.getBranchSpot(), model.getGraph().vertexRef() ); - ModelGraph modelGraph = model.getGraph(); + Spot rootSpot = + referenceModel.getBranchGraph().getFirstLinkedVertex( tree.getBranchSpot(), referenceModel.getGraph().vertexRef() ); + ModelGraph modelGraph = referenceModel.getGraph(); DepthFirstIterator< Spot, Link > iterator = new DepthFirstIterator<>( rootSpot, modelGraph ); iterator.forEachRemaining( spot -> { if ( spot.getTimepoint() < cropStart ) return; if ( spot.getTimepoint() > cropEnd ) return; - TagSetUtils.tagSpotAndIncomingEdges( model, spot, tagSet, tag ); + TagSetUtils.tagSpotAndIncomingEdges( referenceModel, spot, tagSet, tag ); } ); } i++; @@ -226,7 +227,7 @@ private void applyClassification( final Classification< BranchSpotTree > classif private List< BranchSpotTree > getRoots() { - return getRoots( projectModel ); + return getRoots( referenceProjectModel ); } private List< BranchSpotTree > getRoots( final ProjectModel projectModel ) @@ -315,7 +316,7 @@ public List< String > getFeedback() { try { - LineageTreeUtils.getFirstTimepointWithNSpots( model, cropStart ); + LineageTreeUtils.getFirstTimepointWithNSpots( referenceModel, cropStart ); } catch ( NoSuchElementException e ) { @@ -325,7 +326,7 @@ public List< String > getFeedback() } try { - LineageTreeUtils.getFirstTimepointWithNSpots( model, cropEnd ); + LineageTreeUtils.getFirstTimepointWithNSpots( referenceModel, cropEnd ); } catch ( NoSuchElementException e ) { From 114a2008c099d2aa985afa6f2ed9df7390a64a08 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 30 May 2024 14:13:57 +0200 Subject: [PATCH 12/81] Pass distance matrix as param to method classifyLineageTrees() * Allows passing different kinds of distance matrices to classify lineage trees --- .../classification/ClassifyLineagesController.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 8c22344d6..144d0f5c7 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -143,7 +143,8 @@ public void createTagSet() private void runClassification() { List< BranchSpotTree > roots = getRoots(); - classification = classifyLineageTrees( roots ); + double[][] distances = ClassificationUtils.getDistanceMatrix( roots, similarityMeasure ); + classification = classifyLineageTrees( roots, distances ); List< Pair< String, Integer > > tagsAndColors = createTagsAndColors(); applyClassification( classification, tagsAndColors ); if ( showDendrogram ) @@ -171,9 +172,12 @@ private void showDendrogram() dendrogramView.show(); } - private Classification< BranchSpotTree > classifyLineageTrees( final List< BranchSpotTree > roots ) + private Classification< BranchSpotTree > classifyLineageTrees( final List< BranchSpotTree > roots, final double[][] distances ) { - double[][] distances = ClassificationUtils.getDistanceMatrix( roots, similarityMeasure ); + if ( roots.size() != distances.length ) + throw new IllegalArgumentException( + "Number of roots (" + roots.size() + ") and size of distance matrix (" + distances.length + "x" + + distances[ 0 ].length + ") do not match." ); BranchSpotTree[] rootBranchSpots = roots.toArray( new BranchSpotTree[ 0 ] ); Classification< BranchSpotTree > result = ClassificationUtils.getClassificationByClassCount( rootBranchSpots, distances, clusteringMethod.getLinkageStrategy(), numberOfClasses ); From f191373fa89a5fc696e1a9a8b7e92bedc317e915 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 30 May 2024 14:35:56 +0200 Subject: [PATCH 13/81] Add a list of external project files to ClassifyLineagesController and a set method for them --- .../classification/ClassifyLineagesController.java | 13 +++++++++++++ .../classification/ui/ClassifyLineagesCommand.java | 1 + 2 files changed, 14 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 144d0f5c7..6a8e47e7d 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -52,8 +52,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; import java.lang.invoke.MethodHandles; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; @@ -90,6 +92,8 @@ public class ClassifyLineagesController private int minCellDivisions; + private final List< File > externalProjects; + private boolean showDendrogram; private Classification< BranchSpotTree > classification; @@ -115,6 +119,7 @@ public ClassifyLineagesController( final ProjectModel referenceProjectModel, fin this.referenceProjectModel = referenceProjectModel; this.referenceModel = referenceProjectModel.getModel(); this.prefs = prefs; + this.externalProjects = new ArrayList<>(); } /** @@ -298,6 +303,14 @@ public void setVisualisationParams( final boolean showDendrogram ) this.showDendrogram = showDendrogram; } + public void setExternalProjects( final File[] projects ) + { + externalProjects.clear(); + if ( projects == null ) + return; + externalProjects.addAll( Arrays.asList( projects ) ); + } + public List< String > getFeedback() { List< String > feedback = new ArrayList<>(); diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index 4c9ee66f8..c525da782 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -146,6 +146,7 @@ private void updateParams() controller.setComputeParams( SimilarityMeasure.getByName( similarityMeasure ), ClusteringMethod.getByName( clusteringMethod ), numberOfClasses ); controller.setVisualisationParams( showDendrogram ); + controller.setExternalProjects( projects ); } private void updateFeedback() From 9aad78a663a1d730ff7d9892d74c6ff620ceffa8 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 30 May 2024 15:16:59 +0200 Subject: [PATCH 14/81] Add getName() method BranchSpotTree which returns the first the label for the branch spot associated with the BranchSpotTree --- .../treesimilarity/tree/BranchSpotTree.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/classification/treesimilarity/tree/BranchSpotTree.java b/src/main/java/org/mastodon/mamut/classification/treesimilarity/tree/BranchSpotTree.java index 6cdc8f464..280ce33fd 100644 --- a/src/main/java/org/mastodon/mamut/classification/treesimilarity/tree/BranchSpotTree.java +++ b/src/main/java/org/mastodon/mamut/classification/treesimilarity/tree/BranchSpotTree.java @@ -113,6 +113,15 @@ public String toString() return labelSupplier.get(); } + /** + * Gets the first the label for the branch spot associated with the BranchSpotTree + * @return the first label of the branch spot + */ + public String getName() + { + return branchSpot.getFirstLabel(); + } + public class LabelSupplier implements Supplier< String > { private boolean includeName = true; From f58ccd93087db75d18eb259f2598bbba57623ea0 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Tue, 11 Jun 2024 16:15:31 +0200 Subject: [PATCH 15/81] Add new class ProjectAccessor that encapsulates loading project models from a file and closing them, when not needed any longer --- .../classification/util/ProjectAccessor.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/main/java/org/mastodon/mamut/classification/util/ProjectAccessor.java diff --git a/src/main/java/org/mastodon/mamut/classification/util/ProjectAccessor.java b/src/main/java/org/mastodon/mamut/classification/util/ProjectAccessor.java new file mode 100644 index 000000000..c2aee4d79 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/classification/util/ProjectAccessor.java @@ -0,0 +1,85 @@ +package org.mastodon.mamut.classification.util; + +import mpicbg.spim.data.SpimDataException; +import org.mastodon.mamut.ProjectModel; +import org.mastodon.mamut.io.ProjectLoader; +import org.scijava.Context; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.List; + +/** + * This class can be used to manage a list of project files and their corresponding ProjectModel instances. + * When instantiated, it loads the ProjectModels from the given project file objects. + *
+ * It implements the close method from the AutoCloseable interface, which is used to close all loaded ProjectModel instances when they are no longer needed. + */ +public class ProjectAccessor implements AutoCloseable +{ + private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() ); + + private final List< File > projectFiles; + + private final Context context; + + private final List< ProjectModel > projectModels; + + /** + * Loads the ProjectModel instances from the project files and stores the returned list internally. + * + * @param projectFiles A List of File objects representing the project files to load the ProjectModel instances from. + * @param context A Context object used for loading the ProjectModel instances. + */ + public ProjectAccessor( final List< File > projectFiles, final Context context ) + { + this.projectFiles = projectFiles; + this.context = context; + this.projectModels = loadProjectModels(); + } + + /** + * Retrieves the list of ProjectModel instances loaded from the project files. + *
+ * This method returns the list of ProjectModel instances that were loaded when this ProjectAccessor instance was created. + * The returned list is a reference to the internal list, not a copy. Therefore, changes to the returned list will affect the internal list. + * + * @return A List of ProjectModel instances loaded from the project files. + */ + public List< ProjectModel > getProjectModels() + { + return projectModels; + } + + private List< ProjectModel > loadProjectModels() + { + List< ProjectModel > externalProjectModels = new ArrayList<>(); + for ( File project : projectFiles ) + { + try + { + ProjectModel projectModel = + ProjectLoader.open( project.getAbsolutePath(), context, false, true ); + externalProjectModels.add( projectModel ); + } + catch ( IOException | SpimDataException e ) + { + logger.warn( "Could not load project from file: {} ", project.getAbsolutePath(), e ); + } + } + return externalProjectModels; + } + + @Override + public void close() + { + for ( ProjectModel projectModel : projectModels ) + { + projectModel.close(); + } + } +} From 7e5e35ba6c8bd6db3efbd2ea12aeed320e603390 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 30 May 2024 15:31:03 +0200 Subject: [PATCH 16/81] Add new method getRootsAndDistanceMatrix() * In case no external projects exist, returns the roots and the distance matrix for them * Otherwise, reduces the list of roots to the list of common roots according to their names and computes the average distance matrix of them --- .../ClassifyLineagesController.java | 69 ++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 6a8e47e7d..1cedf7da7 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -38,6 +38,7 @@ import org.mastodon.mamut.classification.config.CropCriteria; import org.mastodon.mamut.classification.ui.DendrogramView; import org.mastodon.mamut.classification.util.ClassificationUtils; +import org.mastodon.mamut.classification.util.ProjectAccessor; import org.mastodon.mamut.model.Link; import org.mastodon.mamut.model.Model; import org.mastodon.mamut.model.ModelGraph; @@ -57,11 +58,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; import java.util.StringJoiner; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; /** * Controller class that serves as bridge between the classification algorithm and the user interface. @@ -147,8 +151,9 @@ public void createTagSet() private void runClassification() { - List< BranchSpotTree > roots = getRoots(); - double[][] distances = ClassificationUtils.getDistanceMatrix( roots, similarityMeasure ); + Pair< List< BranchSpotTree >, double[][] > rootsAndDistances = getRootsAndDistanceMatrix(); + List< BranchSpotTree > roots = rootsAndDistances.getLeft(); + double[][] distances = rootsAndDistances.getRight(); classification = classifyLineageTrees( roots, distances ); List< Pair< String, Integer > > tagsAndColors = createTagsAndColors(); applyClassification( classification, tagsAndColors ); @@ -156,6 +161,66 @@ private void runClassification() showDendrogram(); } + private Pair< List< BranchSpotTree >, double[][] > getRootsAndDistanceMatrix() + { + List< BranchSpotTree > roots = getRoots(); + if ( externalProjects.isEmpty() ) + { + double[][] distances = ClassificationUtils.getDistanceMatrix( roots, similarityMeasure ); + return Pair.of( roots, distances ); + } + + try (ProjectAccessor projectAccessor = new ProjectAccessor( externalProjects, referenceProjectModel.getContext() )) + { + List< ProjectModel > externalProjectModels = projectAccessor.getProjectModels(); + List< String > commonRootNames = findCommonRootNames( externalProjectModels ); + if ( logger.isDebugEnabled() ) + { + logger.info( "Found {} common root names in {} projects.", commonRootNames.size(), externalProjectModels.size() + 1 ); + String names = commonRootNames.stream().map( Object::toString ).collect( Collectors.joining( "," ) ); + logger.debug( "Common root names are: {}", names ); + } + List< List< BranchSpotTree > > treeMatrix = new ArrayList<>(); + + keepCommonRootsAndSort( roots, commonRootNames ); + treeMatrix.add( roots ); + for ( ProjectModel projectModel : externalProjectModels ) + { + List< BranchSpotTree > externalRoots = getRoots( projectModel ); + keepCommonRootsAndSort( externalRoots, commonRootNames ); + treeMatrix.add( externalRoots ); + } + return Pair.of( roots, ClassificationUtils.getAverageDistanceMatrix( treeMatrix, similarityMeasure ) ); + } + } + + private List< String > findCommonRootNames( final List< ProjectModel > externalProjectModels ) + { + Set< String > commonRootNames = extractRootNamesFromProjectModel( referenceProjectModel ); + for ( ProjectModel projectModel : externalProjectModels ) + { + Set< String > rootNames = extractRootNamesFromProjectModel( projectModel ); + commonRootNames.retainAll( rootNames ); + } + List< String > commonRootNamesList = new ArrayList<>( commonRootNames ); + commonRootNamesList.sort( String::compareTo ); + return commonRootNamesList; + } + + private Set< String > extractRootNamesFromProjectModel( final ProjectModel projectModel ) + { + List< BranchSpotTree > roots = getRoots( projectModel ); + Set< String > rootNames = new HashSet<>(); + roots.forEach( root -> rootNames.add( root.getName() ) ); + return rootNames; + } + + private static void keepCommonRootsAndSort( final List< BranchSpotTree > roots, final List< String > commonRootNames ) + { + roots.removeIf( root -> !commonRootNames.contains( root.getName() ) ); + roots.sort( Comparator.comparing( BranchSpotTree::getName ) ); + } + String getParameters() { StringJoiner joiner = new StringJoiner( ", " ); From 62c8f0b71eb0e381fc2768111ef826b4bc5d28e9 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 5 Jun 2024 15:40:46 +0200 Subject: [PATCH 17/81] Remove lengthy documentation from ClassifyLineagesCommand and instead refer to an extended readme.md --- doc/classification/readme.md | 35 ++++++++++++------- .../ui/ClassifyLineagesCommand.java | 5 ++- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/doc/classification/readme.md b/doc/classification/readme.md index b8bd8eff8..0ccf1b337 100644 --- a/doc/classification/readme.md +++ b/doc/classification/readme.md @@ -1,14 +1,17 @@ ## Lineage Tree Classification -* This plugin is capable of grouping similar lineage trees together. +* This command is capable of grouping similar lineage trees together. +* The linage classification operates on Mastodon's branch graph. +* Lineage trees are considered similar, if they share a similar structure and thus represent a similar cell division + pattern. The structure of a lineage tree is represented by the tree topology. + This tree topology consists of the actual branching pattern and the cell lifetimes, + i.e. the time points between two subsequent cell divisions. * The similarity of a pair of lineage trees is computed based on the Zhang edit distance for unordered trees ([Zhang, K. Algorithmica 15, 205–222, 1996](https://doi.org/10.1007/BF01975866)). This method captures the cost of the transformation of one tree into the other. -* The cost function applied for the tree edit distance uses the attribute branch spot duration, which is computed as a - difference of time points between to subsequent divisions reflecting the start and the end of a spot's lifetime. -* Thus, the linage classification operates on Mastodon's branch graph. * The Zhang unordered edit distance allows the following edit operations. The edit operations are defined in a way that - they satisfy the constraints elaborated in section 3.1 ("Constrained Edit Distance Mappings") of the paper: + they satisfy the constraints elaborated in section 3.1 ("Constrained Edit Distance Mappings") of the + paper: [Zhang, K. Algorithmica 15, 205–222, 1996](https://doi.org/10.1007/BF01975866) ``` Note: The prefix T may represent a node or a complete subtree. Nodes without this prefix are just nodes. @@ -98,26 +101,32 @@ Tree2 ``` * Edit distance of 69, because: - * one node has a difference of 1 * two nodes have a difference of 24 each * two extra nodes are added with a weight of 10 each * ![zhang_example.gif](zhang_example.gif) -* The tree edit distances are computed between all possible combinations of lineage trees leading to a two-dimensional - matrix. The values in this matrix are considered to reflect similarities of lineage trees. Low tree edit distances - represent a high similarity between a discrete pair of lineage trees. -* This similarity matrix is then used to perform - an [agglomerative hierarchical clustering](https://en.wikipedia.org/wiki/Hierarchical_clustering) into a specifiable + +* The similarity measure uses the attribute cell lifetime, which is computed as a difference of time points between to + subsequent divisions. There are multiple ways to compute the similarity measure between two lineage trees (cf. below). +* The similarities are computed between all possible combinations of lineage trees leading to a two-dimensional + similarity matrix. The values in this matrix are considered to reflect similarities of lineage trees. Low tree edit + distances represent a high similarity between a discrete pair of lineage trees. This matrix is then used to perform + an [Agglomerative Hierarchical Clustering](https://en.wikipedia.org/wiki/Hierarchical_clustering) into a specifiable number of classes. +* For the clustering three + different [linkage methods](https://en.wikipedia.org/wiki/Hierarchical_clustering#Cluster_Linkage) can be chosen. ### Parameters * Crop criterion: - * Time point (default) - * Number of spots + * Number of spots (default) + * Time point * Crop start + * At which number of spots or time point the analysis should start * Crop end + * At which number of spots or time point the analysis should end * Number of classes + * The number of classes the lineage trees should be grouped into * Must not be greater than the number of lineage trees * Minimum number of divisions * The minimum number of divisions a lineage tree must have to be considered in the classification diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index c525da782..a221d7535 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -66,9 +66,8 @@ public class ClassifyLineagesCommand extends InteractiveCommand private String documentation = "\n" + "\n" + "

Classification of Lineage Trees

\n" - + "

This plugin is capable of grouping similar lineage trees together. This is done by creating a tag set and assigning subtrees that are similar to each other with the same tag.

\n" - + "

The similarity between two subtrees is computed based on the Zhang edit distance for unordered trees (Zhang, K. Algorithmica 15, 205–222, 1996). The similarity measure uses the attribute the cell lifetime, which is computed as a difference of timepoints between to subsequent divisions. It is possible to apply the absolute difference, average difference or the normalized difference of cell lifetimes.

\n" - + "

The similarity is computed between all possible combinations of subtrees leading to a two-dimensional similarity matrix. This matrix is then used to perform a agglomerative hierarchical clustering into a specifiable number of classes. For the clustering three different linkage methods can be chosen.

\n" + + "

This command is capable of grouping similar lineage trees together, i.e. trees that share a similar cell division pattern. This is realised by creating a new tag set and assigning the same tag to lineage trees that are similar to each other.

\n" + + "

Refer to the documentation to learn how the similarity is computed.

" + "\n" + "\n"; From 559cfe6635a5a824955fe94df0dd343d4fbc907c Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 5 Jun 2024 15:47:03 +0200 Subject: [PATCH 18/81] Change layout of ClassifyLineagesCommand --- .../ui/ClassifyLineagesCommand.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index a221d7535..92d7cbbfe 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -51,9 +51,7 @@ public class ClassifyLineagesCommand extends InteractiveCommand { - private static final int WIDTH = 15; - - private static final int WIDTH_INPUT = 7; + private static final float WIDTH = 18.5f; private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() ); @@ -88,7 +86,7 @@ public class ClassifyLineagesCommand extends InteractiveCommand private int numberOfClasses; @SuppressWarnings("unused") - @Parameter(label = "Minimum number of cell divisions", min = "0", description = "Only include lineage trees with at least the number of divisions specified here.", callback = "update") + @Parameter( label = "Minimum number
of cell divisions", min = "0", description = "Only include lineage trees with at least the number of divisions specified here.", callback = "update" ) private int numberOfCellDivisions; @SuppressWarnings("all") @@ -96,7 +94,7 @@ public class ClassifyLineagesCommand extends InteractiveCommand public String similarityMeasure = SimilarityMeasure.NORMALIZED_ZHANG_DIFFERENCE.getName(); @SuppressWarnings("all") - @Parameter( label = "Linkage strategy for hierarchical clustering", initializer = "initClusteringMethodChoices", callback = "update" ) + @Parameter( label = "Linkage strategy for
hierarchical clustering", initializer = "initClusteringMethodChoices", callback = "update" ) private String clusteringMethod = ClusteringMethod.AVERAGE_LINKAGE.getName(); @SuppressWarnings("unused") @@ -104,11 +102,11 @@ public class ClassifyLineagesCommand extends InteractiveCommand private String branchDuration; @SuppressWarnings( "unused" ) - @Parameter( label = "List of projects", style = "files,extensions:mastodon", persist = false, callback = "update" ) + @Parameter( label = "List of projects
(Drag & Drop supported)", style = "files,extensions:mastodon", persist = false, callback = "update", initializer = "initProjectsDefault" ) private File[] projects = new File[ 0 ]; @SuppressWarnings("unused") - @Parameter(label = "Show dendrogram of clustering", callback = "update") + @Parameter( label = "Show dendrogram
of clustering", callback = "update" ) private boolean showDendrogram = true; @SuppressWarnings("unused") @@ -150,7 +148,7 @@ private void updateParams() private void updateFeedback() { - paramFeedback = ""; + paramFeedback = ""; if ( controller.isValidParams() ) paramFeedback += "Parameters are valid."; else @@ -179,7 +177,7 @@ private void createTagSet() color = "red"; logger.error( "Error during lineage classification: {}", e.getMessage() ); } - computeFeedback = "" + feedback + ""; + computeFeedback = "" + feedback + ""; } } From fa24f0710e4e3b0546be00775080b1d77622809f Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 5 Jun 2024 15:47:37 +0200 Subject: [PATCH 19/81] Make number of spots the default crop criterion --- .../mamut/classification/ui/ClassifyLineagesCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index 92d7cbbfe..62aa7fe15 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -71,7 +71,7 @@ public class ClassifyLineagesCommand extends InteractiveCommand @SuppressWarnings("all") @Parameter( label = "Crop criterion", initializer = "initCropCriterionChoices", callback = "update" ) - private String cropCriterion = CropCriteria.TIMEPOINT.getName(); + private String cropCriterion = CropCriteria.NUMBER_OF_SPOTS.getName(); @SuppressWarnings("unused") @Parameter(label = "Crop start", min = "0", callback = "update") From 64e9e8b37c53930bef92459d58ed52ee4a89a4f3 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 5 Jun 2024 16:07:24 +0200 Subject: [PATCH 20/81] Init projects choice in ClassifyLineagesCommand * The goal is that with this default choice adding further projects is more convenient, since the file choose opens itself in a directory that should in most case be near to the directory of further projects --- .../classification/ClassifyLineagesController.java | 5 +++++ .../classification/ui/ClassifyLineagesCommand.java | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 1cedf7da7..89e6f3183 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -149,6 +149,11 @@ public void createTagSet() } } + public ProjectModel getProjectModel() + { + return referenceProjectModel; + } + private void runClassification() { Pair< List< BranchSpotTree >, double[][] > rootsAndDistances = getRootsAndDistanceMatrix(); diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index 62aa7fe15..8e2b7cebf 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -33,8 +33,10 @@ import org.mastodon.mamut.classification.config.SimilarityMeasure; import org.mastodon.mamut.classification.ClassifyLineagesController; import org.mastodon.mamut.classification.config.CropCriteria; +import org.mastodon.mamut.io.project.MamutProject; import org.scijava.ItemVisibility; import org.scijava.command.InteractiveCommand; +import org.scijava.module.MutableModuleItem; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import org.scijava.widget.Button; @@ -44,6 +46,7 @@ import java.io.File; import java.lang.invoke.MethodHandles; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -199,6 +202,17 @@ private void initClusteringMethodChoices() getInfo().getMutableInput( "clusteringMethod", String.class ).setChoices( enumNamesAsList( ClusteringMethod.values() ) ); } + @SuppressWarnings( "unused" ) + private void initProjectsDefault() + { + MamutProject project = controller.getProjectModel().getProject(); + MutableModuleItem< File[] > projectsItem = getInfo().getMutableInput( "projects", File[].class ); + if ( project != null ) + projectsItem.setChoices( Collections.singletonList( new File[] { project.getProjectRoot() } ) ); + else + projectsItem.setChoices( Collections.emptyList() ); + } + static List< String > enumNamesAsList( final HasName[] values ) { return Arrays.stream( values ).map( HasName::getName ).collect( Collectors.toList() ); From eca7fb334b859c93efa56788e931aa8609a0ad2e Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 6 Jun 2024 14:18:49 +0200 Subject: [PATCH 21/81] Add Model as param to method applyClassification() * This allows to use the applyClassification method for multiple project models --- .../classification/ClassifyLineagesController.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 89e6f3183..ebc51f5ab 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -161,7 +161,7 @@ private void runClassification() double[][] distances = rootsAndDistances.getRight(); classification = classifyLineageTrees( roots, distances ); List< Pair< String, Integer > > tagsAndColors = createTagsAndColors(); - applyClassification( classification, tagsAndColors ); + applyClassification( classification, tagsAndColors, referenceModel ); if ( showDendrogram ) showDendrogram(); } @@ -276,10 +276,10 @@ private List< Pair< String, Integer > > createTagsAndColors() } private void applyClassification( final Classification< BranchSpotTree > classification, - final List< Pair< String, Integer > > tagsAndColors ) + final List< Pair< String, Integer > > tagsAndColors, final Model model ) { Set< Classification.ObjectClassification< BranchSpotTree > > objectClassifications = classification.getObjectClassifications(); - TagSetStructure.TagSet tagSet = TagSetUtils.addNewTagSetToModel( referenceModel, getTagSetName(), tagsAndColors ); + TagSetStructure.TagSet tagSet = TagSetUtils.addNewTagSetToModel( model, getTagSetName(), tagsAndColors ); int i = 0; for ( Classification.ObjectClassification< BranchSpotTree > objectClassification : objectClassifications ) { @@ -288,16 +288,15 @@ private void applyClassification( final Classification< BranchSpotTree > classif TagSetStructure.Tag tag = tagSet.getTags().get( i ); for ( BranchSpotTree tree : trees ) { - Spot rootSpot = - referenceModel.getBranchGraph().getFirstLinkedVertex( tree.getBranchSpot(), referenceModel.getGraph().vertexRef() ); - ModelGraph modelGraph = referenceModel.getGraph(); + Spot rootSpot = model.getBranchGraph().getFirstLinkedVertex( tree.getBranchSpot(), model.getGraph().vertexRef() ); + ModelGraph modelGraph = model.getGraph(); DepthFirstIterator< Spot, Link > iterator = new DepthFirstIterator<>( rootSpot, modelGraph ); iterator.forEachRemaining( spot -> { if ( spot.getTimepoint() < cropStart ) return; if ( spot.getTimepoint() > cropEnd ) return; - TagSetUtils.tagSpotAndIncomingEdges( referenceModel, spot, tagSet, tag ); + TagSetUtils.tagSpotAndIncomingEdges( model, spot, tagSet, tag ); } ); } i++; From 9a7fe32193cf548ebee279d30cd54b2e918134ca Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 6 Jun 2024 18:09:36 +0200 Subject: [PATCH 22/81] Remove init project choice, since it interferes with adding further projects to the list --- .../ui/ClassifyLineagesCommand.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index 8e2b7cebf..18490272a 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -33,10 +33,8 @@ import org.mastodon.mamut.classification.config.SimilarityMeasure; import org.mastodon.mamut.classification.ClassifyLineagesController; import org.mastodon.mamut.classification.config.CropCriteria; -import org.mastodon.mamut.io.project.MamutProject; import org.scijava.ItemVisibility; import org.scijava.command.InteractiveCommand; -import org.scijava.module.MutableModuleItem; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import org.scijava.widget.Button; @@ -46,7 +44,6 @@ import java.io.File; import java.lang.invoke.MethodHandles; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -105,7 +102,7 @@ public class ClassifyLineagesCommand extends InteractiveCommand private String branchDuration; @SuppressWarnings( "unused" ) - @Parameter( label = "List of projects
(Drag & Drop supported)", style = "files,extensions:mastodon", persist = false, callback = "update", initializer = "initProjectsDefault" ) + @Parameter( label = "List of projects
(Drag & Drop supported)", style = "files,extensions:mastodon", persist = false, callback = "update" ) private File[] projects = new File[ 0 ]; @SuppressWarnings("unused") @@ -202,17 +199,6 @@ private void initClusteringMethodChoices() getInfo().getMutableInput( "clusteringMethod", String.class ).setChoices( enumNamesAsList( ClusteringMethod.values() ) ); } - @SuppressWarnings( "unused" ) - private void initProjectsDefault() - { - MamutProject project = controller.getProjectModel().getProject(); - MutableModuleItem< File[] > projectsItem = getInfo().getMutableInput( "projects", File[].class ); - if ( project != null ) - projectsItem.setChoices( Collections.singletonList( new File[] { project.getProjectRoot() } ) ); - else - projectsItem.setChoices( Collections.emptyList() ); - } - static List< String > enumNamesAsList( final HasName[] values ) { return Arrays.stream( values ).map( HasName::getName ).collect( Collectors.toList() ); From 192499b25198c645d97d756a0b1d1c28339a2d9d Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 6 Jun 2024 18:10:42 +0200 Subject: [PATCH 23/81] Add checks for crop start/end for all projects when crop criterion spots is selected --- .../ClassifyLineagesController.java | 59 ++++++++++++------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index ebc51f5ab..7f9d5472d 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -399,28 +399,7 @@ public List< String > getFeedback() logger.debug( message ); } if ( cropCriterion.equals( CropCriteria.NUMBER_OF_SPOTS ) ) - { - try - { - LineageTreeUtils.getFirstTimepointWithNSpots( referenceModel, cropStart ); - } - catch ( NoSuchElementException e ) - { - String message = e.getMessage(); - feedback.add( message ); - logger.debug( message ); - } - try - { - LineageTreeUtils.getFirstTimepointWithNSpots( referenceModel, cropEnd ); - } - catch ( NoSuchElementException e ) - { - String message = e.getMessage(); - feedback.add( message ); - logger.debug( message ); - } - } + feedback.addAll( checkNumberOfSpots() ); return feedback; } @@ -444,4 +423,40 @@ private String getTagSetName() + minCellDivisions + ") "; } + + private List< String > checkNumberOfSpots() + { + List< String > feedback = new ArrayList<>(); + Set< ProjectModel > allModels = new HashSet<>( Collections.singletonList( referenceProjectModel ) ); + try (ProjectAccessor projectAccessor = new ProjectAccessor( externalProjects, referenceProjectModel.getContext() )) + { + allModels.addAll( projectAccessor.getProjectModels() ); + for ( ProjectModel projectModel : allModels ) + { + Model model = projectModel.getModel(); + String projectName = projectModel.getProjectName(); + try + { + LineageTreeUtils.getFirstTimepointWithNSpots( model, cropStart ); + } + catch ( NoSuchElementException e ) + { + String message = projectName + ", crop start: " + e.getMessage(); + feedback.add( message ); + logger.debug( message ); + } + try + { + LineageTreeUtils.getFirstTimepointWithNSpots( model, cropEnd ); + } + catch ( NoSuchElementException e ) + { + String message = projectName + ", crop end: " + e.getMessage(); + feedback.add( message ); + logger.debug( message ); + } + } + } + return feedback; + } } From 84acfb355b79d8ad214e2566ee4d512679d3196e Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Tue, 11 Jun 2024 16:36:28 +0200 Subject: [PATCH 24/81] Change check for max classes from not more than roots to not more than common root names --- .../mamut/classification/ClassifyLineagesController.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 7f9d5472d..ca5d329cb 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -390,7 +390,11 @@ public List< String > getFeedback() logger.debug( message ); } - int roots = getRoots().size(); + int roots; + try (ProjectAccessor projectAccessor = new ProjectAccessor( externalProjects, referenceProjectModel.getContext() )) + { + roots = findCommonRootNames( projectAccessor.getProjectModels() ).size(); + } if ( numberOfClasses > roots ) { String message = From e26f16efa1d39edcc1cb9256ad808325b8e7de0f Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 13 Jun 2024 13:09:02 +0200 Subject: [PATCH 25/81] Add commands of ClassifyLineagesPlugin to command descriptions --- .../mastodon/mamut/classification/ClassifyLineagesPlugin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java index 4d0a1866e..f890afec3 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java @@ -6,13 +6,13 @@ * %% * 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 From 6f7ea36abd6579abb624de2c8beee4a48c4491ef Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Mon, 17 Jun 2024 15:51:05 +0200 Subject: [PATCH 26/81] Change Classification variable from class to local variable --- .../classification/ClassifyLineagesController.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index ca5d329cb..353b78108 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -100,8 +100,6 @@ public class ClassifyLineagesController private boolean showDendrogram; - private Classification< BranchSpotTree > classification; - private boolean running = false; /** @@ -159,11 +157,11 @@ private void runClassification() Pair< List< BranchSpotTree >, double[][] > rootsAndDistances = getRootsAndDistanceMatrix(); List< BranchSpotTree > roots = rootsAndDistances.getLeft(); double[][] distances = rootsAndDistances.getRight(); - classification = classifyLineageTrees( roots, distances ); - List< Pair< String, Integer > > tagsAndColors = createTagsAndColors(); + Classification< BranchSpotTree > classification = classifyLineageTrees( roots, distances ); + List< Pair< String, Integer > > tagsAndColors = createTagsAndColors( classification ); applyClassification( classification, tagsAndColors, referenceModel ); if ( showDendrogram ) - showDendrogram(); + showDendrogram( classification ); } private Pair< List< BranchSpotTree >, double[][] > getRootsAndDistanceMatrix() @@ -240,7 +238,7 @@ String getParameters() return joiner.toString(); } - private void showDendrogram() + private void showDendrogram( final Classification< BranchSpotTree > classification ) { String header = "Dendrogram of hierarchical clustering of lineages
" + getParameters() + ""; DendrogramView< BranchSpotTree > dendrogramView = new DendrogramView<>( classification, header, referenceModel, prefs ); @@ -261,7 +259,7 @@ private Classification< BranchSpotTree > classifyLineageTrees( final List< Branc return result; } - private List< Pair< String, Integer > > createTagsAndColors() + private List< Pair< String, Integer > > createTagsAndColors( final Classification< BranchSpotTree > classification ) { List< Pair< String, Integer > > tagsAndColors = new ArrayList<>(); From ed7a5276fe70058c7af8db84d2398aac1dc5a3d5 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Tue, 18 Jun 2024 14:20:52 +0200 Subject: [PATCH 27/81] Add time measurement to ProjectAccessor --- .../org/mastodon/mamut/classification/util/ProjectAccessor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/classification/util/ProjectAccessor.java b/src/main/java/org/mastodon/mamut/classification/util/ProjectAccessor.java index c2aee4d79..815471697 100644 --- a/src/main/java/org/mastodon/mamut/classification/util/ProjectAccessor.java +++ b/src/main/java/org/mastodon/mamut/classification/util/ProjectAccessor.java @@ -62,8 +62,10 @@ private List< ProjectModel > loadProjectModels() { try { + long start = System.currentTimeMillis(); ProjectModel projectModel = ProjectLoader.open( project.getAbsolutePath(), context, false, true ); + logger.debug( "Loaded project from file: {} in {} ms", project.getAbsolutePath(), System.currentTimeMillis() - start ); externalProjectModels.add( projectModel ); } catch ( IOException | SpimDataException e ) From a822d3092cd4e2c86c6b81109b552e1e060e2d22 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 16:16:51 +0200 Subject: [PATCH 28/81] Add a method to demo utils that wraps BDV data as model and create mamut project --- .../io/importer/labelimage/util/DemoUtils.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/org/mastodon/mamut/io/importer/labelimage/util/DemoUtils.java b/src/test/java/org/mastodon/mamut/io/importer/labelimage/util/DemoUtils.java index ada6af7bf..b78efe5c6 100644 --- a/src/test/java/org/mastodon/mamut/io/importer/labelimage/util/DemoUtils.java +++ b/src/test/java/org/mastodon/mamut/io/importer/labelimage/util/DemoUtils.java @@ -47,12 +47,15 @@ import org.mastodon.mamut.io.importer.labelimage.LabelImageUtils; import org.mastodon.mamut.io.importer.labelimage.math.CovarianceMatrix; import org.mastodon.mamut.io.importer.labelimage.math.MeansVector; +import org.mastodon.mamut.io.project.MamutProject; import org.mastodon.mamut.model.Model; import org.mastodon.mamut.views.bdv.MamutViewBdv; import org.mastodon.views.bdv.SharedBigDataViewerData; import org.scijava.Context; import javax.annotation.Nonnull; +import java.io.File; +import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -71,6 +74,16 @@ public static ProjectModel wrapAsAppModel( final Img< FloatType > image, final M return ProjectModel.create( context, model, sharedBigDataViewerData, null ); } + public static ProjectModel wrapAsAppModel( final Img< FloatType > image, final Model model, final Context context, final File file ) + throws IOException + { + final SharedBigDataViewerData sharedBigDataViewerData = asSharedBdvDataXyz( image ); + MamutProject mamutProject = new MamutProject( file ); + File datasetXmlFile = File.createTempFile( "test", ".xml" ); + mamutProject.setDatasetXmlFile( datasetXmlFile ); + return ProjectModel.create( context, model, sharedBigDataViewerData, mamutProject ); + } + public static SharedBigDataViewerData asSharedBdvDataXyz( final Img< FloatType > image1 ) { final ImagePlus image = From d9a8a4e18e4c8927b9adc09c5e896de4ae9f4543 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 16:15:41 +0200 Subject: [PATCH 29/81] Add a new Service MastodonProjectService * This service is capable of loading, giving access and closing Mastodon projects, when no longer needed. --- .../mamut/util/MastodonProjectService.java | 112 ++++++++++++++++++ .../mastodon/mamut/util/ProjectSession.java | 61 ++++++++++ .../util/MastodonProjectServiceTest.java | 53 +++++++++ 3 files changed, 226 insertions(+) create mode 100644 src/main/java/org/mastodon/mamut/util/MastodonProjectService.java create mode 100644 src/main/java/org/mastodon/mamut/util/ProjectSession.java create mode 100644 src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java diff --git a/src/main/java/org/mastodon/mamut/util/MastodonProjectService.java b/src/main/java/org/mastodon/mamut/util/MastodonProjectService.java new file mode 100644 index 000000000..5c4908ba6 --- /dev/null +++ b/src/main/java/org/mastodon/mamut/util/MastodonProjectService.java @@ -0,0 +1,112 @@ +package org.mastodon.mamut.util; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import mpicbg.spim.data.SpimDataException; +import org.mastodon.mamut.ProjectModel; +import org.mastodon.mamut.io.ProjectLoader; +import org.scijava.plugin.Plugin; +import org.scijava.service.AbstractService; +import org.scijava.service.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * An SciJava service that manages Mastodon projects. + *
+ * This service is capable of loading, giving access and closing Mastodon projects, when no longer needed. + *
+ * The motivation for this service is that loading a Mastodon project is a time and memory consuming operation. + * Thus, it is beneficial to keep the project models that have been opened in memory and share it among different parts of the application. + *
+ * The service will keep track of all project models that have been opened and the sessions that are currently using them. + * For this purpose, the service provides a method to create a new session for a project file and a method to release a session. + *
+ * When a session is released, the service will close the project model if no other session is using it. + */ +@Plugin( type = Service.class ) +public class MastodonProjectService extends AbstractService +{ + private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() ); + + private final BiMap< File, ProjectModel > projectModels; + + private final Map< ProjectModel, List< ProjectSession > > sessions; + + /** + * Creates a new Mastodon project service. + */ + public MastodonProjectService() + { + projectModels = HashBiMap.create(); + sessions = new HashMap<>(); + } + + /** + * Creates a new session for the project file. + *
+ * If the project model for the file has already been loaded, the existing project model is used. + *
+ * If the project model has not been loaded yet, it is loaded from the file. + * + * @param file the project file to create a session for. + * @return the project session. + * @throws SpimDataException if the project file could not be loaded. + * @throws IOException if the project file could not be loaded. + */ + public ProjectSession createSession( final File file ) throws SpimDataException, IOException + { + // deliver existing project model, if it exists + if ( projectModels.containsKey( file ) ) + { + ProjectModel projectModel = projectModels.get( file ); + ProjectSession projectSession = new ProjectSession( file, projectModel, this ); + sessions.get( projectModel ).add( projectSession ); + return projectSession; + } + + // load project model from file + long start = System.currentTimeMillis(); + ProjectModel projectModel = + ProjectLoader.open( file.getAbsolutePath(), getContext(), false, true ); + logger.debug( "Loaded project from file: {} in {} ms", file.getAbsolutePath(), System.currentTimeMillis() - start ); + ProjectSession projectSession = new ProjectSession( file, projectModel, this ); + projectModels.put( file, projectModel ); + List< ProjectSession > projectSessionList = new ArrayList<>(); + projectSessionList.add( projectSession ); + sessions.put( projectModel, projectSessionList ); + return projectSession; + } + + /** + * Releases the session. + *
+ * If the project model is not used by any other session, the project model is closed. + * @param projectSession the session to release. + */ + public void releaseSession( final ProjectSession projectSession ) + { + ProjectModel projectModel = projectSession.getProjectModel(); + List< ProjectSession > projectSessionList = sessions.get( projectModel ); + projectSessionList.remove( projectSession ); + if ( projectSessionList.isEmpty() ) + { + projectModel.close(); + projectModels.inverse().remove( projectModel ); + sessions.remove( projectModel ); + } + } + + public int openProjectModelsCount() + { + return sessions.size(); + } +} diff --git a/src/main/java/org/mastodon/mamut/util/ProjectSession.java b/src/main/java/org/mastodon/mamut/util/ProjectSession.java new file mode 100644 index 000000000..cb8af2ccd --- /dev/null +++ b/src/main/java/org/mastodon/mamut/util/ProjectSession.java @@ -0,0 +1,61 @@ +package org.mastodon.mamut.util; + +import org.mastodon.mamut.ProjectModel; + +import java.io.File; + +/** + * A session for a Mastodon project. + *

+ * This class represents a session for a Mastodon project. It holds the project model and the file the project was loaded from. + *

+ * The session is created by the {@link MastodonProjectService} and should be closed by the client when it is no longer needed. + */ +public class ProjectSession implements AutoCloseable +{ + private final MastodonProjectService service; + + private final File file; + + private final ProjectModel projectModel; + + /** + * Creates a new project session. + * @param file the file the project was loaded from. + * @param projectModel the project model. + * @param service the service that created this session. + */ + public ProjectSession( final File file, final ProjectModel projectModel, final MastodonProjectService service ) + { + this.file = file; + this.projectModel = projectModel; + this.service = service; + } + + /** + * Closes this session. + */ + @Override + public void close() + { + service.releaseSession( this ); + } + + /** + * Gets the project model. + * @return the project model. + */ + public ProjectModel getProjectModel() + { + return projectModel; + } + + /** + * Gets the file the project was loaded from. + * @return the file. + */ + public File getFile() + { + return file; + } +} diff --git a/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java b/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java new file mode 100644 index 000000000..578344f76 --- /dev/null +++ b/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java @@ -0,0 +1,53 @@ +package org.mastodon.mamut.util; + +import mpicbg.spim.data.SpimDataException; +import net.imglib2.img.Img; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.type.numeric.real.FloatType; +import org.junit.jupiter.api.Test; +import org.mastodon.mamut.ProjectModel; +import org.mastodon.mamut.io.ProjectSaver; +import org.mastodon.mamut.io.importer.labelimage.util.DemoUtils; +import org.mastodon.mamut.model.Model; +import org.scijava.Context; + +import java.io.File; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MastodonProjectServiceTest +{ + @Test + void test() throws IOException, SpimDataException + { + Model model = new Model(); + Img< FloatType > image = ArrayImgs.floats( 1, 1, 1 ); + File mastodonFile1 = File.createTempFile( "test1", ".mastodon" ); + File mastodonFile2 = File.createTempFile( "test2", ".mastodon" ); + try (Context context = new Context()) + { + ProjectModel appModel1 = DemoUtils.wrapAsAppModel( image, model, context, mastodonFile1 ); + ProjectModel appModel2 = DemoUtils.wrapAsAppModel( image, model, context, mastodonFile2 ); + + ProjectSaver.saveProject( mastodonFile1, appModel1 ); + ProjectSaver.saveProject( mastodonFile2, appModel2 ); + + MastodonProjectService service = new MastodonProjectService(); + service.setContext( context ); + ProjectSession projectSession1 = service.createSession( mastodonFile1 ); + assertEquals( 1, service.openProjectModelsCount() ); + ProjectSession projectSession2 = service.createSession( mastodonFile1 ); + assertEquals( 1, service.openProjectModelsCount() ); + ProjectSession projectSession3 = service.createSession( mastodonFile2 ); + assertEquals( 2, service.openProjectModelsCount() ); + projectSession1.close(); + assertEquals( 2, service.openProjectModelsCount() ); + projectSession2.close(); + assertEquals( 1, service.openProjectModelsCount() ); + projectSession3.close(); + assertEquals( 0, service.openProjectModelsCount() ); + assertEquals( mastodonFile1, projectSession1.getFile() ); + } + } +} From 301c30c78d945ccb71835309007d8f9ef33a7d14 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 16:23:44 +0200 Subject: [PATCH 30/81] Add a new class Notification that contains some shortcuts to notify users about some events --- .../mamut/classification/ui/Notification.java | 69 +++++++++++++++++++ .../classification/ui/NotificationTest.java | 14 ++++ 2 files changed, 83 insertions(+) create mode 100644 src/main/java/org/mastodon/mamut/classification/ui/Notification.java create mode 100644 src/test/java/org/mastodon/mamut/classification/ui/NotificationTest.java diff --git a/src/main/java/org/mastodon/mamut/classification/ui/Notification.java b/src/main/java/org/mastodon/mamut/classification/ui/Notification.java new file mode 100644 index 000000000..8e3874bce --- /dev/null +++ b/src/main/java/org/mastodon/mamut/classification/ui/Notification.java @@ -0,0 +1,69 @@ +package org.mastodon.mamut.classification.ui; + +import javax.swing.JDialog; +import javax.swing.JOptionPane; +import javax.swing.Timer; + +/** + * Some shortcut methods to show notifications to the user. + */ +public class Notification +{ + + private static final int DEFAULT_TIME = 2_000; + + private Notification() + { + // prevent instantiation + } + + /** + * Displays a success notification to the user. + *
+ * This method creates a success notification with a green checkmark and the provided message, and displays it to the user. + * The notification is displayed in a non-modal dialog, which means that the user can continue interacting with the application while the notification is displayed. + * The notification is automatically dismissed after a default time period (2 seconds). + * + * @param title The title of the notification dialog. + * @param message The message to display in the notification. + */ + public static void showSuccess( final String title, final String message ) + { + String decoratedMessage = "" + getColoredString( message, "green" ); + JOptionPane pane = new JOptionPane( getHtmlMessage( decoratedMessage ), JOptionPane.INFORMATION_MESSAGE ); + JDialog dialog = pane.createDialog( null, title ); + dialog.setModal( false ); + dialog.setVisible( true ); + new Timer( DEFAULT_TIME, ignore -> dialog.dispose() ).start(); + } + + /** + * Displays a warning notification to the user. The message is displayed in a non-modal dialog. It is show in orange color with a warning icon. + * @param title The title of the notification dialog. + * @param message The message to display in the notification. + */ + public static void showWarning( final String title, final String message ) + { + JOptionPane.showMessageDialog( null, getHtmlMessage( getColoredString( message, "#e5521a" ) ), title, JOptionPane.WARNING_MESSAGE ); + } + + /** + * Displays an error notification to the user. The message is displayed in a non-modal dialog. It is show in red color with an error icon. + * @param title The title of the notification dialog. + * @param message The message to display in the notification. + */ + public static void showError( final String title, final String message ) + { + JOptionPane.showMessageDialog( null, getHtmlMessage( getColoredString( message, "red" ) ), title, JOptionPane.ERROR_MESSAGE ); + } + + private static String getColoredString( String message, String color ) + { + return "" + message + ""; + } + + private static String getHtmlMessage( String message ) + { + return "" + message + ""; + } +} diff --git a/src/test/java/org/mastodon/mamut/classification/ui/NotificationTest.java b/src/test/java/org/mastodon/mamut/classification/ui/NotificationTest.java new file mode 100644 index 000000000..0b1f8b600 --- /dev/null +++ b/src/test/java/org/mastodon/mamut/classification/ui/NotificationTest.java @@ -0,0 +1,14 @@ +package org.mastodon.mamut.classification.ui; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class NotificationTest +{ + @Test + void testShowSuccess() + { + assertDoesNotThrow( () -> Notification.showSuccess( "Success", "This is a success message." ) ); + } +} From 8a4f2fc4f8babd781b26a0a7429901b9841b3e04 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 16:26:18 +0200 Subject: [PATCH 31/81] Remove input branch duration from the ClassifyLineagesCommand * This choice did only have one option thus, it is removed to tidy up the relatively full command --- .../mamut/classification/ui/ClassifyLineagesCommand.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index 18490272a..160ac5f2f 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -97,12 +97,8 @@ public class ClassifyLineagesCommand extends InteractiveCommand @Parameter( label = "Linkage strategy for
hierarchical clustering", initializer = "initClusteringMethodChoices", callback = "update" ) private String clusteringMethod = ClusteringMethod.AVERAGE_LINKAGE.getName(); - @SuppressWarnings("unused") - @Parameter(label = "Feature", choices = "Branch duration", callback = "update") - private String branchDuration; - @SuppressWarnings( "unused" ) - @Parameter( label = "List of projects
(Drag & Drop supported)", style = "files,extensions:mastodon", persist = false, callback = "update" ) + @Parameter( label = "List of projects
(Drag & Drop supported)", style = "files,extensions:mastodon", callback = "update" ) private File[] projects = new File[ 0 ]; @SuppressWarnings("unused") From e13d0ffcf1dbfac966d616f1c7e38f4b0457d069 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 16:28:39 +0200 Subject: [PATCH 32/81] Retrieve the MastodonProjectService from the context and init the ClassifyLineagesController with it --- .../mamut/classification/ClassifyLineagesPlugin.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java index f890afec3..64cad1a08 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java @@ -33,6 +33,7 @@ import org.mastodon.mamut.ProjectModel; import org.mastodon.mamut.classification.ui.ClassifyLineagesCommand; import org.mastodon.mamut.plugin.MamutPlugin; +import org.mastodon.mamut.util.MastodonProjectService; import org.mastodon.ui.keymap.KeyConfigContexts; import org.scijava.AbstractContextual; import org.scijava.command.CommandService; @@ -93,8 +94,9 @@ public void installGlobalActions( Actions actions ) private void classifyLineageTrees() { - ClassifyLineagesController controller = - new ClassifyLineagesController( projectModel, commandService.getContext().getService( PrefService.class ) ); + PrefService prefService = getContext().getService( PrefService.class ); + MastodonProjectService projectService = getContext().getService( MastodonProjectService.class ); + ClassifyLineagesController controller = new ClassifyLineagesController( projectModel, prefService, projectService ); commandService.run( ClassifyLineagesCommand.class, true, "controller", controller ); } From f91f3d8da270fd95eca4c6fb635e965ef33400ac Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 16:49:27 +0200 Subject: [PATCH 33/81] Replace ProjectAccessor by using MastodonProjectService * MastodonProjectService allows a re-using already opened project models without opening them again. This saves time, since opening projects is time consuming. --- .../ClassifyLineagesController.java | 154 +++++++++++------- .../ui/ClassifyLineagesCommand.java | 7 + .../classification/util/ProjectAccessor.java | 87 ---------- 3 files changed, 100 insertions(+), 148 deletions(-) delete mode 100644 src/main/java/org/mastodon/mamut/classification/util/ProjectAccessor.java diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 353b78108..697996d6f 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -28,6 +28,7 @@ */ package org.mastodon.mamut.classification; +import mpicbg.spim.data.SpimDataException; import org.apache.commons.lang3.tuple.Pair; import org.mastodon.collection.RefSet; import org.mastodon.graph.algorithm.traversal.DepthFirstIterator; @@ -38,7 +39,6 @@ import org.mastodon.mamut.classification.config.CropCriteria; import org.mastodon.mamut.classification.ui.DendrogramView; import org.mastodon.mamut.classification.util.ClassificationUtils; -import org.mastodon.mamut.classification.util.ProjectAccessor; import org.mastodon.mamut.model.Link; import org.mastodon.mamut.model.Model; import org.mastodon.mamut.model.ModelGraph; @@ -47,6 +47,8 @@ import org.mastodon.mamut.classification.treesimilarity.tree.BranchSpotTree; import org.mastodon.mamut.classification.treesimilarity.tree.TreeUtils; import org.mastodon.mamut.util.LineageTreeUtils; +import org.mastodon.mamut.util.MastodonProjectService; +import org.mastodon.mamut.util.ProjectSession; import org.mastodon.model.tag.TagSetStructure; import org.mastodon.util.TagSetUtils; import org.scijava.prefs.PrefService; @@ -54,13 +56,16 @@ import org.slf4j.LoggerFactory; import java.io.File; +import java.io.IOException; import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.StringJoiner; @@ -82,6 +87,8 @@ public class ClassifyLineagesController private final PrefService prefs; + private final MastodonProjectService projectService; + private SimilarityMeasure similarityMeasure = SimilarityMeasure.NORMALIZED_ZHANG_DIFFERENCE; private ClusteringMethod clusteringMethod = ClusteringMethod.AVERAGE_LINKAGE; @@ -96,7 +103,7 @@ public class ClassifyLineagesController private int minCellDivisions; - private final List< File > externalProjects; + private final Map< File, ProjectSession > externalProjects; private boolean showDendrogram; @@ -108,20 +115,23 @@ public class ClassifyLineagesController */ public ClassifyLineagesController( final ProjectModel referenceProjectModel ) { - this( referenceProjectModel, null ); + this( referenceProjectModel, null, null ); } /** * Create a new controller for classifying lineage trees. * @param referenceProjectModel the reference project model * @param prefs the preference service + * @param projectService the project service */ - public ClassifyLineagesController( final ProjectModel referenceProjectModel, final PrefService prefs ) + public ClassifyLineagesController( final ProjectModel referenceProjectModel, final PrefService prefs, + final MastodonProjectService projectService ) { this.referenceProjectModel = referenceProjectModel; this.referenceModel = referenceProjectModel.getModel(); this.prefs = prefs; - this.externalProjects = new ArrayList<>(); + this.projectService = projectService; + this.externalProjects = new HashMap<>(); } /** @@ -173,36 +183,26 @@ private Pair< List< BranchSpotTree >, double[][] > getRootsAndDistanceMatrix() return Pair.of( roots, distances ); } - try (ProjectAccessor projectAccessor = new ProjectAccessor( externalProjects, referenceProjectModel.getContext() )) - { - List< ProjectModel > externalProjectModels = projectAccessor.getProjectModels(); - List< String > commonRootNames = findCommonRootNames( externalProjectModels ); - if ( logger.isDebugEnabled() ) - { - logger.info( "Found {} common root names in {} projects.", commonRootNames.size(), externalProjectModels.size() + 1 ); - String names = commonRootNames.stream().map( Object::toString ).collect( Collectors.joining( "," ) ); - logger.debug( "Common root names are: {}", names ); - } - List< List< BranchSpotTree > > treeMatrix = new ArrayList<>(); + List< String > commonRootNames = findCommonRootNames(); + List< List< BranchSpotTree > > treeMatrix = new ArrayList<>(); - keepCommonRootsAndSort( roots, commonRootNames ); - treeMatrix.add( roots ); - for ( ProjectModel projectModel : externalProjectModels ) - { - List< BranchSpotTree > externalRoots = getRoots( projectModel ); - keepCommonRootsAndSort( externalRoots, commonRootNames ); - treeMatrix.add( externalRoots ); - } - return Pair.of( roots, ClassificationUtils.getAverageDistanceMatrix( treeMatrix, similarityMeasure ) ); + keepCommonRootsAndSort( roots, commonRootNames ); + treeMatrix.add( roots ); + for ( ProjectSession projectSession : externalProjects.values() ) + { + List< BranchSpotTree > externalRoots = getRoots( projectSession.getProjectModel() ); + keepCommonRootsAndSort( externalRoots, commonRootNames ); + treeMatrix.add( externalRoots ); } + return Pair.of( roots, ClassificationUtils.getAverageDistanceMatrix( treeMatrix, similarityMeasure ) ); } - private List< String > findCommonRootNames( final List< ProjectModel > externalProjectModels ) + private List< String > findCommonRootNames() { Set< String > commonRootNames = extractRootNamesFromProjectModel( referenceProjectModel ); - for ( ProjectModel projectModel : externalProjectModels ) + for ( ProjectSession projectSession : externalProjects.values() ) { - Set< String > rootNames = extractRootNamesFromProjectModel( projectModel ); + Set< String > rootNames = extractRootNamesFromProjectModel( projectSession.getProjectModel() ); commonRootNames.retainAll( rootNames ); } List< String > commonRootNamesList = new ArrayList<>( commonRootNames ); @@ -372,10 +372,42 @@ public void setVisualisationParams( final boolean showDendrogram ) public void setExternalProjects( final File[] projects ) { - externalProjects.clear(); - if ( projects == null ) + if ( projects == null || projects.length == 0 ) + { + for ( ProjectSession projectSession : externalProjects.values() ) + projectSession.close(); + externalProjects.clear(); return; - externalProjects.addAll( Arrays.asList( projects ) ); + } + + List< File > projectsList = Arrays.asList( projects ); + + // Remove files from the externalProjects map that are not in the projects array + for ( Map.Entry< File, ProjectSession > entry : externalProjects.entrySet() ) + { + File file = entry.getKey(); + if ( !projectsList.contains( file ) ) + { + ProjectSession projectSession = externalProjects.remove( file ); + projectSession.close(); + } + } + + // Add files from projects to the map if they are not already present + for ( File file : projects ) + { + if ( !externalProjects.containsKey( file ) ) + { + try + { + externalProjects.put( file, projectService.createSession( file ) ); + } + catch ( SpimDataException | IOException | RuntimeException e ) + { + logger.warn( "Could not read project from file {}. Error: {}", file.getAbsolutePath(), e.getMessage() ); + } + } + } } public List< String > getFeedback() @@ -388,11 +420,7 @@ public List< String > getFeedback() logger.debug( message ); } - int roots; - try (ProjectAccessor projectAccessor = new ProjectAccessor( externalProjects, referenceProjectModel.getContext() )) - { - roots = findCommonRootNames( projectAccessor.getProjectModels() ).size(); - } + int roots = findCommonRootNames().size(); if ( numberOfClasses > roots ) { String message = @@ -430,35 +458,39 @@ private List< String > checkNumberOfSpots() { List< String > feedback = new ArrayList<>(); Set< ProjectModel > allModels = new HashSet<>( Collections.singletonList( referenceProjectModel ) ); - try (ProjectAccessor projectAccessor = new ProjectAccessor( externalProjects, referenceProjectModel.getContext() )) + for ( ProjectSession projectSession : externalProjects.values() ) + allModels.add( projectSession.getProjectModel() ); + for ( ProjectModel projectModel : allModels ) { - allModels.addAll( projectAccessor.getProjectModels() ); - for ( ProjectModel projectModel : allModels ) + Model model = projectModel.getModel(); + String projectName = projectModel.getProjectName(); + try { - Model model = projectModel.getModel(); - String projectName = projectModel.getProjectName(); - try - { - LineageTreeUtils.getFirstTimepointWithNSpots( model, cropStart ); - } - catch ( NoSuchElementException e ) - { - String message = projectName + ", crop start: " + e.getMessage(); - feedback.add( message ); - logger.debug( message ); - } - try - { - LineageTreeUtils.getFirstTimepointWithNSpots( model, cropEnd ); - } - catch ( NoSuchElementException e ) - { - String message = projectName + ", crop end: " + e.getMessage(); - feedback.add( message ); - logger.debug( message ); - } + LineageTreeUtils.getFirstTimepointWithNSpots( model, cropStart ); + } + catch ( NoSuchElementException e ) + { + String message = projectName + ", crop start: " + e.getMessage(); + feedback.add( message ); + logger.debug( message ); + } + try + { + LineageTreeUtils.getFirstTimepointWithNSpots( model, cropEnd ); + } + catch ( NoSuchElementException e ) + { + String message = projectName + ", crop end: " + e.getMessage(); + feedback.add( message ); + logger.debug( message ); } } return feedback; } + + public void close() + { + for ( ProjectSession projectSession : externalProjects.values() ) + projectSession.close(); + } } diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index 160ac5f2f..b5114f0ae 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -175,6 +175,13 @@ private void createTagSet() } computeFeedback = "" + feedback + ""; } + + } + + @Override + public void cancel() + { + controller.close(); } @SuppressWarnings( "unused" ) diff --git a/src/main/java/org/mastodon/mamut/classification/util/ProjectAccessor.java b/src/main/java/org/mastodon/mamut/classification/util/ProjectAccessor.java deleted file mode 100644 index 815471697..000000000 --- a/src/main/java/org/mastodon/mamut/classification/util/ProjectAccessor.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.mastodon.mamut.classification.util; - -import mpicbg.spim.data.SpimDataException; -import org.mastodon.mamut.ProjectModel; -import org.mastodon.mamut.io.ProjectLoader; -import org.scijava.Context; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.lang.invoke.MethodHandles; -import java.util.ArrayList; -import java.util.List; - -/** - * This class can be used to manage a list of project files and their corresponding ProjectModel instances. - * When instantiated, it loads the ProjectModels from the given project file objects. - *
- * It implements the close method from the AutoCloseable interface, which is used to close all loaded ProjectModel instances when they are no longer needed. - */ -public class ProjectAccessor implements AutoCloseable -{ - private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() ); - - private final List< File > projectFiles; - - private final Context context; - - private final List< ProjectModel > projectModels; - - /** - * Loads the ProjectModel instances from the project files and stores the returned list internally. - * - * @param projectFiles A List of File objects representing the project files to load the ProjectModel instances from. - * @param context A Context object used for loading the ProjectModel instances. - */ - public ProjectAccessor( final List< File > projectFiles, final Context context ) - { - this.projectFiles = projectFiles; - this.context = context; - this.projectModels = loadProjectModels(); - } - - /** - * Retrieves the list of ProjectModel instances loaded from the project files. - *
- * This method returns the list of ProjectModel instances that were loaded when this ProjectAccessor instance was created. - * The returned list is a reference to the internal list, not a copy. Therefore, changes to the returned list will affect the internal list. - * - * @return A List of ProjectModel instances loaded from the project files. - */ - public List< ProjectModel > getProjectModels() - { - return projectModels; - } - - private List< ProjectModel > loadProjectModels() - { - List< ProjectModel > externalProjectModels = new ArrayList<>(); - for ( File project : projectFiles ) - { - try - { - long start = System.currentTimeMillis(); - ProjectModel projectModel = - ProjectLoader.open( project.getAbsolutePath(), context, false, true ); - logger.debug( "Loaded project from file: {} in {} ms", project.getAbsolutePath(), System.currentTimeMillis() - start ); - externalProjectModels.add( projectModel ); - } - catch ( IOException | SpimDataException e ) - { - logger.warn( "Could not load project from file: {} ", project.getAbsolutePath(), e ); - } - } - return externalProjectModels; - } - - @Override - public void close() - { - for ( ProjectModel projectModel : projectModels ) - { - projectModel.close(); - } - } -} From 89d0dbdc8402a8e3ab144f8bb4587c67bcf4f897 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 17:01:35 +0200 Subject: [PATCH 34/81] Add possibility to specify whether generated tag sets should also be added to external projects --- .../ClassifyLineagesController.java | 24 ++++++++++++++++++- .../ui/ClassifyLineagesCommand.java | 6 ++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 697996d6f..549009849 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -39,6 +39,7 @@ import org.mastodon.mamut.classification.config.CropCriteria; import org.mastodon.mamut.classification.ui.DendrogramView; import org.mastodon.mamut.classification.util.ClassificationUtils; +import org.mastodon.mamut.io.ProjectSaver; import org.mastodon.mamut.model.Link; import org.mastodon.mamut.model.Model; import org.mastodon.mamut.model.ModelGraph; @@ -107,6 +108,8 @@ public class ClassifyLineagesController private boolean showDendrogram; + private boolean addTagSetToExternalProjects; + private boolean running = false; /** @@ -170,6 +173,24 @@ private void runClassification() Classification< BranchSpotTree > classification = classifyLineageTrees( roots, distances ); List< Pair< String, Integer > > tagsAndColors = createTagsAndColors( classification ); applyClassification( classification, tagsAndColors, referenceModel ); + if ( addTagSetToExternalProjects ) + { + for ( ProjectSession projectSession : externalProjects.values() ) + { + ProjectModel projectModel = projectSession.getProjectModel(); + File file = projectSession.getFile(); + applyClassification( classification, tagsAndColors, projectModel.getModel() ); + try + { + ProjectSaver.saveProject( file, projectModel ); + } + catch ( IOException e ) + { + logger.warn( "Could not save tag set of project {} to file {}. Message: {}", projectModel.getProjectName(), + file.getAbsolutePath(), e.getMessage() ); + } + } + } if ( showDendrogram ) showDendrogram( classification ); } @@ -370,8 +391,9 @@ public void setVisualisationParams( final boolean showDendrogram ) this.showDendrogram = showDendrogram; } - public void setExternalProjects( final File[] projects ) + public void setExternalProjects( final File[] projects, final boolean addTagSetToExternalProjects ) { + this.addTagSetToExternalProjects = addTagSetToExternalProjects; if ( projects == null || projects.length == 0 ) { for ( ProjectSession projectSession : externalProjects.values() ) diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index b5114f0ae..2877b6ae7 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -101,6 +101,10 @@ public class ClassifyLineagesCommand extends InteractiveCommand @Parameter( label = "List of projects
(Drag & Drop supported)", style = "files,extensions:mastodon", callback = "update" ) private File[] projects = new File[ 0 ]; + @SuppressWarnings( "unused" ) + @Parameter( label = "Add tag sets
also to these projects", callback = "update" ) + private boolean addTagSetToExternalProjects = false; + @SuppressWarnings("unused") @Parameter( label = "Show dendrogram
of clustering", callback = "update" ) private boolean showDendrogram = true; @@ -139,7 +143,7 @@ private void updateParams() controller.setComputeParams( SimilarityMeasure.getByName( similarityMeasure ), ClusteringMethod.getByName( clusteringMethod ), numberOfClasses ); controller.setVisualisationParams( showDendrogram ); - controller.setExternalProjects( projects ); + controller.setExternalProjects( projects, addTagSetToExternalProjects ); } private void updateFeedback() From 748749f74c416c7f96989b0c2743ce9dadf1bc2f Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 17:04:08 +0200 Subject: [PATCH 35/81] Add detection of external projects that could not be opened to the UI --- .../ClassifyLineagesController.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 549009849..5c0277a03 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -106,6 +106,8 @@ public class ClassifyLineagesController private final Map< File, ProjectSession > externalProjects; + private final Map< File, String > failingExternalProjects; + private boolean showDendrogram; private boolean addTagSetToExternalProjects; @@ -135,6 +137,7 @@ public ClassifyLineagesController( final ProjectModel referenceProjectModel, fin this.prefs = prefs; this.projectService = projectService; this.externalProjects = new HashMap<>(); + this.failingExternalProjects = new HashMap<>(); } /** @@ -399,6 +402,7 @@ public void setExternalProjects( final File[] projects, final boolean addTagSetT for ( ProjectSession projectSession : externalProjects.values() ) projectSession.close(); externalProjects.clear(); + failingExternalProjects.clear(); return; } @@ -415,6 +419,14 @@ public void setExternalProjects( final File[] projects, final boolean addTagSetT } } + // Remove files from the failingExternalProjects map that are not in the projects array + for ( Map.Entry< File, String > entry : failingExternalProjects.entrySet() ) + { + File file = entry.getKey(); + if ( !projectsList.contains( file ) ) + failingExternalProjects.remove( file ); + } + // Add files from projects to the map if they are not already present for ( File file : projects ) { @@ -423,9 +435,12 @@ public void setExternalProjects( final File[] projects, final boolean addTagSetT try { externalProjects.put( file, projectService.createSession( file ) ); + failingExternalProjects.remove( file ); } catch ( SpimDataException | IOException | RuntimeException e ) { + failingExternalProjects.put( file, + "Could not read project from file " + file.getAbsolutePath() + ".
Error: " + e.getMessage() ); logger.warn( "Could not read project from file {}. Error: {}", file.getAbsolutePath(), e.getMessage() ); } } @@ -452,6 +467,7 @@ public List< String > getFeedback() } if ( cropCriterion.equals( CropCriteria.NUMBER_OF_SPOTS ) ) feedback.addAll( checkNumberOfSpots() ); + feedback.addAll( failingExternalProjects.values() ); return feedback; } From 3a25307b21f9d0fe013c03bbb2c89460e122ef69 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 17:08:21 +0200 Subject: [PATCH 36/81] Let method createTagSet return the name of the created tag set --- .../ClassifyLineagesController.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 5c0277a03..482cfbf64 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -143,10 +143,10 @@ public ClassifyLineagesController( final ProjectModel referenceProjectModel, fin /** * Create a new tag set based on the current settings of the controller. */ - public void createTagSet() + public String createTagSet() { if ( running ) - return; + return null; if ( !isValidParams() ) throw new IllegalArgumentException( "Invalid parameters settings." ); ReentrantReadWriteLock.WriteLock writeLock = referenceModel.getGraph().getLock().writeLock(); @@ -154,7 +154,7 @@ public void createTagSet() try { running = true; - runClassification(); + return runClassification(); } finally { @@ -168,14 +168,14 @@ public ProjectModel getProjectModel() return referenceProjectModel; } - private void runClassification() + private String runClassification() { Pair< List< BranchSpotTree >, double[][] > rootsAndDistances = getRootsAndDistanceMatrix(); List< BranchSpotTree > roots = rootsAndDistances.getLeft(); double[][] distances = rootsAndDistances.getRight(); Classification< BranchSpotTree > classification = classifyLineageTrees( roots, distances ); List< Pair< String, Integer > > tagsAndColors = createTagsAndColors( classification ); - applyClassification( classification, tagsAndColors, referenceModel ); + String createdTagSetName = applyClassification( classification, tagsAndColors, referenceModel ); if ( addTagSetToExternalProjects ) { for ( ProjectSession projectSession : externalProjects.values() ) @@ -196,6 +196,7 @@ private void runClassification() } if ( showDendrogram ) showDendrogram( classification ); + return createdTagSetName; } private Pair< List< BranchSpotTree >, double[][] > getRootsAndDistanceMatrix() @@ -297,11 +298,12 @@ private List< Pair< String, Integer > > createTagsAndColors( final Classificatio return tagsAndColors; } - private void applyClassification( final Classification< BranchSpotTree > classification, + private String applyClassification( final Classification< BranchSpotTree > classification, final List< Pair< String, Integer > > tagsAndColors, final Model model ) { + String tagSetName = getTagSetName(); Set< Classification.ObjectClassification< BranchSpotTree > > objectClassifications = classification.getObjectClassifications(); - TagSetStructure.TagSet tagSet = TagSetUtils.addNewTagSetToModel( model, getTagSetName(), tagsAndColors ); + TagSetStructure.TagSet tagSet = TagSetUtils.addNewTagSetToModel( model, tagSetName, tagsAndColors ); int i = 0; for ( Classification.ObjectClassification< BranchSpotTree > objectClassification : objectClassifications ) { @@ -323,6 +325,7 @@ private void applyClassification( final Classification< BranchSpotTree > classif } i++; } + return tagSetName; } private List< BranchSpotTree > getRoots() From 6235cdf9cce65d4b421655f2ef54595f1a3df31f Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 17:13:27 +0200 Subject: [PATCH 37/81] Only apply read lock to graph, when writing the tag set to it --- .../ClassifyLineagesController.java | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 482cfbf64..f32912930 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -149,8 +149,6 @@ public String createTagSet() return null; if ( !isValidParams() ) throw new IllegalArgumentException( "Invalid parameters settings." ); - ReentrantReadWriteLock.WriteLock writeLock = referenceModel.getGraph().getLock().writeLock(); - writeLock.lock(); try { running = true; @@ -158,7 +156,6 @@ public String createTagSet() } finally { - writeLock.unlock(); running = false; } } @@ -170,32 +167,41 @@ public ProjectModel getProjectModel() private String runClassification() { - Pair< List< BranchSpotTree >, double[][] > rootsAndDistances = getRootsAndDistanceMatrix(); - List< BranchSpotTree > roots = rootsAndDistances.getLeft(); - double[][] distances = rootsAndDistances.getRight(); - Classification< BranchSpotTree > classification = classifyLineageTrees( roots, distances ); - List< Pair< String, Integer > > tagsAndColors = createTagsAndColors( classification ); - String createdTagSetName = applyClassification( classification, tagsAndColors, referenceModel ); - if ( addTagSetToExternalProjects ) + ReentrantReadWriteLock.ReadLock lock = referenceModel.getGraph().getLock().readLock(); + lock.lock(); + String createdTagSetName; + try { - for ( ProjectSession projectSession : externalProjects.values() ) - { - ProjectModel projectModel = projectSession.getProjectModel(); - File file = projectSession.getFile(); - applyClassification( classification, tagsAndColors, projectModel.getModel() ); - try - { - ProjectSaver.saveProject( file, projectModel ); - } - catch ( IOException e ) + Pair< List< BranchSpotTree >, double[][] > rootsAndDistances = getRootsAndDistanceMatrix(); + List< BranchSpotTree > roots = rootsAndDistances.getLeft(); + double[][] distances = rootsAndDistances.getRight(); + Classification< BranchSpotTree > classification = classifyLineageTrees( roots, distances ); + List< Pair< String, Integer > > tagsAndColors = createTagsAndColors( classification ); + createdTagSetName = applyClassification( classification, tagsAndColors, referenceModel ); + if ( addTagSetToExternalProjects ) + for ( ProjectSession projectSession : externalProjects.values() ) { - logger.warn( "Could not save tag set of project {} to file {}. Message: {}", projectModel.getProjectName(), - file.getAbsolutePath(), e.getMessage() ); + ProjectModel projectModel = projectSession.getProjectModel(); + File file = projectSession.getFile(); + applyClassification( classification, tagsAndColors, projectModel.getModel() ); + try + { + ProjectSaver.saveProject( file, projectModel ); + } + catch ( IOException e ) + { + logger.warn( "Could not save tag set of project {} to file {}. Message: {}", projectModel.getProjectName(), + file.getAbsolutePath(), e.getMessage() ); + } } - } + + if ( showDendrogram ) + showDendrogram( classification ); + } + finally + { + lock.unlock(); } - if ( showDendrogram ) - showDendrogram( classification ); return createdTagSetName; } From 28383c445eb640f0045bdf95f325322e6b10fe4d Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 17:13:51 +0200 Subject: [PATCH 38/81] Add some debug message --- .../mamut/classification/ClassifyLineagesController.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index f32912930..c32c33130 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -238,6 +238,14 @@ private List< String > findCommonRootNames() } List< String > commonRootNamesList = new ArrayList<>( commonRootNames ); commonRootNamesList.sort( String::compareTo ); + + if ( logger.isDebugEnabled() ) + { + logger.info( "Found {} common root names in {} projects.", commonRootNames.size(), externalProjects.size() + 1 ); + String names = commonRootNames.stream().map( Object::toString ).collect( Collectors.joining( "," ) ); + logger.debug( "Common root names are: {}", names ); + } + return commonRootNamesList; } From 7611154b093b3fc258295a2960584fca3440d615 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 17:14:02 +0200 Subject: [PATCH 39/81] Change tag set name --- .../mamut/classification/ClassifyLineagesController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index c32c33130..33eb5c687 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -495,7 +495,8 @@ public boolean isValidParams() private String getTagSetName() { - return "Classification" + String prefix = externalProjects.isEmpty() ? "" : "Average "; + return prefix + "Classification" + " (" + cropCriterion.getNameShort() + ": " From 0475721d0525979fc2979c2c7c3954abb8643774 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 17:17:02 +0200 Subject: [PATCH 40/81] Change ClassifyLineagesCommand from DynamicCommand to Interactive command * This became necessary, since checking potential external projects for parameter validity requires to open them. In order to not introduce a memory leak, it is necessary to close them again, when the command is closed, which was not possible with the Interactive command --- .../ui/ClassifyLineagesCommand.java | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index 2877b6ae7..d57ac2133 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -34,7 +34,7 @@ import org.mastodon.mamut.classification.ClassifyLineagesController; import org.mastodon.mamut.classification.config.CropCriteria; import org.scijava.ItemVisibility; -import org.scijava.command.InteractiveCommand; +import org.scijava.command.DynamicCommand; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import org.scijava.widget.Button; @@ -45,10 +45,11 @@ import java.lang.invoke.MethodHandles; import java.util.Arrays; import java.util.List; +import java.util.StringJoiner; import java.util.stream.Collectors; -@Plugin( type = InteractiveCommand.class, visible = false, label = "Classification of Lineage Trees", initializer = "init" ) -public class ClassifyLineagesCommand extends InteractiveCommand +@Plugin( type = DynamicCommand.class, label = "Classification of Lineage Trees", initializer = "init" ) +public class ClassifyLineagesCommand extends DynamicCommand { private static final float WIDTH = 18.5f; @@ -109,6 +110,10 @@ public class ClassifyLineagesCommand extends InteractiveCommand @Parameter( label = "Show dendrogram
of clustering", callback = "update" ) private boolean showDendrogram = true; + @SuppressWarnings( "unused" ) + @Parameter( label = "Check validity of parameters", callback = "update" ) + private Button checkParameters; + @SuppressWarnings("unused") @Parameter(visibility = ItemVisibility.MESSAGE, required = false, persist = false, label = " ") private String paramFeedback; @@ -127,7 +132,9 @@ public class ClassifyLineagesCommand extends InteractiveCommand @Override public void run() { - // NB: not implemented. Update method is called via callback on each parameter change. + // NB: This method is called, when the user presses the "OK" button. + createTagSet(); + controller.close(); } @SuppressWarnings("unused") @@ -159,25 +166,25 @@ private void updateFeedback() @SuppressWarnings("unused") private void createTagSet() { - update(); + updateParams(); if ( controller.isValidParams() ) { - String feedback; - String color; try { - controller.createTagSet(); - feedback = "Classified lineage trees.

"; - feedback += "Tag set created."; - color = "green"; + String tagSetName = controller.createTagSet(); + Notification.showSuccess( "Classification successful", "Classified lineage trees.

New tag set created: " + tagSetName ); } catch ( IllegalArgumentException e ) { - feedback = e.getMessage(); - color = "red"; + Notification.showError( "Error during lineage classification", e.getMessage() ); logger.error( "Error during lineage classification: {}", e.getMessage() ); } - computeFeedback = "" + feedback + ""; + } + else + { + StringJoiner joiner = new StringJoiner( "

  • " ); + controller.getFeedback().forEach( joiner::add ); + Notification.showWarning( "Invalid parameters", "Please check the parameters.
    • " + joiner + "
    " ); } } From 12fefb022360833fe97c2dca30b843616600c41e Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 17:18:32 +0200 Subject: [PATCH 41/81] Remove button create Tag set from ClassifyLineagesCommand * The tag set is now created, when the command is closed with "OK" --- .../mamut/classification/ui/ClassifyLineagesCommand.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index d57ac2133..e1a9e2dc4 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -122,10 +122,6 @@ public class ClassifyLineagesCommand extends DynamicCommand @Parameter(visibility = ItemVisibility.MESSAGE, required = false, persist = false, label = " ") private String computeFeedback; - @SuppressWarnings("unused") - @Parameter( label = "Classify lineage trees", callback = "createTagSet" ) - private Button createTagSet; - /** * This method is executed whenever a parameter changes */ From 740c31521c253a377bb3d4fa9d88ad07c91228b5 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 17:18:53 +0200 Subject: [PATCH 42/81] Show parameter feedback as a list --- .../mamut/classification/ui/ClassifyLineagesCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index e1a9e2dc4..b450dbfdd 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -155,7 +155,7 @@ private void updateFeedback() if ( controller.isValidParams() ) paramFeedback += "Parameters are valid."; else - paramFeedback += "" + String.join( "

    ", controller.getFeedback() ); + paramFeedback += "

    • " + String.join( "
    • ", controller.getFeedback() ) + "
    "; paramFeedback += ""; } From c1ca424d064798a47352785eae588559d2139e38 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 17:58:15 +0200 Subject: [PATCH 43/81] Reduce complexity of setExternalProjects method by introducing some sub methods --- .../ClassifyLineagesController.java | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 33eb5c687..3f4fa309b 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -424,8 +424,16 @@ public void setExternalProjects( final File[] projects, final boolean addTagSetT } List< File > projectsList = Arrays.asList( projects ); + removeProjects( projectsList ); + cleanUpFailingProjects( projectsList ); + addProjects( projects ); + } - // Remove files from the externalProjects map that are not in the projects array + /** + * Remove files from the externalProjects map that are not in the projects list + */ + private void removeProjects( final List< File > projectsList ) + { for ( Map.Entry< File, ProjectSession > entry : externalProjects.entrySet() ) { File file = entry.getKey(); @@ -435,16 +443,26 @@ public void setExternalProjects( final File[] projects, final boolean addTagSetT projectSession.close(); } } + } - // Remove files from the failingExternalProjects map that are not in the projects array + /** + * Remove files from the failingExternalProjects map that are not in the projects list + */ + private void cleanUpFailingProjects( final List< File > projectsList ) + { for ( Map.Entry< File, String > entry : failingExternalProjects.entrySet() ) { File file = entry.getKey(); if ( !projectsList.contains( file ) ) failingExternalProjects.remove( file ); } + } - // Add files from projects to the map if they are not already present + /** + * Add files from projects to the map if they are not already present + */ + private void addProjects( final File[] projects ) + { for ( File file : projects ) { if ( !externalProjects.containsKey( file ) ) From f7e57dd81f87117b75ea875b709550174646c3b4 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 21 Jun 2024 18:00:44 +0200 Subject: [PATCH 44/81] Improve debug log message --- .../mamut/classification/util/ClassificationUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java b/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java index 2a9848a07..6fe506872 100644 --- a/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java +++ b/src/main/java/org/mastodon/mamut/classification/util/ClassificationUtils.java @@ -117,8 +117,8 @@ public static < T extends Tree< Double > > double[][] getDistanceMatrix( final L } ); stopWatch.stop(); logger.debug( "Computed all distances in {} s.", stopWatch.getTime() / 1000d ); - logger.debug( "Shape of similarity matrix: {}x{}={} entries.", distances.length, distances[ 0 ].length, - distances.length * distances[ 0 ].length ); + logger.debug( "Shape of similarity matrix: {}x{}={} entries.", distances.length, distances.length, + distances.length * distances.length ); return distances; } From c507874f586c928aeaaf0f7d6a889f16154b8193 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Mon, 24 Jun 2024 12:11:21 +0200 Subject: [PATCH 45/81] Create new method in DemoUtils to save an app model to a temp file --- .../io/importer/labelimage/util/DemoUtils.java | 12 ++++++++++++ .../mamut/util/MastodonProjectServiceTest.java | 14 ++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/test/java/org/mastodon/mamut/io/importer/labelimage/util/DemoUtils.java b/src/test/java/org/mastodon/mamut/io/importer/labelimage/util/DemoUtils.java index b78efe5c6..663bd0a5c 100644 --- a/src/test/java/org/mastodon/mamut/io/importer/labelimage/util/DemoUtils.java +++ b/src/test/java/org/mastodon/mamut/io/importer/labelimage/util/DemoUtils.java @@ -44,6 +44,7 @@ import net.imglib2.util.ValuePair; import net.imglib2.view.Views; import org.mastodon.mamut.ProjectModel; +import org.mastodon.mamut.io.ProjectSaver; import org.mastodon.mamut.io.importer.labelimage.LabelImageUtils; import org.mastodon.mamut.io.importer.labelimage.math.CovarianceMatrix; import org.mastodon.mamut.io.importer.labelimage.math.MeansVector; @@ -84,6 +85,17 @@ public static ProjectModel wrapAsAppModel( final Img< FloatType > image, final M return ProjectModel.create( context, model, sharedBigDataViewerData, mamutProject ); } + public static File saveAppModelToTempFile( final Img< FloatType > image, final Model model ) throws IOException + { + File file = File.createTempFile( "test", ".mastodon" ); + try (Context context = new Context()) + { + ProjectModel appModel1 = DemoUtils.wrapAsAppModel( image, model, context, file ); + ProjectSaver.saveProject( file, appModel1 ); + } + return file; + } + public static SharedBigDataViewerData asSharedBdvDataXyz( final Img< FloatType > image1 ) { final ImagePlus image = diff --git a/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java b/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java index 578344f76..0dad8343a 100644 --- a/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java +++ b/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java @@ -5,8 +5,6 @@ import net.imglib2.img.array.ArrayImgs; import net.imglib2.type.numeric.real.FloatType; import org.junit.jupiter.api.Test; -import org.mastodon.mamut.ProjectModel; -import org.mastodon.mamut.io.ProjectSaver; import org.mastodon.mamut.io.importer.labelimage.util.DemoUtils; import org.mastodon.mamut.model.Model; import org.scijava.Context; @@ -23,16 +21,10 @@ void test() throws IOException, SpimDataException { Model model = new Model(); Img< FloatType > image = ArrayImgs.floats( 1, 1, 1 ); - File mastodonFile1 = File.createTempFile( "test1", ".mastodon" ); - File mastodonFile2 = File.createTempFile( "test2", ".mastodon" ); + File mastodonFile1 = DemoUtils.saveAppModelToTempFile( image, model ); + File mastodonFile2 = DemoUtils.saveAppModelToTempFile( image, model ); try (Context context = new Context()) { - ProjectModel appModel1 = DemoUtils.wrapAsAppModel( image, model, context, mastodonFile1 ); - ProjectModel appModel2 = DemoUtils.wrapAsAppModel( image, model, context, mastodonFile2 ); - - ProjectSaver.saveProject( mastodonFile1, appModel1 ); - ProjectSaver.saveProject( mastodonFile2, appModel2 ); - MastodonProjectService service = new MastodonProjectService(); service.setContext( context ); ProjectSession projectSession1 = service.createSession( mastodonFile1 ); @@ -48,6 +40,8 @@ void test() throws IOException, SpimDataException projectSession3.close(); assertEquals( 0, service.openProjectModelsCount() ); assertEquals( mastodonFile1, projectSession1.getFile() ); + assertEquals( mastodonFile1, projectSession2.getFile() ); + assertEquals( mastodonFile2, projectSession3.getFile() ); } } } From 2fe6553479e3c50ecbbc387e2276fe56baaf89e3 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Mon, 24 Jun 2024 12:37:08 +0200 Subject: [PATCH 46/81] Set setVisualisationParams() to true to increase test coverage --- .../mamut/classification/ClassifyLineagesControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java index e79a9a3e6..8d25966cc 100644 --- a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java +++ b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java @@ -92,7 +92,7 @@ void testCreateTagSet() ClassifyLineagesController controller = new ClassifyLineagesController( projectModel ); controller.setInputParams( CropCriteria.TIMEPOINT, 0, 100, 1 ); controller.setComputeParams( SimilarityMeasure.NORMALIZED_ZHANG_DIFFERENCE, ClusteringMethod.AVERAGE_LINKAGE, 3 ); - controller.setVisualisationParams( false ); + controller.setVisualisationParams( true ); controller.createTagSet(); List< TagSetStructure.TagSet > tagSets = model.getTagSetModel().getTagSetStructure().getTagSets(); From 67802fe8696b216efce0fb2ee610a1ec85c0c023 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Mon, 24 Jun 2024 12:37:30 +0200 Subject: [PATCH 47/81] Add new unit test testCreateTagSetWithExternalProjects() --- .../ClassifyLineagesControllerTest.java | 72 ++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java index 8d25966cc..5a9ae097c 100644 --- a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java +++ b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java @@ -6,13 +6,13 @@ * %% * 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 @@ -42,14 +42,19 @@ import org.mastodon.mamut.classification.config.CropCriteria; import org.mastodon.mamut.classification.config.SimilarityMeasure; import org.mastodon.mamut.feature.branch.exampleGraph.ExampleGraph2; +import org.mastodon.mamut.io.importer.labelimage.util.DemoUtils; import org.mastodon.mamut.model.Model; import org.mastodon.mamut.model.ModelGraph; import org.mastodon.mamut.model.Spot; +import org.mastodon.mamut.util.MastodonProjectService; import org.mastodon.model.tag.TagSetStructure; import org.mastodon.util.TagSetUtils; import org.mastodon.views.bdv.SharedBigDataViewerData; import org.scijava.Context; +import org.scijava.prefs.PrefService; +import java.io.File; +import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -121,6 +126,69 @@ void testCreateTagSet() } } + @Test + void testCreateTagSetWithExternalProjects() throws IOException + { + final Model model = new Model(); + + try (Context context = new Context()) + { + final Img< FloatType > dummyImg = ArrayImgs.floats( 1, 1, 1 ); + final ImagePlus dummyImagePlus = + ImgToVirtualStack.wrap( new ImgPlus<>( dummyImg, "image", new AxisType[] { Axes.X, Axes.Y, Axes.Z } ) ); + SharedBigDataViewerData dummyBdv = Objects.requireNonNull( SharedBigDataViewerData.fromImagePlus( dummyImagePlus ) ); + ProjectModel projectModel = ProjectModel.create( context, model, dummyBdv, null ); + + final ModelGraph modelGraph = model.getGraph(); + + addLineageTree1( modelGraph ); + addLineageTree2( modelGraph ); + addLineageTree3( modelGraph ); + addLineageTree4( modelGraph ); + addLineageTree5( modelGraph ); + addEmptyTree( modelGraph ); + + File file1 = DemoUtils.saveAppModelToTempFile( dummyImg, model ); + File file2 = DemoUtils.saveAppModelToTempFile( dummyImg, model ); + File[] files = { file1, file2 }; + + String tagSetName = "Test Tag Set"; + TagSetUtils.addNewTagSetToModel( model, tagSetName, Collections.emptyList() ); + PrefService prefService = context.getService( PrefService.class ); + MastodonProjectService projectService = context.getService( MastodonProjectService.class ); + ClassifyLineagesController controller = new ClassifyLineagesController( projectModel, prefService, projectService ); + controller.setInputParams( CropCriteria.TIMEPOINT, 0, 100, 1 ); + controller.setComputeParams( SimilarityMeasure.NORMALIZED_ZHANG_DIFFERENCE, ClusteringMethod.AVERAGE_LINKAGE, 3 ); + controller.setVisualisationParams( false ); + controller.setExternalProjects( files, true ); + controller.createTagSet(); + + List< TagSetStructure.TagSet > tagSets = model.getTagSetModel().getTagSetStructure().getTagSets(); + TagSetStructure.TagSet tagSet1 = model.getTagSetModel().getTagSetStructure().getTagSets().get( 1 ); + List< TagSetStructure.Tag > tags = tagSet1.getTags(); + TagSetStructure.Tag tag0 = tags.get( 0 ); + TagSetStructure.Tag tag1 = tags.get( 1 ); + TagSetStructure.Tag tag2 = tags.get( 2 ); + + Collection< Spot > tag0Spots = model.getTagSetModel().getVertexTags().getTaggedWith( tag0 ); + Collection< Spot > tag1Spots = model.getTagSetModel().getVertexTags().getTaggedWith( tag1 ); + Collection< Spot > tag2Spots = model.getTagSetModel().getVertexTags().getTaggedWith( tag2 ); + + Set< String > expectedClassNames = new HashSet<>( Arrays.asList( "Class 1", "Class 2", "Class 3" ) ); + Set< String > actualClassNames = new HashSet<>( Arrays.asList( tag0.label(), tag1.label(), tag2.label() ) ); + + Set< Integer > expectedClassCounts = new HashSet<>( Arrays.asList( 9, 12, 14 ) ); + Set< Integer > actualClassCounts = new HashSet<>( Arrays.asList( tag0Spots.size(), tag1Spots.size(), tag2Spots.size() ) ); + + assertEquals( "Average Classification (time: 0-100, classes: 3, min. div: 1) ", tagSet1.getName() ); + assertTrue( controller.isValidParams() ); + assertEquals( 2, tagSets.size() ); + assertEquals( 3, tags.size() ); + assertEquals( expectedClassNames, actualClassNames ); + assertEquals( expectedClassCounts, actualClassCounts ); + } + } + @Test void testGetFeedback() { From aa9e1676bc41a78e999d9d4cdf691cacf2a89978 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Mon, 24 Jun 2024 13:17:22 +0200 Subject: [PATCH 48/81] Remove unrequired method getProjectModel from ClassifyLineagesController --- .../mamut/classification/ClassifyLineagesController.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 3f4fa309b..61942cddb 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -160,11 +160,6 @@ public String createTagSet() } } - public ProjectModel getProjectModel() - { - return referenceProjectModel; - } - private String runClassification() { ReentrantReadWriteLock.ReadLock lock = referenceModel.getGraph().getLock().readLock(); From 4893ddcec2d3f9d5658fa097d464b30ac18dbb72 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Mon, 24 Jun 2024 15:03:05 +0200 Subject: [PATCH 49/81] Add new unit test for ClassifyLineagesController setExternalProjects() method --- .../ClassifyLineagesControllerTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java index 5a9ae097c..0db2474e3 100644 --- a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java +++ b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java @@ -189,6 +189,44 @@ void testCreateTagSetWithExternalProjects() throws IOException } } + @Test + void testSetExternalProjects() throws IOException + { + Model model = new Model(); + try (Context context = new Context()) + { + final Img< FloatType > dummyImg = ArrayImgs.floats( 1, 1, 1 ); + final ImagePlus dummyImagePlus = + ImgToVirtualStack.wrap( new ImgPlus<>( dummyImg, "image", new AxisType[] { Axes.X, Axes.Y, Axes.Z } ) ); + SharedBigDataViewerData dummyBdv = Objects.requireNonNull( SharedBigDataViewerData.fromImagePlus( dummyImagePlus ) ); + ProjectModel projectModel = ProjectModel.create( context, model, dummyBdv, null ); + + File file1 = DemoUtils.saveAppModelToTempFile( dummyImg, model ); + File file2 = DemoUtils.saveAppModelToTempFile( dummyImg, model ); + File file3 = File.createTempFile( "test", ".mastodon" ); + File[] files = { file1, file2 }; + + PrefService prefService = context.getService( PrefService.class ); + MastodonProjectService projectService = context.getService( MastodonProjectService.class ); + ClassifyLineagesController controller = new ClassifyLineagesController( projectModel, prefService, projectService ); + controller.setInputParams( CropCriteria.TIMEPOINT, 0, 100, 1 ); + controller.setComputeParams( SimilarityMeasure.NORMALIZED_ZHANG_DIFFERENCE, ClusteringMethod.AVERAGE_LINKAGE, 0 ); + controller.setExternalProjects( files, false ); + assertEquals( 0, controller.getFeedback().size() ); + controller.setExternalProjects( null, false ); + assertEquals( 0, controller.getFeedback().size() ); + files = new File[] { file1, file3 }; + controller.setExternalProjects( files, false ); + assertEquals( 1, controller.getFeedback().size() ); + File[] files2 = { file1, file2 }; + controller.setExternalProjects( files2, false ); + assertEquals( 0, controller.getFeedback().size() ); + File[] files3 = { file1 }; + controller.setExternalProjects( files3, false ); + assertEquals( 0, controller.getFeedback().size() ); + } + } + @Test void testGetFeedback() { From 392c69de62d5e4e81e0549ad8d7acce025eb3831 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Mon, 24 Jun 2024 15:10:00 +0200 Subject: [PATCH 50/81] Change debug log message --- .../mamut/classification/ClassifyLineagesController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 61942cddb..c0c88b764 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -317,7 +317,7 @@ private String applyClassification( final Classification< BranchSpotTree > class for ( Classification.ObjectClassification< BranchSpotTree > objectClassification : objectClassifications ) { Set< BranchSpotTree > trees = objectClassification.getObjects(); - logger.info( "Class {} has {} trees", i, trees.size() ); + logger.debug( "Applying tag set for class {}, which has {} trees", i, trees.size() ); TagSetStructure.Tag tag = tagSet.getTags().get( i ); for ( BranchSpotTree tree : trees ) { From ad8f8ac8e740dd9a751a25a3d4cfc22df87a187b Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Mon, 24 Jun 2024 15:30:00 +0200 Subject: [PATCH 51/81] Increase xmx for build job to 3g --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c84ee1169..9bea767be 100644 --- a/pom.xml +++ b/pom.xml @@ -216,7 +216,7 @@ maven-surefire-plugin 3.1.0 - -Xmx2g + -Xmx3g From 352ffb89210aaf2a02ba86b6dd5536dc2e35ea31 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Mon, 24 Jun 2024 16:48:14 +0200 Subject: [PATCH 52/81] Avoid concurrent modification exception when removing entries from external projects --- .../mamut/classification/ClassifyLineagesController.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index c0c88b764..53894b63a 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -65,6 +65,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -429,13 +430,16 @@ public void setExternalProjects( final File[] projects, final boolean addTagSetT */ private void removeProjects( final List< File > projectsList ) { - for ( Map.Entry< File, ProjectSession > entry : externalProjects.entrySet() ) + Iterator< Map.Entry< File, ProjectSession > > iterator = externalProjects.entrySet().iterator(); + while ( iterator.hasNext() ) { + Map.Entry< File, ProjectSession > entry = iterator.next(); File file = entry.getKey(); if ( !projectsList.contains( file ) ) { - ProjectSession projectSession = externalProjects.remove( file ); + ProjectSession projectSession = entry.getValue(); projectSession.close(); + iterator.remove(); } } } From c9e0319fed13e5a10d9a46586b9fe05aab57919e Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 27 Jun 2024 15:10:23 +0200 Subject: [PATCH 53/81] Add start end end timepoints to BranchSpotTree class * Add getters as well --- .../treesimilarity/tree/BranchSpotTree.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/classification/treesimilarity/tree/BranchSpotTree.java b/src/main/java/org/mastodon/mamut/classification/treesimilarity/tree/BranchSpotTree.java index 280ce33fd..89daff1f9 100644 --- a/src/main/java/org/mastodon/mamut/classification/treesimilarity/tree/BranchSpotTree.java +++ b/src/main/java/org/mastodon/mamut/classification/treesimilarity/tree/BranchSpotTree.java @@ -53,6 +53,10 @@ public class BranchSpotTree implements Tree< Double > private final Double attribute; + private final int startTimepoint; + + private final int endTimepoint; + BranchSpotTree( final BranchSpot branchSpot, final int startTimepoint, final int endTimepoint ) { this( branchSpot, startTimepoint, endTimepoint, null ); @@ -75,6 +79,8 @@ public BranchSpotTree( final BranchSpot branchSpot, final int startTimepoint, fi this.children = new ArrayList<>(); this.labelSupplier = new LabelSupplier( model ); this.attribute = ( double ) BranchSpotFeatureUtils.branchDuration( branchSpot, startTimepoint, endTimepoint ); + this.startTimepoint = startTimepoint; + this.endTimepoint = endTimepoint; for ( BranchLink branchLink : branchSpot.outgoingEdges() ) { BranchSpot child = branchLink.getTarget(); @@ -102,6 +108,16 @@ public BranchSpot getBranchSpot() return branchSpot; } + public int getStartTimepoint() + { + return startTimepoint; + } + + public int getEndTimepoint() + { + return endTimepoint; + } + public void updateLabeling( final boolean includeName, final boolean includeTag, final TagSetStructure.TagSet tagSet ) { labelSupplier.setParams( includeName, includeTag, tagSet ); From 3f995a0c1611229d08475ad3fd35cc396d0c5f00 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 27 Jun 2024 15:38:04 +0200 Subject: [PATCH 54/81] Remove unused initializer from ClassifyLineagesCommand --- .../mamut/classification/ui/ClassifyLineagesCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index b450dbfdd..6fd48bce0 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -48,7 +48,7 @@ import java.util.StringJoiner; import java.util.stream.Collectors; -@Plugin( type = DynamicCommand.class, label = "Classification of Lineage Trees", initializer = "init" ) +@Plugin( type = DynamicCommand.class, label = "Classification of Lineage Trees" ) public class ClassifyLineagesCommand extends DynamicCommand { From 6c289960c5a6ee21e080f6046abb088000d8dec9 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 27 Jun 2024 15:44:59 +0200 Subject: [PATCH 55/81] Use List instead of Set to store objectClassifications in the Classification class * This change is motivated by the need that objectClassifications have a predictable order --- .../ClassifyLineagesController.java | 20 +++++++++---------- .../ui/CustomizedClusterComponent.java | 10 +++++----- .../classification/util/Classification.java | 10 +++++----- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 53894b63a..f01231b57 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -201,13 +201,13 @@ private String runClassification() return createdTagSetName; } - private Pair< List< BranchSpotTree >, double[][] > getRootsAndDistanceMatrix() + private Pair< List< List< BranchSpotTree > >, double[][] > getRootsAndDistanceMatrix() { List< BranchSpotTree > roots = getRoots(); if ( externalProjects.isEmpty() ) { double[][] distances = ClassificationUtils.getDistanceMatrix( roots, similarityMeasure ); - return Pair.of( roots, distances ); + return Pair.of( Collections.singletonList( roots ), distances ); } List< String > commonRootNames = findCommonRootNames(); @@ -221,7 +221,7 @@ private Pair< List< BranchSpotTree >, double[][] > getRootsAndDistanceMatrix() keepCommonRootsAndSort( externalRoots, commonRootNames ); treeMatrix.add( externalRoots ); } - return Pair.of( roots, ClassificationUtils.getAverageDistanceMatrix( treeMatrix, similarityMeasure ) ); + return Pair.of( treeMatrix, ClassificationUtils.getAverageDistanceMatrix( treeMatrix, similarityMeasure ) ); } private List< String > findCommonRootNames() @@ -298,12 +298,11 @@ private List< Pair< String, Integer > > createTagsAndColors( final Classificatio { List< Pair< String, Integer > > tagsAndColors = new ArrayList<>(); - Set< Classification.ObjectClassification< BranchSpotTree > > objectClassifications = classification.getObjectClassifications(); - int i = 0; - for ( Classification.ObjectClassification< BranchSpotTree > objectClassification : objectClassifications ) + List< Classification.ObjectClassification< BranchSpotTree > > objectClassifications = classification.getObjectClassifications(); + for ( int i = 0; i < objectClassifications.size(); i++ ) { + Classification.ObjectClassification< BranchSpotTree > objectClassification = objectClassifications.get( i ); tagsAndColors.add( Pair.of( "Class " + ( i + 1 ), objectClassification.getColor() ) ); - i++; } return tagsAndColors; } @@ -312,11 +311,11 @@ private String applyClassification( final Classification< BranchSpotTree > class final List< Pair< String, Integer > > tagsAndColors, final Model model ) { String tagSetName = getTagSetName(); - Set< Classification.ObjectClassification< BranchSpotTree > > objectClassifications = classification.getObjectClassifications(); + List< Classification.ObjectClassification< BranchSpotTree > > objectClassifications = classification.getObjectClassifications(); TagSetStructure.TagSet tagSet = TagSetUtils.addNewTagSetToModel( model, tagSetName, tagsAndColors ); - int i = 0; - for ( Classification.ObjectClassification< BranchSpotTree > objectClassification : objectClassifications ) + for ( int i = 0; i < objectClassifications.size(); i++ ) { + Classification.ObjectClassification< BranchSpotTree > objectClassification = objectClassifications.get( i ); Set< BranchSpotTree > trees = objectClassification.getObjects(); logger.debug( "Applying tag set for class {}, which has {} trees", i, trees.size() ); TagSetStructure.Tag tag = tagSet.getTags().get( i ); @@ -333,7 +332,6 @@ private String applyClassification( final Classification< BranchSpotTree > class TagSetUtils.tagSpotAndIncomingEdges( model, spot, tagSet, tag ); } ); } - i++; } return tagSetName; } diff --git a/src/main/java/org/mastodon/mamut/classification/ui/CustomizedClusterComponent.java b/src/main/java/org/mastodon/mamut/classification/ui/CustomizedClusterComponent.java index 8a202c429..cc440f4d6 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/CustomizedClusterComponent.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/CustomizedClusterComponent.java @@ -35,7 +35,7 @@ import java.awt.Color; import java.awt.Graphics2D; -import java.util.Set; +import java.util.List; /** * This class extends the class {@link ClusterComponent} from the hierarchical clustering library. @@ -61,7 +61,7 @@ public class CustomizedClusterComponent extends ClusterComponent private static final Color DEFAULT_COLOR = Color.BLACK; public < T > CustomizedClusterComponent( - final Cluster cluster, final Set< Classification.ObjectClassification< T > > objectClassifications + final Cluster cluster, final List< Classification.ObjectClassification< T > > objectClassifications ) { this( cluster, cluster.isLeaf(), new VCoord( 0, 1d / 2d ), 1d, DEFAULT_COLOR, objectClassifications ); @@ -88,7 +88,7 @@ public < T > CustomizedClusterComponent( */ private < T > CustomizedClusterComponent( final Cluster cluster, final boolean printName, final VCoord splitPoint, final double clusterHeight, final Color color, - final Set< Classification.ObjectClassification< T > > objectClassifications + final List< Classification.ObjectClassification< T > > objectClassifications ) { super( cluster, printName, splitPoint ); @@ -101,7 +101,7 @@ private < T > CustomizedClusterComponent( } private static < T > Color getClusterColor( - final Cluster cluster, final Set< Classification.ObjectClassification< T > > objectClassifications + final Cluster cluster, final List< Classification.ObjectClassification< T > > objectClassifications ) { return objectClassifications.stream().filter( objectClassification -> objectClassification.getCluster().equals( cluster ) ) @@ -110,7 +110,7 @@ private static < T > Color getClusterColor( private < T > void init( final Cluster cluster, final VCoord splitPoint, final double clusterHeight, - final Set< Classification.ObjectClassification< T > > objectClassifications + final List< Classification.ObjectClassification< T > > objectClassifications ) { double leafHeight = clusterHeight / cluster.countLeafs(); diff --git a/src/main/java/org/mastodon/mamut/classification/util/Classification.java b/src/main/java/org/mastodon/mamut/classification/util/Classification.java index 80b435b39..2ce4a149a 100644 --- a/src/main/java/org/mastodon/mamut/classification/util/Classification.java +++ b/src/main/java/org/mastodon/mamut/classification/util/Classification.java @@ -31,7 +31,7 @@ import com.apporiented.algorithm.clustering.Cluster; import org.apache.commons.lang3.tuple.Pair; -import java.util.HashSet; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @@ -57,7 +57,7 @@ */ public class Classification< T > { - private final Set< ObjectClassification< T > > objectClassifications; + private final List< ObjectClassification< T > > objectClassifications; private final Cluster rootCluster; @@ -102,7 +102,7 @@ public Classification( final List< Pair< Set< T >, Cluster > > classifiedObjects final double median, final Map< Cluster, T > clusterNodesToObjects ) { - this.objectClassifications = new HashSet<>(); + this.objectClassifications = new ArrayList<>(); List< Integer > glasbeyColors = ClassificationUtils.getGlasbeyColors( classifiedObjects.size() ); int count = 0; for ( int i = 0; i < classifiedObjects.size(); i++ ) @@ -123,7 +123,7 @@ public Classification( final List< Pair< Set< T >, Cluster > > classifiedObjects } /** - * Returns a {@link Set} of {@link ObjectClassification} objects, where each objects contain: + * Returns a {@link List} of {@link ObjectClassification} objects, where each objects contain: *
      *
    • a {@link Cluster} object, which represents the classified objects in the dendrogram
    • *
    • a {@link Set} of objects, which are classified into the same class
    • @@ -131,7 +131,7 @@ public Classification( final List< Pair< Set< T >, Cluster > > classifiedObjects *
    * @return a {@link Set} of {@link ObjectClassification} objects */ - public Set< ObjectClassification< T > > getObjectClassifications() + public List< ObjectClassification< T > > getObjectClassifications() { return objectClassifications; } From 99ecee1c4af07b4693b099b32b7251a118c491a4 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 27 Jun 2024 15:47:12 +0200 Subject: [PATCH 56/81] Correctly apply classification to external projects --- .../ClassifyLineagesController.java | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index f01231b57..bcf2fe599 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -72,6 +72,7 @@ import java.util.Set; import java.util.StringJoiner; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -168,29 +169,38 @@ private String runClassification() String createdTagSetName; try { - Pair< List< BranchSpotTree >, double[][] > rootsAndDistances = getRootsAndDistanceMatrix(); - List< BranchSpotTree > roots = rootsAndDistances.getLeft(); + Pair< List< List< BranchSpotTree > >, double[][] > rootsAndDistances = getRootsAndDistanceMatrix(); + List< List< BranchSpotTree > > roots = rootsAndDistances.getLeft(); double[][] distances = rootsAndDistances.getRight(); - Classification< BranchSpotTree > classification = classifyLineageTrees( roots, distances ); + Classification< BranchSpotTree > classification = classifyLineageTrees( roots.get( 0 ), distances ); List< Pair< String, Integer > > tagsAndColors = createTagsAndColors( classification ); - createdTagSetName = applyClassification( classification, tagsAndColors, referenceModel ); - if ( addTagSetToExternalProjects ) - for ( ProjectSession projectSession : externalProjects.values() ) + Function< BranchSpotTree, BranchSpot > branchSpotProvider = BranchSpotTree::getBranchSpot; + createdTagSetName = applyClassification( classification, tagsAndColors, referenceModel, branchSpotProvider ); + if ( addTagSetToExternalProjects && roots.size() > 1 ) + { + for ( int i = 1; i < roots.size(); i++ ) { - ProjectModel projectModel = projectSession.getProjectModel(); - File file = projectSession.getFile(); - applyClassification( classification, tagsAndColors, projectModel.getModel() ); - try - { - ProjectSaver.saveProject( file, projectModel ); - } - catch ( IOException e ) + classification = classifyLineageTrees( roots.get( i ), distances ); + for ( ProjectSession projectSession : externalProjects.values() ) { - logger.warn( "Could not save tag set of project {} to file {}. Message: {}", projectModel.getProjectName(), - file.getAbsolutePath(), e.getMessage() ); + ProjectModel projectModel = projectSession.getProjectModel(); + File file = projectSession.getFile(); + branchSpotProvider = branchSpotTree -> projectModel.getModel().getBranchGraph().vertices().stream() + .filter( ( branchSpot -> branchSpot.getLabel().equals( branchSpotTree.getName() ) ) ) + .findFirst().orElse( null ); + applyClassification( classification, tagsAndColors, projectModel.getModel(), branchSpotProvider ); + try + { + ProjectSaver.saveProject( file, projectModel ); + } + catch ( IOException e ) + { + logger.warn( "Could not save tag set of project {} to file {}. Message: {}", projectModel.getProjectName(), + file.getAbsolutePath(), e.getMessage() ); + } } } - + } if ( showDendrogram ) showDendrogram( classification ); } @@ -308,7 +318,8 @@ private List< Pair< String, Integer > > createTagsAndColors( final Classificatio } private String applyClassification( final Classification< BranchSpotTree > classification, - final List< Pair< String, Integer > > tagsAndColors, final Model model ) + final List< Pair< String, Integer > > tagsAndColors, final Model model, + final Function< BranchSpotTree, BranchSpot > branchSpotProvider ) { String tagSetName = getTagSetName(); List< Classification.ObjectClassification< BranchSpotTree > > objectClassifications = classification.getObjectClassifications(); @@ -321,13 +332,14 @@ private String applyClassification( final Classification< BranchSpotTree > class TagSetStructure.Tag tag = tagSet.getTags().get( i ); for ( BranchSpotTree tree : trees ) { - Spot rootSpot = model.getBranchGraph().getFirstLinkedVertex( tree.getBranchSpot(), model.getGraph().vertexRef() ); + BranchSpot rootBranchSpot = branchSpotProvider.apply( tree ); + Spot rootSpot = model.getBranchGraph().getFirstLinkedVertex( rootBranchSpot, model.getGraph().vertexRef() ); ModelGraph modelGraph = model.getGraph(); DepthFirstIterator< Spot, Link > iterator = new DepthFirstIterator<>( rootSpot, modelGraph ); iterator.forEachRemaining( spot -> { - if ( spot.getTimepoint() < cropStart ) + if ( spot.getTimepoint() < tree.getStartTimepoint() ) return; - if ( spot.getTimepoint() > cropEnd ) + if ( spot.getTimepoint() > tree.getEndTimepoint() ) return; TagSetUtils.tagSpotAndIncomingEdges( model, spot, tagSet, tag ); } ); From cdb73a56144f6ba67ce5ee142add73060650a8c7 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 27 Jun 2024 15:48:19 +0200 Subject: [PATCH 57/81] Remove redundant handling for the case of resetting the external projects --- .../classification/ClassifyLineagesController.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index bcf2fe599..723b7e19b 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -420,16 +420,7 @@ public void setVisualisationParams( final boolean showDendrogram ) public void setExternalProjects( final File[] projects, final boolean addTagSetToExternalProjects ) { this.addTagSetToExternalProjects = addTagSetToExternalProjects; - if ( projects == null || projects.length == 0 ) - { - for ( ProjectSession projectSession : externalProjects.values() ) - projectSession.close(); - externalProjects.clear(); - failingExternalProjects.clear(); - return; - } - - List< File > projectsList = Arrays.asList( projects ); + List< File > projectsList = projects == null ? Collections.emptyList() : Arrays.asList( projects ); removeProjects( projectsList ); cleanUpFailingProjects( projectsList ); addProjects( projects ); @@ -472,6 +463,8 @@ private void cleanUpFailingProjects( final List< File > projectsList ) */ private void addProjects( final File[] projects ) { + if ( projects == null ) + return; for ( File file : projects ) { if ( !externalProjects.containsKey( file ) ) From edf25be03c360770da7050ba7446273a661e238c Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 27 Jun 2024 18:13:14 +0200 Subject: [PATCH 58/81] Make search for root branch spots in project model consistent with branch spot tree definition * first label of branch spot is used for both --- .../mamut/classification/ClassifyLineagesController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 723b7e19b..ae92cffe2 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -186,7 +186,7 @@ private String runClassification() ProjectModel projectModel = projectSession.getProjectModel(); File file = projectSession.getFile(); branchSpotProvider = branchSpotTree -> projectModel.getModel().getBranchGraph().vertices().stream() - .filter( ( branchSpot -> branchSpot.getLabel().equals( branchSpotTree.getName() ) ) ) + .filter( ( branchSpot -> branchSpot.getFirstLabel().equals( branchSpotTree.getName() ) ) ) .findFirst().orElse( null ); applyClassification( classification, tagsAndColors, projectModel.getModel(), branchSpotProvider ); try @@ -334,6 +334,8 @@ private String applyClassification( final Classification< BranchSpotTree > class { BranchSpot rootBranchSpot = branchSpotProvider.apply( tree ); Spot rootSpot = model.getBranchGraph().getFirstLinkedVertex( rootBranchSpot, model.getGraph().vertexRef() ); + if ( rootSpot == null ) + continue; ModelGraph modelGraph = model.getGraph(); DepthFirstIterator< Spot, Link > iterator = new DepthFirstIterator<>( rootSpot, modelGraph ); iterator.forEachRemaining( spot -> { From ea29aadfcd46f2a5b73f48d03d6a9e76cbbd367f Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 27 Jun 2024 18:14:49 +0200 Subject: [PATCH 59/81] Extend unit test for classification with external projects by checking, if the tag set has been written to the external project as well --- .../ClassifyLineagesControllerTest.java | 280 +++++++++++++++--- 1 file changed, 236 insertions(+), 44 deletions(-) diff --git a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java index 0db2474e3..aa234a8e7 100644 --- a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java +++ b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java @@ -29,6 +29,7 @@ package org.mastodon.mamut.classification; import ij.ImagePlus; +import mpicbg.spim.data.SpimDataException; import net.imagej.ImgPlus; import net.imagej.axis.Axes; import net.imagej.axis.AxisType; @@ -42,6 +43,7 @@ import org.mastodon.mamut.classification.config.CropCriteria; import org.mastodon.mamut.classification.config.SimilarityMeasure; import org.mastodon.mamut.feature.branch.exampleGraph.ExampleGraph2; +import org.mastodon.mamut.io.ProjectLoader; import org.mastodon.mamut.io.importer.labelimage.util.DemoUtils; import org.mastodon.mamut.model.Model; import org.mastodon.mamut.model.ModelGraph; @@ -85,11 +87,11 @@ void testCreateTagSet() final ModelGraph modelGraph = model.getGraph(); - addLineageTree1( modelGraph ); - addLineageTree2( modelGraph ); - addLineageTree3( modelGraph ); - addLineageTree4( modelGraph ); - addLineageTree5( modelGraph ); + addLineageTree11( modelGraph ); + addLineageTree21( modelGraph ); + addLineageTree31( modelGraph ); + addLineageTree41( modelGraph ); + addLineageTree51( modelGraph ); addEmptyTree( modelGraph ); String tagSetName = "Test Tag Set"; @@ -127,9 +129,10 @@ void testCreateTagSet() } @Test - void testCreateTagSetWithExternalProjects() throws IOException + void testCreateTagSetWithExternalProjects() throws IOException, SpimDataException { - final Model model = new Model(); + final Model model1 = new Model(); + final Model model2 = new Model(); try (Context context = new Context()) { @@ -137,58 +140,74 @@ void testCreateTagSetWithExternalProjects() throws IOException final ImagePlus dummyImagePlus = ImgToVirtualStack.wrap( new ImgPlus<>( dummyImg, "image", new AxisType[] { Axes.X, Axes.Y, Axes.Z } ) ); SharedBigDataViewerData dummyBdv = Objects.requireNonNull( SharedBigDataViewerData.fromImagePlus( dummyImagePlus ) ); - ProjectModel projectModel = ProjectModel.create( context, model, dummyBdv, null ); + ProjectModel projectModel1 = ProjectModel.create( context, model1, dummyBdv, null ); - final ModelGraph modelGraph = model.getGraph(); + final ModelGraph modelGraph1 = model1.getGraph(); + final ModelGraph modelGraph2 = model2.getGraph(); - addLineageTree1( modelGraph ); - addLineageTree2( modelGraph ); - addLineageTree3( modelGraph ); - addLineageTree4( modelGraph ); - addLineageTree5( modelGraph ); - addEmptyTree( modelGraph ); + addLineageTree11( modelGraph1 ); + addLineageTree21( modelGraph1 ); + addLineageTree31( modelGraph1 ); + addLineageTree41( modelGraph1 ); + addLineageTree51( modelGraph1 ); + addEmptyTree( modelGraph1 ); - File file1 = DemoUtils.saveAppModelToTempFile( dummyImg, model ); - File file2 = DemoUtils.saveAppModelToTempFile( dummyImg, model ); - File[] files = { file1, file2 }; + addLineageTree12( modelGraph2 ); + addLineageTree22( modelGraph2 ); + addLineageTree32( modelGraph2 ); + addLineageTree42( modelGraph2 ); + addLineageTree52( modelGraph2 ); + addEmptyTree( modelGraph2 ); + + File file2 = DemoUtils.saveAppModelToTempFile( dummyImg, model2 ); + File[] files = { file2 }; String tagSetName = "Test Tag Set"; - TagSetUtils.addNewTagSetToModel( model, tagSetName, Collections.emptyList() ); + TagSetUtils.addNewTagSetToModel( model1, tagSetName, Collections.emptyList() ); PrefService prefService = context.getService( PrefService.class ); MastodonProjectService projectService = context.getService( MastodonProjectService.class ); - ClassifyLineagesController controller = new ClassifyLineagesController( projectModel, prefService, projectService ); + ClassifyLineagesController controller = new ClassifyLineagesController( projectModel1, prefService, projectService ); controller.setInputParams( CropCriteria.TIMEPOINT, 0, 100, 1 ); controller.setComputeParams( SimilarityMeasure.NORMALIZED_ZHANG_DIFFERENCE, ClusteringMethod.AVERAGE_LINKAGE, 3 ); controller.setVisualisationParams( false ); controller.setExternalProjects( files, true ); controller.createTagSet(); - List< TagSetStructure.TagSet > tagSets = model.getTagSetModel().getTagSetStructure().getTagSets(); - TagSetStructure.TagSet tagSet1 = model.getTagSetModel().getTagSetStructure().getTagSets().get( 1 ); - List< TagSetStructure.Tag > tags = tagSet1.getTags(); - TagSetStructure.Tag tag0 = tags.get( 0 ); - TagSetStructure.Tag tag1 = tags.get( 1 ); - TagSetStructure.Tag tag2 = tags.get( 2 ); - - Collection< Spot > tag0Spots = model.getTagSetModel().getVertexTags().getTaggedWith( tag0 ); - Collection< Spot > tag1Spots = model.getTagSetModel().getVertexTags().getTaggedWith( tag1 ); - Collection< Spot > tag2Spots = model.getTagSetModel().getVertexTags().getTaggedWith( tag2 ); - Set< String > expectedClassNames = new HashSet<>( Arrays.asList( "Class 1", "Class 2", "Class 3" ) ); - Set< String > actualClassNames = new HashSet<>( Arrays.asList( tag0.label(), tag1.label(), tag2.label() ) ); - Set< Integer > expectedClassCounts = new HashSet<>( Arrays.asList( 9, 12, 14 ) ); - Set< Integer > actualClassCounts = new HashSet<>( Arrays.asList( tag0Spots.size(), tag1Spots.size(), tag2Spots.size() ) ); - assertEquals( "Average Classification (time: 0-100, classes: 3, min. div: 1) ", tagSet1.getName() ); assertTrue( controller.isValidParams() ); - assertEquals( 2, tagSets.size() ); - assertEquals( 3, tags.size() ); - assertEquals( expectedClassNames, actualClassNames ); - assertEquals( expectedClassCounts, actualClassCounts ); + assertClassificationEquals( model1, 1, expectedClassNames, expectedClassCounts ); + ProjectModel pm2 = ProjectLoader.open( file2.getAbsolutePath(), context, false, true ); + assertClassificationEquals( pm2.getModel(), 0, expectedClassNames, expectedClassCounts ); } } + private void assertClassificationEquals( Model model, int existingTagSets, Set< String > expectedClassNames, + Set< Integer > expectedClassCounts ) + { + List< TagSetStructure.TagSet > tagSets = model.getTagSetModel().getTagSetStructure().getTagSets(); + int expectedTagSets = existingTagSets + 1; + TagSetStructure.TagSet tagSet = tagSets.get( existingTagSets ); + List< TagSetStructure.Tag > tags1 = tagSet.getTags(); + TagSetStructure.Tag tag0 = tags1.get( 0 ); + TagSetStructure.Tag tag1 = tags1.get( 1 ); + TagSetStructure.Tag tag2 = tags1.get( 2 ); + + Collection< Spot > tag0Spots = model.getTagSetModel().getVertexTags().getTaggedWith( tag0 ); + Collection< Spot > tag1Spots = model.getTagSetModel().getVertexTags().getTaggedWith( tag1 ); + Collection< Spot > tag2Spots = model.getTagSetModel().getVertexTags().getTaggedWith( tag2 ); + + Set< String > actualClassNames = new HashSet<>( Arrays.asList( tag0.label(), tag1.label(), tag2.label() ) ); + Set< Integer > actualClassCounts = new HashSet<>( Arrays.asList( tag0Spots.size(), tag1Spots.size(), tag2Spots.size() ) ); + + assertEquals( "Average Classification (time: 0-100, classes: 3, min. div: 1) ", tagSet.getName() ); + assertEquals( expectedTagSets, tagSets.size() ); + assertEquals( 3, tags1.size() ); + assertEquals( expectedClassNames, actualClassNames ); + assertEquals( expectedClassCounts, actualClassCounts ); + } + @Test void testSetExternalProjects() throws IOException { @@ -269,7 +288,7 @@ void testGetParameters() * branchSpot2(lifespan=10) branchSpot3(lifespan=30) * */ - private static void addLineageTree1( final ModelGraph modelGraph ) + private static void addLineageTree11( final ModelGraph modelGraph ) { Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); Spot spot2 = modelGraph.addVertex().init( 20, new double[ 3 ], 0 ); @@ -278,6 +297,34 @@ private static void addLineageTree1( final ModelGraph modelGraph ) Spot spot5 = modelGraph.addVertex().init( 20, new double[ 3 ], 0 ); Spot spot6 = modelGraph.addVertex().init( 50, new double[ 3 ], 0 ); + spot1.setLabel( "tree1" ); + + modelGraph.addEdge( spot1, spot2 ); + modelGraph.addEdge( spot2, spot3 ); + modelGraph.addEdge( spot2, spot5 ); + modelGraph.addEdge( spot3, spot4 ); + modelGraph.addEdge( spot5, spot6 ); + } + + /** + *
    +	 *                             branchSpot1(lifespan=21)
    +	 *                    ┌-─────────┴─────────────┐
    +	 *                    │                        │
    +	 *                  branchSpot2(lifespan=10)    branchSpot3(lifespan=30)
    +	 * 
    + */ + private static void addLineageTree12( final ModelGraph modelGraph ) + { + Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); + Spot spot2 = modelGraph.addVertex().init( 21, new double[ 3 ], 0 ); + Spot spot3 = modelGraph.addVertex().init( 21, new double[ 3 ], 0 ); + Spot spot4 = modelGraph.addVertex().init( 31, new double[ 3 ], 0 ); + Spot spot5 = modelGraph.addVertex().init( 21, new double[ 3 ], 0 ); + Spot spot6 = modelGraph.addVertex().init( 51, new double[ 3 ], 0 ); + + spot1.setLabel( "tree1" ); + modelGraph.addEdge( spot1, spot2 ); modelGraph.addEdge( spot2, spot3 ); modelGraph.addEdge( spot2, spot5 ); @@ -293,7 +340,7 @@ private static void addLineageTree1( final ModelGraph modelGraph ) * branchSpot2(lifespan=10) branchSpot3(lifespan=20) * */ - public static void addLineageTree2( final ModelGraph modelGraph ) + public static void addLineageTree21( final ModelGraph modelGraph ) { Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); Spot spot2 = modelGraph.addVertex().init( 30, new double[ 3 ], 0 ); @@ -302,6 +349,34 @@ public static void addLineageTree2( final ModelGraph modelGraph ) Spot spot5 = modelGraph.addVertex().init( 30, new double[ 3 ], 0 ); Spot spot6 = modelGraph.addVertex().init( 50, new double[ 3 ], 0 ); + spot1.setLabel( "tree2" ); + + modelGraph.addEdge( spot1, spot2 ); + modelGraph.addEdge( spot2, spot3 ); + modelGraph.addEdge( spot2, spot5 ); + modelGraph.addEdge( spot3, spot4 ); + modelGraph.addEdge( spot5, spot6 ); + } + + /** + *
    +	 *                               branchSpot1(lifespan=31)
    +	 *                      ┌-─────────┴─────────────┐
    +	 *                      │                        │
    +	 *                    branchSpot2(lifespan=10) branchSpot3(lifespan=20)
    +	 * 
    + */ + public static void addLineageTree22( final ModelGraph modelGraph ) + { + Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); + Spot spot2 = modelGraph.addVertex().init( 30, new double[ 3 ], 0 ); + Spot spot3 = modelGraph.addVertex().init( 31, new double[ 3 ], 0 ); + Spot spot4 = modelGraph.addVertex().init( 41, new double[ 3 ], 0 ); + Spot spot5 = modelGraph.addVertex().init( 31, new double[ 3 ], 0 ); + Spot spot6 = modelGraph.addVertex().init( 51, new double[ 3 ], 0 ); + + spot1.setLabel( "tree2" ); + modelGraph.addEdge( spot1, spot2 ); modelGraph.addEdge( spot2, spot3 ); modelGraph.addEdge( spot2, spot5 ); @@ -320,7 +395,7 @@ public static void addLineageTree2( final ModelGraph modelGraph ) * branchSpot4(lifespan=1) branchSpot5(lifespan=100) * */ - private static void addLineageTree3( final ModelGraph modelGraph ) + private static void addLineageTree31( final ModelGraph modelGraph ) { Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); Spot spot2 = modelGraph.addVertex().init( 1, new double[ 3 ], 0 ); @@ -333,6 +408,45 @@ private static void addLineageTree3( final ModelGraph modelGraph ) Spot spot9 = modelGraph.addVertex().init( 2, new double[ 3 ], 0 ); Spot spot10 = modelGraph.addVertex().init( 102, new double[ 3 ], 0 ); + spot1.setLabel( "tree3" ); + + modelGraph.addEdge( spot1, spot2 ); + modelGraph.addEdge( spot2, spot3 ); + modelGraph.addEdge( spot2, spot5 ); + modelGraph.addEdge( spot3, spot4 ); + modelGraph.addEdge( spot5, spot6 ); + modelGraph.addEdge( spot6, spot7 ); + modelGraph.addEdge( spot6, spot9 ); + modelGraph.addEdge( spot7, spot8 ); + modelGraph.addEdge( spot9, spot10 ); + } + + /** + *
    +	 *                              branchSpot1(lifespan=1)
    +	 *                     ┌-─────────┴─────────────┐
    +	 *                     │                        │
    +	 *                   branchSpot2(lifespan=1)  branchSpot3(lifespan=1)
    +	 *          ┌-─────────┴─────────────┐
    +	 *          │                        │
    +	 *        branchSpot4(lifespan=1)  branchSpot5(lifespan=101)
    +	 * 
    + */ + private static void addLineageTree32( final ModelGraph modelGraph ) + { + Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); + Spot spot2 = modelGraph.addVertex().init( 1, new double[ 3 ], 0 ); + Spot spot3 = modelGraph.addVertex().init( 1, new double[ 3 ], 0 ); + Spot spot4 = modelGraph.addVertex().init( 2, new double[ 3 ], 0 ); + Spot spot5 = modelGraph.addVertex().init( 1, new double[ 3 ], 0 ); + Spot spot6 = modelGraph.addVertex().init( 2, new double[ 3 ], 0 ); + Spot spot7 = modelGraph.addVertex().init( 2, new double[ 3 ], 0 ); + Spot spot8 = modelGraph.addVertex().init( 3, new double[ 3 ], 0 ); + Spot spot9 = modelGraph.addVertex().init( 2, new double[ 3 ], 0 ); + Spot spot10 = modelGraph.addVertex().init( 103, new double[ 3 ], 0 ); + + spot1.setLabel( "tree3" ); + modelGraph.addEdge( spot1, spot2 ); modelGraph.addEdge( spot2, spot3 ); modelGraph.addEdge( spot2, spot5 ); @@ -355,7 +469,7 @@ private static void addLineageTree3( final ModelGraph modelGraph ) * * */ - private static void addLineageTree4( final ModelGraph modelGraph ) + private static void addLineageTree41( final ModelGraph modelGraph ) { Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); Spot spot2 = modelGraph.addVertex().init( 3, new double[ 3 ], 0 ); @@ -373,6 +487,56 @@ private static void addLineageTree4( final ModelGraph modelGraph ) Spot spot13 = modelGraph.addVertex().init( 11, new double[ 3 ], 0 ); Spot spot14 = modelGraph.addVertex().init( 13, new double[ 3 ], 0 ); + spot1.setLabel( "tree4" ); + + modelGraph.addEdge( spot1, spot2 ); + modelGraph.addEdge( spot2, spot3 ); + modelGraph.addEdge( spot2, spot5 ); + modelGraph.addEdge( spot3, spot4 ); + modelGraph.addEdge( spot5, spot6 ); + + modelGraph.addEdge( spot6, spot7 ); + modelGraph.addEdge( spot6, spot9 ); + modelGraph.addEdge( spot7, spot8 ); + modelGraph.addEdge( spot9, spot10 ); + + modelGraph.addEdge( spot4, spot11 ); + modelGraph.addEdge( spot4, spot13 ); + modelGraph.addEdge( spot11, spot12 ); + modelGraph.addEdge( spot13, spot14 ); + } + + /** + *
    +	 *                       branchSpot1(lifespan=3)
    +	 *              ┌-─────────┴─────────────────────────────────────────┐
    +	 *              │                                                    │
    +	 *            branchSpot2(lifespan=9)                     branchSpot3(lifespan=9)
    +	 *  ┌-───────────┴─────────────┐                        ┌-───────────┴─────────────┐
    +	 * branchSpot4(lifespan=4)   branchSpot5(lifespan=4)  branchSpot6(lifespan=1)   branchSpot7(lifespan=2)
    +	 *
    +	 * 
    + */ + private static void addLineageTree42( final ModelGraph modelGraph ) + { + Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); + Spot spot2 = modelGraph.addVertex().init( 3, new double[ 3 ], 0 ); + Spot spot3 = modelGraph.addVertex().init( 3, new double[ 3 ], 0 ); + Spot spot4 = modelGraph.addVertex().init( 12, new double[ 3 ], 0 ); + Spot spot5 = modelGraph.addVertex().init( 3, new double[ 3 ], 0 ); + Spot spot6 = modelGraph.addVertex().init( 12, new double[ 3 ], 0 ); + Spot spot7 = modelGraph.addVertex().init( 12, new double[ 3 ], 0 ); + Spot spot8 = modelGraph.addVertex().init( 16, new double[ 3 ], 0 ); + Spot spot9 = modelGraph.addVertex().init( 12, new double[ 3 ], 0 ); + Spot spot10 = modelGraph.addVertex().init( 16, new double[ 3 ], 0 ); + + Spot spot11 = modelGraph.addVertex().init( 12, new double[ 3 ], 0 ); + Spot spot12 = modelGraph.addVertex().init( 13, new double[ 3 ], 0 ); + Spot spot13 = modelGraph.addVertex().init( 12, new double[ 3 ], 0 ); + Spot spot14 = modelGraph.addVertex().init( 14, new double[ 3 ], 0 ); + + spot1.setLabel( "tree4" ); + modelGraph.addEdge( spot1, spot2 ); modelGraph.addEdge( spot2, spot3 ); modelGraph.addEdge( spot2, spot5 ); @@ -398,7 +562,7 @@ private static void addLineageTree4( final ModelGraph modelGraph ) * branchSpot2(lifespan=10) branchSpot3(lifespan=30) * */ - private static void addLineageTree5( final ModelGraph modelGraph ) + private static void addLineageTree51( final ModelGraph modelGraph ) { Spot spot1 = modelGraph.addVertex().init( 101, new double[ 3 ], 0 ); Spot spot2 = modelGraph.addVertex().init( 121, new double[ 3 ], 0 ); @@ -407,6 +571,8 @@ private static void addLineageTree5( final ModelGraph modelGraph ) Spot spot5 = modelGraph.addVertex().init( 121, new double[ 3 ], 0 ); Spot spot6 = modelGraph.addVertex().init( 151, new double[ 3 ], 0 ); + spot1.setLabel( "tree5" ); + modelGraph.addEdge( spot1, spot2 ); modelGraph.addEdge( spot2, spot3 ); modelGraph.addEdge( spot2, spot5 ); @@ -414,6 +580,32 @@ private static void addLineageTree5( final ModelGraph modelGraph ) modelGraph.addEdge( spot5, spot6 ); } + /** + *
    +	 *                             branchSpot1(lifespan=21)
    +	 *                    ┌-─────────┴─────────────┐
    +	 *                    │                        │
    +	 *                  branchSpot2(lifespan=10)    branchSpot3(lifespan=30)
    +	 * 
    + */ + private static void addLineageTree52( final ModelGraph modelGraph ) + { + Spot spot1 = modelGraph.addVertex().init( 101, new double[ 3 ], 0 ); + Spot spot2 = modelGraph.addVertex().init( 122, new double[ 3 ], 0 ); + Spot spot3 = modelGraph.addVertex().init( 122, new double[ 3 ], 0 ); + Spot spot4 = modelGraph.addVertex().init( 132, new double[ 3 ], 0 ); + Spot spot5 = modelGraph.addVertex().init( 122, new double[ 3 ], 0 ); + Spot spot6 = modelGraph.addVertex().init( 152, new double[ 3 ], 0 ); + + modelGraph.addEdge( spot1, spot2 ); + modelGraph.addEdge( spot2, spot3 ); + modelGraph.addEdge( spot2, spot5 ); + modelGraph.addEdge( spot3, spot4 ); + modelGraph.addEdge( spot5, spot6 ); + + spot1.setLabel( "tree5" ); + } + private static void addEmptyTree( final ModelGraph modelGraph ) { modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); From c2a74152129aee586e193edcbe3dcecd54133966 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 18 Jul 2024 11:24:21 +0200 Subject: [PATCH 60/81] Update scijava-common version to 2.99.1-SNAPSHOT --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9bea767be..fbfd2cbf3 100644 --- a/pom.xml +++ b/pom.xml @@ -21,8 +21,8 @@ 1.0.0-beta-30 org.mastodon - - 2.97.1 + + 2.99.1-SNAPSHOT sign,deploy-to-scijava From 3faf6b15ae03355a58aa313604f9ce3654653836 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 18 Jul 2024 11:25:14 +0200 Subject: [PATCH 61/81] Override scijava-ui-swing version to 1.0.2 --- pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pom.xml b/pom.xml index fbfd2cbf3..afe2c7fdd 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,8 @@ org.mastodon 2.99.1-SNAPSHOT + + 1.0.2 sign,deploy-to-scijava From 217689bd3142c620138fe947959f2153eadd8a41 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 18 Jul 2024 11:29:39 +0200 Subject: [PATCH 62/81] Update heap size for tests to 3gb --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index afe2c7fdd..bf22cdf06 100644 --- a/pom.xml +++ b/pom.xml @@ -212,7 +212,7 @@ - + org.apache.maven.plugins maven-surefire-plugin @@ -228,13 +228,13 @@ coverage - + org.apache.maven.plugins maven-surefire-plugin 3.1.0 - @{argLine} -Xmx2g + @{argLine} -Xmx3g From 34887fc9d3be562297d9519f49be157e45b071a5 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 18 Jul 2024 16:53:12 +0200 Subject: [PATCH 63/81] Set log level to INFO in general and to DEBUG for org.mastodon.mamut --- src/test/resources/logback-test.xml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index 1e0c3ac5f..b7fd67d41 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -137,8 +137,8 @@ - - + + @@ -148,7 +148,6 @@ - - + From 381dd82049aced2b854ec3159ad3d4bfbb308e49 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 18 Jul 2024 17:18:15 +0200 Subject: [PATCH 64/81] Declare prefService and projectService as a private field with @Parameter annotation in the ClassifyLineagesPlugin --- .../mamut/classification/ClassifyLineagesPlugin.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java index 64cad1a08..aeba996e5 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java @@ -68,6 +68,14 @@ public class ClassifyLineagesPlugin extends AbstractContextual implements MamutP @Parameter private CommandService commandService; + @SuppressWarnings( "unused" ) + @Parameter + private PrefService prefService; + + @SuppressWarnings( "unused" ) + @Parameter + private MastodonProjectService projectService; + @SuppressWarnings("unused") public ClassifyLineagesPlugin() { @@ -94,8 +102,6 @@ public void installGlobalActions( Actions actions ) private void classifyLineageTrees() { - PrefService prefService = getContext().getService( PrefService.class ); - MastodonProjectService projectService = getContext().getService( MastodonProjectService.class ); ClassifyLineagesController controller = new ClassifyLineagesController( projectModel, prefService, projectService ); commandService.run( ClassifyLineagesCommand.class, true, "controller", controller ); } From cceb23d3fa360c307eb2fbb92e32c62ef8778573 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 18 Jul 2024 17:32:01 +0200 Subject: [PATCH 65/81] Extract new method classifyUsingExternalProjects() --- .../ClassifyLineagesController.java | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index ae92cffe2..8effc915c 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -177,30 +177,7 @@ private String runClassification() Function< BranchSpotTree, BranchSpot > branchSpotProvider = BranchSpotTree::getBranchSpot; createdTagSetName = applyClassification( classification, tagsAndColors, referenceModel, branchSpotProvider ); if ( addTagSetToExternalProjects && roots.size() > 1 ) - { - for ( int i = 1; i < roots.size(); i++ ) - { - classification = classifyLineageTrees( roots.get( i ), distances ); - for ( ProjectSession projectSession : externalProjects.values() ) - { - ProjectModel projectModel = projectSession.getProjectModel(); - File file = projectSession.getFile(); - branchSpotProvider = branchSpotTree -> projectModel.getModel().getBranchGraph().vertices().stream() - .filter( ( branchSpot -> branchSpot.getFirstLabel().equals( branchSpotTree.getName() ) ) ) - .findFirst().orElse( null ); - applyClassification( classification, tagsAndColors, projectModel.getModel(), branchSpotProvider ); - try - { - ProjectSaver.saveProject( file, projectModel ); - } - catch ( IOException e ) - { - logger.warn( "Could not save tag set of project {} to file {}. Message: {}", projectModel.getProjectName(), - file.getAbsolutePath(), e.getMessage() ); - } - } - } - } + classification = classifyUsingExternalProjects( roots, classification, distances, tagsAndColors ); if ( showDendrogram ) showDendrogram( classification ); } @@ -211,6 +188,37 @@ private String runClassification() return createdTagSetName; } + private Classification< BranchSpotTree > classifyUsingExternalProjects( final List< List< BranchSpotTree > > roots, + final Classification< BranchSpotTree > classification, final double[][] distances, + final List< Pair< String, Integer > > tagsAndColors ) + { + Function< BranchSpotTree, BranchSpot > branchSpotProvider; + Classification< BranchSpotTree > averageClassification = classification; + for ( int i = 1; i < roots.size(); i++ ) + { + averageClassification = classifyLineageTrees( roots.get( i ), distances ); + for ( ProjectSession projectSession : externalProjects.values() ) + { + ProjectModel projectModel = projectSession.getProjectModel(); + File file = projectSession.getFile(); + branchSpotProvider = branchSpotTree -> projectModel.getModel().getBranchGraph().vertices().stream() + .filter( ( branchSpot -> branchSpot.getFirstLabel().equals( branchSpotTree.getName() ) ) ) + .findFirst().orElse( null ); + applyClassification( classification, tagsAndColors, projectModel.getModel(), branchSpotProvider ); + try + { + ProjectSaver.saveProject( file, projectModel ); + } + catch ( IOException e ) + { + logger.warn( "Could not save tag set of project {} to file {}. Message: {}", projectModel.getProjectName(), + file.getAbsolutePath(), e.getMessage() ); + } + } + } + return averageClassification; + } + private Pair< List< List< BranchSpotTree > >, double[][] > getRootsAndDistanceMatrix() { List< BranchSpotTree > roots = getRoots(); From 01eda6a5c908e739b3876fba6b42244073e7c21d Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 18 Jul 2024 17:34:36 +0200 Subject: [PATCH 66/81] Rename method setVisualisationParams to setShowDendrogram --- .../mamut/classification/ClassifyLineagesController.java | 2 +- .../mamut/classification/ui/ClassifyLineagesCommand.java | 2 +- .../classification/ClassifyLineagesControllerTest.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 8effc915c..b17eda7d1 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -422,7 +422,7 @@ public void setComputeParams( final SimilarityMeasure similarityMeasure, final C this.numberOfClasses = numberOfClasses; } - public void setVisualisationParams( final boolean showDendrogram ) + public void setShowDendrogram( final boolean showDendrogram ) { this.showDendrogram = showDendrogram; } diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index 6fd48bce0..e96d07399 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -145,7 +145,7 @@ private void updateParams() controller.setInputParams( CropCriteria.getByName( cropCriterion ), start, end, numberOfCellDivisions ); controller.setComputeParams( SimilarityMeasure.getByName( similarityMeasure ), ClusteringMethod.getByName( clusteringMethod ), numberOfClasses ); - controller.setVisualisationParams( showDendrogram ); + controller.setShowDendrogram( showDendrogram ); controller.setExternalProjects( projects, addTagSetToExternalProjects ); } diff --git a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java index aa234a8e7..1367371e1 100644 --- a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java +++ b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java @@ -99,7 +99,7 @@ void testCreateTagSet() ClassifyLineagesController controller = new ClassifyLineagesController( projectModel ); controller.setInputParams( CropCriteria.TIMEPOINT, 0, 100, 1 ); controller.setComputeParams( SimilarityMeasure.NORMALIZED_ZHANG_DIFFERENCE, ClusteringMethod.AVERAGE_LINKAGE, 3 ); - controller.setVisualisationParams( true ); + controller.setShowDendrogram( true ); // NB: increase test coverage controller.createTagSet(); List< TagSetStructure.TagSet > tagSets = model.getTagSetModel().getTagSetStructure().getTagSets(); @@ -169,7 +169,7 @@ void testCreateTagSetWithExternalProjects() throws IOException, SpimDataExceptio ClassifyLineagesController controller = new ClassifyLineagesController( projectModel1, prefService, projectService ); controller.setInputParams( CropCriteria.TIMEPOINT, 0, 100, 1 ); controller.setComputeParams( SimilarityMeasure.NORMALIZED_ZHANG_DIFFERENCE, ClusteringMethod.AVERAGE_LINKAGE, 3 ); - controller.setVisualisationParams( false ); + controller.setShowDendrogram( false ); controller.setExternalProjects( files, true ); controller.createTagSet(); @@ -254,7 +254,7 @@ void testGetFeedback() ClassifyLineagesController controller = new ClassifyLineagesController( projectModel ); controller.setInputParams( CropCriteria.TIMEPOINT, 1, 0, 1 ); controller.setComputeParams( SimilarityMeasure.NORMALIZED_ZHANG_DIFFERENCE, ClusteringMethod.AVERAGE_LINKAGE, 3 ); - controller.setVisualisationParams( false ); + controller.setShowDendrogram( false ); assertEquals( 2, controller.getFeedback().size() ); assertFalse( controller.isValidParams() ); assertThrows( IllegalArgumentException.class, controller::createTagSet ); From d26d428f3befa563f9fe46bbacf30b8058a82105 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Thu, 18 Jul 2024 17:37:45 +0200 Subject: [PATCH 67/81] Add hierarchical to the class javadoc of the classification class --- .../mastodon/mamut/classification/util/Classification.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/util/Classification.java b/src/main/java/org/mastodon/mamut/classification/util/Classification.java index 2ce4a149a..8e37c5a39 100644 --- a/src/main/java/org/mastodon/mamut/classification/util/Classification.java +++ b/src/main/java/org/mastodon/mamut/classification/util/Classification.java @@ -38,10 +38,10 @@ import java.util.stream.Collectors; /** - * A class that encapsulates the result of a clustering algorithm.
    + * A class that encapsulates the result of a hierarchical clustering algorithm.
    * It contains: *
      - *
    • the root {@link Cluster} object, from which the results of the algorithm can be accessed
    • + *
    • the root {@link Cluster} object, from which the results of the algorithm can be accessed. Clusters are hierarchically organized.
    • *
    • a {@link List} of {@link ObjectClassification} objects, where each objects contains: *
        *
      • a {@link Cluster} object, which represents the classified objects in the dendrogram
      • From 8c0b87298a73c0133c1ce63ff2a52b63dc80d583 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 19 Jul 2024 13:47:34 +0200 Subject: [PATCH 68/81] Refactor variable from expectedClassCount to expectedSpotsPerClass --- .../ClassifyLineagesControllerTest.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java index 1367371e1..23f480fc8 100644 --- a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java +++ b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java @@ -116,15 +116,15 @@ void testCreateTagSet() Set< String > expectedClassNames = new HashSet<>( Arrays.asList( "Class 1", "Class 2", "Class 3" ) ); Set< String > actualClassNames = new HashSet<>( Arrays.asList( tag0.label(), tag1.label(), tag2.label() ) ); - Set< Integer > expectedClassCounts = new HashSet<>( Arrays.asList( 9, 12, 14 ) ); - Set< Integer > actualClassCounts = new HashSet<>( Arrays.asList( tag0Spots.size(), tag1Spots.size(), tag2Spots.size() ) ); + Set< Integer > expectedSpotsPerClass = new HashSet<>( Arrays.asList( 9, 12, 14 ) ); + Set< Integer > actualSpotsPerClass = new HashSet<>( Arrays.asList( tag0Spots.size(), tag1Spots.size(), tag2Spots.size() ) ); assertEquals( "Classification (time: 0-100, classes: 3, min. div: 1) ", tagSet1.getName() ); assertTrue( controller.isValidParams() ); assertEquals( 2, tagSets.size() ); assertEquals( 3, tags.size() ); assertEquals( expectedClassNames, actualClassNames ); - assertEquals( expectedClassCounts, actualClassCounts ); + assertEquals( expectedSpotsPerClass, actualSpotsPerClass ); } } @@ -174,17 +174,17 @@ void testCreateTagSetWithExternalProjects() throws IOException, SpimDataExceptio controller.createTagSet(); Set< String > expectedClassNames = new HashSet<>( Arrays.asList( "Class 1", "Class 2", "Class 3" ) ); - Set< Integer > expectedClassCounts = new HashSet<>( Arrays.asList( 9, 12, 14 ) ); + Set< Integer > expectedSpotsPerClass = new HashSet<>( Arrays.asList( 9, 12, 14 ) ); assertTrue( controller.isValidParams() ); - assertClassificationEquals( model1, 1, expectedClassNames, expectedClassCounts ); + assertClassificationEquals( model1, 1, expectedClassNames, expectedSpotsPerClass ); ProjectModel pm2 = ProjectLoader.open( file2.getAbsolutePath(), context, false, true ); - assertClassificationEquals( pm2.getModel(), 0, expectedClassNames, expectedClassCounts ); + assertClassificationEquals( pm2.getModel(), 0, expectedClassNames, expectedSpotsPerClass ); } } private void assertClassificationEquals( Model model, int existingTagSets, Set< String > expectedClassNames, - Set< Integer > expectedClassCounts ) + Set< Integer > expectedSpotsPerClass ) { List< TagSetStructure.TagSet > tagSets = model.getTagSetModel().getTagSetStructure().getTagSets(); int expectedTagSets = existingTagSets + 1; @@ -199,13 +199,13 @@ private void assertClassificationEquals( Model model, int existingTagSets, Set< Collection< Spot > tag2Spots = model.getTagSetModel().getVertexTags().getTaggedWith( tag2 ); Set< String > actualClassNames = new HashSet<>( Arrays.asList( tag0.label(), tag1.label(), tag2.label() ) ); - Set< Integer > actualClassCounts = new HashSet<>( Arrays.asList( tag0Spots.size(), tag1Spots.size(), tag2Spots.size() ) ); + Set< Integer > actualSpotsPerClass = new HashSet<>( Arrays.asList( tag0Spots.size(), tag1Spots.size(), tag2Spots.size() ) ); assertEquals( "Average Classification (time: 0-100, classes: 3, min. div: 1) ", tagSet.getName() ); assertEquals( expectedTagSets, tagSets.size() ); assertEquals( 3, tags1.size() ); assertEquals( expectedClassNames, actualClassNames ); - assertEquals( expectedClassCounts, actualClassCounts ); + assertEquals( expectedSpotsPerClass, actualSpotsPerClass ); } @Test From e51b3a8c0b5793262949c81e444084a5df348817 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Fri, 19 Jul 2024 15:34:07 +0200 Subject: [PATCH 69/81] Remove generation of test dataset from ClassifyLineagesControllerTest * Instead use temp copies of pre generated test models that are stored in files --- .../ClassifyLineagesControllerTest.java | 400 +----------------- .../mamut/classification/model1.mastodon | Bin 0 -> 6008 bytes .../mamut/classification/model2.mastodon | Bin 0 -> 6007 bytes 3 files changed, 22 insertions(+), 378 deletions(-) create mode 100644 src/test/resources/org/mastodon/mamut/classification/model1.mastodon create mode 100644 src/test/resources/org/mastodon/mamut/classification/model2.mastodon diff --git a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java index 23f480fc8..54a719fd3 100644 --- a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java +++ b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java @@ -37,6 +37,7 @@ import net.imglib2.img.array.ArrayImgs; import net.imglib2.img.display.imagej.ImgToVirtualStack; import net.imglib2.type.numeric.real.FloatType; +import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.Test; import org.mastodon.mamut.ProjectModel; import org.mastodon.mamut.classification.config.ClusteringMethod; @@ -46,7 +47,6 @@ import org.mastodon.mamut.io.ProjectLoader; import org.mastodon.mamut.io.importer.labelimage.util.DemoUtils; import org.mastodon.mamut.model.Model; -import org.mastodon.mamut.model.ModelGraph; import org.mastodon.mamut.model.Spot; import org.mastodon.mamut.util.MastodonProjectService; import org.mastodon.model.tag.TagSetStructure; @@ -57,6 +57,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -73,26 +74,13 @@ class ClassifyLineagesControllerTest { @Test - void testCreateTagSet() + void testCreateTagSet() throws SpimDataException, IOException { - final Model model = new Model(); - try (Context context = new Context()) { - final Img< FloatType > dummyImg = ArrayImgs.floats( 1, 1, 1 ); - final ImagePlus dummyImagePlus = - ImgToVirtualStack.wrap( new ImgPlus<>( dummyImg, "image", new AxisType[] { Axes.X, Axes.Y, Axes.Z } ) ); - SharedBigDataViewerData dummyBdv = Objects.requireNonNull( SharedBigDataViewerData.fromImagePlus( dummyImagePlus ) ); - ProjectModel projectModel = ProjectModel.create( context, model, dummyBdv, null ); - - final ModelGraph modelGraph = model.getGraph(); - - addLineageTree11( modelGraph ); - addLineageTree21( modelGraph ); - addLineageTree31( modelGraph ); - addLineageTree41( modelGraph ); - addLineageTree51( modelGraph ); - addEmptyTree( modelGraph ); + File tempFile = getTempFileCopy( "src/test/resources/org/mastodon/mamut/classification/model1.mastodon" ); + ProjectModel projectModel = ProjectLoader.open( tempFile.getAbsolutePath(), context, false, true ); + Model model = projectModel.getModel(); String tagSetName = "Test Tag Set"; TagSetUtils.addNewTagSetToModel( model, tagSetName, Collections.emptyList() ); @@ -116,7 +104,7 @@ void testCreateTagSet() Set< String > expectedClassNames = new HashSet<>( Arrays.asList( "Class 1", "Class 2", "Class 3" ) ); Set< String > actualClassNames = new HashSet<>( Arrays.asList( tag0.label(), tag1.label(), tag2.label() ) ); - Set< Integer > expectedSpotsPerClass = new HashSet<>( Arrays.asList( 9, 12, 14 ) ); + Set< Integer > expectedSpotsPerClass = new HashSet<>( Arrays.asList( 5, 13, 12 ) ); Set< Integer > actualSpotsPerClass = new HashSet<>( Arrays.asList( tag0Spots.size(), tag1Spots.size(), tag2Spots.size() ) ); assertEquals( "Classification (time: 0-100, classes: 3, min. div: 1) ", tagSet1.getName() ); @@ -131,36 +119,15 @@ void testCreateTagSet() @Test void testCreateTagSetWithExternalProjects() throws IOException, SpimDataException { - final Model model1 = new Model(); - final Model model2 = new Model(); - try (Context context = new Context()) { - final Img< FloatType > dummyImg = ArrayImgs.floats( 1, 1, 1 ); - final ImagePlus dummyImagePlus = - ImgToVirtualStack.wrap( new ImgPlus<>( dummyImg, "image", new AxisType[] { Axes.X, Axes.Y, Axes.Z } ) ); - SharedBigDataViewerData dummyBdv = Objects.requireNonNull( SharedBigDataViewerData.fromImagePlus( dummyImagePlus ) ); - ProjectModel projectModel1 = ProjectModel.create( context, model1, dummyBdv, null ); - - final ModelGraph modelGraph1 = model1.getGraph(); - final ModelGraph modelGraph2 = model2.getGraph(); - - addLineageTree11( modelGraph1 ); - addLineageTree21( modelGraph1 ); - addLineageTree31( modelGraph1 ); - addLineageTree41( modelGraph1 ); - addLineageTree51( modelGraph1 ); - addEmptyTree( modelGraph1 ); + File tempFile1 = getTempFileCopy( "src/test/resources/org/mastodon/mamut/classification/model1.mastodon" ); + File tempFile2 = getTempFileCopy( "src/test/resources/org/mastodon/mamut/classification/model2.mastodon" ); - addLineageTree12( modelGraph2 ); - addLineageTree22( modelGraph2 ); - addLineageTree32( modelGraph2 ); - addLineageTree42( modelGraph2 ); - addLineageTree52( modelGraph2 ); - addEmptyTree( modelGraph2 ); + ProjectModel projectModel1 = ProjectLoader.open( tempFile1.getAbsolutePath(), context, false, true ); + Model model1 = projectModel1.getModel(); - File file2 = DemoUtils.saveAppModelToTempFile( dummyImg, model2 ); - File[] files = { file2 }; + File[] files = { tempFile2 }; String tagSetName = "Test Tag Set"; TagSetUtils.addNewTagSetToModel( model1, tagSetName, Collections.emptyList() ); @@ -174,15 +141,23 @@ void testCreateTagSetWithExternalProjects() throws IOException, SpimDataExceptio controller.createTagSet(); Set< String > expectedClassNames = new HashSet<>( Arrays.asList( "Class 1", "Class 2", "Class 3" ) ); - Set< Integer > expectedSpotsPerClass = new HashSet<>( Arrays.asList( 9, 12, 14 ) ); + Set< Integer > expectedSpotsPerClass = new HashSet<>( Arrays.asList( 5, 13, 12 ) ); assertTrue( controller.isValidParams() ); assertClassificationEquals( model1, 1, expectedClassNames, expectedSpotsPerClass ); - ProjectModel pm2 = ProjectLoader.open( file2.getAbsolutePath(), context, false, true ); + ProjectModel pm2 = ProjectLoader.open( tempFile2.getAbsolutePath(), context, false, true ); assertClassificationEquals( pm2.getModel(), 0, expectedClassNames, expectedSpotsPerClass ); } } + private static File getTempFileCopy( final String fileName ) throws IOException + { + File tempFile1 = Files.createTempFile( "model", ".mastodon" ).toFile(); + tempFile1.deleteOnExit(); + FileUtils.copyFile( new File( fileName ), tempFile1 ); + return tempFile1; + } + private void assertClassificationEquals( Model model, int existingTagSets, Set< String > expectedClassNames, Set< Integer > expectedSpotsPerClass ) { @@ -280,335 +255,4 @@ void testGetParameters() controller.getParameters() ); } - /** - *
        -	 *                             branchSpot1(lifespan=20)
        -	 *                    ┌-─────────┴─────────────┐
        -	 *                    │                        │
        -	 *                  branchSpot2(lifespan=10)    branchSpot3(lifespan=30)
        -	 * 
        - */ - private static void addLineageTree11( final ModelGraph modelGraph ) - { - Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); - Spot spot2 = modelGraph.addVertex().init( 20, new double[ 3 ], 0 ); - Spot spot3 = modelGraph.addVertex().init( 20, new double[ 3 ], 0 ); - Spot spot4 = modelGraph.addVertex().init( 30, new double[ 3 ], 0 ); - Spot spot5 = modelGraph.addVertex().init( 20, new double[ 3 ], 0 ); - Spot spot6 = modelGraph.addVertex().init( 50, new double[ 3 ], 0 ); - - spot1.setLabel( "tree1" ); - - modelGraph.addEdge( spot1, spot2 ); - modelGraph.addEdge( spot2, spot3 ); - modelGraph.addEdge( spot2, spot5 ); - modelGraph.addEdge( spot3, spot4 ); - modelGraph.addEdge( spot5, spot6 ); - } - - /** - *
        -	 *                             branchSpot1(lifespan=21)
        -	 *                    ┌-─────────┴─────────────┐
        -	 *                    │                        │
        -	 *                  branchSpot2(lifespan=10)    branchSpot3(lifespan=30)
        -	 * 
        - */ - private static void addLineageTree12( final ModelGraph modelGraph ) - { - Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); - Spot spot2 = modelGraph.addVertex().init( 21, new double[ 3 ], 0 ); - Spot spot3 = modelGraph.addVertex().init( 21, new double[ 3 ], 0 ); - Spot spot4 = modelGraph.addVertex().init( 31, new double[ 3 ], 0 ); - Spot spot5 = modelGraph.addVertex().init( 21, new double[ 3 ], 0 ); - Spot spot6 = modelGraph.addVertex().init( 51, new double[ 3 ], 0 ); - - spot1.setLabel( "tree1" ); - - modelGraph.addEdge( spot1, spot2 ); - modelGraph.addEdge( spot2, spot3 ); - modelGraph.addEdge( spot2, spot5 ); - modelGraph.addEdge( spot3, spot4 ); - modelGraph.addEdge( spot5, spot6 ); - } - - /** - *
        -	 *                               branchSpot1(lifespan=30)
        -	 *                      ┌-─────────┴─────────────┐
        -	 *                      │                        │
        -	 *                    branchSpot2(lifespan=10) branchSpot3(lifespan=20)
        -	 * 
        - */ - public static void addLineageTree21( final ModelGraph modelGraph ) - { - Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); - Spot spot2 = modelGraph.addVertex().init( 30, new double[ 3 ], 0 ); - Spot spot3 = modelGraph.addVertex().init( 30, new double[ 3 ], 0 ); - Spot spot4 = modelGraph.addVertex().init( 40, new double[ 3 ], 0 ); - Spot spot5 = modelGraph.addVertex().init( 30, new double[ 3 ], 0 ); - Spot spot6 = modelGraph.addVertex().init( 50, new double[ 3 ], 0 ); - - spot1.setLabel( "tree2" ); - - modelGraph.addEdge( spot1, spot2 ); - modelGraph.addEdge( spot2, spot3 ); - modelGraph.addEdge( spot2, spot5 ); - modelGraph.addEdge( spot3, spot4 ); - modelGraph.addEdge( spot5, spot6 ); - } - - /** - *
        -	 *                               branchSpot1(lifespan=31)
        -	 *                      ┌-─────────┴─────────────┐
        -	 *                      │                        │
        -	 *                    branchSpot2(lifespan=10) branchSpot3(lifespan=20)
        -	 * 
        - */ - public static void addLineageTree22( final ModelGraph modelGraph ) - { - Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); - Spot spot2 = modelGraph.addVertex().init( 30, new double[ 3 ], 0 ); - Spot spot3 = modelGraph.addVertex().init( 31, new double[ 3 ], 0 ); - Spot spot4 = modelGraph.addVertex().init( 41, new double[ 3 ], 0 ); - Spot spot5 = modelGraph.addVertex().init( 31, new double[ 3 ], 0 ); - Spot spot6 = modelGraph.addVertex().init( 51, new double[ 3 ], 0 ); - - spot1.setLabel( "tree2" ); - - modelGraph.addEdge( spot1, spot2 ); - modelGraph.addEdge( spot2, spot3 ); - modelGraph.addEdge( spot2, spot5 ); - modelGraph.addEdge( spot3, spot4 ); - modelGraph.addEdge( spot5, spot6 ); - } - - /** - *
        -	 *                              branchSpot1(lifespan=1)
        -	 *                     ┌-─────────┴─────────────┐
        -	 *                     │                        │
        -	 *                   branchSpot2(lifespan=1)  branchSpot3(lifespan=1)
        -	 *          ┌-─────────┴─────────────┐
        -	 *          │                        │
        -	 *        branchSpot4(lifespan=1)  branchSpot5(lifespan=100)
        -	 * 
        - */ - private static void addLineageTree31( final ModelGraph modelGraph ) - { - Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); - Spot spot2 = modelGraph.addVertex().init( 1, new double[ 3 ], 0 ); - Spot spot3 = modelGraph.addVertex().init( 1, new double[ 3 ], 0 ); - Spot spot4 = modelGraph.addVertex().init( 2, new double[ 3 ], 0 ); - Spot spot5 = modelGraph.addVertex().init( 1, new double[ 3 ], 0 ); - Spot spot6 = modelGraph.addVertex().init( 2, new double[ 3 ], 0 ); - Spot spot7 = modelGraph.addVertex().init( 2, new double[ 3 ], 0 ); - Spot spot8 = modelGraph.addVertex().init( 3, new double[ 3 ], 0 ); - Spot spot9 = modelGraph.addVertex().init( 2, new double[ 3 ], 0 ); - Spot spot10 = modelGraph.addVertex().init( 102, new double[ 3 ], 0 ); - - spot1.setLabel( "tree3" ); - - modelGraph.addEdge( spot1, spot2 ); - modelGraph.addEdge( spot2, spot3 ); - modelGraph.addEdge( spot2, spot5 ); - modelGraph.addEdge( spot3, spot4 ); - modelGraph.addEdge( spot5, spot6 ); - modelGraph.addEdge( spot6, spot7 ); - modelGraph.addEdge( spot6, spot9 ); - modelGraph.addEdge( spot7, spot8 ); - modelGraph.addEdge( spot9, spot10 ); - } - - /** - *
        -	 *                              branchSpot1(lifespan=1)
        -	 *                     ┌-─────────┴─────────────┐
        -	 *                     │                        │
        -	 *                   branchSpot2(lifespan=1)  branchSpot3(lifespan=1)
        -	 *          ┌-─────────┴─────────────┐
        -	 *          │                        │
        -	 *        branchSpot4(lifespan=1)  branchSpot5(lifespan=101)
        -	 * 
        - */ - private static void addLineageTree32( final ModelGraph modelGraph ) - { - Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); - Spot spot2 = modelGraph.addVertex().init( 1, new double[ 3 ], 0 ); - Spot spot3 = modelGraph.addVertex().init( 1, new double[ 3 ], 0 ); - Spot spot4 = modelGraph.addVertex().init( 2, new double[ 3 ], 0 ); - Spot spot5 = modelGraph.addVertex().init( 1, new double[ 3 ], 0 ); - Spot spot6 = modelGraph.addVertex().init( 2, new double[ 3 ], 0 ); - Spot spot7 = modelGraph.addVertex().init( 2, new double[ 3 ], 0 ); - Spot spot8 = modelGraph.addVertex().init( 3, new double[ 3 ], 0 ); - Spot spot9 = modelGraph.addVertex().init( 2, new double[ 3 ], 0 ); - Spot spot10 = modelGraph.addVertex().init( 103, new double[ 3 ], 0 ); - - spot1.setLabel( "tree3" ); - - modelGraph.addEdge( spot1, spot2 ); - modelGraph.addEdge( spot2, spot3 ); - modelGraph.addEdge( spot2, spot5 ); - modelGraph.addEdge( spot3, spot4 ); - modelGraph.addEdge( spot5, spot6 ); - modelGraph.addEdge( spot6, spot7 ); - modelGraph.addEdge( spot6, spot9 ); - modelGraph.addEdge( spot7, spot8 ); - modelGraph.addEdge( spot9, spot10 ); - } - - /** - *
        -	 *                       branchSpot1(lifespan=3)
        -	 *              ┌-─────────┴─────────────────────────────────────────┐
        -	 *              │                                                    │
        -	 *            branchSpot2(lifespan=8)                     branchSpot3(lifespan=8)
        -	 *  ┌-───────────┴─────────────┐                        ┌-───────────┴─────────────┐
        -	 * branchSpot4(lifespan=4)   branchSpot5(lifespan=4)  branchSpot6(lifespan=1)   branchSpot7(lifespan=2)
        -	 *
        -	 * 
        - */ - private static void addLineageTree41( final ModelGraph modelGraph ) - { - Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); - Spot spot2 = modelGraph.addVertex().init( 3, new double[ 3 ], 0 ); - Spot spot3 = modelGraph.addVertex().init( 3, new double[ 3 ], 0 ); - Spot spot4 = modelGraph.addVertex().init( 11, new double[ 3 ], 0 ); - Spot spot5 = modelGraph.addVertex().init( 3, new double[ 3 ], 0 ); - Spot spot6 = modelGraph.addVertex().init( 11, new double[ 3 ], 0 ); - Spot spot7 = modelGraph.addVertex().init( 11, new double[ 3 ], 0 ); - Spot spot8 = modelGraph.addVertex().init( 15, new double[ 3 ], 0 ); - Spot spot9 = modelGraph.addVertex().init( 11, new double[ 3 ], 0 ); - Spot spot10 = modelGraph.addVertex().init( 15, new double[ 3 ], 0 ); - - Spot spot11 = modelGraph.addVertex().init( 11, new double[ 3 ], 0 ); - Spot spot12 = modelGraph.addVertex().init( 12, new double[ 3 ], 0 ); - Spot spot13 = modelGraph.addVertex().init( 11, new double[ 3 ], 0 ); - Spot spot14 = modelGraph.addVertex().init( 13, new double[ 3 ], 0 ); - - spot1.setLabel( "tree4" ); - - modelGraph.addEdge( spot1, spot2 ); - modelGraph.addEdge( spot2, spot3 ); - modelGraph.addEdge( spot2, spot5 ); - modelGraph.addEdge( spot3, spot4 ); - modelGraph.addEdge( spot5, spot6 ); - - modelGraph.addEdge( spot6, spot7 ); - modelGraph.addEdge( spot6, spot9 ); - modelGraph.addEdge( spot7, spot8 ); - modelGraph.addEdge( spot9, spot10 ); - - modelGraph.addEdge( spot4, spot11 ); - modelGraph.addEdge( spot4, spot13 ); - modelGraph.addEdge( spot11, spot12 ); - modelGraph.addEdge( spot13, spot14 ); - } - - /** - *
        -	 *                       branchSpot1(lifespan=3)
        -	 *              ┌-─────────┴─────────────────────────────────────────┐
        -	 *              │                                                    │
        -	 *            branchSpot2(lifespan=9)                     branchSpot3(lifespan=9)
        -	 *  ┌-───────────┴─────────────┐                        ┌-───────────┴─────────────┐
        -	 * branchSpot4(lifespan=4)   branchSpot5(lifespan=4)  branchSpot6(lifespan=1)   branchSpot7(lifespan=2)
        -	 *
        -	 * 
        - */ - private static void addLineageTree42( final ModelGraph modelGraph ) - { - Spot spot1 = modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); - Spot spot2 = modelGraph.addVertex().init( 3, new double[ 3 ], 0 ); - Spot spot3 = modelGraph.addVertex().init( 3, new double[ 3 ], 0 ); - Spot spot4 = modelGraph.addVertex().init( 12, new double[ 3 ], 0 ); - Spot spot5 = modelGraph.addVertex().init( 3, new double[ 3 ], 0 ); - Spot spot6 = modelGraph.addVertex().init( 12, new double[ 3 ], 0 ); - Spot spot7 = modelGraph.addVertex().init( 12, new double[ 3 ], 0 ); - Spot spot8 = modelGraph.addVertex().init( 16, new double[ 3 ], 0 ); - Spot spot9 = modelGraph.addVertex().init( 12, new double[ 3 ], 0 ); - Spot spot10 = modelGraph.addVertex().init( 16, new double[ 3 ], 0 ); - - Spot spot11 = modelGraph.addVertex().init( 12, new double[ 3 ], 0 ); - Spot spot12 = modelGraph.addVertex().init( 13, new double[ 3 ], 0 ); - Spot spot13 = modelGraph.addVertex().init( 12, new double[ 3 ], 0 ); - Spot spot14 = modelGraph.addVertex().init( 14, new double[ 3 ], 0 ); - - spot1.setLabel( "tree4" ); - - modelGraph.addEdge( spot1, spot2 ); - modelGraph.addEdge( spot2, spot3 ); - modelGraph.addEdge( spot2, spot5 ); - modelGraph.addEdge( spot3, spot4 ); - modelGraph.addEdge( spot5, spot6 ); - - modelGraph.addEdge( spot6, spot7 ); - modelGraph.addEdge( spot6, spot9 ); - modelGraph.addEdge( spot7, spot8 ); - modelGraph.addEdge( spot9, spot10 ); - - modelGraph.addEdge( spot4, spot11 ); - modelGraph.addEdge( spot4, spot13 ); - modelGraph.addEdge( spot11, spot12 ); - modelGraph.addEdge( spot13, spot14 ); - } - - /** - *
        -	 *                             branchSpot1(lifespan=20)
        -	 *                    ┌-─────────┴─────────────┐
        -	 *                    │                        │
        -	 *                  branchSpot2(lifespan=10)    branchSpot3(lifespan=30)
        -	 * 
        - */ - private static void addLineageTree51( final ModelGraph modelGraph ) - { - Spot spot1 = modelGraph.addVertex().init( 101, new double[ 3 ], 0 ); - Spot spot2 = modelGraph.addVertex().init( 121, new double[ 3 ], 0 ); - Spot spot3 = modelGraph.addVertex().init( 121, new double[ 3 ], 0 ); - Spot spot4 = modelGraph.addVertex().init( 131, new double[ 3 ], 0 ); - Spot spot5 = modelGraph.addVertex().init( 121, new double[ 3 ], 0 ); - Spot spot6 = modelGraph.addVertex().init( 151, new double[ 3 ], 0 ); - - spot1.setLabel( "tree5" ); - - modelGraph.addEdge( spot1, spot2 ); - modelGraph.addEdge( spot2, spot3 ); - modelGraph.addEdge( spot2, spot5 ); - modelGraph.addEdge( spot3, spot4 ); - modelGraph.addEdge( spot5, spot6 ); - } - - /** - *
        -	 *                             branchSpot1(lifespan=21)
        -	 *                    ┌-─────────┴─────────────┐
        -	 *                    │                        │
        -	 *                  branchSpot2(lifespan=10)    branchSpot3(lifespan=30)
        -	 * 
        - */ - private static void addLineageTree52( final ModelGraph modelGraph ) - { - Spot spot1 = modelGraph.addVertex().init( 101, new double[ 3 ], 0 ); - Spot spot2 = modelGraph.addVertex().init( 122, new double[ 3 ], 0 ); - Spot spot3 = modelGraph.addVertex().init( 122, new double[ 3 ], 0 ); - Spot spot4 = modelGraph.addVertex().init( 132, new double[ 3 ], 0 ); - Spot spot5 = modelGraph.addVertex().init( 122, new double[ 3 ], 0 ); - Spot spot6 = modelGraph.addVertex().init( 152, new double[ 3 ], 0 ); - - modelGraph.addEdge( spot1, spot2 ); - modelGraph.addEdge( spot2, spot3 ); - modelGraph.addEdge( spot2, spot5 ); - modelGraph.addEdge( spot3, spot4 ); - modelGraph.addEdge( spot5, spot6 ); - - spot1.setLabel( "tree5" ); - } - - private static void addEmptyTree( final ModelGraph modelGraph ) - { - modelGraph.addVertex().init( 0, new double[ 3 ], 0 ); - } - } diff --git a/src/test/resources/org/mastodon/mamut/classification/model1.mastodon b/src/test/resources/org/mastodon/mamut/classification/model1.mastodon new file mode 100644 index 0000000000000000000000000000000000000000..b8c0680c11dbe79f52c5a1feda5c60b151669002 GIT binary patch literal 6008 zcmeGgO>Yxd@NI$>(?R;=) z`ybjf9I8qcZKdWw8}%1ZiB>sqsVV`Nsz^vkTsV==tUdPR5m&0@7Zs@!P4>;3kDZ-) zGtcj3r@A){hV6B+zO=U zglN7dF$R^SW z?h#02r+VYh?=7c(g|EB=m~9(}&A=^|3evL!XU-Vfw({iGV-Z^vViPdLLHxH-uv5c} z1~k~*tHGuYKa(2#)L~LbJm1#f=phX@b@G<|;4PU%PgUy%4PyCIfr3t*EA*kqVr{QJsQ^&V&?Q}4~o{r`;uffj+4K_d5 zU{i;mU#{aPG=Z*9*O}kQDYNl1zPaO=ZzM?LxrJnq$D1wkVt@oaFz6+sF^(%VZUAHt zd3azVKpw9>teJjd!V_uiA*7GIZGZ$wzY0iv9B(HkJa+){QxT5&Ndf8IfTWMSU4R70 z+e1WS{F*|)0W|3&?{&sU-kXd@+ixi}{(2{U9P?ka`+zn92}~I5Cjy>G9{?nMJnM<)?vv;N5eow;N9&J~`eUsAI9z`u@t}SB zryIW=hO`a=wm)d&+6kSRN_5V)#2>48&Yq)aQUuVO<`|7{e(-w&4;~nn_Qo;jSEUnH z16fIATz}RqxRtU8w+2P&htVsSjLh@K(cbt5#7#g$Y=1#m^kj~op2(dPv9dcaJ&@wi z^bIBY{mGWxT@FY9e`FRlK6q z>LGbvMU->o@w2a5Igu`|fxfL@eElOtehIMs8o***y9R2~E9KpAApo#!VwgHf|Yb`yT$1hqq1xwqKjraSL!ET9MIh zt9`}E&sNLuDXXivz=7M`m$Oqw*8uEtbZN`4F?kn2oi$gusv>X8ysZckhZi^RJXJN` zaCv7E;uB~j)WKCPUZT8?g}4h^Ys*q?&8jV)YAos&tUw&s+M>$E5?Af;)bM^Q#64(n ljb)-L;mfFLW7b;WLoa+WhuNuEw1xJ<52^1Vu1itJ{2LM~YM=lB literal 0 HcmV?d00001 diff --git a/src/test/resources/org/mastodon/mamut/classification/model2.mastodon b/src/test/resources/org/mastodon/mamut/classification/model2.mastodon new file mode 100644 index 0000000000000000000000000000000000000000..5ae4cb8e14a7f798d4306ac70d174cb888f6885d GIT binary patch literal 6007 zcmeHL-HQ}Q5brtNyVdhzjHl+yIF26)gqyux^|B;;3Q6=lZq)@>lVFgHyM4!avmeaP z?tS=@kG~=0IS(Nrm<04e5&r>^1QhcW1mlY+2!bF!c~;GC?bfxofxz^kU_otHbye5& zRQIoDyK@sORt}1IJT7+3Km1%U>Ib>%S5C`9V9%C3wWx zh&kB_|6EHt-hG9JH^#pSQPYbu&o%JkT8hBhs51)jaI#U;i!x6g?>-Q9G9jMS5OYpL zOxX01(|3_a48a+B)E}0J2>IywdWrt!>@r&q;~#gF`G*8`JhzY!+IX==TMU?}7lb}i zT5?>`SA$OR(1r&lBDC=j1mck62K85fX*}9^O+o!tW=DA)FwtuutS6-<^HUPqHUd+8 zwDFS!+BN|bq5L{2(4zh(FpWps8^AU?LNQt)xJU z`Zi!1k2VXK2yNR*srzF6iR@{o;n<#2p0bYyJ{-$G`tADhJ9jsq8H2T+t%9!+h?wV` zk{8qWMd*^;4GcG@-6_*C5hg(2|t{}oc9 znoX+NB=MmA=))Voq+nTxfV&^Gaqk4qR4qJbTjJ&dp0j5to0I{ZO($hzfev*^~U9^4v~WH|^QdEUrAX&vp2U%}l3HpIGToJDVT|KYLx zK@qFEv(f`EPVKotC*-M3D$2;9qK*1F>Nw(5bdkS`GV14OL!=RLlp(5!RCG~4M;S+$ ziYD^ssN#rH(L??mMYQHxp4h2 z7zf+y|2w#vz=jwI*Pk3k{P_Otkx$0+Ux@y2^%Il6Q&PX$FZTeX4rp2T!1y=wXD&nL zFZ|~oKv+%H-S9&5r1v31i3o~>KP>HgbS3-HvFz}@#I zR@@@oht_0x*=nD13e)u}yvo7?Zg8-+zstD^qi0Zb?rixrCa(fGX3-6caWxKK t8eVUO_!~-GYn!Np@L^Q6an(9hf?D|O8s;Wq;U}~Y{z&}+c|8|a%%`hiXUzZr literal 0 HcmV?d00001 From ee9476a3302771efb051c72bba6a508f56eee570 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Mon, 22 Jul 2024 10:25:14 +0200 Subject: [PATCH 70/81] Refactor method openProjectSessionsCount to activeSessions() --- .../mastodon/mamut/util/MastodonProjectService.java | 8 ++++++-- .../mamut/util/MastodonProjectServiceTest.java | 12 ++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/util/MastodonProjectService.java b/src/main/java/org/mastodon/mamut/util/MastodonProjectService.java index 5c4908ba6..d806acbe2 100644 --- a/src/main/java/org/mastodon/mamut/util/MastodonProjectService.java +++ b/src/main/java/org/mastodon/mamut/util/MastodonProjectService.java @@ -30,7 +30,7 @@ * The service will keep track of all project models that have been opened and the sessions that are currently using them. * For this purpose, the service provides a method to create a new session for a project file and a method to release a session. *
        - * When a session is released, the service will close the project model if no other session is using it. + * When a session is released, the service will close the project model, if no other session is using it. */ @Plugin( type = Service.class ) public class MastodonProjectService extends AbstractService @@ -105,7 +105,11 @@ public void releaseSession( final ProjectSession projectSession ) } } - public int openProjectModelsCount() + /** + * Returns the number of active sessions. + * @return the number of active sessions. + */ + public int activeSessions() { return sessions.size(); } diff --git a/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java b/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java index 0dad8343a..6db12eef6 100644 --- a/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java +++ b/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java @@ -28,17 +28,17 @@ void test() throws IOException, SpimDataException MastodonProjectService service = new MastodonProjectService(); service.setContext( context ); ProjectSession projectSession1 = service.createSession( mastodonFile1 ); - assertEquals( 1, service.openProjectModelsCount() ); + assertEquals( 1, service.activeSessions() ); ProjectSession projectSession2 = service.createSession( mastodonFile1 ); - assertEquals( 1, service.openProjectModelsCount() ); + assertEquals( 1, service.activeSessions() ); ProjectSession projectSession3 = service.createSession( mastodonFile2 ); - assertEquals( 2, service.openProjectModelsCount() ); + assertEquals( 2, service.activeSessions() ); projectSession1.close(); - assertEquals( 2, service.openProjectModelsCount() ); + assertEquals( 2, service.activeSessions() ); projectSession2.close(); - assertEquals( 1, service.openProjectModelsCount() ); + assertEquals( 1, service.activeSessions() ); projectSession3.close(); - assertEquals( 0, service.openProjectModelsCount() ); + assertEquals( 0, service.activeSessions() ); assertEquals( mastodonFile1, projectSession1.getFile() ); assertEquals( mastodonFile1, projectSession2.getFile() ); assertEquals( mastodonFile2, projectSession3.getFile() ); From 193db4134e87f157e454b7808bcee5ebdcef2a21 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Mon, 22 Jul 2024 12:53:55 +0200 Subject: [PATCH 71/81] Introduce new class ExternalProjects --- .../ClassifyLineagesController.java | 102 ++--------- .../multiproject/ExternalProjects.java | 167 ++++++++++++++++++ .../util/MastodonProjectServiceTest.java | 3 - 3 files changed, 182 insertions(+), 90 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index b17eda7d1..b8cd99106 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -28,13 +28,13 @@ */ package org.mastodon.mamut.classification; -import mpicbg.spim.data.SpimDataException; import org.apache.commons.lang3.tuple.Pair; import org.mastodon.collection.RefSet; import org.mastodon.graph.algorithm.traversal.DepthFirstIterator; import org.mastodon.mamut.ProjectModel; import org.mastodon.mamut.classification.config.ClusteringMethod; import org.mastodon.mamut.classification.config.SimilarityMeasure; +import org.mastodon.mamut.classification.multiproject.ExternalProjects; import org.mastodon.mamut.classification.util.Classification; import org.mastodon.mamut.classification.config.CropCriteria; import org.mastodon.mamut.classification.ui.DendrogramView; @@ -60,12 +60,9 @@ import java.io.IOException; import java.lang.invoke.MethodHandles; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -90,8 +87,6 @@ public class ClassifyLineagesController private final PrefService prefs; - private final MastodonProjectService projectService; - private SimilarityMeasure similarityMeasure = SimilarityMeasure.NORMALIZED_ZHANG_DIFFERENCE; private ClusteringMethod clusteringMethod = ClusteringMethod.AVERAGE_LINKAGE; @@ -106,12 +101,10 @@ public class ClassifyLineagesController private int minCellDivisions; - private final Map< File, ProjectSession > externalProjects; - - private final Map< File, String > failingExternalProjects; - private boolean showDendrogram; + private final ExternalProjects externalProjects; + private boolean addTagSetToExternalProjects; private boolean running = false; @@ -137,9 +130,7 @@ public ClassifyLineagesController( final ProjectModel referenceProjectModel, fin this.referenceProjectModel = referenceProjectModel; this.referenceModel = referenceProjectModel.getModel(); this.prefs = prefs; - this.projectService = projectService; - this.externalProjects = new HashMap<>(); - this.failingExternalProjects = new HashMap<>(); + this.externalProjects = new ExternalProjects( projectService ); } /** @@ -197,10 +188,10 @@ private Classification< BranchSpotTree > classifyUsingExternalProjects( final Li for ( int i = 1; i < roots.size(); i++ ) { averageClassification = classifyLineageTrees( roots.get( i ), distances ); - for ( ProjectSession projectSession : externalProjects.values() ) + for ( Map.Entry< File, ProjectSession > externalProject : externalProjects.getProjects().entrySet() ) { - ProjectModel projectModel = projectSession.getProjectModel(); - File file = projectSession.getFile(); + ProjectModel projectModel = externalProject.getValue().getProjectModel(); + File file = externalProject.getKey(); branchSpotProvider = branchSpotTree -> projectModel.getModel().getBranchGraph().vertices().stream() .filter( ( branchSpot -> branchSpot.getFirstLabel().equals( branchSpotTree.getName() ) ) ) .findFirst().orElse( null ); @@ -233,9 +224,9 @@ private Pair< List< List< BranchSpotTree > >, double[][] > getRootsAndDistanceMa keepCommonRootsAndSort( roots, commonRootNames ); treeMatrix.add( roots ); - for ( ProjectSession projectSession : externalProjects.values() ) + for ( ProjectModel projectModel : externalProjects.getProjectModels() ) { - List< BranchSpotTree > externalRoots = getRoots( projectSession.getProjectModel() ); + List< BranchSpotTree > externalRoots = getRoots( projectModel ); keepCommonRootsAndSort( externalRoots, commonRootNames ); treeMatrix.add( externalRoots ); } @@ -245,9 +236,9 @@ private Pair< List< List< BranchSpotTree > >, double[][] > getRootsAndDistanceMa private List< String > findCommonRootNames() { Set< String > commonRootNames = extractRootNamesFromProjectModel( referenceProjectModel ); - for ( ProjectSession projectSession : externalProjects.values() ) + for ( ProjectModel projectModel : externalProjects.getProjectModels() ) { - Set< String > rootNames = extractRootNamesFromProjectModel( projectSession.getProjectModel() ); + Set< String > rootNames = extractRootNamesFromProjectModel( projectModel ); commonRootNames.retainAll( rootNames ); } List< String > commonRootNamesList = new ArrayList<>( commonRootNames ); @@ -430,68 +421,7 @@ public void setShowDendrogram( final boolean showDendrogram ) public void setExternalProjects( final File[] projects, final boolean addTagSetToExternalProjects ) { this.addTagSetToExternalProjects = addTagSetToExternalProjects; - List< File > projectsList = projects == null ? Collections.emptyList() : Arrays.asList( projects ); - removeProjects( projectsList ); - cleanUpFailingProjects( projectsList ); - addProjects( projects ); - } - - /** - * Remove files from the externalProjects map that are not in the projects list - */ - private void removeProjects( final List< File > projectsList ) - { - Iterator< Map.Entry< File, ProjectSession > > iterator = externalProjects.entrySet().iterator(); - while ( iterator.hasNext() ) - { - Map.Entry< File, ProjectSession > entry = iterator.next(); - File file = entry.getKey(); - if ( !projectsList.contains( file ) ) - { - ProjectSession projectSession = entry.getValue(); - projectSession.close(); - iterator.remove(); - } - } - } - - /** - * Remove files from the failingExternalProjects map that are not in the projects list - */ - private void cleanUpFailingProjects( final List< File > projectsList ) - { - for ( Map.Entry< File, String > entry : failingExternalProjects.entrySet() ) - { - File file = entry.getKey(); - if ( !projectsList.contains( file ) ) - failingExternalProjects.remove( file ); - } - } - - /** - * Add files from projects to the map if they are not already present - */ - private void addProjects( final File[] projects ) - { - if ( projects == null ) - return; - for ( File file : projects ) - { - if ( !externalProjects.containsKey( file ) ) - { - try - { - externalProjects.put( file, projectService.createSession( file ) ); - failingExternalProjects.remove( file ); - } - catch ( SpimDataException | IOException | RuntimeException e ) - { - failingExternalProjects.put( file, - "Could not read project from file " + file.getAbsolutePath() + ".
        Error: " + e.getMessage() ); - logger.warn( "Could not read project from file {}. Error: {}", file.getAbsolutePath(), e.getMessage() ); - } - } - } + externalProjects.setProjects( projects ); } public List< String > getFeedback() @@ -514,7 +444,7 @@ public List< String > getFeedback() } if ( cropCriterion.equals( CropCriteria.NUMBER_OF_SPOTS ) ) feedback.addAll( checkNumberOfSpots() ); - feedback.addAll( failingExternalProjects.values() ); + feedback.addAll( externalProjects.getFailingProjectMessages() ); return feedback; } @@ -544,8 +474,7 @@ private List< String > checkNumberOfSpots() { List< String > feedback = new ArrayList<>(); Set< ProjectModel > allModels = new HashSet<>( Collections.singletonList( referenceProjectModel ) ); - for ( ProjectSession projectSession : externalProjects.values() ) - allModels.add( projectSession.getProjectModel() ); + allModels.addAll( externalProjects.getProjectModels() ); for ( ProjectModel projectModel : allModels ) { Model model = projectModel.getModel(); @@ -576,7 +505,6 @@ private List< String > checkNumberOfSpots() public void close() { - for ( ProjectSession projectSession : externalProjects.values() ) - projectSession.close(); + externalProjects.close(); } } diff --git a/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java b/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java new file mode 100644 index 000000000..d9098075e --- /dev/null +++ b/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java @@ -0,0 +1,167 @@ +package org.mastodon.mamut.classification.multiproject; + +import mpicbg.spim.data.SpimDataException; +import org.mastodon.mamut.ProjectModel; +import org.mastodon.mamut.util.MastodonProjectService; +import org.mastodon.mamut.util.ProjectSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * A collection of external projects. + *
        + * This class manages a collection of external projects. It holds a mapping of project files to project sessions and a mapping of projects that failed to be loaded and the reason why they failed to load. + */ +public class ExternalProjects implements AutoCloseable +{ + private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() ); + + private final MastodonProjectService projectService; + + private final Map< File, ProjectSession > projectSessions; + + private final Map< File, String > failingProjects; + + public ExternalProjects( final MastodonProjectService projectService ) + { + this.projectService = projectService; + this.projectSessions = new HashMap<>(); + this.failingProjects = new HashMap<>(); + } + + /** + * Gets all project models of the external projects + * @return the project models + */ + public Collection< ProjectModel > getProjectModels() + { + return projectSessions.values().stream().map( ProjectSession::getProjectModel ).collect( Collectors.toList() ); + } + + /** + * Gets a mapping of external project files to their project sessions + * @return the mapping + */ + public Map< File, ProjectSession > getProjects() + { + return projectSessions; + } + + /** + * Get the number of external projects + * @return the number of external projects + */ + public int size() + { + return projectSessions.size(); + } + + /** + * Check if the external projects is empty + * @return {@code true} if the external projects is empty, {@code false} otherwise + */ + public boolean isEmpty() + { + return projectSessions.isEmpty(); + } + + /** + * Get a Collection failing projects and the reason why they failed to load. + * @return the failing projects + */ + public Collection< String > getFailingProjectMessages() + { + return failingProjects.values(); + } + + /** + * Set the external projects + * @param projects the external projects + */ + public void setProjects( final File[] projects ) + { + List< File > projectsList = projects == null ? Collections.emptyList() : Arrays.asList( projects ); + removeProjects( projectsList ); + cleanUpFailingProjects( projectsList ); + addProjects( projects ); + logger.debug( "Set {} projects. Active project sessions: {}.", projectsList.size(), projectService.activeSessions() ); + } + + /** + * Remove files from the externalProjects map that are not in the projects list + */ + private void removeProjects( final List< File > projectsList ) + { + Iterator< Map.Entry< File, ProjectSession > > iterator = projectSessions.entrySet().iterator(); + while ( iterator.hasNext() ) + { + Map.Entry< File, ProjectSession > entry = iterator.next(); + File file = entry.getKey(); + if ( !projectsList.contains( file ) ) + { + ProjectSession projectSession = entry.getValue(); + projectSession.close(); + iterator.remove(); + } + } + } + + /** + * Remove files from the failingExternalProjects map that are not in the projects list + */ + private void cleanUpFailingProjects( final List< File > projectsList ) + { + for ( Map.Entry< File, String > entry : failingProjects.entrySet() ) + { + File file = entry.getKey(); + if ( !projectsList.contains( file ) ) + failingProjects.remove( file ); + } + } + + /** + * Add files from projects to the map if they are not already present + */ + private void addProjects( final File[] files ) + { + if ( files == null ) + return; + for ( File file : files ) + { + if ( !projectSessions.containsKey( file ) ) + { + try + { + projectSessions.put( file, projectService.createSession( file ) ); + failingProjects.remove( file ); + } + catch ( SpimDataException | IOException | RuntimeException e ) + { + failingProjects.put( file, + "Could not read project from file " + file.getAbsolutePath() + ".
        Error: " + e.getMessage() ); + logger.warn( "Could not read project from file {}. Error: {}", file.getAbsolutePath(), e.getMessage() ); + } + } + } + } + + @Override + public void close() + { + for ( ProjectSession projectSession : projectSessions.values() ) + projectSession.close(); + logger.debug( "Remaining active project sessions: {}.", projectService.activeSessions() ); + } +} diff --git a/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java b/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java index 6db12eef6..3a3f64a01 100644 --- a/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java +++ b/src/test/java/org/mastodon/mamut/util/MastodonProjectServiceTest.java @@ -39,9 +39,6 @@ void test() throws IOException, SpimDataException assertEquals( 1, service.activeSessions() ); projectSession3.close(); assertEquals( 0, service.activeSessions() ); - assertEquals( mastodonFile1, projectSession1.getFile() ); - assertEquals( mastodonFile1, projectSession2.getFile() ); - assertEquals( mastodonFile2, projectSession3.getFile() ); } } } From cbcafbabfd822f6042becbb46aa6313a87589e9d Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Tue, 23 Jul 2024 09:17:11 +0200 Subject: [PATCH 72/81] Make constructor of ProjectSession package protected --- src/main/java/org/mastodon/mamut/util/ProjectSession.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/util/ProjectSession.java b/src/main/java/org/mastodon/mamut/util/ProjectSession.java index cb8af2ccd..5129563c5 100644 --- a/src/main/java/org/mastodon/mamut/util/ProjectSession.java +++ b/src/main/java/org/mastodon/mamut/util/ProjectSession.java @@ -25,7 +25,7 @@ public class ProjectSession implements AutoCloseable * @param projectModel the project model. * @param service the service that created this session. */ - public ProjectSession( final File file, final ProjectModel projectModel, final MastodonProjectService service ) + ProjectSession( final File file, final ProjectModel projectModel, final MastodonProjectService service ) { this.file = file; this.projectModel = projectModel; From 83e35ca56ce4fd84859dbb577dc9ad12507e2d1f Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Tue, 23 Jul 2024 09:18:42 +0200 Subject: [PATCH 73/81] Change methods runClassification and classifyExternalProjects in ClassifyLineagesController --- .../ClassifyLineagesController.java | 73 +++++++++---------- .../multiproject/ExternalProjects.java | 9 ++- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index b8cd99106..58ec4fb80 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -64,7 +64,6 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.StringJoiner; @@ -160,15 +159,16 @@ private String runClassification() String createdTagSetName; try { - Pair< List< List< BranchSpotTree > >, double[][] > rootsAndDistances = getRootsAndDistanceMatrix(); - List< List< BranchSpotTree > > roots = rootsAndDistances.getLeft(); + Pair< List< Pair< ProjectSession, List< BranchSpotTree > > >, double[][] > rootsAndDistances = getRootsAndDistanceMatrix(); + List< Pair< ProjectSession, List< BranchSpotTree > > > rootsMatrix = rootsAndDistances.getLeft(); double[][] distances = rootsAndDistances.getRight(); - Classification< BranchSpotTree > classification = classifyLineageTrees( roots.get( 0 ), distances ); + Pair< ProjectSession, List< BranchSpotTree > > referenceRoots = rootsMatrix.get( 0 ); + Classification< BranchSpotTree > classification = classifyLineageTrees( referenceRoots.getRight(), distances ); List< Pair< String, Integer > > tagsAndColors = createTagsAndColors( classification ); Function< BranchSpotTree, BranchSpot > branchSpotProvider = BranchSpotTree::getBranchSpot; createdTagSetName = applyClassification( classification, tagsAndColors, referenceModel, branchSpotProvider ); - if ( addTagSetToExternalProjects && roots.size() > 1 ) - classification = classifyUsingExternalProjects( roots, classification, distances, tagsAndColors ); + if ( addTagSetToExternalProjects && rootsMatrix.size() > 1 ) + classifyExternalProjects( rootsMatrix, distances, tagsAndColors ); if ( showDendrogram ) showDendrogram( classification ); } @@ -179,58 +179,57 @@ private String runClassification() return createdTagSetName; } - private Classification< BranchSpotTree > classifyUsingExternalProjects( final List< List< BranchSpotTree > > roots, - final Classification< BranchSpotTree > classification, final double[][] distances, - final List< Pair< String, Integer > > tagsAndColors ) + private void classifyExternalProjects( final List< Pair< ProjectSession, List< BranchSpotTree > > > rootsMatrix, + final double[][] distances, final List< Pair< String, Integer > > tagsAndColors ) { Function< BranchSpotTree, BranchSpot > branchSpotProvider; - Classification< BranchSpotTree > averageClassification = classification; - for ( int i = 1; i < roots.size(); i++ ) + for ( int i = 1; i < rootsMatrix.size(); i++ ) // NB: start at 1 to skip reference project { - averageClassification = classifyLineageTrees( roots.get( i ), distances ); - for ( Map.Entry< File, ProjectSession > externalProject : externalProjects.getProjects().entrySet() ) + Pair< ProjectSession, List< BranchSpotTree > > sessionAndRoots = rootsMatrix.get( i ); + Classification< BranchSpotTree > classification = classifyLineageTrees( sessionAndRoots.getRight(), distances ); + ProjectSession projectSession = sessionAndRoots.getLeft(); + ProjectModel projectModel = projectSession.getProjectModel(); + Model model = projectModel.getModel(); + File file = projectSession.getFile(); + branchSpotProvider = branchSpotTree -> model.getBranchGraph().vertices().stream() + .filter( ( branchSpot -> branchSpot.getFirstLabel().equals( branchSpotTree.getName() ) ) ) + .findFirst().orElse( null ); + applyClassification( classification, tagsAndColors, model, branchSpotProvider ); + try { - ProjectModel projectModel = externalProject.getValue().getProjectModel(); - File file = externalProject.getKey(); - branchSpotProvider = branchSpotTree -> projectModel.getModel().getBranchGraph().vertices().stream() - .filter( ( branchSpot -> branchSpot.getFirstLabel().equals( branchSpotTree.getName() ) ) ) - .findFirst().orElse( null ); - applyClassification( classification, tagsAndColors, projectModel.getModel(), branchSpotProvider ); - try - { - ProjectSaver.saveProject( file, projectModel ); - } - catch ( IOException e ) - { - logger.warn( "Could not save tag set of project {} to file {}. Message: {}", projectModel.getProjectName(), - file.getAbsolutePath(), e.getMessage() ); - } + ProjectSaver.saveProject( file, projectModel ); + } + catch ( IOException e ) + { + logger.warn( "Could not save tag set of project {} to file {}. Message: {}", projectModel.getProjectName(), + file.getAbsolutePath(), e.getMessage() ); } } - return averageClassification; } - private Pair< List< List< BranchSpotTree > >, double[][] > getRootsAndDistanceMatrix() + private Pair< List< Pair< ProjectSession, List< BranchSpotTree > > >, double[][] > getRootsAndDistanceMatrix() { List< BranchSpotTree > roots = getRoots(); if ( externalProjects.isEmpty() ) { double[][] distances = ClassificationUtils.getDistanceMatrix( roots, similarityMeasure ); - return Pair.of( Collections.singletonList( roots ), distances ); + return Pair.of( Collections.singletonList( Pair.of( null, roots ) ), distances ); } List< String > commonRootNames = findCommonRootNames(); - List< List< BranchSpotTree > > treeMatrix = new ArrayList<>(); + List< Pair< ProjectSession, List< BranchSpotTree > > > projectSessionsAndRootLists = new ArrayList<>(); keepCommonRootsAndSort( roots, commonRootNames ); - treeMatrix.add( roots ); - for ( ProjectModel projectModel : externalProjects.getProjectModels() ) + projectSessionsAndRootLists.add( Pair.of( null, roots ) ); + for ( ProjectSession projectSession : externalProjects.getProjectSessions() ) { - List< BranchSpotTree > externalRoots = getRoots( projectModel ); + List< BranchSpotTree > externalRoots = getRoots( projectSession.getProjectModel() ); keepCommonRootsAndSort( externalRoots, commonRootNames ); - treeMatrix.add( externalRoots ); + projectSessionsAndRootLists.add( Pair.of( projectSession, externalRoots ) ); } - return Pair.of( treeMatrix, ClassificationUtils.getAverageDistanceMatrix( treeMatrix, similarityMeasure ) ); + List< List< BranchSpotTree > > treeMatrix = + projectSessionsAndRootLists.stream().map( Pair::getRight ).collect( Collectors.toList() ); + return Pair.of( projectSessionsAndRootLists, ClassificationUtils.getAverageDistanceMatrix( treeMatrix, similarityMeasure ) ); } private List< String > findCommonRootNames() diff --git a/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java b/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java index d9098075e..7fc48be1d 100644 --- a/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java +++ b/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java @@ -10,6 +10,7 @@ import java.io.File; import java.io.IOException; import java.lang.invoke.MethodHandles; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -51,12 +52,12 @@ public Collection< ProjectModel > getProjectModels() } /** - * Gets a mapping of external project files to their project sessions - * @return the mapping + * Gets a list of {@link ProjectSession} + * @return the list */ - public Map< File, ProjectSession > getProjects() + public List< ProjectSession > getProjectSessions() { - return projectSessions; + return new ArrayList<>( projectSessions.values() ); } /** From eefe2fe57bbea433177bf84947b116e6f1ffe657 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 24 Jul 2024 11:39:24 +0200 Subject: [PATCH 74/81] Replace ProjectSession and MastodonProjectService by simpler ClassifiableProject --- .../ClassifyLineagesController.java | 51 +++++++++-------- .../ClassifyLineagesPlugin.java | 7 +-- .../multiproject/ClassifiableProject.java | 41 ++++++++++++++ .../multiproject/ExternalProjects.java | 55 +++++++++---------- .../ClassifyLineagesControllerTest.java | 7 +-- .../mamut/util/MastodonProjectService.java | 0 .../mastodon/mamut/util/ProjectSession.java | 0 7 files changed, 96 insertions(+), 65 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/classification/multiproject/ClassifiableProject.java rename src/{main => test}/java/org/mastodon/mamut/util/MastodonProjectService.java (100%) rename src/{main => test}/java/org/mastodon/mamut/util/ProjectSession.java (100%) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 58ec4fb80..bba9f9901 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -34,6 +34,7 @@ import org.mastodon.mamut.ProjectModel; import org.mastodon.mamut.classification.config.ClusteringMethod; import org.mastodon.mamut.classification.config.SimilarityMeasure; +import org.mastodon.mamut.classification.multiproject.ClassifiableProject; import org.mastodon.mamut.classification.multiproject.ExternalProjects; import org.mastodon.mamut.classification.util.Classification; import org.mastodon.mamut.classification.config.CropCriteria; @@ -48,10 +49,9 @@ import org.mastodon.mamut.classification.treesimilarity.tree.BranchSpotTree; import org.mastodon.mamut.classification.treesimilarity.tree.TreeUtils; import org.mastodon.mamut.util.LineageTreeUtils; -import org.mastodon.mamut.util.MastodonProjectService; -import org.mastodon.mamut.util.ProjectSession; import org.mastodon.model.tag.TagSetStructure; import org.mastodon.util.TagSetUtils; +import org.scijava.Context; import org.scijava.prefs.PrefService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,6 +64,7 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.StringJoiner; @@ -121,15 +122,14 @@ public ClassifyLineagesController( final ProjectModel referenceProjectModel ) * Create a new controller for classifying lineage trees. * @param referenceProjectModel the reference project model * @param prefs the preference service - * @param projectService the project service + * @param context the SciJava context */ - public ClassifyLineagesController( final ProjectModel referenceProjectModel, final PrefService prefs, - final MastodonProjectService projectService ) + public ClassifyLineagesController( final ProjectModel referenceProjectModel, final PrefService prefs, final Context context ) { this.referenceProjectModel = referenceProjectModel; this.referenceModel = referenceProjectModel.getModel(); this.prefs = prefs; - this.externalProjects = new ExternalProjects( projectService ); + this.externalProjects = new ExternalProjects( context ); } /** @@ -159,11 +159,11 @@ private String runClassification() String createdTagSetName; try { - Pair< List< Pair< ProjectSession, List< BranchSpotTree > > >, double[][] > rootsAndDistances = getRootsAndDistanceMatrix(); - List< Pair< ProjectSession, List< BranchSpotTree > > > rootsMatrix = rootsAndDistances.getLeft(); + Pair< List< ClassifiableProject >, double[][] > rootsAndDistances = getRootsAndDistanceMatrix(); + List< ClassifiableProject > rootsMatrix = rootsAndDistances.getLeft(); double[][] distances = rootsAndDistances.getRight(); - Pair< ProjectSession, List< BranchSpotTree > > referenceRoots = rootsMatrix.get( 0 ); - Classification< BranchSpotTree > classification = classifyLineageTrees( referenceRoots.getRight(), distances ); + ClassifiableProject referenceProject = rootsMatrix.get( 0 ); + Classification< BranchSpotTree > classification = classifyLineageTrees( referenceProject.getTrees(), distances ); List< Pair< String, Integer > > tagsAndColors = createTagsAndColors( classification ); Function< BranchSpotTree, BranchSpot > branchSpotProvider = BranchSpotTree::getBranchSpot; createdTagSetName = applyClassification( classification, tagsAndColors, referenceModel, branchSpotProvider ); @@ -179,18 +179,17 @@ private String runClassification() return createdTagSetName; } - private void classifyExternalProjects( final List< Pair< ProjectSession, List< BranchSpotTree > > > rootsMatrix, + private void classifyExternalProjects( final List< ClassifiableProject > rootsMatrix, final double[][] distances, final List< Pair< String, Integer > > tagsAndColors ) { Function< BranchSpotTree, BranchSpot > branchSpotProvider; for ( int i = 1; i < rootsMatrix.size(); i++ ) // NB: start at 1 to skip reference project { - Pair< ProjectSession, List< BranchSpotTree > > sessionAndRoots = rootsMatrix.get( i ); - Classification< BranchSpotTree > classification = classifyLineageTrees( sessionAndRoots.getRight(), distances ); - ProjectSession projectSession = sessionAndRoots.getLeft(); - ProjectModel projectModel = projectSession.getProjectModel(); + ClassifiableProject project = rootsMatrix.get( i ); + Classification< BranchSpotTree > classification = classifyLineageTrees( project.getTrees(), distances ); + ProjectModel projectModel = project.getProjectModel(); Model model = projectModel.getModel(); - File file = projectSession.getFile(); + File file = project.getFile(); branchSpotProvider = branchSpotTree -> model.getBranchGraph().vertices().stream() .filter( ( branchSpot -> branchSpot.getFirstLabel().equals( branchSpotTree.getName() ) ) ) .findFirst().orElse( null ); @@ -207,29 +206,29 @@ private void classifyExternalProjects( final List< Pair< ProjectSession, List< B } } - private Pair< List< Pair< ProjectSession, List< BranchSpotTree > > >, double[][] > getRootsAndDistanceMatrix() + private Pair< List< ClassifiableProject >, double[][] > getRootsAndDistanceMatrix() { List< BranchSpotTree > roots = getRoots(); + ClassifiableProject referenceProject = new ClassifiableProject( null, referenceProjectModel, roots ); if ( externalProjects.isEmpty() ) { double[][] distances = ClassificationUtils.getDistanceMatrix( roots, similarityMeasure ); - return Pair.of( Collections.singletonList( Pair.of( null, roots ) ), distances ); + return Pair.of( Collections.singletonList( referenceProject ), distances ); } List< String > commonRootNames = findCommonRootNames(); - List< Pair< ProjectSession, List< BranchSpotTree > > > projectSessionsAndRootLists = new ArrayList<>(); + List< ClassifiableProject > projects = new ArrayList<>(); keepCommonRootsAndSort( roots, commonRootNames ); - projectSessionsAndRootLists.add( Pair.of( null, roots ) ); - for ( ProjectSession projectSession : externalProjects.getProjectSessions() ) + projects.add( referenceProject ); + for ( Map.Entry< File, ProjectModel > project : externalProjects.getProjects() ) { - List< BranchSpotTree > externalRoots = getRoots( projectSession.getProjectModel() ); + List< BranchSpotTree > externalRoots = getRoots( project.getValue() ); keepCommonRootsAndSort( externalRoots, commonRootNames ); - projectSessionsAndRootLists.add( Pair.of( projectSession, externalRoots ) ); + projects.add( new ClassifiableProject( project.getKey(), project.getValue(), externalRoots ) ); } - List< List< BranchSpotTree > > treeMatrix = - projectSessionsAndRootLists.stream().map( Pair::getRight ).collect( Collectors.toList() ); - return Pair.of( projectSessionsAndRootLists, ClassificationUtils.getAverageDistanceMatrix( treeMatrix, similarityMeasure ) ); + List< List< BranchSpotTree > > treeMatrix = projects.stream().map( ClassifiableProject::getTrees ).collect( Collectors.toList() ); + return Pair.of( projects, ClassificationUtils.getAverageDistanceMatrix( treeMatrix, similarityMeasure ) ); } private List< String > findCommonRootNames() diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java index aeba996e5..fe6e11c77 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesPlugin.java @@ -33,7 +33,6 @@ import org.mastodon.mamut.ProjectModel; import org.mastodon.mamut.classification.ui.ClassifyLineagesCommand; import org.mastodon.mamut.plugin.MamutPlugin; -import org.mastodon.mamut.util.MastodonProjectService; import org.mastodon.ui.keymap.KeyConfigContexts; import org.scijava.AbstractContextual; import org.scijava.command.CommandService; @@ -72,10 +71,6 @@ public class ClassifyLineagesPlugin extends AbstractContextual implements MamutP @Parameter private PrefService prefService; - @SuppressWarnings( "unused" ) - @Parameter - private MastodonProjectService projectService; - @SuppressWarnings("unused") public ClassifyLineagesPlugin() { @@ -102,7 +97,7 @@ public void installGlobalActions( Actions actions ) private void classifyLineageTrees() { - ClassifyLineagesController controller = new ClassifyLineagesController( projectModel, prefService, projectService ); + ClassifyLineagesController controller = new ClassifyLineagesController( projectModel, prefService, getContext() ); commandService.run( ClassifyLineagesCommand.class, true, "controller", controller ); } diff --git a/src/main/java/org/mastodon/mamut/classification/multiproject/ClassifiableProject.java b/src/main/java/org/mastodon/mamut/classification/multiproject/ClassifiableProject.java new file mode 100644 index 000000000..d8f8d9bfe --- /dev/null +++ b/src/main/java/org/mastodon/mamut/classification/multiproject/ClassifiableProject.java @@ -0,0 +1,41 @@ +package org.mastodon.mamut.classification.multiproject; + +import org.mastodon.mamut.ProjectModel; +import org.mastodon.mamut.classification.treesimilarity.tree.BranchSpotTree; + +import java.io.File; +import java.util.List; + +/** + * Small helper class to hold a project file, its {@link ProjectModel} and the trees that are to be classified. + */ +public class ClassifiableProject +{ + private final File file; + + private final ProjectModel projectModel; + + private final List< BranchSpotTree > trees; + + public ClassifiableProject( final File file, final ProjectModel projectModel, final List< BranchSpotTree > trees ) + { + this.file = file; + this.projectModel = projectModel; + this.trees = trees; + } + + public File getFile() + { + return file; + } + + public ProjectModel getProjectModel() + { + return projectModel; + } + + public List< BranchSpotTree > getTrees() + { + return trees; + } +} diff --git a/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java b/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java index 7fc48be1d..9c15976ec 100644 --- a/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java +++ b/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java @@ -2,15 +2,14 @@ import mpicbg.spim.data.SpimDataException; import org.mastodon.mamut.ProjectModel; -import org.mastodon.mamut.util.MastodonProjectService; -import org.mastodon.mamut.util.ProjectSession; +import org.mastodon.mamut.io.ProjectLoader; +import org.scijava.Context; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.lang.invoke.MethodHandles; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -18,7 +17,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.Set; /** * A collection of external projects. @@ -29,16 +28,16 @@ public class ExternalProjects implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() ); - private final MastodonProjectService projectService; + private final Context context; - private final Map< File, ProjectSession > projectSessions; + private final Map< File, ProjectModel > projects; private final Map< File, String > failingProjects; - public ExternalProjects( final MastodonProjectService projectService ) + public ExternalProjects( final Context context ) { - this.projectService = projectService; - this.projectSessions = new HashMap<>(); + this.context = context; + this.projects = new HashMap<>(); this.failingProjects = new HashMap<>(); } @@ -48,16 +47,12 @@ public ExternalProjects( final MastodonProjectService projectService ) */ public Collection< ProjectModel > getProjectModels() { - return projectSessions.values().stream().map( ProjectSession::getProjectModel ).collect( Collectors.toList() ); + return projects.values(); } - /** - * Gets a list of {@link ProjectSession} - * @return the list - */ - public List< ProjectSession > getProjectSessions() + public Set< Map.Entry< File, ProjectModel > > getProjects() { - return new ArrayList<>( projectSessions.values() ); + return projects.entrySet(); } /** @@ -66,7 +61,7 @@ public List< ProjectSession > getProjectSessions() */ public int size() { - return projectSessions.size(); + return projects.size(); } /** @@ -75,7 +70,7 @@ public int size() */ public boolean isEmpty() { - return projectSessions.isEmpty(); + return projects.isEmpty(); } /** @@ -97,7 +92,7 @@ public void setProjects( final File[] projects ) removeProjects( projectsList ); cleanUpFailingProjects( projectsList ); addProjects( projects ); - logger.debug( "Set {} projects. Active project sessions: {}.", projectsList.size(), projectService.activeSessions() ); + logger.debug( "Set {} projects.", projectsList.size() ); } /** @@ -105,15 +100,15 @@ public void setProjects( final File[] projects ) */ private void removeProjects( final List< File > projectsList ) { - Iterator< Map.Entry< File, ProjectSession > > iterator = projectSessions.entrySet().iterator(); + Iterator< Map.Entry< File, ProjectModel > > iterator = projects.entrySet().iterator(); while ( iterator.hasNext() ) { - Map.Entry< File, ProjectSession > entry = iterator.next(); + Map.Entry< File, ProjectModel > entry = iterator.next(); File file = entry.getKey(); if ( !projectsList.contains( file ) ) { - ProjectSession projectSession = entry.getValue(); - projectSession.close(); + ProjectModel projectModel = entry.getValue(); + projectModel.close(); iterator.remove(); } } @@ -141,11 +136,16 @@ private void addProjects( final File[] files ) return; for ( File file : files ) { - if ( !projectSessions.containsKey( file ) ) + if ( !projects.containsKey( file ) ) { try { - projectSessions.put( file, projectService.createSession( file ) ); + // load project model from file + long start = System.currentTimeMillis(); + ProjectModel projectModel = + ProjectLoader.open( file.getAbsolutePath(), context, false, true ); + logger.debug( "Loaded project from file: {} in {} ms", file.getAbsolutePath(), System.currentTimeMillis() - start ); + projects.put( file, projectModel ); failingProjects.remove( file ); } catch ( SpimDataException | IOException | RuntimeException e ) @@ -161,8 +161,7 @@ private void addProjects( final File[] files ) @Override public void close() { - for ( ProjectSession projectSession : projectSessions.values() ) - projectSession.close(); - logger.debug( "Remaining active project sessions: {}.", projectService.activeSessions() ); + for ( ProjectModel projectModel : projects.values() ) + projectModel.close(); } } diff --git a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java index 54a719fd3..39bbb3997 100644 --- a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java +++ b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java @@ -48,7 +48,6 @@ import org.mastodon.mamut.io.importer.labelimage.util.DemoUtils; import org.mastodon.mamut.model.Model; import org.mastodon.mamut.model.Spot; -import org.mastodon.mamut.util.MastodonProjectService; import org.mastodon.model.tag.TagSetStructure; import org.mastodon.util.TagSetUtils; import org.mastodon.views.bdv.SharedBigDataViewerData; @@ -132,8 +131,7 @@ void testCreateTagSetWithExternalProjects() throws IOException, SpimDataExceptio String tagSetName = "Test Tag Set"; TagSetUtils.addNewTagSetToModel( model1, tagSetName, Collections.emptyList() ); PrefService prefService = context.getService( PrefService.class ); - MastodonProjectService projectService = context.getService( MastodonProjectService.class ); - ClassifyLineagesController controller = new ClassifyLineagesController( projectModel1, prefService, projectService ); + ClassifyLineagesController controller = new ClassifyLineagesController( projectModel1, prefService, context ); controller.setInputParams( CropCriteria.TIMEPOINT, 0, 100, 1 ); controller.setComputeParams( SimilarityMeasure.NORMALIZED_ZHANG_DIFFERENCE, ClusteringMethod.AVERAGE_LINKAGE, 3 ); controller.setShowDendrogram( false ); @@ -201,8 +199,7 @@ void testSetExternalProjects() throws IOException File[] files = { file1, file2 }; PrefService prefService = context.getService( PrefService.class ); - MastodonProjectService projectService = context.getService( MastodonProjectService.class ); - ClassifyLineagesController controller = new ClassifyLineagesController( projectModel, prefService, projectService ); + ClassifyLineagesController controller = new ClassifyLineagesController( projectModel, prefService, context ); controller.setInputParams( CropCriteria.TIMEPOINT, 0, 100, 1 ); controller.setComputeParams( SimilarityMeasure.NORMALIZED_ZHANG_DIFFERENCE, ClusteringMethod.AVERAGE_LINKAGE, 0 ); controller.setExternalProjects( files, false ); diff --git a/src/main/java/org/mastodon/mamut/util/MastodonProjectService.java b/src/test/java/org/mastodon/mamut/util/MastodonProjectService.java similarity index 100% rename from src/main/java/org/mastodon/mamut/util/MastodonProjectService.java rename to src/test/java/org/mastodon/mamut/util/MastodonProjectService.java diff --git a/src/main/java/org/mastodon/mamut/util/ProjectSession.java b/src/test/java/org/mastodon/mamut/util/ProjectSession.java similarity index 100% rename from src/main/java/org/mastodon/mamut/util/ProjectSession.java rename to src/test/java/org/mastodon/mamut/util/ProjectSession.java From ff76f59b9815d0e086d6bb9e4a890f7e197b9859 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 24 Jul 2024 11:43:58 +0200 Subject: [PATCH 75/81] Remove Dendrogram test from ClassifyLineagesControllerTest --- .../mamut/classification/ClassifyLineagesControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java index 39bbb3997..ca00e82aa 100644 --- a/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java +++ b/src/test/java/org/mastodon/mamut/classification/ClassifyLineagesControllerTest.java @@ -86,7 +86,7 @@ void testCreateTagSet() throws SpimDataException, IOException ClassifyLineagesController controller = new ClassifyLineagesController( projectModel ); controller.setInputParams( CropCriteria.TIMEPOINT, 0, 100, 1 ); controller.setComputeParams( SimilarityMeasure.NORMALIZED_ZHANG_DIFFERENCE, ClusteringMethod.AVERAGE_LINKAGE, 3 ); - controller.setShowDendrogram( true ); // NB: increase test coverage + controller.setShowDendrogram( false ); controller.createTagSet(); List< TagSetStructure.TagSet > tagSets = model.getTagSetModel().getTagSetStructure().getTagSets(); From de2a8dde3b4f6d6827372ce4d2d355ba268c9955 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 24 Jul 2024 12:04:29 +0200 Subject: [PATCH 76/81] Add name of current project to ClassifyLineagesCommand --- .../mamut/classification/ClassifyLineagesController.java | 5 +++++ .../mamut/classification/ui/ClassifyLineagesCommand.java | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index bba9f9901..e718f42a8 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -505,4 +505,9 @@ public void close() { externalProjects.close(); } + + public String getProjectName() + { + return referenceProjectModel.getProjectName(); + } } diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index e96d07399..bc5ad32db 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -98,6 +98,9 @@ public class ClassifyLineagesCommand extends DynamicCommand @Parameter( label = "Linkage strategy for
        hierarchical clustering", initializer = "initClusteringMethodChoices", callback = "update" ) private String clusteringMethod = ClusteringMethod.AVERAGE_LINKAGE.getName(); + @Parameter( visibility = ItemVisibility.MESSAGE, required = false, persist = false, label = "Current project", initializer = "initProjectName" ) + private String currentProjectName; + @SuppressWarnings( "unused" ) @Parameter( label = "List of projects
        (Drag & Drop supported)", style = "files,extensions:mastodon", callback = "update" ) private File[] projects = new File[ 0 ]; @@ -209,6 +212,12 @@ private void initClusteringMethodChoices() getInfo().getMutableInput( "clusteringMethod", String.class ).setChoices( enumNamesAsList( ClusteringMethod.values() ) ); } + @SuppressWarnings( "unused" ) + private void initProjectName() + { + this.currentProjectName = controller.getProjectName(); + } + static List< String > enumNamesAsList( final HasName[] values ) { return Arrays.stream( values ).map( HasName::getName ).collect( Collectors.toList() ); From 180c3f61db4e5734fdbceccbd3881f7bdb46e2d8 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 24 Jul 2024 12:04:46 +0200 Subject: [PATCH 77/81] Change label of further projects parameter --- .../mamut/classification/ui/ClassifyLineagesCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java index bc5ad32db..8eacb6a25 100644 --- a/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java +++ b/src/main/java/org/mastodon/mamut/classification/ui/ClassifyLineagesCommand.java @@ -102,7 +102,7 @@ public class ClassifyLineagesCommand extends DynamicCommand private String currentProjectName; @SuppressWarnings( "unused" ) - @Parameter( label = "List of projects
        (Drag & Drop supported)", style = "files,extensions:mastodon", callback = "update" ) + @Parameter( label = "List of
        further projects
        (Drag & Drop supported)", style = "files,extensions:mastodon", callback = "update" ) private File[] projects = new File[ 0 ]; @SuppressWarnings( "unused" ) From 52a4292dd95963e707f61aeff2d2179d07994c5b Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 24 Jul 2024 12:05:07 +0200 Subject: [PATCH 78/81] Fix messages for crop start and end --- .../mamut/classification/ClassifyLineagesController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index e718f42a8..89fae861c 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -483,7 +483,7 @@ private List< String > checkNumberOfSpots() } catch ( NoSuchElementException e ) { - String message = projectName + ", crop start: " + e.getMessage(); + String message = projectName + "Crop start: " + e.getMessage(); feedback.add( message ); logger.debug( message ); } @@ -493,7 +493,7 @@ private List< String > checkNumberOfSpots() } catch ( NoSuchElementException e ) { - String message = projectName + ", crop end: " + e.getMessage(); + String message = projectName + "Crop end: " + e.getMessage(); feedback.add( message ); logger.debug( message ); } From 4a3a94bd732824c8778ac7030fd49f49082ec9a0 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 24 Jul 2024 12:16:39 +0200 Subject: [PATCH 79/81] Ensure that only distinct projects are part of the analysis --- .../mamut/classification/multiproject/ExternalProjects.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java b/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java index 9c15976ec..30a805d31 100644 --- a/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java +++ b/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; /** * A collection of external projects. @@ -89,6 +90,7 @@ public Collection< String > getFailingProjectMessages() public void setProjects( final File[] projects ) { List< File > projectsList = projects == null ? Collections.emptyList() : Arrays.asList( projects ); + projectsList = projectsList.stream().distinct().collect( Collectors.toList() ); // remove duplicates removeProjects( projectsList ); cleanUpFailingProjects( projectsList ); addProjects( projects ); From 96b09e514db3f071ab234ee94632f13ef0f19478 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 24 Jul 2024 12:17:51 +0200 Subject: [PATCH 80/81] Ensure that current project is not added again as further project --- .../mamut/classification/ClassifyLineagesController.java | 2 +- .../mamut/classification/multiproject/ExternalProjects.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 89fae861c..94d8bd732 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -419,7 +419,7 @@ public void setShowDendrogram( final boolean showDendrogram ) public void setExternalProjects( final File[] projects, final boolean addTagSetToExternalProjects ) { this.addTagSetToExternalProjects = addTagSetToExternalProjects; - externalProjects.setProjects( projects ); + externalProjects.setProjects( projects, referenceProjectModel.getProject().getProjectRoot() ); } public List< String > getFeedback() diff --git a/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java b/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java index 30a805d31..ba32620f2 100644 --- a/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java +++ b/src/main/java/org/mastodon/mamut/classification/multiproject/ExternalProjects.java @@ -87,14 +87,15 @@ public Collection< String > getFailingProjectMessages() * Set the external projects * @param projects the external projects */ - public void setProjects( final File[] projects ) + public void setProjects( final File[] projects, final File currentProject ) { List< File > projectsList = projects == null ? Collections.emptyList() : Arrays.asList( projects ); projectsList = projectsList.stream().distinct().collect( Collectors.toList() ); // remove duplicates + projectsList.remove( currentProject ); // remove current project removeProjects( projectsList ); cleanUpFailingProjects( projectsList ); addProjects( projects ); - logger.debug( "Set {} projects.", projectsList.size() ); + logger.debug( "Set {} external projects.", projectsList.size() ); } /** From cdaf1f7968b460c78f0c4b79bde39562a53546d3 Mon Sep 17 00:00:00 2001 From: Stefan Hahmann Date: Wed, 24 Jul 2024 12:59:33 +0200 Subject: [PATCH 81/81] Fix unit test bug --- .../mamut/classification/ClassifyLineagesController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java index 94d8bd732..0a0429ace 100644 --- a/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java +++ b/src/main/java/org/mastodon/mamut/classification/ClassifyLineagesController.java @@ -41,6 +41,7 @@ import org.mastodon.mamut.classification.ui.DendrogramView; import org.mastodon.mamut.classification.util.ClassificationUtils; import org.mastodon.mamut.io.ProjectSaver; +import org.mastodon.mamut.io.project.MamutProject; import org.mastodon.mamut.model.Link; import org.mastodon.mamut.model.Model; import org.mastodon.mamut.model.ModelGraph; @@ -419,7 +420,8 @@ public void setShowDendrogram( final boolean showDendrogram ) public void setExternalProjects( final File[] projects, final boolean addTagSetToExternalProjects ) { this.addTagSetToExternalProjects = addTagSetToExternalProjects; - externalProjects.setProjects( projects, referenceProjectModel.getProject().getProjectRoot() ); + MamutProject mamutProject = referenceProjectModel.getProject(); + externalProjects.setProjects( projects, mamutProject == null ? null : mamutProject.getProjectRoot() ); } public List< String > getFeedback()