Sorry, something went wrong.
|
Seems like there are slight color differences between the cmap library and matplotlib ones? You can download the screenshot diffs at the bottom: https://github.com/fastplotlib/fastplotlib/actions/runs/7090258613 I don't think it's an issue, but I'm curious why it's different. |
Sorry, something went wrong.
|
thanks for the heads up @kushalkolar. I'll have a look at it and figure out what's going on. I do actually test for parity with matplotlib here (both naming and the actual effect of applying the cmap to an image), so i suspect that the differences might be in the way that colormaps are actually applied in the functions module. I did notice a very slight difference in the values of get_cmap as well, so I suspect that might be the root of it. I'll do some digging and get back to you :) |
Sorry, something went wrong.
|
btw, I'd like to run these tests locally, but i ran into #388 ... any thoughts there? |
Sorry, something went wrong.
|
Thanks! Everything in the regenerate screenshots workflow seems fine visually, so you can replace the ground truth of the failing tests with those. |
Sorry, something went wrong.
There was a problem hiding this comment.
Other than that docstring lgtm! The colormap text files can now be removed too.
Curious what the difference was previously when the colors weren't matching?
Sorry, something went wrong.
|
Curious what the difference was previously when the colors weren't matching? oh good. it's working on CI too! the cmap.Colormap object behaves very much like a matplotlib.colors.Colormap, with the main API being the __call__ method, which takes an array of scalars and returns an array of colors. And, just like the matplotlib API, if the input array is of dtype float, it expects values from 0-1, and it's of dtype int, it expects 0-255. In [27]: import cmap
In [28]: viridis = cmap.Colormap('viridis')
In [29]: viridis(np.linspace(0, 255, 5, dtype=int))
Out[29]:
array([[0.267004, 0.004874, 0.329415, 1. ],
[0.231674, 0.318106, 0.544834, 1. ],
[0.128729, 0.563265, 0.551229, 1. ],
[0.360741, 0.785964, 0.387814, 1. ],
[0.993248, 0.906157, 0.143936, 1. ]])
In [31]: from matplotlib import colormaps
In [32]: mpl_viridis = colormaps['viridis']
In [33]: mpl_viridis(np.linspace(0, 255, 5, dtype=int))
Out[33]:
array([[0.267004, 0.004874, 0.329415, 1. ],
[0.231674, 0.318106, 0.544834, 1. ],
[0.128729, 0.563265, 0.551229, 1. ],
[0.360741, 0.785964, 0.387814, 1. ],
[0.993248, 0.906157, 0.143936, 1. ]])
There's of course a subtle difference in the input array there, for N evenly spaced numbers: In [35]: np.linspace(0, 1, 5, dtype=float)
Out[35]: array([0. , 0.25, 0.5 , 0.75, 1. ])
In [36]: np.linspace(0, 255, 5, dtype=int)/255
Out[36]: array([0. , 0.24705882, 0.49803922, 0.74901961, 1. ])
and In [29]: viridis(np.linspace(0, 255, 5, dtype=int))
Out[29]:
array([[0.267004, 0.004874, 0.329415, 1. ],
[0.231674, 0.318106, 0.544834, 1. ],
[0.128729, 0.563265, 0.551229, 1. ],
[0.360741, 0.785964, 0.387814, 1. ],
[0.993248, 0.906157, 0.143936, 1. ]])
In [30]: viridis(np.linspace(0, 1, 5, dtype=float))
Out[30]:
array([[0.267004, 0.004874, 0.329415, 1. ],
[0.229739, 0.322361, 0.545706, 1. ],
[0.127568, 0.566949, 0.550556, 1. ],
[0.369214, 0.788888, 0.382914, 1. ],
[0.993248, 0.906157, 0.143936, 1. ]])
... so, in my original PR, I used a slightly lower level construct to retrieve the N evenly spaced colors. Rather than using Colormap.__call__(some_array), I used Colormap.lut(n_colors) ... which was slightly different than the existing code you had here which used np.take on a linspace array of int: In [37]: viridis.lut(5)
Out[37]:
array([[0.267004 , 0.004874 , 0.329415 , 1. ],
[0.23022275, 0.32129725, 0.545488 , 1. ],
[0.1281485 , 0.565107 , 0.5508925 , 1. ],
[0.36285925, 0.786695 , 0.386589 , 1. ],
[0.993248 , 0.906157 , 0.143936 , 1. ]])
so I switched it to the __call__ approach and then everything matched the current screenshots. probably visually imperceptible, but might as well keep the same behavior :) Don't hesitate to ask if you have any questions about the cmap object. But in general, the __call__ method was designed (and tested) to behave exactly like matplotlibs. |
Sorry, something went wrong.
|
The colormap text files can now be removed too. oh wow... to be honest, I somehow missed the distinction there that matplotlib was only being used as a fallback. So, a question for you: I can definitely understand wanting to keep dependencies to the absolute minimum. So, would you like me to update this PR slightly to keep the current behavior for named colormaps that you already bundle, remove cmap from setup.py and fallback to cmap only in the case that a string name is unrecognized? That would still allow you to support custom colormaps using all the previously discussed syntax, but the user would have to opt-in to that behavior. Thoughts? |
Sorry, something went wrong.
|
There's of course a subtle difference in the input array there, for N evenly spaced numbers: In [35]: np.linspace(0, 1, 5, dtype=float)
Out[35]: array([0. , 0.25, 0.5 , 0.75, 1. ])
In [36]: np.linspace(0, 255, 5, dtype=int)/255
Out[36]: array([0. , 0.24705882, 0.49803922, 0.74901961, 1. ])
That is a subtle difference, thanks for tracking it down! I think the reason we used the approach that we did, and use int, is because it makes it easier to work with qualitative colormaps when you give n_colors. (not entirely sure, I wrote that part of the code years ago in a previous library) So, a question for you: I can definitely understand wanting to keep dependencies to the absolute minimum. So, would you like me to update this PR slightly to keep the current behavior for named colormaps that you already bundle, remove cmap from setup.py and fallback to cmap only in the case that a string name is unrecognized? That would still allow you to support custom colormaps using all the previously discussed syntax, but the user would have to opt-in to that behavior. Thoughts? Since cmap only depends on numpy I'm fine with keeping it in, if there are issues down the road I would rather vendor it in, but I hope you're able to keep producing all the cool things you've been doing in the imaging ecosystem! :) I like that this will now enable us to do things like line.cmap = ["blue", "green", "yellow"] to create custom arbitrary colormaps. Speaking of which, would you be interested in implementing that? No worries if you don't have time, I can easily do it in another PR 😄 |
Sorry, something went wrong.
|
One more thing, does the cmap lib allow centering diverging colormaps arbitrarily? For example if the following values exist and you want to center the seismic colormap around 0, instead of the middle value here which would be -2? [-6, -5, -4, -3, -2, -1, 0, 1, 2]
|
Sorry, something went wrong.
Sorry, something went wrong.
|
Ah that's nice when two libraries work together without extra work, maybe speaks to the design of both 😄 . Does the list of colors also work with the line collection and image example? If that works I will merge, thanks a lot! :D I'm curious what your thoughts are on this part of our API, our goal was make it easier than and more intuitive than other libraries |
Sorry, something went wrong.
Sorry, something went wrong.
|
This example: https://github.com/fastplotlib/fastplotlib/blob/main/examples/desktop/line_collection/line_stack.py If you have pyqt6 installed you can use the %gui qt hook. Otherwise you can force the glfw canvas with plot = fpl.Plot(canvas="glfw") |
Sorry, something went wrong.
|
thanks. Yeah that example works, but it does get more confusing due to the overloading of the argument as a colormap or list of colormaps. So, one has to be rather careful there. If I do this: # line stack takes all the same arguments as line collection and behaves similarly
cmaps = ["jet"] * len(data)
cmaps[-2:] = ["cubehelix", ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]]
plot.add_line_stack(data, cmap=cmaps)
... but it's rather easy to get into trouble there |
Sorry, something went wrong.
|
Yes you are right, because then this will raise: plot.add_line_stack(data, cmap=["red", "blue", "green"])`
But if a line collection is already created, then this will work: plot.graphics[0].cmap = ["red", "blue", "green"]
File ~/repos/fastplotlib/fastplotlib/graphics/line_collection.py:125, in LineCollection.__init__(self, data, z_position, thickness, colors, alpha, cmap, cmap_values, name, metadata, *args, **kwargs)
123 elif isinstance(cmap, (tuple, list)):
124 if len(cmap) != len(data):
--> 125 raise ValueError(
126 "cmap argument must be a single cmap or a list of cmaps "
127 "with the same length as the data"
128 )
129 single_color = False
130 else:
ValueError: cmap argument must be a single cmap or a list of cmaps with the same length as the data
Question: We could enforce that the cmap kwarg should represent a single cmap. If the user wants to pass a list of cmaps we make a new kwarg called cmaps? After the line collection has been created, this still works to change cmaps of individual lines: plot.graphics[0][2:5].cmap = ["blue", "yellow", "green"]
|
Sorry, something went wrong.
|
Question: We could enforce that the cmap kwarg should represent a single cmap. If the user wants to pass a list of cmaps we make a new kwarg called cmaps? I can see arguments for and against. (notably, it's a breaking change). With the appropriate type checking and exception messages, it should be possible to keep a single cmap argument, but you just need to be careful about the inspection of the object and provide clear help/feedback |
Sorry, something went wrong.
|
Question: We could enforce that the cmap kwarg should represent a single cmap. If the user wants to pass a list of cmaps we make a new kwarg called cmaps? I can see arguments for and against. (notably, it's a breaking change). With the appropriate type checking and exception messages, it should be possible to keep a single cmap argument, but you just need to be careful about the inspection of the object and provide clear help/feedback Hi, sorry for the late reply, been slow getting back into this after the winter holidays! I think we can allow the following types of use for the cmap argument in LineCollection:
I think this should cover all the cases? 😄 |
Sorry, something went wrong.
this PR is an example of using https://github.com/tlambert03/cmap instead of matplotlib for colormaps.
It does add cmap to setup.py ... but if you'd prefer not to go that far, we could guard it all behind an import and say "must install cmap to display stuff with colormaps"
closes #387