Skip to content

Commit

Permalink
Merge pull request #127 from mastodon-sc/fix-dendrogram-view
Browse files Browse the repository at this point in the history
Fix dendrogram view
  • Loading branch information
stefanhahmann authored Dec 5, 2024
2 parents c7c21ef + 32d8d5d commit c809e53
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import org.mastodon.mamut.ProjectModel;
import org.mastodon.mamut.clustering.config.ClusteringMethod;
import org.mastodon.mamut.clustering.config.SimilarityMeasure;
import org.mastodon.mamut.clustering.multiproject.ClassifiableProject;
import org.mastodon.mamut.clustering.multiproject.ClusterableProject;
import org.mastodon.mamut.clustering.multiproject.ExternalProjects;
import org.mastodon.mamut.clustering.util.HierarchicalClusteringResult;
import org.mastodon.mamut.clustering.config.CropCriteria;
Expand Down Expand Up @@ -145,31 +145,32 @@ public String createTagSet()
try
{
running = true;
return runClassification();
return runClustering();
}
finally
{
running = false;
}
}

private String runClassification()
private String runClustering()
{
referenceProjectModel.getBranchGraphSync().sync();
ReentrantReadWriteLock.ReadLock lock = referenceModel.getGraph().getLock().readLock();
lock.lock();
String createdTagSetName;
try
{
Pair< List< ClassifiableProject >, double[][] > rootsAndDistances = getRootsAndDistanceMatrix();
List< ClassifiableProject > rootsMatrix = rootsAndDistances.getLeft();
Pair< List< ClusterableProject >, double[][] > rootsAndDistances = getRootsAndDistanceMatrix();
List< ClusterableProject > rootsMatrix = rootsAndDistances.getLeft();
double[][] distances = rootsAndDistances.getRight();
ClassifiableProject referenceProject = rootsMatrix.get( 0 );
ClusterableProject referenceProject = rootsMatrix.get( 0 );
HierarchicalClusteringResult< BranchSpotTree > hierarchicalClusteringResult =
classifyLineageTrees( referenceProject.getTrees(), distances );
clusterLineageTrees( referenceProject.getTrees(), distances );
Function< BranchSpotTree, BranchSpot > branchSpotProvider = BranchSpotTree::getBranchSpot;
createdTagSetName = applyClassification( hierarchicalClusteringResult, referenceModel, branchSpotProvider );
createdTagSetName = applyTagSet( hierarchicalClusteringResult, referenceModel, branchSpotProvider );
if ( addTagSetToExternalProjects && rootsMatrix.size() > 1 )
classifyExternalProjects( rootsMatrix, distances );
clusterExternalProjects( rootsMatrix, distances );
if ( showDendrogram )
showDendrogram( hierarchicalClusteringResult );
}
Expand All @@ -180,21 +181,21 @@ private String runClassification()
return createdTagSetName;
}

private void classifyExternalProjects( final List< ClassifiableProject > rootsMatrix, final double[][] distances )
private void clusterExternalProjects( final List< ClusterableProject > rootsMatrix, final double[][] distances )
{
Function< BranchSpotTree, BranchSpot > branchSpotProvider;
for ( int i = 1; i < rootsMatrix.size(); i++ ) // NB: start at 1 to skip reference project
{
ClassifiableProject project = rootsMatrix.get( i );
ClusterableProject project = rootsMatrix.get( i );
HierarchicalClusteringResult< BranchSpotTree > hierarchicalClusteringResult =
classifyLineageTrees( project.getTrees(), distances );
clusterLineageTrees( project.getTrees(), distances );
ProjectModel projectModel = project.getProjectModel();
Model model = projectModel.getModel();
File file = project.getFile();
branchSpotProvider = branchSpotTree -> model.getBranchGraph().vertices().stream()
.filter( ( branchSpot -> branchSpot.getFirstLabel().equals( branchSpotTree.getName() ) ) )
.findFirst().orElse( null );
applyClassification( hierarchicalClusteringResult, model, branchSpotProvider );
applyTagSet( hierarchicalClusteringResult, model, branchSpotProvider );
try
{
ProjectSaver.saveProject( file, projectModel );
Expand All @@ -207,28 +208,28 @@ private void classifyExternalProjects( final List< ClassifiableProject > rootsMa
}
}

private Pair< List< ClassifiableProject >, double[][] > getRootsAndDistanceMatrix()
private Pair< List< ClusterableProject >, double[][] > getRootsAndDistanceMatrix()
{
List< BranchSpotTree > roots = getRoots();
ClassifiableProject referenceProject = new ClassifiableProject( null, referenceProjectModel, roots );
ClusterableProject referenceProject = new ClusterableProject( null, referenceProjectModel, roots );
if ( externalProjects.isEmpty() )
{
double[][] distances = HierarchicalClusteringUtils.getDistanceMatrix( roots, similarityMeasure );
return Pair.of( Collections.singletonList( referenceProject ), distances );
}

List< String > commonRootNames = findCommonRootNames();
List< ClassifiableProject > projects = new ArrayList<>();
List< ClusterableProject > projects = new ArrayList<>();

keepCommonRootsAndSort( roots, commonRootNames );
projects.add( referenceProject );
for ( Map.Entry< File, ProjectModel > project : externalProjects.getProjects() )
{
List< BranchSpotTree > externalRoots = getRoots( project.getValue() );
keepCommonRootsAndSort( externalRoots, commonRootNames );
projects.add( new ClassifiableProject( project.getKey(), project.getValue(), externalRoots ) );
projects.add( new ClusterableProject( project.getKey(), project.getValue(), externalRoots ) );
}
List< List< BranchSpotTree > > treeMatrix = projects.stream().map( ClassifiableProject::getTrees ).collect( Collectors.toList() );
List< List< BranchSpotTree > > treeMatrix = projects.stream().map( ClusterableProject::getTrees ).collect( Collectors.toList() );
return Pair.of( projects, HierarchicalClusteringUtils.getAverageDistanceMatrix( treeMatrix, similarityMeasure ) );
}

Expand Down Expand Up @@ -286,10 +287,10 @@ private void showDendrogram( final HierarchicalClusteringResult< BranchSpotTree
String header = "<html><body>Dendrogram of hierarchical clustering of lineages<br>" + getParameters() + "</body></html>";
DendrogramView< BranchSpotTree > dendrogramView =
new DendrogramView<>( hierarchicalClusteringResult, header, referenceModel, prefs, referenceProjectModel.getProjectName() );
dendrogramView.show();
dendrogramView.setVisible( true );
}

private HierarchicalClusteringResult< BranchSpotTree > classifyLineageTrees( final List< BranchSpotTree > roots,
private HierarchicalClusteringResult< BranchSpotTree > clusterLineageTrees( final List< BranchSpotTree > roots,
final double[][] distances )
{
if ( roots.size() != distances.length )
Expand All @@ -306,9 +307,8 @@ private HierarchicalClusteringResult< BranchSpotTree > classifyLineageTrees( fin
return result;
}

private String applyClassification( final HierarchicalClusteringResult< BranchSpotTree > hierarchicalClusteringResult,
final Model model,
final Function< BranchSpotTree, BranchSpot > branchSpotProvider )
private String applyTagSet( final HierarchicalClusteringResult< BranchSpotTree > hierarchicalClusteringResult,
final Model model, final Function< BranchSpotTree, BranchSpot > branchSpotProvider )
{
String tagSetName = getTagSetName();
List< HierarchicalClusteringResult.Group< BranchSpotTree > > groups = hierarchicalClusteringResult.getGroups();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@
import java.util.List;

/**
* Small helper class to hold a project file, its {@link ProjectModel} and the trees that are to be classified.
* Small helper class to hold a project file, its {@link ProjectModel} and the trees that are to be clustered.
*/
public class ClassifiableProject
public class ClusterableProject
{
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 )
public ClusterableProject( final File file, final ProjectModel projectModel, final List< BranchSpotTree > trees )
{
this.file = file;
this.projectModel = projectModel;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,16 @@ public void setParams( final boolean includeName, final boolean includeTag, fina
private String getTagLabel()
{
Spot ref = model.getGraph().vertexRef();
String tagLabel = LegacyTagSetUtils.getTagLabel( model, branchSpot, tagSet, ref );
String tagLabel = null;
try
{
// TODO: this try-catch block is a workaround. It will not be needed anymore after mastodon-core beta-34
tagLabel = LegacyTagSetUtils.getTagLabel( model, branchSpot, tagSet, ref );
}
catch ( NullPointerException e )
{
// happens, when the branchSpot is not in the model anymore
}
model.getGraph().releaseRef( ref );
if ( tagLabel == null )
tagLabel = "";
Expand Down
97 changes: 56 additions & 41 deletions src/main/java/org/mastodon/mamut/clustering/ui/DendrogramView.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,19 @@
*/
package org.mastodon.mamut.clustering.ui;

import net.miginfocom.swing.MigLayout;
import org.mastodon.mamut.clustering.util.HierarchicalClusteringResult;
import org.mastodon.mamut.model.Model;
import org.mastodon.model.tag.TagSetModel;
import org.mastodon.model.tag.TagSetStructure;
import org.mastodon.ui.util.ExtensionFileFilter;
import org.mastodon.ui.util.FileChooser;
import org.scijava.prefs.PrefService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.List;
import java.util.function.ObjIntConsumer;

import javax.swing.BorderFactory;
import javax.swing.JButton;
Expand All @@ -50,24 +53,25 @@
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.WindowConstants;
import java.awt.Color;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.List;
import java.util.function.ObjIntConsumer;

import net.miginfocom.swing.MigLayout;

import org.mastodon.mamut.clustering.util.HierarchicalClusteringResult;
import org.mastodon.mamut.model.Model;
import org.mastodon.model.tag.TagSetModel;
import org.mastodon.model.tag.TagSetStructure;
import org.mastodon.ui.util.ExtensionFileFilter;
import org.mastodon.ui.util.FileChooser;
import org.scijava.prefs.PrefService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A class that represents a UI view of a dendrogram.<br>
* It encapsulates a {@link HierarchicalClusteringResult} object and a headline that write the parameters that were used for it.
* @param <T> the type of the objects that are clustered
*/
public class DendrogramView< T > implements TagSetModel.TagSetModelListener
public class DendrogramView< T > extends JFrame implements TagSetModel.TagSetModelListener
{
private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() );

Expand All @@ -93,8 +97,6 @@ public class DendrogramView< T > implements TagSetModel.TagSetModelListener

private final PrefService prefs;

private final JFrame frame;

private final JPanel canvas = new JPanel( new MigLayout( "fill" ) );

private final JCheckBox showThresholdCheckBox = new JCheckBox( "Show clustering threshold" );
Expand Down Expand Up @@ -126,21 +128,23 @@ public DendrogramView( final HierarchicalClusteringResult< T > hierarchicalClust
final PrefService prefs,
final String projectName )
{
super( "Hierarchical clustering of lineage trees" );

this.hierarchicalClusteringResult = hierarchicalClusteringResult;
this.model = model;
this.prefs = prefs;
this.projectName = projectName;

frame = new JFrame( "Hierarchical clustering of lineage trees" );
frame.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE );

int minHeight = 50;
int initialDendrogramHeight = hierarchicalClusteringResult == null ? minHeight
: ( hierarchicalClusteringResult.getObjectCount() ) * getDefaultFontHeight() + DendrogramPanel.DENDROGRAM_VERTICAL_OFFSET;
initialDendrogramHeight += 200;
initialDendrogramHeight = Math.min( initialDendrogramHeight, 1000 );
frame.setSize( 1000, initialDendrogramHeight );
frame.setLayout( new MigLayout( "insets 10, fill" ) );
frame.add( canvas, "grow" );
setSize( 1000, initialDendrogramHeight );
setLayout( new MigLayout( "insets 10, fill" ) );
add( canvas, "grow" );
setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE );

headlineLabel = new JLabel( headline );
dendrogramPanel = new DendrogramPanel<>( hierarchicalClusteringResult );
Expand All @@ -153,14 +157,6 @@ public DendrogramView( final HierarchicalClusteringResult< T > hierarchicalClust
this.model.getTagSetModel().listeners().add( this );
}

/**
* Sets the visibility of the frame to {@code true}.
*/
public void show()
{
frame.setVisible( true );
}

public JPanel getCanvas()
{
return canvas;
Expand Down Expand Up @@ -214,8 +210,10 @@ private void initSettings()

private void initBehavior()
{
showThresholdCheckBox.addActionListener( ignore -> showThreshold( showThresholdCheckBox.isSelected() ) );
showMedianCheckBox.addActionListener( ignore -> showMedian( showMedianCheckBox.isSelected() ) );
ActionListener showThresholdListener = ignore -> showThreshold( showThresholdCheckBox.isSelected() );
showThresholdCheckBox.addActionListener( showThresholdListener );
ActionListener showMedianListener = ignore -> showMedian( showMedianCheckBox.isSelected() );
showMedianCheckBox.addActionListener( showMedianListener );
ActionListener tagSetListener = event -> {
selectedTagSet =
tagSetComboBox.getSelectedItem() == null ? null : ( ( TagSetElement ) tagSetComboBox.getSelectedItem() ).tagSet;
Expand All @@ -225,7 +223,24 @@ private void initBehavior()
showTagLabelsCheckBox.addActionListener( tagSetListener );
tagSetComboBox.addActionListener( tagSetListener );
JPopupMenu popupMenu = new PopupMenu();
menuButton.addActionListener( event -> popupMenu.show( menuButton, 0, menuButton.getHeight() ) );
ActionListener menuListener = event -> popupMenu.show( menuButton, 0, menuButton.getHeight() );
menuButton.addActionListener( menuListener );

addWindowListener( new WindowAdapter()
{
@Override
public void windowClosing( WindowEvent windowEvent )
{
showThresholdCheckBox.removeActionListener( showThresholdListener );
showMedianCheckBox.removeActionListener( showMedianListener );
showRootLabelsCheckBox.removeActionListener( tagSetListener );
showTagLabelsCheckBox.removeActionListener( tagSetListener );
tagSetComboBox.removeActionListener( tagSetListener );
menuButton.removeActionListener( menuListener );
if ( null != model )
model.getTagSetModel().listeners().remove( DendrogramView.this );
}
} );
}

private void showThreshold( final boolean showThreshold )
Expand Down Expand Up @@ -333,7 +348,7 @@ private PopupMenu()

private void chooseFileAndExport( final String extension, final String fileName, final ObjIntConsumer< File > exportFunction )
{
File chosenFile = FileChooser.chooseFile( frame, fileName + '.' + extension,
File chosenFile = FileChooser.chooseFile( this, fileName + '.' + extension,
new ExtensionFileFilter( extension ), "Save dendrogram to " + extension, FileChooser.DialogType.SAVE );
if ( chosenFile != null )
{
Expand Down

0 comments on commit c809e53

Please sign in to comment.