Skip to content

Commit 8b58ef6

Browse files
Merge pull request #83 from mastodon-sc/division_counts
Add new command Export Division counts per timepoint
2 parents e5da4b4 + c1b4d2e commit 8b58ef6

File tree

5 files changed

+224
-4
lines changed

5 files changed

+224
-4
lines changed

README.md

+10-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
* [Export measurements](#export-measurements)
3838
* [Export spot counts per lineage](#export-spot-counts-per-lineage)
3939
* [Export spot counts per time point](#export-spot-counts-per-time-point)
40-
* [Export lineage lengths](#export-lineage-lengths)
40+
* [Export division counts per time point](#export-division-counts-per-time-point)
4141
* [Export phyloXML for selected spot](#export-phyloxml-for-selected-spot)
4242

4343
## Documentation of Mastodon
@@ -349,18 +349,25 @@ The plugin allows performing various operations based on the correspondence info
349349

350350
### Export spot counts per lineage
351351

352-
* Menu Location: `File > Export > Export measurements > Export spot counts per lineage`
352+
* Menu Location: `File > Export > Export measurements > Spot counts > Export spot counts per lineage`
353353
* Export spots counts per lineage per time point.
354354
* If spots are selected, only lineages with selected spots are included, otherwise all lineages are included.
355355
* Results in one CSV file per lineage.
356356
* Example: ![export_spot_counts_per_lineage.png](doc/export/export_spot_counts_per_lineage.png)
357357

358358
### Export spot counts per time point
359359

360-
* Menu Location: `File > Export > Export measurements > Export spot counts per time point`
360+
* Menu Location: `File > Export > Export measurements > Spot counts > Export spot counts per time point`
361361
* This command writes the time point and the number of spots at each time point to a single CSV file.
362362
* Example: ![export_spot_counts_per_time_point.png](doc/export/export_spot_counts_per_time_point.png)
363363

364+
### Export division counts per time point
365+
366+
* Menu Location: `File > Export > Export measurements > Export division counts per time point`
367+
* This command writes the time point and the number of divisions at each time point to a single CSV file.
368+
* A division is defined as a spot with more than one outgoing edge.
369+
* Example: ![export_division_counts_per_time_point.png](doc/export/export_division_counts_per_time_point.png)
370+
364371
### Export phyloXML for selected spot
365372

366373
* Menu Location: `File > Export > Export phyloXML for selected spot`
Loading

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

+21-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.mastodon.mamut.tomancak.compact_lineage.CompactLineageFrame;
4848
import org.mastodon.mamut.tomancak.divisiontagset.CellDivisionsTagSetCommand;
4949
import org.mastodon.mamut.tomancak.export.ExportCounts;
50+
import org.mastodon.mamut.tomancak.export.ExportDivisionCountsPerTimepointCommand;
5051
import org.mastodon.mamut.tomancak.export.ExportSpotCountsPerTimepointCommand;
5152
import org.mastodon.mamut.tomancak.export.LineageLengthExporter;
5253
import org.mastodon.mamut.tomancak.export.MakePhyloXml;
@@ -97,6 +98,8 @@ public class TomancakPlugins extends AbstractContextual implements MamutPlugin
9798

9899
private static final String EXPORT_SPOTS_COUNTS_PER_TIMEPOINT = "[tomancak] export spot counts per timepoint";
99100

101+
private static final String EXPORT_DIVISION_COUNTS_PER_TIMEPOINT = "[tomancak] export division counts per timepoint";
102+
100103
private static final String ADD_CENTER_SPOTS = "[tomancak] add center spots";
101104
private static final String MIRROR_SPOTS = "[tomancak] mirror spots";
102105
private static final String CREATE_CONFLICT_TAG_SET = "[tomancak] create conflict tag set";
@@ -123,6 +126,8 @@ public class TomancakPlugins extends AbstractContextual implements MamutPlugin
123126
private static final String[] EXPORTS_SPOTS_COUNTS_PER_LINEAGE_KEYS = { "not mapped" };
124127
private static final String[] EXPORTS_SPOTS_COUNTS_PER_TIMEPOINT_KEYS = { "not mapped" };
125128

129+
private static final String[] EXPORT_DIVISION_COUNTS_PER_TIMEPOINT_KEYS = { "not mapped" };
130+
126131
private static final String[] ADD_CENTER_SPOTS_KEYS = { "not mapped" };
127132
private static final String[] MIRROR_SPOTS_KEYS = { "not mapped" };
128133
private static final String[] CREATE_CONFLICT_TAG_SET_KEYS = { "not mapped" };
@@ -151,6 +156,7 @@ public class TomancakPlugins extends AbstractContextual implements MamutPlugin
151156
menuTexts.put( EXPORTS_LINEAGE_LENGTHS, "Export lineage lengths" );
152157
menuTexts.put( EXPORT_SPOTS_COUNTS_PER_LINEAGE, "Export spot counts per lineage" );
153158
menuTexts.put( EXPORT_SPOTS_COUNTS_PER_TIMEPOINT, "Export spot counts per timepoint" );
159+
menuTexts.put( EXPORT_DIVISION_COUNTS_PER_TIMEPOINT, "Export division counts per timepoint" );
154160
menuTexts.put( ADD_CENTER_SPOTS, "Add center spots" );
155161
menuTexts.put( MIRROR_SPOTS, "Mirror spots along X-axis" );
156162
menuTexts.put( CREATE_CONFLICT_TAG_SET, "Create conflict tag set" );
@@ -193,6 +199,8 @@ public void getCommandDescriptions( final CommandDescriptions descriptions )
193199
"Exports counts of spots into CSV-like files to be imported in data processors. One file per lineage." );
194200
descriptions.add( EXPORT_SPOTS_COUNTS_PER_TIMEPOINT, EXPORTS_SPOTS_COUNTS_PER_TIMEPOINT_KEYS,
195201
"Exports counts of spots per timepoint into CSV-like files to be imported in data processors. One file." );
202+
descriptions.add( EXPORT_DIVISION_COUNTS_PER_TIMEPOINT, EXPORT_DIVISION_COUNTS_PER_TIMEPOINT_KEYS,
203+
"Exports counts of divisions per timepoint into CSV-like files to be imported in data processors. One file." );
196204
descriptions.add( ADD_CENTER_SPOTS, ADD_CENTER_SPOTS_KEYS, "On each timepoint with selected spots, add a new spot that is in the center (average position)." );
197205
descriptions.add( MIRROR_SPOTS, MIRROR_SPOTS_KEYS, "Mirror spots along x-axis." );
198206
descriptions.add( CREATE_CONFLICT_TAG_SET, CREATE_CONFLICT_TAG_SET_KEYS, "Search spots that overlap and create a tag set that highlights these conflicts." );
@@ -239,6 +247,8 @@ public void getCommandDescriptions( final CommandDescriptions descriptions )
239247

240248
private final AbstractNamedAction exportSpotsCountsPerTimepointAction;
241249

250+
private final AbstractNamedAction exportDivisionCountsPerTimepointAction;
251+
242252
// private final AbstractNamedAction mergeProjectsAction;
243253

244254
private final AbstractNamedAction addCenterSpots;
@@ -275,6 +285,8 @@ public TomancakPlugins()
275285
exportLineageLengthsAction = new RunnableAction( EXPORTS_LINEAGE_LENGTHS, this::exportLengths );
276286
exportSpotsCountsPerLineageAction = new RunnableAction( EXPORT_SPOTS_COUNTS_PER_LINEAGE, this::exportCountsPerLineage );
277287
exportSpotsCountsPerTimepointAction = new RunnableAction( EXPORT_SPOTS_COUNTS_PER_TIMEPOINT, this::exportCountsPerTimepoint );
288+
exportDivisionCountsPerTimepointAction =
289+
new RunnableAction( EXPORT_DIVISION_COUNTS_PER_TIMEPOINT, this::exportDivisionCountsPerTimepoint );
278290
addCenterSpots = new RunnableAction( ADD_CENTER_SPOTS, this::addCenterSpots );
279291
mirrorSpots = new RunnableAction( MIRROR_SPOTS, this::mirrorSpots );
280292
createConflictTagSet = new RunnableAction( CREATE_CONFLICT_TAG_SET, this::createConflictTagSet );
@@ -299,7 +311,8 @@ public List< ViewMenuBuilder.MenuItem > getMenuItems()
299311
menu( "Export measurements",
300312
menu( "Spot counts",
301313
item( EXPORT_SPOTS_COUNTS_PER_LINEAGE ),
302-
item( EXPORT_SPOTS_COUNTS_PER_TIMEPOINT ) ) ),
314+
item( EXPORT_SPOTS_COUNTS_PER_TIMEPOINT ) ),
315+
item( EXPORT_DIVISION_COUNTS_PER_TIMEPOINT ) ),
303316
// item( EXPORTS_LINEAGE_LENGTHS ) ), // NB: deactivated for now, since the function is too prototype-y
304317
item( EXPORT_PHYLOXML ) ) ),
305318
menu( "Plugins",
@@ -356,6 +369,7 @@ public void installGlobalActions( final Actions actions )
356369
actions.namedAction( exportLineageLengthsAction, EXPORTS_LINEAGE_LENGTHS_KEYS );
357370
actions.namedAction( exportSpotsCountsPerLineageAction, EXPORTS_SPOTS_COUNTS_PER_LINEAGE_KEYS );
358371
actions.namedAction( exportSpotsCountsPerTimepointAction, EXPORTS_SPOTS_COUNTS_PER_TIMEPOINT_KEYS );
372+
actions.namedAction( exportDivisionCountsPerTimepointAction, EXPORT_DIVISION_COUNTS_PER_TIMEPOINT_KEYS );
359373
actions.namedAction( addCenterSpots, ADD_CENTER_SPOTS_KEYS );
360374
actions.namedAction( mirrorSpots, MIRROR_SPOTS_KEYS );
361375
actions.namedAction( createConflictTagSet, CREATE_CONFLICT_TAG_SET_KEYS );
@@ -449,6 +463,12 @@ private void exportCountsPerTimepoint()
449463
"context", projectModel.getContext() );
450464
}
451465

466+
private void exportDivisionCountsPerTimepoint()
467+
{
468+
commandService.run( ExportDivisionCountsPerTimepointCommand.class, true, "projectModel", projectModel,
469+
"context", projectModel.getContext() );
470+
}
471+
452472
private void changeBranchLabels()
453473
{
454474
RenameBranchLabels.run( projectModel );
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*-
2+
* #%L
3+
* mastodon-tomancak
4+
* %%
5+
* Copyright (C) 2018 - 2025 Tobias Pietzsch
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
package org.mastodon.mamut.tomancak.export;
30+
31+
import java.io.File;
32+
import java.io.FileWriter;
33+
import java.io.IOException;
34+
import java.util.Iterator;
35+
36+
import com.opencsv.CSVWriter;
37+
38+
import org.mastodon.mamut.ProjectModel;
39+
import org.mastodon.mamut.model.Model;
40+
import org.mastodon.mamut.model.Spot;
41+
import org.mastodon.util.TreeUtils;
42+
import org.scijava.Context;
43+
import org.scijava.ItemVisibility;
44+
import org.scijava.app.StatusService;
45+
import org.scijava.command.Command;
46+
import org.scijava.plugin.Parameter;
47+
import org.scijava.plugin.Plugin;
48+
49+
@Plugin( type = Command.class, label = "Export division counts per timepoint" )
50+
public class ExportDivisionCountsPerTimepointCommand implements Command
51+
{
52+
53+
@Parameter( visibility = ItemVisibility.MESSAGE, required = false, persist = false )
54+
private String documentation = "<html>\n"
55+
+ "<body width=15cm align=left>\n"
56+
+ "<h1>Export division counts per timepoint</h1>\n"
57+
+ "<p>This command writes the timepoint and the number of divisions at each timepoint to a single CSV file.</p>\n"
58+
+ "<p>A division is defined as a spot with more than one outgoing edge.</p>\n"
59+
+ "<p>The format is: \"timepoint\", \"divisions\".</p>\n"
60+
+ "<p>It is recommended to use '*.csv' as file extension.</p>\n"
61+
+ "</body>\n"
62+
+ "</html>\n";
63+
64+
@Parameter( label = "Save to" )
65+
private File saveTo;
66+
67+
@Parameter
68+
private ProjectModel projectModel;
69+
70+
@Parameter
71+
private Context context;
72+
73+
@Override
74+
public void run()
75+
{
76+
try
77+
{
78+
writeDivisionCountsToFile( projectModel.getModel(), saveTo, context.service( StatusService.class ) );
79+
}
80+
catch ( IOException e )
81+
{
82+
System.err.println(
83+
"Could not write division counts to file: " + saveTo.getAbsolutePath() + ". Error message: " + e.getMessage() );
84+
}
85+
}
86+
87+
/**
88+
* Writes all timepoints and the number of divisions for each timepoint to the given file.
89+
* <ul>
90+
* <li>The file will be overwritten, if it already exists.</li>
91+
* <li>The file will be created, if it does not exist.</li>
92+
* <li>The format is: "timepoint", "divisions".</li>
93+
* <li>The first line is the header.</li>
94+
* </ul>
95+
*/
96+
public static void writeDivisionCountsToFile( final Model model, final File file, final StatusService statusService ) throws IOException
97+
{
98+
if ( file == null )
99+
throw new IllegalArgumentException( "Cannot write division counts to file. Given file is null." );
100+
101+
try (CSVWriter csvWriter = new CSVWriter( new FileWriter( file ) ))
102+
{
103+
csvWriter.writeNext( new String[] { "timepoint", "divisions" } );
104+
int minTimepoint = TreeUtils.getMinTimepoint( model );
105+
int maxTimepoint = TreeUtils.getMaxTimepoint( model );
106+
for ( int timepoint = minTimepoint; timepoint <= maxTimepoint; timepoint++ )
107+
{
108+
int divisions = 0;
109+
for ( Spot spot : model.getSpatioTemporalIndex().getSpatialIndex( timepoint ) )
110+
{
111+
if ( spot.outgoingEdges().size() > 1 )
112+
divisions++;
113+
}
114+
csvWriter.writeNext( new String[] { String.valueOf( timepoint ), String.valueOf( divisions ) }, false );
115+
statusService.showProgress( timepoint, maxTimepoint );
116+
}
117+
}
118+
}
119+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*-
2+
* #%L
3+
* mastodon-tomancak
4+
* %%
5+
* Copyright (C) 2018 - 2025 Tobias Pietzsch
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
package org.mastodon.mamut.tomancak.export;
30+
31+
import static org.junit.Assert.assertEquals;
32+
33+
import java.io.File;
34+
import java.io.IOException;
35+
36+
import org.apache.commons.io.FileUtils;
37+
import org.junit.Test;
38+
import org.mastodon.mamut.feature.branch.exampleGraph.ExampleGraph2;
39+
import org.mastodon.mamut.model.Model;
40+
import org.scijava.Context;
41+
import org.scijava.app.StatusService;
42+
43+
/**
44+
* Tests {@link ExportDivisionCountsPerTimepointCommand}
45+
*/
46+
public class ExportDivisionCountsPerTimepointCommandTest
47+
{
48+
49+
@Test
50+
public void testWriteDivisionCountsToFile() throws IOException
51+
{
52+
try (Context context = new Context())
53+
{
54+
Model model = new ExampleGraph2().getModel();
55+
File outputFile = File.createTempFile( "divisioncounts", ".csv" );
56+
outputFile.deleteOnExit();
57+
StatusService service = context.service( StatusService.class );
58+
59+
ExportDivisionCountsPerTimepointCommand.writeDivisionCountsToFile( model, outputFile, service );
60+
61+
String content = FileUtils.readFileToString( outputFile );
62+
String expected = "\"timepoint\",\"divisions\"\n"
63+
+ "0,0\n"
64+
+ "1,0\n"
65+
+ "2,1\n"
66+
+ "3,0\n"
67+
+ "4,1\n"
68+
+ "5,0\n"
69+
+ "6,0\n"
70+
+ "7,0\n";
71+
assertEquals( expected, content );
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)