TwelveMonkeys icon indicating copy to clipboard operation
TwelveMonkeys copied to clipboard

Decoding of lossless JPEG became slow

Open MichaelSchaich opened this issue 2 years ago • 1 comments

Describe the bug When updating Twelvemonkeys imageio from version 3.4.1 to version 3.8.2, decoding of lossless JPEG slowed down by a factor of almost 10.

Version information

  1. The version of the TwelveMonkeys ImageIO library in use. 3.8.2; performance used to be ok with 3.4.1

  2. The exact output of java --version (or java -version for older Java releases). openjdk 11.0.14.1 2022-02-08 LTS OpenJDK Runtime Environment Corretto-11.0.14.10.1 (build 11.0.14.1+10-LTS) OpenJDK 64-Bit Server VM Corretto-11.0.14.10.1 (build 11.0.14.1+10-LTS, mixed mode)

  3. Extra information about OS version, server version, standalone program or web application packaging, executable wrapper, etc. Please state exact version numbers where applicable. OS: Microsoft Windows 10 Version 21H2 (Build 19044.1706)

To Reproduce Steps to reproduce the behavior:

  1. Compile the below JUnit test sample code
  2. Download the sample image file jpegLossless.dcm (i found lossless compressed JPEG only wrapped in DICOM)
  3. Download ImageReaderFactory64Bit.properties and place it in resources\dicom. Needed to make sure the DicomImageReader uses the Twelvemonkeys JPEGImageReader for decoding JPEG.
  4. Run the JUnit test.
  5. If compiled with Twelvemonkeys version 3.8.2, i get for typical output testReaderPerformance: ImageReader com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReader needed 2587 milliseconds to read testfiles/jpegLossless.dcm If compiled with Twelvemonkeys version 3.4.1, typical output times are like testReaderPerformance: ImageReader com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReader needed 318 milliseconds to read testfiles/jpegLossless.dcm

Expected behavior Decode times like with Twelvemonkeys version 3.4.1, i.e. around 300 milliseconds for the attached sample file instead of about 2000 milliseconds

Example code -------------------------------------------------------------------------------------------------------------- { package com.agfa.hap.ddc.util;

import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail;

import java.awt.image.BufferedImage; import java.io.File; import java.lang.reflect.Field; import java.util.Iterator;

import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.stream.FileImageInputStream;

import org.apache.log4j.ConsoleAppender; import org.apache.log4j.Logger; import org.dcm4che3.imageio.plugins.dcm.DicomImageReader; import org.junit.Test;

public class TestImageReaderPerformance {

private static final Logger LOGGER = Logger.getLogger(TestImageReaderPerformance.class);
static {
	LOGGER.addAppender(new ConsoleAppender(new org.apache.log4j.PatternLayout(), "System.out"));
}


@Test
public void testReaderPerformance() throws Exception {
	// Configure to Load available imageReaders by using ImageIO
	System.setProperty("dcm4che.useImageIOServiceRegistry", "true");
	// Set the list of Readers to be used by dcm4che
	System.setProperty("org.dcm4che3.imageio.codec.ImageReaderFactory", "dicom/ImageReaderFactory64Bit.properties");
	try {
		String testfile = TestImageReaderPerformance.class.getResource("/testfiles/jpegLossless.dcm").getFile();
		ImageReader imageReader = null;
		Iterator<ImageReader> it  = ImageIO.getImageReadersBySuffix("jpg");
		it  = ImageIO.getImageReadersBySuffix("dcm");
		while (it.hasNext() ) {
			imageReader = it.next();
			if (imageReader != null) {
				Class<? extends ImageReader> clazz = imageReader.getClass();
				if (it.hasNext()) {
					//skip all DICOM reader except "org.dcm4che.imageio.plugins.dcm"
					if (!clazz.getPackage().getName().equalsIgnoreCase("org.dcm4che.imageio.plugins.dcm")){
						continue; //it's not the expected DICOM reader. Skip to the next one
					}
					//this DICOM reader is OK. Stop iteration and return this reader
					break;
				}
			}
		}
		assertNotNull(imageReader);
		String readerName = imageReader.getClass().getName();
		assertTrue("ImageReader should be org.dcm4che3.imageio.plugins.dcm.DicomImageReader, but is "+readerName, imageReader instanceof org.dcm4che3.imageio.plugins.dcm.DicomImageReader);
		imageReader.setInput(new FileImageInputStream(new File(testfile)));

		long time = System.currentTimeMillis();
		BufferedImage bi = imageReader.read(0);
		time = System.currentTimeMillis() - time;
		
		Field decompressorField = DicomImageReader.class.getDeclaredField("decompressor");
		decompressorField.setAccessible(true);
		ImageReader decompressor = (ImageReader)(decompressorField.get(imageReader));
		readerName = decompressor.getClass().getName();

		LOGGER.error("testReaderPerformance: ImageReader "+readerName+" needed "+time+" milliseconds to read "+testfile);
		assertNotNull(bi);
		long timeLimit = 1000;
		assertTrue("testReaderPerformance: Time to read "+testfile+": "+time+" exceeds "+timeLimit+" milliseconds", time<timeLimit);
	} catch (Exception exception) {
		fail();
	}
}

} } End example code --------------------------------------------------------------------------------------------------------------

** DicomImageReader configuration file ImageReaderFactory64Bit.properties** ImageReaderFactory64Bit.zip

Image sample file jpegLossy.dcm jpegLossy.zip

MichaelSchaich avatar Jul 19 '22 17:07 MichaelSchaich

Hi Michael,

I believe this is the same issue as #687, where the removal of the BufferedImageInputStream has led to a performance regression. I am working on creating replacement streams that will make this faster, but it will take some time...

In the meantime, things will go faster if you use a different stream implementation (see my comment in the other issue) if that is possible for your use case.

haraldk avatar Aug 02 '22 15:08 haraldk

New stream support is out in the 3.9.0 release. Feel free to give it a try and report back! 😀

haraldk avatar Oct 15 '22 11:10 haraldk

Hi Harald, sorry for the late reply, i have been too busy with other tasks. The perforance problem on decompressing lossless JEPG persists in Twelvemonkeys 3.9, checked for 3.9.0 and 3.9.4. See attached file PerformanceDifferentVersions_JPEGLossless.txt, where i have added the new test results. I have a new machine and decoding is about 3 times faster, but still there is about a factor 10 between decoding times with twelvemonkeys versions 3.4 and 3.9. ImageIO.setUseCache(false) made no difference.

MichaelSchaich avatar Jan 12 '23 15:01 MichaelSchaich

Attaching failed, so i post the test results here:

Tests from July 2022 (typical values): Bound with Twelvemonkeys version 3.4.1: testReaderPerformance: ImageReader com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReader needed 318 milliseconds to read /C:/SVN/DDC/branches/ddc-3.17.x/com.agfa.hap.ddc_core/target/test-classes/testfiles/jpegLossless.dcm

Bound with Twelvemonkeys version 3.8.2: testReaderPerformance: ImageReader com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReader needed 2587 milliseconds to read /C:/SVN/DDC/branches/ddc-3.17.x/com.agfa.hap.ddc_core/target/test-classes/testfiles/jpegLossless.dcm

Tests from Jan 11 2023, different machine, typical values: Bound with Twelvemonkeys version 3.4.1: testReaderPerformance: ImageReader com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReader@7216fb24 needed 105 milliseconds to read /C:/SVN/DDC/branches/ddc-3.18.x/com.agfa.hap.ddc_core/target/test-classes/testfiles/jpegLossless.dcm

Bound with Twelvemonkeys version 3.9.4: testReaderPerformance: ImageReader com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReader@66ce957f needed 1240 milliseconds to read /C:/SVN/DDC/trunk/com.agfa.hap.ddc_core/target/test-classes/testfiles/jpegLossless.dcm

MichaelSchaich avatar Jan 12 '23 15:01 MichaelSchaich

Hello Harald, due to the vulnerability 'Found security vulnerability CVE-2021-23792 with severity >= 9 (severity = 9.8)' (see https://nvd.nist.gov/vuln/detail/CVE-2021-23792) and the issue #288 'Exif orientation' we cannot stick with twelvemonkeys version 3.4.1 much longer. Thus, is there a chance for progress on this Lossless JPEG performance issue

MichaelSchaich avatar Apr 12 '23 15:04 MichaelSchaich

Hi Michael,

As your test case involves far more than just TwelveMonkeys, it's a bit hard for me to see exactly what makes things slow in your case. The stream you pass to the DICOM plugin and how the DICOM plugin uses the JPEG plugin might affect these things. I have very limited spare time these days, so anything you can do to make it easier to pinpoint, will also help resolve this faster.

I understand you want to upgrade to the latest version as soon a possible (I'm not sure I understand why 288 is relevant in this discussion).

haraldk avatar Apr 14 '23 07:04 haraldk