Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix dendrogram view #127

Merged
merged 4 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading