package com.github.celldynamics.quimp.plugin.protanalysis;
import java.awt.Color;
import java.awt.Point;
import java.awt.Polygon;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.celldynamics.quimp.plugin.protanalysis.ProtAnalysisOptions.OutlinesToImage;
import com.github.celldynamics.quimp.plugin.qanalysis.STmap;
import com.github.celldynamics.quimp.utils.graphics.GraphicsElements;
import ij.ImagePlus;
import ij.ImageStack;
import ij.gui.Overlay;
import ij.gui.PointRoi;
import ij.gui.PolygonRoi;
import ij.plugin.ZProjector;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;
/**
* Support various methods of visualising protrusion data.
*
* In general all plots are added to image used to construct this object as overlay layer.
*
*
This super class contains methods for creating static plots in [x,y] domain from coordinates
* in [outline,frame] system (native for maps generated by Qanalysis).
*
*
Warning
*
*
It is assumed that plotted maps have frames on x-axis and indexes on y-axis
*
* @author p.baniukiewicz
*
*/
public abstract class TrackVisualisation {
/**
* The Constant LOGGER.
*/
static final Logger LOGGER = LoggerFactory.getLogger(TrackVisualisation.class.getName());
/**
* Radius of circles plotted.
*/
public double circleRadius = 7.;
/**
* Color for maxima points.
*/
public static Color MAXIMA_COLOR = Color.MAGENTA;
/**
* Definition of colors used to plot tracks.
*
*
These are:
*
* - index 0 - backtracked position of point
*
- index 1 - forwardtracked position of point.
*
- index 2 - other
*
*/
public static Color[] color = { Color.YELLOW, Color.GREEN, Color.WHITE };
/**
* The original image.
*/
protected ImagePlus originalImage; // reference of image to be plotted on
/**
* The overlay.
*/
protected Overlay overlay;
/**
* Create correct object.
*
* If input image contains any overlay data, they will be extended by new plots.
*
* @param originalImage Image to be plotted on.
*/
public TrackVisualisation(ImagePlus originalImage) {
this.originalImage = originalImage;
LOGGER.trace("Num of slices: " + originalImage.getStackSize());
overlay = originalImage.getOverlay(); // check for existing overlay
if (overlay == null) {
overlay = new Overlay();
}
}
/**
* Construct object from raw ImageProcessor.
*
* @param name Name of the image
* @param imp ImageProcessor
*/
public TrackVisualisation(String name, ImageProcessor imp) {
this(new ImagePlus(name, imp));
}
/**
* Plot filled circle on overlay.
*
* @param x center
* @param y center
* @param color color
* @param radius radius
*/
public void plotCircle(double x, double y, Color color, double radius) {
// create ROI
PolygonRoi or = GraphicsElements.getCircle(x, y, color, radius);
overlay.add(or); // add to collection of overlays
}
/**
* getOriginalImage.
*
* @return the originalImage
*/
public ImagePlus getOriginalImage() {
return originalImage;
}
/**
* Helper method.
*
*
Allows to convert enum to index of array of Colors.
*
* @param track track
* @return Color from color array
*/
protected Color getColor(Track track) {
Color c;
Track.TrackType type = track.type;
switch (type) {
case FORWARD:
c = color[1];
break;
case BACKWARD:
c = color[0];
break;
case OTHER:
c = color[2];
break;
default:
throw new IllegalArgumentException("Color not supported");
}
return c;
}
/**
* Flatten stack according to given type.
*
*
Output has the same resolution (x,y,t) as input. For stacks, slices are duplicated. Refer to
* ij.plugin.ZProjector.setMethod(int)
*
* @param method How to flatten - ZProjector methods.
* @param preserveStack - if true size of output stack is preserved (slices are
* duplicated to form stack with the same number of slices as original one). Otherwise
* only one slice is built
*/
public void flatten(int method, boolean preserveStack) {
ImageStack is = originalImage.getStack();
is = is.convertToFloat(); // for better averaging
ZProjector zp = new ZProjector(new ImagePlus(originalImage.getTitle(), is));
zp.setStartSlice(1);
zp.setStopSlice(originalImage.getStackSize());
zp.setMethod(method);
zp.doProjection();
ImagePlus ret = zp.getProjection();
// recreate stack if needed
if (originalImage.getStackSize() > 1 && preserveStack) {
ImageStack imS = new ImageStack(ret.getWidth(), ret.getHeight());
for (int s = 0; s < originalImage.getStackSize(); s++) {
imS.addSlice(ret.getProcessor().convertToByte(true));
}
originalImage = new ImagePlus(originalImage.getTitle(), imS);
} else { // return only one slice (due to input format or preserveStack flag status)
originalImage =
new ImagePlus(originalImage.getTitle(), ret.getProcessor().convertToByte(true));
}
}
/**
* Clear overlay.
*/
public void clear() {
if (overlay != null) {
overlay.clear();
}
}
/**
* Subclass for plotting on single image in coord space [outline,frame].
*
* @author p.baniukiewicz
*
*/
static class Map extends TrackVisualisation {
/**
* Denote if map is rotated.
*
*
QuimP maps returned by
* {@link STmap#map2ColorImagePlus(String, String, double[][], double, double)} are rotated in
* relation to raw map returned by {@link STmap#getMotMap()}. If this filed is false it means
* raw maps, use true for ImagePlus map.
*
*
Internally all tracks refer to map that has time on x axis (rotated==false).
*
* @see STmap#map2ImagePlus(String, ImageProcessor)
*/
private boolean rotated = false;
// scales if maps are scaled by map2ColorImagePlus
private double ts = 1.0;
private double os = 1.0;
/**
* Instantiates a new map from ImagePlus.
*
*
Note that x-coords must be time and y - outline which is reverse to QuimP format.
*
* @param originalImage the original image
*/
public Map(ImagePlus originalImage) {
super(originalImage);
}
/**
* Instantiates a new map from ImagePlus.
*
*
Should be used with output from
* {@link STmap#map2ColorImagePlus(String, String, double[][], double, double)} after setting
* correct scales. Typically ts = 400/frames and os = 1
*
* @param originalImage the original image
* @param rotated true if map is rotated so time is y and outline is x. Typically map is output
* from {@link STmap#map2ColorImagePlus(String, String, double[][], double, double)}
* @param ts time scale if map is scaled as abobe
* @param os outline scale
*/
public Map(ImagePlus originalImage, boolean rotated, double ts, double os) {
super(originalImage);
this.rotated = rotated;
this.ts = ts;
this.os = os;
}
/**
* Instantiates a new map.
*
* @param name the name
* @param imp the imp
*/
public Map(String name, ImageProcessor imp) {
super(name, imp);
}
/**
* Create object from raw data like e.g. motility map.
*
* @param name Name of the image
* @param data 2D data.
*/
public Map(String name, float[][] data) {
super(name, new FloatProcessor(data));
}
/**
* Plot unrelated points on image (static).
*
* @param points list of points to plot in coordinates (index,frame)
* @param color color of point
* @param radius radius of point
*/
public void addCirclesToImage(Polygon points, Color color, double radius) {
Polygon polsc = scale(points); // time scale
int[] indexes = polsc.ypoints;
int[] frames = polsc.xpoints;
for (int n = 0; n < points.npoints; n++) {
// decode frame,outline to screen coordinates
if (frames[n] < 0 || indexes[n] < 0) {
continue;
}
plotCircle(frames[n], indexes[n], color, radius);
}
originalImage.setOverlay(overlay); // add to image
}
/**
* Plot maxima found by {@link MaximaFinder} on current image.
*
* @param maxF properly initialized {@link MaximaFinder} object.
*/
public void addMaximaToImage(MaximaFinder maxF) {
Polygon max = maxF.getMaxima();
Polygon polsc = scale(max); // time scale
PointRoi pr = GraphicsElements.getPoint(polsc, TrackVisualisation.MAXIMA_COLOR);
overlay.add(pr);
originalImage.setOverlay(overlay);
}
private int[] scale(int[] in, int len, double sc) {
int[] ret = new int[len];
for (int i = 0; i < len; i++) {
ret[i] = (int) Math.round(in[i] * sc);
}
return ret;
}
private Polygon scale(Polygon in) {
int[] xp;
int[] yp;
if (rotated) {
yp = scale(in.xpoints, in.npoints, ts);
xp = scale(in.ypoints, in.npoints, os);
} else {
xp = scale(in.xpoints, in.npoints, ts);
yp = scale(in.ypoints, in.npoints, os);
}
return new Polygon(xp, yp, xp.length);
}
/**
* Add lines defined as polygons to image.
*
* @param trackCollection initialised TrackCollection object
*
*/
public void addTrackingLinesToImage(TrackCollection trackCollection) {
Iterator> it = trackCollection.iterator();
while (it.hasNext()) {
Pair