Skip to content

Commit

Permalink
Merge pull request #229 from mastodon-sc/fix-show-track-downward
Browse files Browse the repository at this point in the history
ShowSelectedTracksAction: don't get stuck if there's a loop in the graph
  • Loading branch information
maarzt authored May 31, 2023
2 parents 2cb97e1 + e49cb67 commit e00cd43
Show file tree
Hide file tree
Showing 6 changed files with 531 additions and 53 deletions.
158 changes: 158 additions & 0 deletions src/main/java/org/mastodon/util/TreeUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package org.mastodon.util;

import java.util.Collection;

import org.mastodon.collection.RefCollections;
import org.mastodon.collection.RefList;
import org.mastodon.collection.RefSet;
import org.mastodon.collection.RefStack;
import org.mastodon.graph.Edge;
import org.mastodon.graph.Graph;
import org.mastodon.graph.Vertex;

public class TreeUtils
{

private TreeUtils()
{
// prevent instantiation of utility class
}

/**
* This method finds a subset of the given {@code selectedVertices} that
* contains only the roots of the subtrees that are selected. The order
* of the returned list follows the order of the {@code roots} list and
* the order of the outgoing edges of the graphs vertices.
* <p>
* Example {@code graph}:
* <pre>
* A B
* / \ / \
* a1 a2 b1 b2
* | / \ / \
* a3 b3 b4 b5 b6
* / \
* a4 a5
* </pre>
*
* If {@code selectedVertices} contains: {@code {a2, a4, a5, b1, b3, b4, b6}},
* then the returned list will be: {@code [a2, b1, b6]}.
*/
public static < V extends Vertex< ? > > RefList< V > findSelectedSubtreeRoots(
final Graph< V, ? > graph,
final RefList< V > roots,
final RefSet< V > selectedVertices )
{
final RefList< V > selectedSubtreeRoots = RefCollections.createRefList( graph.vertices() );
final RefSet< V > visitedNodes = RefCollections.createRefSet( graph.vertices() );

for ( final V root : roots )
for ( final DepthFirstIteration.Step< V > step : DepthFirstIteration.forRoot( graph, root ) )
if ( ensureNoLoop( step, visitedNodes ) && !step.isSecondVisit() ) {

final V node = step.node();
if ( selectedVertices.contains( node ) )
{
selectedSubtreeRoots.add( node );
step.truncate(); // don't visit the child nodes
}
}

return selectedSubtreeRoots;
}

private static < V extends Vertex< ? > > boolean ensureNoLoop( DepthFirstIteration.Step< V > step, RefSet< V > visitedNodes )
{
if ( !step.isFirstVisit() )
return true;

boolean isLoop = !visitedNodes.add( step.node() ); // The depth first iteration enters a node for the second time. -> there's a loop.
if ( isLoop )
step.truncate(); // Break the loop by not visiting the child nodes.

return !isLoop;
}

/**
* This method returns the root nodes of the tracks (connected components)
* that contain any of the given {@code nodes}.
* <p>
* Example:
* <pre>
* A B C
* / \ / \ /
* a1 a2 b1 b2
* | / \ / \
* a3 b3 b4 b5 b6
* / \
* a4 a5
* </pre>
* <p>
* If {@code nodes} contains {@code {a2, a4}} then the method will return
* {@code {A}}.
* <p>
* If {@code nodes} contains {@code {a2, a4, b4}} then the method will
* return {@code {A, B, C}}.
*/
public static <V extends Vertex<E>, E extends Edge< V > > RefSet< V > findRootsOfTheGivenNodes( Graph< V, E > graph, Collection< V > nodes )
{
return filterRoots( graph, findAllConnectedNodes( graph, nodes ) );
}

/**
* @return the set of predecessors of the given {@code nodes}. Please note
* that returned set also contains all the given {@code nodes}.
*/
private static < V extends Vertex<E>, E extends Edge< V > > RefSet< V > findAllConnectedNodes( Graph< V, E > graph, Collection< V > nodes )
{
// The following code performs an inverse depth first search starting
// from the given nodes. The set of visited nodes is returned.
V ref = graph.vertexRef();
V ref2 = graph.vertexRef();
try
{
final RefSet< V > visited = RefCollections.createRefSet( graph.vertices() );
visited.addAll( nodes );
final RefStack< V > stack = RefCollections.createRefStack( graph.vertices() );
stack.addAll( visited );
while ( ! stack.isEmpty() ) {
V node = stack.pop( ref );
for ( E edge : node.incomingEdges() ) {
V parentNode = edge.getSource( ref2 );
addNode( visited, stack, parentNode );
}
for ( E edge : node.outgoingEdges() ) {
V childNode = edge.getTarget( ref2 );
addNode( visited, stack, childNode );
}
}
return visited;
}
finally
{
graph.releaseRef( ref );
graph.releaseRef( ref2 );
}
}

private static < V extends Vertex<E>, E extends Edge< V > > void addNode( RefSet< V > visited, RefStack< V > stack, V parentNode )
{
boolean firstVisit = visited.add( parentNode );
if ( firstVisit )
stack.add( parentNode );
}

/**
* @return a subset of the given {@code nodes} that contains only those
* nodes that are roots of the {@code graph}. (A note is considered a root
* if it has no incoming edges.)
*/
private static < V extends Vertex<E>, E extends Edge< V > > RefSet< V > filterRoots( Graph< V, E > graph, Collection< V > nodes )
{
final RefSet< V > roots = RefCollections.createRefSet( graph.vertices() );
for ( V node : nodes )
if ( node.incomingEdges().isEmpty() )
roots.add( node );
return roots;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -41,7 +41,7 @@
import org.mastodon.ui.keymap.CommandDescriptionProvider;
import org.mastodon.ui.keymap.CommandDescriptions;
import org.mastodon.ui.keymap.KeyConfigContexts;
import org.mastodon.util.DepthFirstIteration;
import org.mastodon.util.TreeUtils;
import org.mastodon.views.trackscheme.LexicographicalVertexOrder;
import org.mastodon.views.trackscheme.TrackSchemeEdge;
import org.mastodon.views.trackscheme.TrackSchemeGraph;
Expand Down Expand Up @@ -172,7 +172,8 @@ private RefList< TrackSchemeVertex > getSelectedSubtreeRoots()
final RefSet< TrackSchemeVertex > selectedNodes = new RefSetImp<>( viewGraph.getVertexPool() );
selectedNodes.addAll( selectionModel.getSelectedVertices() );
addEdgeTargets( selectedNodes, selectionModel.getSelectedEdges() );
return filterRootNodes( selectedNodes );
RefList< TrackSchemeVertex > sortedRoots = LexicographicalVertexOrder.sort( viewGraph, viewGraph.getRoots() );
return TreeUtils.findSelectedSubtreeRoots( viewGraph, sortedRoots, selectedNodes );
}

private RefList< TrackSchemeVertex > getSelectedWholeTrackRoots()
Expand All @@ -192,40 +193,9 @@ private void addEdgeTargets( final RefSet< TrackSchemeVertex > selected,
viewGraph.releaseRef( targetRef );
}

private RefList< TrackSchemeVertex > filterRootNodes( final RefSet< TrackSchemeVertex > selectedVertices )
{
final RefList< TrackSchemeVertex > roots = new RefArrayList<>( viewGraph.getVertexPool() );
for ( final TrackSchemeVertex realRoot : LexicographicalVertexOrder.sort( viewGraph, viewGraph.getRoots() ) )
for ( final DepthFirstIteration.Step< TrackSchemeVertex > step : DepthFirstIteration.forRoot( viewGraph,
realRoot ) )
{
final TrackSchemeVertex node = step.node();
if ( selectedVertices.contains( node ) )
{
roots.add( node );
step.truncate();
}
}
return roots;
}

private RefList< TrackSchemeVertex > getRealRoots( final RefSet< TrackSchemeVertex > selectedNodes )
{
final TrackSchemeVertex parent = viewGraph.vertexRef();
final RefSet< TrackSchemeVertex > roots = new RefSetImp<>( viewGraph.getVertexPool() );
A: for ( final TrackSchemeVertex vertex : selectedNodes )
{
parent.refTo( vertex );
while ( !parent.incomingEdges().isEmpty() )
{
parent.incomingEdges().iterator().next().getSource( parent );
if ( selectedNodes.contains( parent ) )
continue A;
}
roots.add( parent );
}
viewGraph.releaseRef( parent );
RefSet< TrackSchemeVertex > roots = TreeUtils.findRootsOfTheGivenNodes( viewGraph, selectedNodes );
return LexicographicalVertexOrder.sort( viewGraph, roots );
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,26 @@
*/
public class ExampleGraph1 extends AbstractExampleGraph
{
public final Spot spot0;

public final Spot spot1;

public final Spot spot2;

public final Spot spot3;

public final Spot spot4;

public final BranchSpot branchSpotA;

public ExampleGraph1()
{
super();
Spot spot0 = addNode( "0", 0, new double[] { 1d, 2d, 3d } );
Spot spot1 = addNode( "1", 1, new double[] { 2d, 4d, 6d } );
Spot spot2 = addNode( "2", 2, new double[] { 3d, 6d, 9d } );
Spot spot3 = addNode( "3", 3, new double[] { 4d, 8d, 12d } );
Spot spot4 = addNode( "4", 3, new double[] { 5d, 10d, 15d } );
spot0 = addNode( "0", 0, new double[] { 1d, 2d, 3d } );
spot1 = addNode( "1", 1, new double[] { 2d, 4d, 6d } );
spot2 = addNode( "2", 2, new double[] { 3d, 6d, 9d } );
spot3 = addNode( "3", 3, new double[] { 4d, 8d, 12d } );
spot4 = addNode( "4", 3, new double[] { 5d, 10d, 15d } );

addEdge( spot0, spot1 );
addEdge( spot1, spot2 );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,31 @@
*/
public class ExampleGraph2 extends AbstractExampleGraph
{

public final Spot spot0;

public final Spot spot1;

public final Spot spot2;

public final Spot spot3;

public final Spot spot4;

public final Spot spot5;

public final Spot spot6;

public final Spot spot7;

public final Spot spot8;

public final Spot spot10;

public final Spot spot11;

public final Spot spot13;

public final BranchSpot branchSpotA;

public final BranchSpot branchSpotB;
Expand All @@ -58,18 +83,18 @@ public class ExampleGraph2 extends AbstractExampleGraph

public ExampleGraph2()
{
Spot spot0 = addNode( "0", 0, new double[]{1d, 2d, 3d} );
Spot spot1 = addNode( "1", 1, new double[]{0d, 0d, 0d} );
Spot spot2 = addNode( "2", 2, new double[]{3d, 6d, 9d} );
Spot spot3 = addNode( "3", 3, new double[]{4d, 8d, 12d} );
Spot spot4 = addNode( "4", 4, new double[]{5d, 10d, 15d} );
Spot spot5 = addNode( "5", 5, new double[]{6d, 12d, 18d} );
Spot spot6 = addNode( "6", 6, new double[]{0d, 0d, 0d} );
Spot spot7 = addNode( "7", 7, new double[]{8d, 16d, 24d} );
Spot spot8 = addNode( "8", 5, new double[]{9d, 18d, 27d} );
Spot spot10 = addNode( "10", 7, new double[]{11d, 22d, 33d} );
Spot spot11 = addNode( "11", 3, new double[]{12d, 24d, 36d} );
Spot spot13 = addNode( "13", 5, new double[]{14d, 28d, 42d} );
spot0 = addNode( "0", 0, new double[] { 1d, 2d, 3d } );
spot1 = addNode( "1", 1, new double[] { 0d, 0d, 0d } );
spot2 = addNode( "2", 2, new double[] { 3d, 6d, 9d } );
spot3 = addNode( "3", 3, new double[] { 4d, 8d, 12d } );
spot4 = addNode( "4", 4, new double[] { 5d, 10d, 15d } );
spot5 = addNode( "5", 5, new double[] { 6d, 12d, 18d } );
spot6 = addNode( "6", 6, new double[] { 0d, 0d, 0d } );
spot7 = addNode( "7", 7, new double[] { 8d, 16d, 24d } );
spot8 = addNode( "8", 5, new double[] { 9d, 18d, 27d } );
spot10 = addNode( "10", 7, new double[] { 11d, 22d, 33d } );
spot11 = addNode( "11", 3, new double[] { 12d, 24d, 36d } );
spot13 = addNode( "13", 5, new double[] { 14d, 28d, 42d } );

addEdge( spot0, spot1 );
addEdge( spot1, spot2 );
Expand Down
Loading

0 comments on commit e00cd43

Please sign in to comment.