DHI / terracotta

A light-weight, versatile XYZ tile server, built with Flask and Rasterio :earth_africa:

Home Page:https://terracotta-python.readthedocs.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Mismatch between raster pixel values and returned colormap

mz8i opened this issue · comments

We're relying on data returned from the /colormap endpoint to show approximate raw raster values upon hovering over a point in a frontend application.
However, for a small number of pixels in almost every tile, the pixel color doesn't match any of the entries returned from /colormap. The "invalid" color is usually off by 1 on one of the three r,g,b components from the nearest legend entry. This seems like a bug but I'm not sure what might be causing it.

Example (the three marked pixels are the ones where no colormap entry can be found for the pixel value queried from the image):
text7372

I checked the details of some of those raster cells.

For point A:

  • the raw value is 0.513400018215179
  • the raster color is rgb(246,245,249)
  • the nearest entry in the colormap JSON is raw value 0.5118110236220472 - but the color for that is rgb(247,245,249) (red component is off by 1)

For point B:

  • the raw value is 0.713100016117096
  • the raster color is rgb(244,243,248)
  • the nearest entry in the colormap is raw value 0.7086614173228346 but the color for that is rgb(245,243,248) (again, red off by 1)

Interestingly, the raster tile color values match the colormap values stored in purples_rgba.npy. So it seems that it's the JSON coming from the /colormap endpoint that has distorted values.

See result of querying /colormap?colormap=purples&stretch_range=[0,10]:

{
"colormap": [
//...
		{
			"value": 0.4330708661417323,
			"rgba": [
				247,
				246,
				250,
				255
			]
		},
		{
			"value": 0.47244094488188976,
			"rgba": [
				247,
				245,
				249,
				255
			]
		},
		{
			"value": 0.5118110236220472,
			"rgba": [
				247,
				245,
				249,
				255
			]
		},
		{
			"value": 0.5511811023622047,
			"rgba": [
				246,
				244,
				249,
				255
			]
		},
		{
			"value": 0.5905511811023622,
			"rgba": [
				246,
				244,
				249,
				255
			]
		},
		{
			"value": 0.6299212598425197,
			"rgba": [
				245,
				243,
				248,
				255
			]
		},
		{
			"value": 0.6692913385826772,
			"rgba": [
				245,
				243,
				248,
				255
			]
		},
		{
			"value": 0.7086614173228346,
			"rgba": [
				245,
				243,
				248,
				255
			]
		},
		{
			"value": 0.7480314960629921,
			"rgba": [
				244,
				242,
				248,
				255
			]
		},
		{
			"value": 0.7874015748031495,
			"rgba": [
				244,
				242,
				248,
				255
			]
		},
// ...
]
}

Compare with corresponding fragment of the purples_rgba.npy file:

// ...
11: 247, 246, 250, 255
12: 247, 245, 249, 255
13: 246, 245, 249, 255
14: 246, 244, 249, 255
15: 245, 244, 249, 255
16: 245, 243, 248, 255
17: 245, 243, 248, 255
18: 244, 243, 248, 255
// ...

The JSON contains rgb values that do not exist in the colormaps.

For context. we're setting the following in the config, but not sure if it's relevant:

PNG_COMPRESS_LEVEL = 0
RESAMPLING_METHOD = "nearest"
REPROJECTION_METHOD = "nearest"

Thanks for the detailed report. I think the most interesting part is this:

The JSON contains rgb values that do not exist in the colormaps.

Which ones? I couldn't see any.

You're right, my mistake. It should've actually been the opposite:
The JSON is missing some values that both the tiles and the .npy file contains - for example 246,245,249 is not in the JSON.

Which actually presents a conclusion - the way currently the /colormap JSON is generated, is to start with a series of evenly spaced values covering the stretch_range and to map each of these to an index in the colormap array. I don't have a full understanding of how that's done, but presumably some calculation results in some indices of the colormap array being missed completely, and so they are not present in the JSON.

To me it seems that the more sensible approach would be to go the other way round: generate a raw value for each of the colormap array elements, thus ensuring all possible colors are included in the JSON. Perhaps this will cause the raw values to be spaced slightly unevenly, but this seems less important here.

To me it seems that the more sensible approach would be to go the other way round: generate a raw value for each of the colormap array elements, thus ensuring all possible colors are included in the JSON. Perhaps this will cause the raw values to be spaced slightly unevenly, but this seems less important here.

Keep in mind that /colormap takes a num_values parameter, so in general that is not possible. But I agree that, for num_values=255, the output should ideally match the colormap file exactly.

I have identified the issue and will push a fix in a few minutes.

Could you verify whether this is fixed in #250?

A brief check suggests that indeed it is, thank you!

@dionhaefner any schedule for when a new package version will be released that includes this fix?

Done.

I'm sorry to report that after using version 0.7.5 across our project to display multiple layers, I can now see the issue is not fixed... It does not appear in that particular layer/geographical area I was testing, but it does appear in many other places - perhaps even more than before the update.

Please let me know if there is any information I can send that would be useful to debug this.
As a workaround, I might just take the .npy files from this project to generate the colour-to-value mappings locally.

Longer term, I am wondering if there is space for two endpoints in terracotta, one to get a customisable number of values like in the current endpoint, and another to get a precise value lookup for all possible colours (in case it's difficult to combine the two use cases in one piece of logic)

Can you give me a request to /colormap that does not return the same as the npy files?

@dionhaefner I'm sorry, the error was on my side - I overlooked a non-standard mechanism in which our app was caching the old responses in local storage, so it didn't work despite updating.
I've now double-checked all colour scales we use, and the responses for all match the .npy files exactly.
Apologies again for the confusion and thank you for fixing this.