Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable chroma subsampling in JPEGCodec #29

Merged
merged 4 commits into from
Nov 30, 2023
Merged

Conversation

iwbh15
Copy link
Contributor

@iwbh15 iwbh15 commented Jun 29, 2023

Follow up to PR #26

see #26 (comment) :

There is another option that has a decisive influence on the Jpeg image quality: 'Chroma Subsampling'.
Especially with biological/microscopic images in RGB format (transmitted light or fluorescence) chroma subsampling leads to intolerable image errors.
Chroma subsampling should therefore - in my view - be deactivatable in libraries like BioFormats.

Unfortunately, access to the 'Chroma Sampling' property is not configurable via the 'ImageWriteParam' (like e.g. the JpegQuality).
Instead, it can be accessed via the 'IIOMetadata'.

Unfortunately, so far have not been able to find a direct and simple description.
But it seems to exist in some form in the "JPEG Metadata Format Specification and Usage Notes".

In ImageJ, access to the 'Chroma Sampling' property is already integrated.
see https://imagej.nih.gov/ij/notes.html
1.52s 10 December 2019
Thanks to Peter Haub, chroma subsampling is disabled when saving in JPEG format if the Quality setting (Edit>Options>Input/Output) is 90 or higher, resulting in higher quality images but larger file sizes.

For example, chroma subsampling can be disabled with the following macro command:
JpegWriter.disableChromaSubsampling(true);
By default, 'Chroma Sampling' is disabled in ImageJ when JpegQuality >= 90.

My source at that time was Apache/Shindig.
See:
https://github.com/imagej/ImageJ/blob/d82104f1c55290af56d5afc29004cf8298055067/ij/plugin/JpegWriter.java#L86
// Disable JPEG chroma subsampling
// http://svn.apache.org/repos/asf/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/BaseOptimizer.java
// http://svn.apache.org/repos/asf/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/image/JpegImageUtils.java

There is a test script from Wayne here https://imagej.nih.gov/ij/macros/js/JpegQualityTests.js .
It can be called in the ImageJ menu under: Help>Examples>Java Scripts> JPEG Quality Plot

I think it would be worth and important to have the 'Chroma Sampling' property in mind, especially with the current code changes to support variable Jpeg quality.

Unfortunately I lack the overview of ome-codecs to elaborate this hint more concretely.

Here are some further and interesting links:
https://stackoverflow.com/questions/14149739/disable-java-imageio-chroma-subsampling
https://stackoverflow.com/questions/72581932/why-is-the-color-of-my-image-changed-after-writing-it-as-a-jpg-file
libvips/libvips#538
http://poynton.ca/PDFs/Chroma_subsampling_notation.pdf

@iwbh15
Copy link
Contributor Author

iwbh15 commented Jun 29, 2023

Important Note:
The change made in ome.codecs.CodecOptions.java must also be inserted into loci.formats.codec.CodecOptions.
Can somebody please create this change in bioformats.

This is because loci.formats.codec.CodecOptions will be wrapped to ome.codecs.CodecOptions.java
in WrappedCodec.java (see https://github.com/ome/bioformats/blob/971fb341745e5c7e4b0cbf8c2af59deacc73db75/components/formats-bsd/src/loci/formats/codec/WrappedCodec.java#L139)
(called from TiffCompression.java, see https://github.com/ome/bioformats/blob/971fb341745e5c7e4b0cbf8c2af59deacc73db75/components/formats-bsd/src/loci/formats/tiff/TiffCompression.java#L360)

@dgault dgault added the include label Jun 29, 2023
@dgault
Copy link
Member

dgault commented Jun 29, 2023

Thanks @iwbh15 for getting this PR opened, we are currently preparing a Bio-Formats 6.14.0 release for next week. Once that is out we we will aim to get this reviewed and tested for a Bio-Formats 7.0.0 which will follow shortly after.

@iwbh15 iwbh15 changed the title Disable chroma subsampling in JEPGCodec Disable chroma subsampling in JPEGCodec Jun 29, 2023
@sbesson sbesson closed this Jul 6, 2023
@sbesson sbesson reopened this Jul 6, 2023
melissalinkert added a commit to melissalinkert/bioformats that referenced this pull request Jul 18, 2023
Copy link
Member

@melissalinkert melissalinkert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation/comment style aside, I am concerned that I could not find a way to actually make use of this feature. It's not clear to me if that's expected, or if I'm just not using the right example to test. See also ome/bioformats#4057

src/main/java/ome/codecs/JPEGCodec.java Outdated Show resolved Hide resolved
src/main/java/ome/codecs/JPEGCodec.java Show resolved Hide resolved
// 3 child nodes if the color representation is YCbCr, and
// 4 child nodes if the color representation is YCMK.
// This subsampling applies only to YCbCr.
if (node.getNodeName().equalsIgnoreCase("sof") && node.hasChildNodes() && node.getChildNodes().getLength() == 3) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not so far been able to create an example where the SOF node has 3 child nodes. I believe this is because img cannot have the relevant ColorSpace/ColorModel (see https://github.com/ome/ome-codecs/blob/master/src/main/java/ome/codecs/gui/AWTImageTools.java#L1077 and https://github.com/ome/ome-codecs/blob/master/src/main/java/ome/codecs/gui/AWTImageTools.java#L1101), but perhaps I misunderstand.

Note I tested with ome/bioformats#4057 and the snippet therein. Is there an example that shows this code working as intended?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I)
Basically, your test snippet works.

The problem is that the setting of the option disableChromaSubsampling is lost on the way from the snippet to the JPEGCodec.java.

Namely in the constructor
public CodecOptions(CodecOptions options)
and in the function
private static void copyOptions(CodecOptions src, ome.codecs.CodecOptions dest)

In both places the code must be supplemented as follows:
dst.disableChromaSubsampling = src.disableChromaSubsampling;
(of course also with corresponding declaration of the variable disableChromaSubsampling).

Alternatively, you can also temporarely hardcode the option disableChromaSubsampling in the JPEGCodec.java
disableChromaSubsampling = true / false;

II)
In the structured fake data (where R = G = B) the effect of the disabled chroma subsampling is not huge.
Therefore I have use the image lena-std.tif for my test (available in the ImageJ example images zip archive).
(Of course you can use any other RGB tif image.)

I used a modified snipped for the test.

The test shows that the resulting images (with / without chroma subsampling) are different and that the image saved with disableChromaSubsampling is closer to the original, as expected.

(The resulting images can be opened correctly in ImageJ and QuPath. But it seems that Windows Preview and other viewers misinterpret the images as YCbCr. I could not find the necessary settings to turn this off. However, this is not relevant for the test.)

III)
Apart from the basic functionality of the option disableChromaSubsampling, I was surprised by the fact that Tif images saved with JPEG compression (at high quality settings) are larger than the original uncompressed image.
Apparently, the image is saved line by line, which does not allow the full potential of JPEG compression to be realized. I have not investigated this circumstance further. But it should be considered to localize the cause of this behavior, because in this form JPEG compression is not really helpful.

At this point, perhaps a word about the application:
WSI images - which are to a large extent RGB transmitted light images - are still saved by various manufacturers as Tif with JPEG compression. From my point of view it would be helpful if this format could also be written - regardless of the disadvantageous JPEG artifacts. It is precisely to minimize these artifacts that switching off chroma subsampling serves and to be able to create Tif images with JPEG compression with high quality.

IV)
As can be seen from the tests, the color representation does not have to be explicitly YCbCr.
The listed node conditions are also fulfilled with RGB representation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note:
" ... the image is saved line by line, which does not allow the full potential of JPEG compression to be realized. ..."

In order to investigate this issue further, I have temporarily modified the function writeImage in loci.formats.tiff.TiffSaver.java.

I have temporarily inserted

	if (compression.getCodecName().equals("JPEG"))      
	   ifd.putIFDValue(IFD.ROWS_PER_STRIP, new long[] {8});

directly after the line
compression = ifd.getCompression();

This modification significantly reduces the size of the Jpeg compressed file.
In my test (with jpegquality = 0.75 and disableChromaSubsampling = false) the file size reduced from 585 kB to 83 kB.

Inline usernames removed
Error in Boolean query corrected.
@dgault dgault self-requested a review September 4, 2023 13:24
Copy link
Member

@melissalinkert melissalinkert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on testing in ome/bioformats#4057 (comment), I think I'm OK with this now, particularly as the default behavior is unchanged. It would be good to have a second reviewer though before merging.

Copy link
Member

@dgault dgault left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested along with ome/bioformats#4057

Using the code snippet from ome/bioformats#4057 (comment) as the base for the tests.

I converted a number of the samples from https://imagej.nih.gov/ij/download/sample-images.zip as well as https://downloads.openmicroscopy.org/images/TIFF/libtiff/

All of the samples using the original chroma subsampling enabled are slightly smaller than with the new option to disable chroma subsampling.
When writing uncompressed data the files are all unaffected as expected.

Using the code snippet from ome/bioformats#4057 (comment), the 2 outputs were identical with matching MD5's.

With the input modified to use test&sizeC=3&rgb=3&interleaved=true.fake, then the default behaviour with the subsampling is slightly smaller than with sub sampling disabled

All the sample files can be opened and displayed as expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants