From 291b067d5bf86d2d90f678f124d65e6fe7750c9a Mon Sep 17 00:00:00 2001 From: Amandine Tournay <amandine.tournay@pasteur.fr> Date: Tue, 9 Mar 2021 18:51:35 +0100 Subject: [PATCH] Added project --- .gitignore | 6 + pom.xml | 101 ++++ .../processors/TrackProcessorMSD.java | 532 ++++++++++++++++++ 3 files changed, 639 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/plugins/fab/trackmanager/processors/TrackProcessorMSD.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d47f98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea/ +target/ +.settings/ +*.iml +.project +.classpath \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..3932634 --- /dev/null +++ b/pom.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <!-- Inherited Icy Parent POM --> + <parent> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>parent-pom-plugin</artifactId> + <version>1.0.3</version> + </parent> + + <!-- Project Information --> + <artifactId>msd-track-processor</artifactId> + <version>1.1.1</version> + + <packaging>jar</packaging> + + <name>MSD Track Processor</name> + <description>Track Processor computing the MSD (Mean Square Displacement).</description> + <url>http://icy.bioimageanalysis.org/plugin/track-processor-msd/</url> + <inceptionYear>2020</inceptionYear> + + <organization> + <name>Institut Pasteur</name> + <url>https://pasteur.fr</url> + </organization> + + <licenses> + <license> + <name>GNU GPLv3</name> + <url>https://www.gnu.org/licenses/gpl-3.0.en.html</url> + <distribution>repo</distribution> + </license> + </licenses> + + <developers> + <developer> + <id>sdallongeville</id> + <name>Stéphane Dallongeville</name> + <url>https://research.pasteur.fr/fr/member/stephane-dallongeville/</url> + <roles> + <role>founder</role> + <role>lead</role> + <role>architect</role> + <role>developer</role> + <role>debugger</role> + <role>tester</role> + <role>maintainer</role> + <role>support</role> + </roles> + </developer> + </developers> + + <!-- Project properties --> + <properties> + + </properties> + + <!-- Project build configuration --> + <build> + + </build> + + <!-- List of project's dependencies --> + <dependencies> + <!-- The core of Icy --> + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>icy-kernel</artifactId> + </dependency> + + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>jfreechart</artifactId> + <version>1.5.3</version> + </dependency> + + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>spot-detection-utilities</artifactId> + <version>1.1.6</version> + </dependency> + + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>distance-profiler</artifactId> + <version>1.0.0</version> + </dependency> + </dependencies> + + <!-- Icy Maven repository (to find parent POM) --> + <repositories> + <repository> + <id>icy</id> + <name>Icy's Nexus</name> + <url>https://icy-nexus.pasteur.fr/repository/Icy/</url> + </repository> + </repositories> +</project> diff --git a/src/main/java/plugins/fab/trackmanager/processors/TrackProcessorMSD.java b/src/main/java/plugins/fab/trackmanager/processors/TrackProcessorMSD.java new file mode 100644 index 0000000..727c7f6 --- /dev/null +++ b/src/main/java/plugins/fab/trackmanager/processors/TrackProcessorMSD.java @@ -0,0 +1,532 @@ +package plugins.fab.trackmanager.processors; + +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.io.File; +import java.io.IOException; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.XYItemRenderer; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; + +import icy.canvas.IcyCanvas; +import icy.gui.util.GuiUtil; +import icy.painter.Painter; +import icy.preferences.GeneralPreferences; +import icy.sequence.Sequence; +import icy.util.StringUtil; +import icy.util.XLSUtil; +import jxl.write.WritableSheet; +import jxl.write.WritableWorkbook; +import jxl.write.WriteException; +import plugins.fab.trackmanager.PluginTrackManagerProcessor; +import plugins.fab.trackmanager.TrackSegment; +import plugins.nchenouard.spot.Detection; + +/** + * @author Fabrice de Chaumont + */ +public class TrackProcessorMSD extends PluginTrackManagerProcessor implements ActionListener, Painter +{ + JFreeChart chart; + JCheckBox displayLegendCheckBox = new JCheckBox("Display legend.", false); + JCheckBox displayGraphInSequenceCheckBox = new JCheckBox("Display graph on sequence.", false); + JButton useRoiAsBoundsForChartButton = new JButton("place graph in ROI #1"); + JCheckBox forceAllSequenceGraphWidthCheckBox = new JCheckBox("Force graph width.", false); + JCheckBox useRealScalesBox = new JCheckBox("use real scales", false); + JPanel chartpanel = new JPanel(); + JTextField scaleTextField = new JTextField("1.0"); + JButton exportButton = new JButton("export to console"); + JButton exportExcelButton = new JButton("export to excel"); + + public TrackProcessorMSD() + { + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); + chartpanel.add(new ChartPanel(chart, 500, 300, 500, 300, 500, 300, false, false, true, true, true, true)); + panel.add(GuiUtil.besidesPanel(chartpanel)); + panel.add(GuiUtil.besidesPanel(useRealScalesBox)); + useRealScalesBox.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + Compute(); + } + }); + panel.add(GuiUtil.besidesPanel(displayLegendCheckBox, forceAllSequenceGraphWidthCheckBox)); + // panel.add( GuiUtil.besidesPanel( displayGraphInSequenceCheckBox , useRoiAsBoundsForChartButton ) ); + // panel.add( GuiUtil.besidesPanel( new JLabel("Scale:") , scaleTextField ) ); + exportButton.addActionListener(this); + exportExcelButton.addActionListener(this); + panel.add(GuiUtil.besidesPanel(exportButton, exportExcelButton)); + useRoiAsBoundsForChartButton.addActionListener(this); + displayGraphInSequenceCheckBox.addActionListener(this); + displayLegendCheckBox.addActionListener(this); + forceAllSequenceGraphWidthCheckBox.addActionListener(this); + setName("Mean square displacement"); + // trackPool.getDisplaySequence().addPainter( this ); + } + + @Override + public void Compute() + { + if (!super.isEnabled()) + return; + + XYSeriesCollection xyDataset = new XYSeriesCollection(); + + final Sequence seq = trackPool.getDisplaySequence(); + final double tScale = (seq != null) ? seq.getTimeInterval() : 1d; + + if (useRealScalesBox.isSelected()) + { + for (TrackSegment ts : trackPool.getTrackSegmentList()) + { + XYSeries series = new XYSeries("Track " + trackPool.getTrackIndex(ts)); + + if (ts.isAllDetectionEnabled()) + { + double[] msd = scaledMeanSquaredDisplacement(seq, ts); + + for (int i = 0; i < msd.length; i++) + series.add(i * tScale, msd[i]); + + xyDataset.addSeries(series); + } + } + } + else + { + for (TrackSegment ts : trackPool.getTrackSegmentList()) + { + XYSeries series = new XYSeries("Track " + trackPool.getTrackIndex(ts)); + + if (ts.isAllDetectionEnabled()) + { + double[] msd = scaledMeanSquaredDisplacement(null, ts); + + for (int i = 0; i < msd.length; i++) + series.add(i, msd[i]); + + xyDataset.addSeries(series); + } + } + } + String TitleString = ""; + String TitleString2 = ""; + String TitleString3 = ""; + + if (displayLegendCheckBox.isSelected()) + { + if (useRealScalesBox.isSelected()) + { + TitleString = "Mean Square Displacement"; + TitleString2 = "Delta (s)"; + TitleString3 = "MSD (um^2)"; + } + else + { + TitleString = "Mean Square Displacement"; + TitleString2 = "Delta (frame)"; + TitleString3 = "MSD (pixel^2)"; + } + } + + chart = ChartFactory.createXYLineChart(TitleString, // chart title + TitleString2, // x axis label + TitleString3, // y axis label + xyDataset, // data + PlotOrientation.VERTICAL, displayLegendCheckBox.isSelected(), // include legend + true, // tooltips + false // urls + ); + + if (forceAllSequenceGraphWidthCheckBox.isSelected()) + { + XYSeries series = new XYSeries(""); + series.add(trackPool.getLastDetectionTimePoint(), 0); + xyDataset.addSeries(series); + } + + chartpanel.removeAll(); + + if (chart != null) + { + // replace default chart colors by detection colors (taken from t=0) + XYItemRenderer renderer = ((XYPlot) chart.getPlot()).getRenderer(); + for (TrackSegment ts : trackPool.getTrackSegmentList()) + renderer.setSeriesPaint(trackPool.getTrackIndex(ts), ts.getFirstDetection().getColor()); + } + chartpanel.add(new ChartPanel(chart, 400, 300, 400, 300, 400, 300, false, false, true, true, true, true)); + + chartpanel.updateUI(); + panel.updateUI(); + + } + + /** Export to console and optionaly in xls */ + private void exportMSD(boolean xlsExport) + { + WritableWorkbook workbook = null; + WritableSheet page = null; + String outputName; + + if ((trackPool.getDisplaySequence() == null) + || StringUtil.isEmpty(trackPool.getDisplaySequence().getFilename())) + outputName = GeneralPreferences.getResultFolder() + "Tracks"; + else + outputName = trackPool.getDisplaySequence().getFilename(); + + outputName += ".msd.xls"; + + if (xlsExport) + { + try + { + workbook = XLSUtil.createWorkbook(new File(outputName)); + } + catch (IOException e) + { + e.printStackTrace(); + } + + } + + if (workbook != null) + page = XLSUtil.createNewPage(workbook, "results"); + + final Sequence seq = trackPool.getDisplaySequence(); + final double tScale = (seq != null) ? seq.getTimeInterval() : 1d; + + if (useRealScalesBox.isSelected()) + { + int cnt = 0; + int row = 0; + for (TrackSegment ts : trackPool.getTrackSegmentList()) + { + if (ts.isAllDetectionEnabled()) + { + System.out.println("track " + cnt); + + if (page != null) + XLSUtil.setCellString(page, 0, row, "track " + cnt); + + double[] msd = scaledMeanSquaredDisplacement(seq, ts); + for (int i = 0; i < msd.length; i++) + { + System.out.println((i * tScale) + "\t" + msd[i]); + // System.out.println(i + "\t" + msd[i]); + + if (page != null) + XLSUtil.setCellNumber(page, i + 1, row, msd[i]); + } + + cnt++; + row++; + } + } + } + else + { + int cnt = 0; + int row = 0; + + for (TrackSegment ts : trackPool.getTrackSegmentList()) + { + if (ts.isAllDetectionEnabled()) + { + System.out.println("track " + cnt); + + if (page != null) + XLSUtil.setCellString(page, 0, row, "track " + cnt); + + double[] msd = scaledMeanSquaredDisplacement(null, ts); + // double[] msd = meanSquaredDisplacement(ts); + + for (int i = 0; i < msd.length; i++) + { + System.out.println(i + "\t" + msd[i]); + + if (page != null) + XLSUtil.setCellNumber(page, i + 1, row, msd[i]); + } + + cnt++; + row++; + } + } + } + + if (workbook != null) + { + try + { + XLSUtil.saveAndClose(workbook); + } + catch (WriteException e) + { + e.printStackTrace(); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + Rectangle2D chartRectangleInSequence = new Rectangle2D.Float(250, 20 + 260 * 0, 490, 240); + + JLabel outLabel = new JLabel(); + + private double[] scaledMeanSquaredDisplacement(Sequence seq, TrackSegment ts) + { + final double sx, sy, sz; + + if (seq != null) + { + sx = seq.getPixelSizeX(); + sy = seq.getPixelSizeY(); + sz = seq.getPixelSizeZ(); + } + else + { + sx = 1d; + sy = 1d; + sz = 1d; + } + + int nMSD = ts.getDetectionList().size(); + // double[] dt_n = new double[nMSD]; + double[] msd = new double[nMSD]; + + for (int t = 1; t < nMSD; t++) + { + msd[t] = 0d; + for (int j = 1; j <= t; j++) + msd[t] += scaledSquaredDistance(ts.getDetectionAt(0), ts.getDetectionAt(j), sx, sy, sz); + + msd[t] /= t; + } + + // for (int dt = 1; dt < nMSD; dt++) + // { + // for (int j = 0; (j + dt) < nMSD; j += dt) + // { + // msd[dt] += scaledSquaredDistance(ts.getDetectionAt(j), ts.getDetectionAt(j + dt), sx, sy, sz); + // dt_n[dt]++; + // } + // } + // + // for (int dt = 1; dt < nMSD; dt++) + // msd[dt] = (dt_n[dt] != 0) ? msd[dt] / dt_n[dt] : 0; + + return msd; + } + + // mean squared displacement + private double[] meanSquaredDisplacement(TrackSegment ts) + { + int nMSD = ts.getDetectionList().size(); + + double[] dt_n = new double[nMSD]; + double[][] msd = new double[nMSD][2]; + + for (int dt = 1; dt < nMSD; dt++) + { + msd[dt][0] = dt * 1; + + for (int j = 0; j + dt < ts.getDetectionList().size(); j += dt) + { + msd[dt][1] += squaredDistance(ts.getDetectionAt(j), ts.getDetectionAt(j + dt)); + dt_n[dt]++; + } + } + + for (int dt = 1; dt < nMSD; dt++) + msd[dt][1] = (dt_n[dt] != 0) ? msd[dt][1] / dt_n[dt] : 0; + + double[] resultmsd = new double[nMSD]; + resultmsd[0] = 0; + for (int dt = 1; dt < nMSD; dt++) + resultmsd[dt] = msd[dt][1]; + + return resultmsd; + + } + + private static double squaredDistance(Detection d1, Detection d2) + { + return scaledSquaredDistance(d1, d2, 1d, 1d, 1d); + } + + private static double scaledSquaredDistance(Detection d1, Detection d2, double sx, double sy, double sz) + { + return Math.pow((d1.getX() - d2.getX()) * sx, 2) + Math.pow((d1.getY() - d2.getY()) * sy, 2) + + Math.pow((d1.getZ() - d2.getZ()) * sz, 2); + } + + // public static double getScaledDistance(double x1, double y1, double z1, double x2, double y2, double z2) + // { + // return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2)); + // } + // + // public static double getDistance(double x1, double y1, double z1, double x2, double y2, double z2) + // { + // return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + (z1 - z2) * (z1 - z2)); + // } + // + // public double getDistance(Detection d1, Detection d2) + // { + // return getDistance(d1.getX(), d1.getY(), d1.getZ(), d2.getX(), d2.getY(), d2.getZ()); + // } + + @Override + public void Close() + { + Sequence sequence = trackPool.getDisplaySequence(); + if (sequence != null) + { + sequence.removePainter(this); + } + } + + public void actionPerformed(ActionEvent e) + { + + // if ( e.getSource() == useRoiAsBoundsForChartButton ) + // { + // Shape shape = (Shape) trackPool.getDisplaySequence().getROIs().get( 0 ); + // chartRectangleInSequence = (Rectangle2D) shape.getBounds2D().clone(); + // } + if (e.getSource() == exportButton) + { + exportMSD(false); + } + + if (e.getSource() == exportExcelButton) + { + exportMSD(true); + } + + trackPool.fireTrackEditorProcessorChange(); + + } + + public void keyPressed(Point p, KeyEvent e) + { + + } + + public void mouseClick(Point p, MouseEvent e) + { + + } + + public void mouseDrag(Point p, MouseEvent e) + { + + } + + public void mouseMove(Point p, MouseEvent e) + { + + } + + @Override + public void displaySequenceChanged() + { + + } + + @Override + public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) + { + + double scale = Double.parseDouble(scaleTextField.getText()); + double minX = chartRectangleInSequence.getCenterX(); + double minY = chartRectangleInSequence.getCenterY(); + + Rectangle2D transformedChartRectangleInSequence = (Rectangle2D) chartRectangleInSequence.clone(); + transformedChartRectangleInSequence.setRect((-chartRectangleInSequence.getWidth() / 2) * (1d / scale), + (-chartRectangleInSequence.getHeight() / 2) * (1d / scale), + chartRectangleInSequence.getWidth() * (1d / scale), + chartRectangleInSequence.getHeight() * (1d / scale)); + + Graphics2D g2 = (Graphics2D) g; + + AffineTransform transform = g2.getTransform(); + g2.scale(scale, scale); + g2.translate(minX * (1d / scale), minY * (1d / scale)); + + if (displayGraphInSequenceCheckBox.isSelected()) + chart.draw((Graphics2D) g, transformedChartRectangleInSequence); + + g2.setTransform(transform); + + // if ( displayGraphInSequenceCheckBox.isSelected() ) + // chart.draw( (Graphics2D)g , chartRectangleInSequence ); + + } + + @Override + public void mousePressed(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) + { + + } + + @Override + public void mouseReleased(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) + { + + } + + @Override + public void mouseClick(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) + { + + } + + @Override + public void mouseMove(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) + { + + } + + @Override + public void mouseDrag(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) + { + + } + + @Override + public void keyPressed(KeyEvent e, Point2D imagePoint, IcyCanvas canvas) + { + + } + + @Override + public void keyReleased(KeyEvent e, Point2D imagePoint, IcyCanvas canvas) + { + + } + +} -- GitLab