-
Notifications
You must be signed in to change notification settings - Fork 37
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
Layers appear out of sync in WMS and WCS if band math is used in styling #1041
Comments
Thanks Christoph. As it happens we've had some very similar conversations around WMS/WCS interoperation internally at DEA recently. The problem is that WMS and WCS are very different protocols with very different data models and intended use cases. OWS does blur those distinctions somewhat in an effort to promote consistency between them, but there's only so far we can go while still being standards compliant for both. The main issues complicating consistency are:
With that mind however, it should be possible to collect the index styles offered by a given layer, and advertise those indexes as additional "raw bands" on the corresponding coverage in WCS. That way your WCS query above (which doesn't specify measurements and therefore gets all available measurements) would get the raw bands AND the calculated style indexes. But multi-date handler indexes in WCS (as previously requested by @robbibt ) are probably unachievable without breaking WCS's request model. |
I haven't really used WCS before, so your considerations are probably a lot more profound, but they are also going a lot further and are a lot more fundamental than what I actually meant. I don't need multi-date stuff or more than one style per layer, I just want a 2D raster of the values that I see colour-coded in the WMS. And that's my main point: Currently, the output of the two services is fundamentally different when an index function is involved. When I see an NDVI map in the WMS, and specify The problems you mentioned are valid if one wanted to fully map every feature of each standard to the other. But I don't see how they would block fixing the problem I described?
Because I needed this now, I plunged forward and constructed a hacky tweak that works for our use case. For this I replaced the standard WCS TIFF handler in "wcs": {
"formats": {
"GeoTIFF": {
"renderers": {
"1": "datacube_ows.wcs1_utils.get_tiff" with a self-written function "1": "ows_refactored.s2_vi.wcs.as_tiff_wcs1", that does essentially this: from datacube_ows.wcs1_utils import get_tiff
class FakeBandIndex:
def band_label(self, band):
return "index_function"
def nodata_val(self, band):
return 0
def as_tiff_wcs1(req, data):
dataarray = req.product.styles[0].apply_index(data)
dataset = dataarray.to_dataset()
req.product.band_idx = FakeBandIndex() # very hacky...
return get_tiff(req, dataset) See eo2cube/ows_config@47d122f (the code there has comments) So I just apply the index function, which of course fundamentally changes the data structure, createing several issues further down, which I just silence by injecting that It works beautifully, you can try it by requesting the WCS link from my initial post. But of course it's absolutely not generic... The only current problem for me is that masking is not applied. I thought I could sneak that functionality too, but it turns out that the mask (1) is not built encapsulatedly, but in the middle of Do you have an idea how this could be solved currently (without replicating a lot of code)? And what do you think about this approach? Would a more generic version be of interest to be included into datacube-ows? |
Okay, so it turns out that I don't even need all the logic inside fake_rgb = dataset.rename({"index_function": "red"})
masked = req.product.styles[0].apply_mask_to_image(fake_rgb, mask, 1, 1)
result = masked["red"].where(masked["alpha"]).to_dataset().rename({"red": "index_function"}) Until I realised that I don't need the logic in mask = req.product.styles[0].to_mask(data)
result = dataset.where(mask) See eo2cube/ows_config@bc4b795 But it appears that the original datacube-ows/datacube_ows/wcs1_utils.py Lines 452 to 456 in a1ab57c
to the if cfg.wcs_tiff_statistics:
dst.update_tags(idx, STATISTICS_MINIMUM=numpy.nanmin(data[band].values))
dst.update_tags(idx, STATISTICS_MAXIMUM=numpy.nanmax(data[band].values))
dst.update_tags(idx, STATISTICS_MEAN=numpy.nanmean(data[band].values))
dst.update_tags(idx, STATISTICS_STDDEV=numpy.nanstd(data[band].values)) That's the only modification in |
Nice - yes that's one (slightly hacky) way of doing it! I would like to handle this more generically at some point by treating the index functions of all defined colour ramp styles as pseudo-measurements within WCS. The stats fix is a bug that could affect all floating-point bands though - if you want to do a quick PR just for that patch, I'll happily merge it. |
I would still argue that the index function(s) are the only 'proper' measurement in this case, but I get your point and if we just add more layers (instead of replacing), everyone gets what they want :) I would highly appreciate if you were to implement this more generically! With my understanding of the architecture I don't feel like I can do it at this point, but experimenting with a few more requests against my current solution revealed several cases that don't seem to work just yet, so it really is a hacky workaround that is somewhat okay for now but shouldn't stay around for too long, desiring to be replaced by something proper. PR's there :) I applied the same fix in WCS2 too. |
Hi all, have been listening in to this in the background - just want to confirm that this below very much aligns with what I would think of as "expected functionality" too. Getting back the raw spectral band layers when I select "export" from a specific index function is pretty suprising, and I suspect pretty confusing to our users who just want to export the data that's actually being shown/styled on the map.
(totally recognise the complexities to actually achieving this, but just emphasising that the current functionality isn't ideal from a usability perspective). |
Description
The standard way to serve calculated products (e.g. NDVI) via WMS seems to be to load the raw bands and do the band math accordingly, e.g. like in this example from the documentation:
Seeing this all over the docs, I set up a system that uses somewhat complex index functions to calculate various products (see e.g. https://github.com/eo2cube/ows_config/blob/4fe21710cc278e9c2ee4b46a742aaaa9f343899d/s2_vi/formulas.py#L16).
Now we need the raw values of those calculations elsewhere and thought "Perfect, we've got the WCS, we can just export them from there!" But as the WCS serves the "raw data" and the calculations are technically happening in the styling part, the only thing I got were the very raw imagery values, not the calculated product values. (To stay with the example: I got the Red and NIR values, not the NDVI calculated from them.)
I browsed through the existing issues and this has been touched upon before at #510 (comment) with Paul stating:
I agree that I'm perfectly capable of doing the same bandmath again elsewhere, but (1) it's not me who wants to use those values and (2) I've got everything so nicely configured in OWS already that it would be super sweet to just download the stuff from there straight away. I also agree that WCS should provide the raw data, but the question is how raw should it be?
I would also like to raise the point of consistency of the same product across the various services. Which leads me to formulate the expected behaviour: WMS and WCS export "the same thing". As in: Taking the values from the WCS and applying the same colour ramp as in the styling config results in the same image that the WMS exports. Or worded vice versa: 'Un-applying' the colour ramp from the WMS image yields the WCS values.
Or is there another way to get values out of the WCS that have the index function already applied?
Steps to Reproduce
Compare the result of this WMS request:
https://ows.eo2cube.org/wms?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&LAYERS=s2_vi_ndvi_diff_winter_wheat&STYLES=&WIDTH=512&HEIGHT=512&FORMAT=image%2Fpng&CRS=EPSG%3A3857&DPI=96&MAP_RESOLUTION=96&FORMAT_OPTIONS=dpi%3A96&TRANSPARENT=TRUE&TIME=2020-01-02T00%3A00%3A00Z&BBOX=1389247.83251150674186647,7142683.10669049434363842,1392738.67198907374404371,7146173.94616806134581566
with what you get from this corresponding WCS request:
https://ows.eo2cube.org/wcs?SERVICE=WCS&VERSION=1.0.0&REQUEST=GetCoverage&FORMAT=GeoTIFF&COVERAGE=s2_vi_ndvi_diff_winter_wheat&WIDTH=512&HEIGHT=512&CRS=EPSG:3857&RESPONSE_CRS=EPSG:3857&TIME=2020-01-02T00%3A00%3A00Z&BBOX=1389247.83251150674186647,7142683.10669049434363842,1392738.67198907374404371,7146173.94616806134581566
(same TIME and BBOX and LAYERS/COVERAGE)
Context (Environment)
datacube-ows
versiondatacube-ows-update --version
Open Data Cube Open Web Services (datacube-ows) version 1.8.36.dev0+g67003f3.d20240617
datacube-ows
confighttps://github.com/eo2cube/ows_config/
datacube product metadata
datacube product show s2_c1_l2a
Result
The text was updated successfully, but these errors were encountered: