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