package ij.plugin;
import ij.*;
import ij.gui.*;
import ij.process.*;
import ij.measure.*;
import ij.plugin.filter.Analyzer;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.util.*;
public class GelAnalyzer implements PlugIn {
static final String OPTIONS = "gel.options";
static final String VSCALE = "gel.vscale";
static final String HSCALE = "gel.hscale";
static final int OD=1, PERCENT=2, OUTLINE=4, INVERT=8;
static int saveID;
static int nLanes, saveNLanes;
static Rectangle firstRect;
static final int MAX_LANES = 100;
static int[] x = new int[MAX_LANES+1];
static PlotsCanvas plotsCanvas;
static ImageProcessor ipLanes;
static ImagePlus gel;
static int plotHeight;
static int options = (int)Prefs.get(OPTIONS, PERCENT+INVERT);
static boolean uncalibratedOD = (options&OD)!=0;
static boolean labelWithPercentages = (options&PERCENT)!=0;;
static boolean outlineLanes;
static boolean invertPeaks = (options&INVERT)!=0;
static double verticalScaleFactor = Prefs.get(VSCALE, 1.0);
static double horizontalScaleFactor = Prefs.get(HSCALE, 1.0);
static Overlay overlay;
boolean invertedLut;
ImagePlus imp;
Font f;
double odMin=Double.MAX_VALUE, odMax=-Double.MAX_VALUE;
static boolean isVertical;
static boolean showLaneDialog = true;
public void run(String arg) {
if (arg.equals("options")) {
showDialog();
return;
}
imp = WindowManager.getCurrentImage();
if (imp==null) {
IJ.noImage();
return;
}
if (arg.equals("reset")) {
nLanes = 0;
saveNLanes = 0;
saveID = 0;
if (plotsCanvas!=null)
plotsCanvas.reset();
ipLanes = null;
overlay = null;
if (gel!=null) {
ImageCanvas ic = gel.getCanvas();
if (ic!=null) ic.setDisplayList(null);
gel.draw();
}
return;
}
if (arg.equals("percent") && plotsCanvas!=null) {
plotsCanvas.displayPercentages();
return;
}
if (arg.equals("label") && plotsCanvas!=null) {
if (plotsCanvas.counter==0)
show("There are no peak area measurements.");
else
plotsCanvas.labelPeaks();
return;
}
if (imp.getID()!=saveID) {
nLanes=0;
ipLanes = null;
saveID = 0;
}
if (arg.equals("replot")) {
if (saveNLanes==0) {
show("The data needed to re-plot the lanes is not available");
return;
}
nLanes = saveNLanes;
plotLanes(gel, true);
return;
}
if (arg.equals("draw")) {
outlineLanes();
return;
}
Roi roi = imp.getRoi();
if (roi==null || roi.getType()!=Roi.RECTANGLE) {
show("Rectangular selection required.");
return;
}
Rectangle rect = roi.getBounds();
if (nLanes==0) {
invertedLut = imp.isInvertedLut();
IJ.register(GelAnalyzer.class); }
if (arg.equals("first")) {
selectFirstLane(rect);
return;
}
if (nLanes==0) {
show("You must first use the \"Select First Lane\" command.");
return;
}
if (arg.equals("next")) {
selectNextLane(rect);
return;
}
if (arg.equals("plot")) {
if (( isVertical && (rect.x!=x[nLanes]) ) || ( !(isVertical) && (rect.y!=x[nLanes]) )) {
selectNextLane(rect);
}
plotLanes(gel, false);
return;
}
}
void showDialog() {
GenericDialog gd = new GenericDialog("Gel Analyzer");
gd.addNumericField("Vertical scale factor:", verticalScaleFactor, 1);
gd.addNumericField("Horizontal scale factor:", horizontalScaleFactor, 1);
gd.addCheckbox("Uncalibrated OD", uncalibratedOD);
gd.addCheckbox("Label with percentages", labelWithPercentages);
gd.addCheckbox("Invert peaks", invertPeaks);
gd.addHelp(IJ.URL+"/docs/menus/analyze.html#gels");
gd.showDialog();
if (gd.wasCanceled())
return;
verticalScaleFactor = gd.getNextNumber();
horizontalScaleFactor = gd.getNextNumber();
uncalibratedOD = gd.getNextBoolean();
labelWithPercentages = gd.getNextBoolean();
invertPeaks = gd.getNextBoolean();
options = 0;
if (uncalibratedOD) options |= OD;
if (labelWithPercentages) options |= PERCENT;
if (invertPeaks) options |= INVERT;
if (verticalScaleFactor==0.0) verticalScaleFactor=1.0;
if (horizontalScaleFactor==0.0) horizontalScaleFactor=1.0;
Prefs.set(OPTIONS, options);
Prefs.set(VSCALE, verticalScaleFactor);
Prefs.set(HSCALE, horizontalScaleFactor);
}
void selectFirstLane(Rectangle rect) {
if (rect.width/rect.height>=2 || IJ.altKeyDown()) {
if (showLaneDialog) {
String msg = "Are the lanes really horizontal?\n \n"+
"ImageJ assumes the lanes are\n"+
"horizontal if the selection is more\n"+
"than twice as wide as it is tall. Note\n"+
"that the selection can only be moved\n"+
"vertically when the lanes are horizontal.";
GenericDialog gd = new GenericDialog("Gel Analyzer");
gd.addMessage(msg);
gd.setOKLabel("Yes");
gd.showDialog();
if (gd.wasCanceled()) return;
showLaneDialog = false;
}
isVertical = false;
} else
isVertical = true;
IJ.showStatus("Lane 1 selected ("+(isVertical?"vertical":"horizontal")+" lanes)");
firstRect = rect;
nLanes = 1;
saveNLanes = 0;
if(isVertical)
x[1] = rect.x;
else
x[1] = rect.y;
gel = imp;
saveID = imp.getID();
overlay = null;
updateRoiList(rect);
}
void selectNextLane(Rectangle rect) {
if (rect.width!=firstRect.width || rect.height!=firstRect.height) {
show("Selections must all be the same size.");
return;
}
if (nLanes<MAX_LANES)
nLanes += 1;
IJ.showStatus("Lane " + nLanes + " selected");
if (isVertical)
x[nLanes] = rect.x;
else
x[nLanes] = rect.y;
if(x[nLanes]==x[nLanes-1]){
nLanes--; return;
}
if (isVertical && rect.y!=firstRect.y) {
rect.y = firstRect.y;
gel.setRoi(rect);
} else if (!isVertical && rect.x!=firstRect.x) {
rect.x = firstRect.x;
gel.setRoi(rect);
}
updateRoiList(rect);
}
void updateRoiList(Rectangle rect) {
if (gel==null)
return;
if (overlay==null) {
overlay = new Overlay();
overlay.drawLabels(true);
overlay.setLabelColor(Color.white);
overlay.drawBackgrounds(true);
}
overlay.add(new Roi(rect.x, rect.y, rect.width, rect.height, null));
gel.setOverlay(overlay);
}
void plotLanes(ImagePlus imp, boolean replot) {
int topMargin = 16;
int bottomMargin = 2;
double min = Double.MAX_VALUE;
double max = -Double.MAX_VALUE;
int plotWidth;
double[][] profiles;
profiles = new double[MAX_LANES+1][];
IJ.showStatus("Plotting " + nLanes + " lanes");
ImageProcessor ipRotated = imp.getProcessor();
if (isVertical)
ipRotated = ipRotated.rotateLeft();
ImagePlus imp2 = new ImagePlus("", ipRotated);
imp2.setCalibration(imp.getCalibration());
if (uncalibratedOD && (imp2.getType()==ImagePlus.GRAY16 || imp2.getType()==ImagePlus.GRAY32))
new ImageConverter(imp2).convertToGray8();
if (invertPeaks) {
ImageProcessor ip2 = imp2.getProcessor().duplicate();
ip2.invert();
imp2.setProcessor(null, ip2);
}
for (int i=1; i<=nLanes; i++) {
if (isVertical)
imp2.setRoi(firstRect.y,
ipRotated.getHeight() - x[i] - firstRect.width,
firstRect.height, firstRect.width);
else
imp2.setRoi(firstRect.x, x[i], firstRect.width, firstRect.height);
ProfilePlot pp = new ProfilePlot(imp2);
profiles[i] = pp.getProfile();
if (pp.getMin()<min)
min = pp.getMin();
if (pp.getMax()>max)
max = pp.getMax();
if (uncalibratedOD)
profiles[i] = od(profiles[i]);
}
if (uncalibratedOD) {
min = odMin;
max = odMax;
}
if (isVertical)
plotWidth = firstRect.height;
else
plotWidth = firstRect.width;
if (plotWidth<650)
plotWidth = 650;
if (isVertical) {
if (plotWidth>4*firstRect.height)
plotWidth = 4*firstRect.height;
} else {
if (plotWidth>4*firstRect.width)
plotWidth = 4*firstRect.width;
}
if (verticalScaleFactor==0.0) verticalScaleFactor=1.0;
if (horizontalScaleFactor==0.0) horizontalScaleFactor=1.0;
Dimension screen = IJ.getScreenSize();
if (plotWidth>screen.width-screen.width/6)
plotWidth = screen.width - screen.width/6;
plotWidth = (int)(plotWidth*horizontalScaleFactor);
plotHeight = plotWidth/2;
if (plotHeight<250) plotHeight = 250;
plotHeight = (int)(plotHeight*verticalScaleFactor);
ImageProcessor ip = new ByteProcessor(plotWidth, topMargin+nLanes*plotHeight+bottomMargin);
ip.setColor(Color.white);
ip.fill();
ip.setColor(Color.black);
int h= ip.getHeight();
ip.moveTo(0,0);
ip.lineTo(plotWidth-1,0);
ip.lineTo(plotWidth-1, h-1);
ip.lineTo(0, h-1);
ip.lineTo(0, 0);
ip.moveTo(0, h-2);
ip.lineTo(plotWidth-1, h-2);
String s = imp.getTitle()+"; ";
Calibration cal = imp.getCalibration();
if (cal.calibrated())
s += cal.getValueUnit();
else if (uncalibratedOD)
s += "Uncalibrated OD";
else
s += "Uncalibrated";
ip.moveTo(5,topMargin);
ip.drawString(s);
double xScale = (double)plotWidth/profiles[1].length;
double yScale;
if ((max-min)==0.0)
yScale = 1.0;
else
yScale = plotHeight/(max-min);
for (int i=1; i<=nLanes; i++) {
double[] profile = profiles[i];
int top = (i-1)*plotHeight + topMargin;
int base = top+plotHeight;
ip.moveTo(0, base);
ip.lineTo((int)(profile.length*xScale), base);
ip.moveTo(0, base-(int)((profile[0]-min)*yScale));
for (int j = 1; j<profile.length; j++)
ip.lineTo((int)(j*xScale+0.5), base-(int)((profile[j]-min)*yScale+0.5));
}
Line.setWidth(1);
ImagePlus plots = new Plots();
plots.setProcessor("Plots of "+imp.getShortTitle(), ip);
plots.changes = true;
ip.setThreshold(0,0,ImageProcessor.NO_LUT_UPDATE); if (cal.calibrated()) {
double pixelsAveraged = isVertical?firstRect.width:firstRect.height;
double scale = Math.sqrt((xScale*yScale)/pixelsAveraged);
Calibration plotsCal = plots.getCalibration();
plotsCal.setUnit("unit");
plotsCal.pixelWidth = 1.0/scale;
plotsCal.pixelHeight = 1.0/scale;
}
plots.show();
saveNLanes = nLanes;
nLanes = 0;
saveID = 0;
ipLanes = null;
Toolbar toolbar = Toolbar.getInstance();
toolbar.setColor(Color.black);
toolbar.setTool(Toolbar.LINE);
ImageWindow win = WindowManager.getCurrentWindow();
ImageCanvas canvas = win.getCanvas();
if (canvas instanceof PlotsCanvas)
plotsCanvas = (PlotsCanvas)canvas;
else
plotsCanvas = null;
}
double[] od(double[] profile) {
double v;
for (int i=0; i<profile.length; i++) {
v = 0.434294481*Math.log(255.0/(255.0-profile[i]));
if (v<odMin) odMin = v;
if (v>odMax) odMax = v;
profile[i] = v;
}
return profile;
}
void outlineLanes() {
if (gel==null || overlay==null) {
show("Data needed to outline lanes is no longer available.");
return;
}
int lineWidth = (int)(1.0/gel.getCanvas().getMagnification());
if (lineWidth<1)
lineWidth = 1;
Font f = new Font("Helvetica", Font.PLAIN, 12*lineWidth);
ImageProcessor ip = gel.getProcessor();
ImageProcessor ipLanes = ip.duplicate();
if (!(ipLanes instanceof ByteProcessor))
ipLanes = ipLanes.convertToByte(true);
ipLanes.setFont(f);
ipLanes.setLineWidth(lineWidth);
setCustomLut(ipLanes);
ImagePlus lanes = new ImagePlus("Lanes of "+gel.getShortTitle(), ipLanes);
lanes.changes = true;
lanes.setRoi(gel.getRoi());
gel.deleteRoi();
for (int i=0; i<overlay.size(); i++) {
Roi roi = overlay.get(i);
Rectangle r = roi.getBounds();
ipLanes.drawRect(r.x, r.y, r.width, r.height);
String s = ""+(i+1);
if(isVertical) {
int yloc = r.y;
if (yloc<lineWidth*12) yloc += lineWidth*14;
ipLanes.drawString(s, r.x+r.width/2-ipLanes.getStringWidth(s)/2, yloc);
} else {
int xloc = r.x-ipLanes.getStringWidth(s)-2;
if (xloc<lineWidth*10) xloc = r.x + 2;
ipLanes.drawString(s, xloc, r.y+r.height/2+6);
}
}
lanes.deleteRoi();
lanes.show();
}
void setCustomLut(ImageProcessor ip) {
IndexColorModel cm = (IndexColorModel)ip.getColorModel();
byte[] reds = new byte[256];
byte[] greens = new byte[256];
byte[] blues = new byte[256];
cm.getReds(reds);
cm.getGreens(greens);
cm.getBlues(blues);
reds[1] =(byte) 255;
greens[1] = (byte)0;
blues[1] = (byte)0;
ip.setColorModel(new IndexColorModel(8, 256, reds, greens, blues));
byte[] pixels = (byte[])ip.getPixels();
for (int i=0; i<pixels.length; i++)
if ((pixels[i]&255)==1)
pixels[i] = 0;
ip.setColor(1);
}
void show(String msg) {
IJ.showMessage("Gel Analyzer", msg);
}
public static ImagePlus getGelImage() {
return gel;
}
}
class Plots extends ImagePlus {
public void show() {
img = ip.createImage();
ImageCanvas ic = new PlotsCanvas(this);
win = new ImageWindow(this, ic);
IJ.showStatus("");
if (ic.getMagnification()==1.0)
return;
while(ic.getMagnification()<1.0)
ic.zoomIn(0,0);
Point loc = win.getLocation();
int w = getWidth()+20;
int h = getHeight()+30;
Dimension screen = IJ.getScreenSize();
if (loc.x+w>screen.width)
w = screen.width-loc.x-20;
if (loc.y+h>screen.height)
h = screen.height-loc.y-30;
win.setSize(w, h);
win.validate();
repaintWindow();
}
}
class PlotsCanvas extends ImageCanvas {
public static final int MAX_PEAKS = 200;
double[] actual = {428566.00,351368.00,233977.00,99413.00,60057.00,31382.00,
14531.00,7843.00,2146.00,752.00,367.00};
double[] measured = new double[MAX_PEAKS];
Rectangle[] rect = new Rectangle[MAX_PEAKS];
int counter;
ResultsTable rt;
public PlotsCanvas(ImagePlus imp) {
super(imp);
}
public void mousePressed(MouseEvent e) {
super.mousePressed(e);
Roi roi = imp.getRoi();
if (roi==null)
return;
if (roi.getType()==Roi.LINE)
Roi.setColor(Color.blue);
else
Roi.setColor(Color.yellow);
if (Toolbar.getToolId()!=Toolbar.WAND || IJ.spaceBarDown())
return;
if (IJ.shiftKeyDown()) {
IJ.showMessage("Gel Analyzer", "Unable to measure area because shift key is down.");
imp.deleteRoi();
counter = 0;
return;
}
ImageStatistics stats = imp.getStatistics();
if (counter==0) {
rt = ResultsTable.getResultsTable();
rt.reset();
}
double perimeter = roi.getLength();
String error = "";
double circularity = 4.0*Math.PI*(stats.pixelCount/(perimeter*perimeter));
if (circularity<0.025)
error = " (error?)";
double area = stats.pixelCount+perimeter/2.0; Calibration cal = imp.getCalibration();
area = area*cal.pixelWidth*cal.pixelHeight;
rect[counter] = roi.getBounds();
int places = cal.scaled()?3:0;
rt.incrementCounter();
rt.addValue("Area", area);
rt.show("Results");
measured[counter] = area;
if (counter<MAX_PEAKS)
counter++;
}
public void mouseReleased(MouseEvent e) {
super.mouseReleased(e);
Roi roi = imp.getRoi();
if (roi!=null && roi.getType()==Roi.LINE) {
Undo.setup(Undo.FILTER, imp);
imp.getProcessor().snapshot();
roi.drawPixels();
imp.updateAndDraw();
imp.deleteRoi();
}
}
void reset() {
counter = 0;
}
void labelPeaks() {
imp.deleteRoi();
double total = 0.0;
for (int i=0; i<counter; i++)
total += measured[i];
ImageProcessor ip = imp.getProcessor();
ip.setFont(new Font("SansSerif", Font.PLAIN, 9));
for (int i=0; i<counter; i++) {
Rectangle r = rect[i];
String s;
if (GelAnalyzer.labelWithPercentages)
s = IJ.d2s((measured[i]/total)*100, 2);
else
s = IJ.d2s(measured[i], 0);
int swidth = ip.getStringWidth(s);
int x = r.x + r.width/2 - swidth/2;
int y = r.y + r.height*3/4 + 9;
int[] data = new int[swidth];
ip.getRow(x, y, data, swidth);
boolean fits = true;
for (int j=0; j<swidth; j++)
if (data[j]!=255) {
fits = false;
break;
}
fits = fits && measured[i]>500;
if (r.height>=(GelAnalyzer.plotHeight-11))
fits = true;
if (!fits)
y = r.y - 2;
ip.drawString(s, x, y);
}
imp.updateAndDraw();
displayPercentages();
reset();
}
void displayPercentages() {
ResultsTable rt = ResultsTable.getResultsTable();
rt.reset();
double total = 0.0;
for (int i=0; i<counter; i++)
total += measured[i];
if (IJ.debugMode && counter==actual.length) {
debug();
return;
}
for (int i=0; i<counter; i++) {
double percent = (measured[i]/total)*100;
rt.incrementCounter();
rt.addValue("Area", measured[i]);
rt.addValue("Percent", percent);
}
rt.show("Results");
}
void debug() {
for (int i=0; i<counter; i++) {
double a = (actual[i]/actual[0])*100;
double m = (measured[i]/measured[0])*100;
IJ.write(IJ.d2s(a, 4)+" "
+IJ.d2s(m, 4)+" "
+IJ.d2s(((m-a)/m)*100, 4));
}
}
}