001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as
007 * published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
008 * 
009 * Icy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
011 * 
012 * You should have received a copy of the GNU General Public License along with Icy. If not, see
013 * <http://www.gnu.org/licenses/>.
014 */
015package icy.imagej;
016
017import icy.common.listener.ProgressListener;
018import icy.image.IcyBufferedImage;
019import icy.math.ArrayMath;
020import icy.roi.ROI;
021import icy.roi.ROI2D;
022import icy.sequence.Sequence;
023import icy.system.thread.ThreadUtil;
024import icy.type.DataType;
025import icy.type.collection.array.Array1DUtil;
026import icy.type.collection.array.Array2DUtil;
027import icy.type.collection.array.ArrayUtil;
028
029import java.awt.Color;
030import java.awt.Point;
031import java.awt.Rectangle;
032import java.awt.geom.Area;
033import java.awt.geom.Point2D;
034import java.awt.geom.Rectangle2D;
035import java.util.ArrayList;
036import java.util.List;
037
038import ij.CompositeImage;
039import ij.ImagePlus;
040import ij.ImageStack;
041import ij.LookUpTable;
042import ij.gui.Line;
043import ij.gui.OvalRoi;
044import ij.gui.PointRoi;
045import ij.gui.PolygonRoi;
046import ij.gui.Roi;
047import ij.gui.ShapeRoi;
048import ij.measure.Calibration;
049import ij.plugin.frame.RoiManager;
050import ij.process.FloatPolygon;
051import ij.process.ImageProcessor;
052import plugins.kernel.roi.roi2d.ROI2DArea;
053import plugins.kernel.roi.roi2d.ROI2DEllipse;
054import plugins.kernel.roi.roi2d.ROI2DLine;
055import plugins.kernel.roi.roi2d.ROI2DPath;
056import plugins.kernel.roi.roi2d.ROI2DPoint;
057import plugins.kernel.roi.roi2d.ROI2DPolyLine;
058import plugins.kernel.roi.roi2d.ROI2DPolygon;
059import plugins.kernel.roi.roi2d.ROI2DRectangle;
060import plugins.kernel.roi.roi2d.ROI2DShape;
061
062/**
063 * ImageJ utilities class.
064 * 
065 * @author Stephane
066 */
067public class ImageJUtil
068{
069    /**
070     * Convert the specified native 1D array to supported ImageJ native data array.
071     */
072    private static Object convertToIJType(Object array, boolean signed)
073    {
074        // double[] not supported in ImageJ
075        if (array instanceof double[])
076            return Array1DUtil.arrayToFloatArray(array, signed);
077        // long[] not supported in ImageJ
078        if (array instanceof long[])
079            return Array1DUtil.arrayToShortArray(array, signed);
080        // int[] means Color image for ImageJ
081        if (array instanceof int[])
082            return Array1DUtil.arrayToShortArray(array, signed);
083
084        return Array1DUtil.copyOf(array);
085    }
086
087    /**
088     * Append the specified {@link IcyBufferedImage} to the given ImageJ {@link ImageStack}.<br>
089     * If input {@link ImageStack} is <code>null</code> then a new {@link ImageStack} is returned.
090     */
091    private static ImageStack appendToStack(IcyBufferedImage img, ImageStack stack)
092    {
093        final ImageStack result;
094
095        if (stack == null)
096            result = new ImageStack(img.getSizeX(), img.getSizeY(), LookUpTable.createGrayscaleColorModel(false));
097        else
098            result = stack;
099
100        for (int c = 0; c < img.getSizeC(); c++)
101            result.addSlice(null, convertToIJType(img.getDataXY(c), img.isSignedDataType()));
102
103        return result;
104    }
105
106    /**
107     * Convert the specified Icy {@link Sequence} object to {@link ImagePlus}.
108     */
109    private static ImagePlus createImagePlus(Sequence sequence, ProgressListener progressListener)
110    {
111        final int sizeZ = sequence.getSizeZ();
112        final int sizeT = sequence.getSizeT();
113        final int len = sizeZ * sizeT;
114
115        int position = 0;
116        ImageStack stack = null;
117
118        for (int t = 0; t < sizeT; t++)
119        {
120            for (int z = 0; z < sizeZ; z++)
121            {
122                if (progressListener != null)
123                    progressListener.notifyProgress(position, len);
124
125                stack = appendToStack(sequence.getImage(t, z), stack);
126
127                position++;
128            }
129        }
130
131        // return the image
132        return new ImagePlus(sequence.getName(), stack);
133    }
134
135    /**
136     * Calibrate the specified Icy {@link Sequence} from the specified ImageJ {@link Calibration} object.
137     */
138    private static void calibrateIcySequence(Sequence sequence, Calibration cal)
139    {
140        if (cal != null)
141        {
142            if (cal.scaled())
143            {
144                // TODO : apply unit conversion
145                sequence.setPixelSizeX(cal.pixelWidth);
146                sequence.setPixelSizeY(cal.pixelHeight);
147                sequence.setPixelSizeZ(cal.pixelDepth);
148            }
149
150            // TODO : apply unit conversion
151            sequence.setTimeInterval(cal.frameInterval);
152        }
153    }
154
155    /**
156     * Calibrate the specified ImageJ {@link ImagePlus} from the specified Icy {@link Sequence}.
157     */
158    private static void calibrateImageJImage(ImagePlus image, Sequence seq)
159    {
160        final Calibration cal = image.getCalibration();
161
162        final double psx = seq.getPixelSizeX();
163        final double psy = seq.getPixelSizeY();
164        final double psz = seq.getPixelSizeZ();
165
166        // different from defaults values ?
167        if ((psx != 1d) || (psy != 1d) || (psz != 1d))
168        {
169            cal.pixelWidth = psx;
170            cal.pixelHeight = psy;
171            cal.pixelDepth = psz;
172            // default unit size icy
173            cal.setUnit("µm");
174        }
175
176        final double ti = seq.getTimeInterval();
177        // different from default value
178        if (ti != 0.1d)
179        {
180            cal.frameInterval = ti;
181            cal.setTimeUnit("sec");
182        }
183
184        image.setDimensions(seq.getSizeC(), seq.getSizeZ(), seq.getSizeT());
185        image.setOpenAsHyperStack(image.getNDimensions() > 3);
186
187        // final ImageProcessor ip = image.getProcessor();
188        // ip.setMinAndMax(seq.getChannelMin(0) displayMin, displayMax);
189    }
190
191    /**
192     * Convert the ImageJ {@link ImagePlus} image at position [Z,T] into an Icy image
193     */
194    public static IcyBufferedImage convertToIcyBufferedImage(ImagePlus image, int z, int t, int sizeX, int sizeY,
195            int sizeC, int type, boolean signed16)
196    {
197        // set position
198        image.setPosition(1, z + 1, t + 1);
199
200        // directly use the buffered image to do the conversion...
201        if ((sizeC == 1) && ((type == ImagePlus.COLOR_256) || (type == ImagePlus.COLOR_RGB)))
202            return IcyBufferedImage.createFrom(image.getBufferedImage());
203
204        final ImageProcessor ip = image.getProcessor();
205        final Object data = Array1DUtil.copyOf(ip.getPixels());
206        final DataType dataType = ArrayUtil.getDataType(data);
207        final Object[] datas = Array2DUtil.createArray(dataType, sizeC);
208
209        // first channel data (get a copy)
210        datas[0] = data;
211        // special case of 16 bits signed data --> subtract 32768
212        if (signed16)
213            datas[0] = ArrayMath.subtract(datas[0], Double.valueOf(32768));
214
215        // others channels data
216        for (int c = 1; c < sizeC; c++)
217        {
218            image.setPosition(c + 1, z + 1, t + 1);
219            datas[c] = Array1DUtil.copyOf(image.getProcessor().getPixels());
220            // special case of 16 bits signed data --> subtract 32768
221            if (signed16)
222                datas[c] = ArrayMath.subtract(datas, Double.valueOf(32768));
223        }
224
225        // create a single image from all channels
226        return new IcyBufferedImage(sizeX, sizeY, datas, signed16);
227    }
228
229    /**
230     * Convert the ImageJ {@link ImagePlus} image at position [Z,T] into an Icy image
231     */
232    public static IcyBufferedImage convertToIcyBufferedImage(ImagePlus image, int z, int t)
233    {
234        final int[] dim = image.getDimensions(true);
235
236        return convertToIcyBufferedImage(image, z, t, dim[0], dim[1], dim[2], image.getType(), image
237                .getLocalCalibration().isSigned16Bit());
238    }
239
240    /**
241     * Convert the specified ImageJ {@link ImagePlus} object to Icy {@link Sequence}
242     */
243    public static Sequence convertToIcySequence(ImagePlus image, ProgressListener progressListener)
244    {
245        final Sequence result = new Sequence(image.getTitle());
246        final int[] dim = image.getDimensions(true);
247
248        final int sizeX = dim[0];
249        final int sizeY = dim[1];
250        final int sizeC = dim[2];
251        final int sizeZ = dim[3];
252        final int sizeT = dim[4];
253        final int type = image.getType();
254        // only integer signed type allowed in ImageJ is 16 bit signed
255        final boolean signed16 = image.getLocalCalibration().isSigned16Bit();
256
257        final int len = sizeZ * sizeT;
258        int position = 0;
259
260        result.beginUpdate();
261        try
262        {
263            // convert image
264            for (int t = 0; t < sizeT; t++)
265            {
266                for (int z = 0; z < sizeZ; z++)
267                {
268                    if (progressListener != null)
269                        progressListener.notifyProgress(position, len);
270
271                    result.setImage(t, z, convertToIcyBufferedImage(image, z, t, sizeX, sizeY, sizeC, type, signed16));
272
273                    position++;
274                }
275            }
276
277            // convert ROI(s)
278            final RoiManager roiManager = RoiManager.getInstance();
279            final Roi[] rois;
280
281            if (roiManager != null)
282                rois = roiManager.getRoisAsArray();
283            else
284                rois = new Roi[] {};
285
286            if (rois.length > 0)
287            {
288                for (Roi ijRoi : rois)
289                {
290                    // can happen
291                    if (ijRoi != null)
292                        for (ROI icyRoi : convertToIcyRoi(ijRoi))
293                            result.addROI(icyRoi);
294                }
295            }
296            else
297            {
298                final Roi roi = image.getRoi();
299
300                if (roi != null)
301                    for (ROI icyRoi : convertToIcyRoi(roi))
302                        result.addROI(icyRoi);
303            }
304
305            // calibrate
306            calibrateIcySequence(result, image.getCalibration());
307        }
308        finally
309        {
310            result.endUpdate();
311        }
312
313        return result;
314    }
315
316    /**
317     * Convert the specified Icy {@link Sequence} object to ImageJ {@link ImagePlus}
318     */
319    public static ImagePlus convertToImageJImage(Sequence sequence, boolean useRoiManager,
320            ProgressListener progressListener)
321    {
322        // create the image
323        final ImagePlus result = createImagePlus(sequence, progressListener);
324        // calibrate
325        calibrateImageJImage(result, sequence);
326
327        // convert ROI
328        final List<Roi> ijRois = new ArrayList<Roi>();
329        for (ROI2D roi : sequence.getROI2Ds())
330            ijRois.add(convertToImageJRoi(roi));
331
332        if (ijRois.size() > 0)
333        {
334            if ((ijRois.size() > 1) && useRoiManager)
335            {
336                RoiManager roiManager = RoiManager.getInstance();
337                if (roiManager == null)
338                {
339                    ThreadUtil.invokeNow(new Runnable()
340                    {
341                        @Override
342                        public void run()
343                        {
344                            // need to do it on EDT
345                            new RoiManager();
346                        }
347                    });
348                }
349
350                roiManager = RoiManager.getInstance();
351                int n = 0;
352                for (Roi roi : ijRois)
353                    roiManager.add(result, roi, n++);
354            }
355
356            result.setRoi(ijRois.get(0));
357        }
358
359        if (result.getNChannels() > 4)
360            return new CompositeImage(result, CompositeImage.COLOR);
361        else if (result.getNChannels() > 1)
362            return new CompositeImage(result, CompositeImage.COMPOSITE);
363
364        return result;
365    }
366
367    /**
368     * Convert the specified Icy {@link Sequence} object to ImageJ {@link ImagePlus}
369     */
370    public static ImagePlus convertToImageJImage(Sequence sequence, ProgressListener progressListener)
371    {
372        return convertToImageJImage(sequence, false, progressListener);
373    }
374
375    /**
376     * Convert the specified ImageJ {@link Roi} object to Icy {@link ROI}.
377     */
378    public static List<ROI2D> convertToIcyRoi(Roi roi)
379    {
380        final List<ROI2D> result = new ArrayList<ROI2D>();
381        final List<Point2D> pts = new ArrayList<Point2D>();
382        final FloatPolygon fp;
383
384        switch (roi.getType())
385        {
386            default:
387                result.add(new ROI2DRectangle(roi.getFloatBounds()));
388                break;
389
390            case Roi.OVAL:
391                result.add(new ROI2DEllipse(roi.getFloatBounds()));
392                break;
393
394            case Roi.LINE:
395                final Rectangle2D rect = roi.getFloatBounds();
396                final double x = rect.getX();
397                final double y = rect.getY();
398                result.add(new ROI2DLine(new Point2D.Double(x, y), new Point2D.Double(x + rect.getWidth(), y
399                        + rect.getHeight())));
400                break;
401
402            case Roi.TRACED_ROI:
403            case Roi.POLYGON:
404            case Roi.FREEROI:
405                fp = ((PolygonRoi) roi).getFloatPolygon();
406                for (int p = 0; p < fp.npoints; p++)
407                    pts.add(new Point2D.Float(fp.xpoints[p], fp.ypoints[p]));
408
409                final ROI2DPolygon roiPolygon = new ROI2DPolygon();
410                roiPolygon.setPoints(pts);
411
412                // TRACED_ROI should be converted to ROI2DArea
413                if (roi.getType() == Roi.TRACED_ROI)
414                    result.add(new ROI2DArea(roiPolygon.getBooleanMask(true)));
415                else
416                    result.add(roiPolygon);
417                break;
418
419            case Roi.FREELINE:
420            case Roi.POLYLINE:
421            case Roi.ANGLE:
422                fp = ((PolygonRoi) roi).getFloatPolygon();
423                for (int p = 0; p < fp.npoints; p++)
424                    pts.add(new Point2D.Float(fp.xpoints[p], fp.ypoints[p]));
425
426                final ROI2DPolyLine roiPolyline = new ROI2DPolyLine();
427                roiPolyline.setPoints(pts);
428
429                result.add(roiPolyline);
430                break;
431
432            case Roi.COMPOSITE:
433                final ROI2DPath roiPath = new ROI2DPath(((ShapeRoi) roi).getShape());
434                final Rectangle2D.Double roiBounds = roi.getFloatBounds();
435                // we have to adjust position as Shape do not contains it
436                if (roiPath.canSetPosition())
437                    roiPath.setPosition2D(new Point2D.Double(roiBounds.x, roiBounds.y));
438                result.add(roiPath);
439                break;
440
441            case Roi.POINT:
442                fp = ((PolygonRoi) roi).getFloatPolygon();
443                for (int p = 0; p < fp.npoints; p++)
444                    pts.add(new Point2D.Float(fp.xpoints[p], fp.ypoints[p]));
445
446                for (Point2D pt : pts)
447                    result.add(new ROI2DPoint(pt));
448                break;
449        }
450
451        int ind = 0;
452        for (ROI2D r : result)
453        {
454            r.setC(roi.getCPosition() - 1);
455            r.setZ(roi.getZPosition() - 1);
456            r.setT(roi.getTPosition() - 1);
457            r.setSelected(false);
458            if (result.size() > 1)
459                r.setName(roi.getName() + " " + ind);
460            else
461                r.setName(roi.getName());
462            Color c = roi.getStrokeColor();
463            if (c == null)
464                c = roi.getFillColor();
465            if (c != null)
466                r.setColor(c);
467        }
468
469        return result;
470    }
471
472    /**
473     * Convert the specified Icy {@link ROI} object to ImageJ {@link Roi}.
474     */
475    public static Roi convertToImageJRoi(ROI2D roi)
476    {
477        final Roi result;
478
479        if (roi instanceof ROI2DShape)
480        {
481            final List<Point2D> pts = ((ROI2DShape) roi).getPoints();
482
483            if (roi instanceof ROI2DPoint)
484            {
485                final Point2D p = pts.get(0);
486                result = new PointRoi(p.getX(), p.getY());
487            }
488            else if (roi instanceof ROI2DLine)
489            {
490                final Point2D p1 = pts.get(0);
491                final Point2D p2 = pts.get(1);
492                result = new Line(p1.getX(), p1.getY(), p2.getX(), p2.getY());
493            }
494            else if (roi instanceof ROI2DRectangle)
495            {
496                final Rectangle2D r = roi.getBounds2D();
497                result = new Roi(r.getX(), r.getY(), r.getWidth(), r.getHeight(), 0);
498            }
499            else if (roi instanceof ROI2DEllipse)
500            {
501                final Rectangle2D r = roi.getBounds2D();
502                result = new OvalRoi(r.getX(), r.getY(), r.getWidth(), r.getHeight());
503            }
504            else if ((roi instanceof ROI2DPolyLine) || (roi instanceof ROI2DPolygon))
505            {
506                final FloatPolygon fp = new FloatPolygon();
507                for (Point2D p : pts)
508                    fp.addPoint(p.getX(), p.getY());
509                if (roi instanceof ROI2DPolyLine)
510                    result = new PolygonRoi(fp, Roi.POLYLINE);
511                else
512                    result = new PolygonRoi(fp, Roi.POLYGON);
513            }
514            else
515                // create compatible shape ROI
516                result = new ShapeRoi(((ROI2DPath) roi).getShape());
517        }
518        else if (roi instanceof ROI2DArea)
519        {
520            final ROI2DArea roiArea = (ROI2DArea) roi;
521            final Point[] points = roiArea.getBooleanMask(true).getPoints();
522
523            final Area area = new Area();
524            for (Point pt : points)
525                area.add(new Area(new Rectangle(pt.x, pt.y, 1, 1)));
526
527            result = new ShapeRoi(area);
528        }
529        else
530        {
531            // create standard ROI
532            final Rectangle2D r = roi.getBounds2D();
533            result = new Roi(r.getX(), r.getY(), r.getWidth(), r.getHeight());
534        }
535
536        result.setPosition(roi.getC() + 1, roi.getZ() + 1, roi.getT() + 1);
537        result.setName(roi.getName());
538        result.setStrokeColor(roi.getColor());
539        // result.setFillColor(roi.getColor());
540        // result.setStrokeWidth(roi.getStroke());
541
542        return result;
543    }
544
545    /**
546     * @deprecated Use {@link #convertToImageJRoi(ROI2D)} instead.
547     */
548    @Deprecated
549    public static PointRoi convertToImageJRoiPoint(List<ROI2DPoint> points)
550    {
551        final int size = points.size();
552        final float x[] = new float[size];
553        final float y[] = new float[size];
554
555        for (int i = 0; i < points.size(); i++)
556        {
557            final ROI2DPoint point = points.get(i);
558
559            x[i] = (float) point.getPoint().getX();
560            y[i] = (float) point.getPoint().getY();
561        }
562
563        return new PointRoi(x, y, size);
564    }
565}