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 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.file; 020 021import icy.gui.frame.progress.FailedAnnounceFrame; 022import icy.gui.frame.progress.FileFrame; 023import icy.gui.menu.ApplicationMenu; 024import icy.image.IcyBufferedImage; 025import icy.image.IcyBufferedImageUtil; 026import icy.image.colormodel.IcyColorModel; 027import icy.image.lut.LUT; 028import icy.main.Icy; 029import icy.painter.Overlay; 030import icy.preferences.GeneralPreferences; 031import icy.roi.ROI; 032import icy.sequence.MetaDataUtil; 033import icy.sequence.Sequence; 034import icy.system.IcyExceptionHandler; 035import icy.type.DataType; 036import icy.util.OMEUtil; 037import icy.util.StringUtil; 038 039import java.awt.image.BufferedImage; 040import java.io.File; 041import java.io.IOException; 042import java.text.DecimalFormat; 043 044import loci.common.services.ServiceException; 045import loci.formats.FormatException; 046import loci.formats.IFormatWriter; 047import loci.formats.UnknownFormatException; 048import loci.formats.meta.MetadataRetrieve; 049import loci.formats.out.APNGWriter; 050import loci.formats.out.AVIWriter; 051import loci.formats.out.JPEG2000Writer; 052import loci.formats.out.JPEGWriter; 053import loci.formats.out.OMETiffWriter; 054import loci.formats.out.TiffWriter; 055import ome.xml.meta.OMEXMLMetadata; 056 057/** 058 * Sequence / Image saver class.<br> 059 * <br> 060 * Supported save format are the following : TIFF (preferred), PNG, JPG and AVI. 061 * When sequence is saved as multiple file the following naming convention is used :<br> 062 * <code>filename-tttt-zzzz</code> 063 * 064 * @author Stephane & Fab 065 */ 066public class Saver 067{ 068 /** 069 * @deprecated use {@link OMEUtil#generateMetaData(int, int, int, int, int, DataType, boolean)} instead 070 */ 071 @Deprecated 072 public static loci.formats.ome.OMEXMLMetadata generateMetaData(int sizeX, int sizeY, int sizeC, int sizeZ, 073 int sizeT, DataType dataType) throws ServiceException 074 { 075 return (loci.formats.ome.OMEXMLMetadata) OMEUtil.generateMetaData(sizeX, sizeY, sizeC, sizeZ, sizeT, dataType, 076 false); 077 } 078 079 /** 080 * @deprecated use {@link OMEUtil#generateMetaData(int, int, int, int, int, DataType, boolean)} instead 081 */ 082 @Deprecated 083 public static loci.formats.ome.OMEXMLMetadata generateMetaData(int sizeX, int sizeY, int sizeC, int sizeZ, 084 int sizeT, int dataType, boolean signedDataType) throws ServiceException 085 { 086 return (loci.formats.ome.OMEXMLMetadata) OMEUtil.generateMetaData(sizeX, sizeY, sizeC, sizeZ, sizeT, 087 DataType.getDataType(dataType, signedDataType), false); 088 } 089 090 /** 091 * @deprecated use {@link OMEUtil#generateMetaData(int, int, int, DataType, boolean)} instead 092 */ 093 @Deprecated 094 public static loci.formats.ome.OMEXMLMetadata generateMetaData(int sizeX, int sizeY, int sizeC, DataType dataType) 095 throws ServiceException 096 { 097 return (loci.formats.ome.OMEXMLMetadata) OMEUtil.generateMetaData(sizeX, sizeY, sizeC, 1, 1, dataType, false); 098 } 099 100 /** 101 * @deprecated use {@link OMEUtil#generateMetaData(int, int, int, DataType, boolean)} instead 102 */ 103 @Deprecated 104 public static loci.formats.ome.OMEXMLMetadata generateMetaData(int sizeX, int sizeY, int sizeC, int dataType, 105 boolean signedDataType) throws ServiceException 106 { 107 return (loci.formats.ome.OMEXMLMetadata) OMEUtil.generateMetaData(sizeX, sizeY, sizeC, 108 DataType.getDataType(dataType, signedDataType), false); 109 } 110 111 /** 112 * Returns the {@link ImageFileFormat} corresponding to specified {@link IFormatWriter}.<br> 113 * <code>defaultValue</code> is returned if no matching format is found. 114 */ 115 public static ImageFileFormat getImageFileFormat(IFormatWriter writer, ImageFileFormat defaultValue) 116 { 117 if (writer instanceof TiffWriter) 118 return ImageFileFormat.TIFF; 119 if (writer instanceof APNGWriter) 120 return ImageFileFormat.PNG; 121 if (writer instanceof JPEGWriter) 122 return ImageFileFormat.JPG; 123 if (writer instanceof JPEG2000Writer) 124 return ImageFileFormat.JPG; 125 if (writer instanceof AVIWriter) 126 return ImageFileFormat.AVI; 127 128 return defaultValue; 129 } 130 131 /** 132 * @deprecated Use {@link #getImageFileFormat(IFormatWriter, ImageFileFormat)} instead. 133 */ 134 @Deprecated 135 public static FileFormat getFileFormat(IFormatWriter writer, FileFormat defaultValue) 136 { 137 return getImageFileFormat(writer, ImageFileFormat.getFormat(defaultValue)).toFileFormat(); 138 } 139 140 /** 141 * Return the writer to use for the specified ImageFileFormat.<br> 142 * <br> 143 * The following writer are currently supported :<br> 144 * <code>OMETiffWriter</code> : TIFF image file (default)<br> 145 * <code>APNGWriter</code> : PNG image file<br> 146 * <code>JPEGWriter</code> : JPG image file<br> 147 * <code>AVIWriter</code> : AVI video file<br> 148 * 149 * @param format 150 * {@link ImageFileFormat} we want to retrieve the saver.<br> 151 * Accepted values:<br> 152 * {@link ImageFileFormat#TIFF}<br> 153 * {@link ImageFileFormat#PNG}<br> 154 * {@link ImageFileFormat#JPG}<br> 155 * {@link ImageFileFormat#AVI}<br> 156 * null 157 */ 158 public static IFormatWriter getWriter(ImageFileFormat format) 159 { 160 final IFormatWriter result; 161 162 switch (format) 163 { 164 case PNG: 165 result = new APNGWriter(); 166 break; 167 168 case JPG: 169 result = new JPEGWriter(); 170 break; 171 172 case AVI: 173 result = new AVIWriter(); 174 break; 175 176 default: 177 result = new OMETiffWriter(); 178 // this way we are sure the TIF saver is always compressing 179 try 180 { 181 result.setCompression("LZW"); 182 } 183 catch (FormatException e) 184 { 185 // no compression 186 } 187 break; 188 } 189 190 return result; 191 } 192 193 /** 194 * @deprecated Use {@link #getWriter(ImageFileFormat)} instead. 195 */ 196 @Deprecated 197 public static IFormatWriter getWriter(FileFormat fileFormat) 198 { 199 return getWriter(ImageFileFormat.getFormat(fileFormat)); 200 } 201 202 /** 203 * Return the writer to use for the specified filename extension.<br> 204 * <br> 205 * The following writer are currently supported :<br> 206 * <code>OMETiffWriter</code> : TIFF image file (default)<br> 207 * <code>APNGWriter</code> : PNG image file<br> 208 * <code>JPEGWriter</code> : JPG image file<br> 209 * <code>AVIWriter</code> : AVI video file<br> 210 * 211 * @param ext 212 * Extension we want to retrieve the corresponding image writer. 213 * @param defaultFormat 214 * default {@link ImageFileFormat} to use if <code>ext</code> is not recognized.<br> 215 * Accepted values:<br> 216 * {@link ImageFileFormat#TIFF}<br> 217 * {@link ImageFileFormat#PNG}<br> 218 * {@link ImageFileFormat#JPG}<br> 219 * {@link ImageFileFormat#AVI}<br> 220 * null 221 */ 222 public static IFormatWriter getWriter(String ext, ImageFileFormat defaultFormat) 223 { 224 return getWriter(ImageFileFormat.getWriteFormat(ext, defaultFormat)); 225 } 226 227 /** 228 * @deprecated Use {@link #getWriter(String, ImageFileFormat)} instead. 229 */ 230 @Deprecated 231 public static IFormatWriter getWriter(String ext, FileFormat defaultFormat) 232 { 233 return getWriter(ext, ImageFileFormat.getFormat(defaultFormat)); 234 } 235 236 /** 237 * @deprecated Use {@link #getWriter(String, FileFormat)} instead. 238 */ 239 @Deprecated 240 public static IFormatWriter getWriter(String ext) 241 { 242 return getWriter(ext, ImageFileFormat.TIFF); 243 } 244 245 /** 246 * Return the writer to use for the specified file.<br> 247 * <br> 248 * The following writer are currently supported :<br> 249 * <code>OMETiffWriter</code> : TIFF image file (default)<br> 250 * <code>APNGWriter</code> : PNG image file<br> 251 * <code>JPEGWriter</code> : JPG image file<br> 252 * <code>AVIWriter</code> : AVI video file<br> 253 * 254 * @param file 255 * File we want to retrieve the corresponding image writer. 256 * @param defaultFormat 257 * default {@link ImageFileFormat} to use if <code>file</code> is not recognized.<br> 258 * Accepted values:<br> 259 * {@link ImageFileFormat#TIFF}<br> 260 * {@link ImageFileFormat#PNG}<br> 261 * {@link ImageFileFormat#JPG}<br> 262 * {@link ImageFileFormat#AVI}<br> 263 * null 264 */ 265 public static IFormatWriter getWriter(File file, ImageFileFormat defaultFormat) 266 { 267 return getWriter(FileUtil.getFileExtension(file.getName(), false), defaultFormat); 268 } 269 270 /** 271 * @deprecated Use {@link #getWriter(File, ImageFileFormat)} instead. 272 */ 273 @Deprecated 274 public static IFormatWriter getWriter(File file, FileFormat defaultFormat) 275 { 276 return getWriter(file, ImageFileFormat.getFormat(defaultFormat)); 277 } 278 279 /** 280 * @deprecated Use {@link #getWriter(File, FileFormat)} instead. 281 */ 282 @Deprecated 283 public static IFormatWriter getWriter(File file) 284 { 285 return getWriter(file, ImageFileFormat.TIFF); 286 } 287 288 /** 289 * Return the closest compatible {@link IcyColorModel} supported by the specified ImageFileFormat 290 * from the specified image description.<br> 291 * That means this file format is able to save the data described by the returned {@link IcyColorModel} without any 292 * loss 293 * or conversion.<br> 294 * 295 * @param imageFileFormat 296 * Image file format we want to test compatibility 297 * @param numChannel 298 * number of channel of the image 299 * @param dataType 300 * image data type 301 */ 302 public static IcyColorModel getCompatibleColorModel(ImageFileFormat imageFileFormat, int numChannel, 303 DataType dataType) 304 { 305 final DataType outDataType; 306 final int outNumChannel; 307 308 switch (imageFileFormat) 309 { 310 default: 311 case TIFF: 312 // TIFF supports all formats 313 outDataType = dataType; 314 outNumChannel = numChannel; 315 break; 316 317 case PNG: 318 // PNG only supports byte data type (short is not really valid) 319 if (dataType.getSize() > 1) 320 outDataType = DataType.UBYTE; 321 else 322 outDataType = dataType; 323 324 // PNG supports a maximum of 4 channels 325 outNumChannel = Math.min(numChannel, 4); 326 break; 327 328 case AVI: 329 case JPG: 330 // JPG, AVI, default only supports byte data type 331 if (dataType.getSize() > 1) 332 outDataType = DataType.UBYTE; 333 else 334 outDataType = dataType; 335 336 // 3 channels at max 337 if (numChannel > 3) 338 outNumChannel = 3; 339 else 340 { 341 // special case of 2 channels 342 if (numChannel == 2) 343 // convert to RGB 344 outNumChannel = 3; 345 else 346 outNumChannel = numChannel; 347 } 348 break; 349 } 350 351 return IcyColorModel.createInstance(outNumChannel, outDataType); 352 } 353 354 /** 355 * Return the closest compatible {@link IcyColorModel} supported by the specified image file format 356 * from the specified {@link IcyColorModel}.<br> 357 * That means this image file format supports saving data described by the returned {@link IcyColorModel} without 358 * any loss or conversion. 359 * 360 * @param imageFileFormat 361 * Image file format we want to test compatibility 362 * @param colorModel 363 * the colorModel describing data / image format 364 */ 365 public static IcyColorModel getCompatibleColorModel(ImageFileFormat imageFileFormat, IcyColorModel colorModel) 366 { 367 return getCompatibleColorModel(imageFileFormat, colorModel.getNumComponents(), colorModel.getDataType_()); 368 } 369 370 /** 371 * Return true if the specified image file format is compatible with the image description.<br> 372 * That means this image file format supports saving data without any loss or conversion. 373 * 374 * @param imageFileFormat 375 * Image file format we want to test compatibility 376 * @param numChannel 377 * number of channel of the image 378 * @param alpha 379 * true if the image has an alpha channel 380 * @param dataType 381 * image data type 382 */ 383 public static boolean isCompatible(ImageFileFormat imageFileFormat, int numChannel, boolean alpha, 384 DataType dataType) 385 { 386 return isCompatible(imageFileFormat, IcyColorModel.createInstance(numChannel, dataType)); 387 } 388 389 /** 390 * Return true if the specified image file format is compatible with the given {@link IcyColorModel}. <br> 391 * That means this image file format supports saving data described by the returned {@link IcyColorModel} without 392 * any loss or conversion.<br> 393 * The color map data are never preserved, they are always restored to their default.<br> 394 */ 395 public static boolean isCompatible(ImageFileFormat imageFileFormat, IcyColorModel colorModel) 396 { 397 return colorModel.isCompatible(getCompatibleColorModel(imageFileFormat, colorModel)); 398 } 399 400 /** 401 * Return true if the specified image file format is compatible to save the given Sequence.<br> 402 * That means this image file format supports saving all original data (3D/4D/5D) without any loss or conversion. 403 */ 404 public static boolean isCompatible(ImageFileFormat imageFileFormat, Sequence sequence) 405 { 406 final boolean multiZ = sequence.getSizeZ() > 1; 407 final boolean multiT = sequence.getSizeT() > 1; 408 409 switch (imageFileFormat) 410 { 411 case JPG: 412 case PNG: 413 // JPG and PNG: no support for time sequence or 3D image 414 if ((multiZ) || (multiT)) 415 return false; 416 break; 417 418 case AVI: 419 // AVI: not support for 3D image 420 if (multiZ) 421 return false; 422 break; 423 } 424 425 return isCompatible(imageFileFormat, sequence.getColorModel()); 426 } 427 428 /** 429 * Return the separate channel flag from specified image file format and color space 430 */ 431 private static boolean getSeparateChannelFlag(ImageFileFormat imageFileFormat, int numChannel, DataType dataType) 432 { 433 // only if we have more than 1 channel 434 if (numChannel > 1) 435 { 436 // only TIFF writer support it: better to not separate channel for RGB images 437 if (imageFileFormat.equals(ImageFileFormat.TIFF)) 438 return (numChannel != 3) || (dataType.getSize() > 1); 439 } 440 441 // others writers does not support separated channel 442 return false; 443 } 444 445 /** 446 * Return the separate channel flag from specified image file format and color space 447 */ 448 private static boolean getSeparateChannelFlag(ImageFileFormat imageFileFormat, IcyColorModel colorModel) 449 { 450 return getSeparateChannelFlag(imageFileFormat, colorModel.getNumComponents(), colorModel.getDataType_()); 451 } 452 453 /** 454 * Return the closest compatible {@link IcyColorModel} supported by writer 455 * from the specified image description.<br> 456 * That means the writer is able to save the data described by the returned {@link IcyColorModel} without any loss 457 * or conversion.<br> 458 * 459 * @param writer 460 * IFormatWriter we want to test compatibility 461 * @param numChannel 462 * number of channel of the image 463 * @param dataType 464 * image data type 465 */ 466 public static IcyColorModel getCompatibleColorModel(IFormatWriter writer, int numChannel, DataType dataType) 467 { 468 return getCompatibleColorModel(getImageFileFormat(writer, ImageFileFormat.TIFF), numChannel, dataType); 469 } 470 471 /** 472 * Return the closest compatible {@link IcyColorModel} supported by writer 473 * from the specified {@link IcyColorModel}.<br> 474 * That means the writer is able to save the data described by the returned {@link IcyColorModel} without any loss 475 * or conversion.<br> 476 * 477 * @param writer 478 * IFormatWriter we want to test compatibility 479 * @param colorModel 480 * the colorModel describing data / image format 481 */ 482 public static IcyColorModel getCompatibleColorModel(IFormatWriter writer, IcyColorModel colorModel) 483 { 484 return getCompatibleColorModel(writer, colorModel.getNumComponents(), colorModel.getDataType_()); 485 } 486 487 /** 488 * Return true if the specified writer is compatible with the image description.<br> 489 * That means the writer is able to save the data without any loss or conversion.<br> 490 * 491 * @param numChannel 492 * number of channel of the image 493 * @param alpha 494 * true if the image has an alpha channel 495 * @param dataType 496 * image data type 497 */ 498 public static boolean isCompatible(IFormatWriter writer, int numChannel, boolean alpha, DataType dataType) 499 { 500 return isCompatible(writer, IcyColorModel.createInstance(numChannel, dataType)); 501 } 502 503 /** 504 * Return true if the specified writer is compatible with the given {@link IcyColorModel}. <br> 505 * That means the writer is able to save the data described by the colorModel without any loss 506 * or conversion.<br> 507 * The color map data are never preserved, they are always restored to their default.<br> 508 */ 509 public static boolean isCompatible(IFormatWriter writer, IcyColorModel colorModel) 510 { 511 return colorModel.isCompatible(getCompatibleColorModel(writer, colorModel)); 512 } 513 514 /** 515 * Return true if the specified writer is compatible to save the given Sequence.<br> 516 * That means the writer is able to save all original data (3D/4D/5D) without any loss or conversion. 517 */ 518 public static boolean isCompatible(IFormatWriter writer, Sequence sequence) 519 { 520 return isCompatible(getImageFileFormat(writer, ImageFileFormat.TIFF), sequence); 521 } 522 523 /** 524 * Return the separate channel flag from specified writer and color space 525 */ 526 private static boolean getSeparateChannelFlag(IFormatWriter writer, int numChannel, DataType dataType) 527 { 528 return getSeparateChannelFlag(getImageFileFormat(writer, ImageFileFormat.TIFF), numChannel, dataType); 529 } 530 531 /** 532 * Return the separate channel flag from specified writer and color space 533 */ 534 private static boolean getSeparateChannelFlag(IFormatWriter writer, IcyColorModel colorModel) 535 { 536 return getSeparateChannelFlag(writer, colorModel.getNumComponents(), colorModel.getDataType_()); 537 } 538 539 /** 540 * Save the specified sequence in the specified file.<br> 541 * If sequence contains severals images then file is used as a directory<br> 542 * to store all single images. 543 * 544 * @param sequence 545 * sequence to save 546 * @param file 547 * file where we want to save sequence 548 */ 549 public static void save(Sequence sequence, File file) 550 { 551 save(sequence, file, 15, (sequence.getSizeZ() * sequence.getSizeT()) > 1, true); 552 } 553 554 /** 555 * @deprecated Use {@link #save(Sequence, File, boolean, boolean)} instead. 556 */ 557 @Deprecated 558 public static void save(Sequence sequence, File file, boolean multipleFiles) 559 { 560 save(sequence, file, 0, sequence.getSizeZ() - 1, 0, sequence.getSizeT() - 1, 15, multipleFiles, true); 561 } 562 563 /** 564 * Save the specified sequence in the specified file.<br> 565 * When the sequence contains severals image the multiFile flag is used to indicate<br> 566 * if images are saved in severals files (file then specify a directory) or in a single file. 567 * 568 * @param sequence 569 * sequence to save 570 * @param file 571 * file where we want to save sequence 572 * @param multipleFiles 573 * flag to indicate if images are saved in separate file 574 * @param showProgress 575 * show progress bar 576 */ 577 public static void save(Sequence sequence, File file, boolean multipleFiles, boolean showProgress) 578 { 579 save(sequence, file, 15, multipleFiles, showProgress); 580 } 581 582 /** 583 * @deprecated Use {@link #save(Sequence, File, int, boolean, boolean)} instead. 584 */ 585 @Deprecated 586 public static void save(Sequence sequence, File file, int zMin, int zMax, int tMin, int tMax, int fps, 587 boolean multipleFiles) 588 { 589 save(sequence, file, zMin, zMax, tMin, tMax, fps, multipleFiles, true); 590 } 591 592 /** 593 * @deprecated Use {@link #save(Sequence, File, int, boolean, boolean)} instead. 594 */ 595 @Deprecated 596 public static void save(Sequence sequence, File file, int zMin, int zMax, int tMin, int tMax, int fps, 597 boolean multipleFile, boolean showProgress) 598 { 599 save(null, sequence, file, fps, multipleFile, showProgress, true); 600 } 601 602 /** 603 * Save the specified sequence in the specified file.<br> 604 * When the sequence contains severals image the multipleFile flag is used to indicate<br> 605 * if images are saved as separate files (file then specify a directory) or not.<br> 606 * zMin - zMax and tMin - tMax define the Z and T images range to save.<br> 607 * 608 * @param sequence 609 * sequence to save 610 * @param file 611 * file where we want to save sequence 612 * @param fps 613 * frame rate for AVI sequence save 614 * @param multipleFile 615 * flag to indicate if images are saved in separate file 616 * @param showProgress 617 * show progress bar 618 */ 619 public static void save(Sequence sequence, File file, int fps, boolean multipleFile, boolean showProgress) 620 { 621 save(null, sequence, file, fps, multipleFile, showProgress, true); 622 } 623 624 /** 625 * @deprecated Use {@link #save(IFormatWriter, Sequence, File, int, boolean, boolean, boolean)} instead. 626 */ 627 @Deprecated 628 public static void save(IFormatWriter formatWriter, Sequence sequence, File file, int zMin, int zMax, int tMin, 629 int tMax, int fps, boolean multipleFile, boolean showProgress) 630 { 631 save(formatWriter, sequence, file, fps, multipleFile, showProgress, true); 632 } 633 634 /** 635 * @deprecated Use {@link #save(IFormatWriter, Sequence, File, int, boolean, boolean, boolean)} instead. 636 */ 637 @Deprecated 638 public static void save(IFormatWriter formatWriter, Sequence sequence, File file, int zMin, int zMax, int tMin, 639 int tMax, int fps, boolean multipleFile, boolean showProgress, boolean addToRecent) 640 { 641 save(formatWriter, sequence, file, fps, multipleFile, showProgress, addToRecent); 642 } 643 644 /** 645 * Save the specified sequence in the specified file.<br> 646 * When the sequence contains severals image the multipleFile flag is used to indicate 647 * if images are saved as separate files (file then specify a directory) or not.<br> 648 * <code>zMin</code> - <code>zMax</code> and <code>tMin</code> - <code>tMax</code> define the Z 649 * and T images range to save.<br> 650 * 651 * @param formatWriter 652 * writer used to save sequence (define the image format).<br> 653 * If set to <code>null</code> then writer is determined from the file extension.<br> 654 * If destination file does not have a valid extension (for folder for instance) then you 655 * have to specify a valid Writer to write the image file (see {@link #getWriter(ImageFileFormat)}) 656 * @param sequence 657 * sequence to save 658 * @param file 659 * file where we want to save sequence.<br> 660 * Depending the <code>formatWriter</code> the file extension may be modified.<br> 661 * That is preferred as saving an image with a wrong extension may result in error on 662 * future read (wrong reader detection).<br> 663 * @param fps 664 * frame rate for AVI sequence save 665 * @param multipleFile 666 * flag to indicate if images are saved in separate file.<br> 667 * When multiple file is enabled the <code>file</code> parameter is considerer as a folder if it doens't have 668 * any extension 669 * @param showProgress 670 * show progress bar 671 * @param addToRecent 672 * add the saved sequence to recent opened sequence list 673 */ 674 public static void save(IFormatWriter formatWriter, Sequence sequence, File file, int fps, boolean multipleFile, 675 boolean showProgress, boolean addToRecent) 676 { 677 final String filePath = FileUtil.cleanPath(FileUtil.getGenericPath(file.getAbsolutePath())); 678 final int sizeT = sequence.getSizeT(); 679 final int sizeZ = sequence.getSizeZ(); 680 final int numImages = sizeT * sizeZ; 681 final FileFrame saveFrame; 682 final ApplicationMenu mainMenu; 683 684 if (addToRecent) 685 mainMenu = Icy.getMainInterface().getApplicationMenu(); 686 else 687 mainMenu = null; 688 if (showProgress && !Icy.getMainInterface().isHeadLess()) 689 saveFrame = new FileFrame("Saving", filePath); 690 else 691 saveFrame = null; 692 try 693 { 694 if (saveFrame != null) 695 { 696 saveFrame.setLength(numImages); 697 saveFrame.setPosition(0); 698 } 699 700 final IFormatWriter writer; 701 final Sequence savedSequence; 702 703 // get the writer 704 if (formatWriter == null) 705 writer = getWriter(file, ImageFileFormat.TIFF); 706 else 707 writer = formatWriter; 708 709 if (writer == null) 710 throw new UnknownFormatException("Can't find a valid image writer for the specified file: " + file); 711 712 // need multiple files ? 713 if ((numImages > 1) && multipleFile) 714 { 715 // save as severals images 716 final DecimalFormat decimalFormat = new DecimalFormat("0000"); 717 final String fileName = FileUtil.getFileName(filePath, false); 718 String fileExt = FileUtil.getFileExtension(filePath, true); 719 720 String fileBaseDirectory = FileUtil.getDirectory(filePath); 721 if (fileBaseDirectory.endsWith("/")) 722 fileBaseDirectory = fileBaseDirectory.substring(0, fileBaseDirectory.length() - 1); 723 724 // no extension (directory) ? 725 if (StringUtil.isEmpty(fileExt)) 726 { 727 // filename is part of directory 728 fileBaseDirectory += FileUtil.separator + fileName; 729 // use the default file extension for the specified writer 730 fileExt = "." + getImageFileFormat(writer, ImageFileFormat.TIFF).getExtensions()[0]; 731 } 732 733 final String filePathWithoutExt = fileBaseDirectory + FileUtil.separator + fileName; 734 735 // create output directory 736 FileUtil.createDir(fileBaseDirectory); 737 738 // default name used --> use filename 739 if (sequence.isDefaultName()) 740 sequence.setName(fileName); 741 sequence.setFilename(fileBaseDirectory); 742 // reset origin informations as now we are saved 743 sequence.resetOriginInformation(); 744 // reset image provider 745 sequence.setImageProvider(null); 746 747 // assume that is the saved sequence (used for metadata) 748 savedSequence = sequence; 749 750 for (int t = 0; t < sizeT; t++) 751 { 752 for (int z = 0; z < sizeZ; z++) 753 { 754 String filename = filePathWithoutExt; 755 756 if (sizeT > 1) 757 filename += "_t" + decimalFormat.format(t); 758 if (sizeZ > 1) 759 filename += "_z" + decimalFormat.format(z); 760 filename += fileExt; 761 762 // save as single image file 763 save(writer, sequence, filename, t, z, fps, saveFrame); 764 } 765 } 766 767 // add as one item to recent file list 768 if (mainMenu != null) 769 mainMenu.addRecentFile(fileBaseDirectory); 770 } 771 else 772 { 773 final ImageFileFormat iff = getImageFileFormat(writer, ImageFileFormat.TIFF); 774 final String fileExt = FileUtil.getFileExtension(filePath, false); 775 // force to set correct file extension 776 final String fixedFilePath; 777 778 if (iff.matches(fileExt)) 779 fixedFilePath = filePath; 780 else 781 fixedFilePath = filePath + "." + iff.getExtensions()[0]; 782 783 // default name used --> use filename 784 if (sequence.isDefaultName()) 785 sequence.setName(FileUtil.getFileName(filePath, false)); 786 787 // save whole sequence into a single file 788 savedSequence = save(writer, sequence, fixedFilePath, -1, -1, fps, saveFrame); 789 790 // we set filename on actual saved Sequence 791 savedSequence.setFilename(filePath); 792 // reset origin informations as now we are saved 793 sequence.resetOriginInformation(); 794 795 // add as one item to recent file list 796 if (mainMenu != null) 797 mainMenu.addRecentFile(fixedFilePath); 798 } 799 800 // Sequence persistence enabled --> save XML 801 if (GeneralPreferences.getSequencePersistence()) 802 savedSequence.saveXMLData(); 803 } 804 catch (Exception e) 805 { 806 IcyExceptionHandler.showErrorMessage(e, true); 807 if (showProgress && !Icy.getMainInterface().isHeadLess()) 808 new FailedAnnounceFrame("Failed to save image(s) (see output console for details)", 15); 809 return; 810 } 811 finally 812 { 813 if (saveFrame != null) 814 saveFrame.close(); 815 } 816 } 817 818 /** 819 * Save a single image from bytes buffer to the specified file. 820 */ 821 private static void saveImage(IFormatWriter formatWriter, byte[] data, int width, int height, int numChannel, 822 boolean separateChannel, DataType dataType, File file, boolean force) throws FormatException, IOException 823 { 824 final String filePath = FileUtil.cleanPath(FileUtil.getGenericPath(file.getAbsolutePath())); 825 826 if (FileUtil.exists(filePath)) 827 { 828 // forced ? first delete the file else LOCI won't save it 829 if (force) 830 FileUtil.delete(filePath, true); 831 else 832 throw new IOException("File already exists"); 833 } 834 // ensure parent directory exist 835 FileUtil.ensureParentDirExist(filePath); 836 837 final IFormatWriter writer; 838 final boolean separateCh; 839 840 if (formatWriter == null) 841 { 842 // get the writer 843 writer = getWriter(FileUtil.getFileExtension(filePath, false), ImageFileFormat.TIFF); 844 845 // prepare the metadata 846 try 847 { 848 separateCh = getSeparateChannelFlag(writer, numChannel, dataType); 849 writer.setMetadataRetrieve((MetadataRetrieve) MetaDataUtil.generateMetaData(width, height, numChannel, 850 dataType, separateCh)); 851 } 852 catch (ServiceException e) 853 { 854 System.err.println("Saver.saveImage(...) error :"); 855 IcyExceptionHandler.showErrorMessage(e, true); 856 } 857 } 858 else 859 { 860 // ready to use writer (metadata already prepared) 861 writer = formatWriter; 862 separateCh = separateChannel; 863 } 864 865 // we never interleaved data even if some image viewer need it to correctly read image (win XP viewer) 866 writer.setInterleaved(false); 867 writer.setId(filePath); 868 writer.setSeries(0); 869 // usually give better save performance 870 writer.setWriteSequentially(true); 871 872 try 873 { 874 // separated channel data 875 if (separateChannel) 876 { 877 final int pitch = width * height * dataType.getSize(); 878 final byte[] dataChannel = new byte[pitch]; 879 int offset = 0; 880 881 for (int c = 0; c < numChannel; c++) 882 { 883 System.arraycopy(data, offset, dataChannel, 0, pitch); 884 writer.saveBytes(c, dataChannel); 885 offset += pitch; 886 } 887 } 888 else 889 // save all data at once 890 writer.saveBytes(0, data); 891 } 892 catch (Exception e) 893 { 894 System.err.println("Saver.saveImage(...) error :"); 895 IcyExceptionHandler.showErrorMessage(e, true); 896 } 897 898 writer.close(); 899 } 900 901 /** 902 * Save a single image from bytes buffer to the specified file. 903 */ 904 public static void saveImage(byte[] data, int width, int height, int numChannel, DataType dataType, File file, 905 boolean force) throws FormatException, IOException 906 { 907 saveImage(null, data, width, height, numChannel, false, dataType, file, force); 908 } 909 910 /** 911 * @deprecated Use {@link #saveImage(byte[], int, int, int, DataType, File, boolean)} instead 912 */ 913 @Deprecated 914 public static void saveImage(byte[] data, int width, int height, int numChannel, int dataType, 915 boolean signedDataType, File file, boolean force) throws FormatException, IOException 916 { 917 saveImage(data, width, height, numChannel, DataType.getDataType(dataType, signedDataType), file, force); 918 } 919 920 /** 921 * Save a single image to the specified file 922 * 923 * @param image 924 * @throws IOException 925 * @throws FormatException 926 */ 927 public static void saveImage(IcyBufferedImage image, File file, boolean force) throws FormatException, IOException 928 { 929 final IFormatWriter writer = getWriter(file, ImageFileFormat.TIFF); 930 931 if (writer == null) 932 throw new UnknownFormatException("Can't find a valid image writer for the specified file: " + file); 933 934 final boolean separateChannel = getSeparateChannelFlag(writer, image.getIcyColorModel()); 935 936 try 937 { 938 writer.setMetadataRetrieve((MetadataRetrieve) MetaDataUtil.generateMetaData(image, separateChannel)); 939 } 940 catch (ServiceException e) 941 { 942 System.err.println("Saver.saveImage(...) error :"); 943 IcyExceptionHandler.showErrorMessage(e, true); 944 } 945 946 // get byte order 947 final boolean littleEndian = !writer.getMetadataRetrieve().getPixelsBinDataBigEndian(0, 0).booleanValue(); 948 // then save the image 949 saveImage(writer, image.getRawData(littleEndian), image.getSizeX(), image.getSizeY(), image.getSizeC(), 950 separateChannel, image.getDataType_(), file, force); 951 } 952 953 /** 954 * Save the specified sequence in the specified file using the given writer.<br> 955 * If posT or/and posZ are defined then only a sub part of the original Sequence is saved. 956 * 957 * @param writer 958 * writer used to save sequence (define the image format, cannot be <code>null</code> at this point) 959 * @param sequence 960 * sequence to save 961 * @param filePath 962 * file name where we want to save sequence 963 * @param posT 964 * frame index to save (-1 to save all frame from input sequence) 965 * @param posZ 966 * slice index to save (-1 to save all slice from input sequence) 967 * @param fps 968 * frame rate for AVI writer 969 * @param saveFrame 970 * progress frame for save operation (can be null) 971 * @return Actual saved Sequence (can be different from input one if conversion was needed) 972 * @throws ServiceException 973 * @throws IOException 974 * @throws FormatException 975 */ 976 private static Sequence save(IFormatWriter writer, Sequence sequence, String filePath, int posT, int posZ, int fps, 977 FileFrame saveFrame) throws ServiceException, FormatException, IOException 978 { 979 // TODO: temporary fix for the "incorrect close operation" bug in Bio-Formats 980 // with OME TIF writer, remove it when fixed. 981 // { 982 // try 983 // { 984 // writer = formatWriter.getClass().newInstance(); 985 // } 986 // catch (Exception e) 987 // { 988 // throw new ServiceException("Can't create new writer instance: " + e); 989 // } 990 // } 991 992 final File file = new File(filePath); 993 994 // first delete the file else LOCI won't save it correctly 995 if (file.exists()) 996 file.delete(); 997 // ensure parent directory exist 998 FileUtil.ensureParentDirExist(file); 999 1000 final ImageFileFormat saveFormat = getImageFileFormat(writer, ImageFileFormat.TIFF); 1001 final int sizeT = sequence.getSizeT(); 1002 final int sizeZ = sequence.getSizeZ(); 1003 final int adjZ, adjT; 1004 final int tMin, tMax; 1005 final int zMin, zMax; 1006 1007 // adjust posT and posZ depending the writer support 1008 switch (saveFormat) 1009 { 1010 default: 1011 case TIFF: 1012 // no restriction for TIFF 1013 adjZ = posZ; 1014 adjT = posT; 1015 break; 1016 1017 case AVI: 1018 // AVI: always save single slice 1019 adjZ = (posZ < 0) ? sizeZ / 2 : posZ; 1020 adjT = posT; 1021 break; 1022 1023 case JPG: 1024 case PNG: 1025 // JPG or PNG: always save single image 1026 adjZ = (posZ < 0) ? sizeZ / 2 : posZ; 1027 adjT = (posT < 0) ? sizeT / 2 : posT; 1028 break; 1029 } 1030 1031 // convert Sequence in good format for specified writer 1032 final Sequence compatibleSequence = getCompatibleSequenceForWriter(writer, sequence, adjT, adjZ); 1033 // get channel separation flag 1034 final boolean separateChannel = getSeparateChannelFlag(saveFormat, compatibleSequence.getColorModel()); 1035 // prepare metadata 1036 final OMEXMLMetadata metadata = MetaDataUtil.generateMetaData(compatibleSequence, separateChannel); 1037 1038 // clean unwanted planes 1039 MetaDataUtil.keepPlanes(metadata, 0, adjT, adjZ, -1); 1040 if (adjT < 0) 1041 { 1042 // all frame 1043 tMin = 0; 1044 tMax = sizeT - 1; 1045 } 1046 else 1047 { 1048 // single frame 1049 tMin = tMax = adjT; 1050 MetaDataUtil.setSizeT(metadata, 0, 1); 1051 } 1052 if (adjZ < 0) 1053 { 1054 // all slice 1055 zMin = 0; 1056 zMax = sizeZ - 1; 1057 } 1058 else 1059 { 1060 // single slice 1061 zMin = zMax = adjZ; 1062 MetaDataUtil.setSizeZ(metadata, 0, 1); 1063 } 1064 1065 // specific to TIFF writer 1066 if (writer instanceof TiffWriter) 1067 { 1068 // > 2GB --> use big tiff (important to do it before setId(..) call) 1069 if (MetaDataUtil.getDataSize(metadata, 0, 0) > 2000000000L) 1070 ((TiffWriter) writer).setBigTiff(true); 1071 } 1072 1073 // set settings 1074 writer.setFramesPerSecond(fps); 1075 // generate metadata 1076 writer.setMetadataRetrieve((MetadataRetrieve) metadata); 1077 // no interleave (XP default viewer want interleaved channel to correctly read image) 1078 writer.setInterleaved(false); 1079 // set id 1080 writer.setId(filePath); 1081 // init 1082 writer.setSeries(0); 1083 // usually give better save performance 1084 writer.setWriteSequentially(true); 1085 1086 final int sizeC = compatibleSequence.getSizeC(); 1087 // get endianess 1088 final boolean littleEndian = !writer.getMetadataRetrieve().getPixelsBinDataBigEndian(0, 0).booleanValue(); 1089 byte[] data = null; 1090 1091 try 1092 { 1093 int imageIndex = 0; 1094 // XYCZT order is important here (see metadata) 1095 for (int t = tMin; t <= tMax; t++) 1096 { 1097 for (int z = zMin; z <= zMax; z++) 1098 { 1099 // interrupt process (partial save) 1100 if ((saveFrame != null) && saveFrame.isCancelRequested()) 1101 return compatibleSequence; 1102 1103 final IcyBufferedImage image = compatibleSequence.getImage(t, z); 1104 1105 // separated channel data 1106 if (separateChannel) 1107 { 1108 for (int c = 0; c < sizeC; c++) 1109 { 1110 if (image != null) 1111 { 1112 // avoid multiple allocation 1113 data = image.getRawData(c, data, 0, littleEndian); 1114 writer.saveBytes(imageIndex, data); 1115 } 1116 1117 imageIndex++; 1118 } 1119 } 1120 else 1121 { 1122 if (image != null) 1123 { 1124 // avoid multiple allocation 1125 data = image.getRawData(data, 0, littleEndian); 1126 writer.saveBytes(imageIndex, data); 1127 } 1128 1129 imageIndex++; 1130 } 1131 1132 if (saveFrame != null) 1133 saveFrame.incPosition(); 1134 } 1135 } 1136 } 1137 finally 1138 { 1139 // always close writer after a file has been saved 1140 writer.close(); 1141 } 1142 1143 return compatibleSequence; 1144 } 1145 1146 /** 1147 * Returns a compatible Sequence representing the input sequence so it can be saved with the specified writer.<br> 1148 * If the writer support the input sequence then the input sequence is directly returned. 1149 * 1150 * @param writer 1151 * writer used to save sequence (define the image format, cannot be <code>null</code>) 1152 * @param sequence 1153 * sequence to save 1154 * @param posT 1155 * frame index to keep (-1 for all frame) 1156 * @param posZ 1157 * slice index to keep (-1 for all slice) 1158 * @return the compatible sequence for given Writer 1159 */ 1160 public static Sequence getCompatibleSequenceForWriter(IFormatWriter writer, Sequence sequence, int posT, int posZ) 1161 { 1162 final int sizeC = sequence.getSizeC(); 1163 final DataType dataType = sequence.getDataType_(); 1164 final boolean needConvert; 1165 final ImageFileFormat imageFormat = getImageFileFormat(writer, ImageFileFormat.TIFF); 1166 1167 // adjust posT and posZ depending the writer support 1168 switch (imageFormat) 1169 { 1170 default: 1171 // assume TIFF 1172 needConvert = false; 1173 break; 1174 1175 case AVI: 1176 case JPG: 1177 // JPG, AVI: only supports byte data type and Gray/RGB images 1178 needConvert = (dataType.getSize() > 1) || (sizeC == 2) || (sizeC > 3); 1179 break; 1180 1181 case PNG: 1182 // PNG: support byte data type with a maximum of 4 channels 1183 needConvert = (dataType.getSize() > 1) || (sizeC > 4); 1184 break; 1185 } 1186 1187 // no conversion needed 1188 if (!needConvert) 1189 return sequence; 1190 1191 final int sizeT = sequence.getSizeT(); 1192 final int sizeZ = sequence.getSizeZ(); 1193 final int tMin, tMax; 1194 final int zMin, zMax; 1195 1196 if (posT < 0) 1197 { 1198 // all frame 1199 tMin = 0; 1200 tMax = sizeT - 1; 1201 } 1202 else 1203 // single frame 1204 tMin = tMax = posT; 1205 if (posZ < 0) 1206 { 1207 // all slice 1208 zMin = 0; 1209 zMax = sizeZ - 1; 1210 } 1211 else 1212 // single slice 1213 zMin = zMax = posZ; 1214 1215 // wanted image type 1216 final int imageType = (sizeC > 1) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_BYTE_GRAY; 1217 // image receiver 1218 final BufferedImage imgOut = new BufferedImage(sequence.getSizeX(), sequence.getSizeY(), imageType); 1219 // conversion LUT (use default sequence one) 1220 final LUT lut = sequence.getDefaultLUT(); 1221 1222 // create compatible sequence 1223 final Sequence result = new Sequence(OMEUtil.createOMEXMLMetadata(sequence.getOMEXMLMetadata())); 1224 1225 result.beginUpdate(); 1226 try 1227 { 1228 for (int t = tMin; t <= tMax; t++) 1229 for (int z = zMin; z <= zMax; z++) 1230 result.setImage(t, z, IcyBufferedImageUtil.toBufferedImage(sequence.getImage(t, z), imgOut, lut)); 1231 1232 // preserve ROI and overlays (for XML metadata preservation) 1233 for (ROI roi : sequence.getROIs()) 1234 result.addROI(roi); 1235 for (Overlay overlay : sequence.getOverlays()) 1236 result.addOverlay(overlay); 1237 1238 // rename channels and set final name 1239 switch (imageType) 1240 { 1241 default: 1242 case BufferedImage.TYPE_INT_RGB: 1243 result.setChannelName(0, "red"); 1244 result.setChannelName(1, "green"); 1245 result.setChannelName(2, "blue"); 1246 break; 1247 1248 case BufferedImage.TYPE_BYTE_GRAY: 1249 result.setChannelName(0, "gray"); 1250 break; 1251 } 1252 result.setName(sequence.getName() + " (" + imageFormat + ")"); 1253 } 1254 finally 1255 { 1256 result.endUpdate(); 1257 } 1258 1259 return result; 1260 } 1261}