Open robin-snt opened 4 years ago
Hi @robin-snt
Thanks for the notice. I already had WebP on the radar, unfortunately there does not seem to be a decoder library I can use as a dependency to decode WebP images. All the stuff on NPM is either really dubious or uses C dependencies which do not work well browsers at the moment.
Unless someone is willing to investigate or has a good alternative this will have to wait.
Fully understand. Thanks for supporting a really nice library btw.
This seems like a really good case for WASM tough.
This seems like a really good case for WASM tough.
Agreed. Unfortunately I have very limited experience with both development and support of wasm.
+1 for WebP support, this lib would allow me to get rid of some server-side items. WebP is truly superior to all other compression options for Cloud Optimized GeoTIFFs.
I'm wondering if, at the very least, can tiles be pulled and decoded by.the browser? WebP support is pretty much complete in all major browsers.
Of course, I don't really understand the intricacies of GeoTiff decoding, so I'm guessing that's not possible
Hi @marty-sullivan
This is actually hard to say! One problem is that WebP inside TIFF is not standardized at all, unlike e.g JPEG compressed images.
With JPEG the answer was: it depends. In some images you could just take the raw tile and interpret it as a standalone JPEG, but if some of the information is actually stored in TIFF Tags instead (like https://www.awaresystems.be/imaging/tiff/tifftags/jpegtables.html) this would not be possible.
I simply don't know if WebP in TIFF uses any additional Tags.
If you have an image, you could actually try this. Here is some (untested, pseudo-) code to do this:
const tiff = await fromUrl(sourceUrl);
const image = await tiff.getImage();
const rawData = await image.getTileOrStrip(0, 0); // this is now an ArrayBuffer
// create a Blob to later read from
const blob = new Blob([rawData]);
// get the image from the HTML document to load the WebP into
const htmlImage = document.getElementById('image');
// construct a filereader to read the Blob as a file
const reader = new FileReader();
reader.onload = function(e) {
htmlImage.src = e.target.result;
};
reader.readAsDataURL(blob);
Hi @constantinius
This does seem like a cool experiment anyway :)
I am able to load an example WebP GeoTIFF and it seems like it is able to read the metadata fine. I see all the expected tiling information in the Image object.
However, when I try to run await image.getTileOrStrip(0, 0);
I get the following stack trace:
Uncaught (in promise) TypeError: Cannot read property 'decode' of undefined
at e.<anonymous> (geotiffimage.js:265)
at l (runtime.js:45)
at Generator._invoke (runtime.js:274)
at Generator.forEach.e.<computed> [as next] (runtime.js:97)
at n (asyncToGenerator.js:3)
at s (asyncToGenerator.js:25)
After looking at geotiffimage.js, I I have a few ideas of what is happening here but what is your take?
@marty-sullivan
Ah! Of course, I can't just drop half of the parameters and then expect it to work 🙄
I also realized, that getTileOrStrip
will try to uncompress the image so this is also not the right function to call anyhow. So we need to go deeper and access the file directly.
This is an updated example, assuming that all samples/channels are interlaced and not separate:
const tiff = await fromUrl(sourceUrl);
const image = await tiff.getImage();
// ---- start new stuff
const index = (y * numTilesPerRow) + x;
let offset;
let byteCount;
if (image.isTiled) {
offset = image.fileDirectory.TileOffsets[index];
byteCount = image.fileDirectory.TileByteCounts[index];
} else {
offset = image.fileDirectory.StripOffsets[index];
byteCount = image.fileDirectory.StripByteCounts[index];
}
const rawData = await image.source.fetch(offset, byteCount);
// ---- end new stuff
// create a Blob to later read from
const blob = new Blob([rawData]);
// get the image from the HTML document to load the WebP into
const htmlImage = document.getElementById('image');
// construct a filereader to read the Blob as a file
const reader = new FileReader();
reader.onload = function(e) {
htmlImage.src = e.target.result;
};
reader.readAsDataURL(blob);
Let me know if this worked!
@constantinius
It worked!
btw, the GeoTIFF I'm using is generated by GDAL 3.2 as a COG (Cloud Optimized GeoTIFF) using the GoogleMapsCompatible option and WEBP compression (lossy). If you were to choose a WEBP standard, I'd say this would be a good one.
Here's the final code, just some minor tweaks from what you had:
const tiff = await GeoTIFF.fromUrl('/my_geotiff.tif');
const image = await tiff.getImage();
const numTilesPerRow = Math.ceil(image.getWidth() / image.getTileWidth());
const numTilesPerCol = Math.ceil(image.getHeight() / image.getTileHeight());
const x = 0;
const y = 0;
const index = (y * numTilesPerRow) + x;
let offset;
let byteCount;
offset = image.fileDirectory.TileOffsets[index];
byteCount = image.fileDirectory.TileByteCounts[index];
const rawData = await image.source.fetch(offset, byteCount);
const blob = new Blob([rawData]);
const reader = new FileReader()
const html_image = document.getElementById('webpimg');
reader.onload = function(e) {
var b64_data = 'data:image/webp;' + e.target.result.split(';')[1];
html_image.src = b64_data;
}
reader.readAsDataURL(blob);
Awesome!
Would you share your image? Or the GDAL commands to create it?
As this seems to be a viable way to decode WebP images in the Browser, I'm thinking of a way to make this re-usable and plug this in to the normal reading process (at least for Browsers).
Sure, I create two formats of these COGs, one in the native projection and another in web mercator to be compatible with the various map SDKs out there (GoogleMapsCompatible). QUALITY can be set to 100 to enable LOSSLESS mode for WEBP.
Here is the Python GDAL command I use to create the COG in "GoogleMapsCompatible" scheme:
gdal.Translate(
destName=mercator_tiled_path,
srcDS=geotiff_path,
format='COG',
creationOptions=[
'COMPRESS=WEBP',
'QUALITY=95',
'NUM_THREADS=ALL_CPUS',
'RESAMPLING=NEAREST',
'OVERVIEWS=AUTO',
'TILING_SCHEME=GoogleMapsCompatible',
'ZOOM_LEVEL_STRATEGY=UPPER',
],
)
and the native projection:
gdal.Translate(
destName=native_tiled_path,
srcDS=geotiff_path,
format='COG',
creationOptions=[
'COMPRESS=WEBP',
'QUALITY=95',
'NUM_THREADS=ALL_CPUS',
'RESAMPLING=NEAREST',
'OVERVIEWS=AUTO',
'TILING_SCHEME=CUSTOM',
'ZOOM_LEVEL_STRATEGY=UPPER',
]
)
@marty-sullivan
I created a PR for this: #194
Can you please test this and let me know if this works for you?
Caveat: it only works in the browser that support WebP
I'm happy to test but I don't have a good environment to build JS modules from source at the moment. Can you provide a CDN link or built code for this branch to work with?
@marty-sullivan There you go: geotiffjs-webp.zip
@marty-sullivan did you have a chance to test?
@constantinius I did quickly test the other day and was going to return to it but I think I ran into the same issue as above await image.getTileOrStrip(0, 0)
results in TypeError: Cannot read property 'decode' of undefined
when I expected the tile to be returned.
I could be doing something different than you expect though. If this behavior is expected, what would your recommended test be?
@marty-sullivan No, that is not expected. Do you have a complete stacktrace for that?
@constantinius It's the same trace as from the above trace when I first tried it. I just went through and confirmed that webimage.js is available and that the compression code you're looking for matches that in my geotiff (50001) so it should be calling the right decoder.
I'm mainly testing using Chrome 88.0.4324.96, but also tried in Firefox 85.0.1 and got a similar, but slightly different trace:
Uncaught (in promise) TypeError: n is undefined
e geotiffimage.js:264
h runtime.js:45
_invoke runtime.js:274
r runtime.js:97
Babel 2
n
c
@marty-sullivan
I think the issue is that no decoder is passed to the function.
import WebImageDecoder from 'geotiff/compression/webimage';
// ...
await image.getTileOrStrip(0, 0, 0, new WebImageDecoder());
Would you mind testing this?
Is there any update on this issue ?
@constantinius Does your solution work for any COG produced by GDAL ?
Seems like a really useful format for quicklooks and presentational purposes.
https://medium.com/@_VincentS_/do-you-really-want-people-using-your-data-ec94cd94dc3f