From cd631bd9cdf43b164ffd477eb09d2ffc4f3b1d78 Mon Sep 17 00:00:00 2001 From: kahlep Date: Mon, 9 Apr 2018 14:56:44 +0200 Subject: [PATCH] More robust code for creating BufferedImages. See #4 --- .../transkribus/interfaces/types/Image.java | 79 ------- .../interfaces/types/util/ImageUtils.java | 196 +++++++++++++++--- .../native_wrapper/NativeProxyUtilsTest.java | 1 - .../interfaces/types/ImageTest.java | 54 +++-- 4 files changed, 203 insertions(+), 127 deletions(-) diff --git a/src/main/java/eu/transkribus/interfaces/types/Image.java b/src/main/java/eu/transkribus/interfaces/types/Image.java index 0867304..db3dbc9 100644 --- a/src/main/java/eu/transkribus/interfaces/types/Image.java +++ b/src/main/java/eu/transkribus/interfaces/types/Image.java @@ -9,20 +9,9 @@ import java.io.IOException; import java.net.URL; import java.util.HashSet; -import java.util.Iterator; import java.util.Set; -import javax.imageio.ImageIO; -import javax.imageio.ImageReader; -import javax.imageio.spi.IIORegistry; -import javax.imageio.spi.ImageReaderWriterSpi; - import org.opencv.core.Mat; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.sun.media.imageioimpl.plugins.tiff.TIFFImageReaderSpi; -import com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriterSpi; import eu.transkribus.interfaces.types.util.ImageUtils; @@ -31,7 +20,6 @@ * @author gundram */ public class Image { - private static final Logger logger = LoggerFactory.getLogger(Image.class); private URL imageUrl; private BufferedImage imageBufferedImage; @@ -199,68 +187,6 @@ private Object getImageObject(Type type) { } } - static { - registerImageIOServices(); - listImageIOServices(); - } - - public static void listImageIOServices() { - IIORegistry registry = IIORegistry.getDefaultInstance(); - logger.debug("image-io services:"); - Iterator> cats = registry.getCategories(); - while (cats.hasNext()) { - Class cat = cats.next(); - logger.debug("image-io category = " + cat); - - Iterator providers = registry.getServiceProviders(cat, true); - while (providers.hasNext()) { - logger.debug("image-io provider = " + providers.next()); - } - } - } - - public static void registerImageIOServices() { - logger.debug("registering image readers / writers"); - - IIORegistry registry = IIORegistry.getDefaultInstance(); - - // have to programmatically register tiff reader / writer in tomcat - registerImageIOService(registry, new TIFFImageWriterSpi(), javax.imageio.spi.ImageWriterSpi.class); - registerImageIOService(registry, new TIFFImageReaderSpi(), javax.imageio.spi.ImageReaderSpi.class); - - registerImageIOService(registry, new com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi(), - javax.imageio.spi.ImageWriterSpi.class); - registerImageIOService(registry, new com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi(), - javax.imageio.spi.ImageReaderSpi.class); - } - - private static void registerImageIOService(IIORegistry registry, T provider, Class clazz) { - if(registry == null || provider == null || clazz == null) { - throw new IllegalArgumentException("An argument is null!"); - } - if(registry.registerServiceProvider(provider, clazz)) { - logger.info("Registered IIOServiceProvider: " + provider.getPluginClassName()); - } else { - logger.info("Ignoring already registered IIOServiceProvider: " + provider.getPluginClassName()); - } - } - - public static void testReaders() throws IOException { - String[] formats = { "JPEG", "TIFF", "TIF", "PNG" }; - for (String format : formats) { - logger.info("testing readers for format: " + format); - Iterator readers = ImageIO.getImageReadersByFormatName(format); - - if (!readers.hasNext()) { - throw new IOException("No reader found for format: " + format); - } - - while (readers.hasNext()) { - logger.info("reader: " + readers.next()); - } - } - } - public void dispose() { if(imageBufferedImage != null) { imageBufferedImage.flush(); @@ -271,9 +197,4 @@ public void dispose() { imageOpenCVImage = null; } } - - public static void main(String[] args) throws IOException { - testReaders(); - } - } diff --git a/src/main/java/eu/transkribus/interfaces/types/util/ImageUtils.java b/src/main/java/eu/transkribus/interfaces/types/util/ImageUtils.java index b57f527..f7195aa 100644 --- a/src/main/java/eu/transkribus/interfaces/types/util/ImageUtils.java +++ b/src/main/java/eu/transkribus/interfaces/types/util/ImageUtils.java @@ -7,15 +7,26 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLDecoder; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; import java.util.Properties; import java.util.UUID; +import javax.imageio.IIOException; import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.spi.IIORegistry; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.spi.ImageReaderWriterSpi; +import javax.imageio.stream.ImageInputStream; import org.opencv.core.Core; import org.opencv.core.CvType; @@ -24,6 +35,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.sun.media.imageioimpl.plugins.tiff.TIFFImageReaderSpi; +import com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriterSpi; + import eu.transkribus.interfaces.util.SysPathUtils; /** @@ -36,6 +50,8 @@ public class ImageUtils { private static final Logger logger = LoggerFactory.getLogger(ImageUtils.class); + private static final boolean USE_IMAGE_IO_READ_IMPL = false; + public static BufferedImage convertToBufferedImage(Mat m) { // BufferedImage imageBufferedImage = new // BufferedImage(imageOpenCVImage.width(), imageOpenCVImage.height(), @@ -65,23 +81,98 @@ public static BufferedImage convertToBufferedImage(Mat m) { } public static BufferedImage convertToBufferedImage(URL u) throws IOException { - BufferedImage b = ImageIO.read(u); + BufferedImage b = read(u); logger.debug("read buffered image from url: "+b); if (b == null) { //ImageIO.read() can't handle 302 status code on url File tmpFile = ImageUtils.downloadImgFile(u); - b = ImageIO.read(tmpFile); + b = read(tmpFile); logger.debug("read buffered image from file: "+b); if (!tmpFile.delete()) { logger.warn("Temp file could not be deleted: " + tmpFile.getAbsolutePath()); } } if (b == null) { - throw new IOException("Could read buffered image from URL: " + u.toString()); + throw new IOException("Could not read buffered image from URL: " + u.toString()); } return b; } + + public static BufferedImage read(URL u) throws IOException { + if(USE_IMAGE_IO_READ_IMPL) { + return ImageIO.read(u); + } else { + try ( + InputStream is = u.openStream(); + ImageInputStream iis = ImageIO.createImageInputStream(is) + ) { + return read(iis); + } + } + } + + public static BufferedImage read(File f) throws IOException { + if(USE_IMAGE_IO_READ_IMPL) { + return ImageIO.read(f); + } else { + try (ImageInputStream iis = ImageIO.createImageInputStream(f)) { + return read(iis); + } + } + } + + /** + * Alternative implementation of ImageIO::read.
+ * If one image reader claims to be able to read an ImageInputStream but fails then, + * this method, in contrast to the ImageIO impl., will try with other readers before throwing an Exception. + * + * @param iis the ImageInputStream + * @return a BufferedImage or null only if no reader claimed to support the ImageInputSource + * @throws IOException + */ + private static BufferedImage read(ImageInputStream iis) throws IOException { + Iterator it = IIORegistry.getDefaultInstance().getServiceProviders(ImageReaderSpi.class, true); + BufferedImage bi = null; + List fails = new LinkedList<>(); + while(it.hasNext()) { + ImageReaderSpi rSpi = it.next(); + if(rSpi.canDecodeInput(iis)) { + logger.debug(rSpi.getPluginClassName() + " claims to support image source."); + ImageReader reader = rSpi.createReaderInstance(); + ImageReadParam param = reader.getDefaultReadParam(); + reader.setInput(iis, true, true); + try { + bi = reader.read(0, param); + } catch(IIOException iioe) { + logger.debug(reader.getClass().getCanonicalName() + " failed to read image source!"); + StackTraceElement ste = iioe.getStackTrace()[0]; + fails.add(reader.getClass().getCanonicalName() + ": " + iioe.getMessage() + + " (" + ste.getClassName() + "::" + ste.getMethodName() + " in " + ste.getFileName() + ")"); + } finally { + reader.dispose(); + } + if(bi != null) { + break; + } + } + } + if(!fails.isEmpty()) { + //Generate an error report + StringBuffer sb = new StringBuffer("ImageReaders failed to read the source!"); + for(String s : fails) { + sb.append("\n"+s); + } + if(bi == null) { + //All suitable readers failed. Throw IIOExceptions with the report + throw new IIOException(sb.toString()); + } else { + //Just log a warning + logger.warn(sb.toString()); + } + } + return bi; + } public static void loadOpenCV() throws Exception { @@ -126,15 +217,6 @@ public static void loadOpenCV() throws Exception { logger.debug("Loaded opencv ("+opencvlibname+") in t = "+(System.currentTimeMillis()-t0)); } - -// static { -// try { -// loadOpenCV(); -// } catch (Exception e) { -// e.printStackTrace(); -// System.exit(1); -// } -// } public static Mat convertToOpenCvImage(URL u) throws IOException { File tmpFile = ImageUtils.downloadImgFile(u); @@ -145,26 +227,15 @@ public static Mat convertToOpenCvImage(URL u) throws IOException { throw new IOException(e); } -// try { -// System.loadLibrary(Core.NATIVE_LIBRARY_NAME); -// } catch (java.lang.UnsatisfiedLinkError error) { -// try { -// System.loadLibrary("opencv_java2410"); -// } catch (UnsatisfiedLinkError error2) { -//// throw error; -// throw new IOException("Could not find opencv: "+Core.NATIVE_LIBRARY_NAME+"/"+"opencv_java2410"); -// } -// } - Mat imageOpenCVImage = null; try { - Class clazz = Class.forName("org.opencv.imgcodecs.Imgcodecs"); + Class clazz = Class.forName("org.opencv.imgcodecs.Imgcodecs"); Method method = clazz.getMethod("imread", String.class); imageOpenCVImage = (Mat) method.invoke(null, tmpFile.getAbsolutePath()); } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { try { logger.info("expect OpenCV 2.* - use other static loader"); - Class clazz = Class.forName("org.opencv.highgui.Highgui"); + Class clazz = Class.forName("org.opencv.highgui.Highgui"); Method method = clazz.getMethod("imread", String.class); imageOpenCVImage = (Mat) method.invoke(null, tmpFile.getAbsolutePath()); } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex2) { @@ -247,7 +318,7 @@ public static File downloadImgFile(URL imageUrl) throws IOException { public static File saveAsFile(Mat m, String path) throws IOException { try { - Class clazz = Class.forName("org.opencv.imgcodecs.Imgcodecs"); + Class clazz = Class.forName("org.opencv.imgcodecs.Imgcodecs"); Method method = clazz.getMethod("imwrite", String.class, Mat.class); Boolean res = (Boolean) method.invoke(null, path, m); if (res != null && res) { @@ -256,7 +327,7 @@ public static File saveAsFile(Mat m, String path) throws IOException { } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { try { logger.info("expect OpenCV 2.* - use other static loader"); - Class clazz = Class.forName("org.opencv.highgui.Highgui"); + Class clazz = Class.forName("org.opencv.highgui.Highgui"); Method method = clazz.getMethod("imwrite", String.class, Mat.class); Boolean res = (Boolean) method.invoke(null, path, m); if (res != null && res) { @@ -288,4 +359,75 @@ public static File saveAsFile(BufferedImage b, String path) throws IOException { throw new IOException("Could not write image to: " + path); } } + + // ImageIO related methods ========================================== + /** + * Java applications register plugins on the classpath automatically and in Tomcat the + * ContextListener (included in the Twelvemonkeys servlet artifact) can be used for that.
+ * Check issue #4 before using this method! + */ + public static void registerImageIOServices() { + logger.debug("registering image readers / writers"); + + IIORegistry registry = IIORegistry.getDefaultInstance(); + + // have to programmatically register tiff reader / writer in tomcat + registerImageIOService(registry, new TIFFImageWriterSpi(), javax.imageio.spi.ImageWriterSpi.class); + registerImageIOService(registry, new TIFFImageReaderSpi(), javax.imageio.spi.ImageReaderSpi.class); + + registerImageIOService(registry, new com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi(), + javax.imageio.spi.ImageWriterSpi.class); + registerImageIOService(registry, new com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi(), + javax.imageio.spi.ImageReaderSpi.class); + } + + /** + * @param registry the ImageIO registry to use + * @param provider the service provider object to be registered. + * @param category the category under which to register the provider. + */ + private static void registerImageIOService(IIORegistry registry, T provider, Class category) { + if(registry == null || provider == null || category == null) { + throw new IllegalArgumentException("An argument is null!"); + } + if(registry.registerServiceProvider(provider, category)) { + logger.debug("Registered IIOServiceProvider: " + provider.getPluginClassName()); + } else { + /* + * Actually ImageIO unregistered the old instance of the provider and registered the new one here! + */ + logger.debug("Replaced existing IIOServiceProvider in registry: " + provider.getPluginClassName()); + } + } + + public static void listImageIOServices() { + IIORegistry registry = IIORegistry.getDefaultInstance(); + logger.debug("image-io services:"); + Iterator> cats = registry.getCategories(); + while (cats.hasNext()) { + Class cat = cats.next(); + logger.debug("image-io category = " + cat); + + Iterator providers = registry.getServiceProviders(cat, true); + while (providers.hasNext()) { + logger.debug("image-io provider = " + providers.next()); + } + } + } + + public static void testReaders() throws IOException { + String[] formats = { "JPEG", "TIFF", "TIF", "PNG" }; + for (String format : formats) { + logger.info("testing readers for format: " + format); + Iterator readers = ImageIO.getImageReadersByFormatName(format); + + if (!readers.hasNext()) { + throw new IOException("No reader found for format: " + format); + } + + while (readers.hasNext()) { + logger.info("reader: " + readers.next()); + } + } + } } diff --git a/src/test/java/eu/transkribus/interfaces/native_wrapper/NativeProxyUtilsTest.java b/src/test/java/eu/transkribus/interfaces/native_wrapper/NativeProxyUtilsTest.java index b51b762..7f8dea6 100644 --- a/src/test/java/eu/transkribus/interfaces/native_wrapper/NativeProxyUtilsTest.java +++ b/src/test/java/eu/transkribus/interfaces/native_wrapper/NativeProxyUtilsTest.java @@ -18,7 +18,6 @@ public void setupEnvironment() { NativeLibRegistry.INSTANCE.addDirsToPath("/usr/local/share/OpenCV/java/"); NativeLibRegistry.INSTANCE.loadTranskribusInterfacesLib(); NativeLibRegistry.INSTANCE.loadLibrary("opencv_java310"); - Image.registerImageIOServices(); } @Test diff --git a/src/test/java/eu/transkribus/interfaces/types/ImageTest.java b/src/test/java/eu/transkribus/interfaces/types/ImageTest.java index bbf1f17..de1aa87 100644 --- a/src/test/java/eu/transkribus/interfaces/types/ImageTest.java +++ b/src/test/java/eu/transkribus/interfaces/types/ImageTest.java @@ -8,25 +8,27 @@ import org.slf4j.LoggerFactory; import eu.transkribus.interfaces.types.Image.Type; +import eu.transkribus.interfaces.types.util.ImageUtils; public class ImageTest { private static final Logger logger = LoggerFactory.getLogger(ImageTest.class); - - // public static void main(String[] args) { - // System.out.println(PackageUtil.getVendor()); - // System.out.println(PackageUtil.getVersion()); - // System.out.println(PackageUtil.getSpecificationTitle()); - // } - + +// @Before +// public void init() { +// ImageUtils.listImageIOServices(); +// ImageUtils.registerImageIOServices(); +// ImageUtils.listImageIOServices(); +// } + @Test public void testReaders() throws IOException { - Image.testReaders(); + ImageUtils.testReaders(); } -// @Test + @Test public void testFromUrlToOpenCv() throws IOException { logger.debug("testFromUrlToOpenCv"); @@ -40,23 +42,35 @@ public void testFromUrlToOpenCv() throws IOException { logger.info("Created opencv image for url: " + url); } - } - - @Test - public void testFromUrlToBufferedImage() throws IOException { - logger.debug("testFromUrlToBufferedImage"); + @Test + public void testFromUrlToBufferedImage() throws IOException { + logger.debug("testFromUrlToBufferedImage"); - URL[] urls = { - new URL("https://dbis-thure.uibk.ac.at/f/Get?fileType=orig&id=VJCQMQBNZFDFCZMAJZHKNJKW") }; + URL[] urls = { new URL("https://dbis-thure.uibk.ac.at/f/Get?fileType=orig&id=VJCQMQBNZFDFCZMAJZHKNJKW") }; - for (URL url : urls) { - Image img = new Image(url); + for (URL url : urls) { + Image img = new Image(url); + try { img.createType(Type.JAVA); - - logger.info("Created java image for url: " + url); + } catch (Exception e) { + logger.error("Could not create BufferedImage from URL resource!", e); + throw e; } + logger.info("Created java image for url: " + url); } + + } + + /** + * A main for testing the behavior without JUnit + * + * @param args + * @throws IOException + */ + public static void main(String[] args) throws IOException { + new ImageTest().testFromUrlToBufferedImage(); + } }