zkbt / chromatic

Tools for visualizing spectrosopic light curves, with flux as a function of wavelength and time.
MIT License
14 stars 4 forks source link

imshow interpolation default #238

Open Pat-Wachiraphan opened 4 months ago

Pat-Wachiraphan commented 4 months ago

Hi, I'm running chromatic version 0.4.11

The problem: I was trying to use Rainbow/Rainbow feature and then encountered this issue. The plot from Rainbow/Rainbow and flux1/flux2 (outside chromatic) are not the same as shown below.

This is from chromatic visit1_diff_chromatic

This is just from straight plt.imshow visit1_diff_python

You can see that all the sudden changes has been smeared out and overall scatter seems higher.

Cause of problem: It seem that Rainbow.imshow() have a default interpolation='nearest'and Rainbow.pcolormesh() have a default shading='flat' which I'm not sure if we want that to be a default or not.

Potential Solution: I managed to change interpolation=None for imshow but I couldn't find the way to turn off shading for pcolormesh()

zkbt commented 3 months ago

@Pat-Wachiraphan , could you please send me the example Rainbow objects you're dividing here? I'd love to test this out with what you have. (In the meantime I'll play with some simulated noise, but it'd be good to test with the same data as you.)

zkbt commented 3 months ago

I'm guessing this might have something to do with the way that the imshowed pixels are being downsampled from the super-high time resolution of the dataset, but the fact that you showed me yesterday that the automatic color scales change dramatically suggests something else weird potentially happening.

Pat-Wachiraphan commented 3 months ago

Hi @zkbt, sure! I attached a OneDrive link to the file here.

Pat-Wachiraphan commented 3 months ago

It is not exactly the same as provided in the original post, but it should show the difference.

zkbt commented 1 month ago

It looks like this has to do with the way that matplotlib renders images when the data in the imshow or pcolormesh is at much higher resolution than the image is actually being displayed on the screen or in an image file. If it has to visually average multiple data pixels together into one screen pixel, there are different choices for how that's done.

The issue is described to some extent here. I believe that the simplest fix for this is to use keywords of interpolation='antialiased' for imshow and 'antialiased=Trueforpcolormesh`, but I'm still working on putting together a working example demonstrating this is definitely the case.

zkbt commented 1 month ago

Ug, OK, this is turning out to be slightly more complicated. Here a gist exploring what's going on with different options.

I think that above fix will work for imshow uniform (or log-uniform) arrays, in the sense that interpolation='antialiased' seems to do a good job both zoomed out (showing big patterns and not just high frequency noise) and zoomed in (showing hard edges between pixels, instead of ugly interpolated smoothing).

However, as far as I can tell, even though pcolormesh accepts an antialiased=True keyword, it doesn't actually seem to do anything. There's no difference with it turned on or off. This inactive matplotlib issue seems to confirm someone else had similar problems. It looks like, maybe, pcolor will work though?

zkbt commented 1 month ago
import matplotlib.pyplot as plt
import numpy as np

for dpi in [50, 500]:
    for f in ['pcolormesh', 'pcolor']:

        fig, ax = plt.subplots(1, 2, figsize=(8,4), dpi=dpi)
        x = np.arange(500) / 500 - 0.5
        y = np.arange(500) / 500 - 0.5

        X, Y = np.meshgrid(x, y)
        R = np.sqrt(X**2 + Y**2)
        f0 = 10
        k = 250
        a = np.sin(np.pi * 2 * (f0 * R + k * R**2 / 2))
        for i, anti in enumerate([False, True]):
            exec(f'ax[i].{f}(a, antialiased=anti)')
            ax[i].set_title(f'dpi={dpi}, antialiased={anti}')
        plt.suptitle(f)
        plt.show()

image image image image

zkbt commented 1 month ago

So, I think the solution is just to replace pcolormesh with pcolor, and use the antialiased=True option?

zkbt commented 1 month ago

Ug, with pcolor it is both annoyingly slow (like a minute for the MIRI/LRS dataset) and the behavior is not super intuitive. Whereas I think imshow(interpolation='antialiased') is doing the required interpolation in data space (see here), I suspect that pcolor(antialiaseds=True) is maybe doing it in RGBA space, so it's producing colors in the rasterized image that don't appear in the actual colorbar.

image image image

It also seems to be adding little white spaces between pixels, that affect the overall color intensity, unless we explicitly draw edges between the faces, but that still ends up looking icky. I can kind of see the large scale bumps in these antialiased pcolor images, but I think it comes at too high of a cost for interpretability.

Pat-Wachiraphan commented 1 month ago

I have a feeling that we might need to do imshow but in a weird log-scale on y-axis?

zkbt commented 1 month ago

Yes, I think so.

All of these issues fundamentally come down to it being hard to make matplotlib make the right choice in how to visually bin together adjacent colorful pixels that it's trying to render. I think these problems only emerge when we're trying to display more pixels than fit in the image. I think that instead of asking it to do the very hard thing of doing an intuitive and understandable binning when trying to display a massive (244w, 22862t) array, we should just bin it ourselves to a more sensible grid, and then display it.

zkbt commented 1 month ago
binned = rainbow.bin(dw=0.1*u.micron, dt=1*u.minute)
fi, ax = plt.subplots(1, 2, figsize=(8, 3))
binned.imshow(ax=ax[0])
binned.pcolormesh(ax=ax[1])

image

zkbt commented 1 month ago

I'm about to get off the bus, but I'll try to push up the small changes to fix imshow and maybe add a warning to pcolormesh (much?) later tonight.

zkbt commented 1 month ago

Sorry this took me so long, and I still wasn't even able to solve it!