/*
* Copyright 2010-2015 Institut Pasteur.
*
* This file is part of Icy.
*
* Icy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* Icy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Icy. If not, see
* <http://www.gnu.org/licenses/>.
*/
package icy.imagej;
import icy.common.listener.ProgressListener;
import icy.image.IcyBufferedImage;
import icy.math.ArrayMath;
import icy.roi.ROI;
import icy.roi.ROI2D;
import icy.sequence.Sequence;
import icy.system.thread.ThreadUtil;
import icy.type.DataType;
import icy.type.collection.array.Array1DUtil;
import icy.type.collection.array.Array2DUtil;
import icy.type.collection.array.ArrayUtil;
import ij.CompositeImage;
import ij.ImagePlus;
import ij.ImageStack;
import ij.LookUpTable;
import ij.gui.Line;
import ij.gui.OvalRoi;
import ij.gui.PointRoi;
import ij.gui.PolygonRoi;
import ij.gui.Roi;
import ij.gui.ShapeRoi;
import ij.measure.Calibration;
import ij.plugin.frame.RoiManager;
import ij.process.FloatPolygon;
import ij.process.ImageProcessor;
import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import plugins.kernel.roi.roi2d.ROI2DArea;
import plugins.kernel.roi.roi2d.ROI2DEllipse;
import plugins.kernel.roi.roi2d.ROI2DLine;
import plugins.kernel.roi.roi2d.ROI2DPath;
import plugins.kernel.roi.roi2d.ROI2DPoint;
import plugins.kernel.roi.roi2d.ROI2DPolyLine;
import plugins.kernel.roi.roi2d.ROI2DPolygon;
import plugins.kernel.roi.roi2d.ROI2DRectangle;
import plugins.kernel.roi.roi2d.ROI2DShape;
/**
* ImageJ utilities class.
*
* @author Stephane
*/
public class ImageJUtil
{
/**
* Convert the specified native 1D array to supported ImageJ native data array.
*/
private static Object convertToIJType(Object array, boolean signed)
{
// double[] not supported in ImageJ
if (array instanceof double[])
return Array1DUtil.arrayToFloatArray(array, signed);
// long[] not supported in ImageJ
if (array instanceof long[])
return Array1DUtil.arrayToShortArray(array, signed);
// int[] means Color image for ImageJ
if (array instanceof int[])
return Array1DUtil.arrayToShortArray(array, signed);
return Array1DUtil.copyOf(array);
}
/**
* Append the specified {@link IcyBufferedImage} to the given ImageJ {@link ImageStack}.<br>
* If input {@link ImageStack} is <code>null</code> then a new {@link ImageStack} is returned.
*/
private static ImageStack appendToStack(IcyBufferedImage img, ImageStack stack)
{
final ImageStack result;
if (stack == null)
result = new ImageStack(img.getSizeX(), img.getSizeY(), LookUpTable.createGrayscaleColorModel(false));
else
result = stack;
for (int c = 0; c < img.getSizeC(); c++)
result.addSlice(null, convertToIJType(img.getDataXY(c), img.isSignedDataType()));
return result;
}
/**
* Convert the specified Icy {@link Sequence} object to {@link ImagePlus}.
*/
private static ImagePlus createImagePlus(Sequence sequence, ProgressListener progressListener)
{
final int sizeZ = sequence.getSizeZ();
final int sizeT = sequence.getSizeT();
final int len = sizeZ * sizeT;
int position = 0;
ImageStack stack = null;
for (int t = 0; t < sizeT; t++)
{
for (int z = 0; z < sizeZ; z++)
{
if (progressListener != null)
progressListener.notifyProgress(position, len);
stack = appendToStack(sequence.getImage(t, z), stack);
position++;
}
}
// return the image
return new ImagePlus(sequence.getName(), stack);
}
/**
* Calibrate the specified Icy {@link Sequence} from the specified ImageJ {@link Calibration} object.
*/
private static void calibrateIcySequence(Sequence sequence, Calibration cal)
{
if (cal != null)
{
if (cal.scaled())
{
// TODO : apply unit conversion
sequence.setPixelSizeX(cal.pixelWidth);
sequence.setPixelSizeY(cal.pixelHeight);
sequence.setPixelSizeZ(cal.pixelDepth);
}
// TODO : apply unit conversion
sequence.setTimeInterval(cal.frameInterval);
}
}
/**
* Calibrate the specified ImageJ {@link ImagePlus} from the specified Icy {@link Sequence}.
*/
private static void calibrateImageJImage(ImagePlus image, Sequence seq)
{
final Calibration cal = image.getCalibration();
final double psx = seq.getPixelSizeX();
final double psy = seq.getPixelSizeY();
final double psz = seq.getPixelSizeZ();
// different from defaults values ?
if ((psx != 1d) || (psy != 1d) || (psz != 1d))
{
cal.pixelWidth = psx;
cal.pixelHeight = psy;
cal.pixelDepth = psz;
// default unit size icy
cal.setUnit("�m");
}
final double ti = seq.getTimeInterval();
// different from default value
if (ti != 0.1d)
{
cal.frameInterval = ti;
cal.setTimeUnit("sec");
}
image.setDimensions(seq.getSizeC(), seq.getSizeZ(), seq.getSizeT());
image.setOpenAsHyperStack(image.getNDimensions() > 3);
// final ImageProcessor ip = image.getProcessor();
// ip.setMinAndMax(seq.getChannelMin(0) displayMin, displayMax);
}
/**
* Convert the ImageJ {@link ImagePlus} image at position [Z,T] into an Icy image
*/
public static IcyBufferedImage convertToIcyBufferedImage(ImagePlus image, int z, int t, int sizeX, int sizeY,
int sizeC, int type, boolean signed16)
{
// set position
image.setPosition(1, z + 1, t + 1);
// directly use the buffered image to do the conversion...
if ((sizeC == 1) && ((type == ImagePlus.COLOR_256) || (type == ImagePlus.COLOR_RGB)))
return IcyBufferedImage.createFrom(image.getBufferedImage());
final ImageProcessor ip = image.getProcessor();
final Object data = Array1DUtil.copyOf(ip.getPixels());
final DataType dataType = ArrayUtil.getDataType(data);
final Object[] datas = Array2DUtil.createArray(dataType, sizeC);
// first channel data (get a copy)
datas[0] = data;
// special case of 16 bits signed data --> subtract 32768
if (signed16)
datas[0] = ArrayMath.subtract(datas[0], Double.valueOf(32768));
// others channels data
for (int c = 1; c < sizeC; c++)
{
image.setPosition(c + 1, z + 1, t + 1);
datas[c] = Array1DUtil.copyOf(image.getProcessor().getPixels());
// special case of 16 bits signed data --> subtract 32768
if (signed16)
datas[c] = ArrayMath.subtract(datas, Double.valueOf(32768));
}
// create a single image from all channels
return new IcyBufferedImage(sizeX, sizeY, datas, signed16);
}
/**
* Convert the ImageJ {@link ImagePlus} image at position [Z,T] into an Icy image
*/
public static IcyBufferedImage convertToIcyBufferedImage(ImagePlus image, int z, int t)
{
final int[] dim = image.getDimensions(true);
return convertToIcyBufferedImage(image, z, t, dim[0], dim[1], dim[2], image.getType(), image
.getLocalCalibration().isSigned16Bit());
}
/**
* Convert the specified ImageJ {@link ImagePlus} object to Icy {@link Sequence}
*/
public static Sequence convertToIcySequence(ImagePlus image, ProgressListener progressListener)
{
final Sequence result = new Sequence(image.getTitle());
final int[] dim = image.getDimensions(true);
final int sizeX = dim[0];
final int sizeY = dim[1];
final int sizeC = dim[2];
final int sizeZ = dim[3];
final int sizeT = dim[4];
final int type = image.getType();
// only integer signed type allowed in ImageJ is 16 bit signed
final boolean signed16 = image.getLocalCalibration().isSigned16Bit();
final int len = sizeZ * sizeT;
int position = 0;
result.beginUpdate();
try
{
// convert image
for (int t = 0; t < sizeT; t++)
{
for (int z = 0; z < sizeZ; z++)
{
if (progressListener != null)
progressListener.notifyProgress(position, len);
result.setImage(t, z, convertToIcyBufferedImage(image, z, t, sizeX, sizeY, sizeC, type, signed16));
position++;
}
}
// convert ROI(s)
final RoiManager roiManager = RoiManager.getInstance();
final Roi[] rois;
if (roiManager != null)
rois = roiManager.getRoisAsArray();
else
rois = new Roi[] {};
if (rois.length > 0)
{
for (Roi ijRoi : rois)
{
// can happen
if (ijRoi != null)
for (ROI icyRoi : convertToIcyRoi(ijRoi))
result.addROI(icyRoi);
}
}
else
{
final Roi roi = image.getRoi();
if (roi != null)
for (ROI icyRoi : convertToIcyRoi(roi))
result.addROI(icyRoi);
}
// calibrate
calibrateIcySequence(result, image.getCalibration());
}
finally
{
result.endUpdate();
}
return result;
}
/**
* Convert the specified Icy {@link Sequence} object to ImageJ {@link ImagePlus}
*/
public static ImagePlus convertToImageJImage(Sequence sequence, boolean useRoiManager,
ProgressListener progressListener)
{
// create the image
final ImagePlus result = createImagePlus(sequence, progressListener);
// calibrate
calibrateImageJImage(result, sequence);
// convert ROI
final List<Roi> ijRois = new ArrayList<Roi>();
for (ROI2D roi : sequence.getROI2Ds())
ijRois.add(convertToImageJRoi(roi));
if (ijRois.size() > 0)
{
if ((ijRois.size() > 1) && useRoiManager)
{
RoiManager roiManager = RoiManager.getInstance();
if (roiManager == null)
{
ThreadUtil.invokeNow(new Runnable()
{
@Override
public void run()
{
// need to do it on EDT
new RoiManager();
}
});
}
roiManager = RoiManager.getInstance();
int n = 0;
for (Roi roi : ijRois)
roiManager.add(result, roi, n++);
}
result.setRoi(ijRois.get(0));
}
if (result.getNChannels() > 4)
return new CompositeImage(result, CompositeImage.COLOR);
else if (result.getNChannels() > 1)
return new CompositeImage(result, CompositeImage.COMPOSITE);
return result;
}
/**
* Convert the specified Icy {@link Sequence} object to ImageJ {@link ImagePlus}
*/
public static ImagePlus convertToImageJImage(Sequence sequence, ProgressListener progressListener)
{
return convertToImageJImage(sequence, false, progressListener);
}
/**
* Convert the specified ImageJ {@link Roi} object to Icy {@link ROI}.
*/
public static List<ROI2D> convertToIcyRoi(Roi roi)
{
final List<ROI2D> result = new ArrayList<ROI2D>();
final List<Point2D> pts = new ArrayList<Point2D>();
final FloatPolygon fp;
switch (roi.getType())
{
default:
result.add(new ROI2DRectangle(roi.getFloatBounds()));
break;
case Roi.OVAL:
result.add(new ROI2DEllipse(roi.getFloatBounds()));
break;
case Roi.LINE:
final Rectangle2D rect = roi.getFloatBounds();
final double x = rect.getX();
final double y = rect.getY();
result.add(new ROI2DLine(new Point2D.Double(x, y), new Point2D.Double(x + rect.getWidth(), y
+ rect.getHeight())));
break;
case Roi.TRACED_ROI:
case Roi.POLYGON:
case Roi.FREEROI:
fp = ((PolygonRoi) roi).getFloatPolygon();
for (int p = 0; p < fp.npoints; p++)
pts.add(new Point2D.Float(fp.xpoints[p], fp.ypoints[p]));
final ROI2DPolygon roiPolygon = new ROI2DPolygon();
roiPolygon.setPoints(pts);
// TRACED_ROI should be converted to ROI2DArea
if (roi.getType() == Roi.TRACED_ROI)
result.add(new ROI2DArea(roiPolygon.getBooleanMask(true)));
else
result.add(roiPolygon);
break;
case Roi.FREELINE:
case Roi.POLYLINE:
case Roi.ANGLE:
fp = ((PolygonRoi) roi).getFloatPolygon();
for (int p = 0; p < fp.npoints; p++)
pts.add(new Point2D.Float(fp.xpoints[p], fp.ypoints[p]));
final ROI2DPolyLine roiPolyline = new ROI2DPolyLine();
roiPolyline.setPoints(pts);
result.add(roiPolyline);
break;
case Roi.COMPOSITE:
final ROI2DPath roiPath = new ROI2DPath(((ShapeRoi) roi).getShape());
final Rectangle2D.Double roiBounds = roi.getFloatBounds();
// we have to adjust position as Shape do not contains it
if (roiPath.canSetPosition())
roiPath.setPosition2D(new Point2D.Double(roiBounds.x, roiBounds.y));
result.add(roiPath);
break;
case Roi.POINT:
fp = ((PolygonRoi) roi).getFloatPolygon();
for (int p = 0; p < fp.npoints; p++)
pts.add(new Point2D.Float(fp.xpoints[p], fp.ypoints[p]));
for (Point2D pt : pts)
result.add(new ROI2DPoint(pt));
break;
}
int ind = 0;
for (ROI2D r : result)
{
r.setC(roi.getCPosition() - 1);
r.setZ(roi.getZPosition() - 1);
r.setT(roi.getTPosition() - 1);
r.setSelected(false);
if (result.size() > 1)
r.setName(roi.getName() + " " + ind);
else
r.setName(roi.getName());
Color c = roi.getStrokeColor();
if (c == null)
c = roi.getFillColor();
if (c != null)
r.setColor(c);
}
return result;
}
/**
* Convert the specified Icy {@link ROI} object to ImageJ {@link Roi}.
*/
public static Roi convertToImageJRoi(ROI2D roi)
{
final Roi result;
if (roi instanceof ROI2DShape)
{
final List<Point2D> pts = ((ROI2DShape) roi).getPoints();
if (roi instanceof ROI2DPoint)
{
final Point2D p = pts.get(0);
result = new PointRoi(p.getX(), p.getY());
}
else if (roi instanceof ROI2DLine)
{
final Point2D p1 = pts.get(0);
final Point2D p2 = pts.get(1);
result = new Line(p1.getX(), p1.getY(), p2.getX(), p2.getY());
}
else if (roi instanceof ROI2DRectangle)
{
final Rectangle2D r = roi.getBounds2D();
result = new Roi(r.getX(), r.getY(), r.getWidth(), r.getHeight(), 0);
}
else if (roi instanceof ROI2DEllipse)
{
final Rectangle2D r = roi.getBounds2D();
result = new OvalRoi(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
else if ((roi instanceof ROI2DPolyLine) || (roi instanceof ROI2DPolygon))
{
final FloatPolygon fp = new FloatPolygon();
for (Point2D p : pts)
fp.addPoint(p.getX(), p.getY());
if (roi instanceof ROI2DPolyLine)
result = new PolygonRoi(fp, Roi.POLYLINE);
else
result = new PolygonRoi(fp, Roi.POLYGON);
}
else
// create compatible shape ROI
result = new ShapeRoi(((ROI2DPath) roi).getShape());
}
else if (roi instanceof ROI2DArea)
{
final ROI2DArea roiArea = (ROI2DArea) roi;
final Point[] points = roiArea.getBooleanMask(true).getPoints();
final Area area = new Area();
for (Point pt : points)
area.add(new Area(new Rectangle(pt.x, pt.y, 1, 1)));
result = new ShapeRoi(area);
}
else
{
// create standard ROI
final Rectangle2D r = roi.getBounds2D();
result = new Roi(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
result.setPosition(roi.getC() + 1, roi.getZ() + 1, roi.getT() + 1);
result.setName(roi.getName());
result.setStrokeColor(roi.getColor());
// result.setFillColor(roi.getColor());
// result.setStrokeWidth(roi.getStroke());
return result;
}
/**
* @deprecated Use {@link #convertToImageJRoi(ROI2D)} instead.
*/
@Deprecated
public static PointRoi convertToImageJRoiPoint(List<ROI2DPoint> points)
{
final int size = points.size();
final float x[] = new float[size];
final float y[] = new float[size];
for (int i = 0; i < points.size(); i++)
{
final ROI2DPoint point = points.get(i);
x[i] = (float) point.getPoint().getX();
y[i] = (float) point.getPoint().getY();
}
return new PointRoi(x, y, size);
}
}