diff --git a/.classpath-template b/.classpath-template index a0f916528c8..3fc4761d056 100644 --- a/.classpath-template +++ b/.classpath-template @@ -6,8 +6,6 @@ - - diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml index 297e13e8087..e93b61600fc 100644 --- a/.github/workflows/ant.yml +++ b/.github/workflows/ant.yml @@ -11,7 +11,7 @@ jobs: os: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@v3 with: @@ -23,7 +23,7 @@ jobs: ant docs - name: Upload artifacts # upload just one set of artifacts for easier PR review - if: matrix.os == 'ubuntu-latest' && matrix.java == '1.8' + if: matrix.os == 'ubuntu-latest' && matrix.java == '8' uses: actions/upload-artifact@v3 with: path: artifacts/ diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 6987a2cb073..3440ecedb6d 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -19,7 +19,7 @@ jobs: env: maven_commands: install # default is install steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@v3 with: @@ -28,12 +28,45 @@ jobs: cache: 'maven' - name: Build run: mvn ${{ env.maven_commands }} - deploy: + deploy_snapshots: if: ${{ github.ref == 'refs/heads/develop' && github.repository_owner == 'ome' }} needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + - name: Retrieve version + id: get_version + run: | + VERSION=$( mvn help:evaluate -Dexpression=project.version -q -DforceStdout ) + echo "version=$VERSION" >> $GITHUB_OUTPUT + - name: Set server + id: set_server + run: | + if [[ ${{ steps.get_version.outputs.version }} =~ 'SNAPSHOT' ]]; then + echo server='ome.snapshots' >> $GITHUB_OUTPUT + else + echo server='ome.releases' >> $GITHUB_OUTPUT + fi + - name: Set up Repository + uses: actions/setup-java@v3 + with: + java-version: 8 + distribution: 'zulu' + server-id: ${{ steps.set_server.outputs.server }} + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + - name: Deploy SNAPSHOT + if: ${{ steps.set_server.outputs.server == 'ome.snapshots' }} + run: mvn deploy + env: + MAVEN_USERNAME: ${{ secrets.CI_DEPLOY_USER }} + MAVEN_PASSWORD: ${{ secrets.CI_DEPLOY_PASS }} + deploy_tags: + if: startsWith(github.ref, 'refs/tags') && github.repository_owner == 'ome' + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 - name: Retrieve version id: get_version run: | @@ -45,7 +78,7 @@ jobs: if [[ ${{ steps.get_version.outputs.version }} =~ 'SNAPSHOT' ]]; then echo server='ome.snapshots' >> $GITHUB_OUTPUT else - echo server='ome.staging' >> $GITHUB_OUTPUT + echo server='ome.releases' >> $GITHUB_OUTPUT fi - name: Set up Repository uses: actions/setup-java@v3 @@ -55,7 +88,8 @@ jobs: server-id: ${{ steps.set_server.outputs.server }} server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD - - name: Deploy SNAPSHOT + - name: Deploy Tags + if: ${{ steps.set_server.outputs.server == 'ome.releases' }} run: mvn deploy env: MAVEN_USERNAME: ${{ secrets.CI_DEPLOY_USER }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8886e06f13a..667c6c32a4e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ jobs: name: Release artifacts runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up JDK uses: actions/setup-java@v3 with: diff --git a/.mailmap b/.mailmap index 87096ec93b2..51af58ef1c9 100644 --- a/.mailmap +++ b/.mailmap @@ -1,12 +1,69 @@ +Aaron Avery +Abraham Sorber +Aleksandra Tarkowska +Alex Herbert aherbert +Alexandr Virodov +Andreas Knab +Andrew Patterson Andrew J Patterson Andrew Patterson +Balaji Ramalingam bramalingam +Brian Long berl +Brian Long +Brian Loranger Chris Allan Chris Allan +Christian Niedworok cniedwor +Christian Sachs +Christian Sachs +Claire McQuin mcquin +Colin Blackburn +Colin Blackburn Curtis Rueden -Jean-Marie Burel +Constanze Wendtland +Constanze Wendtland <88091453+XLEFReaderForBioformats@users.noreply.github.com> +David Gault dgault +David Pinto +Donald MacDonald +Eliana Andreica dinelia +Eliana Andreica ANDREICA Eliana +Helen Flynn +Ian Munro imunro +Ilya Parmon Parm0n +Jan Eglinger +Jean-Marie Burel jburel +Jean-Marie Burel jean-marie burel +Jean-Yves Tinevez +Jeremy Muhlich +Jim Crowe redcrow +Johan Herz <45658761+LambertJohan@users.noreply.github.com> Johannes Schindelin -Josh Moore -Kristin Briney +Josh Moore +Josh Moore +Kristian Kjaergaard kkjaergaard +Kristin Briney Kristin +Mark Carroll Mark Hiner +Mark Kittisopikul +Matthieu Moisse mmoisse Melissa Linkert -Premysl Fiala +Nicholas Chiaruttini +Nicola Papp nicola +Nils Gladitz +Paul van Schayck pvc-local +Peter Bankhead Pete +Premysl Fiala PremyslFiala +Premysl Fiala Premysl.Fiala +Richard Myers +Richard Myers Roger Leigh +Roger Leigh +Roger Leigh +Sébastien Besson +Shaquille Louisa <118732057+ShaquilleLouisa-LambertInstruments@users.noreply.github.com> +Simone Leo +Stefan Helfrich +Stefan Helfrich +Stephan Wagner-Conrad <46338941+swg08@users.noreply.github.com> +Tomas Farago +Wim Pomp +Zachary Connerty-Marin zacsimile diff --git a/build.xml b/build.xml index adff48eb6b1..024237e0dcf 100644 --- a/build.xml +++ b/build.xml @@ -16,8 +16,7 @@ Bio-Formats JAR file: bio-formats.jar Path: components/bio-formats Project deps: Formats Common, OME-XML Java library, JAI Image I/O Tools, - MDB Tools (Java port), Apache Jakarta POI, - Luratech LuraWave stubs + MDB Tools (Java port), Apache Jakarta POI Library deps: JGoodies Forms, Logback, NetCDF, Simple Logging Facade for Java API, TestNG Optional: Xalan Serializer, Xalan @@ -76,21 +75,6 @@ JAI Image I/O Tools This component will be removed once our changes have been added to the official JAI CVS repository. -=============================================================================== -The following components are stubs of third party projects: - -Luratech LuraWave stubs - Stub of proprietary Java API to handle Luratech LWF compression - -=- - JAR file: lwf-stubs.jar - Path: components/stubs/lwf-stubs - Project deps: (none) - Optional: (none) - License: BSD - Project URL: http://www.luratech.com/ - Notes: required to compile Bio-Formats's support for Luratech LWF - compression for the Opera Flex format - =============================================================================== The following external dependencies (in the jar folder) may be required: Ant-Contrib @@ -142,7 +126,7 @@ Logback License: EPL v1.0 and LGPL 2.1 Native library loader - JAR file: native-lib-loader-2.0.2.jar + JAR file: native-lib-loader-2.4.0.jar URL: http://github.com/scijava/native-lib-loader Notes: required for loading native libraries License: BSD diff --git a/components/bio-formats-plugins/pom.xml b/components/bio-formats-plugins/pom.xml index a7e7d412057..fdb7aab2935 100644 --- a/components/bio-formats-plugins/pom.xml +++ b/components/bio-formats-plugins/pom.xml @@ -8,7 +8,7 @@ ome pom-bio-formats - 6.12.1-SNAPSHOT + 7.1.0-SNAPSHOT ../.. diff --git a/components/bio-formats-plugins/src/loci/plugins/config/ConfigWindow.java b/components/bio-formats-plugins/src/loci/plugins/config/ConfigWindow.java index db184791796..6155c86f472 100644 --- a/components/bio-formats-plugins/src/loci/plugins/config/ConfigWindow.java +++ b/components/bio-formats-plugins/src/loci/plugins/config/ConfigWindow.java @@ -218,7 +218,6 @@ public ConfigWindow() { // + upgrade button for "ImageJ" just launches ImageJ upgrade plugin // - can install native libs by downloading installer from its web site // + QuickTime for Java - // + Nikon ND2 plugin // + ImageIO Tools libInfo.add(makeLabel("Path", false)); @@ -375,18 +374,6 @@ public void run() { // Ant replaces date token with datestamp of the build if (bfVersion.equals("@" + "date" + "@")) bfVersion = "Internal build"; - String qtVersion = null; - try { - Class qtToolsClass = Class.forName("loci.formats.gui.LegacyQTTools"); - Object qtTools = qtToolsClass.newInstance(); - Method getQTVersion = qtToolsClass.getMethod("getQTVersion"); - qtVersion = (String) getQTVersion.invoke(qtTools); - } - catch (Throwable t) { - log.println("Could not determine QuickTime version:"); - t.printStackTrace(log); - } - String clibIIOVersion = null; try { Class jpegSpi = Class.forName( @@ -428,7 +415,6 @@ public void run() { HashMap versions = new HashMap(); versions.put("javaVersion", javaVersion); versions.put("bfVersion", bfVersion); - if (qtVersion != null) versions.put("qtVersion", qtVersion); if (clibIIOVersion != null) versions.put("clibIIOVersion", clibIIOVersion); if (matlabVersion != null) versions.put("matlabVersion", matlabVersion); diff --git a/components/bio-formats-plugins/src/loci/plugins/config/FlexWidgets.java b/components/bio-formats-plugins/src/loci/plugins/config/FlexWidgets.java deleted file mode 100644 index a241f8b04e2..00000000000 --- a/components/bio-formats-plugins/src/loci/plugins/config/FlexWidgets.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * #%L - * Bio-Formats Plugins for ImageJ: a collection of ImageJ plugins including the - * Bio-Formats Importer, Bio-Formats Exporter, Bio-Formats Macro Extensions, - * Data Browser and Stack Slicer. - * %% - * Copyright (C) 2006 - 2017 Open Microscopy Environment: - * - Board of Regents of the University of Wisconsin-Madison - * - Glencoe Software, Inc. - * - University of Dundee - * %% - * This program 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 2 of the - * License, or (at your option) any later version. - * - * This program 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 this program. If not, see - * . - * #L% - */ - -package loci.plugins.config; - -import ij.Prefs; - -import java.awt.Component; - -import javax.swing.JTextField; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; - -import loci.common.services.DependencyException; -import loci.common.services.ServiceFactory; -import loci.formats.services.LuraWaveService; -import loci.formats.services.LuraWaveServiceImpl; - -/** - * Custom widgets for configuring Bio-Formats Flex support. - * - * @author Curtis Rueden ctrueden at wisc.edu - */ -public class FlexWidgets implements DocumentListener, IFormatWidgets { - - // -- Fields -- - - private String[] labels; - private Component[] widgets; - - private JTextField licenseBox; - - // -- Constructor -- - - public FlexWidgets() { - LuraWaveService service; - try { - ServiceFactory factory = new ServiceFactory(); - service = factory.getInstance(LuraWaveService.class); - } - catch (DependencyException e) { - throw new RuntimeException(e); - } - - // get license code from ImageJ preferences - String prefCode = Prefs.get(LuraWaveServiceImpl.LICENSE_PROPERTY, null); - String propCode = service.getLicenseCode(); - String code = ""; - if (prefCode != null) code = prefCode; - else if (propCode != null) code = null; // hidden code - - String licenseLabel = "LuraWave license code"; - licenseBox = ConfigWindow.makeTextField(); - licenseBox.setText(code == null ? "(Licensed)" : code); - licenseBox.setEditable(code != null); - licenseBox.getDocument().addDocumentListener(this); - - labels = new String[] {licenseLabel}; - widgets = new Component[] {licenseBox}; - } - - // -- DocumentListener API methods -- - - @Override - public void changedUpdate(DocumentEvent e) { - documentUpdate(); - } - @Override - public void removeUpdate(DocumentEvent e) { - documentUpdate(); - } - @Override - public void insertUpdate(DocumentEvent e) { - documentUpdate(); - } - - // -- IFormatWidgets API methods -- - - @Override - public String[] getLabels() { - return labels; - } - - @Override - public Component[] getWidgets() { - return widgets; - } - - // -- Helper methods -- - - private void documentUpdate() { - String code = licenseBox.getText(); - Prefs.set(LuraWaveServiceImpl.LICENSE_PROPERTY, code); - } - -} diff --git a/components/bio-formats-plugins/src/loci/plugins/config/InstallWizard.java b/components/bio-formats-plugins/src/loci/plugins/config/InstallWizard.java index 002341f372e..019aaa8b211 100644 --- a/components/bio-formats-plugins/src/loci/plugins/config/InstallWizard.java +++ b/components/bio-formats-plugins/src/loci/plugins/config/InstallWizard.java @@ -83,15 +83,6 @@ public InstallWizard() { // check for conflicting JARs -- i.e., duplicate classes - // Win32: download and install QuickTime - // Download http://www.apple.com/quicktime/download/ - // Find line with qtimewin, extract URL - // Download the URL - // Find line with QuickTimeInstaller.exe, extract URL - // Download the URL - // Execute program - // Wait for process completion before continuing - // Linux: download and install Image I/O Tools native codecs // Download http://download.java.net/media/jai-imageio/builds/release/1.1/jai_imageio-1_1-lib-linux-i586-jre.bin // Chmod 755 and execute? @@ -105,12 +96,6 @@ public InstallWizard() { // Find jre/bin folder by checking ImageJ.cfg file? // test with data/dicom/john/E724_S007_A0024.dcm - // Win32: download and install Nikon ND2 plugin - // (gray out option to use Nikon ND2 if plugin is not available) - // Download http://rsb.info.nih.gov/ij/plugins/download/jars/ImageJND2ReaderPlugin.zip - // Extract .msi file and execute it - // Wait for process completion before continuing - // Option to download Image5D // Download http://rsb.info.nih.gov/ij/plugins/download/jars/Image_5D.jar // Place in ImageJ plugins folder diff --git a/components/bio-formats-plugins/src/loci/plugins/config/ND2Widgets.java b/components/bio-formats-plugins/src/loci/plugins/config/ND2Widgets.java index 6ae1f4f4253..3fc5871922d 100644 --- a/components/bio-formats-plugins/src/loci/plugins/config/ND2Widgets.java +++ b/components/bio-formats-plugins/src/loci/plugins/config/ND2Widgets.java @@ -35,7 +35,7 @@ import javax.swing.JCheckBox; -import loci.formats.in.NativeND2Reader; +import loci.formats.in.ND2Reader; import loci.plugins.util.LociPrefs; /** @@ -53,23 +53,17 @@ public class ND2Widgets implements IFormatWidgets, ItemListener { // -- Constructor -- public ND2Widgets() { - boolean nikon = Prefs.get(LociPrefs.PREF_ND2_NIKON, false); - - String legacyLabel = "Nikon"; - JCheckBox legacyBox = new JCheckBox( - "Use Nikon's ND2 library instead of native ND2 support", nikon); - legacyBox.addItemListener(this); boolean chunkmap = Prefs.get(LociPrefs.PREF_ND2_CHUNKMAP, - NativeND2Reader.USE_CHUNKMAP_DEFAULT); + ND2Reader.USE_CHUNKMAP_DEFAULT); String chunkmapLabel = "Chunkmap"; JCheckBox chunkmapBox = new JCheckBox( "Use chunkmap table to read image offsets", chunkmap); chunkmapBox.addItemListener(this); - labels = new String[] {legacyLabel, chunkmapLabel}; - widgets = new Component[] {legacyBox, chunkmapBox}; + labels = new String[] {chunkmapLabel}; + widgets = new Component[] {chunkmapBox}; } // -- IFormatWidgets API methods -- @@ -89,10 +83,7 @@ public Component[] getWidgets() { @Override public void itemStateChanged(ItemEvent e) { JCheckBox box = (JCheckBox) e.getSource(); - if (box.equals(getWidgets()[0])) { - Prefs.set(LociPrefs.PREF_ND2_NIKON, box.isSelected()); - } - else if (box.equals(getWidgets()[1])) { + if (box.equals(getWidgets()[1])) { Prefs.set(LociPrefs.PREF_ND2_CHUNKMAP, box.isSelected()); } } diff --git a/components/bio-formats-plugins/src/loci/plugins/config/PictWidgets.java b/components/bio-formats-plugins/src/loci/plugins/config/PictWidgets.java deleted file mode 100644 index abf774583ce..00000000000 --- a/components/bio-formats-plugins/src/loci/plugins/config/PictWidgets.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * #%L - * Bio-Formats Plugins for ImageJ: a collection of ImageJ plugins including the - * Bio-Formats Importer, Bio-Formats Exporter, Bio-Formats Macro Extensions, - * Data Browser and Stack Slicer. - * %% - * Copyright (C) 2006 - 2017 Open Microscopy Environment: - * - Board of Regents of the University of Wisconsin-Madison - * - Glencoe Software, Inc. - * - University of Dundee - * %% - * This program 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 2 of the - * License, or (at your option) any later version. - * - * This program 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 this program. If not, see - * . - * #L% - */ - -package loci.plugins.config; - -import ij.Prefs; - -import java.awt.Component; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; - -import javax.swing.JCheckBox; - -import loci.plugins.util.LociPrefs; - -/** - * Custom widgets for configuring Bio-Formats PICT support. - * - * @author Curtis Rueden ctrueden at wisc.edu - */ -public class PictWidgets implements IFormatWidgets, ItemListener { - - // -- Fields -- - - private String[] labels; - private Component[] widgets; - - // -- Constructor -- - - public PictWidgets() { - boolean qtJava = Prefs.get(LociPrefs.PREF_PICT_QTJAVA, false); - - String legacyLabel = "Legacy"; - JCheckBox legacyBox = new JCheckBox( - "Use QTJava instead of native PICT support", qtJava); - legacyBox.addItemListener(this); - - labels = new String[] {legacyLabel}; - widgets = new Component[] {legacyBox}; - } - - // -- IFormatWidgets API methods -- - - @Override - public String[] getLabels() { - return labels; - } - - @Override - public Component[] getWidgets() { - return widgets; - } - - // -- ItemListener API methods -- - - @Override - public void itemStateChanged(ItemEvent e) { - JCheckBox box = (JCheckBox) e.getSource(); - Prefs.set(LociPrefs.PREF_PICT_QTJAVA, box.isSelected()); - } - -} diff --git a/components/bio-formats-plugins/src/loci/plugins/config/QTWidgets.java b/components/bio-formats-plugins/src/loci/plugins/config/QTWidgets.java deleted file mode 100644 index 2382f5d32ae..00000000000 --- a/components/bio-formats-plugins/src/loci/plugins/config/QTWidgets.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * #%L - * Bio-Formats Plugins for ImageJ: a collection of ImageJ plugins including the - * Bio-Formats Importer, Bio-Formats Exporter, Bio-Formats Macro Extensions, - * Data Browser and Stack Slicer. - * %% - * Copyright (C) 2006 - 2017 Open Microscopy Environment: - * - Board of Regents of the University of Wisconsin-Madison - * - Glencoe Software, Inc. - * - University of Dundee - * %% - * This program 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 2 of the - * License, or (at your option) any later version. - * - * This program 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 this program. If not, see - * . - * #L% - */ - -package loci.plugins.config; - -import ij.Prefs; - -import java.awt.Component; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; - -import javax.swing.JCheckBox; - -import loci.plugins.util.LociPrefs; - -/** - * Custom widgets for configuring Bio-Formats QuickTime support. - * - * @author Curtis Rueden ctrueden at wisc.edu - */ -public class QTWidgets implements IFormatWidgets, ItemListener { - - // -- Fields -- - - private String[] labels; - private Component[] widgets; - - // -- Constructor -- - - public QTWidgets() { - boolean qtJava = Prefs.get(LociPrefs.PREF_QT_QTJAVA, false); - - String legacyLabel = "Legacy"; - JCheckBox legacyBox = new JCheckBox( - "Use QTJava instead of native QT support", qtJava); - legacyBox.addItemListener(this); - - labels = new String[] {legacyLabel}; - widgets = new Component[] {legacyBox}; - } - - // -- IFormatWidgets API methods -- - - @Override - public String[] getLabels() { - return labels; - } - - @Override - public Component[] getWidgets() { - return widgets; - } - - // -- ItemListener API methods -- - - @Override - public void itemStateChanged(ItemEvent e) { - JCheckBox box = (JCheckBox) e.getSource(); - Prefs.set(LociPrefs.PREF_QT_QTJAVA, box.isSelected()); - } - -} diff --git a/components/bio-formats-plugins/src/loci/plugins/config/libraries.txt b/components/bio-formats-plugins/src/loci/plugins/config/libraries.txt index 59fc1e61c61..04cafed934b 100644 --- a/components/bio-formats-plugins/src/loci/plugins/config/libraries.txt +++ b/components/bio-formats-plugins/src/loci/plugins/config/libraries.txt @@ -44,32 +44,6 @@ notes = Not used; listed for informational purposes only. # native libraries -[QuickTime for Java] -type = Native library -class = quicktime.QTSession -version = qtVersion -url = http://www.apple.com/quicktime/ -license = Commercial -notes = Bio-Formats has two modes of operation for QuickTime movies:\n - 1) QTJava mode requires the QuickTime for Java library to be - installed.\n - 2) Native mode works on systems with no QuickTime (e.g., Linux).\n - \n - Using QTJava mode adds or improves support for the following - codecs:\n - 1) [iraw] Intel YUV Uncompressed: enables write\n - 2) [rle] Animation (run length encoded RGB): - improves read, enables write\n - 3) [rpza] Apple Video 16 bit "road pizza": improves read\n - 4) [cvid] Cinepak: enables read and write\n - 5) [svq1] Sorenson Video: enables read and write\n - 6) [svq3] Sorenson Video 3: enables read and write\n - 7) [mp4v] MPEG-4: enables read and write\n - 8) [h263] H.263: enables read and write\n - \n - You can toggle which mode is used - in the Formats tab's "QuickTime" entry. - [JAI Image I/O Tools - native codecs] type = Native library class = com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReaderSpi @@ -78,15 +52,6 @@ url = https://jai-imageio.dev.java.net/ license = BSD notes = Used by Bio-Formats for lossless JPEG support in DICOM. -[Nikon ND2 plugin] -type = Native library -class = ND_to_Image6D -url = http://rsb.info.nih.gov/ij/plugins/nd2-reader.html -license = Commercial -notes = Optional plugin. If you have Nikon's ND2 plugin installed, you can - configure Bio-Formats to use it instead of its native ND2 support - in the Formats tab's "Nikon ND2" entry. - # ImageJ plugins [Bio-Formats plugins] @@ -296,11 +261,3 @@ url = http://www.loci.wisc.edu/software/ome-notes license = GPL notes = OME Notes library for flexible organization and presentation of OME-XML metadata. - -[LuraWave decoder SDK] -type = Java library -class = com.luratech.lwf.lwfDecoder -url = http://www.luratech.com/ -license = Commercial -notes = Used by Bio-Formats to decode Flex files - compressed with the LuraWave JPEG2000 codec. diff --git a/components/bio-formats-plugins/src/loci/plugins/in/ImagePlusReader.java b/components/bio-formats-plugins/src/loci/plugins/in/ImagePlusReader.java index f86d53f154e..5a8fe13c834 100644 --- a/components/bio-formats-plugins/src/loci/plugins/in/ImagePlusReader.java +++ b/components/bio-formats-plugins/src/loci/plugins/in/ImagePlusReader.java @@ -39,6 +39,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Vector; +import java.util.regex.Matcher; import loci.common.DateTools; import loci.common.Location; @@ -59,7 +60,6 @@ import loci.plugins.util.BFVirtualStack; import loci.plugins.util.ImageProcessorReader; import loci.plugins.util.LociPrefs; -import loci.plugins.util.LuraWave; import loci.plugins.util.VirtualImagePlus; import ome.units.UNITS; import ome.units.quantity.Time; @@ -384,7 +384,13 @@ private ImageStack readPlanes(ImportProcess process, int s, List luts, updateTiming(s, current, current++, total); // get image processor for ith plane - final ImageProcessor[] p = readProcessors(process, i, region, thumbnail); + ImageProcessor[] p; + if (thumbnail) { + p = reader.openThumbProcessors(i); + } else { + p = reader.openProcessors(i, region.x, region.y, region.width, region.height); + } + if (p == null || p.length == 0) { throw new FormatException("Cannot read plane #" + i); } @@ -400,40 +406,6 @@ private ImageStack readPlanes(ImportProcess process, int s, List luts, return createStack(procs, labels, luts); } - /** - * HACK: This method mainly exists to prompt the user for a missing - * LuraWave license code, in the case of LWF-compressed Flex. - * - * @see ImportProcess#setId() - */ - private ImageProcessor[] readProcessors(ImportProcess process, - int no, Region r, boolean thumbnail) throws FormatException, IOException - { - final ImageProcessorReader reader = process.getReader(); - final ImporterOptions options = process.getOptions(); - - boolean first = true; - for (int i=0; i concatenate(List imps) { @@ -598,7 +570,7 @@ private String constructSliceLabel(int ndx, IFormatReader r, filename = sliceLabelPattern; filename = filename.replaceAll(FormatTools.SERIES_NUM, String.format("%d", series)); - filename = filename.replaceAll(FormatTools.SERIES_NAME, imageName); + filename = filename.replaceAll(FormatTools.SERIES_NAME, Matcher.quoteReplacement(imageName)); if (sizeC > 1) { int[] subC; String[] subCTypes; diff --git a/components/bio-formats-plugins/src/loci/plugins/in/ImportProcess.java b/components/bio-formats-plugins/src/loci/plugins/in/ImportProcess.java index ad05b258552..e0c4bdafed3 100644 --- a/components/bio-formats-plugins/src/loci/plugins/in/ImportProcess.java +++ b/components/bio-formats-plugins/src/loci/plugins/in/ImportProcess.java @@ -62,7 +62,6 @@ import loci.plugins.BF; import loci.plugins.util.ImageProcessorReader; import loci.plugins.util.LociPrefs; -import loci.plugins.util.LuraWave; import loci.plugins.util.VirtualReader; import loci.plugins.util.WindowTools; import ome.xml.model.enums.DimensionOrder; @@ -544,7 +543,7 @@ private void initializeStack() throws FormatException, IOException { baseReader.getMetadataOptions().setMetadataLevel( MetadataLevel.NO_OVERLAYS); } - setId(); + reader.setId(options.getId()); computeSeriesLabels(reader); } @@ -687,34 +686,6 @@ private void saveDefaults() { // -- Helper methods -- ImportStep.STACK -- - /** - * HACK: This method mainly exists to prompt the user for a missing - * LuraWave license code, in the case of LWF-compressed Flex. - * - * @see ImagePlusReader#readProcessors(ImportProcess, int, Region, boolean) - */ - private void setId() throws FormatException, IOException { - boolean first = true; - for (int i=0; i 1) { store.setPixelsSizeC(new PositiveInteger(channels*imp.getNChannels()), 0); store.setPixelsSizeT(new PositiveInteger(imp.getNFrames()), 0); + try { + // if a subset of the data was opened, the number of Channels + // in the OME-XML may not match the ImagePlus channel count + // channel count mismatches can cause problems when actually writing pixels + service.removeChannels(service.getOMEMetadata(store), 0, imp.getNChannels()); + } + catch (ServiceException e) { + } + if (store.getImageID(0) == null) { store.setImageID(MetadataTools.createLSID("Image", 0), 0); } diff --git a/components/bio-formats-plugins/src/loci/plugins/util/LociPrefs.java b/components/bio-formats-plugins/src/loci/plugins/util/LociPrefs.java index 7ad80e5611a..c065734fc67 100644 --- a/components/bio-formats-plugins/src/loci/plugins/util/LociPrefs.java +++ b/components/bio-formats-plugins/src/loci/plugins/util/LociPrefs.java @@ -37,10 +37,7 @@ import loci.formats.in.DynamicMetadataOptions; import loci.formats.in.LIFReader; import loci.formats.in.MetadataOptions; -import loci.formats.in.NativeND2Reader; import loci.formats.in.ND2Reader; -import loci.formats.in.PictReader; -import loci.formats.in.QTReader; import loci.formats.in.SDTReader; import loci.formats.in.TiffDelegateReader; import loci.formats.in.ZeissCZIReader; @@ -56,9 +53,6 @@ public final class LociPrefs { public static final String PREF_READER_ENABLED = "bioformats.enabled"; public static final String PREF_READER_WINDOWLESS = "bioformats.windowless"; - public static final String PREF_ND2_NIKON = "bioformats.nd2.nikon"; - public static final String PREF_PICT_QTJAVA = "bioformats.pict.qtjava"; - public static final String PREF_QT_QTJAVA = "bioformats.qt.qtjava"; public static final String PREF_SDT_INTENSITY = "bioformats.sdt.intensity"; public static final String PREF_TIFF_IMAGEIO = "bioformats.tiff.imageio"; public static final String PREF_CZI_AUTOSTITCH = @@ -66,7 +60,7 @@ public final class LociPrefs { public static final String PREF_CZI_ATTACHMENT = "bioformats.zeissczi.include.attachments"; public static final String PREF_ND2_CHUNKMAP = - "bioformats.nativend2.chunkmap"; + "bioformats.nd2.chunkmap"; public static final String PREF_LEICA_LIF_PHYSICAL_SIZE = "bioformats.leicalif.physicalsize.compatibility"; public static final String PREF_SLICE_LABEL_PATTERN = "bioformats.sliceLabelPattern"; @@ -106,7 +100,7 @@ public static ImageReader makeImageReader() { ((DynamicMetadataOptions) options).setBoolean( ZeissCZIReader.INCLUDE_ATTACHMENTS_KEY, includeCZIAttachments()); ((DynamicMetadataOptions) options).setBoolean( - NativeND2Reader.USE_CHUNKMAP_KEY, useND2Chunkmap()); + ND2Reader.USE_CHUNKMAP_KEY, useND2Chunkmap()); ((DynamicMetadataOptions) options).setBoolean( LIFReader.OLD_PHYSICAL_SIZE_KEY, isLeicaLIFPhysicalSizeBackwardsCompatible()); ((DynamicMetadataOptions) options).setBoolean( @@ -115,26 +109,11 @@ public static ImageReader makeImageReader() { } // toggle reader-specific options - boolean nd2Nikon = LociPrefs.isND2Nikon(); - boolean pictQTJava = LociPrefs.isPictQTJava(); - boolean qtQTJava = LociPrefs.isQTQTJava(); boolean sdtIntensity = LociPrefs.isSDTIntensity(); boolean tiffImageIO = LociPrefs.isTiffImageIO(); IFormatReader[] r = reader.getReaders(); for (int i=0; i c) { return getPref(PREF_READER_ENABLED, c, true); } - public static boolean isND2Nikon() { - return Prefs.get(PREF_ND2_NIKON, false); - } - - public static boolean isPictQTJava() { - return Prefs.get(PREF_PICT_QTJAVA, false); - } - - public static boolean isQTQTJava() { - return Prefs.get(PREF_QT_QTJAVA, false); - } - public static boolean isSDTIntensity() { return Prefs.get(PREF_SDT_INTENSITY, false); } @@ -190,7 +157,7 @@ public static boolean includeCZIAttachments() { } public static boolean useND2Chunkmap() { - return Prefs.get(PREF_ND2_CHUNKMAP, NativeND2Reader.USE_CHUNKMAP_DEFAULT); + return Prefs.get(PREF_ND2_CHUNKMAP, ND2Reader.USE_CHUNKMAP_DEFAULT); } public static boolean isLeicaLIFPhysicalSizeBackwardsCompatible() { diff --git a/components/bio-formats-plugins/src/loci/plugins/util/LuraWave.java b/components/bio-formats-plugins/src/loci/plugins/util/LuraWave.java deleted file mode 100644 index 160bd872b32..00000000000 --- a/components/bio-formats-plugins/src/loci/plugins/util/LuraWave.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * #%L - * Bio-Formats Plugins for ImageJ: a collection of ImageJ plugins including the - * Bio-Formats Importer, Bio-Formats Exporter, Bio-Formats Macro Extensions, - * Data Browser and Stack Slicer. - * %% - * Copyright (C) 2006 - 2017 Open Microscopy Environment: - * - Board of Regents of the University of Wisconsin-Madison - * - Glencoe Software, Inc. - * - University of Dundee - * %% - * This program 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 2 of the - * License, or (at your option) any later version. - * - * This program 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 this program. If not, see - * . - * #L% - */ - -package loci.plugins.util; - -import ij.Prefs; -import ij.gui.GenericDialog; -import loci.formats.FormatException; -import loci.formats.services.LuraWaveServiceImpl; - -/** - * Utility methods for dealing with proprietary LuraWave licensing. - */ -public final class LuraWave { - - // -- Constants -- - - public static final int MAX_TRIES = 5; - public static final String TOO_MANY_TRIES = - "Too many LuraWave license code attempts; giving up."; - - // -- Constructor -- - - private LuraWave() { } - - // -- Utility methods -- - - /** Reads LuraWave license code from ImageJ preferences, if available. */ - public static String initLicenseCode() { - String code = Prefs.get(LuraWaveServiceImpl.LICENSE_PROPERTY, null); - if (code != null && code.length() >= 6) { - System.setProperty(LuraWaveServiceImpl.LICENSE_PROPERTY, code); - } - return code; - } - - /** - * Returns true if the given exception was cause - * by a missing or invalid LuraWave license code. - */ - public static boolean isLicenseCodeException(FormatException exc) { - String msg = exc == null ? null : exc.getMessage(); - return msg != null && (msg.equals(LuraWaveServiceImpl.NO_LICENSE_MSG) || - msg.startsWith(LuraWaveServiceImpl.INVALID_LICENSE_MSG)); - } - - /** - * Prompts the user to enter their LuraWave - * license code in an ImageJ dialog window. - */ - public static String promptLicenseCode(String code, boolean first) { - GenericDialog gd = new GenericDialog("LuraWave License Code"); - if (!first) gd.addMessage("Invalid license code; try again."); - gd.addStringField("LuraWave_License Code: ", code, 16); - gd.showDialog(); - if (gd.wasCanceled()) return null; - code = gd.getNextString(); - if (code != null) Prefs.set(LuraWaveServiceImpl.LICENSE_PROPERTY, code); - return code; - } - -} diff --git a/components/bio-formats-plugins/src/loci/plugins/util/RecordedImageProcessor.java b/components/bio-formats-plugins/src/loci/plugins/util/RecordedImageProcessor.java index 0163a947a9d..feefa9a5119 100644 --- a/components/bio-formats-plugins/src/loci/plugins/util/RecordedImageProcessor.java +++ b/components/bio-formats-plugins/src/loci/plugins/util/RecordedImageProcessor.java @@ -489,6 +489,12 @@ public double getBackgroundValue() { return proc.getBackgroundValue(); } + @Override + public double getForegroundValue() { + record("getForegroundValue"); + return proc.getForegroundValue(); + } + @Override public int getAutoThreshold(int[] histogram) { record("getAutoThreshold", histogram, int[].class); diff --git a/components/bio-formats-tools/pom.xml b/components/bio-formats-tools/pom.xml index aa7df1942bc..9fe6f016778 100644 --- a/components/bio-formats-tools/pom.xml +++ b/components/bio-formats-tools/pom.xml @@ -8,7 +8,7 @@ ome pom-bio-formats - 6.12.1-SNAPSHOT + 7.1.0-SNAPSHOT ../.. diff --git a/components/bio-formats-tools/src/loci/formats/tools/GenerateCache.java b/components/bio-formats-tools/src/loci/formats/tools/GenerateCache.java index dd34d0c598c..5a297e71513 100644 --- a/components/bio-formats-tools/src/loci/formats/tools/GenerateCache.java +++ b/components/bio-formats-tools/src/loci/formats/tools/GenerateCache.java @@ -9,13 +9,13 @@ * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE diff --git a/components/bio-formats-tools/src/loci/formats/tools/ImageConverter.java b/components/bio-formats-tools/src/loci/formats/tools/ImageConverter.java index 4f886f52ed4..f8259fe90eb 100644 --- a/components/bio-formats-tools/src/loci/formats/tools/ImageConverter.java +++ b/components/bio-formats-tools/src/loci/formats/tools/ImageConverter.java @@ -128,6 +128,7 @@ public final class ImageConverter { private boolean originalMetadata = true; private boolean noSequential = false; private String swapOrder = null; + private Byte fillColor = null; private IFormatReader reader; private MinMaxCalculator minMax; @@ -259,6 +260,10 @@ else if (args[i].equals("-no-sequential")) { else if (args[i].equals("-swap")) { swapOrder = args[++i].toUpperCase(); } + else if (args[i].equals("-fill")) { + // allow specifying 0-255 + fillColor = (byte) Integer.parseInt(args[++i]); + } else if (!args[i].equals(CommandLineTools.NO_UPGRADE_CHECK)) { LOGGER.error("Found unknown command flag: {}; exiting.", args[i]); return false; @@ -334,7 +339,7 @@ private void printUsage() { " [-nolookup] [-autoscale] [-version] [-no-upgrade] [-padded]", " [-option key value] [-novalid] [-validate] [-tilex tileSizeX]", " [-tiley tileSizeY] [-pyramid-scale scale]", - " [-swap dimensionsOrderString]", + " [-swap dimensionsOrderString] [-fill color]", " [-pyramid-resolutions numResolutionLevels] in_file out_file", "", " -version: print the library version and exit", @@ -378,6 +383,7 @@ private void printUsage() { "-pyramid-resolutions: generates a pyramid image with the given number of resolution levels ", " -no-sequential: do not assume that planes are written in sequential order", " -swap: override the default input dimension order; argument is f.i. XYCTZ", + " -fill: byte value to use for undefined pixels (0-255)", "", "The extension of the output file specifies the file format to use", "for the conversion. The list of available formats and extensions is:", @@ -500,6 +506,7 @@ public boolean testConvert(IFormatWriter writer, String[] args) reader.setMetadataFiltered(true); reader.setOriginalMetadataPopulated(originalMetadata); reader.setFlattenedResolutions(flat); + reader.setFillColor(fillColor); OMEXMLService service = null; try { ServiceFactory factory = new ServiceFactory(); @@ -592,9 +599,16 @@ public boolean testConvert(IFormatWriter writer, String[] args) writer.setMetadataRetrieve((MetadataRetrieve) meta); } else { - meta.setPixelsSizeX(new PositiveInteger(width), 0); - meta.setPixelsSizeY(new PositiveInteger(height), 0); for (int i=0; i ome pom-bio-formats - 6.12.1-SNAPSHOT + 7.1.0-SNAPSHOT ../../../ @@ -40,13 +40,6 @@ ${project.version} - - org.openmicroscopy - lwf-stubs - ${lwf-stubs.version} - provided - - org.openmicroscopy mipav-stubs diff --git a/components/forks/turbojpeg/pom.xml b/components/forks/turbojpeg/pom.xml index 0bedd52151a..df27e1968ff 100644 --- a/components/forks/turbojpeg/pom.xml +++ b/components/forks/turbojpeg/pom.xml @@ -8,7 +8,7 @@ ome pom-bio-formats - 6.12.1-SNAPSHOT + 7.1.0-SNAPSHOT ../../../ diff --git a/components/formats-api/pom.xml b/components/formats-api/pom.xml index 2d8ab8354b9..a5b34b42d13 100644 --- a/components/formats-api/pom.xml +++ b/components/formats-api/pom.xml @@ -8,7 +8,7 @@ ome pom-bio-formats - 6.12.1-SNAPSHOT + 7.1.0-SNAPSHOT ../.. diff --git a/components/formats-api/src/loci/formats/CoreMetadataList.java b/components/formats-api/src/loci/formats/CoreMetadataList.java index 82928f5eaf4..8d2ffdf65ba 100644 --- a/components/formats-api/src/loci/formats/CoreMetadataList.java +++ b/components/formats-api/src/loci/formats/CoreMetadataList.java @@ -9,13 +9,13 @@ * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE diff --git a/components/formats-api/src/loci/formats/FormatReader.java b/components/formats-api/src/loci/formats/FormatReader.java index 9734b20152d..ddc632132ec 100644 --- a/components/formats-api/src/loci/formats/FormatReader.java +++ b/components/formats-api/src/loci/formats/FormatReader.java @@ -180,6 +180,9 @@ public abstract class FormatReader extends FormatHandler /** Whether or not to group multi-file formats. */ protected boolean group = true; + /** Fill value for undefined pixels. */ + protected Byte fillColor = null; + /** List of domains in which this format is used. */ protected String[] domains = new String[0]; @@ -998,6 +1001,26 @@ public int fileGroupOption(String id) return FormatTools.CANNOT_GROUP; } + /* @see IFormatReader#setFillColor(Byte) */ + @Override + public void setFillColor(Byte color) { + fillColor = color; + } + + /** + * @see IFormatReader#getFillColor() + * + * If a fill color was not defined by the reader or + * {@link #setFillColor(Byte)}, then 0 is returned. + */ + @Override + public Byte getFillColor() { + if (fillColor == null) { + return 0; + } + return fillColor; + } + /* @see IFormatReader#isMetadataComplete() */ @Override public boolean isMetadataComplete() { diff --git a/components/formats-api/src/loci/formats/IFormatReader.java b/components/formats-api/src/loci/formats/IFormatReader.java index cccd4486e5b..fd302b326cc 100644 --- a/components/formats-api/src/loci/formats/IFormatReader.java +++ b/components/formats-api/src/loci/formats/IFormatReader.java @@ -354,6 +354,29 @@ Object openPlane(int no, int x, int y, int w, int h) */ boolean isGroupFiles(); + /** + * Set the fill value for undefined pixels. + * This can be used to override the default fill value + * defined in a reader. + * + * The default implementation in IFormatReader is a no-op. + * + * @param color value that will be used to fill pixel byte arrays + */ + default void setFillColor(Byte color) { + } + + /** + * Return the fill value for undefined pixels. + * + * The default implementation in IFormatReader always returns 0. + * + * @see #setFillColor(Byte) + */ + default Byte getFillColor() { + return 0; + } + /** Returns true if this format's metadata is completely parsed. */ boolean isMetadataComplete(); diff --git a/components/formats-api/src/loci/formats/ImageReader.java b/components/formats-api/src/loci/formats/ImageReader.java index 315700daf48..6e5a7d0b374 100644 --- a/components/formats-api/src/loci/formats/ImageReader.java +++ b/components/formats-api/src/loci/formats/ImageReader.java @@ -614,6 +614,20 @@ public int fileGroupOption(String id) throws FormatException, IOException { return getReader(id).fileGroupOption(id); } + /* @see IFormatReader#setFillColor(Byte) */ + @Override + public void setFillColor(Byte fill) { + for (IFormatReader r : readers) { + r.setFillColor(fill); + } + } + + /* @see IFormatReader#getFillColor() */ + @Override + public Byte getFillColor() { + return getReader().getFillColor(); + } + /* @see IFormatReader#isMetadataComplete() */ @Override public boolean isMetadataComplete() { diff --git a/components/formats-api/src/loci/formats/MetadataList.java b/components/formats-api/src/loci/formats/MetadataList.java index b1a09f9ac35..078d7322bee 100644 --- a/components/formats-api/src/loci/formats/MetadataList.java +++ b/components/formats-api/src/loci/formats/MetadataList.java @@ -1,6 +1,6 @@ /* * #%L - * primary reader and writer APIs + * Top-level reader and writer APIs * %% * Copyright (C) 2018 Open Microscopy Environment: * - Board of Regents of the University of Wisconsin-Madison @@ -9,13 +9,13 @@ * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE diff --git a/components/formats-api/src/loci/formats/ReaderWrapper.java b/components/formats-api/src/loci/formats/ReaderWrapper.java index 1ec164832e8..e827e377595 100644 --- a/components/formats-api/src/loci/formats/ReaderWrapper.java +++ b/components/formats-api/src/loci/formats/ReaderWrapper.java @@ -395,6 +395,16 @@ public int fileGroupOption(String id) throws FormatException, IOException { return reader.fileGroupOption(id); } + @Override + public void setFillColor(Byte color) { + reader.setFillColor(color); + } + + @Override + public Byte getFillColor() { + return reader.getFillColor(); + } + @Override public boolean isMetadataComplete() { return reader.isMetadataComplete(); diff --git a/components/formats-api/src/loci/formats/readers.txt b/components/formats-api/src/loci/formats/readers.txt index e6c2c68829e..c98b7b21164 100644 --- a/components/formats-api/src/loci/formats/readers.txt +++ b/components/formats-api/src/loci/formats/readers.txt @@ -18,6 +18,7 @@ loci.formats.in.SlideBook6Reader[type=external] # sld loci.formats.in.SlideBook7Reader[type=external] # sldy loci.formats.in.ScreenReader[type=external] # .screen loci.formats.in.ZarrReader[type=external] # .zarr +ch.epfl.biop.formats.in.ZeissQuickStartCZIReader[type=external] # .czi alternative reader # standalone readers with unique file extensions loci.formats.in.PGMReader # pgm @@ -140,7 +141,6 @@ loci.formats.in.ND2Reader # nd2, jp2 [JAI-ImageIO] loci.formats.in.PCIReader # cxd [POI] loci.formats.in.ImarisHDFReader # ims [NetCDF] loci.formats.in.CellH5Reader # ch5 [JHDF] -loci.formats.in.WlzReader # wlz [JWlz] loci.formats.in.VeecoReader # hdf [NetCDF] loci.formats.in.TecanReader # db [SQLite JDBC] @@ -149,7 +149,7 @@ loci.formats.in.ZeissLSMReader # lsm, mdb [MDB Tools] loci.formats.in.SEQReader # seq loci.formats.in.GelReader # gel loci.formats.in.ImarisTiffReader # ims -loci.formats.in.FlexReader # flex [LuraWave] +loci.formats.in.FlexReader # flex loci.formats.in.SVSReader # svs loci.formats.in.ImaconReader # fff loci.formats.in.LEOReader # sxm diff --git a/components/formats-api/src/loci/formats/writers.txt b/components/formats-api/src/loci/formats/writers.txt index 3ed4335f920..728feb570b9 100644 --- a/components/formats-api/src/loci/formats/writers.txt +++ b/components/formats-api/src/loci/formats/writers.txt @@ -18,5 +18,4 @@ loci.formats.out.V3DrawWriter # v3draw loci.formats.out.DicomWriter # dcm # writers requiring third-party libraries -loci.formats.out.WlzWriter # wlz loci.formats.out.CellH5Writer # ch5 diff --git a/components/formats-api/test/loci/formats/utests/CoreMetadataListTest.java b/components/formats-api/test/loci/formats/utests/CoreMetadataListTest.java index 0cd22e6a887..beb18cc1f53 100644 --- a/components/formats-api/test/loci/formats/utests/CoreMetadataListTest.java +++ b/components/formats-api/test/loci/formats/utests/CoreMetadataListTest.java @@ -9,13 +9,13 @@ * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE diff --git a/components/formats-api/test/loci/formats/utests/MetadataListTest.java b/components/formats-api/test/loci/formats/utests/MetadataListTest.java index 548a5a15293..e325066acf4 100644 --- a/components/formats-api/test/loci/formats/utests/MetadataListTest.java +++ b/components/formats-api/test/loci/formats/utests/MetadataListTest.java @@ -9,13 +9,13 @@ * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE diff --git a/components/formats-bsd/build.xml b/components/formats-bsd/build.xml index a954b69f87d..020bddbe631 100644 --- a/components/formats-bsd/build.xml +++ b/components/formats-bsd/build.xml @@ -33,7 +33,6 @@ Type "ant -p" for a list of targets. - diff --git a/components/formats-bsd/matlab/bfGetReader.m b/components/formats-bsd/matlab/bfGetReader.m index b2a408239db..472533a58ca 100644 --- a/components/formats-bsd/matlab/bfGetReader.m +++ b/components/formats-bsd/matlab/bfGetReader.m @@ -70,14 +70,6 @@ id = f.Name; end -% set LuraWave license code, if available -if exist('lurawaveLicense', 'var') - path = fullfile(fileparts(mfilename('fullpath')), 'lwf_jsdk2.6.jar'); - javaaddpath(path); - javaMethod('setProperty', 'java.lang.System', ... - 'lurawave.license', lurawaveLicense); -end - % Create a loci.formats.ReaderWrapper object r = javaObject('loci.formats.ChannelSeparator', ... javaObject('loci.formats.ChannelFiller')); diff --git a/components/formats-bsd/matlab/bfopen.m b/components/formats-bsd/matlab/bfopen.m index 3b311f3813c..d6e9e1ff89e 100644 --- a/components/formats-bsd/matlab/bfopen.m +++ b/components/formats-bsd/matlab/bfopen.m @@ -98,9 +98,6 @@ % named files into a single dataset based on file numbering. stitchFiles = 0; -% To work with compressed Evotec Flex, fill in your LuraWave license code. -%lurawaveLicense = 'xxxxxx-xxxxxxx'; - % -- Main function - no need to edit anything past this point -- % load the Bio-Formats library into the MATLAB environment diff --git a/components/formats-bsd/pom.xml b/components/formats-bsd/pom.xml index d5d355ca380..28842a8ae24 100644 --- a/components/formats-bsd/pom.xml +++ b/components/formats-bsd/pom.xml @@ -8,7 +8,7 @@ ome pom-bio-formats - 6.12.1-SNAPSHOT + 7.1.0-SNAPSHOT ../.. @@ -61,7 +61,7 @@ org.scijava native-lib-loader - 2.1.4 + 2.4.0 com.jgoodies @@ -88,14 +88,6 @@ slf4j-api ${slf4j.version} - - - org.openmicroscopy - lwf-stubs - ${lwf-stubs.version} - provided - - org.openmicroscopy mipav-stubs @@ -165,7 +157,7 @@ org.yaml snakeyaml - 1.32 + 2.0 diff --git a/components/formats-bsd/src/loci/formats/FileStitcher.java b/components/formats-bsd/src/loci/formats/FileStitcher.java index 38137c7a0eb..bfd488bc509 100644 --- a/components/formats-bsd/src/loci/formats/FileStitcher.java +++ b/components/formats-bsd/src/loci/formats/FileStitcher.java @@ -144,6 +144,11 @@ public FileStitcher(IFormatReader r, boolean patternIds) { */ public void setReaderClassList(ClassList classList) { this.classList = classList; + try { + reader.close(true); + } catch (IOException e) { + LOGGER.debug("Close failed", e); + } reader = DimensionSwapper.makeDimensionSwapper(new ImageReader(classList)); } @@ -205,7 +210,7 @@ public int getAdjustedIndex(int no) throws FormatException, IOException { */ public int[] getAxisTypes() { FormatTools.assertId(getCurrentFile(), true, 2); - return externals[getExternalSeries()].getAxisGuesser().getAxisTypes(); + return getAxisGuesser().getAxisTypes(); } /** @@ -220,7 +225,7 @@ public int[] getAxisTypes() { */ public void setAxisTypes(int[] axes) throws FormatException { FormatTools.assertId(getCurrentFile(), true, 2); - externals[getExternalSeries()].getAxisGuesser().setAxisTypes(axes); + getAxisGuesser().setAxisTypes(axes); computeAxisLengths(); } @@ -237,6 +242,11 @@ public FilePattern getFilePattern() { */ public AxisGuesser getAxisGuesser() { FormatTools.assertId(getCurrentFile(), true, 2); + if (externals == null) { + return new AxisGuesser(getFilePattern(), reader.getDimensionOrder(), + reader.getSizeZ(), reader.getSizeT(), reader.getEffectiveSizeC(), + reader.isOrderCertain()); + } return externals[getExternalSeries()].getAxisGuesser(); } @@ -488,7 +498,7 @@ public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) // return a blank image to cover for the fact that // this file does not contain enough image planes - Arrays.fill(buf, (byte) 0); + Arrays.fill(buf, getFillColor()); return buf; } @@ -902,10 +912,12 @@ public IFormatReader[] getUnderlyingReaders() { @Override public void reopenFile() throws IOException { reader.reopenFile(); - for (ExternalSeries s : externals) { - for (DimensionSwapper r : s.getReaders()) { - if (r.getCurrentFile() != null) { - r.reopenFile(); + if (externals != null) { + for (ExternalSeries s : externals) { + for (DimensionSwapper r : s.getReaders()) { + if (r.getCurrentFile() != null) { + r.reopenFile(); + } } } } @@ -959,6 +971,7 @@ else if (canChangePattern() || !Location.getMappedId(id).equals(id)) { reader.setId(fp.getFiles()[0]); } else reader.setId(id); + LOGGER.info("File pattern ignored; {} groups files", reader.getFormat()); return; } diff --git a/components/formats-bsd/src/loci/formats/Memoizer.java b/components/formats-bsd/src/loci/formats/Memoizer.java index c085622f45d..edebb099a37 100644 --- a/components/formats-bsd/src/loci/formats/Memoizer.java +++ b/components/formats-bsd/src/loci/formats/Memoizer.java @@ -60,6 +60,7 @@ import com.esotericsoftware.kryo.KryoException; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy; import org.objenesis.strategy.StdInstantiatorStrategy; @@ -121,7 +122,12 @@ public static class KryoDeser implements Deser { final public Kryo kryo = new Kryo(); { // See https://github.com/EsotericSoftware/kryo/issues/216 - ((Kryo.DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy()).setFallbackInstantiatorStrategy(new StdInstantiatorStrategy()); + ((DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy()).setFallbackInstantiatorStrategy(new StdInstantiatorStrategy()); + + // switch from 5.0.x default settings to 4.0.2 default settings + // see https://github.com/EsotericSoftware/kryo/wiki/Migration-to-v5#configuration-changes + kryo.setRegistrationRequired(false); + kryo.setReferences(true); } FileInputStream fis; @@ -442,13 +448,16 @@ public Memoizer() { * will be created. */ public Memoizer(long minimumElapsed) { - this(minimumElapsed, null); + // setting the directory to null explicitly disables memo files + // setting doInPlaceCaching allows a memo file to be written to the + // original file's directory only - see getMemoFile + this(null, minimumElapsed, null); this.doInPlaceCaching = true; } /** - * Constructs a memoizer around a new {@link ImageReader} creating memo file - * files under the {@code directory} argument including the full path of the + * Constructs a memoizer around a new {@link ImageReader} creating memo files + * under the {@code directory} argument including the full path of the * original file only if the call to {@link #setId} takes longer than * {@code minimumElapsed} in milliseconds. * @@ -459,9 +468,7 @@ public Memoizer(long minimumElapsed) { * files should be created. If {@code null}, disable memoization. */ public Memoizer(long minimumElapsed, File directory) { - super(); - this.minimumElapsed = minimumElapsed; - this.directory = directory; + this(null, minimumElapsed, directory); } /** @@ -470,10 +477,15 @@ public Memoizer(long minimumElapsed, File directory) { * call to {@link #setId} takes longer than * {@value DEFAULT_MINIMUM_ELAPSED} in milliseconds. * - * @param r an {@link IFormatReader} instance + * @param r an {@link IFormatReader} instance. + * If {@code null}, a new {@link ImageReader} is created. */ public Memoizer(IFormatReader r) { - this(r, DEFAULT_MINIMUM_ELAPSED); + // setting the directory to null explicitly disables memo files + // setting doInPlaceCaching allows a memo file to be written to the + // original file's directory only - see getMemoFile + this(r, DEFAULT_MINIMUM_ELAPSED, null); + this.doInPlaceCaching = true; } /** @@ -483,15 +495,34 @@ public Memoizer(IFormatReader r) { * milliseconds. * * @param r an {@link IFormatReader} instance + * If {@code null}, a new {@link ImageReader} is created. * @param minimumElapsed a long specifying the number of milliseconds which * must elapse during the call to {@link #setId} before a memo file * will be created. */ public Memoizer(IFormatReader r, long minimumElapsed) { + // setting the directory to null explicitly disables memo files + // setting doInPlaceCaching allows a memo file to be written to the + // original file's directory only - see getMemoFile this(r, minimumElapsed, null); this.doInPlaceCaching = true; } + /** + * Constructs a memoizer around the given {@link IFormatReader} creating + * memo file under the {@code directory} argument including the full path of the + * original file only if the call to {@link #setId} takes longer than + * {@value DEFAULT_MINIMUM_ELAPSED} in milliseconds. + * + * @param r an {@link IFormatReader} instance + * If {@code null}, a new {@link ImageReader} is created. + * @param directory a {@link File} specifying the directory where all memo + * files should be created. If {@code null}, disable memoization. + */ + public Memoizer(IFormatReader r, File directory) { + this(r, DEFAULT_MINIMUM_ELAPSED, directory); + } + /** * Constructs a memoizer around the given {@link IFormatReader} creating * memo files under the {@code directory} argument including the full path @@ -499,6 +530,7 @@ public Memoizer(IFormatReader r, long minimumElapsed) { * {@code minimumElapsed} in milliseconds. * * @param r an {@link IFormatReader} instance + * If {@code null}, a new {@link ImageReader} is created. * @param minimumElapsed a long specifying the number of milliseconds which * must elapse during the call to {@link #setId} before a memo file * will be created. @@ -506,12 +538,16 @@ public Memoizer(IFormatReader r, long minimumElapsed) { * files should be created. If {@code null}, disable memoization. */ public Memoizer(IFormatReader r, long minimumElapsed, File directory) { - super(r); + if (r == null) { + reader = new ImageReader(); + } + else { + reader = r; + } this.minimumElapsed = minimumElapsed; this.directory = directory; } - /** * Returns whether the {@link #reader} instance currently active was loaded * from the memo file during {@link #setId(String)}. @@ -1003,6 +1039,21 @@ public boolean saveMemo() { return rv; } + /** + * Force the current memo file to be deleted, if it exists. + * + * @return true if the delete succeeded + */ + public boolean deleteMemo() { + return deleteQuietly(memoFile); + } + + /** + * @return current memo file (may be null) + */ + public File getMemoFile() { + return memoFile; + } /** * Return the {@link IFormatReader} instance that is passed in or null if diff --git a/components/formats-bsd/src/loci/formats/codec/LuraWaveCodec.java b/components/formats-bsd/src/loci/formats/codec/LuraWaveCodec.java deleted file mode 100644 index c0a03447221..00000000000 --- a/components/formats-bsd/src/loci/formats/codec/LuraWaveCodec.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * #%L - * BSD implementations of Bio-Formats readers and writers - * %% - * Copyright (C) 2005 - 2017 Open Microscopy Environment: - * - Board of Regents of the University of Wisconsin-Madison - * - Glencoe Software, Inc. - * - University of Dundee - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package loci.formats.codec; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.IOException; - -import loci.common.DataTools; -import loci.common.RandomAccessInputStream; -import loci.common.services.DependencyException; -import loci.common.services.ServiceException; -import loci.common.services.ServiceFactory; -import loci.formats.FormatException; -import loci.formats.MissingLibraryException; -import loci.formats.UnsupportedCompressionException; -import loci.formats.services.LuraWaveService; -import loci.formats.services.LuraWaveServiceImpl; - -/** - * This class provides LuraWave decompression, using LuraWave's Java decoding - * library. Compression is not supported. Decompression requires a LuraWave - * license code, specified in the lurawave.license system property (e.g., - * -Dlurawave.license=XXXX on the command line). - * - * @author Curtis Rueden ctrueden at wisc.edu - */ -public class LuraWaveCodec extends WrappedCodec { - public LuraWaveCodec() { - super(new ome.codecs.LuraWaveCodec()); - } -} diff --git a/components/formats-bsd/src/loci/formats/dicom/DicomAttribute.java b/components/formats-bsd/src/loci/formats/dicom/DicomAttribute.java index 21aa6a4f77e..acc3cf26009 100644 --- a/components/formats-bsd/src/loci/formats/dicom/DicomAttribute.java +++ b/components/formats-bsd/src/loci/formats/dicom/DicomAttribute.java @@ -670,6 +670,7 @@ public enum DicomAttribute { ITEM(0xFFFEE000), ITEM_DELIMITATION_ITEM(0xFFFEE00D), SEQUENCE_DELIMITATION_ITEM(0xFFFEE0DD), + TRAILING_PADDING(0xFFFCFFFC), // directory structuring elements FILE_SET_ID(0x00041130), diff --git a/components/formats-bsd/src/loci/formats/dicom/DicomTag.java b/components/formats-bsd/src/loci/formats/dicom/DicomTag.java index b96fa9986d7..caec5c19e70 100644 --- a/components/formats-bsd/src/loci/formats/dicom/DicomTag.java +++ b/components/formats-bsd/src/loci/formats/dicom/DicomTag.java @@ -134,7 +134,11 @@ else if (vr == IMPLICIT) { vr = DicomAttribute.getDefaultVR(tag); } - if (!readValue) { + if (!readValue || attribute == PIXEL_DATA) { + long skip = elementLength & 0xffffffffL; + if (skip > 0 && skip <= in.length() - in.getFilePointer()) { + in.skipBytes(skip); + } return; } @@ -352,10 +356,20 @@ private int getNextTag(RandomAccessInputStream in) throws FormatException, IOExc return tag; } - if (elementLength < 0 && groupWord == 0x7fe0) { - in.skipBytes(12); - elementLength = in.readInt(); - if (elementLength < 0) elementLength = in.readInt(); + // indicates that we have found pixel data + if (groupWord == 0x7fe0) { + // the length may legitimately be between 2 and 4 GB + long length = elementLength & 0xffffffffL; + + // length of 0xffffffff means undefined length, which is uncommon but allowed + if (elementLength == -1 || (length > 0 && length < in.length())) { + return tag; + } + else { + in.skipBytes(12); + elementLength = in.readInt(); + if (elementLength < 0) elementLength = in.readInt(); + } } if (elementLength == 0 && (groupWord == 0x7fe0 || tag == 0x291014)) { diff --git a/components/formats-bsd/src/loci/formats/gui/DataConverter.java b/components/formats-bsd/src/loci/formats/gui/DataConverter.java index a3bdba4da6b..d0b460794a7 100644 --- a/components/formats-bsd/src/loci/formats/gui/DataConverter.java +++ b/components/formats-bsd/src/loci/formats/gui/DataConverter.java @@ -98,7 +98,7 @@ public class DataConverter extends JFrame implements private boolean shutdown, force = true; private JTextField input, output; - private JCheckBox qtJava, forceType, includeZ, includeT, includeC; + private JCheckBox forceType, includeZ, includeT, includeC; private JLabel zLabel, tLabel, cLabel; private JComboBox zChoice, tChoice, cChoice, codec; private JSpinner fps, series; @@ -272,11 +272,6 @@ public DataConverter() { pane.add(Box.createVerticalStrut(9)); - boolean canDoQT = new LegacyQTTools().canDoQT(); - qtJava = new JCheckBox("Use QTJava", canDoQT); - qtJava.setEnabled(canDoQT); - row5.add(qtJava); - row5.add(Box.createHorizontalStrut(3)); forceType = new JCheckBox("Force", true); @@ -480,10 +475,6 @@ public void run() { } catch (NullPointerException npe) { } - //boolean isQT = swap.getFormat().equals("QuickTime"); - //boolean useQTJ = isQT && qtJava.isSelected(); - //((QTReader) reader.getReader(QTReader.class)).setLegacy(useQTJ); - // swap dimensions based on user input String order = swap.getDimensionOrder(); diff --git a/components/formats-bsd/src/loci/formats/gui/LegacyQTTools.java b/components/formats-bsd/src/loci/formats/gui/LegacyQTTools.java deleted file mode 100644 index be620b37b28..00000000000 --- a/components/formats-bsd/src/loci/formats/gui/LegacyQTTools.java +++ /dev/null @@ -1,341 +0,0 @@ -/* - * #%L - * BSD implementations of Bio-Formats readers and writers - * %% - * Copyright (C) 2005 - 2017 Open Microscopy Environment: - * - Board of Regents of the University of Wisconsin-Madison - * - Glencoe Software, Inc. - * - University of Dundee - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package loci.formats.gui; - -import java.awt.Dimension; -import java.awt.Image; -import java.awt.Toolkit; -import java.awt.image.DirectColorModel; -import java.awt.image.MemoryImageSource; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.StringTokenizer; - -import loci.common.Location; -import loci.common.ReflectException; -import loci.common.ReflectedUniverse; -import loci.formats.FormatException; -import loci.formats.MissingLibraryException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Utility class for working with QuickTime for Java. - */ -public class LegacyQTTools { - - // -- Constants -- - - private static final Logger LOGGER = - LoggerFactory.getLogger(LegacyQTTools.class); - - public static final String NO_QT_MSG = - "QuickTime for Java is required to read some QuickTime files. " + - "Please install QuickTime for Java from http://www.apple.com/quicktime/"; - - public static final String JVM_64BIT_MSG = - "QuickTime for Java is not supported with a 64-bit JVM. " + - "Please invoke the 32-bit JVM (-d32) to utilize QTJava functionality."; - - public static final String EXPIRED_QT_MSG = - "Your version of QuickTime for Java has expired. " + - "Please reinstall QuickTime for Java from http://www.apple.com/quicktime/"; - - protected static final String[] SUFFIXES = {"mov", "qt"}; - - protected static final boolean MAC_OS_X = - System.getProperty("os.name").equals("Mac OS X"); - - // -- Static fields -- - - /** - * This custom class loader searches additional paths for the QTJava.zip - * library. Java has a restriction where only one class loader can have a - * native library loaded within a JVM. So the class loader must be static, - * shared by all QTForms, or else an UnsatisfiedLinkError is thrown when - * attempting to initialize QTJava multiple times. - */ - protected static final ClassLoader LOADER = constructLoader(); - - protected static ClassLoader constructLoader() { - // set up additional QuickTime for Java paths - URL[] paths = null; - - if (MAC_OS_X) { - try { - paths = new URL[] { - new URL("file:/System/Library/Java/Extensions/QTJava.zip") - }; - } - catch (MalformedURLException exc) { LOGGER.info("", exc); } - return paths == null ? null : new URLClassLoader(paths); - } - - // case for Windows - try { - String windir = System.getProperty("java.library.path"); - StringTokenizer st = new StringTokenizer(windir, ";"); - - while (st.hasMoreTokens()) { - Location f = new Location(st.nextToken(), "QTJava.zip"); - if (f.exists()) { - try { - paths = new URL[] {f.toURL()}; - } - catch (MalformedURLException exc) { LOGGER.info("", exc); } - return paths == null ? null : new URLClassLoader(paths); - } - } - } - catch (SecurityException e) { - // this is common when using Bio-Formats within an applet - LOGGER.warn("Cannot read value of 'java.library.path'", e); - } - - return null; - } - - // -- Fields -- - - /** Flag indicating this class has been initialized. */ - protected boolean initialized = false; - - /** Flag indicating QuickTime for Java is not installed. */ - protected boolean noQT = false; - - /** Flag indicating 64-bit JVM (does not support QTJava). */ - protected boolean jvm64Bit = false; - - /** Flag indicating QuickTime for Java has expired. */ - protected boolean expiredQT = false; - - /** Reflection tool for QuickTime for Java calls. */ - protected ReflectedUniverse r; - - // -- LegacyQTTools API methods -- - - /** Initializes the class. */ - protected void initClass() { - if (initialized) return; - - String arch = System.getProperty("os.arch"); - if (arch != null && arch.indexOf("64") >= 0) { - // QTJava is not supported on 64-bit Java; don't even try - noQT = true; - jvm64Bit = true; - initialized = true; - return; - } - - boolean needClose = false; - r = new ReflectedUniverse(LOADER); - try { - r.exec("import quicktime.QTSession"); - r.exec("QTSession.open()"); - needClose = true; - - // for LegacyQTReader and LegacyQTWriter - r.exec("import quicktime.io.QTFile"); - r.exec("import quicktime.std.movies.Movie"); - - // for LegacyQTReader - r.exec("import quicktime.app.view.MoviePlayer"); - r.exec("import quicktime.app.view.QTImageProducer"); - r.exec("import quicktime.io.OpenMovieFile"); - r.exec("import quicktime.qd.QDDimension"); - r.exec("import quicktime.std.StdQTConstants"); - r.exec("import quicktime.std.movies.TimeInfo"); - r.exec("import quicktime.std.movies.Track"); - - // for LegacyQTWriter - r.exec("import quicktime.qd.QDGraphics"); - r.exec("import quicktime.qd.QDRect"); - r.exec("import quicktime.std.image.CSequence"); - r.exec("import quicktime.std.image.CodecComponent"); - r.exec("import quicktime.std.image.ImageDescription"); - r.exec("import quicktime.std.movies.media.VideoMedia"); - r.exec("import quicktime.util.QTHandle"); - r.exec("import quicktime.util.RawEncodedImage"); - r.exec("import quicktime.util.EndianOrder"); - } - catch (ExceptionInInitializerError err) { - noQT = true; - Throwable t = err.getException(); - if (t instanceof SecurityException) { - SecurityException exc = (SecurityException) t; - if (exc.getMessage().indexOf("expired") >= 0) expiredQT = true; - } - } - catch (Throwable t) { - noQT = true; - LOGGER.debug("Could not find QuickTime for Java", t); - } - finally { - if (needClose) { - try { r.exec("QTSession.close()"); } - catch (Throwable t) { - LOGGER.debug("Could not close QuickTime session", t); - } - } - initialized = true; - } - } - - /** Whether QuickTime is available to this JVM. */ - public boolean canDoQT() { - if (!initialized) initClass(); - return !noQT; - } - - /** Whether this JVM is 64-bit. */ - public boolean isJVM64Bit() { - if (!initialized) initClass(); - return jvm64Bit; - } - - /** Whether QuickTime for Java has expired. */ - public boolean isQTExpired() { - if (!initialized) initClass(); - return expiredQT; - } - - /** Gets the QuickTime for Java version number. */ - public String getQTVersion() { - if (isJVM64Bit()) return "Not available"; - else if (isQTExpired()) return "Expired"; - else if (!canDoQT()) return "Missing"; - else { - try { - String qtMajor = r.exec("QTSession.getMajorVersion()").toString(); - String qtMinor = r.exec("QTSession.getMinorVersion()").toString(); - return qtMajor + "." + qtMinor; - } - catch (Throwable t) { - LOGGER.debug("Could not retrieve QuickTime for Java version", t); - return "Error"; - } - } - } - - /** Gets QuickTime for Java reflected universe. */ - public ReflectedUniverse getUniverse() { - if (!initialized) initClass(); - return r; - } - - /** Gets width and height for the given PICT bytes. */ - public Dimension getPictDimensions(byte[] bytes) - throws FormatException, ReflectException - { - checkQTLibrary(); - try { - r.exec("QTSession.open()"); - r.setVar("bytes", bytes); - r.exec("pict = new Pict(bytes)"); - r.exec("box = pict.getPictFrame()"); - int width = ((Integer) r.exec("box.getWidth()")).intValue(); - int height = ((Integer) r.exec("box.getHeight()")).intValue(); - r.exec("QTSession.close()"); - return new Dimension(width, height); - } - catch (ReflectException e) { - r.exec("QTSession.close()"); - throw new FormatException("PICT height determination failed", e); - } - } - - /** Converts the given byte array in PICT format to a Java image. */ - public synchronized Image pictToImage(byte[] bytes) - throws FormatException - { - checkQTLibrary(); - try { - r.exec("QTSession.open()"); - - // Code adapted from: - // http://www.onjava.com/pub/a/onjava/2002/12/23/jmf.html?page=2 - r.setVar("bytes", bytes); - r.exec("pict = new Pict(bytes)"); - r.exec("box = pict.getPictFrame()"); - int width = ((Integer) r.exec("box.getWidth()")).intValue(); - int height = ((Integer) r.exec("box.getHeight()")).intValue(); - // note: could get a RawEncodedImage from the Pict, but - // apparently no way to get a PixMap from the REI - r.exec("g = new QDGraphics(box)"); - r.exec("pict.draw(g, box)"); - // get data from the QDGraphics - r.exec("pixMap = g.getPixMap()"); - r.exec("rei = pixMap.getPixelData()"); - - // copy bytes to an array - int rowBytes = ((Integer) r.exec("pixMap.getRowBytes()")).intValue(); - int intsPerRow = rowBytes / 4; - int pixLen = intsPerRow * height; - r.setVar("pixLen", pixLen); - int[] pixels = new int[pixLen]; - r.setVar("pixels", pixels); - r.setVar("zero", new Integer(0)); - r.exec("rei.copyToArray(zero, pixels, zero, pixLen)"); - - // now coax into image, ignoring alpha for speed - int bitsPerSample = 32; - int redMask = 0x00ff0000; - int greenMask = 0x0000ff00; - int blueMask = 0x000000ff; - int alphaMask = 0x00000000; - DirectColorModel colorModel = new DirectColorModel( - bitsPerSample, redMask, greenMask, blueMask, alphaMask); - - r.exec("QTSession.close()"); - return Toolkit.getDefaultToolkit().createImage(new MemoryImageSource( - width, height, colorModel, pixels, 0, intsPerRow)); - } - catch (ReflectException e) { - try { r.exec("QTSession.close()"); } - catch (ReflectException exc) { - LOGGER.info("Could not close QuickTime session", exc); - } - throw new FormatException("PICT extraction failed", e); - } - } - - /** Checks whether QTJava is available, throwing an exception if not. */ - public void checkQTLibrary() throws MissingLibraryException { - if (isJVM64Bit()) throw new MissingLibraryException(JVM_64BIT_MSG); - if (isQTExpired()) throw new MissingLibraryException(EXPIRED_QT_MSG); - if (!canDoQT()) throw new MissingLibraryException(NO_QT_MSG); - } - -} diff --git a/components/formats-bsd/src/loci/formats/in/DicomReader.java b/components/formats-bsd/src/loci/formats/in/DicomReader.java index 1231fe0652f..aba35119fd4 100644 --- a/components/formats-bsd/src/loci/formats/in/DicomReader.java +++ b/components/formats-bsd/src/loci/formats/in/DicomReader.java @@ -672,6 +672,9 @@ else if (child.attribute == OPTICAL_PATH_DESCRIPTION) { case EXTENDED_DEPTH_OF_FIELD: edf = tag.getStringValue().equalsIgnoreCase("yes"); break; + case TRAILING_PADDING: + decodingTags = false; + break; default: in.seek(tag.getEndPointer()); } @@ -707,6 +710,9 @@ else if (child.attribute == OPTICAL_PATH_DESCRIPTION) { if (m.sizeZ == 0) { m.sizeZ = 1; } + if (m.imageCount == 0) { + m.imageCount = 1; + } if (opticalChannels == 0 || (concatenationNumber == null && ((imagesPerFile / m.sizeZ) % opticalChannels != 0))) { opticalChannels = 1; } @@ -1494,8 +1500,8 @@ private void getTile(DicomTile tile, byte[] buf, int x, int y, int w, int h) LOGGER.error("attempted to read beyond end of file ({}, {})", tile.fileOffset, tile.file); return; } - stream.seek(tile.fileOffset); LOGGER.debug("reading from offset = {}, file = {}", tile.fileOffset, tile.file); + stream.seek(tile.fileOffset); if (tile.isRLE) { // plane is compressed using run-length encoding @@ -1601,7 +1607,14 @@ else if (pt < b.length - 2) { options.interleaved = isInterleaved(); if (tile.isJPEG) codec = new JPEGCodec(); else codec = new JPEG2000Codec(); - b = codec.decompress(b, options); + + try { + b = codec.decompress(b, options); + } + catch (NullPointerException e) { + LOGGER.debug("Could not read empty or invalid tile", e); + return; + } int rowLen = w * bpp; int srcRowLen = tile.region.width * bpp; @@ -1670,10 +1683,17 @@ private void calculatePixelsOffsets(long baseOffset) throws FormatException, IOE if (baseOffset == in.length()) { return; } + // for tiled images, the RGB channel count will likely be 0, + // as the image count has not yet been set correctly + // for RGB tiles, the channel count needs to be adjusted + // so that the correct number of pixel bytes are anticipated int channelCount = getRGBChannelCount(); if (lut != null || channelCount == 0) { channelCount = 1; } + if (isRGB()) { + channelCount = getSizeC() / channelCount; + } int bpp = FormatTools.getBytesPerPixel(getPixelType()); int plane = originalX * originalY * channelCount * bpp; @@ -1702,7 +1722,10 @@ private void calculatePixelsOffsets(long baseOffset) throws FormatException, IOE in.seek(in.getFilePointer() - 1); } } - in.skipBytes(i == 0 ? 64 : 53); + if (in.readInt() == 0xe000fffe) { + in.skipBytes(12); + } + in.skipBytes(i == 0 ? 60 : 49); while (in.read() == 0); offset = in.getFilePointer() - 1; } @@ -1750,7 +1773,7 @@ else if (buf[q] == (byte) 0xff && buf[q + 1] == (byte) 0xd9) { } } } - else offset = baseOffset + plane*i; + else offset = baseOffset + (long) plane*i; tilePositions.get(getCoreIndex()).get(i).fileOffset = offset; tilePositions.get(getCoreIndex()).get(i).last = i == imagesPerFile - 1; diff --git a/components/formats-bsd/src/loci/formats/in/ICSReader.java b/components/formats-bsd/src/loci/formats/in/ICSReader.java index 34e5e0fc4ec..0513eac024e 100644 --- a/components/formats-bsd/src/loci/formats/in/ICSReader.java +++ b/components/formats-bsd/src/loci/formats/in/ICSReader.java @@ -32,6 +32,7 @@ package loci.formats.in; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; @@ -1477,7 +1478,9 @@ else if (labels.equalsIgnoreCase("x y t")) { MetadataTools.populatePixels(store, this, true); // populate Image data - + imageName = imageName.replace('/', File.separatorChar); + imageName = imageName.replace('\\', File.separatorChar); + imageName = imageName.substring(imageName.lastIndexOf(File.separator) + 1); store.setImageName(imageName, 0); if (date != null) store.setImageAcquisitionDate(new Timestamp(date), 0); diff --git a/components/formats-bsd/src/loci/formats/in/KLBReader.java b/components/formats-bsd/src/loci/formats/in/KLBReader.java index 70b80ffc75c..550463d8c0e 100644 --- a/components/formats-bsd/src/loci/formats/in/KLBReader.java +++ b/components/formats-bsd/src/loci/formats/in/KLBReader.java @@ -261,6 +261,13 @@ else if (compressionType == COMPRESSION_ZLIB) { int imageRowSize = w * bytesPerPixel; int blockRowSize = blockSizeAux[0] * bytesPerPixel; int fullBlockRowSize = dims_blockSize[0] * bytesPerPixel; + + // Actual block values used as offsets for Z planes + // This covers the use case when you have a block that overlaps a tile and also + // is a partial block at the end of a row or column + int actualBlockWidth = Math.min(dims_blockSize[0], (getSizeX() - coordBlock[0])); + int actualBlockHeight = Math.min(dims_blockSize[1], (getSizeY() - coordBlock[1])); + int actualBlockPlaneSize = bytesPerPixel * actualBlockHeight * actualBlockWidth; // Location in output buffer to copy block outputOffset = (imageRowSize * (coordBlock[1] - y)) + ((coordBlock[0] - x) * bytesPerPixel); @@ -269,13 +276,13 @@ else if (compressionType == COMPRESSION_ZLIB) { if (coordBlock[1] < y && coordBlock[0] < x && blockSizeAux[1] != dims_blockSize[1] && blockSizeAux[0] != dims_blockSize[0]) outputOffset = 0; // Location within the block for required XY plane - int inputOffset = (coordBlock[2] % dims_blockSize[2]) * blockRowSize * blockSizeAux[1]; - if (coordBlock[0] < x && coordBlock[1] < y && blockSizeAux[1] != dims_blockSize[1] && blockSizeAux[0] != dims_blockSize[0]) inputOffset += ((dims_blockSize[0] * (dims_blockSize[1] - blockSizeAux[1])) + (x - coordBlock[0])) * bytesPerPixel; + int inputOffset = (currentCoords[0] % dims_blockSize[2]) * actualBlockPlaneSize; + if (coordBlock[0] < x && coordBlock[1] < y && blockSizeAux[1] != dims_blockSize[1] && blockSizeAux[0] != dims_blockSize[0]) inputOffset += ((dims_blockSize[0] * (y - coordBlock[1])) + (x - coordBlock[0])) * bytesPerPixel; // Partial block at the start of x tile - else if (coordBlock[0] < x && blockSizeAux[0] != dims_blockSize[0]) inputOffset += (dims_blockSize[0] - blockSizeAux[0]) * bytesPerPixel; + else if (coordBlock[0] < x && blockSizeAux[0] != dims_blockSize[0]) inputOffset += (x - coordBlock[0]) * bytesPerPixel; // Partial block at the start of y tile - else if (coordBlock[1] < y && blockSizeAux[1] != dims_blockSize[1] && coordBlock[0] + blockSizeAux[0] == dims_xyzct[0]) inputOffset += blockSizeAux[0] * (dims_blockSize[1] - blockSizeAux[1]) * bytesPerPixel; - else if (coordBlock[1] < y && blockSizeAux[1] != dims_blockSize[1]) inputOffset += dims_blockSize[0] * (dims_blockSize[1] - blockSizeAux[1]) * bytesPerPixel; + else if (coordBlock[1] < y && blockSizeAux[1] != dims_blockSize[1] && coordBlock[0] + blockSizeAux[0] == dims_xyzct[0]) inputOffset += blockSizeAux[0] * (y - coordBlock[1]) * bytesPerPixel; + else if (coordBlock[1] < y && blockSizeAux[1] != dims_blockSize[1]) inputOffset += dims_blockSize[0] * (y - coordBlock[1]) * bytesPerPixel; inputOffset += (coordBlock[3] % dims_blockSize[3]) * blockRowSize * blockSizeAux[1] * blockSizeAux[2]; inputOffset += (coordBlock[4] % dims_blockSize[4]) * blockRowSize * blockSizeAux[1] * blockSizeAux[2] * blockSizeAux[3]; diff --git a/components/formats-bsd/src/loci/formats/in/LegacyQTReader.java b/components/formats-bsd/src/loci/formats/in/LegacyQTReader.java deleted file mode 100644 index 406b04fba53..00000000000 --- a/components/formats-bsd/src/loci/formats/in/LegacyQTReader.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * #%L - * BSD implementations of Bio-Formats readers and writers - * %% - * Copyright (C) 2005 - 2017 Open Microscopy Environment: - * - Board of Regents of the University of Wisconsin-Madison - * - Glencoe Software, Inc. - * - University of Dundee - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package loci.formats.in; - -import java.awt.Dimension; -import java.awt.Image; -import java.awt.Toolkit; -import java.awt.image.BufferedImage; -import java.awt.image.ImageProducer; -import java.io.IOException; -import java.util.Vector; - -import loci.common.Location; -import loci.common.ReflectException; -import loci.common.ReflectedUniverse; -import loci.formats.CoreMetadata; -import loci.formats.FormatException; -import loci.formats.FormatTools; -import loci.formats.MetadataTools; -import loci.formats.gui.AWTImageTools; -import loci.formats.gui.LegacyQTTools; -import loci.formats.meta.MetadataStore; - -/** - * LegacyQTReader is a file format reader for QuickTime movie files. - * To use it, QuickTime for Java must be installed. - * - * Much of this code was based on the QuickTime Movie Opener for ImageJ - * (available at http://rsb.info.nih.gov/ij/plugins/movie-opener.html). - */ -public class LegacyQTReader extends BIFormatReader { - - // -- Fields -- - - /** Instance of LegacyQTTools to handle QuickTime for Java detection. */ - protected LegacyQTTools tools; - - /** Reflection tool for QuickTime for Java calls. */ - protected ReflectedUniverse r; - - /** Time offset for each frame. */ - protected int[] times; - - /** Image containing current frame. */ - protected Image image; - - // -- Constructor -- - - /** Constructs a new QT reader. */ - public LegacyQTReader() { - super("QuickTime", "mov"); - domains = new String[] {FormatTools.GRAPHICS_DOMAIN}; - } - - // -- IFormatReader API methods -- - - /* @see loci.formats.IFormatReader#openPlane(int, int, int, int, int int) */ - @Override - public Object openPlane(int no, int x, int y, int w, int h) - throws FormatException, IOException - { - FormatTools.checkPlaneParameters(this, no, -1, x, y, w, h); - - // paint frame into image - try { - r.setVar("time", times[no]); - r.exec("moviePlayer.setTime(time)"); - r.exec("qtip.redraw(null)"); - r.exec("qtip.updateConsumers(null)"); - } - catch (ReflectException re) { - throw new FormatException("Open movie failed", re); - } - return AWTImageTools.getSubimage(AWTImageTools.makeBuffered(image), - isLittleEndian(), x, y, w, h); - } - - /* @see loci.formats.IFormatReader#close(boolean) */ - @Override - public void close(boolean fileOnly) throws IOException { - try { - if (r != null && r.getVar("openMovieFile") != null) { - r.exec("openMovieFile.close()"); - if (!fileOnly) { - r.exec("m.disposeQTObject()"); - r.exec("imageTrack.disposeQTObject()"); - r.exec("QTSession.close()"); - } - } - } - catch (ReflectException e) { - LOGGER.debug("Failed to close QuickTime session", e); - } - if (!fileOnly) { - currentId = null; - times = null; - image = null; - } - } - - // -- Internal FormatReader API methods -- - - /* @see loci.formats.FormatReader#initFile(String) */ - @Override - protected void initFile(String id) throws FormatException, IOException { - LOGGER.info("Checking for QuickTime Java"); - - if (tools == null) { - tools = new LegacyQTTools(); - r = tools.getUniverse(); - } - tools.checkQTLibrary(); - - super.initFile(id); - - LOGGER.info("Reading movie dimensions"); - try { - r.exec("QTSession.open()"); - - // open movie file - Location file = new Location(id); - r.setVar("path", file.getAbsolutePath()); - r.exec("qtf = new QTFile(path)"); - r.exec("openMovieFile = OpenMovieFile.asRead(qtf)"); - r.exec("m = Movie.fromFile(openMovieFile)"); - - int numTracks = ((Integer) r.exec("m.getTrackCount()")).intValue(); - int trackMostLikely = 0; - int trackNum = 0; - while (++trackNum <= numTracks && trackMostLikely == 0) { - r.setVar("trackNum", trackNum); - r.exec("imageTrack = m.getTrack(trackNum)"); - r.exec("d = imageTrack.getSize()"); - Integer w = (Integer) r.exec("d.getWidth()"); - if (w.intValue() > 0) trackMostLikely = trackNum; - } - - r.setVar("trackMostLikely", trackMostLikely); - r.exec("imageTrack = m.getTrack(trackMostLikely)"); - r.exec("d = imageTrack.getSize()"); - Integer w = (Integer) r.exec("d.getWidth()"); - Integer h = (Integer) r.exec("d.getHeight()"); - - r.exec("moviePlayer = new MoviePlayer(m)"); - r.setVar("dim", new Dimension(w.intValue(), h.intValue())); - ImageProducer qtip = (ImageProducer) - r.exec("qtip = new QTImageProducer(moviePlayer, dim)"); - image = Toolkit.getDefaultToolkit().createImage(qtip); - - r.setVar("zero", 0); - r.setVar("one", 1f); - r.exec("timeInfo = new TimeInfo(zero, zero)"); - r.exec("moviePlayer.setTime(zero)"); - Vector v = new Vector(); - int time = 0; - Integer q = new Integer(time); - do { - v.add(q); - r.exec("timeInfo = imageTrack.getNextInterestingTime(" + - "StdQTConstants.nextTimeMediaSample, timeInfo.time, one)"); - q = (Integer) r.getVar("timeInfo.time"); - time = q.intValue(); - } - while (time >= 0); - - CoreMetadata m = core.get(0); - m.imageCount = v.size(); - times = new int[getImageCount()]; - for (int i=0; i offsets; - - /** Pixel data for the previous image plane. */ - private byte[] prevPixels; - - /** Previous plane number. */ - private int prevPlane; - - /** Flag indicating whether we can safely use prevPixels. */ - private boolean canUsePrevious; - - /** Video codec used by this movie. */ - private String codec; - - /** Some movies use two video codecs -- this is the second codec. */ - private String altCodec; - - /** Number of frames that use the alternate codec. */ - private int altPlanes; - - /** Amount to subtract from each offset. */ - private int scale; - - /** Number of bytes in each plane. */ - private Vector chunkSizes; - - /** Set to true if the scanlines in a plane are interlaced (mjpb only). */ - private boolean interlaced; - - /** Flag indicating whether the resource and data fork are separated. */ - private boolean separatedFork; - private String forkFile; - - private boolean flip; - - // -- Constructor -- - - /** Constructs a new QuickTime reader. */ - public NativeQTReader() { - super("QuickTime", "mov"); - suffixNecessary = false; - domains = new String[] {FormatTools.GRAPHICS_DOMAIN}; - } - - // -- IFormatReader API methods -- - - /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ - @Override - public boolean isThisType(RandomAccessInputStream stream) throws IOException { - final int blockLen = 64; - if (!FormatTools.validStream(stream, blockLen, false)) return false; - // use a crappy hack for now - String s = stream.readString(blockLen); - for (int i=0; i= 0 && - !CONTAINER_TYPES[i].equals("imag")) - { - return true; - } - } - return s.indexOf("wide") >= 0 || - s.indexOf("mdat") >= 0 || s.indexOf("ftypqt") >= 0; - } - - /* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */ - @Override - public String[] getSeriesUsedFiles(boolean noPixels) { - FormatTools.assertId(currentId, true, 1); - if (noPixels) { - return forkFile == null ? null : new String[] {forkFile}; - } - return forkFile == null ? new String[] {currentId} : - new String[] {currentId, forkFile}; - } - - /** - * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int) - */ - @Override - public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) - throws FormatException, IOException - { - FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h); - - String code = codec; - if (no >= getImageCount() - altPlanes) code = altCodec; - - int offset = offsets.get(no).intValue(); - int nextOffset = (int) pixelBytes; - - scale = offsets.get(0).intValue(); - offset -= scale; - - if (no < offsets.size() - 1) { - nextOffset = offsets.get(no + 1).intValue() - scale; - } - - if ((nextOffset - offset) < 0) { - int temp = offset; - offset = nextOffset; - nextOffset = temp; - } - - byte[] pixs = new byte[nextOffset - offset]; - - in.seek(pixelOffset + offset); - in.read(pixs); - - canUsePrevious = (prevPixels != null) && (prevPlane == no - 1) && - !code.equals(altCodec); - - byte[] t = prevPlane == no && prevPixels != null && !code.equals(altCodec) ? - prevPixels : uncompress(pixs, code); - if (code.equals("rpza")) { - for (int i=0; i 0) { - prevPixels = t; - } - prevPlane = no; - - // determine whether we need to strip out any padding bytes - - int bytes = bitsPerPixel < 40 ? bitsPerPixel / 8 : (bitsPerPixel - 32) / 8; - int pad = (4 - (getSizeX() % 4)) % 4; - if (codec.equals("mjpb")) pad = 0; - - int expectedSize = FormatTools.getPlaneSize(this); - - if (prevPixels.length == expectedSize || - (bitsPerPixel == 32 && (3 * (prevPixels.length / 4)) == expectedSize)) - { - pad = 0; - } - - if (pad > 0) { - t = new byte[prevPixels.length - getSizeY()*pad]; - - for (int row=0; row(); - chunkSizes = new Vector(); - LOGGER.info("Parsing tags"); - - parse(0, 0, in.length()); - - CoreMetadata m = core.get(0); - - m.imageCount = offsets.size(); - if (chunkSizes.size() < getImageCount() && chunkSizes.size() > 0) { - m.imageCount = chunkSizes.size(); - } - - LOGGER.info("Populating metadata"); - - int bytes = (bitsPerPixel / 8) % 4; - m.pixelType = bytes == 2 ? FormatTools.UINT16 : FormatTools.UINT8; - - m.sizeZ = 1; - m.dimensionOrder = "XYCZT"; - m.littleEndian = false; - m.metadataComplete = true; - m.indexed = false; - m.falseColor = false; - - // this handles the case where the data and resource forks have been - // separated - if (separatedFork) { - // first we want to check if there is a resource fork present - // the resource fork will generally have the same name as the data fork, - // but will have either the prefix "._" or the suffix ".qtr" - // (or /rsrc on a Mac) - - String base = null; - // it's not enough to just check the first index of "." - // on Windows in particular, the directory name could contain "." while - // the file name has no extension - if (id.indexOf(".", id.lastIndexOf(File.separator) + 1) != -1) { - base = id.substring(0, id.lastIndexOf(".")); - } - else base = id; - - Location f = new Location(base + ".qtr"); - LOGGER.debug("Searching for resource fork:"); - if (f.exists()) { - LOGGER.debug("\t Found: {}", f); - if (in != null) in.close(); - in = new RandomAccessInputStream(f.getAbsolutePath()); - forkFile = f.getAbsolutePath(); - - stripHeader(); - parse(0, 0, in.length()); - m.imageCount = offsets.size(); - } - else { - LOGGER.debug("\tAbsent: {}", f); - f = new Location(id.substring(0, - id.lastIndexOf(File.separator) + 1) + "._" + - id.substring(base.lastIndexOf(File.separator) + 1)); - if (f.exists()) { - LOGGER.debug("\t Found: {}", f); - if (in != null) in.close(); - in = new RandomAccessInputStream(f.getAbsolutePath()); - forkFile = f.getAbsolutePath(); - stripHeader(); - parse(0, in.getFilePointer(), in.length()); - m.imageCount = offsets.size(); - } - else { - LOGGER.debug("\tAbsent: {}", f); - f = new Location(id + "/..namedfork/rsrc"); - if (f.exists()) { - LOGGER.debug("\t Found: {}", f); - if (in != null) in.close(); - in = new RandomAccessInputStream(f.getAbsolutePath()); - forkFile = f.getAbsolutePath(); - stripHeader(); - parse(0, in.getFilePointer(), in.length()); - m.imageCount = offsets.size(); - } - else { - LOGGER.debug("\tAbsent: {}", f); - throw new FormatException("QuickTime resource fork not found. " + - " To avoid this issue, please flatten your QuickTime movies " + - "before importing with Bio-Formats."); - } - } - } - // reset the stream, otherwise openBytes will try to read pixels - // from the resource fork - if (in != null) in.close(); - in = new RandomAccessInputStream(currentId); - } - - m.rgb = bitsPerPixel < 40; - m.sizeC = isRGB() ? 3 : 1; - m.interleaved = isRGB(); - m.sizeT = getImageCount(); - - // The metadata store we're working with. - MetadataStore store = makeFilterMetadata(); - MetadataTools.populatePixels(store, this); - } - - // -- Helper methods -- - - /** Parse all of the atoms in the file. */ - private void parse(int depth, long offset, long length) - throws FormatException, IOException - { - while (offset < length) { - in.seek(offset); - - // first 4 bytes are the atom size - long atomSize = in.readInt() & 0xffffffffL; - - // read the atom type - String atomType = in.readString(4); - - // if atomSize is 1, then there is an 8 byte extended size - if (atomSize == 1) { - atomSize = in.readLong(); - } - - if (atomSize < 0) { - LOGGER.warn("QTReader: invalid atom size: {}", atomSize); - } - else if (atomSize > in.length()) { - offset += 4; - continue; - } - - LOGGER.debug("Seeking to {}; atomType={}; atomSize={}", - new Object[] {offset, atomType, atomSize}); - - // if this is a container atom, parse the children - if (isContainer(atomType)) { - parse(depth++, in.getFilePointer(), offset + atomSize); - } - else { - if (atomSize == 0) atomSize = in.length(); - long oldpos = in.getFilePointer(); - - if (atomType.equals("mdat")) { - // we've found the pixel data - pixelOffset = in.getFilePointer(); - pixelBytes = atomSize; - - if (pixelBytes > (in.length() - pixelOffset)) { - pixelBytes = in.length() - pixelOffset; - } - } - else if (atomType.equals("tkhd")) { - // we've found the dimensions - - in.skipBytes(38); - int[][] matrix = new int[3][3]; - - for (int i=0; i 0) break; - separatedFork = false; - in.skipBytes(4); - int numPlanes = in.readInt(); - if (numPlanes != getImageCount()) { - in.seek(in.getFilePointer() - 4); - int off = in.readInt(); - offsets.add(new Integer(off)); - for (int i=1; i 0) && (i < chunkSizes.size())) { - rawSize = chunkSizes.get(i).intValue(); - } - else i = getImageCount(); - off += rawSize; - offsets.add(new Integer(off)); - } - } - else { - for (int i=0; i jpegOffsets = new Vector(); // -- Constructor -- @@ -113,13 +108,6 @@ public PictReader() { domains = new String[] {FormatTools.GRAPHICS_DOMAIN}; } - // -- PictReader API methods -- - - /** Control whether or not legacy reader (QT Java) is used. */ - public void setLegacy(boolean legacy) { - this.legacy = legacy; - } - // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#get8BitLookupTable() */ @@ -163,20 +151,6 @@ public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) return buf; } - if (legacy || strips.size() == 0) { - in.seek(512); - byte[] pix = new byte[(int) (in.length() - in.getFilePointer())]; - in.read(pix); - byte[][] b = AWTImageTools.getBytes( - AWTImageTools.makeBuffered(qtTools.pictToImage(pix))); - pix = null; - for (int i=0; i offsets; + + /** Pixel data for the previous image plane. */ + private byte[] prevPixels; + + /** Previous plane number. */ + private int prevPlane; + + /** Flag indicating whether we can safely use prevPixels. */ + private boolean canUsePrevious; + + /** Video codec used by this movie. */ + private String codec; + + /** Some movies use two video codecs -- this is the second codec. */ + private String altCodec; + + /** Number of frames that use the alternate codec. */ + private int altPlanes; + + /** Amount to subtract from each offset. */ + private int scale; + + /** Number of bytes in each plane. */ + private Vector chunkSizes; + + /** Set to true if the scanlines in a plane are interlaced (mjpb only). */ + private boolean interlaced; + + /** Flag indicating whether the resource and data fork are separated. */ + private boolean separatedFork; + private String forkFile; + + private boolean flip; // -- Constructor -- /** Constructs a new QuickTime reader. */ public QTReader() { super("QuickTime", "mov"); - nativeReader = new NativeQTReader(); - legacyReader = new LegacyQTReader(); - nativeReaderInitialized = false; - legacyReaderInitialized = false; + suffixNecessary = false; domains = new String[] {FormatTools.GRAPHICS_DOMAIN}; } + // -- IFormatReader API methods -- + + /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ + @Override + public boolean isThisType(RandomAccessInputStream stream) throws IOException { + final int blockLen = 64; + if (!FormatTools.validStream(stream, blockLen, false)) return false; + // use a crappy hack for now + String s = stream.readString(blockLen); + for (int i=0; i= 0 && + !CONTAINER_TYPES[i].equals("imag")) + { + return true; + } + } + return s.indexOf("wide") >= 0 || + s.indexOf("mdat") >= 0 || s.indexOf("ftypqt") >= 0; + } + + /* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */ + @Override + public String[] getSeriesUsedFiles(boolean noPixels) { + FormatTools.assertId(currentId, true, 1); + if (noPixels) { + return forkFile == null ? null : new String[] {forkFile}; + } + return forkFile == null ? new String[] {currentId} : + new String[] {currentId, forkFile}; + } + + /** + * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int) + */ + @Override + public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) + throws FormatException, IOException + { + FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h); + + String code = codec; + if (no >= getImageCount() - altPlanes) code = altCodec; + + int offset = offsets.get(no).intValue(); + int nextOffset = (int) pixelBytes; + + scale = offsets.get(0).intValue(); + offset -= scale; + + if (no < offsets.size() - 1) { + nextOffset = offsets.get(no + 1).intValue() - scale; + } + + if ((nextOffset - offset) < 0) { + int temp = offset; + offset = nextOffset; + nextOffset = temp; + } + + byte[] pixs = new byte[nextOffset - offset]; + + in.seek(pixelOffset + offset); + in.read(pixs); + + canUsePrevious = (prevPixels != null) && (prevPlane == no - 1) && + !code.equals(altCodec); + + byte[] t = prevPlane == no && prevPixels != null && !code.equals(altCodec) ? + prevPixels : uncompress(pixs, code); + if (code.equals("rpza")) { + for (int i=0; i 0) { + prevPixels = t; + } + prevPlane = no; + + // determine whether we need to strip out any padding bytes + + int bytes = bitsPerPixel < 40 ? bitsPerPixel / 8 : (bitsPerPixel - 32) / 8; + int pad = (4 - (getSizeX() % 4)) % 4; + if (codec.equals("mjpb")) pad = 0; + + int expectedSize = FormatTools.getPlaneSize(this); + + if (prevPixels.length == expectedSize || + (bitsPerPixel == 32 && (3 * (prevPixels.length / 4)) == expectedSize)) + { + pad = 0; + } + + if (pad > 0) { + t = new byte[prevPixels.length - getSizeY()*pad]; + + for (int row=0; row(); + chunkSizes = new Vector(); + LOGGER.info("Parsing tags"); + + parse(0, 0, in.length()); + + CoreMetadata m = core.get(0); + + m.imageCount = offsets.size(); + if (chunkSizes.size() < getImageCount() && chunkSizes.size() > 0) { + m.imageCount = chunkSizes.size(); + } + + LOGGER.info("Populating metadata"); + + int bytes = (bitsPerPixel / 8) % 4; + m.pixelType = bytes == 2 ? FormatTools.UINT16 : FormatTools.UINT8; + + m.sizeZ = 1; + m.dimensionOrder = "XYCZT"; + m.littleEndian = false; + m.metadataComplete = true; + m.indexed = false; + m.falseColor = false; + + // this handles the case where the data and resource forks have been + // separated + if (separatedFork) { + // first we want to check if there is a resource fork present + // the resource fork will generally have the same name as the data fork, + // but will have either the prefix "._" or the suffix ".qtr" + // (or /rsrc on a Mac) + + String base = null; + // it's not enough to just check the first index of "." + // on Windows in particular, the directory name could contain "." while + // the file name has no extension + if (id.indexOf(".", id.lastIndexOf(File.separator) + 1) != -1) { + base = id.substring(0, id.lastIndexOf(".")); + } + else base = id; + + Location f = new Location(base + ".qtr"); + LOGGER.debug("Searching for resource fork:"); + if (f.exists()) { + LOGGER.debug("\t Found: {}", f); + if (in != null) in.close(); + in = new RandomAccessInputStream(f.getAbsolutePath()); + forkFile = f.getAbsolutePath(); + + stripHeader(); + parse(0, 0, in.length()); + m.imageCount = offsets.size(); + } + else { + LOGGER.debug("\tAbsent: {}", f); + f = new Location(id.substring(0, + id.lastIndexOf(File.separator) + 1) + "._" + + id.substring(base.lastIndexOf(File.separator) + 1)); + if (f.exists()) { + LOGGER.debug("\t Found: {}", f); + if (in != null) in.close(); + in = new RandomAccessInputStream(f.getAbsolutePath()); + forkFile = f.getAbsolutePath(); + stripHeader(); + parse(0, in.getFilePointer(), in.length()); + m.imageCount = offsets.size(); + } + else { + LOGGER.debug("\tAbsent: {}", f); + f = new Location(id + "/..namedfork/rsrc"); + if (f.exists()) { + LOGGER.debug("\t Found: {}", f); + if (in != null) in.close(); + in = new RandomAccessInputStream(f.getAbsolutePath()); + forkFile = f.getAbsolutePath(); + stripHeader(); + parse(0, in.getFilePointer(), in.length()); + m.imageCount = offsets.size(); + } + else { + LOGGER.debug("\tAbsent: {}", f); + throw new FormatException("QuickTime resource fork not found. " + + " To avoid this issue, please flatten your QuickTime movies " + + "before importing with Bio-Formats."); + } + } + } + // reset the stream, otherwise openBytes will try to read pixels + // from the resource fork + if (in != null) in.close(); + in = new RandomAccessInputStream(currentId); + } + + m.rgb = bitsPerPixel < 40; + m.sizeC = isRGB() ? 3 : 1; + m.interleaved = isRGB(); + m.sizeT = getImageCount(); + + // The metadata store we're working with. + MetadataStore store = makeFilterMetadata(); + MetadataTools.populatePixels(store, this); + } + + // -- Helper methods -- + + /** Parse all of the atoms in the file. */ + private void parse(int depth, long offset, long length) + throws FormatException, IOException + { + while (offset < length) { + in.seek(offset); + + // first 4 bytes are the atom size + long atomSize = in.readInt() & 0xffffffffL; + + // read the atom type + String atomType = in.readString(4); + + // if atomSize is 1, then there is an 8 byte extended size + if (atomSize == 1) { + atomSize = in.readLong(); + } + + if (atomSize < 0) { + LOGGER.warn("QTReader: invalid atom size: {}", atomSize); + } + else if (atomSize > in.length()) { + offset += 4; + continue; + } + + LOGGER.debug("Seeking to {}; atomType={}; atomSize={}", + new Object[] {offset, atomType, atomSize}); + + // if this is a container atom, parse the children + if (isContainer(atomType)) { + parse(depth++, in.getFilePointer(), offset + atomSize); + } + else { + if (atomSize == 0) atomSize = in.length(); + long oldpos = in.getFilePointer(); + + if (atomType.equals("mdat")) { + // we've found the pixel data + pixelOffset = in.getFilePointer(); + pixelBytes = atomSize; + + if (pixelBytes > (in.length() - pixelOffset)) { + pixelBytes = in.length() - pixelOffset; + } + } + else if (atomType.equals("tkhd")) { + // we've found the dimensions + + in.skipBytes(38); + int[][] matrix = new int[3][3]; + + for (int i=0; i 0) break; + separatedFork = false; + in.skipBytes(4); + int numPlanes = in.readInt(); + if (numPlanes != getImageCount()) { + in.seek(in.getFilePointer() - 4); + int off = in.readInt(); + offsets.add(new Integer(off)); + for (int i=1; i 0) && (i < chunkSizes.size())) { + rawSize = chunkSizes.get(i).intValue(); + } + else i = getImageCount(); + off += rawSize; + offsets.add(new Integer(off)); + } + } + else { + for (int i=0; i. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. * #L% */ diff --git a/components/formats-bsd/src/loci/formats/out/DicomWriter.java b/components/formats-bsd/src/loci/formats/out/DicomWriter.java index bb20ce45106..3aa822f437b 100644 --- a/components/formats-bsd/src/loci/formats/out/DicomWriter.java +++ b/components/formats-bsd/src/loci/formats/out/DicomWriter.java @@ -41,6 +41,7 @@ import java.rmi.dgc.VMID; import java.rmi.server.UID; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import loci.common.Constants; @@ -62,6 +63,12 @@ import loci.formats.in.MetadataOptions; import loci.formats.meta.IPyramidStore; import loci.formats.meta.MetadataRetrieve; +import loci.formats.tiff.IFD; +import loci.formats.tiff.PhotoInterp; +import loci.formats.tiff.TiffCompression; +import loci.formats.tiff.TiffConstants; +import loci.formats.tiff.TiffRational; +import loci.formats.tiff.TiffSaver; import ome.xml.model.enums.DimensionOrder; import ome.units.UNITS; @@ -83,6 +90,9 @@ public class DicomWriter extends FormatWriter { public static final String UID_ROOT_KEY = "dicom.uid_root"; public static final String UID_DEFAULT_ROOT = "1"; + /** Option for turning off TIFF metadata. */ + public static final String TIFF_KEY = "dicom.dual_personality"; + // see http://dicom.nema.org/medical/dicom/current/output/chtml/part06/chapter_A.html private static final String SOP_CLASS_UID_VALUE = "1.2.840.10008.5.1.4.1.1.77.1.6"; @@ -92,6 +102,8 @@ public class DicomWriter extends FormatWriter { private int[] pixelDataSize; private long[] transferSyntaxPointer; private long[] compressionMethodPointer; + private long[] nextIFDPointer; + private IFD[][] ifds; private long fileMetaLengthPointer; private int baseTileWidth = 0; private int baseTileHeight = 0; @@ -104,6 +116,11 @@ public class DicomWriter extends FormatWriter { private String instanceUIDValue; private String implementationUID; + private boolean bigTiff = false; + private TiffSaver tiffSaver; + + private Boolean validPixelCount = null; + // -- Constructor -- public DicomWriter() { @@ -115,6 +132,29 @@ public DicomWriter() { }; } + /** + * Sets whether or not BigTIFF files should be written. + * This flag is not reset when close() is called. + */ + public void setBigTiff(boolean bigTiff) { + FormatTools.assertId(currentId, false, 1); + this.bigTiff = bigTiff; + } + + /** + * Checks the writer's associated MetadataOptions to see + * if dual personality writing has been explicitly enabled + * or disabled. If the option is not set, the default + * is to return true, enabling dual personality writing. + */ + public boolean writeDualPersonality() { + MetadataOptions options = getMetadataOptions(); + if (options instanceof DynamicMetadataOptions) { + return ((DynamicMetadataOptions) options).getBoolean(TIFF_KEY, true); + } + return true; + } + // -- IFormatWriter API methods -- @Override @@ -148,6 +188,10 @@ public void saveBytes(int no, byte[] buf, int x, int y, int w, int h) { checkParams(no, buf, x, y, w, h); + int resolutionIndex = getIndex(series, resolution); + int thisTileWidth = tileWidth[resolutionIndex]; + int thisTileHeight = tileHeight[resolutionIndex]; + MetadataRetrieve r = getMetadataRetrieve(); if ((!(r instanceof IPyramidStore) || ((IPyramidStore) r).getResolutionCount(series) == 1) && @@ -155,10 +199,17 @@ public void saveBytes(int no, byte[] buf, int x, int y, int w, int h) { throw new FormatException("DicomWriter does not allow tiles for non-pyramid images"); } + else if (x % thisTileWidth != 0 || y % thisTileHeight != 0 || + (w != thisTileWidth && x + w != getSizeX()) || + (h != thisTileHeight && y + h != getSizeY())) + { + throw new FormatException("Tile too small, expected " + thisTileWidth + "x" + thisTileHeight + + ". Setting the tile size to " + getSizeX() + "x" + getSizeY() + " or smaller may work."); + } + checkPixelCount(false); boolean first = x == 0 && y == 0; boolean last = x + w == getSizeX() && y + h == getSizeY(); - int resolutionIndex = getIndex(series, resolution); // the compression type isn't supplied to the writer until // after setId is called, so metadata that indicates or @@ -170,6 +221,17 @@ public void saveBytes(int no, byte[] buf, int x, int y, int w, int h) out.seek(compressionMethodPointer[resolutionIndex]); out.writeBytes(getCompressionMethod()); + + // the corresponding IFD is expected to be null + // if dual personality writing is turned off + if (writeDualPersonality()) { + ifds[resolutionIndex][no].put(IFD.COMPRESSION, getTIFFCompression().getCode()); + + // see https://github.com/ome/bioformats/issues/3856 + if (getTIFFCompression() == TiffCompression.JPEG) { + ifds[resolutionIndex][no].put(IFD.PHOTOMETRIC_INTERPRETATION, PhotoInterp.Y_CB_CR.getCode()); + } + } } // TILED_SPARSE, so the tile coordinates must be written @@ -212,14 +274,16 @@ public void saveBytes(int no, byte[] buf, int x, int y, int w, int h) byte[] paddedBuf = null; + int thisTilePixels = thisTileWidth * thisTileHeight; + // pad the last row and column of tiles to match specified tile size - if ((x + w == getSizeX() && w < tileWidth[resolutionIndex]) || - (y + h == getSizeY() && h < tileHeight[resolutionIndex])) + if ((x + w == getSizeX() && w < thisTileWidth) || + (y + h == getSizeY() && h < thisTileHeight)) { if (interleaved || getSamplesPerPixel() == 1) { int srcRowLen = w * bytesPerPixel * getSamplesPerPixel(); - int destRowLen = tileWidth[resolutionIndex] * bytesPerPixel * getSamplesPerPixel(); - paddedBuf = new byte[tileHeight[resolutionIndex] * destRowLen]; + int destRowLen = thisTileWidth * bytesPerPixel * getSamplesPerPixel(); + paddedBuf = new byte[thisTileHeight * destRowLen]; for (int row=0; row 1 && interleaved; + options.interleaved = true; + + if (codec instanceof JPEG2000Codec) { + options = JPEG2000CodecOptions.getDefaultOptions(options); + ((JPEG2000CodecOptions) options).numDecompositionLevels = 0; + } byte[] compressed = codec.compress(paddedBuf, options); boolean pad = compressed.length % 2 == 1; @@ -274,6 +380,10 @@ public void saveBytes(int no, byte[] buf, int x, int y, int w, int h) writeTag(bot); } + if (tileByteCounts != null) { + tileByteCounts[tileIndex] = compressed.length; + } + DicomTag item = new DicomTag(ITEM, IMPLICIT); item.elementLength = compressed.length; if (pad) { @@ -281,6 +391,9 @@ public void saveBytes(int no, byte[] buf, int x, int y, int w, int h) } item.value = compressed; writeTag(item); + if (tileOffsets != null) { + tileOffsets[tileIndex] = out.getFilePointer() - compressed.length; + } if (pad) { out.writeByte(0); } @@ -291,6 +404,7 @@ public void saveBytes(int no, byte[] buf, int x, int y, int w, int h) writeTag(end); } } + } /* @see loci.formats.IFormatWriter#canDoStacks() */ @@ -322,6 +436,8 @@ public void setId(String id) throws FormatException, IOException { out.close(); } + checkPixelCount(true); + uids = new UIDCreator(); MetadataRetrieve r = getMetadataRetrieve(); @@ -345,6 +461,8 @@ public void setId(String id) throws FormatException, IOException { pixelDataSize = new int[totalFiles]; transferSyntaxPointer = new long[totalFiles]; compressionMethodPointer = new long[totalFiles]; + nextIFDPointer = new long[totalFiles]; + ifds = new IFD[totalFiles][]; planeOffsets = new PlaneOffset[totalFiles][]; tileWidth = new int[totalFiles]; @@ -366,9 +484,10 @@ public void setId(String id) throws FormatException, IOException { instanceUIDValue = uids.getUID(); resolution = res; - openFile(series, resolution); int resolutionIndex = getIndex(series, resolution); + ifds[resolutionIndex] = new IFD[getPlaneCount(pyramid)]; + ArrayList tags = new ArrayList(); DicomTag imageType = new DicomTag(IMAGE_TYPE, CS); @@ -397,6 +516,20 @@ public void setId(String id) throws FormatException, IOException { String pixelType = r.getPixelsType(pyramid).toString(); int bytesPerPixel = FormatTools.getBytesPerPixel(pixelType); int nChannels = getSamplesPerPixel(); + int sizeC = r.getPixelsSizeC(pyramid).getValue().intValue(); + int sizeT = r.getPixelsSizeT(pyramid).getValue().intValue(); + + // check the number of uncompressed pixel bytes in this resolution + // if we suspect that there will be more than 4 GB written (including tags/IFDs), + // automatically switch to BigTIFF for this and all subsequent resolutions + // writing BigTIFF even when not necessary is generally safer than + // trying to write plain TIFF for larger datasets + long rawPixelBytes = (long) width * height * bytesPerPixel * sizeZ * sizeC * sizeT; + if (rawPixelBytes >= TiffConstants.BIG_TIFF_CUTOFF) { + bigTiff = true; + } + + openFile(series, resolution); tileWidth[resolutionIndex] = getTileSizeX(); if (fullImage || tileWidth[resolutionIndex] <= 0) { @@ -458,7 +591,8 @@ public void setId(String id) throws FormatException, IOException { tags.add(highBit); DicomTag pixelRepresentation = new DicomTag(PIXEL_REPRESENTATION, US); - boolean isSigned = FormatTools.isSigned(FormatTools.pixelTypeFromString(pixelType)); + int pixelTypeCode = FormatTools.pixelTypeFromString(pixelType); + boolean isSigned = FormatTools.isSigned(pixelTypeCode); pixelRepresentation.value = new short[] {(short) (isSigned ? 1 : 0)}; tags.add(pixelRepresentation); @@ -912,6 +1046,60 @@ public int compare(DicomTag a, DicomTag b) { pixelData.elementLength = (int) 0xffffffff; writeTag(pixelData); pixelDataLengthPointer[resolutionIndex] = out.getFilePointer() - 4; + + if (writeDualPersonality()) { + // construct one IFD per plane + // saveBytes will fill in the tile offsets and byte counts + // close will write the IFDs to the file(s) + for (int plane=0; plane 1; + + IFD ifd = new IFD(); + ifd.put(IFD.LITTLE_ENDIAN, out.isLittleEndian()); + ifd.put(IFD.IMAGE_WIDTH, (long) width); + ifd.put(IFD.IMAGE_LENGTH, (long) height); + ifd.put(IFD.TILE_WIDTH, tileWidth[resolutionIndex]); + ifd.put(IFD.TILE_LENGTH, tileHeight[resolutionIndex]); + + // this is a placeholder, as the compression type isn't supplied + // until after setId + ifd.put(IFD.COMPRESSION, getTIFFCompression().getCode()); + + ifd.put(IFD.PLANAR_CONFIGURATION, 1); + + int sampleFormat = 1; + if (FormatTools.isFloatingPoint(pixelTypeCode)) { + sampleFormat = 3; + } + else if (FormatTools.isSigned(pixelTypeCode)) { + sampleFormat = 2; + } + + ifd.put(IFD.SAMPLE_FORMAT, sampleFormat); + + int[] bps = new int[rgb ? nChannels : 1]; + Arrays.fill(bps, FormatTools.getBytesPerPixel(pixelTypeCode) * 8); + ifd.put(IFD.BITS_PER_SAMPLE, bps); + + ifd.put(IFD.PHOTOMETRIC_INTERPRETATION, + rgb ? PhotoInterp.RGB.getCode() : PhotoInterp.BLACK_IS_ZERO.getCode()); + ifd.put(IFD.SAMPLES_PER_PIXEL, bps.length); + + ifd.put(IFD.SOFTWARE, FormatTools.CREATOR); + + int tileCount = tileCountX * tileCountY; + + ifd.put(IFD.TILE_BYTE_COUNTS, new long[tileCount]); + ifd.put(IFD.TILE_OFFSETS, new long[tileCount]); + + ifd.put(IFD.RESOLUTION_UNIT, 3); + ifd.put(IFD.X_RESOLUTION, getPhysicalSize(physicalX)); + ifd.put(IFD.Y_RESOLUTION, getPhysicalSize(physicalY)); + + ifds[resolutionIndex][plane] = ifd; + } + } } } setSeries(0); @@ -920,6 +1108,37 @@ public int compare(DicomTag a, DicomTag b) { /* @see loci.formats.FormatWriter#close() */ @Override public void close() throws IOException { + if (writeDualPersonality()) { + // write IFDs to the end of each file + + MetadataRetrieve r = getMetadataRetrieve(); + for (int pyramid=0; pyramid= 0 || return type; } + private void writeIFDs(int resIndex) throws IOException { + long ifdStart = out.getFilePointer(); + out.seek(nextIFDPointer[resIndex]); + if (bigTiff) { + out.writeLong(ifdStart); + } + else { + out.writeInt((int) ifdStart); + } + out.seek(ifdStart); + + for (int no=0; no - *
  • QTWriter.CODEC_CINEPAK
  • - *
  • QTWriter.CODEC_ANIMATION
  • - *
  • QTWriter.CODEC_H_263
  • - *
  • QTWriter.CODEC_SORENSON
  • - *
  • QTWriter.CODEC_SORENSON_3
  • - *
  • QTWriter.CODEC_MPEG_4
  • - *
  • QTWriter.CODEC_RAW
  • - * - */ - public void setCodec(int codec) { this.codec = codec; } - - /** - * Sets the quality of the encoded movie. - * @param quality Quality value:
      - *
    • QTWriter.QUALITY_LOW
    • - *
    • QTWriter.QUALITY_MEDIUM
    • - *
    • QTWriter.QUALITY_HIGH
    • - *
    • QTWriter.QUALITY_MAXIMUM
    • - *
    - */ - public void setQuality(int quality) { this.quality = quality; } - - // -- IFormatWriter API methods -- - - /** - * @see loci.formats.IFormatWriter#saveBytes(int, byte[], int, int, int, int) - */ - @Override - public void saveBytes(int no, byte[] buf, int x, int y, int w, int h) - throws FormatException, IOException - { - checkParams(no, buf, x, y, w, h); - MetadataRetrieve meta = getMetadataRetrieve(); - BufferedImage image = AWTImageTools.makeImage(buf, - interleaved, meta, series); - savePlane(no, image, x, y, w, h); - } - - /** - * @see loci.formats.IFormatWriter#savePlane(int, Object, int, int, int, int) - */ - @Override - public void savePlane(int no, Object plane, int x, int y, int w, int h) - throws FormatException, IOException - { - if (!(plane instanceof Image)) { - throw new IllegalArgumentException( - "Object to save must be a java.awt.Image"); - } - - if (tools == null || r == null) { - tools = new LegacyQTTools(); - r = tools.getUniverse(); - } - tools.checkQTLibrary(); - - BufferedImage img = AWTImageTools.makeBuffered((Image) plane); - - if (!initialized[series][no]) { - initialized[series][no] = true; - - try { - r.exec("QTSession.open()"); - width = img.getWidth(); - height = img.getHeight(); - r.setVar("path", currentId); - r.setVar("width", (float) width); - r.setVar("height", (float) height); - - r.exec("movFile = new QTFile(path)"); - r.exec("kMoviePlayer = StdQTConstants.kMoviePlayer"); - int resFlag = ((Integer) - r.exec("StdQTConstants.createMovieFileDontCreateResFile")).intValue(); - r.setVar("flags", resFlag); - r.exec("movie = Movie.createMovieFile(movFile, kMoviePlayer, flags)"); - r.setVar("timeScale", TIME_SCALE); - r.setVar("zero", 0); - r.setVar("zeroFloat", (float) 0); - r.exec("videoTrack = movie.addTrack(width, height, zeroFloat)"); - r.exec("videoMedia = new VideoMedia(videoTrack, timeScale)"); - r.exec("videoMedia.beginEdits()"); - - r.setVar("width", width); - r.setVar("height", height); - r.exec("bounds = new QDRect(zero, zero, width, height)"); - r.exec("gw = new QDGraphics(bounds)"); - - r.exec("pixMap = gw.getPixMap()"); - r.exec("pixSize = pixMap.getPixelSize()"); - r.setVar("codec", codec); - r.setVar("quality", quality); - - int rawImageSize = width * height * 4; - r.setVar("rawImageSize", rawImageSize); - - r.setVar("boolTrue", true); - r.exec("imageHandle = new QTHandle(rawImageSize, boolTrue)"); - r.exec("imageHandle.lock()"); - r.exec("compressedImage = RawEncodedImage.fromQTHandle(imageHandle)"); - - r.setVar("rate", 30); - - r.exec("seq = new CSequence(gw, bounds, pixSize, codec, " + - "CodecComponent.bestFidelityCodec, quality, quality, rate, null, " + - "zero)"); - - r.exec("imgDesc = seq.getDescription()"); - } - catch (ReflectException e) { - LOGGER.debug("", e); - throw new FormatException("Legacy QuickTime writer failed", e); - } - } - - numWritten++; - - try { - r.exec("pixelData = pixMap.getPixelData()"); - - r.exec("intsPerRow = pixelData.getRowBytes()"); - int intsPerRow = ((Integer) r.getVar("intsPerRow")).intValue() / 4; - - byte[][] px = AWTImageTools.getBytes(img); - - int[] pixels = new int[px[0].length]; - for (int i=0; i getNativeDataType() { - return Image.class; - } - - /* @see loci.formats.IFormatHandler#close() */ - @Override - public void close() throws IOException { - super.close(); - r = null; - numWritten = 0; - width = 0; - height = 0; - pixels2 = null; - } - -} diff --git a/components/formats-bsd/src/loci/formats/out/OMETiffWriter.java b/components/formats-bsd/src/loci/formats/out/OMETiffWriter.java index 19a14d0608b..eb4afa8fe55 100644 --- a/components/formats-bsd/src/loci/formats/out/OMETiffWriter.java +++ b/components/formats-bsd/src/loci/formats/out/OMETiffWriter.java @@ -77,6 +77,7 @@ public class OMETiffWriter extends TiffWriter { FormatTools.URL_OME_TIFF + ". -->"; public static final String COMPANION_KEY = "ometiff.companion"; + public static final String CREATOR_KEY = "ometiff.preserve_creator"; // -- Fields -- @@ -248,6 +249,12 @@ public void setId(String id) throws FormatException, IOException { } // -- OMETiff-specific methods -- + + /** + * Get the value of the {@link COMPANION_KEY} option. + * + * @return path to the companion file, or null if a companion file is not used + */ public String getCompanion() { MetadataOptions options = getMetadataOptions(); if (options instanceof DynamicMetadataOptions) { @@ -256,6 +263,26 @@ public String getCompanion() { return null; } + /** + * Get the value of the {@link CREATOR_KEY} option. + * This toggles whether or not the OME Creator attribute will be + * overwritten with the current Bio-Formats version. For input + * data that does not have a Creator attribute defined, this makes + * no difference. + * + * By default, returns false, i.e. the Creator will be overwritten + * if it exists. + * + * @return true if the Creator attribute should be preserved (if it exists) + */ + public boolean preserveCreator() { + MetadataOptions options = getMetadataOptions(); + if (options instanceof DynamicMetadataOptions) { + return ((DynamicMetadataOptions) options).getBoolean(CREATOR_KEY, false); + } + return false; + } + // -- Helper methods -- /** Gets the UUID corresponding to the given filename. */ @@ -295,7 +322,7 @@ private String getOMEXML(String file) throws FormatException, IOException { omeMeta.setUUID(uuid); OMEXMLMetadataRoot root = (OMEXMLMetadataRoot) omeMeta.getRoot(); - root.setCreator(FormatTools.CREATOR); + setCreator(root); String xml; try { @@ -318,10 +345,30 @@ private String getBinaryOnlyOMEXML( meta.setBinaryOnlyMetadataFile(new Location(companion).getName()); meta.setBinaryOnlyUUID(companionUUID); OMEXMLMetadataRoot root = (OMEXMLMetadataRoot) meta.getRoot(); - root.setCreator(FormatTools.CREATOR); + setCreator(root); return service.getOMEXML(meta); } + /** + * Set the Creator attribute on the given OME-XML root object. + * If the Creator was not previously set, it will be set to the + * current Bio-Formats version. + * If the Creator has been set already, the {@link CREATOR_KEY} + * option (via {@link preserveCreator}) is used to determine + * whether to overwrite the existing value with the Bio-Formats version. + * + * @param root OME-XML root object + */ + private void setCreator(OMEXMLMetadataRoot root) { + String creator = root.getCreator(); + if (!preserveCreator() || creator == null) { + if (creator != null) { + LOGGER.warn("Overwriting existing Creator attribute: {}", creator); + } + root.setCreator(FormatTools.CREATOR); + } + } + private void saveComment(String file, String xml) throws IOException { if (out != null) out.close(); out = new RandomAccessOutputStream(file); diff --git a/components/formats-bsd/src/loci/formats/out/OMEXMLWriter.java b/components/formats-bsd/src/loci/formats/out/OMEXMLWriter.java index c66b2a976cb..14d2691c565 100644 --- a/components/formats-bsd/src/loci/formats/out/OMEXMLWriter.java +++ b/components/formats-bsd/src/loci/formats/out/OMEXMLWriter.java @@ -53,6 +53,8 @@ import loci.formats.codec.JPEG2000Codec; import loci.formats.codec.JPEGCodec; import loci.formats.codec.ZlibCodec; +import loci.formats.in.MetadataOptions; +import loci.formats.in.DynamicMetadataOptions; import loci.formats.meta.MetadataRetrieve; import loci.formats.ome.OMEXMLMetadata; import loci.formats.services.OMEXMLService; @@ -69,6 +71,8 @@ public class OMEXMLWriter extends FormatWriter { // -- Fields -- + public static final String CREATOR_KEY = "omexml.preserve_creator"; + private List xmlFragments; private String currentFragment; private OMEXMLService service; @@ -104,7 +108,13 @@ public void setId(String id) throws FormatException, IOException { service.removeBinData(noBin); OMEXMLMetadataRoot root = (OMEXMLMetadataRoot) noBin.getRoot(); - root.setCreator(FormatTools.CREATOR); + String creator = root.getCreator(); + if (!preserveCreator() || creator == null) { + if (creator != null) { + LOGGER.warn("Overwriting existing Creator attribute: {}", creator); + } + root.setCreator(FormatTools.CREATOR); + } xml = service.getOMEXML(noBin); } catch (DependencyException de) { @@ -218,6 +228,28 @@ public int[] getPixelTypes(String codec) { return super.getPixelTypes(codec); } + // -- OMEXMLWriter-specific methods -- + + /** + * Get the value of the {@link CREATOR_KEY} option. + * This toggles whether or not the OME Creator attribute will be + * overwritten with the current Bio-Formats version. For input + * data that does not have a Creator attribute defined, this makes + * no difference. + * + * By default, returns false, i.e. the Creator will be overwritten + * if it exists. + * + * @return true if the Creator attribute should be preserved (if it exists) + */ + public boolean preserveCreator() { + MetadataOptions options = getMetadataOptions(); + if (options instanceof DynamicMetadataOptions) { + return ((DynamicMetadataOptions) options).getBoolean(CREATOR_KEY, false); + } + return false; + } + // -- Helper methods -- /** diff --git a/components/formats-bsd/src/loci/formats/out/QTWriter.java b/components/formats-bsd/src/loci/formats/out/QTWriter.java index 7a3ca43562c..6ee887df4e4 100644 --- a/components/formats-bsd/src/loci/formats/out/QTWriter.java +++ b/components/formats-bsd/src/loci/formats/out/QTWriter.java @@ -42,7 +42,6 @@ import loci.formats.FormatWriter; import loci.formats.MetadataTools; import loci.formats.codec.CompressionType; -import loci.formats.gui.LegacyQTTools; import loci.formats.meta.MetadataRetrieve; /** @@ -115,33 +114,13 @@ public class QTWriter extends FormatWriter { /** Number of padding bytes in each row. */ protected int pad; - /** Whether we need the legacy writer. */ - protected boolean needLegacy = false; - - /** Legacy QuickTime writer. */ - protected LegacyQTWriter legacy; - private int numWritten = 0; // -- Constructor -- public QTWriter() { super("QuickTime", "mov"); - LegacyQTTools tools = new LegacyQTTools(); - if (tools.canDoQT()) { - compressionTypes = new String[] { - CompressionType.UNCOMPRESSED.getCompression(), - // NB: Writing to Motion JPEG-B with QTJava seems to be broken. - /*"Motion JPEG-B",*/ - CompressionType.CINEPAK.getCompression(), - CompressionType.ANIMATION.getCompression(), - CompressionType.H_263.getCompression(), - CompressionType.SORENSON.getCompression(), - CompressionType.SORENSON_3.getCompression(), - CompressionType.MPEG_4.getCompression() - }; - } - else compressionTypes = new String[] { + compressionTypes = new String[] { CompressionType.UNCOMPRESSED.getCompression()}; } @@ -182,10 +161,6 @@ public void saveBytes(int no, byte[] buf, int x, int y, int w, int h) throws FormatException, IOException { checkParams(no, buf, x, y, w, h); - if (needLegacy) { - legacy.saveBytes(no, buf, x, y, w, h); - return; - } MetadataRetrieve r = getMetadataRetrieve(); @@ -204,10 +179,7 @@ public void saveBytes(int no, byte[] buf, int x, int y, int w, int h) initialized[series][no] = true; setCodec(); if (codec != CODEC_RAW) { - needLegacy = true; - legacy.setId(currentId); - legacy.saveBytes(no, buf, x, y, w, h); - return; + throw new FormatException("Codec is not supported, only uncompressed data is supported with this writer"); } // update the number of pixel bytes written @@ -229,7 +201,7 @@ public void saveBytes(int no, byte[] buf, int x, int y, int w, int h) // but needs to be reversed in QTReader byte[] tmp = new byte[buf.length]; - if (nChannels == 1 && !needLegacy) { + if (nChannels == 1) { for (int i=0; i 1 ? 0 : (4 - (width % 4)) % 4; - if (legacy == null) { - legacy = new LegacyQTWriter(); - legacy.setCodec(codec); - legacy.setMetadataRetrieve(r); - } offsets = new ArrayList(); created = (int) System.currentTimeMillis(); numBytes = 0; diff --git a/components/formats-bsd/src/loci/formats/out/TiffWriter.java b/components/formats-bsd/src/loci/formats/out/TiffWriter.java index 036923767df..9f0bc132cdc 100644 --- a/components/formats-bsd/src/loci/formats/out/TiffWriter.java +++ b/components/formats-bsd/src/loci/formats/out/TiffWriter.java @@ -44,6 +44,7 @@ import loci.formats.meta.MetadataRetrieve; import loci.formats.tiff.IFD; import loci.formats.tiff.TiffCompression; +import loci.formats.tiff.TiffConstants; import loci.formats.tiff.TiffParser; import loci.formats.tiff.TiffRational; import loci.formats.tiff.TiffSaver; @@ -72,13 +73,6 @@ public class TiffWriter extends FormatWriter { private static final String[] BIG_TIFF_SUFFIXES = {"tf2", "tf8", "btf"}; - /** - * Number of bytes at which to automatically switch to BigTIFF - * This is approximately 3.9 GB instead of 4 GB, - * to allow space for the IFDs. - */ - private static final long BIG_TIFF_CUTOFF = (long) 1024 * 1024 * 3990; - /** TIFF tiles must be of a height and width divisible by 16. */ private static final int TILE_GRANULARITY = 16; @@ -185,7 +179,7 @@ else if (compression == null || compression.equals(COMPRESSION_UNCOMPRESSED)) { totalBytes += (long)sizeX * (long)sizeY * (long)sizeZ * (long)sizeC * (long)sizeT * bpp; } - if (totalBytes >= BIG_TIFF_CUTOFF) { + if (totalBytes >= TiffConstants.BIG_TIFF_CUTOFF) { if (canDetectBigTiff) { LOGGER.info("Switching to BigTIFF (by file size)"); isBigTiff = true; diff --git a/components/formats-bsd/src/loci/formats/services/JPEGTurboServiceImpl.java b/components/formats-bsd/src/loci/formats/services/JPEGTurboServiceImpl.java index 49f82deff58..3231da2686b 100644 --- a/components/formats-bsd/src/loci/formats/services/JPEGTurboServiceImpl.java +++ b/components/formats-bsd/src/loci/formats/services/JPEGTurboServiceImpl.java @@ -105,8 +105,10 @@ public JPEGTurboServiceImpl() { logger = Logger.getLogger(NATIVE_LIB_CLASS); logger.setLevel(Level.SEVERE); if (!libraryLoaded) { - NativeLibraryUtil.loadNativeLibrary(TJ.class, "turbojpeg"); - libraryLoaded = true; + libraryLoaded = NativeLibraryUtil.loadNativeLibrary(TJ.class, "turbojpeg"); + if (!libraryLoaded) { + throw new RuntimeException("TurboJPEG could not be loaded"); + } } } diff --git a/components/formats-bsd/src/loci/formats/services/LuraWaveService.java b/components/formats-bsd/src/loci/formats/services/LuraWaveService.java deleted file mode 100644 index 1d43532fda4..00000000000 --- a/components/formats-bsd/src/loci/formats/services/LuraWaveService.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * #%L - * BSD implementations of Bio-Formats readers and writers - * %% - * Copyright (C) 2005 - 2017 Open Microscopy Environment: - * - Board of Regents of the University of Wisconsin-Madison - * - Glencoe Software, Inc. - * - University of Dundee - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package loci.formats.services; - -import java.io.IOException; -import java.io.InputStream; - -import loci.common.services.DependencyException; -import loci.common.services.Service; -import loci.common.services.ServiceException; - -/** - * - * @author callan - */ -public interface LuraWaveService extends Service { - - /** - * Overrides the license code to use when initializing the LuraWave decoder. - * By default the license code is loaded from the "lurawave.license" system - * property. - * @param license String license code. - */ - public void setLicenseCode(String license); - - /** - * Retrieves the current license code as a string. - * @return See above. - */ - public String getLicenseCode(); - - /** - * Wraps {@link com.luratech.lwf.lwfDecoder#lwfDecoder(InputStream, String, String)}. - * @throws IOException If parsing of the image header fails. - * @throws DependencyException If no license code was specified. - * @throws ServiceException If the license code is invalid. - */ - public void initialize(InputStream stream) - throws IOException, DependencyException, ServiceException; - - /** Wraps {@link com.luratech.lwf.lwfDecoder#getWidth()} */ - public int getWidth(); - - /** Wraps {@link com.luratech.lwf.lwfDecoder#getHeight()} */ - public int getHeight(); - - /** - * Wraps {@link com.luratech.lwf.lwfDecoder#decodeToMemoryGray8(byte[], int, int, int)}. - * @throws ServiceException If the license code is invalid. - */ - public void decodeToMemoryGray8(byte[] image, int limit, - int quality, int scale) - throws ServiceException; - - /** - * Wraps {@link com.luratech.lwf.lwfDecoder#decodeToMemoryGray16(short[], int, int, int, int, int, int, int, int, int, int)}. - * @throws ServiceException If the license code is invalid. - */ - public void decodeToMemoryGray16( - short[] image, int imageoffset, int limit, int quality, int scale, - int pdx, int pdy, int clip_x, int clip_y, int clip_w, int clip_h) - throws ServiceException; - -} diff --git a/components/formats-bsd/src/loci/formats/services/LuraWaveServiceImpl.java b/components/formats-bsd/src/loci/formats/services/LuraWaveServiceImpl.java deleted file mode 100644 index e034df296d8..00000000000 --- a/components/formats-bsd/src/loci/formats/services/LuraWaveServiceImpl.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * #%L - * BSD implementations of Bio-Formats readers and writers - * %% - * Copyright (C) 2005 - 2017 Open Microscopy Environment: - * - Board of Regents of the University of Wisconsin-Madison - * - Glencoe Software, Inc. - * - University of Dundee - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package loci.formats.services; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; - -import loci.common.services.AbstractService; -import loci.common.services.DependencyException; -import loci.common.services.ServiceException; - -import com.luratech.lwf.lwfDecoder; - -/** - * - * @author callan - */ -public class LuraWaveServiceImpl extends AbstractService - implements LuraWaveService { - - /** System property to check for the LuraWave license code. */ - public static final String LICENSE_PROPERTY = "lurawave.license"; - - /** Message displayed if the LuraWave LWF decoder library is not found. */ - public static final String NO_LURAWAVE_MSG = - "The LuraWave decoding library, lwf_jsdk2.6.jar, is required to decode " + - "this file.\r\nPlease make sure it is present in your classpath."; - - /** Message to display if no LuraWave license code is given. */ - public static final String NO_LICENSE_MSG = - "No LuraWave license code was specified.\r\nPlease set one in the " + - LICENSE_PROPERTY + " system property (e.g., with -D" + LICENSE_PROPERTY + - "=XXXX from the command line)."; - - /** Message to display if an invalid LuraWave license code is given. */ - public static final String INVALID_LICENSE_MSG = "Invalid license code: "; - - /** Identifying field in stub class. */ - public static final String STUB_FIELD = "IS_STUB"; - - /** LuraWave decoder delegate. */ - private transient Object delegate; - - /** License code. */ - private String license; - - /** - * Default constructor. - */ - public LuraWaveServiceImpl() throws DependencyException { - checkClassDependency(com.luratech.lwf.lwfDecoder.class); - try { - Field isStub = com.luratech.lwf.lwfDecoder.class.getField(STUB_FIELD); - if (isStub != null) { - throw new DependencyException(NO_LURAWAVE_MSG); - } - } - catch (NoSuchFieldException e) { } - } - - /* (non-Javadoc) - * @see loci.formats.services.LuraWaveService#setLicenseCode(java.lang.String) - */ - @Override - public void setLicenseCode(String license) { - this.license = license; - } - - /* (non-Javadoc) - * @see loci.formats.services.LuraWaveService#getLicenseCode() - */ - @Override - public String getLicenseCode() { - return license; - } - - /* (non-Javadoc) - * @see loci.formats.services.LuraWaveService#initialize(java.io.InputStream) - */ - @Override - public void initialize(InputStream stream) - throws IOException, DependencyException, ServiceException { - initLicense(); - try { - delegate = new lwfDecoder(stream, null, license); - } - catch (SecurityException e) { - throw new ServiceException(e); - } - } - - /* (non-Javadoc) - * @see loci.formats.services.LuraWaveService#getWidth() - */ - @Override - public int getWidth() { - return ((lwfDecoder) delegate).getWidth(); - } - - /* (non-Javadoc) - * @see loci.formats.services.LuraWaveService#getHeight() - */ - @Override - public int getHeight() { - return ((lwfDecoder) delegate).getHeight(); - } - - /* (non-Javadoc) - * @see loci.formats.services.LuraWaveService#decodeToMemoryGray8(byte[], int, int, int) - */ - @Override - public void decodeToMemoryGray8(byte[] image, int limit, - int quality, int scale) - throws ServiceException { - try { - ((lwfDecoder) delegate).decodeToMemoryGray8(image, limit, quality, scale); - } - catch (SecurityException e) { - throw new ServiceException(e); - } - } - - /* (non-Javadoc) - * @see loci.formats.services.LuraWaveService#decodeToMemoryGray16(short[], int, int, int, int, int, int, int, int, int, int) - */ - @Override - public void decodeToMemoryGray16( - short[] image, int imageoffset, int limit, int quality, int scale, - int pdx, int pdy, int clip_x, int clip_y, int clip_w, int clip_h) - throws ServiceException { - try { - ((lwfDecoder) delegate).decodeToMemoryGray16(image, imageoffset, limit, quality, scale, - pdx, pdy, clip_x, clip_y, clip_w, clip_h); - } - catch (SecurityException e) { - throw new ServiceException(e); - } - } - - private void initLicense() throws DependencyException { - if (license != null) return; // license already initialized - license = System.getProperty(LICENSE_PROPERTY); - if (license == null) throw new DependencyException(NO_LICENSE_MSG); - } - -} diff --git a/components/formats-bsd/src/loci/formats/tiff/IFD.java b/components/formats-bsd/src/loci/formats/tiff/IFD.java index 4fff9d2cf90..71ab6c02ccd 100644 --- a/components/formats-bsd/src/loci/formats/tiff/IFD.java +++ b/components/formats-bsd/src/loci/formats/tiff/IFD.java @@ -615,6 +615,9 @@ public int getPixelType() throws FormatException { case 24: return FormatTools.FLOAT; case 64: + if (bitFormat != 3) { + throw new FormatException("64-bit int data not supported"); + } return FormatTools.DOUBLE; case 32: if (bitFormat == 3) return FormatTools.FLOAT; diff --git a/components/formats-bsd/src/loci/formats/tiff/TiffCompression.java b/components/formats-bsd/src/loci/formats/tiff/TiffCompression.java index 8a4ef1d079c..46a8e0cba95 100644 --- a/components/formats-bsd/src/loci/formats/tiff/TiffCompression.java +++ b/components/formats-bsd/src/loci/formats/tiff/TiffCompression.java @@ -49,7 +49,6 @@ import loci.formats.codec.JPEGCodec; import loci.formats.codec.JPEGXRCodec; import loci.formats.codec.LZWCodec; -import loci.formats.codec.LuraWaveCodec; import loci.formats.codec.NikonCodec; import loci.formats.codec.PackbitsCodec; import loci.formats.codec.PassthroughCodec; @@ -195,7 +194,6 @@ public CodecOptions getCompressionCodecOptions(IFD ifd, CodecOptions opt) }, NIKON(34713, new NikonCodec(), "Nikon"), - LURAWAVE(65535, new LuraWaveCodec(), "LuraWave"), JPEGXR(22610, new JPEGXRCodec(), "JPEG-XR"), ZSTD(50000, new ZstdCodec(), "Zstandard"); diff --git a/components/formats-bsd/src/loci/formats/tiff/TiffConstants.java b/components/formats-bsd/src/loci/formats/tiff/TiffConstants.java index 06ef5704bfa..eb616c08396 100644 --- a/components/formats-bsd/src/loci/formats/tiff/TiffConstants.java +++ b/components/formats-bsd/src/loci/formats/tiff/TiffConstants.java @@ -56,6 +56,13 @@ public final class TiffConstants { public static final int LITTLE = 0x49; public static final int BIG = 0x4d; + /** + * Number of bytes at which to automatically switch to BigTIFF + * This is approximately 3.9 GB instead of 4 GB, + * to allow space for the IFDs. + */ + public static final long BIG_TIFF_CUTOFF = (long) 1024 * 1024 * 3990; + // -- Constructor -- private TiffConstants() { } diff --git a/components/formats-bsd/src/loci/formats/tiff/TiffParser.java b/components/formats-bsd/src/loci/formats/tiff/TiffParser.java index 809bd5d3356..9658bfd21fa 100644 --- a/components/formats-bsd/src/loci/formats/tiff/TiffParser.java +++ b/components/formats-bsd/src/loci/formats/tiff/TiffParser.java @@ -546,6 +546,9 @@ public Object getIFDValue(TiffIFDEntry entry) throws IOException { offset &= 0xffffffffL; offset += 0x100000000L; } + if (offset >= in.length()) { + return null; + } in.seek(offset); } diff --git a/components/formats-bsd/src/loci/formats/tiff/TiffSaver.java b/components/formats-bsd/src/loci/formats/tiff/TiffSaver.java index 36566e3c046..886d5b9555a 100644 --- a/components/formats-bsd/src/loci/formats/tiff/TiffSaver.java +++ b/components/formats-bsd/src/loci/formats/tiff/TiffSaver.java @@ -506,10 +506,38 @@ else if (isTiled) { // Note synchronization here is via writeImage private ByteArrayHandle ifdBuffer = new ByteArrayHandle(); + /** + * Write the given IFD to the open stream. + * The provided offset will be used as the offset to the next IFD, + * and can be a placeholder value. + * + * @param ifd the complete IFD to be written + * @param nextOffset the offset of the next IFD to be written + */ public void writeIFD(IFD ifd, long nextOffset) throws FormatException, IOException { + writeIFD(ifd, nextOffset, false); + } + /** + * Write the given IFD to the open stream. + * The provided offset will be used as the offset to the next IFD, + * unless offset calculation is requested. + * If offset calculation is requested, the next IFD's offset will be + * set so that it immediately follows the current IFD. + * Don't enable offset calculation when writing the last IFD, + * unless the offset will later be overwritten with 0. + * + * @param ifd the complete IFD to be written + * @param nextOffset the offset of the next IFD to be written (if known) + * @param calculateOffset true if nextOffset should be ignored, and the + * next IFD should be assumed to immediately follow + * this one + */ + public void writeIFD(IFD ifd, long nextOffset, boolean calculateOffset) + throws FormatException, IOException + { TreeSet keys = new TreeSet(ifd.keySet()); keys.remove(Integer.valueOf(IFD.LITTLE_ENDIAN)); keys.remove(Integer.valueOf(IFD.BIG_TIFF)); @@ -535,6 +563,11 @@ public void writeIFD(IFD ifd, long nextOffset) writeIFDValue(extraStream, ifdBytes + fp, key.intValue(), value); } if (bigTiff) out.seek(out.getFilePointer()); + + if (calculateOffset) { + nextOffset = fp + ifdBytes + extra.length(); + } + writeIntValue(out, nextOffset); out.write(extra.getBytes(), 0, (int) extra.length()); @@ -989,7 +1022,13 @@ private void makeValidIFD(IFD ifd, int pixelType, int nChannels) { pi = PhotoInterp.RGB_PALETTE; } else if (nChannels == 3) { - pi = PhotoInterp.RGB; + if (ifd.getIFDValue(IFD.COMPRESSION).equals(TiffCompression.JPEG.getCode())) { + // see https://github.com/ome/bioformats/issues/3856 + pi = PhotoInterp.Y_CB_CR; + } + else { + pi = PhotoInterp.RGB; + } } ifd.putIFDValue(IFD.PHOTOMETRIC_INTERPRETATION, pi.getCode()); diff --git a/components/formats-bsd/test/loci/formats/utests/CompressDecompressTest.java b/components/formats-bsd/test/loci/formats/utests/CompressDecompressTest.java index 2d89866a46a..5c979725f70 100644 --- a/components/formats-bsd/test/loci/formats/utests/CompressDecompressTest.java +++ b/components/formats-bsd/test/loci/formats/utests/CompressDecompressTest.java @@ -46,7 +46,6 @@ * Not yet supported: * Nikon * PackBits - * LuraWave * * @author Jean-Marie Burel */ diff --git a/components/formats-bsd/test/loci/formats/utests/MemoizerTest.java b/components/formats-bsd/test/loci/formats/utests/MemoizerTest.java index 4bbe108c57a..608823470e9 100644 --- a/components/formats-bsd/test/loci/formats/utests/MemoizerTest.java +++ b/components/formats-bsd/test/loci/formats/utests/MemoizerTest.java @@ -120,11 +120,18 @@ public void tearDown() throws Exception { recursiveDeleteOnExit(idDir); } + @Test public void testDefaultConstructor() throws Exception { Memoizer memoizer = new Memoizer(); checkMemoFile(memoizer.getMemoFile(id)); } + @Test + public void testNullReader() throws Exception { + Memoizer memoizer = new Memoizer(null); + checkMemoFile(memoizer.getMemoFile(id)); + } + @Test public void testConstructorTimeElapsed() throws Exception { Memoizer memoizer = new Memoizer(0); @@ -239,6 +246,23 @@ public void testRelocate() throws Exception { recursiveDeleteOnExit(newidDir); } + @Test + public void testDeleteMemo() throws Exception { + // Create an in-place memo file + Memoizer memoizer = new Memoizer(reader, 0); + memoizer.setId(id); + memoizer.close(); + assertFalse(memoizer.isLoadedFromMemo()); + assertTrue(memoizer.isSavedToMemo()); + + // attempt to delete the memo file, and make sure it's really gone + File currentMemoFile = memoizer.getMemoFile(); + assertTrue(currentMemoFile.exists()); + boolean success = memoizer.deleteMemo(); + assertTrue(success); + assertFalse(currentMemoFile.exists()); + } + @Test public void testWrappedReader() throws Exception { Memoizer memoizer = new Memoizer(reader, 0); diff --git a/components/formats-bsd/test/loci/formats/utests/tiff/TiffCompressionCompressTest.java b/components/formats-bsd/test/loci/formats/utests/tiff/TiffCompressionCompressTest.java index 42ebb6204ed..95ccbd30592 100644 --- a/components/formats-bsd/test/loci/formats/utests/tiff/TiffCompressionCompressTest.java +++ b/components/formats-bsd/test/loci/formats/utests/tiff/TiffCompressionCompressTest.java @@ -181,13 +181,6 @@ public void testNIKON() throws FormatException, IOException { compression.compress(data, options); } - @Test(expectedExceptions={ FormatException.class }) - public void testLURAWAVE() throws FormatException, IOException { - TiffCompression compression = TiffCompression.LURAWAVE; - CodecOptions options = compression.getCompressionCodecOptions(ifd); - compression.compress(data, options); - } - @Test(enabled=true) public void testJPEG_2000_ResetQuality() throws FormatException, IOException { TiffCompression compression = TiffCompression.JPEG_2000; diff --git a/components/formats-bsd/test/loci/formats/utests/tiff/TiffCompressionDecompressTest.java b/components/formats-bsd/test/loci/formats/utests/tiff/TiffCompressionDecompressTest.java index b5975436b74..8d8a189ab77 100644 --- a/components/formats-bsd/test/loci/formats/utests/tiff/TiffCompressionDecompressTest.java +++ b/components/formats-bsd/test/loci/formats/utests/tiff/TiffCompressionDecompressTest.java @@ -181,9 +181,4 @@ public void testNIKON() throws FormatException, IOException { assertNotNull(compression.decompress(DATA, options)); } - @Test(expectedExceptions={ FormatException.class }) - public void testLURAWAVE() throws FormatException, IOException { - TiffCompression compression = TiffCompression.LURAWAVE; - assertNotNull(compression.decompress(DATA, OPTIONS)); - } } diff --git a/components/formats-bsd/test/spec/AbstractTest.java b/components/formats-bsd/test/spec/AbstractTest.java index c2130923bc8..b5444a77692 100644 --- a/components/formats-bsd/test/spec/AbstractTest.java +++ b/components/formats-bsd/test/spec/AbstractTest.java @@ -1,8 +1,11 @@ /* * #%L - * Tests for OME-XML specification classes. + * BSD implementations of Bio-Formats readers and writers * %% - * Copyright (C) 2010-2013 Glencoe Software, Inc. + * Copyright (C) 2005 - 2017 Open Microscopy Environment: + * - Board of Regents of the University of Wisconsin-Madison + * - Glencoe Software, Inc. + * - University of Dundee * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -24,10 +27,6 @@ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are - * those of the authors and should not be interpreted as representing official - * policies, either expressed or implied, of any organization. * #L% */ diff --git a/components/formats-gpl/pom.xml b/components/formats-gpl/pom.xml index a92b3652f65..71c926459de 100644 --- a/components/formats-gpl/pom.xml +++ b/components/formats-gpl/pom.xml @@ -8,7 +8,7 @@ ome pom-bio-formats - 6.12.1-SNAPSHOT + 7.1.0-SNAPSHOT ../.. @@ -115,16 +115,6 @@ slf4j-api ${slf4j.version}
    - - woolz - JWlz - 1.4.0 - - - joda-time - joda-time - 2.2 - com.esotericsoftware kryo @@ -163,7 +153,7 @@ org.json json - 20090211 + 20231013 org.xerial diff --git a/components/formats-gpl/src/loci/formats/in/AFIReader.java b/components/formats-gpl/src/loci/formats/in/AFIReader.java index 7ea0672338d..53a83f8aa1a 100644 --- a/components/formats-gpl/src/loci/formats/in/AFIReader.java +++ b/components/formats-gpl/src/loci/formats/in/AFIReader.java @@ -156,7 +156,7 @@ public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) return reader[channel].openBytes(index, buf, x, y, w, h); } else if (diff > 0) { - Arrays.fill(buf, (byte) 0); + Arrays.fill(buf, getFillColor()); byte[] tmp = reader[channel].openBytes(index, x, y, w, h); for (int i=0, dest=0; i 61000) { in.seek(PIXEL_OFFSET - 196); - while (!in.readString(3).equals("scn")) { - in.seek(in.getFilePointer() - 2); + while (!in.readString(5).equals("scn0x")) { + in.seek(in.getFilePointer() - 4); + } + + in.skipBytes(69); + + // check byte indicates presence of additional metadata + // possibly specific to cropped images? + int check = in.read(); + in.skipBytes(19); + if (check != 0) { + in.skipBytes(in.readShort() - 2); } - in.skipBytes(91); int len = in.readShort(); in.skipBytes(len); in.skipBytes(32); diff --git a/components/formats-gpl/src/loci/formats/in/CV7000Reader.java b/components/formats-gpl/src/loci/formats/in/CV7000Reader.java index 9da19ebf004..47029608dc3 100644 --- a/components/formats-gpl/src/loci/formats/in/CV7000Reader.java +++ b/components/formats-gpl/src/loci/formats/in/CV7000Reader.java @@ -92,6 +92,8 @@ public class CV7000Reader extends FormatReader { private String startTime, endTime; private ArrayList extraFiles; + private transient Map acquiredWells = new HashMap(); + // -- Constructor -- /** Constructs a new Yokogawa CV7000 reader. */ @@ -130,7 +132,7 @@ public String[] getUsedFiles(boolean noPixels) { ArrayList files = new ArrayList(); files.add(new Location(currentId).getAbsolutePath()); for (String file : allFiles) { - if (!files.contains(file) && (!noPixels || !checkSuffix(file, "tif"))) { + if (file != null && !files.contains(file) && (!noPixels || !checkSuffix(file, "tif"))) { files.add(file); } } @@ -176,7 +178,7 @@ public String[] getSeriesUsedFiles(boolean noPixels) { } files.addAll(extraFiles); for (String file : allFiles) { - if (!checkSuffix(file, "tif") && !(new Location(file).isDirectory())) { + if (file != null && !checkSuffix(file, "tif") && !(new Location(file).isDirectory())) { files.add(file); } } @@ -206,6 +208,7 @@ public void close(boolean fileOnly) throws IOException { endTime = null; reversePlaneLookup = null; extraFiles = null; + acquiredWells.clear(); } } @@ -218,7 +221,7 @@ public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) { FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h); - Arrays.fill(buf, (byte) 0); + Arrays.fill(buf, getFillColor()); Plane p = lookupPlane(getSeries(), no); LOGGER.trace("series = {}, no = {}, file = {}", series, no, p == null ? null : p.file); if (p != null && p.file != null) { @@ -242,7 +245,13 @@ protected void initFile(String id) throws FormatException, IOException { allFiles = parent.list(true); Arrays.sort(allFiles); for (int i=0; i uniqueWells = new HashSet(); + HashSet uniqueChannels = new HashSet(); for (Plane p : planeData) { if (p != null) { + if (!isWellAcquired(p.field.row, p.field.column)) { + continue; + } + p.channelIndex = getChannelIndex(p); int wellIndex = p.field.row * plate.getPlateColumns() + p.field.column; @@ -327,12 +341,17 @@ public int compare(Channel c1, Channel c2) { if (p.z < m.minZ) { m.minZ = p.z; } + + // min and max channel indexes not currently used, + // but continue to calculate for completeness + // they may be needed in future CV7000/8000 work if (p.channelIndex > m.maxC) { m.maxC = p.channelIndex; } if (p.channelIndex < m.minC) { m.minC = p.channelIndex; } + uniqueChannels.add(p.channelIndex); if (p.field.field >= fields) { fields = p.field.field + 1; @@ -354,6 +373,9 @@ public int compare(Channel c1, Channel c2) { Arrays.sort(wells); reversePlaneLookup = new int[realWells * fields][]; + Integer[] channelIndexes = uniqueChannels.toArray(new Integer[uniqueChannels.size()]); + Arrays.sort(channelIndexes); + for (int i=0; i 0) { core.add(new CoreMetadata(core.get(0))); @@ -363,7 +385,7 @@ public int compare(Channel c1, Channel c2) { MinMax m = minMax.get(wellIndex); core.get(i).sizeZ = (m.maxZ - m.minZ) + 1; core.get(i).sizeT = (m.maxT - m.minT) + 1; - core.get(i).sizeC = reader.getSizeC() * ((m.maxC - m.minC) + 1); + core.get(i).sizeC = reader.getSizeC() * uniqueChannels.size(); core.get(i).imageCount = core.get(i).sizeZ * core.get(i).sizeT * (core.get(i).sizeC / reader.getSizeC()); reversePlaneLookup[i] = new int[core.get(i).imageCount]; @@ -376,7 +398,17 @@ public int compare(Channel c1, Channel c2) { extraFiles = new ArrayList(); for (int i=0; i-Dlurawave.license=XXXX on the command line). */ public class FlexReader extends FormatReader { @@ -256,7 +254,7 @@ public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) factor = 1d; } else { - Arrays.fill(buf, (byte) 0); + Arrays.fill(buf, getFillColor()); return buf; } } @@ -1234,8 +1232,14 @@ private void groupFiles(String[] fileList, MetadataStore store) firstIFD = parser.getFirstIFD(); ifdCount = parser.getIFDOffsets().length; } - boolean compressed = - firstIFD.getCompression() != TiffCompression.UNCOMPRESSED; + Boolean compressed = true; + try { + compressed = + firstIFD.getCompression() != TiffCompression.UNCOMPRESSED; + } + catch (EnumException e) { + LOGGER.trace("Could not get compression for " + file, e); + } if (firstCompressed == null) { firstCompressed = compressed; firstIFDCount = ifdCount; @@ -1341,8 +1345,13 @@ private void groupFiles(String[] fileList, MetadataStore store) LOGGER.info("Parsing IFDs for well {}{}", FormatTools.getWellRowName(row), col + 1); IFD firstIFD = tp.getFirstIFD(); - compressed = - firstIFD.getCompression() != TiffCompression.UNCOMPRESSED; + try { + compressed = + firstIFD.getCompression() != TiffCompression.UNCOMPRESSED; + } + catch (EnumException e) { + LOGGER.trace("Could not get compression", e); + } if (compressed || firstIFD.getStripOffsets()[0] == 16 || firstIFD.getStripOffsets().length == 1) diff --git a/components/formats-gpl/src/loci/formats/in/GatanReader.java b/components/formats-gpl/src/loci/formats/in/GatanReader.java index 9029b59eab0..ae4e99caebf 100644 --- a/components/formats-gpl/src/loci/formats/in/GatanReader.java +++ b/components/formats-gpl/src/loci/formats/in/GatanReader.java @@ -347,8 +347,15 @@ else if (pixelSizes.size() == 4) { for (String token : scopeInfo) { token = token.trim(); if (token.startsWith("Mode")) { - token = token.substring(token.indexOf(' ')).trim(); - mode = token.substring(0, token.indexOf(' ')).trim(); + if (token.indexOf(' ') > 0) { + token = token.substring(token.indexOf(' ')).trim(); + } + if (token.indexOf(' ') > 0) { + mode = token.substring(0, token.indexOf(' ')).trim(); + } + else { + mode = token; + } if (mode.equals("TEM")) mode = "Other"; } } diff --git a/components/formats-gpl/src/loci/formats/in/GelReader.java b/components/formats-gpl/src/loci/formats/in/GelReader.java index 37ed73714f7..1d0a2b36d41 100644 --- a/components/formats-gpl/src/loci/formats/in/GelReader.java +++ b/components/formats-gpl/src/loci/formats/in/GelReader.java @@ -90,7 +90,11 @@ public GelReader() { public boolean isThisType(RandomAccessInputStream stream) throws IOException { TiffParser parser = new TiffParser(stream); parser.setDoCaching(false); - IFD ifd = parser.getFirstIFD(); + long[] offsets = parser.getIFDOffsets(); + if (offsets.length == 0 || offsets.length > 2) { + return false; + } + IFD ifd = parser.getIFD(offsets[0]); if (ifd == null) return false; return ifd.containsKey(MD_FILETAG); } diff --git a/components/formats-gpl/src/loci/formats/in/IonpathMIBITiffReader.java b/components/formats-gpl/src/loci/formats/in/IonpathMIBITiffReader.java index 88ad08268e8..76a3e9d7440 100644 --- a/components/formats-gpl/src/loci/formats/in/IonpathMIBITiffReader.java +++ b/components/formats-gpl/src/loci/formats/in/IonpathMIBITiffReader.java @@ -11,12 +11,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program 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 this program. If not, see * . @@ -130,13 +130,10 @@ protected void initStandardMetadata() throws FormatException, IOException { jsonDescription = new JSONObject(imageDescription); imageType = jsonDescription.getString("image.type"); if (imageType.equals("SIMS")) { - String mass = jsonDescription.getString("channel.mass"); - if (mass == null) { - throw new FormatException("Channel masses are mandatory."); - } + Double mass = jsonDescription.getDouble("channel.mass"); String target = jsonDescription.getString("channel.target"); - channelIDs.add(mass); - channelNames.add(target != null && target != "null" ? target : mass); + channelIDs.add(mass.toString()); + channelNames.add(target != null && target != "null" ? target : mass.toString()); } } catch (JSONException e) { throw new FormatException("Unexpected format in SIMS description JSON."); @@ -166,7 +163,7 @@ protected void initStandardMetadata() throws FormatException, IOException { while (keySet.hasNext()) { String key = (String) keySet.next(); if (key.startsWith("mibi.")) { - simsDescription.put(key, jsonDescription.getString(key)); + simsDescription.put(key, jsonDescription.get(key).toString()); } } } catch (JSONException e) { diff --git a/components/formats-gpl/src/loci/formats/in/IvisionReader.java b/components/formats-gpl/src/loci/formats/in/IvisionReader.java index 4de6677c9d6..22b758de9b5 100644 --- a/components/formats-gpl/src/loci/formats/in/IvisionReader.java +++ b/components/formats-gpl/src/loci/formats/in/IvisionReader.java @@ -94,7 +94,12 @@ public boolean isThisType(RandomAccessInputStream stream) throws IOException { String version = stream.readString(3); try { Double.parseDouble(version); - return version.indexOf('.') != -1 && version.indexOf('-') == -1; + boolean validVersion = version.indexOf('.') != -1 && version.indexOf('-') == -1; + boolean validPatch = Character.isAlphabetic(stream.read()); + stream.skipBytes(1); + int dataType = stream.read(); + boolean validType = dataType >= 0 && dataType <= 8; + return validVersion && validPatch && validType; } catch (NumberFormatException e) { } return false; diff --git a/components/formats-gpl/src/loci/formats/in/LIFReader.java b/components/formats-gpl/src/loci/formats/in/LIFReader.java index b6275cbffb7..33e342ee3a8 100644 --- a/components/formats-gpl/src/loci/formats/in/LIFReader.java +++ b/components/formats-gpl/src/loci/formats/in/LIFReader.java @@ -322,8 +322,8 @@ public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) int index = getTileIndex(series); if (index >= offsets.size()) { - // truncated file; imitate LAS AF and return black planes - Arrays.fill(buf, (byte) 0); + // truncated file; imitate LAS AF and return blank planes + Arrays.fill(buf, getFillColor()); return buf; } @@ -339,8 +339,8 @@ public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) if ((getSizeX() % 4) == 0) bytesToSkip = 0; if (offset + (planeSize + bytesToSkip * getSizeY()) * no >= in.length()) { - // truncated file; imitate LAS AF and return black planes - Arrays.fill(buf, (byte) 0); + // truncated file; imitate LAS AF and return blank planes + Arrays.fill(buf, getFillColor()); return buf; } @@ -1131,6 +1131,10 @@ private void translateMetadata(Element root) throws FormatException { } NodeList images = getNodes(realRoot, "Image"); + if (images == null) { + throw new FormatException("No images found. This file is not valid."); + } + List imageNodes = new ArrayList(); Long[] oldOffsets = null; if (images.getLength() > offsets.size()) { diff --git a/components/formats-gpl/src/loci/formats/in/LOFReader.java b/components/formats-gpl/src/loci/formats/in/LOFReader.java index cebcfac6398..fccda2a1622 100644 --- a/components/formats-gpl/src/loci/formats/in/LOFReader.java +++ b/components/formats-gpl/src/loci/formats/in/LOFReader.java @@ -276,8 +276,8 @@ public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) throws F int tileIndex = getTileIndex(series); if (tileIndex >= offsets.size()) { - // truncated file; imitate LAS AF and return black planes - Arrays.fill(buf, (byte) 0); + // truncated file; imitate LAS AF and return blank planes + Arrays.fill(buf, getFillColor()); return buf; } @@ -294,8 +294,8 @@ public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) throws F bytesToSkip = 0; if (offset + (planeSize + bytesToSkip * getSizeY()) * no >= in.length()) { - // truncated file; imitate LAS AF and return black planes - Arrays.fill(buf, (byte) 0); + // truncated file; imitate LAS AF and return blank planes + Arrays.fill(buf, getFillColor()); return buf; } @@ -530,4 +530,4 @@ public void setIdWithMetadata(String id, XlifDocument xml) throws FormatExceptio associatedXmlDoc = xml; super.setId(id); } -} \ No newline at end of file +} diff --git a/components/formats-gpl/src/loci/formats/in/LegacyND2Reader.java b/components/formats-gpl/src/loci/formats/in/LegacyND2Reader.java deleted file mode 100644 index 9211b9cab04..00000000000 --- a/components/formats-gpl/src/loci/formats/in/LegacyND2Reader.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * #%L - * OME Bio-Formats package for reading and converting biological file formats. - * %% - * Copyright (C) 2005 - 2017 Open Microscopy Environment: - * - Board of Regents of the University of Wisconsin-Madison - * - Glencoe Software, Inc. - * - University of Dundee - * %% - * This program 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 2 of the - * License, or (at your option) any later version. - * - * This program 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 this program. If not, see - * . - * #L% - */ - -package loci.formats.in; - -import java.io.IOException; - -import loci.formats.CoreMetadata; -import loci.formats.FormatException; -import loci.formats.FormatReader; -import loci.formats.FormatTools; -import loci.formats.MetadataTools; -import loci.formats.MissingLibraryException; -import loci.formats.meta.MetadataStore; - -/** - * LegacyND2Reader is a file format reader for Nikon ND2 files that uses - * the Nikon ND2 SDK - it is only usable on Windows machines. - */ -public class LegacyND2Reader extends FormatReader { - - // -- Constants -- - - /** Modality types. */ - private static final int WIDE_FIELD = 0; - private static final int BRIGHT_FIELD = 1; - private static final int LASER_SCAN_CONFOCAL = 2; - private static final int SPIN_DISK_CONFOCAL = 3; - private static final int SWEPT_FIELD_CONFOCAL = 4; - private static final int MULTI_PHOTON = 5; - - private static final String URL_NIKON_ND2 = - "https://docs.openmicroscopy.org/bio-formats/" + FormatTools.VERSION + - "/formats/nikon-nis-elements-nd2.html"; - private static final String NO_NIKON_MSG = "Nikon ND2 library not found. " + - "Please see " + URL_NIKON_ND2 + " for details."; - - // -- Static initializers -- - - private static boolean libraryFound = true; - - static { - try { - System.loadLibrary("LegacyND2Reader"); - } - catch (UnsatisfiedLinkError e) { - LOGGER.trace(NO_NIKON_MSG, e); - libraryFound = false; - } - catch (SecurityException e) { - LOGGER.warn("Insufficient permission to load native library", e); - } - } - - // -- Constructor -- - - public LegacyND2Reader() { - super("Nikon ND2 (Legacy)", new String[] {"jp2", "nd2"}); - domains = new String[] {FormatTools.LM_DOMAIN}; - } - - // -- IFormatReader API methods -- - - /* @see IFormatReader#isThisType(String, boolean) */ - @Override - public boolean isThisType(String file, boolean open) { - return libraryFound && super.isThisType(file, open); - } - - /** - * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int) - */ - @Override - public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) - throws FormatException, IOException - { - FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h); - - int[] zct = FormatTools.getZCTCoords(this, no); - int bpc = FormatTools.getBytesPerPixel(getPixelType()); - byte[] b = new byte[FormatTools.getPlaneSize(this)]; - - getImage(b, getSeries(), zct[0], zct[1], zct[2]); - - int pixel = bpc * getRGBChannelCount(); - int rowLen = w * pixel; - for (int row=0; row= in.length() - in.getFilePointer()) { + return; + } String key = in.readString(keyLength); in.skipBytes(4); diff --git a/components/formats-gpl/src/loci/formats/in/MetamorphTiffReader.java b/components/formats-gpl/src/loci/formats/in/MetamorphTiffReader.java index 2c73e82e9c1..072d13a2224 100644 --- a/components/formats-gpl/src/loci/formats/in/MetamorphTiffReader.java +++ b/components/formats-gpl/src/loci/formats/in/MetamorphTiffReader.java @@ -282,7 +282,7 @@ else if (!label.equals(stageLabel)) { xPositions.add(x); yPositions.add(y); } - else { + else if (x != null && y != null) { final Length previousX = xPositions.get(xPositions.size() - 1); final Length previousY = yPositions.get(yPositions.size() - 1); diff --git a/components/formats-gpl/src/loci/formats/in/ND2Reader.java b/components/formats-gpl/src/loci/formats/in/ND2Reader.java index 2f99ff9d9e8..01e7ce6ccdd 100644 --- a/components/formats-gpl/src/loci/formats/in/ND2Reader.java +++ b/components/formats-gpl/src/loci/formats/in/ND2Reader.java @@ -25,38 +25,2817 @@ package loci.formats.in; -import loci.formats.DelegateReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import java.util.TreeMap; + +import loci.common.ByteArrayHandle; +import loci.common.Constants; +import loci.common.DataTools; +import loci.common.Location; +import loci.common.RandomAccessInputStream; +import loci.common.xml.XMLTools; + +import loci.formats.CoreMetadata; +import loci.formats.CoreMetadataList; +import loci.formats.FormatException; import loci.formats.FormatTools; +import loci.formats.ImageTools; +import loci.formats.MetadataTools; +import loci.formats.SubResolutionFormatReader; +import loci.formats.codec.Codec; +import loci.formats.codec.CodecOptions; +import loci.formats.codec.JPEG2000Codec; +import loci.formats.codec.ZlibCodec; +import loci.formats.meta.MetadataStore; + +import ome.xml.model.primitives.Color; + +import ome.units.quantity.ElectricPotential; +import ome.units.quantity.Frequency; +import ome.units.quantity.Length; +import ome.units.quantity.Temperature; +import ome.units.quantity.Time; +import ome.units.UNITS; /** * ND2Reader is the file format reader for Nikon ND2 files. - * It does not read files directly, but chooses which ND2 reader is - * more appropriate. + * The JAI ImageIO library is required to use this reader; it is available from + * http://jai-imageio.dev.java.net. Note that JAI ImageIO is bundled with a + * version of the JJ2000 library, so it is important that either: + * (1) the JJ2000 jar file is *not* in the classpath; or + * (2) the JAI jar file precedes JJ2000 in the classpath. * - * @see NativeND2Reader - * @see LegacyND2Reader + * Thanks to Tom Caswell for additions to the ND2 metadata parsing logic. */ -public class ND2Reader extends DelegateReader { + +public class ND2Reader extends SubResolutionFormatReader { + + // -- Constants -- + + public static final long ND2_MAGIC_BYTES_1 = 0xdacebe0aL; + public static final long ND2_MAGIC_BYTES_2 = 0x6a502020L; + private static final int BUFFER_SIZE = 32 * 1024; + + /** Legacy chunkmap option key will be removed in Bio-Formats 8.0.0. */ + @Deprecated + public static final String USE_CHUNKMAP_LEGACY_KEY = "nativend2.chunkmap"; + public static final String USE_CHUNKMAP_KEY = "nd2.chunkmap"; + public static final boolean USE_CHUNKMAP_DEFAULT = true; + + // -- Fields -- + + /** Array of image offsets. */ + private long[][] offsets; + + /** Whether or not the pixel data is compressed using JPEG 2000. */ + private boolean isJPEG; + + /** Codec to use when decompressing pixel data. */ + private Codec codec; + + /** Whether or not the pixel data is losslessly compressed. */ + private boolean isLossless; + + private ArrayList tsT = new ArrayList(); + + private int positionCount = 0; + + private int fieldIndex; + + private long xOffset, yOffset, zOffset; + private long pfsOffset, pfsStateOffset; + + private ArrayList posX; + private ArrayList posY; + private ArrayList posZ; + private ArrayList exposureTime = new ArrayList(); + + private Map channelColors; + private boolean split = false; + private int lastChannel = 0; + private int[] colors; + private Boolean useZ = null; + + private int nXFields; + + private ND2Handler backupHandler; + + private double trueSizeX = 0; + private double trueSizeY = 0; + private Double trueSizeZ = null; + + private ArrayList textChannelNames = new ArrayList(); + private ArrayList textEmissionWavelengths = new ArrayList(); + + private boolean textData = false; + private Double refractiveIndex = null; + Boolean imageMetadataLVProcessed = false; + String imageMetadataLVOrder = ""; + private transient Double lensNA = null; + private transient Double objectiveMag = null; + private transient String objectiveModel = null; // -- Constructor -- /** Constructs a new ND2 reader. */ public ND2Reader() { - super("Nikon ND2", "nd2"); - nativeReader = new NativeND2Reader(); - legacyReader = new LegacyND2Reader(); - nativeReaderInitialized = false; - legacyReaderInitialized = false; + super("Nikon ND2", new String[] {"nd2", "jp2"}); + suffixSufficient = false; domains = new String[] {FormatTools.LM_DOMAIN}; } + // -- ND2Reader methods -- + + public boolean useChunkMap() { + MetadataOptions options = getMetadataOptions(); + if (options instanceof DynamicMetadataOptions) { + if (((DynamicMetadataOptions) options).get(USE_CHUNKMAP_LEGACY_KEY) != null) { + LOGGER.warn("Legacy chunkmap option detected use {} instead", + USE_CHUNKMAP_KEY); + } + Boolean defaultValue = ((DynamicMetadataOptions) options).getBoolean( + USE_CHUNKMAP_LEGACY_KEY, USE_CHUNKMAP_DEFAULT); + return ((DynamicMetadataOptions) options).getBoolean( + USE_CHUNKMAP_KEY, defaultValue); + } + return USE_CHUNKMAP_DEFAULT; + } + // -- IFormatReader API methods -- - /* @see loci.formats.IFormatReader#getOptimalTileHeight() */ + /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ + @Override + public boolean isThisType(RandomAccessInputStream stream) throws IOException { + final int blockLen = 8; + if (!FormatTools.validStream(stream, blockLen, false)) return false; + long magic1 = stream.readInt() & 0xffffffffL; + long magic2 = stream.readInt() & 0xffffffffL; + return magic1 == ND2_MAGIC_BYTES_1 || magic2 == ND2_MAGIC_BYTES_2; + } + + /* @see loci.formats.IFormatReader#get8BitLookupTable() */ + @Override + public byte[][] get8BitLookupTable() { + if (FormatTools.getBytesPerPixel(getPixelType()) != 1 || + !isIndexed() || lastChannel < 0 || lastChannel >= colors.length) + { + return null; + } + + int color = colors[lastChannel]; + if (color == 0) return null; + + byte[][] lut = new byte[3][256]; + + int redMax = color & 0xff; + int greenMax = (color & 0xff00) >> 8; + int blueMax = (color & 0xff0000) >> 16; + + for (int i=0; i<256; i++) { + double scale = i / 255.0; + lut[0][i] = (byte) (redMax * scale); + lut[1][i] = (byte) (greenMax * scale); + lut[2][i] = (byte) (blueMax * scale); + } + + return lut; + } + + /* @see loci.formats.IFormatReader#get16BitLookupTable() */ + @Override + public short[][] get16BitLookupTable() { + if (FormatTools.getBytesPerPixel(getPixelType()) != 2 || + !isIndexed() || lastChannel < 0 || lastChannel >= colors.length) + { + return null; + } + + int color = colors[lastChannel]; + if (color == 0) return null; + + short[][] lut = new short[3][65536]; + + int redMax = color & 0xff; + int greenMax = (color & 0xff00) >> 8; + int blueMax = (color & 0xff0000) >> 16; + + for (int i=0; i<65536; i++) { + // *Max values are from 0-255; we want LUT values from 0-65535 + double scale = i / 255.0; + lut[0][i] = (short) (redMax * scale); + lut[1][i] = (short) (greenMax * scale); + lut[2][i] = (short) (blueMax * scale); + } + + return lut; + } + + /** + * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int) + */ + @Override + public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) + throws FormatException, IOException + { + FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h); + + lastChannel = split ? no % getSizeC() : 0; + int planeIndex = split ? no / getSizeC() : no; + in.seek(offsets[getSeries()][planeIndex]); + + int bpp = FormatTools.getBytesPerPixel(getPixelType()); + int pixel = bpp * getRGBChannelCount(); + if (split) pixel *= getSizeC(); + + int totalPlanes = split ? getImageCount() / getSizeC() : getImageCount(); + + long maxFP = planeIndex == totalPlanes - 1 ? + in.length() : offsets[getSeries()][planeIndex + 1]; + + CodecOptions options = new CodecOptions(); + options.littleEndian = isLittleEndian(); + options.interleaved = isInterleaved(); + options.maxBytes = (int) maxFP; + + int scanlinePad = getScanlinePad(); + + if (isJPEG || isLossless) { + if (codec == null) codec = createCodec(isJPEG); + byte[] t = null; + try { + t = codec.decompress(in, options); + } + catch (IOException e) { + LOGGER.debug("Failed to decompress; plane may be corrupt", e); + return buf; + } + if ((getSizeX() + scanlinePad) * getSizeY() * pixel > t.length) { + // one padding pixel per row total, instead of one padding pixel + // per channel per row + int rowLength = getSizeX() * pixel + scanlinePad * bpp; + int destLength = w * pixel; + + int p = rowLength * y + x * pixel; + byte[] pix = new byte[destLength * h]; + for (int row=0; row getAvailableOptions() { + ArrayList optionsList = super.getAvailableOptions(); + optionsList.add(USE_CHUNKMAP_KEY); + return optionsList; + } + + static class ChunkMapEntry { + public String name; + public long position; + public long length; + + public String toString() { + return String.format("ChunkMapEntry<%s@%d(%d)>", name, position, length); + } + } + + /* @see loci.formats.FormatReader#initFile(String) */ @Override - public int getOptimalTileHeight() { - FormatTools.assertId(currentId, true, 1); - return getSizeY(); + protected void initFile(String id) throws FormatException, IOException { + super.initFile(id); + + // using a 32KB buffer instead of the default 1MB gives + // better performance with the seek/skip pattern used here + in = new RandomAccessInputStream(id, BUFFER_SIZE); + + boolean useChunkMap = useChunkMap(); + LOGGER.debug("Attempting to use chunk map = {}", useChunkMap); + + channelColors = new HashMap(); + + if (in.read() == -38 && in.read() == -50) { + // newer version of ND2 - doesn't use JPEG2000 + LOGGER.info("Searching for blocks"); + + isJPEG = false; + in.seek(0); + in.order(true); + + // assemble offsets to each block + + ArrayList imageNames = new ArrayList(); + ArrayList imageOffsets = new ArrayList(); + ArrayList imageLengths = new ArrayList(); + ArrayList customDataOffsets = new ArrayList(); + ArrayList customDataLengths = new ArrayList(); + + // order matters when working with the text blocks, which is + // why two ArrayLists are used instead of a HashMap + ArrayList textStrings = new ArrayList(); + ArrayList validDimensions = new ArrayList(); + + ByteArrayHandle xml = new ByteArrayHandle(); + final StringBuilder name = new StringBuilder(); + + int extraZDataCount = 0; + boolean foundMetadata = false; + boolean foundAttributes = false; + boolean useLastText = false; + int blockCount = 0; + + + TreeMap allChunkPositions = new TreeMap(); + + if(useChunkMap) { + /* In modern ND2 files, the chunk map is stored near the end, and contains + * a list of blocks and their offsets. By using these offsets instead of + * scanning through the whole file, an enormous speed up can be achieved. + * + * Implementation: Read the chunk map beforehand, process the file as normally, + * once the first ImageDataSeq block is reached, add all images and skip past the + * image data to process remaining metadata. + * I haven't read through all of ND2Reader, but I hope to have the least + * chance of inadvertedly breaking something by this approach. + */ + String chunkMapSignature = "ND2 CHUNK MAP SIGNATURE 0000001"; + in.seek(in.length() - 40); + + if(!in.readString(chunkMapSignature.length()).equals(chunkMapSignature)) { + useChunkMap = false; + LOGGER.info("ND2 Warning: No chunk map found!"); + } else { + in.skipBytes(1); + + long chunkMapPosition = in.readLong(); + in.seek(chunkMapPosition); + + int tmpLenOne = in.readInt(); + int tmpLenTwo = in.readInt(); + long chunkMapLength = in.readLong(); + + chunkMapPosition += 16 + tmpLenTwo; + in.seek(chunkMapPosition); + + long chunkMapEnd = chunkMapPosition + chunkMapLength; + + int imageDataCount = 0; + int maxImageIndex = -1; + + while(in.getFilePointer() + 1 + 16 < chunkMapEnd) { + + char b = (char) in.readByte(); + while (b != '!') { + name.append(b); + b = (char) in.readByte(); + } + + ChunkMapEntry entry = new ChunkMapEntry(); + entry.name = name.toString(); + name.delete(0, name.length()); + + if(entry.name.equals(chunkMapSignature)) + break; + + entry.position = in.readLong(); + entry.length = in.readLong(); + + if (entry.name.startsWith("ImageDataSeq|")) { + imageDataCount++; + int imageIndex = -1; + try { + imageIndex = Integer.parseInt(entry.name.substring("ImageDataSeq|".length())); + if (imageIndex > maxImageIndex) { + maxImageIndex = imageIndex; + } + } + catch (NumberFormatException e) { + LOGGER.trace(entry.name, e); + } + } + + allChunkPositions.put(entry.position, entry); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("ND2 {}", entry.toString()); + } + } + if (imageDataCount != maxImageIndex + 1) { + LOGGER.warn("Discarding chunk map; image data count = {}, max index = {}", + imageDataCount, maxImageIndex); + useChunkMap = false; + } + } + + in.seek(0); + } + + // search for blocks + byte[] sigBytes = {-38, -50, -66, 10}; // 0xDACEBE0A + byte[] buf = new byte[BUFFER_SIZE]; + + if(useChunkMap) { + + long checkEvery = in.length() / 10; + long nextCheck = 0; + + // chunk map sanity checks + + for (ChunkMapEntry entry : allChunkPositions.values()) { + if (!entry.name.startsWith("ImageDataSeq")) { + continue; + } + + if(entry.position > nextCheck) { + + in.seek(entry.position); + in.read(buf, 0, sigBytes.length); + + if (!(buf[0] == sigBytes[0] && buf[1] == sigBytes[1] && buf[2] == sigBytes[2] && buf[3] == sigBytes[3])) { + LOGGER.warn("Broken ND2 File detected! Disabling chunk map processing."); + + useChunkMap = false; + break; + } + + nextCheck = entry.position + checkEvery; + } + } + } + + Boolean currentCountSetted = false; + int XYCount = 1; + int timeCount = 1; + int zCount = 1; + + in.seek(0); + int validBits = 0; + + while (in.getFilePointer() < in.length() - 1 && in.getFilePointer() >= 0) + { + int foundIndex = -1; + in.read(buf, 0, sigBytes.length); + while (foundIndex == -1 && in.getFilePointer() < in.length()) { + int n = in.read(buf, sigBytes.length, buf.length - sigBytes.length); + for (int i=0; i in.length() - 24 || foundIndex == -1) { + break; + } + + Long helper = in.getFilePointer(); // Remember starting position + + int nameLength = in.readInt(); // Length of the block name + long dataLength = in.readLong(); // Length of the data + String nameAttri = in.readString(nameLength).trim(); // Read the name + Long stop = helper + (dataLength + nameLength); // Where this block ends + + boolean seq = false; // Only 1 MetadataSeq is needed + + // Send to iteration + // (we are interested only in xxxxLV - LV = light variant) + if (nameAttri.contains("MetadataLV") || + nameAttri.contains("CalibrationLV") || + (nameAttri.contains("MetadataSeqLV") && !seq)) + { + // prevent position count from being doubled + if (nameAttri.equals("ImageMetadataLV!")) { + positionCount = 0; + } + iterateIn(in, stop); + } + + // Only 1 MetadataSeq is needed + // (others should be same, and time is saved elsewhere) + if (nameAttri.contains("MetadataSeqLV")) { + seq = true; + } + + in.seek(helper + 12); // Return to starting position + + long len = nameLength + dataLength; + + long fp = in.getFilePointer(); + String blockType = in.readString(12); + + int percent = (int) (100 * fp / in.length()); + LOGGER.info("Parsing block '{}' {}%", blockType, percent); + blockCount++; + + long skip = len - 12 - nameLength * 2; + if (skip <= 0) skip += nameLength * 2; + + // Image calibration for newer nd2 files + + if (blockType.endsWith("Calibra")) { + long veryStart = in.getFilePointer(); + in.skipBytes(12); // ImageCalibra|tionLV + + long endFP = in.getFilePointer() + len - 24; + while (in.read() == 0); + + while (in.getFilePointer() < endFP) { + int nameLen = in.read(); + if (nameLen == 0) { + in.seek(in.getFilePointer() - 3); + nameLen = in.read(); + } + if (nameLen < 0) { + break; + } + + // Get data + String attributeName = + DataTools.stripString(in.readString(nameLen * 2)); + double valueOrLength = in.readDouble(); + + if (attributeName.equals("dCalibration")) { + if (valueOrLength > 0) { + addGlobalMeta(attributeName, valueOrLength); + if (trueSizeX == 0) { + trueSizeX = valueOrLength; + } + else if (trueSizeY == 0) { + trueSizeY = valueOrLength; + } + } + break; // Done with calibration + } + } + in.seek(veryStart); // For old nd2 files + } + + if (blockType.startsWith("ImageDataSeq")) { + if (foundMetadata && foundAttributes) { + imageOffsets.clear(); + imageNames.clear(); + imageLengths.clear(); + customDataOffsets.clear(); + customDataLengths.clear(); + foundMetadata = false; + foundAttributes = false; + extraZDataCount = 0; + useLastText = true; + } + + if(useChunkMap) { + ChunkMapEntry lastImage = null; + + // sanity check: see if the chunk we just found is actually in the chunkmap ... + + long lookupPosition = in.getFilePointer() - 28; + + Long lookupResult = allChunkPositions.floorKey(lookupPosition); + + if(lookupResult == null || lookupResult != lookupPosition) { + // if not, deactivate chunkmap processing and try classic + useChunkMap = false; + in.seek(lookupPosition); + continue; + } + + for(ChunkMapEntry entry : allChunkPositions.values()) { + if((entry.position + 28) < in.getFilePointer()) { + continue; + } + + if(!entry.name.startsWith("ImageDataSeq")) { + continue; + } + + lastImage = entry; + + imageOffsets.add(new Long(entry.position + 16)); + int realLength = (int) Math.max(entry.name.length() + 1, nameLength); + imageLengths.add(new long[] {realLength, entry.length - nameLength - 16, getSizeX() * getSizeY()}); + imageNames.add(entry.name.substring(12)); + + blockCount ++; + + percent = (int) (100 * entry.position / in.length()); + LOGGER.info("Parsing block '{}' {}%", "ImageDataSeq", percent); + } + + blockCount --; // one was already added by the outer blockCount ++; + + if (lastImage.position + lastImage.length >= in.length()) { + in.seek(lastImage.position + 16); + } + else { + in.seek(lastImage.position + lastImage.length); + } + + continue; + + } + + dataLength -= 31; + LOGGER.debug( + "Adding non-chunkmap offset {}, nameLength = {}, dataLength = {}", + fp, nameLength, dataLength); + imageOffsets.add(fp); + imageLengths.add(new long[] {nameLength, dataLength, getSizeX() * getSizeY()}); + char b = (char) in.readByte(); + while (b != '!') { + name.append(b); + b = (char) in.readByte(); + } + imageNames.add(name.toString()); + name.setLength(0); + } + else if (blockType.startsWith("ImageText")) { + foundMetadata = true; + in.skipBytes(6); + while (in.read() == 0); + long startFP = in.getFilePointer(); + in.seek(startFP - 1); + + // text block can contain XML (which may be cut off) + // or sequence of string objects + + int typeByte = in.read(); + + // 11 is the max object type code (see iterateIn case statement) + // don't look for '<' because old ND2s might have + // cut off or incorrectly aligned XML + + if (typeByte > 11) { + in.seek(startFP - 1); + String textString = DataTools.stripString(in.readString((int) dataLength)); + textStrings.add(textString); + validDimensions.add(blockCount > 2); + if (!textString.startsWith("<")) { + skip = 0; + } + } + else { + int charCount = in.read(); + textStrings.add(DataTools.stripString(in.readString(charCount * 2))); + validDimensions.add(blockCount > 2); + int numTextInfos = in.readInt(); + long remainingBytes = in.readLong(); + + // reassemble sequence of strings into single multi-line string + // that will be parsed once so that the same ND2Handler is + // used for the whole block + // this should maybe be refactored at some point? + ArrayList text = iterateIn(in, startFP + dataLength - 1, true); + StringBuffer b = new StringBuffer(); + for (int t=0; t 2); + + // make sure the file pointer is at the end of the block + in.seek(startFP + dataLength - 1); + skip = 0; + } + } + else if (blockType.startsWith("Image") || + blockType.startsWith("CustomDataVa")) + { + if (blockType.equals("ImageAttribu")) { + foundAttributes = true; + in.skipBytes(6); + long endFP = in.getFilePointer() + len - 18; + while (in.read() == 0); + + boolean canBeLossless = true; + + while (in.getFilePointer() < endFP) { + int nameLen = in.read(); + if (nameLen == 0) { + in.seek(in.getFilePointer() - 3); + nameLen = in.read(); + } + if (nameLen < 0) { + break; + } + long start = in.getFilePointer(); + String attributeName = + DataTools.stripString(in.readString(nameLen * 2)); + if (attributeName.startsWith("xml ") || + attributeName.startsWith("ml version") || + attributeName.startsWith("l version") || + attributeName.startsWith("version")) + { + if (attributeName.startsWith("xml ")) { + in.seek(start - 2); + } + else if (attributeName.startsWith("ml version")) { + in.seek(start - 3); + } + else if (attributeName.startsWith("l version")) { + in.seek(start - 4); + } + else { + in.seek(start - 6); + } + attributeName = in.readCString(); + String xmlString = XMLTools.sanitizeXML(attributeName.trim()); + xmlString = + xmlString.substring(0, xmlString.lastIndexOf(">") + 1); + if (xmlString.startsWith("') + 1); + } + if (!xmlString.endsWith("")) { + xmlString += ""; + } + + if (getDimensionOrder() == null) { + core.get(0, 0).dimensionOrder = ""; + } + + try { + ND2Handler handler = + new ND2Handler(core, imageOffsets.size()); + XMLTools.parseXML(xmlString, handler); + xmlString = null; + core = handler.getCoreMetadataList(); + if (backupHandler == null) { + backupHandler = handler; + } + } + catch (IOException e) { + LOGGER.debug("Could not parse XML", e); + } + + in.seek(in.getFilePointer() - 8); + break; + } + + int valueOrLength = in.readInt(); + + addGlobalMeta(attributeName, valueOrLength); + + if (attributeName.equals("uiWidth")) { + core.get(0, 0).sizeX = valueOrLength; + } + else if (attributeName.equals("uiHeight")) { + core.get(0, 0).sizeY = valueOrLength; + } + else if (attributeName.equals("uiComp")) { + core.get(0, 0).sizeC = valueOrLength; + } + else if (attributeName.equals("uiBpcInMemory")) { + core.get(0, 0).pixelType = FormatTools.pixelTypeFromBytes( + valueOrLength / 8, false, true); + } + else if (attributeName.equals("uiBpcSignificant")) { + validBits = valueOrLength; + core.get(0, 0).bitsPerPixel = valueOrLength; + } + else if (attributeName.equals("dCompressionParam")) { + isLossless = valueOrLength >= 0; + } + else if (attributeName.equals("eCompression")) { + canBeLossless = valueOrLength <= 0; + } + else if (attributeName.equals("SLxImageAttributes")) { + int toSkip = valueOrLength - 5; + if ((toSkip % 2) == 1) { + toSkip++; + } + in.skipBytes(toSkip); + } + else if (attributeName.endsWith("Desc")) { + in.seek(in.getFilePointer() - 2); + } + else if (attributeName.equals("SLxExperiment")) { + in.skipBytes(8); + } + else if (attributeName.equals("wsCameraName")) { + in.seek(in.getFilePointer() - 4); + byte[] b = new byte[2]; + in.read(b); + StringBuilder value = new StringBuilder(); + while (b[0] != 0) { + value.append(b[0]); + in.read(b); + } + addGlobalMeta(attributeName, value.toString()); + } + else if (attributeName.equals("uLoopPars")) { + int v2 = in.readInt(); + int v3 = in.readInt(); + addGlobalMeta(attributeName, + valueOrLength + ", " + v2 + ", " + v3); + } + else if (attributeName.equals("pPeriod")) { + in.skipBytes(22); + } + else if (attributeName.equals("dPeriod") || + attributeName.equals("dDuration")) + { + in.skipBytes(4); + } + else if (attributeName.equals("bDurationPref")) { + in.seek(in.getFilePointer() - 3); + } + in.skipBytes(1); + } + + if (in.getFilePointer() > endFP) { + in.seek(endFP); + } + + isLossless = isLossless && canBeLossless; + } + else { + if (blockType.startsWith("ImageMetadat") && !imageMetadataLVProcessed) { + foundMetadata = true; + long startFilePointer = in.getFilePointer(); + in.skipBytes(6); + long endFP = in.getFilePointer() + len - 18; + while (in.read() == 0); + + int eType = 0; + Boolean nextExperiment = true; + + long currentFilePointer = in.getFilePointer(); + + while (true) { + in.seek(currentFilePointer); + + int nameLen = in.read(); + if (nameLen == 0 || nameLen < 0) { + currentFilePointer++; + continue; + } + + String attributeName = + DataTools.stripString(in.readString(nameLen * 2)); + + if(attributeName.length() != nameLen - 1) + { + currentFilePointer++; + continue; + } + + if (attributeName.equals("SLxExperiment")) { + currentFilePointer += nameLen * 2; + imageMetadataLVProcessed = true; + imageMetadataLVOrder = ""; + } + + if (attributeName.equals("eType")) { + currentFilePointer += nameLen * 2; + if(nextExperiment) + eType = in.readInt(); + nextExperiment = false; + } else + if (attributeName.equals("uiCount")) { + currentFilePointer += nameLen * 2; + + if(!currentCountSetted) + { + if(eType == 2) + { + imageMetadataLVOrder = "M" + imageMetadataLVOrder; + XYCount = in.readInt(); + } else if(eType == 1) + { + imageMetadataLVOrder = "T" + imageMetadataLVOrder; + timeCount = in.readInt(); + } if(eType == 4) + { + imageMetadataLVOrder = "Z" + imageMetadataLVOrder; + zCount = in.readInt(); + } + currentCountSetted = true; + } + } else + if (attributeName.equals("bKeepObject")) { + currentFilePointer += nameLen * 2; + } else + if (attributeName.equals("uiRepeatCount")) { + currentFilePointer += nameLen * 2; + } else + if (attributeName.equals("vectStimulationConfigurationsSize")) { + currentFilePointer += nameLen * 2; + } else + if (attributeName.equals("uiNextLevelCount")) { + currentFilePointer += nameLen * 2; + int uiNextLevelCount = in.readInt(); + + if(uiNextLevelCount == 0) + { + break; + } + currentCountSetted = false; + nextExperiment = true; + } + + if (in.getFilePointer() > endFP) { + in.seek(startFilePointer); + break; + } + + currentFilePointer++; + } + + if (in.getFilePointer() > startFilePointer) { + in.seek(startFilePointer); + } + } + // more than 2GB of XML is not supported and likely indicates + // some other parsing error + if (len - 12 > Integer.MAX_VALUE) { + LOGGER.warn("Found {} bytes of XML, this is probably incorrect", len - 12); + } + + int length = (int) (len - 12); + byte[] b = new byte[length]; + in.read(b); + + // strip out invalid characters + int off = 0; + for (int j=0; j= 5 && b[off] == '<' && b[off + 1] == '?' && + b[off + 2] == 'x' && b[off + 3] == 'm' && b[off + 4] == 'l') + { + boolean endBracketFound = false; + while (!endBracketFound) { + if (b[off++] == '>') { + endBracketFound = true; + } + } + xml.write(b, off, b.length - off); + } + + } + skip = 0; + } + else if (getMetadataOptions().getMetadataLevel() != + MetadataLevel.MINIMUM) + { + long nDoubles = len / 8; + long nInts = len / 4; + long doubleOffset = fp + 8 * (nDoubles - imageOffsets.size()); + long intOffset = fp + 4 * (nInts - imageOffsets.size()); + if (nameAttri.startsWith("CustomData|AcqTimesCache")) { + customDataOffsets.add(fp); + customDataLengths.add(new long[] {nameLength, dataLength}); + } + else if (blockType.startsWith("CustomData|Z")) { + if (zOffset == 0) { + zOffset = doubleOffset; + } + extraZDataCount++; + } + else if (blockType.startsWith("CustomData|X")) { + xOffset = doubleOffset; + } + else if (blockType.startsWith("CustomData|Y")) { + yOffset = doubleOffset; + } + else if (blockType.startsWith("CustomData|P")) { + if (pfsOffset == 0) { + pfsOffset = intOffset; + } + else if (pfsStateOffset == 0) { + pfsStateOffset = intOffset; + } + } + } + if (skip > 0 && skip + in.getFilePointer() <= in.length()) { + in.skipBytes(skip); + } + } + + if(currentCountSetted && imageMetadataLVOrder.length() > 0 && + (imageOffsets.size() == 0 || timeCount * zCount * XYCount == imageOffsets.size())) { + setDimensions(timeCount, zCount, XYCount); + } + else { + imageMetadataLVProcessed = false; + } + + // parse text blocks + + int nChannelNames = textChannelNames.size(); + for (int i=0; i nChannelNames) { + int diff = textChannelNames.size() - nChannelNames; + while (textChannelNames.size() > diff) { + textChannelNames.remove(0); + } + } + + // parse XML blocks + + String xmlString = + new String(xml.getBytes(), 0, (int) xml.length(), Constants.ENCODING); + xml = null; + xmlString = "" + + xmlString + ""; + xmlString = XMLTools.sanitizeXML(xmlString); + + core.get(0, 0).dimensionOrder = ""; + ND2Handler handler = + new ND2Handler(core, getSizeX() == 0, imageOffsets.size()); + XMLTools.parseXML(xmlString, handler); + xmlString = null; + + if (handler.getChannelColors().size() > 0) { + channelColors = handler.getChannelColors(); + } + if (!isLossless) { + isLossless = handler.isLossless(); + } + fieldIndex = handler.getFieldIndex(); + core = handler.getCoreMetadataList(); + + final Map globalMetadata = handler.getMetadata(); + nXFields = handler.getXFields(); + if (nXFields > 6) { + nXFields = 0; + } + for (String key : globalMetadata.keySet()) { + addGlobalMeta(key, globalMetadata.get(key)); + if (key.equals("ChannelCount")) { + for (int i=0; i 1) { + ms.rgb = true; + } + } + } + } + else if (key.equals("uiBpcInMemory")) { + int bpc = Integer.parseInt(globalMetadata.get(key).toString()); + core.get(0, 0).pixelType = FormatTools.pixelTypeFromBytes( + bpc / 8, false, false); + } + } + + int planeCount = core.size() * getSizeZ() * getSizeT(); + if (!textData && planeCount < imageOffsets.size() && planeCount > 0 && + (imageOffsets.size() % (planeCount / core.size())) == 0) + { + int seriesCount = imageOffsets.size() / (planeCount / core.size()); + core = new CoreMetadataList(); + + for (int i=0; i 1) { + for (int i=1; i 1) { + CoreMetadata ms0 = core.get(0, 0); + core = new CoreMetadataList(); + core.add(ms0); + } + + if (positionCount != getSeriesCount() && (getSizeZ() == imageOffsets.size() || (extraZDataCount > 1 && getSizeZ() == 1 && (extraZDataCount == getSizeC())) || (handler.getXPositions().size() == 0 && (xOffset == 0 && getSizeZ() != getSeriesCount()))) && getSeriesCount() > 1) { + CoreMetadata ms0 = core.get(0, 0); + if (getSeriesCount() > ms0.sizeZ) { + ms0.sizeZ = getSeriesCount(); + } + core = new CoreMetadataList(); + core.add(ms0); + } + + // make sure the channel count is reasonable + // sometimes the XML will indicate that there are multiple channels, + // when in fact there is only one channel + + long firstOffset = imageOffsets.get(0); + long secondOffset = + imageOffsets.size() > 1 ? imageOffsets.get(1) : in.length(); + long availableBytes = secondOffset - firstOffset; + + // make sure that we have the compression setting correct + // it's not always easy to tell from the metadata + isLossless = true; + + long fp = in.getFilePointer(); + long[] firstLengths = imageLengths.get(0); + in.seek(firstOffset + firstLengths[0] + 8); + + if (codec == null) codec = createCodec(false); + try { + CodecOptions options = new CodecOptions(); + options.littleEndian = isLittleEndian(); + options.interleaved = true; + options.maxBytes = (int) secondOffset; + byte[] t = codec.decompress(in, options); + + if (t.length == 2 * getSizeX() * getSizeY() && + getPixelType() == FormatTools.INT8) + { + core.get(0, 0).pixelType = FormatTools.UINT16; + } + availableBytes = t.length; + } + catch (IOException e) { + isLossless = false; + } + + boolean allEqual = true; + long offsetDiff = imageOffsets.get(0) - imageLengths.get(0)[1]; + for (int i=1; i 1) { + int plane = (getSizeX() + getScanlinePad()) * getSizeY(); + boolean fixByteCounts = false; + if (plane > 0) { + for (int i=0; i 0 && plane != check)) { + if (imageOffsets.get(i) - length != offsetDiff + 8) { + if (i == 0) { + fixByteCounts = true; + } + imageOffsets.remove(i); + imageLengths.remove(i); + i--; + } + } + } + } + + if (fixByteCounts) { + firstOffset = imageOffsets.get(0); + secondOffset = imageOffsets.size() > 1 ? + imageOffsets.get(1) : in.length(); + availableBytes = secondOffset - firstOffset; + + if (isLossless) { + firstLengths = imageLengths.get(0); + in.seek(firstOffset + firstLengths[0] + 8); + CodecOptions options = new CodecOptions(); + options.littleEndian = isLittleEndian(); + options.interleaved = true; + options.maxBytes = (int) secondOffset; + byte[] t = codec.decompress(in, options); + availableBytes = t.length; + } + + } + } + + in.seek(fp); + + long planeSize = getSizeX() * getSizeY() * getSizeC() * + FormatTools.getBytesPerPixel(getPixelType()); + + if (availableBytes < planeSize) { + LOGGER.debug("Correcting SizeC: was {}", getSizeC()); + LOGGER.debug("plane size = {}", planeSize); + LOGGER.debug("available bytes = {}", availableBytes); + + core.get(0, 0).sizeC = (int) (availableBytes / (planeSize / getSizeC())); + if (getSizeC() == 0) { + core.get(0, 0).sizeC = 1; + } + planeSize = getSizeX() * getSizeY() * getSizeC() * + FormatTools.getBytesPerPixel(getPixelType()); + } + + if (planeSize > 0 && availableBytes % planeSize != 0) { + // an extra 4K block of zeros may have been appended + if ((availableBytes - 4096) % planeSize == 0) { + availableBytes -= 4096; + } + } + if (planeSize > 0 && imageOffsets.size() > 1 && + availableBytes > DataTools.safeMultiply64(planeSize, 3)) + { + if (availableBytes < DataTools.safeMultiply64(planeSize, 6)) { + core.get(0, 0).sizeC = 3; + core.get(0, 0).rgb = true; + if (getPixelType() == FormatTools.INT8) { + core.get(0, 0).pixelType = availableBytes > planeSize * 5 ? + FormatTools.UINT16 : FormatTools.UINT8; + } + } + } + else if (((planeSize > 0 && + availableBytes >= DataTools.safeMultiply64(planeSize, 2)) || + getSizeC() > 3) && getPixelType() == FormatTools.INT8) + { + core.get(0, 0).pixelType = FormatTools.UINT16; + planeSize *= 2; + if (getSizeC() > 3 && availableBytes % planeSize != 0 && + planeSize > availableBytes) + { + core.get(0, 0).sizeC = 3; + core.get(0, 0).rgb = true; + } + } + else if (getSizeC() == 2 && getPixelType() == FormatTools.INT8 && + availableBytes >= planeSize * 2) + { + core.get(0, 0).pixelType = FormatTools.UINT16; + } + else if (getPixelType() == FormatTools.INT8) { + core.get(0, 0).pixelType = FormatTools.UINT8; + } + + // now that pixel type is est, restore number of valid bits per pixel + if (core.get(0, 0).bitsPerPixel == 0 && validBits > 0 && + validBits <= 8 * FormatTools.getBytesPerPixel(core.get(0, 0).pixelType)) + { + core.get(0, 0).bitsPerPixel = validBits; + } + + if (getSizeX() == 0) { + core.get(0, 0).sizeX = (int) Math.sqrt(availableBytes / + (getSizeC() * FormatTools.getBytesPerPixel(getPixelType()))); + core.get(0, 0).sizeY = getSizeX(); + } + + int rowSize = getSizeX() * FormatTools.getBytesPerPixel(getPixelType()) * + getSizeC(); + long sizeY = availableBytes / rowSize; + if (sizeY < getSizeY()) { + core.get(0, 0).sizeY = (int) sizeY; + } + + if (getSizeT() == imageOffsets.size() && getSeriesCount() > 1) { + CoreMetadata firstCore = core.get(0, 0); + core = new CoreMetadataList(); + core.add(firstCore); + } + + // calculate the image count + for (int i=0; i imageOffsets.size() / getSeriesCount()) { + int diff = imageOffsets.size() - ms.imageCount; + if (diff >= 0 && diff < ms.sizeZ && diff < ms.sizeT) { + CoreMetadata ms0 = core.get(0, 0); + core = new CoreMetadataList(); + core.add(ms0); + numSeries = 1; + break; + } + else if (imageOffsets.size() % ms.sizeT == 0) { + ms.imageCount = imageOffsets.size() / getSeriesCount(); + ms.sizeZ = ms.imageCount / ms.sizeT; + ms.dimensionOrder = "CZT"; + } + else { + ms.imageCount = imageOffsets.size() / getSeriesCount(); + ms.sizeZ = 1; + ms.sizeT = ms.imageCount; + } + } + } + + if (numSeries * getImageCount() == 1 && imageOffsets.size() > 1) { + for (int i=0; i 1; + int count = imageOffsets.size() / getSeriesCount(); + if (!split && count >= getSizeC()) { + count /= getSizeC(); + } + + int diff = count - getSizeZ() * getSizeT(); + if (diff == getSizeZ()) { + core.get(0, 0).sizeT++; + } + else if (getSizeT() > getSizeZ()) { + core.get(0, 0).sizeZ = 1; + core.get(0, 0).sizeT = count; + } + else { + core.get(0, 0).sizeT = 1; + core.get(0, 0).sizeZ = count; + } + + if (useZ != null && !useZ) { + CoreMetadata original = core.get(0, 0); + int nSeries = imageOffsets.size() / (getSizeZ() * getSizeT()); + for (int i=1; i 4) + { + core.get(0, 0).sizeZ = 1; + core.get(0, 0).sizeT = imageOffsets.size() / getSeriesCount(); + } + + core.get(0, 0).imageCount = getSizeZ() * getSizeT() * getSizeC(); + } + + if (getDimensionOrder().equals("T")) { + fieldIndex = 0; + } + else if (getDimensionOrder().equals("ZT") && fieldIndex == 2) { + fieldIndex--; + } + + if (getSizeC() > 1 && getDimensionOrder().indexOf('C') == -1) { + core.get(0, 0).dimensionOrder = "C" + getDimensionOrder(); + } + + core.get(0, 0).dimensionOrder = "XY" + getDimensionOrder(); + if (getDimensionOrder().indexOf('Z') == -1) core.get(0, 0).dimensionOrder += 'Z'; + if (getDimensionOrder().indexOf('C') == -1) core.get(0, 0).dimensionOrder += 'C'; + if (getDimensionOrder().indexOf('T') == -1) core.get(0, 0).dimensionOrder += 'T'; + + if (getSizeZ() == 0) { + core.get(0, 0).sizeZ = 1; + } + if (getSizeT() == 0) { + core.get(0, 0).sizeT = 1; + } + if (getSizeC() == 0) { + core.get(0, 0).sizeC = 1; + } + core.get(0, 0).imageCount = getSizeZ() * getSizeT(); + if (!isRGB()) { + core.get(0, 0).imageCount *= getSizeC(); + } + + posX = handler.getXPositions(); + posY = handler.getYPositions(); + posZ = handler.getZPositions(); + + int uniqueX = 0, uniqueY = 0, uniqueZ = 0; + if (posX.size() == 0 && xOffset != 0) { + in.seek(xOffset); + for (int i=0; i 1 && ((getImageCount() == imageOffsets.size() && + getSizeC() == 1) || (getImageCount() == imageOffsets.size() * getSizeC()))) + { + CoreMetadata first = core.get(0, 0); + core.clear(); + core.add(first); + numSeries = 1; + } + + offsets = new long[numSeries][getImageCount()]; + + int[] lengths = new int[4]; + if(!imageMetadataLVProcessed) { + int nextChar = 2; + for (int i = 0; i < lengths.length; i++) { + if (i == fieldIndex) lengths[i] = core.size(); + else { + char axis = getDimensionOrder().charAt(nextChar++); + if (axis == 'Z') lengths[i] = getSizeZ(); + else if (axis == 'C') lengths[i] = 1; + else if (axis == 'T') lengths[i] = getSizeT(); + } + } + } else + { + int currPos = 1; + lengths[0] = 1; + lengths[1] = 1; + lengths[2] = 1; + lengths[3] = 1; + for (char c: + imageMetadataLVOrder.toCharArray()) { + if(c == 'Z') + { + lengths[currPos] = getSizeZ(); + } + else if(c == 'M') + { + fieldIndex = currPos; + lengths[currPos] = core.size(); + } + else if(c == 'T') + { + lengths[currPos] = getSizeT(); + } + else + { + currPos--; + } + currPos++; + } + + if(!imageMetadataLVOrder.contains("M")) + fieldIndex = 3; + } + int[] zctLengths = new int[4]; + System.arraycopy(lengths, 0, zctLengths, 0, lengths.length); + zctLengths[fieldIndex] = 1; + + boolean oneIndexed = false; + for (int i=0; i tmpOffsets = new ArrayList(); + for (int i=0; i 0 && offsets[i][0] > 0) { + tmpOffsets.add(offsets[i]); + } + } + + offsets = new long[tmpOffsets.size()][]; + for (int i=0; i 1; + for (int i=0; i 0 && hasColor; + ms.falseColor = true; + ms.metadataComplete = true; + ms.imageCount = ms.sizeZ * ms.sizeT * ms.sizeC; + } + + // read first CustomData block + + if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) { + if (customDataOffsets.size() > 0) { + in.seek(customDataOffsets.get(0).longValue()); + long[] p = customDataLengths.get(0); + long len = p[0] + p[1]; + + int timestampBytes = imageOffsets.size() * 8; + in.seek(in.getFilePointer() + (len - timestampBytes)); + + // the acqtimecache is a undeliniated stream of doubles + + for (int series=0; series vs = new ArrayList(); + + long pos = in.getFilePointer(); + boolean lastBoxFound = false; + int length = 0; + int box = 0; + + // assemble offsets to each plane + + int x = 0, y = 0, c = 0, type = 0; + + while (!lastBoxFound) { + pos = in.getFilePointer(); + length = in.readInt(); + long nextPos = pos + length; + if (nextPos < 0 || nextPos >= in.length() || length == 0) { + lastBoxFound = true; + } + box = in.readInt(); + pos = in.getFilePointer(); + length -= 8; + + if (box == 0x6a703263) { + vs.add(pos); + } + else if (box == 0x6a703268) { + in.skipBytes(4); + String s = in.readString(4); + if (s.equals("ihdr")) { + y = in.readInt(); + x = in.readInt(); + c = in.readShort(); + type = in.readInt(); + if (type == 0xf070100 || type == 0xf070000) type = FormatTools.UINT16; + else type = FormatTools.UINT8; + } + } + if (!lastBoxFound && box != 0x6a703268) in.skipBytes(length); + } + + LOGGER.info("Finding XML metadata"); + + // read XML metadata from the end of the file + + in.seek(vs.get(vs.size() - 1).longValue()); + + boolean found = false; + long off = -1; + byte[] buf = new byte[8192]; + while (!found && in.getFilePointer() < in.length()) { + int read = 0; + if (in.getFilePointer() == vs.get(vs.size() - 1).longValue()) { + read = in.read(buf); + } + else { + System.arraycopy(buf, buf.length - 10, buf, 0, 10); + read = in.read(buf, 10, buf.length - 10); + } + + if (read == buf.length) read -= 10; + for (int i=0; i zs = new ArrayList(); + ArrayList ts = new ArrayList(); + + int numSeries = 0; + ND2Handler handler = null; + if (off > 0 && off < in.length() - 5 && (in.length() - off - 5) > 14) { + in.seek(off + 4); + + StringBuilder sb = new StringBuilder(); + // stored XML doesn't have a root node - add one, so that we can parse + // using SAX + + sb.append(""); + + String s = null; + int blockLength = 0; + + while (in.getFilePointer() < in.length()) { + blockLength = in.readShort(); + if (blockLength < 2) break; + blockLength -= 2; + if (blockLength + in.getFilePointer() >= in.length()) { + blockLength = (int) (in.length() - in.getFilePointer()); + } + s = in.readString(blockLength); + s = s.replaceAll(" - 6.12.1-SNAPSHOT + 7.1.0-SNAPSHOT ${maven.build.timestamp} 2017 ${basedir} - 1.51r + 1.54c 1.7.2 1.3.5 - 5.3.2 - ${ome-stubs.version} + 6.0.1 ${ome-stubs.version} 5.3.5 ${ome-metakit.version} 2.0.4 - 4.0.2 + 5.4.0 6.8 - 6.0.14 + 6.0.20 org.openmicroscopy - 6.3.2 + 6.3.3 5.3.7 5.3.2 0.1.3 - 0.4.4 + 1.0.0 0.2.4 2.7.2 3.28.0 @@ -474,16 +473,16 @@ - - ome.staging - OME Staging Repository - https://artifacts.openmicroscopy.org/artifactory/ome.staging - ome.snapshots OME Snapshots Repository https://artifacts.openmicroscopy.org/artifactory/ome.snapshots + + ome.releases + OME Releases Repository + https://artifacts.openmicroscopy.org/artifactory/ome.releases + diff --git a/tools/bf-unconfigured b/tools/bf-unconfigured new file mode 100755 index 00000000000..f39cf7dca46 --- /dev/null +++ b/tools/bf-unconfigured @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# bf-unconfigured: a script for identifying datasets with no .bioformats configuration + +# Required JARs: bioformats_package.jar, bio-formats-testing-framework.jar + +RESOLVED_PATH=$(readlink -f "$0" 2>/dev/null \ + || perl -MCwd -le 'print Cwd::abs_path(shift)' "$0" 2>/dev/null \ + || echo "$0") +BF_DIR=$(dirname $RESOLVED_PATH) + +BF_PROG=loci.tests.testng.ReportEnabledStatus "$BF_DIR/bf.sh" "$@" diff --git a/tools/bf-unconfigured.bat b/tools/bf-unconfigured.bat new file mode 100644 index 00000000000..27243719bd5 --- /dev/null +++ b/tools/bf-unconfigured.bat @@ -0,0 +1,12 @@ +@echo off + +rem bf-unconfigured.bat: a batch file for identifying datasets with no .bioformats configuration + +rem Required JARs: bioformats_package.jar, bio-formats-testing-framework.jar + +setlocal +set BF_DIR=%~dp0 +if "%BF_DIR:~-1%" == "\" set BF_DIR=%BF_DIR:~0,-1% + +set BF_PROG=loci.tests.testng.ReportEnabledStatus +call "%BF_DIR%\bf.bat" %* diff --git a/tools/bf.bat b/tools/bf.bat index 38402536e33..f574ae40503 100644 --- a/tools/bf.bat +++ b/tools/bf.bat @@ -61,6 +61,9 @@ if exist "%BF_JAR_DIR%\bioformats_package.jar" ( echo and place in the same directory as the command line tools. goto end ) +if exist "%BF_JAR_DIR/bio-formats-testing-framework.jar" ( + set BF_CP=%BF_CP%;"%BF_JAR_DIR%\bio-formats-testing-framework.jar" +) java %BF_FLAGS% -cp "%BF_DIR%";%BF_CP% %BF_PROG% %* diff --git a/tools/bf.sh b/tools/bf.sh index db40871c5b1..0afe7f42aa6 100755 --- a/tools/bf.sh +++ b/tools/bf.sh @@ -70,5 +70,9 @@ else echo "and place in the same directory as the command line tools." exit 2 fi + if [ -e "$BF_JAR_DIR/bio-formats-testing-framework.jar" ] + then + BF_CP="$BF_CP:$BF_JAR_DIR/bio-formats-testing-framework.jar" + fi java $BF_FLAGS -cp "$BF_DIR:$BF_CP" $BF_PROG "$@" fi