Skip to content

Commit a040706

Browse files
committed
Add a plugin for lineage registration based on spindle directions
* An transformation between the two dataset is estimated from the labeled spots in the first time point of the two datasets * The tree is sorted such that the spindle direction matches between the two datasets. Scalar product > 0.
1 parent c6d13d1 commit a040706

13 files changed

Lines changed: 742 additions & 4 deletions

pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@
4141
<groupId>org.scijava</groupId>
4242
<artifactId>ui-behaviour</artifactId>
4343
</dependency>
44+
<dependency>
45+
<groupId>net.preibisch</groupId>
46+
<artifactId>multiview-reconstruction</artifactId>
47+
<version>0.11.5</version>
48+
</dependency>
4449

4550
<!-- Test dependencies -->
4651
<dependency>
@@ -80,7 +85,7 @@
8085
<license.organizationName>Mastodon authors</license.organizationName>
8186
<license.copyrightOwners>Tobias Pietzsch</license.copyrightOwners>
8287

83-
<mastodon.version>1.0.0-beta-26</mastodon.version>
88+
<mastodon.version>1.0.0-beta-27-SNAPSHOT</mastodon.version>
8489

8590
<!-- NB: Deploy releases to the SciJava Maven repository. -->
8691
<releaseProfiles>sign,deploy-to-scijava</releaseProfiles>

src/main/java/org/mastodon/mamut/tomancak/TomancakPlugins.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.mastodon.mamut.tomancak.sort_tree.SortTreeExternInternDialog;
5656
import org.mastodon.mamut.tomancak.spots.FilterOutSolists;
5757
import org.mastodon.mamut.tomancak.spots.InterpolateMissingSpots;
58+
import org.mastodon.mamut.tomancak.lineage_registration.LineageRegistrationPlugin;
5859
import org.mastodon.ui.keymap.CommandDescriptionProvider;
5960
import org.mastodon.ui.keymap.CommandDescriptions;
6061
import org.mastodon.ui.keymap.KeyConfigContexts;
@@ -77,6 +78,7 @@ public class TomancakPlugins extends AbstractContextual implements MamutPlugin
7778
private static final String COMPACT_LINEAGE_VIEW = "[tomancak] show compact lineage";
7879
private static final String SORT_TREE = "[tomancak] sort lineage tree";
7980
private static final String SORT_TREE_EXTERN_INTERN = "[tomancak] sort lineage tree extern intern";
81+
private static final String MATCH_TREE = "[tomancak] match tree to other project";
8082
private static final String LABEL_SPOTS_SYSTEMATICALLY = "[tomancak] label spots systematically";
8183
private static final String REMOVE_SOLISTS_SPOTS = "[tomancak] remove solists spots";
8284
private static final String EXPORTS_LINEAGE_LENGTHS = "[tomancak] export lineage lengths";
@@ -93,6 +95,7 @@ public class TomancakPlugins extends AbstractContextual implements MamutPlugin
9395
private static final String[] COMPACT_LINEAGE_VIEW_KEYS = { "not mapped" };
9496
private static final String[] SORT_TREE_KEYS = { "ctrl S" };
9597
private static final String[] SORT_TREE_EXTERN_INTERN_KEYS = { "not mapped" };
98+
private static final String[] MATCH_TREE_KEYS = { "not mapped"};
9699
private static final String[] LABEL_SPOTS_SYSTEMATICALLY_KEYS = { "not mapped" };
97100
private static final String[] REMOVE_SOLISTS_SPOTS_KEYS = { "not mapped" };
98101
private static final String[] EXPORTS_LINEAGE_LENGTHS_KEYS = { "not mapped" };
@@ -112,8 +115,9 @@ public class TomancakPlugins extends AbstractContextual implements MamutPlugin
112115
menuTexts.put( CHANGE_BRANCH_LABELS, "Change Branch's Labels");
113116
menuTexts.put( COMPACT_LINEAGE_VIEW, "Show Compact Lineage" );
114117
menuTexts.put( SORT_TREE, "Sort Lineage Tree" );
115-
menuTexts.put( SORT_TREE_EXTERN_INTERN, "Sort Lineage Tree, Extern-Intern");
116-
menuTexts.put( LABEL_SPOTS_SYSTEMATICALLY, "Systematically Label Spots, Extern-Intern" );
118+
menuTexts.put( SORT_TREE_EXTERN_INTERN, "Sort Lineage Tree (Extern-Intern)");
119+
menuTexts.put( MATCH_TREE, "Sort Lineage Tree (To Match Other Project)" );
120+
menuTexts.put( LABEL_SPOTS_SYSTEMATICALLY, "Systematically Label Spots (Extern-Intern)" );
117121
menuTexts.put( REMOVE_SOLISTS_SPOTS, "Remove Spots Solists" );
118122
menuTexts.put( EXPORTS_LINEAGE_LENGTHS, "Export Lineage Lengths" );
119123
menuTexts.put( EXPORTS_SPOTS_COUNTS, "Export Spots Counts" );
@@ -144,6 +148,7 @@ public void getCommandDescriptions( final CommandDescriptions descriptions )
144148
descriptions.add( COMPACT_LINEAGE_VIEW, COMPACT_LINEAGE_VIEW_KEYS, "Show compact representation of the lineage tree.");
145149
descriptions.add( SORT_TREE, SORT_TREE_KEYS, "Sort selected node according to tagged anchors.");
146150
descriptions.add( SORT_TREE_EXTERN_INTERN, SORT_TREE_EXTERN_INTERN_KEYS, "Sort selected nodes according to tagged center anchor.");
151+
descriptions.add( MATCH_TREE, MATCH_TREE_KEYS, "Sort the TrackScheme such that the order matches another project.");
147152
descriptions.add( LABEL_SPOTS_SYSTEMATICALLY, LABEL_SPOTS_SYSTEMATICALLY_KEYS, "Child cells are named after their parent cell, with a \"1\" or \"2\" appended to the label.");
148153
descriptions.add( REMOVE_SOLISTS_SPOTS, REMOVE_SOLISTS_SPOTS_KEYS, "Finds and removes isolated spots from the lineage, based on conditions." );
149154
descriptions.add( EXPORTS_LINEAGE_LENGTHS, EXPORTS_LINEAGE_LENGTHS_KEYS, "Exports lineage lengths into CSV-like files to be imported in data processors." );
@@ -171,6 +176,8 @@ public void getCommandDescriptions( final CommandDescriptions descriptions )
171176

172177
private final AbstractNamedAction sortTreeExternInternAction;
173178

179+
private final AbstractNamedAction matchTreeAction;
180+
174181
private final AbstractNamedAction labelSpotsSystematicallyAction;
175182

176183
private final AbstractNamedAction removeSolistsAction;
@@ -196,6 +203,7 @@ public TomancakPlugins()
196203
lineageTreeViewAction = new RunnableAction( COMPACT_LINEAGE_VIEW, this::showLineageView );
197204
sortTreeAction = new RunnableAction( SORT_TREE, this::sortTree );
198205
sortTreeExternInternAction = new RunnableAction( SORT_TREE_EXTERN_INTERN, this::sortTreeExternIntern );
206+
matchTreeAction = new RunnableAction( MATCH_TREE, this::matchTree );
199207
labelSpotsSystematicallyAction = new RunnableAction( LABEL_SPOTS_SYSTEMATICALLY, this::labelSpotsSystematically );
200208
removeSolistsAction = new RunnableAction( REMOVE_SOLISTS_SPOTS, this::filterOutSolists );
201209
exportLineageLengthsAction = new RunnableAction( EXPORTS_LINEAGE_LENGTHS, this::exportLengths );
@@ -228,7 +236,8 @@ public List< ViewMenuBuilder.MenuItem > getMenuItems()
228236
item( FLIP_DESCENDANTS ),
229237
item( SORT_TREE ),
230238
item( SORT_TREE_EXTERN_INTERN ),
231-
item( LABEL_SPOTS_SYSTEMATICALLY )),
239+
item( MATCH_TREE )),
240+
item( LABEL_SPOTS_SYSTEMATICALLY ),
232241
menu( "Exports",
233242
item( EXPORTS_LINEAGE_LENGTHS ),
234243
item( EXPORTS_SPOTS_COUNTS ),
@@ -262,6 +271,7 @@ public void installGlobalActions( final Actions actions )
262271
actions.namedAction( exportSpotsCountsAction, EXPORTS_SPOTS_COUNTS_KEYS );
263272
actions.namedAction( mergeProjectsAction, MERGE_PROJECTS_KEYS );
264273
actions.namedAction( tweakDatasetPathAction, TWEAK_DATASET_PATH_KEYS );
274+
actions.namedAction( matchTreeAction, MATCH_TREE_KEYS );
265275
}
266276

267277
private void updateEnabledActions()
@@ -281,6 +291,7 @@ private void updateEnabledActions()
281291
exportSpotsCountsAction.setEnabled( appModel != null );
282292
mergeProjectsAction.setEnabled( appModel != null );
283293
tweakDatasetPathAction.setEnabled( appModel != null );
294+
matchTreeAction.setEnabled( appModel!= null );
284295
}
285296

286297
private void exportPhyloXml()
@@ -411,4 +422,10 @@ private void labelSpotsSystematically()
411422
LabelSpotsSystematicallyDialog.showDialog( pluginAppModel.getAppModel() );
412423
}
413424
}
425+
426+
private void matchTree()
427+
{
428+
if ( pluginAppModel != null )
429+
LineageRegistrationPlugin.showDialog( pluginAppModel.getAppModel() );
430+
}
414431
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package org.mastodon.mamut.tomancak.lineage_registration;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import mpicbg.models.AbstractAffineModel3D;
7+
import mpicbg.models.IllDefinedDataPointsException;
8+
import mpicbg.models.NotEnoughDataPointsException;
9+
import mpicbg.models.Point;
10+
import mpicbg.models.PointMatch;
11+
import mpicbg.models.SimilarityModel3D;
12+
import net.imglib2.RealPoint;
13+
import net.imglib2.realtransform.AffineTransform3D;
14+
import org.mastodon.collection.RefRefMap;
15+
import org.mastodon.mamut.model.Spot;
16+
17+
public class EstimateTransformation
18+
{
19+
20+
/**
21+
* Return a affine transform, that is composed of scaling, rotation and translation operation.
22+
* The transformation is optimized to minimize the distances of the transformed "key" spots
23+
* to the "value" spots.
24+
*/
25+
public static AffineTransform3D estimateScaleRotationAndTranslation( RefRefMap< Spot, Spot > pairs )
26+
{
27+
List< RealPoint > pointsA = new ArrayList<>();
28+
List< RealPoint > pointsB = new ArrayList<>();
29+
for( Spot rootA : pairs.keySet() ) {
30+
Spot rootB = pairs.get( rootA );
31+
pointsA.add( new RealPoint( rootA ) );
32+
pointsB.add( new RealPoint( rootB ) );
33+
}
34+
return estimateScaleRotationAndTranslation( pointsA, pointsB );
35+
}
36+
37+
static AffineTransform3D estimateScaleRotationAndTranslation( List< RealPoint > a, List< RealPoint > b )
38+
{
39+
AbstractAffineModel3D model = new SimilarityModel3D();
40+
assert a.size() == b.size();
41+
List< PointMatch > matches = new ArrayList<>(a.size());
42+
for ( int i = 0; i < a.size(); i++ )
43+
{
44+
matches.add(new PointMatch( new Point( a.get( i ).positionAsDoubleArray() ), new Point( b.get( i ).positionAsDoubleArray() ), 1 ));
45+
}
46+
try
47+
{
48+
model.fit( matches );
49+
}
50+
catch ( NotEnoughDataPointsException | IllDefinedDataPointsException e )
51+
{
52+
throw new RuntimeException(e);
53+
}
54+
double[] m = model.getMatrix( null );
55+
AffineTransform3D affineTransform3D = new AffineTransform3D();
56+
affineTransform3D.set( m );
57+
return affineTransform3D;
58+
}
59+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package org.mastodon.mamut.tomancak.lineage_registration;
2+
3+
import net.imglib2.realtransform.AffineTransform3D;
4+
import org.mastodon.collection.RefList;
5+
import org.mastodon.collection.RefRefMap;
6+
import org.mastodon.collection.ref.RefArrayList;
7+
import org.mastodon.mamut.model.Model;
8+
import org.mastodon.mamut.model.ModelGraph;
9+
import org.mastodon.mamut.model.Spot;
10+
import org.mastodon.mamut.tomancak.sort_tree.FlipDescendants;
11+
import org.mastodon.mamut.tomancak.sort_tree.SortTreeUtils;
12+
13+
/**
14+
* An algorithm that by compares the "spindle directions" in two lineages.
15+
* By doing so it figures out which spots need to be flipped in order
16+
* to match the TrackSchemes of both lineages.
17+
*/
18+
public class LineageRegistrationAlgorithm
19+
{
20+
private final AffineTransform3D transformAB;
21+
22+
private final ModelGraph graphA;
23+
24+
private final ModelGraph graphB;
25+
26+
private final RefList< Spot > toBeFlipped;
27+
28+
public static void run( Model embryoA, Model embryoB )
29+
{
30+
ModelGraph graphA = embryoA.getGraph();
31+
ModelGraph graphB = embryoB.getGraph();
32+
RefRefMap< Spot, Spot > roots = RootsPairing.pairRoots( graphA, graphB );
33+
AffineTransform3D transformAB = EstimateTransformation.estimateScaleRotationAndTranslation( roots );
34+
RefList< Spot > toBeFlipped = new LineageRegistrationAlgorithm(
35+
graphA, graphB,
36+
roots, transformAB ).getToBeFlipped();
37+
FlipDescendants.flipDescendants( embryoB, toBeFlipped );
38+
}
39+
40+
public LineageRegistrationAlgorithm( ModelGraph graphA, ModelGraph graphB, RefRefMap< Spot, Spot > roots,
41+
AffineTransform3D transformAB ) {
42+
this.transformAB = noOffsetTransform( transformAB );
43+
this.graphA = graphA;
44+
this.graphB = graphB;
45+
this.toBeFlipped = new RefArrayList<>( graphB.vertices().getRefPool() );
46+
for( Spot rootA : roots.keySet() ) {
47+
Spot rootB = roots.get( rootA );
48+
matchTree( rootA, rootB );
49+
}
50+
}
51+
52+
private void matchTree(Spot rootA, Spot rootB)
53+
{
54+
Spot dividingA = LineageTreeUtils.getBranchEnd( graphA, rootA );
55+
Spot dividingB = LineageTreeUtils.getBranchEnd( graphB, rootB );
56+
try
57+
{
58+
if(dividingA.outgoingEdges().size() != 2 ||
59+
dividingB.outgoingEdges().size() != 2)
60+
return;
61+
double[] directionA = SortTreeUtils.directionOfCellDevision( graphA, dividingA );
62+
double[] directionB = SortTreeUtils.directionOfCellDevision( graphB, dividingB );
63+
transformAB.apply( directionA, directionA );
64+
boolean flip = SortTreeUtils.scalarProduct( directionA, directionB ) < 0;
65+
if(flip)
66+
toBeFlipped.add( dividingB );
67+
matchChildTree( dividingA, dividingB, 0, flip ? 1 : 0 );
68+
matchChildTree( dividingA, dividingB, 1, flip ? 0 : 1 );
69+
} finally
70+
{
71+
graphA.releaseRef( dividingA );
72+
graphB.releaseRef( dividingB );
73+
}
74+
}
75+
76+
private void matchChildTree( Spot dividingA, Spot dividingB, int indexA, int indexB )
77+
{
78+
Spot childA = dividingA.outgoingEdges().get( indexA ).getTarget();
79+
Spot childB = dividingB.outgoingEdges().get( indexB ).getTarget();
80+
try
81+
{
82+
matchTree( childA, childB );
83+
}
84+
finally
85+
{
86+
graphA.releaseRef( childA );
87+
graphB.releaseRef( childB );
88+
}
89+
}
90+
91+
public RefList< Spot > getToBeFlipped()
92+
{
93+
return toBeFlipped;
94+
}
95+
96+
private static AffineTransform3D noOffsetTransform( AffineTransform3D transformAB )
97+
{
98+
AffineTransform3D noOffsetTransform = new AffineTransform3D();
99+
noOffsetTransform.set( transformAB );
100+
noOffsetTransform.setTranslation( 0, 0, 0 );
101+
return noOffsetTransform;
102+
}
103+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package org.mastodon.mamut.tomancak.lineage_registration;
2+
3+
import java.io.File;
4+
5+
import javax.swing.JButton;
6+
import javax.swing.JDialog;
7+
import javax.swing.JFrame;
8+
import javax.swing.JLabel;
9+
import javax.swing.JTextArea;
10+
import javax.swing.filechooser.FileNameExtensionFilter;
11+
12+
import net.miginfocom.swing.MigLayout;
13+
import org.mastodon.ui.util.FileChooser;
14+
15+
public class LineageRegistrationDialog extends JDialog
16+
{
17+
private final JTextArea pathTextArea;
18+
19+
private File mastodonProject = null;
20+
21+
private boolean ok = false;
22+
23+
public LineageRegistrationDialog() {
24+
super(( JFrame ) null,"Sort TrackScheme to Match Another Lineage", true);
25+
setLayout( new MigLayout("insets dialog, fill") );
26+
27+
final String introText = "<html><body>"
28+
+ "The \"Tree Matching\" plugin allows to sorts the TrackScheme in this project<br>"
29+
+ "such that the order matches the other project.<br>"
30+
+ "These requirements should be met:"
31+
+ "<ul>"
32+
+ "<li>Both project should show a stereotypically developing embryo.</li>"
33+
+ "<li>The first frame should show the two embryo at a similar stage.</li>"
34+
+ "<li>Root nodes must be named, and the names should match between the two projects.</li>"
35+
+ "</ul>"
36+
+ "</body></html>";
37+
add(new JLabel("Please select Mastodon project to match to:"), "wrap");
38+
pathTextArea = new JTextArea(2, 50);
39+
pathTextArea.setEditable( false );
40+
add(pathTextArea, "grow, wrap");
41+
add( newButton( "select", this::onSelectClicked ), "wrap");
42+
add(new JLabel(introText), "gaptop unrelated, wrap");
43+
add( newButton( "Sort TrackScheme", this::onOkClicked ), "gaptop unrelated, split 2, pushx, align right" );
44+
add( newButton( "Cancel", this::onCancelClicked ) );
45+
}
46+
47+
private void onSelectClicked()
48+
{
49+
mastodonProject = FileChooser.chooseFile( this, null, new FileNameExtensionFilter( "Mastodon project", "mastodon" ), "Open Mastodon Project, To Match To", FileChooser.DialogType.LOAD );
50+
pathTextArea.setText( mastodonProject.getPath() );
51+
}
52+
53+
private void onOkClicked()
54+
{
55+
ok = true;
56+
super.setVisible( false );
57+
}
58+
59+
private void onCancelClicked()
60+
{
61+
super.setVisible( false );
62+
}
63+
64+
private JButton newButton( String select, Runnable action )
65+
{
66+
JButton button = new JButton( select );
67+
button.addActionListener( ignored -> action.run() );
68+
return button;
69+
}
70+
71+
public static File showDialog() {
72+
LineageRegistrationDialog dialog = new LineageRegistrationDialog();
73+
dialog.setLocationByPlatform( true );
74+
dialog.pack();
75+
dialog.setVisible( true );
76+
return dialog.ok ? dialog.mastodonProject : null;
77+
}
78+
79+
public static void main(String... args) {
80+
File file = LineageRegistrationDialog.showDialog();
81+
System.out.println(file);
82+
}
83+
}

0 commit comments

Comments
 (0)