package endrov.IJ.roi; import java.io.*; import java.util.*; import java.net.*; import java.awt.*; /* ImageJ/NIH Image 64 byte ROI outline header 2 byte numbers are big-endian signed shorts 0-3 "Iout" 4-5 version (>=217) 6-7 roi type 8-9 top 10-11 left 12-13 bottom 14-15 right 16-17 NCoordinates 18-33 x1,y1,x2,y2 (straight line) 34-35 stroke width (v1.43i or later) 36-39 ShapeRoi size (type must be 1 if this value>0) 40-43 stroke color (v1.43i or later) 44-47 fill color (v1.43i or later) 48-49 subtype (v1.43k or later) 50-51 options (v1.43k or later) 52-52 arrow style or aspect ratio (v1.43p or later) 53-53 arrow head size (v1.43p or later) 54-55 rounded rect arc size (v1.43p or later) 56-59 position 60-63 header2 offset 64- x-coordinates (short), followed by y-coordinates */ /** Decodes an ImageJ, NIH Image or Scion Image ROI file. */ public class RoiDecoder { // offsets public static final int VERSION_OFFSET = 4; public static final int TYPE = 6; public static final int TOP = 8; public static final int LEFT = 10; public static final int BOTTOM = 12; public static final int RIGHT = 14; public static final int N_COORDINATES = 16; public static final int X1 = 18; public static final int Y1 = 22; public static final int X2 = 26; public static final int Y2 = 30; public static final int STROKE_WIDTH = 34; public static final int SHAPE_ROI_SIZE = 36; public static final int STROKE_COLOR = 40; public static final int FILL_COLOR = 44; public static final int SUBTYPE = 48; public static final int OPTIONS = 50; public static final int ARROW_STYLE = 52; public static final int ELLIPSE_ASPECT_RATIO = 52; public static final int ARROW_HEAD_SIZE = 53; public static final int ROUNDED_RECT_ARC_SIZE = 54; public static final int POSITION = 56; public static final int HEADER2_OFFSET = 60; public static final int COORDINATES = 64; // header2 offsets public static final int C_POSITION = 4; public static final int Z_POSITION = 8; public static final int T_POSITION = 12; public static final int NAME_OFFSET = 16; public static final int NAME_LENGTH = 20; public static final int OVERLAY_LABEL_COLOR = 24; public static final int OVERLAY_FONT_SIZE = 28; //short public static final int AVAILABLE_SHORT1 = 30; //short // subtypes public static final int TEXT = 1; public static final int ARROW = 2; public static final int ELLIPSE = 3; // options public static final int SPLINE_FIT = 1; public static final int DOUBLE_HEADED = 2; public static final int OUTLINE = 4; public static final int OVERLAY_LABELS = 8; public static final int OVERLAY_NAMES = 16; public static final int OVERLAY_BACKGROUNDS = 32; public static final int OVERLAY_BOLD = 64; // types private final int polygon=0, rect=1, oval=2, line=3, freeline=4, polyline=5, noRoi=6, freehand=7, traced=8, angle=9, point=10; private byte[] data; private String path; private InputStream is; private String name; private int size; /** Constructs an RoiDecoder using a file path. */ public RoiDecoder(String path) { this.path = path; } /** Constructs an RoiDecoder using a byte array. */ public RoiDecoder(byte[] bytes, String name) { is = new ByteArrayInputStream(bytes); this.name = name; this.size = bytes.length; } /** Returns the ROI. */ public Roi getRoi() throws IOException { if (path!=null) { File f = new File(path); size = (int)f.length(); if (!path.endsWith(".roi") && size>5242880) throw new IOException("This is not an ROI or file size>5MB)"); name = f.getName(); is = new FileInputStream(path); } data = new byte[size]; int total = 0; while (total0 && hdr2Offset+AVAILABLE_SHORT1+2<=size) { channel = getInt(hdr2Offset+C_POSITION); slice = getInt(hdr2Offset+Z_POSITION); frame = getInt(hdr2Offset+T_POSITION); overlayLabelColor = getInt(hdr2Offset+OVERLAY_LABEL_COLOR); overlayFontSize = getShort(hdr2Offset+OVERLAY_FONT_SIZE); } if (name!=null && name.endsWith(".roi")) name = name.substring(0, name.length()-4); boolean isComposite = getInt(SHAPE_ROI_SIZE)>0; Roi roi = null; if (isComposite) { roi = getShapeRoi(); if (version>=218) getStrokeWidthAndColor(roi); roi.setPosition(position); if (channel>0 || slice>0 || frame>0) roi.setPosition(channel, slice, frame); decodeOverlayOptions(roi, version, options, overlayLabelColor, overlayFontSize); return roi; } switch (type) { case rect: roi = new Roi(left, top, width, height); int arcSize = getShort(ROUNDED_RECT_ARC_SIZE); if (arcSize>0) roi.setCornerDiameter(arcSize); break; case oval: roi = new OvalRoi(left, top, width, height); break; case line: int x1 = (int)getFloat(X1); int y1 = (int)getFloat(Y1); int x2 = (int)getFloat(X2); int y2 = (int)getFloat(Y2); if (subtype==ARROW) { roi = new Arrow(x1, y1, x2, y2); ((Arrow)roi).setDoubleHeaded((options&DOUBLE_HEADED)!=0); ((Arrow)roi).setOutline((options&OUTLINE)!=0); int style = getByte(ARROW_STYLE); if (style>=Arrow.FILLED && style<=Arrow.HEADLESS) ((Arrow)roi).setStyle(style); int headSize = getByte(ARROW_HEAD_SIZE); if (headSize>=0 && style<=30) ((Arrow)roi).setHeadSize(headSize); } else roi = new Line(x1, y1, x2, y2); //IJ.write("line roi: "+x1+" "+y1+" "+x2+" "+y2); break; case polygon: case freehand: case traced: case polyline: case freeline: case angle: case point: //IJ.log("type: "+type); //IJ.log("n: "+n); //IJ.log("rect: "+left+","+top+" "+width+" "+height); if (n==0) break; int[] x = new int[n]; int[] y = new int[n]; int base1 = COORDINATES; int base2 = base1+2*n; int xtmp, ytmp; for (int i=0; i=218) { getStrokeWidthAndColor(roi); boolean splineFit = (options&SPLINE_FIT)!=0; if (splineFit && roi instanceof PolygonRoi) ((PolygonRoi)roi).fitSpline(); } if (version>=218 && subtype==TEXT) roi = getTextRoi(roi); roi.setPosition(position); if (channel>0 || slice>0 || frame>0) roi.setPosition(channel, slice, frame); decodeOverlayOptions(roi, version, options, overlayLabelColor, overlayFontSize); return roi; } void decodeOverlayOptions(Roi roi, int version, int options, int color, int fontSize) { Overlay proto = new Overlay(); proto.drawLabels((options&OVERLAY_LABELS)!=0); proto.drawNames((options&OVERLAY_NAMES)!=0); proto.drawBackgrounds((options&OVERLAY_BACKGROUNDS)!=0); if (version>=220) proto.setLabelColor(new Color(color)); boolean bold = (options&OVERLAY_BOLD)!=0; if (fontSize>0 || bold) { proto.setLabelFont(new Font("SansSerif", bold?Font.BOLD:Font.PLAIN, fontSize)); } roi.setPrototypeOverlay(proto); } void getStrokeWidthAndColor(Roi roi) { int strokeWidth = getShort(STROKE_WIDTH); if (strokeWidth>0) roi.setStrokeWidth(strokeWidth); int strokeColor = getInt(STROKE_COLOR); if (strokeColor!=0) { int alpha = (strokeColor>>24)&0xff; roi.setStrokeColor(new Color(strokeColor, alpha!=255)); } int fillColor = getInt(FILL_COLOR); if (fillColor!=0) { int alpha = (fillColor>>24)&0xff; roi.setFillColor(new Color(fillColor, alpha!=255)); } } public Roi getShapeRoi() throws IOException { int type = getByte(TYPE); if (type!=rect) throw new IllegalArgumentException("Invalid composite ROI type"); int top= getShort(TOP); int left = getShort(LEFT); int bottom = getShort(BOTTOM); int right = getShort(RIGHT); int width = right-left; int height = bottom-top; int n = getInt(SHAPE_ROI_SIZE); ShapeRoi roi = null; float[] shapeArray = new float[n]; int base = COORDINATES; for(int i=0; isize) return fileName; char[] name = new char[length]; for (int i=0; i32767 and unsigned return n; } int getInt(int base) { int b0 = data[base]&255; int b1 = data[base+1]&255; int b2 = data[base+2]&255; int b3 = data[base+3]&255; return ((b0<<24) + (b1<<16) + (b2<<8) + b3); } float getFloat(int base) { return Float.intBitsToFloat(getInt(base)); } /** Opens an ROI from a byte array. */ public static Roi openFromByteArray(byte[] bytes) { Roi roi = null; try { RoiDecoder decoder = new RoiDecoder(bytes, null); roi = decoder.getRoi(); } catch (IOException e) { return null; } return roi; } }