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}