package ij.plugin.filter;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.util.Tools;
import ij.measure.Measurements;
import java.awt.*;
public class StackLabeler implements ExtendedPlugInFilter, DialogListener {
private static final String[] formats = {"0", "0000", "00:00", "00:00:00", "Text","Label"};
private static final int NUMBER=0, ZERO_PADDED_NUMBER=1, MIN_SEC=2, HOUR_MIN_SEC=3, TEXT=4, LABEL=5;
private static int format = (int)Prefs.get("label.format", NUMBER);
private int flags = DOES_ALL;
private ImagePlus imp;
private static int x = 5;
private static int y = 20;
private static int fontSize = 18;
private int maxWidth;
private Font font;
private static double start = 0;
private static double interval = 1;
private static String text = "";
private static int decimalPlaces = 0;
private static boolean useOverlay;
private static boolean useTextToolFont;
private int fieldWidth;
private Color color;
private int firstFrame, lastFrame, defaultLastFrame;
private Overlay overlay;
private Overlay baseOverlay;
private boolean previewing;
private boolean virtualStack;
private int yoffset;
public int setup(String arg, ImagePlus imp) {
if (imp!=null) {
virtualStack = imp.getStack().isVirtual();
if (virtualStack) useOverlay = true;
baseOverlay = imp.getOverlay();
flags += virtualStack?0:DOES_STACKS;
firstFrame=1; lastFrame=defaultLastFrame=imp.getStackSize();
}
this.imp = imp;
return flags;
}
public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) {
ImageProcessor ip = imp.getProcessor();
Rectangle roi = ip.getRoi();
if (roi.width<ip.getWidth() || roi.height<ip.getHeight()) {
x = roi.x;
y = roi.y+roi.height;
fontSize = (int) ((roi.height - 1.10526)/0.934211);
if (fontSize<7) fontSize = 7;
if (fontSize>80) fontSize = 80;
}
if (IJ.macroRunning()) {
format = NUMBER;
decimalPlaces = 0;
interval=1;
text = "";
start = 0;
useOverlay = false;
useTextToolFont = false;
String options = Macro.getOptions();
if (options!=null) {
if (options.indexOf("interval=0")!=-1 && options.indexOf("format=")==-1)
format = TEXT;
if (options.indexOf(" slice=")!=-1) {
options = options.replaceAll(" slice=", " range=");
Macro.setOptions(options);
}
}
}
if (format<0||format>LABEL) format = NUMBER;
int defaultLastFrame = imp.getStackSize();
if (imp.isHyperStack()) {
if (imp.getNFrames()>1)
defaultLastFrame = imp.getNFrames();
else if (imp.getNSlices()>1)
defaultLastFrame = imp.getNSlices();
}
GenericDialog gd = new GenericDialog("Label Stacks");
gd.setInsets(2, 5, 0);
gd.addChoice("Format:", formats, formats[format]);
gd.addStringField("Starting value:", IJ.d2s(start,decimalPlaces));
gd.addStringField("Interval:", ""+IJ.d2s(interval,decimalPlaces));
gd.addNumericField("X location:", x, 0);
gd.addNumericField("Y location:", y, 0);
gd.addNumericField("Font size:", fontSize, 0);
gd.addStringField("Text:", text, 10);
addRange(gd, "Range:", 1, defaultLastFrame);
gd.setInsets(10,20,0);
gd.addCheckbox(" Use overlay", useOverlay);
gd.addCheckbox(" Use_text tool font", useTextToolFont);
gd.addPreviewCheckbox(pfr);
gd.addHelp(IJ.URL+"/docs/menus/image.html#label");
gd.addDialogListener(this);
previewing = true;
gd.showDialog();
previewing = false;
if (gd.wasCanceled())
return DONE;
else
return flags;
}
void addRange(GenericDialog gd, String label, int start, int end) {
gd.addStringField(label, start+"-"+end);
}
double[] getRange(GenericDialog gd, int start, int end) {
String[] range = Tools.split(gd.getNextString(), " -");
double d1 = Tools.parseDouble(range[0]);
double d2 = range.length==2?Tools.parseDouble(range[1]):Double.NaN;
double[] result = new double[2];
result[0] = Double.isNaN(d1)?1:(int)d1;
result[1] = Double.isNaN(d2)?end:(int)d2;
if (result[0]<start) result[0] = start;
if (result[1]>end) result[1] = end;
if (result[0]>result[1]) {
result[0] = start;
result[1] = end;
}
return result;
}
public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
format = gd.getNextChoiceIndex();
start = Tools.parseDouble(gd.getNextString());
String str = gd.getNextString();
interval = Tools.parseDouble(str);
x = (int)gd.getNextNumber();
y = (int)gd.getNextNumber();
fontSize = (int)gd.getNextNumber();
text = gd.getNextString();
double[] range = getRange(gd, 1, defaultLastFrame);
useOverlay = gd.getNextBoolean();
useTextToolFont = gd.getNextBoolean();
if (virtualStack) useOverlay = true;
firstFrame=(int)range[0]; lastFrame=(int)range[1];
int index = str.indexOf(".");
if (index!=-1)
decimalPlaces = str.length()-index-1;
else
decimalPlaces = 0;
if (gd.invalidNumber()) return false;
if (useTextToolFont)
font = new Font(TextRoi.getFont(), TextRoi.getStyle(), fontSize);
else
font = new Font("SansSerif", Font.PLAIN, fontSize);
if (y<fontSize) y = fontSize+5;
ImageProcessor ip = imp.getProcessor();
ip.setFont(font);
int size = defaultLastFrame;
maxWidth = ip.getStringWidth(getString(size, interval, format));
fieldWidth = 1;
if (size>=10) fieldWidth = 2;
if (size>=100) fieldWidth = 3;
if (size>=1000) fieldWidth = 4;
if (size>=10000) fieldWidth = 5;
Prefs.set("label.format", format);
return true;
}
public void run(ImageProcessor ip) {
int image = ip.getSliceNumber();
int n = image - 1;
if (imp.isHyperStack()) n = updateIndex(n);
if (virtualStack) {
int nSlices = imp.getStackSize();
if (previewing) nSlices = 1;
for (int i=1; i<=nSlices; i++) {
image=i; n=i-1;
if (imp.isHyperStack()) n = updateIndex(n);
drawLabel(ip, image, n);
}
} else {
if (previewing && overlay!=null) {
imp.setOverlay(baseOverlay);
overlay = null;
}
drawLabel(ip, image, n);
}
}
int updateIndex(int n) {
if (imp.getNFrames()>1)
return (int)(n*((double)(imp.getNFrames())/imp.getStackSize()));
else if (imp.getNSlices()>1)
return (int)(n*((double)(imp.getNSlices())/imp.getStackSize()));
else
return n;
}
void drawLabel(ImageProcessor ip, int image, int n) {
String s = getString(n, interval, format);
ip.setFont(font);
int textWidth = ip.getStringWidth(s);
if (color==null) {
color = Toolbar.getForegroundColor();
if ((color.getRGB()&0xffffff)==0) {
ip.setRoi(x, y-fontSize, maxWidth+textWidth, fontSize);
double mean = ImageStatistics.getStatistics(ip, Measurements.MEAN, null).mean;
if (mean<50.0 && !ip.isInvertedLut()) color=Color.white;
ip.resetRoi();
}
}
int frame = image;
int[] pos = new int[]{0, 0, 0};
if (imp.isHyperStack()) {
pos = imp.convertIndexToPosition(image);
if (imp.getNFrames()>1)
frame = pos[2];
else if (imp.getNSlices()>1)
frame = pos[1];
}
if (useOverlay) {
if (image==1) {
overlay = new Overlay();
if (baseOverlay!=null) {
for (int i=0; i<baseOverlay.size(); i++)
overlay.add(baseOverlay.get(i));
}
Roi roi = imp.getRoi();
Rectangle r = roi!=null?roi.getBounds():null;
yoffset = r!=null?r.height:fontSize;
}
if (frame>=firstFrame&&frame<=lastFrame) {
int xloc = format==LABEL?x:x+maxWidth-textWidth;
Roi roi = new TextRoi(xloc, y-yoffset, s, font);
roi.setStrokeColor(color);
roi.setNonScalable(true);
if (imp.isHyperStack())
roi.setPosition(pos[0], pos[1], pos[2]);
else
roi.setPosition(image);
overlay.add(roi);
}
if (image==imp.getStackSize()||previewing)
imp.setOverlay(overlay);
} else if (frame>=firstFrame&&frame<=lastFrame) {
ip.setColor(color);
ip.setAntialiasedText(fontSize>=18);
int xloc = format==LABEL?x:x+maxWidth-textWidth;
ip.moveTo(xloc, y);
ip.drawString(s);
}
}
String getString(int index, double interval, int format) {
double time = start + (index+1-firstFrame)*interval;
int itime = (int)Math.floor(time);
int sign = 1;
if (itime < 0) sign = -1;
itime = itime*sign;
String str = "";
switch (format) {
case NUMBER: str=IJ.d2s(time, decimalPlaces)+" "+text; break;
case ZERO_PADDED_NUMBER:
if (decimalPlaces==0)
str=zeroFill((int)time);
else
str=IJ.d2s(time, decimalPlaces);
str = text +" " + str;
break;
case MIN_SEC:
str=pad((int)Math.floor((itime/60)%60))+":"+pad(itime%60)+" "+text;
if (sign == -1) str = "-"+str;
break;
case HOUR_MIN_SEC:
str=pad((int)Math.floor(itime/3600))+":"+pad((int)Math.floor((itime/60)%60))+":"+pad(itime%60)+" "+text;
if (sign == -1) str = "-"+str;
break;
case TEXT:
str=text;
break;
case LABEL:
if (0<=index && index<imp.getStackSize()) {
str = imp.getStack().getShortSliceLabel(index+1);
str = str==null?"null slice label ("+(index+1)+")":str;
} else
str="void";
break;
}
return str;
}
String pad(int n) {
String str = ""+n;
if (str.length()==1) str="0"+str;
return str;
}
String zeroFill(int n) {
String str = ""+n;
while (str.length()<fieldWidth)
str = "0" + str;
return str;
}
public void setNPasses (int nPasses) {}
}