loris-imageserver / loris

Loris IIIF Image Server
Other
209 stars 87 forks source link

Bitonal images without PIL im.mode '1' rendering gray #193

Open ghukill opened 8 years ago

ghukill commented 8 years ago

Before submitting a pull request, thought submitting an issue might be a better place to start. We had a headscratcher with JP2s, where when originating from bitonal TIFFs, were rendering gray.

Example JP2 rendering gray, where ideally it'd be white: http://digital.library.wayne.edu/loris/fedora:wayne:Afkestie1906b50081986%7CIMAGE_7_JP2/full/full/0/default.jpg

The original bitonal TIFF, rendering through Loris, showing normal tones: http://digital.library.wayne.edu/loris/fedora:wayne:Afkestie1906b50081986%7CIMAGE_7/full/full/0/default.jpg

To demonstrate that it's only JP2s originating from bitonal images, here's one that came from an RGB TIFF that renders normally: http://digital.library.wayne.edu/loris/fedora:wayne:Afkestie1906b50081986%7CIMAGE_6_JP2/full/full/0/default.jpg

The imagemagick convert command we're using used to generate JP2s: convert {input} {output}[256x256].jp2

Hilariously, perhaps you've bumped up against this too!?

As is the case with the TIFF example above, Loris handles bitonal images just fine if the im.mode is '1'. The problem arises when it's not.

Found an approach that seems to work. PIL allows you to check image band extremes with the im.getextrema() method. All of our originally bitonal images, when not im.mode '1', have extremes of ( (0,192), (0,192), (0,192) ). Putting in a check for that im.getextrema() signature, and if detected, converting to im.mode '1' with the appropriate dithering seems to do the trick. Images render much closer to a non-bitonal image, and performance doesn't seem to take much of a hit.

Here's a bitonal JP2, without im.mode '1', rendering correctly with that check in place: http://digital.library.wayne.edu/loris_dev/wayne:Afkestie1906b50081986%7CIMAGE_7_JP2/full/full/0/default.jpg

And a non bitonal JP2, with the check in place but not firing (because the im.getextrema() values are much more diverse than 0's and 192's): http://digital.library.wayne.edu/loris_dev/wayne:Afkestie1906b50081986%7CIMAGE_6_JP2/full/full/0/default.jpg

Worth pursuing or issuing a pull request? or maybe better left addressed locally with our Loris instance? One gotcha I can see, however infinitesimally small, would be a non-bitonal image with im.getextrema() that is ( (0,192), (0,192), (0,192) ).

jpstroop commented 8 years ago

Hilariously, perhaps you've bumped up against this too!?

That was related to something slightly different. In the description of qualities in the 1.0 version of the API we said that bitonal images had to be "1 bit per pixel". When I joined the editorial team for 1.1 I was pretty sure that wasn't possible with JPEGs, so that post is an artifact of my trying confirm without digging into the JPEG spec :smile:.

I agree that this case needs handling, but I think there might be something up with the color management of your JP2s as well, because: http://libimages.princeton.edu/loris2/AC125%2Fc0008%2F00000006.jp2/full/full/0/bitonal.jpg

FWIW, If you're using a newer version of ImageMagick, you might be OK, but older versions used JasPer, which was a very flawed JP2 implementation, for encoding JP2s. Newer versions use OpenJpeg, which is likely to be compliant with the spec. We use Kakadu for our encoding, and (again, FWIW) you can see our "recipes" here. That example image above was probably encoded with the 20_1_gray recipe (20_1 = 20:1 compression), and I think the -jp2_space sLUM param/arg is the key.

Found an approach that seems to work. PIL allows you to check image band extremes with the im.getextrema() method.

I can't immediately think of anything better. That seems like it would be a pretty expensive check, but presumably you'd only need to do it here? I'd say go ahead and submit a PR.

ghukill commented 8 years ago

Thanks for such a detailed response. I think you're absolutely right that our JP2s could use some color management. We have been making JP2 derivatives upon ingest, and they've looked and worked great, until now. But this is a good opportunity to revisit that process; thanks for the Kakadu "recipes". To be honest, not sure what library we delegated imagemagick to use at the time, it's been awhile now.

As for the check in Loris, maybe it'd make sense to sit on it for a bit? I was wary to even mention it, as I had feared it was specific to our JP2s. Perhaps they rendered just as they should, given the odd JP2s Loris was working with. We'll try some alternative derivates, see how they look, and report back. Thanks again for all your thoughts and insight!

jpstroop commented 8 years ago

Whichever. I'm not opposed to putting the check in. JP2 is complicated enough that having these kinds of patches is really just practical--there are things like this all over the place.

ghukill commented 8 years ago

Here's the rub, I think it would have to be outside the image_request.quality check here to be effective.

Our JP2s are coming through with im.mode 'RGB', and unfortunately, we don't always have the ability to explicitly request 'bitonal' from the front-end (mixed thumbnails, or some of our digitized books even mix color and bitonal images).

That would mean it would fire on many (if not most) image requests. But I can got ahead and submit a PR, and see how it looks and feels.

ghukill commented 8 years ago

Cue truck reverse beeps. I had a pull request ready to go, but thought I should dig in a bit more and see if the .getextrema() check covered all instances. It didn't. Something about our JP2's, the highest level of tiles in the file did not have the signature ((0,192),(0,192),(0,192)) bands. They would be uniform, in that they generally start at 0, but would terminate at an unknown maximum (e.g. ((0,243),(0,243),(0,243)) or ((0,168),(0,168),(0,168)), etc.). This would mean checking for repeating bands in .getextrema(), which improbable for a color image, certainly isn't impossible.

This has convinced me it's really more of a hack for ill-formed JP2s, rather than a prudent check, and the problem is really with the JP2s themselves. But thanks again for thinking through this, much appreciated, and will update you on other channels or a new issue if we discover anything of note.

ghukill commented 8 years ago

Just to update this - we've regenerated our JP2s via your kindly shared recipes, and they all render beautifully now, further supporting the idea that fixing the JP2s was the better route than trying to sniff out poorly formed, bitonal JP2s.