Skip to content
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
2 changes: 2 additions & 0 deletions .github/build.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/bin/sh
export DISPLAY=:99
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
curl -fsLO https://raw.githubusercontent.com/scijava/scijava-scripts/main/ci-build.sh
sh ci-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@
import java.util.List;

import org.apache.commons.lang3.tuple.Triple;
import org.mastodon.mamut.ProjectModel;
import org.mastodon.mamut.model.Link;
import org.mastodon.mamut.model.Model;
import org.mastodon.mamut.model.Spot;
import org.mastodon.model.SelectionModel;
import org.mastodon.util.TreeUtils;

public class SpotAndDivisionCount
Expand All @@ -46,23 +49,33 @@ private SpotAndDivisionCount()
/**
* Calculates the number of spots and divisions per time point in the given model.
*
* @param model The model containing the spots and edges.
* @param projectModel The project model containing the spots and edges.
* @param onlySelectedSpots If {@code true}, only counts spots that are selected.
* @return A list of triples, where each triple contains (in that order) the timepoint, the number of spots at that timepoint, and the number of divisions at that timepoint.
*/
public static List< Triple< Integer, Integer, Integer > > getSpotAndDivisionsPerTimepoint( final Model model )
public static List< Triple< Integer, Integer, Integer > > getSpotAndDivisionsPerTimepoint( final ProjectModel projectModel,
final boolean onlySelectedSpots )
{
final Model model = projectModel.getModel();
final SelectionModel< Spot, Link > selectionModel = projectModel.getSelectionModel();
int minTimepoint = TreeUtils.getMinTimepoint( model );
int maxTimepoint = TreeUtils.getMaxTimepoint( model );
List< Triple< Integer, Integer, Integer > > timepointAndDivisions = new ArrayList<>();
for ( int timepoint = minTimepoint; timepoint <= maxTimepoint; timepoint++ )
{
int spots = 0;
int divisions = 0;
for ( Spot spot : model.getSpatioTemporalIndex().getSpatialIndex( timepoint ) )
{
if ( onlySelectedSpots && !selectionModel.isSelected( spot ) )
continue;
if ( spot.outgoingEdges().size() > 1 )
divisions++;
if ( onlySelectedSpots )
spots++;
}
int spots = model.getSpatioTemporalIndex().getSpatialIndex( timepoint ).size();
if ( !onlySelectedSpots )
spots = model.getSpatioTemporalIndex().getSpatialIndex( timepoint ).size();
timepointAndDivisions.add( Triple.of( timepoint, spots, divisions ) );
}
return timepointAndDivisions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@
import java.awt.geom.Rectangle2D;
import java.util.List;

import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.WindowConstants;
Expand All @@ -62,9 +64,10 @@
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.mastodon.mamut.ProjectModel;
import org.mastodon.model.SelectionListener;
import org.scijava.prefs.PrefService;

public class SpotAndDivisionCountChart extends JFrame
public class SpotAndDivisionCountChart extends JFrame implements SelectionListener
{

private static final String SPOT_COLOR = "spotColor";
Expand All @@ -83,19 +86,25 @@ public class SpotAndDivisionCountChart extends JFrame

private static final String DIVISION_COUNT_SLIDING_AVERAGE_WINDOW_SIZE = "divisionCountSlidingAverageWindowSize";

private static final String ONLY_SELECTED_SPOTS = "onlySelectedSpots";

private static final int SPOT_COUNT_DEFAULT_COLOR = new Color( 230, 159, 0 ).getRGB(); // Dark Orange

private static final int DIVISION_COUNT_DEFAULT_COLOR = new Color( 86, 180, 233 ).getRGB(); // Light Blue

private static final int DEFAULT_SLIDING_WINDOW_SIZE = 10;

public static final String GROW_X = "growx";

private Color spotCountColor; // Dark Orange

private Color divisionCountColor; // Light Blue

private final static String TITLE = "Spot and Division Counts over Time";
private final static String SPOTS_COUNT_SERIES_NAME = "Spot Counts";
private final static String DIVISION_COUNT_SERIES_NAME = "Division Counts";
private static final String TITLE = "Spot and Division Counts over Time";

private static final String SPOTS_COUNT_SERIES_NAME = "Spot Counts";

private static final String DIVISION_COUNT_SERIES_NAME = "Division Counts";

private final XYPlot plot;

Expand All @@ -107,11 +116,20 @@ public class SpotAndDivisionCountChart extends JFrame

private int divisionWindowSize;

private boolean onlySelectedSpots;

private final PrefService prefs;

SpotAndDivisionCountChart( final double[] timepoints, final double[] spotCounts, final double[] divisionCounts,
final PrefService prefs )
private final ProjectModel projectModel;

private XYSeriesCollection spotCountsSeries;

private XYSeriesCollection divisionCountsSeries;

SpotAndDivisionCountChart( final ProjectModel projectModel, final PrefService prefs )
{
this.projectModel = projectModel;
this.projectModel.getSelectionModel().listeners().add( this );
this.prefs = prefs;

this.spotCountColor = new Color(
Expand All @@ -122,12 +140,9 @@ public class SpotAndDivisionCountChart extends JFrame
prefs.getInt( SpotAndDivisionCountChart.class, SPOT_COUNT_SLIDING_AVERAGE_WINDOW_SIZE, DEFAULT_SLIDING_WINDOW_SIZE );
this.divisionWindowSize =
prefs.getInt( SpotAndDivisionCountChart.class, DIVISION_COUNT_SLIDING_AVERAGE_WINDOW_SIZE, DEFAULT_SLIDING_WINDOW_SIZE );
this.onlySelectedSpots = prefs.getBoolean( SpotAndDivisionCountChart.class, ONLY_SELECTED_SPOTS, false );

XYSeriesCollection spotCountsSeries = createSeries(
timepoints, spotCounts, SPOTS_COUNT_SERIES_NAME, spotWindowSize );

XYSeriesCollection divisionCountsSeries = createSeries(
timepoints, divisionCounts, DIVISION_COUNT_SERIES_NAME, divisionWindowSize );
updateChartData();

JFreeChart chart = ChartFactory.createXYLineChart(
TITLE,
Expand Down Expand Up @@ -168,7 +183,7 @@ public class SpotAndDivisionCountChart extends JFrame
chartPanel.setPreferredSize( new Dimension( 800, 600 ) );

// Add color chooser and visibility controls
JPanel controlPanel = createControlPanel( timepoints, spotCounts, divisionCounts );
JPanel controlPanel = createControlPanel();

// Set up the frame layout
setLayout( new BorderLayout() );
Expand All @@ -184,23 +199,34 @@ public class SpotAndDivisionCountChart extends JFrame
repaint();
}

public static void show( final ProjectModel projectModel, final PrefService prefService )
private void updateChartData()
{
List< Triple< Integer, Integer, Integer > > divisionCounts =
SpotAndDivisionCount.getSpotAndDivisionsPerTimepoint( projectModel.getModel() );
double[] timepoints = divisionCounts.stream().mapToDouble( Triple::getLeft ).toArray();
double[] spots = divisionCounts.stream().mapToDouble( Triple::getMiddle ).toArray();
double[] divisions = divisionCounts.stream().mapToDouble( Triple::getRight ).toArray();
List< Triple< Integer, Integer, Integer > > counts =
SpotAndDivisionCount.getSpotAndDivisionsPerTimepoint( projectModel, this.onlySelectedSpots );
double[] timepoints = counts.stream().mapToDouble( Triple::getLeft ).toArray();
double[] spotCounts = counts.stream().mapToDouble( Triple::getMiddle ).toArray();
double[] divisionCounts = counts.stream().mapToDouble( Triple::getRight ).toArray();
spotCountsSeries = createSeries( timepoints, spotCounts, SPOTS_COUNT_SERIES_NAME, spotWindowSize );
divisionCountsSeries = createSeries( timepoints, divisionCounts, DIVISION_COUNT_SERIES_NAME, divisionWindowSize );

if ( plot != null )
{
plot.setDataset( 0, spotCountsSeries );
plot.setDataset( 1, divisionCountsSeries );
}
}

SpotAndDivisionCountChart chart = new SpotAndDivisionCountChart( timepoints, spots, divisions, prefService );
public static void show( final ProjectModel projectModel, final PrefService prefService )
{
SpotAndDivisionCountChart chart = new SpotAndDivisionCountChart( projectModel, prefService );
chart.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE );
chart.setVisible( true );
}

/**
* Creates a control panel with color choosers and checkboxes for visibility controls.
*/
private JPanel createControlPanel( final double[] timepoints, final double[] spotCounts, final double[] divisionCounts )
private JPanel createControlPanel()
{
JPanel controlPanel = new JPanel( new MigLayout( "fill, wrap 5", "[grow]", "[]10[]10[]10[]10[]" ) );

Expand Down Expand Up @@ -237,7 +263,7 @@ private JPanel createControlPanel( final double[] timepoints, final double[] spo
spotWindowSpinner.addChangeListener( e -> {
spotWindowSize = ( int ) spotWindowSpinner.getValue();
prefs.put( SpotAndDivisionCountChart.class, SPOT_COUNT_SLIDING_AVERAGE_WINDOW_SIZE, spotWindowSize );
updateSlidingAverage( timepoints, spotCounts, divisionCounts );
updateChartData();
} );

// Division-related controls
Expand Down Expand Up @@ -273,22 +299,43 @@ private JPanel createControlPanel( final double[] timepoints, final double[] spo
divisionWindowSpinner.addChangeListener( e -> {
divisionWindowSize = ( int ) divisionWindowSpinner.getValue();
prefs.put( SpotAndDivisionCountChart.class, DIVISION_COUNT_SLIDING_AVERAGE_WINDOW_SIZE, divisionWindowSize );
updateSlidingAverage( timepoints, spotCounts, divisionCounts );
updateChartData();
} );

JRadioButton allSpots = new JRadioButton( "All spots", !onlySelectedSpots );
JRadioButton selectedSpots = new JRadioButton( "Only selected spots", onlySelectedSpots );
ButtonGroup group = new ButtonGroup();
group.add( allSpots );
group.add( selectedSpots );
allSpots.addActionListener( e -> {
onlySelectedSpots = false;
prefs.put( SpotAndDivisionCountChart.class, ONLY_SELECTED_SPOTS, onlySelectedSpots );
updateChartData();
repaint();
} );
selectedSpots.addActionListener( e -> {
this.onlySelectedSpots = true;
prefs.put( SpotAndDivisionCountChart.class, ONLY_SELECTED_SPOTS, onlySelectedSpots );
updateChartData();
repaint();
} );

// Add components to the control panel
controlPanel.add( spotColorButton, "growx" );
controlPanel.add( showSpotCounts, "growx" );
controlPanel.add( showSpotAverage, "growx" );
controlPanel.add( spotColorButton, GROW_X );
controlPanel.add( showSpotCounts, GROW_X );
controlPanel.add( showSpotAverage, GROW_X );
controlPanel.add( new JLabel( "Window Size:" ), "align right" );
controlPanel.add( spotWindowSpinner, "wmax 50" );

controlPanel.add( divisionColorButton, "growx" );
controlPanel.add( showDivisionCounts, "growx" );
controlPanel.add( showDivisionAverage, "growx" );
controlPanel.add( divisionColorButton, GROW_X );
controlPanel.add( showDivisionCounts, GROW_X );
controlPanel.add( showDivisionAverage, GROW_X );
controlPanel.add( new JLabel( "Window Size:" ), "align right" );
controlPanel.add( divisionWindowSpinner, "wmax 50" );

controlPanel.add( allSpots, GROW_X );
controlPanel.add( selectedSpots, "span, growx" );

// Add description
controlPanel.add( new JLabel(
"<html>This windows shows the number of spots and divisions at each timepoint together with a sliding average.<br>A division is defined as a spot with more than one outgoing edge.</html>" ),
Expand Down Expand Up @@ -331,27 +378,23 @@ private void updateChartColors()
*/
private void updateAxisVisibility()
{
XYItemRenderer spotCountRenderer = plot.getRenderer( 0 );
Boolean spotCountVisible = spotCountRenderer.getSeriesVisible( 0 );
Boolean spotAverageVisible = spotCountRenderer.getSeriesVisible( 1 );
boolean isSpotCountVisible = spotCountVisible != null && spotCountVisible;
boolean isSpotAverageVisible = spotAverageVisible != null && spotAverageVisible;
boolean showSpotAxis = isSpotCountVisible || isSpotAverageVisible;
leftAxis.setLabel( showSpotAxis ? SPOTS_COUNT_SERIES_NAME : null );
leftAxis.setVisible( showSpotAxis );

XYItemRenderer divisionCountRenderer = plot.getRenderer( 1 );
Boolean divisionCountVisible = divisionCountRenderer.getSeriesVisible( 0 );
Boolean divisionAverageVisible = divisionCountRenderer.getSeriesVisible( 1 );
boolean isDivisionCountVisible = divisionCountVisible != null && divisionCountVisible;
boolean isDivisionAverageVisible = divisionAverageVisible != null && divisionAverageVisible;
boolean showDivisionAxis = isDivisionCountVisible || isDivisionAverageVisible;
rightAxis.setLabel( showDivisionAxis ? DIVISION_COUNT_SERIES_NAME : null );
rightAxis.setVisible( showDivisionAxis );

updateAxisVisibility( plot.getRenderer( 0 ), leftAxis, SPOTS_COUNT_SERIES_NAME );
updateAxisVisibility( plot.getRenderer( 1 ), rightAxis, DIVISION_COUNT_SERIES_NAME );
repaint();
}

private void updateAxisVisibility( final XYItemRenderer renderer, final NumberAxis axis,
final String labelIfVisible )
{
Boolean countVisible = renderer.getSeriesVisible( 0 );
Boolean averageVisible = renderer.getSeriesVisible( 1 );
boolean isCountVisible = countVisible != null && countVisible;
boolean isAverageVisible = averageVisible != null && averageVisible;
boolean showAxis = isCountVisible || isAverageVisible;
axis.setLabel( showAxis ? labelIfVisible : null );
axis.setVisible( showAxis );
}

private XYLineAndShapeRenderer createRenderer( final Color color, final Shape shape )
{
XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
Expand Down Expand Up @@ -406,20 +449,6 @@ private static XYSeriesCollection createSeries( final double[] xValues, final do
return dataset;
}

/**
* Updates the sliding average series based on the new window sizes.
*/
private void updateSlidingAverage( double[] timepoints, double[] spotCounts, double[] divisionCounts )
{
XYSeriesCollection spotCountsSeries = createSeries(
timepoints, spotCounts, SPOTS_COUNT_SERIES_NAME, spotWindowSize );
XYSeriesCollection divisionCountsSeries = createSeries(
timepoints, divisionCounts, DIVISION_COUNT_SERIES_NAME, divisionWindowSize );

plot.setDataset( 0, spotCountsSeries );
plot.setDataset( 1, divisionCountsSeries );
}

private static double[] calculateSlidingAverage( double[] values, int windowSize )
{
double[] result = new double[ values.length ];
Expand All @@ -433,4 +462,10 @@ private static double[] calculateSlidingAverage( double[] values, int windowSize
}
return result;
}

@Override
public void selectionChanged()
{
updateChartData();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@

import org.apache.commons.lang3.tuple.Triple;
import org.mastodon.mamut.ProjectModel;
import org.mastodon.mamut.model.Model;
import org.mastodon.mamut.tomancak.divisioncount.SpotAndDivisionCount;
import org.scijava.Context;
import org.scijava.ItemVisibility;
Expand Down Expand Up @@ -75,7 +74,7 @@ public void run()
{
try
{
writeDivisionCountsToFile( projectModel.getModel(), saveTo, context.service( StatusService.class ) );
writeDivisionCountsToFile( projectModel, saveTo, context.service( StatusService.class ) );
}
catch ( IOException e )
{
Expand All @@ -93,7 +92,8 @@ public void run()
* <li>The first line is the header.</li>
* </ul>
*/
public static void writeDivisionCountsToFile( final Model model, final File file, final StatusService statusService ) throws IOException
public static void writeDivisionCountsToFile( final ProjectModel projectModel, final File file, final StatusService statusService )
throws IOException
{
if ( file == null )
throw new IllegalArgumentException( "Cannot write division counts to file. Given file is null." );
Expand All @@ -102,7 +102,7 @@ public static void writeDivisionCountsToFile( final Model model, final File file
{
csvWriter.writeNext( new String[] { "timepoint", "divisions" } );
List< Triple< Integer, Integer, Integer > > timepointAndDivisions =
SpotAndDivisionCount.getSpotAndDivisionsPerTimepoint( model );
SpotAndDivisionCount.getSpotAndDivisionsPerTimepoint( projectModel, false );
for ( Triple< Integer, Integer, Integer > pair : timepointAndDivisions )
{
csvWriter.writeNext( new String[] { String.valueOf( pair.getLeft() ), String.valueOf( pair.getRight() ) }, false );
Expand Down
Loading