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

Added support for predefined colormaps with alpha channel and an example #161

Merged
merged 4 commits into from Feb 25, 2020

Conversation

brianpojo56
Copy link
Contributor

The usecase that lead to this change is overlaying cloud coverage. See screenshot below. Cloud coverage information is extrapolated from GOES-16 ABI channel 13 and displayed on top of Bing Aerial Maps. Allowing a fourth channel in the predefined color maps allows us to have a varying transparency without sending a bulky json custom colormap with each api request. The varying transparency gives us the nice feathered edges for the clouds.
image

@j08lue
Copy link
Collaborator

j08lue commented Jan 29, 2020

Thanks for the PR.

We already have the functionality to provide a color map with alpha/transparency channel: You can provide the color map as 4D array:

https://github.com/DHI-GRAS/terracotta/blob/ed4e45ae5cb5f5a94bae2751b226efba397e825a/terracotta/image.py#L82-L94

Doesn't that already fulfill your requirements?

Copy link
Collaborator

@j08lue j08lue left a comment

Choose a reason for hiding this comment

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

just a note

@@ -7,7 +7,7 @@ def test_get_cmap():
from terracotta.cmaps import get_cmap, AVAILABLE_CMAPS
for name in AVAILABLE_CMAPS:
cmap = get_cmap(name)
assert cmap.shape == (255, 3)
assert cmap.shape == (255, 3) or cmap.shape == (255, 4)
Copy link
Collaborator

Choose a reason for hiding this comment

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

perhaps make that assert cmap.shape in [(255, 3), (255, 4)]

@dionhaefner
Copy link
Collaborator

dionhaefner commented Jan 29, 2020

I haven't looked at the code yet, but here's my take.

As I see it, the new functionality is a predefined color map that has an alpha gradient. So far, you had to specify your own color map to achieve this.

Old version:

/singleband/{z}/{x}/{y}.png?colormap=explicit&explicit_color_map="{0: [255, 255, 255, 0], 1: [255, 255, 255, 1], ..., 255: [255, 255, 255, 255]}"

(assuming the raster consists of uint8 values, so you have to specify 256 colors in the explicit_color_map argument)

New version:

/singleband/{z}/{x}/{y}.png?colormap=gray_w_alpha

This is useful functionality IMO, but it also marks a break between our and matplotlib's color maps. At the least, we should generate a colormap reference (like this one) for our docs.

An alternative would be an interpolated color map type, so you could do this like so:

/singleband/{z}/{x}/{y}.png?colormap=explicit_linear&explicit_color_map="{0: [255, 255, 255, 0], 255: [255, 255, 255, 255]}"

which would interpolate between the colors, just like matplotlib's LinearSegmentedColormap.

Happy for any opinions.

@brianpojo56
Copy link
Contributor Author

@dionhaefner Yes! Your take is exactly correct, looking back at the code more thoroughly I may have only needed to make a change in the get_cmap method (line 41 in init.py) for the array shape assertion. I also really like the idea of allowing interpolated explicit colormaps. There stands a benefit for predefined cmaps to avoid recomputing the interpolation.

@j08lue dionhaefner explained it better than me and provided clear examples, but add some number, if you have a color value for each of the 255 values and an average of 16 characters per color ( 10:[25,25,25,25] for example), you can easily end up with a json object just a little over 4kb in size going out with each request.

I'm going to go back over the changes, and make sure I'm passing all of the tests before pushing again. I do apologize, I got a bit ahead of myself and jumped on pushing before it was ready.

P.S. I love the work the team is doing and this is an amazing product. At work, we've used it with a couple projects now and I hope in doing so we've helped with some exposure to make it more well known.

@dionhaefner
Copy link
Collaborator

Maybe something like an environment variable TC_CMAP_FOLDER would be nice, that you can point to custom colormaps for your deployment. That way we can have the users decide which colormaps they want to provide.

@dionhaefner
Copy link
Collaborator

P.S. I love the work the team is doing and this is an amazing product. At work, we've used it with a couple projects now and I hope in doing so we've helped with some exposure to make it more well known.

And thank you for your kind words :)

@j08lue
Copy link
Collaborator

j08lue commented Jan 31, 2020

@brianpojo56 Do you as user have a suggestion how you could see yourself accessing your custom (4D) colormap on a deployed Terracotta instance?

I very much understand that you do not want to pass the colormap data with every request. But how do you want to specify and provide it then?

Would @dionhaefner's proposed solution work, which is:

  1. You include with your deployment a folder containing your custom color map, say myclouds.npy
  2. You point Terracotta to that folder via an environment variable TC_CMAP_FOLDER=/path/to/your/cmaps
  3. You reference it by name /singleband/{z}/{x}/{y}.png?colormap=myclouds

Another solution would be to define additional color maps as JSON in an environment variable, e.g. TC_CUSTOM_CMAPS='{"myclouds": {0: [255, 255, 255, 0], 1: [255, 255, 255, 1], ..., 255: [255, 255, 255, 255]}}}' and then reference them by name. We would need to parse that string every time at runtime, but there is no network traffic involved.

Finally, you could also add your color map as a file to Terracottas cmaps folder and then you can reference it by name already. I assume that this is the solution you had in mind?

In any case, we need to get the kind of changes in place that you proposed in this PR, so I'd encourage you to fix it up so the tests pass and we can take the discussion about deployment on the side.

@j08lue
Copy link
Collaborator

j08lue commented Feb 6, 2020

Let's leave the question of how to deploy cmaps to later. I am up for going through with the purpose of this PR (supporting 4D cmaps), provided that tests get fixed etc. first. What do you think @dionhaefner?

@dionhaefner
Copy link
Collaborator

Why don’t we just convert all colormaps to RGBA. That way we just need to support one file format.

@brianpojo56
Copy link
Contributor Author

brianpojo56 commented Feb 13, 2020

@j08lue @dionhaefner I'm really sorry I went MIA for so long, I had to make a short notice poorly planned road trip to see some family.
However, I did have a lot of time to think on this while driving and I believe I came up with a different approach that makes more sense. I'll do my best to describe it.

There will be a separate folder for alpha maps.
The folder would be named amaps to correlate with cmaps.
The alpha maps are stored as *.npy format, size 255 and type uint8.
Naming scheme for the stored alpha maps will be *_a.npy or *_alpha.npy (and *_r_a.npy to indicate a reverse order).
There will be a utility method to retrieve an alpha map by name, almost exactly like the method get_cmap().
There will be an optional path parameter 'alphamap' on the image api (in addition an update to the metdata api about alphamap usage).

The benefits to this are:
The colormaps remain in the matplotlib compatible format.
Colormaps will not have near-duplicates that differ only in the alpha channel.
My early assumption is that this will lead to a cleaner implementation in image.py.
A user can mix and match colormaps and alphamaps without needing to build new custom 4 channel colormaps.

These are the only alpha maps that I can think of that may be useful:
Range (1, 255) and (128, 255) in Linear, Logarithmic, and Exponential, and the reverse of those. 12 in total.

For the alphamap filenames I am very open to suggestions but I was thinking along the lines of these:
full_linear_a.npy, full_linear_r_a.npy, half_exponential.npy, etc.

If this all makes sense and sounds useful to you both I will have time this weekend I can devote to finishing this.
Since this is a complete overhaul of the original pull request I can close this one and open a new one, or I can just continue on this one, just let me know what's best.

@dionhaefner
Copy link
Collaborator

Thanks for thinking more about this problem, but I'm afraid I can't agree with your proposal.

We established that having an alpha gradient is useful in some cases, but it is and always will be a niche use case. That does IMO not warrant increasing the complexity of the API by introducing the alpha parameter.

My counter proposal is this:

  1. Convert all colormaps to RGBA
  2. Make sure image.py supports RGBA color maps, drop RGB support if it makes the code simpler
  3. Add your cloud color map as a new default color map (because I can see a clear use case here)
  4. Auto-generate a color map reference for our docs, don't link to matplotlib's anymore
  5. Introduce TC_CMAP_FOLDER where you can dump deployment-specific color maps in RGBA format
  6. Validate the custom color maps on startup (so people can't ship broken color maps)
  7. Document this new capability

Unless anyone has any serious objections to this, this is how I would like to proceed. I would of course be happy if you @brianpojo56 want to contribute any of these pieces (some of them are already covered by this PR).

Copy link
Collaborator

@j08lue j08lue left a comment

Choose a reason for hiding this comment

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

I think this concisely implements the solution @dionhaefner proposed. Nice!

Comment on lines +92 to +94
assert transparency_arr.shape == (256,)
assert transparency_arr.dtype == 'uint8'
transparency = transparency_arr.tobytes()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, I guess these assert might as well apply to both, colormaps retrieved by name, and those user-defined.

@codecov
Copy link

codecov bot commented Feb 18, 2020

Codecov Report

Merging #161 into master will increase coverage by <.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #161      +/-   ##
==========================================
+ Coverage   98.16%   98.16%   +<.01%     
==========================================
  Files          43       43              
  Lines        2122     2123       +1     
  Branches      260      260              
==========================================
+ Hits         2083     2084       +1     
  Misses         21       21              
  Partials       18       18
Impacted Files Coverage Δ
terracotta/cmaps/__init__.py 100% <100%> (ø) ⬆️
terracotta/cmaps/generate_cmaps.py 100% <100%> (ø) ⬆️
terracotta/handlers/colormap.py 100% <0%> (ø) ⬆️
terracotta/image.py 100% <0%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3c9feff...0c86cfe. Read the comment docs.

@dionhaefner
Copy link
Collaborator

👋 @brianpojo56, thanks for the commits. Is this still a work in progress from your side or are you done with your contribution?

@brianpojo56
Copy link
Contributor Author

@dionhaefner I had hoped to contribute a little more but it looks like I need to turn it over to you guys. The semester is starting to ramp up and I've lost my free time.
Going back to the list I put a star next to what the pull request covers, the other pieces still need to be done.

  1. *Convert all colormaps to RGBA
  2. *Make sure image.py supports RGBA color maps, drop RGB support if it makes the code simpler
  3. *Add your cloud color map as a new default color map (because I can see a clear use case here)
  4. Auto-generate a color map reference for our docs, don't link to matplotlib's anymore
  5. Introduce TC_CMAP_FOLDER where you can dump deployment-specific color maps in RGBA format
  6. Validate the custom color maps on startup (so people can't ship broken color maps)
  7. Document this new capability

@j08lue
Copy link
Collaborator

j08lue commented Feb 24, 2020

Nice overview of things to be done, @brianpojo56. I would be fine with merging now, which would fix your original issue, and leaving the other points to another PR. What do you think, @dionhaefner?

@dionhaefner dionhaefner changed the base branch from master to cmaps February 25, 2020 08:59
@dionhaefner dionhaefner merged commit 4792c44 into DHI:cmaps Feb 25, 2020
@dionhaefner
Copy link
Collaborator

Merged, I will continue in another PR. Thanks for the contribution @brianpojo56!

@dionhaefner dionhaefner mentioned this pull request Feb 25, 2020
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants