implot icon indicating copy to clipboard operation
implot copied to clipboard

Backends branch: log scale does not render properly

Open clo-yunhee opened this issue 2 years ago • 13 comments

Log axis scale does not render properly using the OpenGL backend:

With Y axis log scale on: image

With X axis log scale on: image

With both of them on: image

clo-yunhee avatar Apr 06 '22 04:04 clo-yunhee

I see nothing wrong in the image. I honestly think this is how it should look like.

As a side note though, please note that the leftmost column of pixels represent X=0, and that point is a singularity in log scale: in logarithmic scale, there's an infinite distance between 0 and any other non-zero, finite point. This will usually result in extremelly stretched-out plots like the first two images. But that's not a bug, that is how logarithmic scale works :)

marcizhu avatar Apr 06 '22 06:04 marcizhu

ImPlot corrects for when the lower bound is 0, it also still renders that way with higher values for it. And comparing with the master branch makes it very clear that this render is not intended behaviour.

On Wed, 6 Apr 2022, 08:33 Marc, @.***> wrote:

I see nothing wrong in the image. I honestly think this is how it should look like.

As a side note though, please note that the leftmost column of pixels represent X=0, and that point is a singularity in log scale: in logarithmic scale, there's an infinite distance between 0 and any other non-zero, finite point. This will usually result in extremelly stretched-out plots like the first two images. But that's not a bug, that is how logarithmic scale works :)

— Reply to this email directly, view it on GitHub https://github.com/epezent/implot/issues/349#issuecomment-1089874936, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABSS3MIZZLXTCN753F22MDTVDUV4VANCNFSM5SUXYSAQ . You are receiving this because you authored the thread.Message ID: @.***>

clo-yunhee avatar Apr 06 '22 11:04 clo-yunhee

I disagree. This correction for X=0 is done on the CPU in the master branch and on the GPU in the backends flag. Both branches differ because the implementations differ, and as I explained, you simply cannot plot X=0. Including 0 in the scale simply makes the plot result undefined. And because it is undefined, both versions might render differently, and that does not mean that this is unintended behaviour. It simply means that you're pushing a feature off-bounds and implementation details start to show up.

This issue was already discussed and clarified (with the same exact example program) when I implemented this feature. Quote:

The LogY does this because the heatmp starts at 0 and it basically stretches it to -inf. Setting the minimum Y position to something like 0.001 does the trick and works fine. The older implementation didn't have this issue since it rendered a bunch of small quads from the center point, and thus when it rendered the bottom one, the Y coordinate (or X for that matter) was always < 0.0 and was treated accordingly. In the GPU implementation this doesn't happen since I'm rendering the entire quad at once and so the point (X,0) goes to infinity. I think I can patch that one easily so that both heatmaps work the same and there's no need to change anything.

So simply replacing the 0 to something like 0.001 will do the trick.

marcizhu avatar Apr 06 '22 11:04 marcizhu

Huh... I might not be running into the same issue then, because I'm still getting the same output if I set it to like X=20

On Wed, 6 Apr 2022, 13:56 Marc, @.***> wrote:

I disagree. This correction for X=0 is done on the CPU in the master branch and on the GPU in the backends flag. Both branches differ because the implementations differ, and as I explained, you simply cannot plot X=0. Including 0 in the scale simply makes the plot result undefined. And because it is undefined, both versions might render differently, and that does not mean that this is unintended behaviour. It simply means that you're pushing a feature off-bounds and implementation details start to show up.

This issue was already discussed https://github.com/epezent/implot/pull/254#issuecomment-868156329 and clarified https://github.com/epezent/implot/pull/254#issuecomment-868317828 (with the same exact example program) when I implemented this feature. Quote:

The LogY does this because the heatmp starts at 0 and it basically stretches it to -inf. Setting the minimum Y position to something like 0.001 does the trick and works fine. The older implementation didn't have this issue since it rendered a bunch of small quads from the center point, and thus when it rendered the bottom one, the Y coordinate (or X for that matter) was always < 0.0 and was treated accordingly. In the GPU implementation this doesn't happen since I'm rendering the entire quad at once and so the point (X,0) goes to infinity. I think I can patch that one easily so that both heatmaps work the same and there's no need to change anything.

So simply replacing the 0 to something like 0.001 will do the trick.

— Reply to this email directly, view it on GitHub https://github.com/epezent/implot/issues/349#issuecomment-1090182855, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABSS3MJGFAGQ6WTVXKISTJDVDV3WRANCNFSM5SUXYSAQ . You are receiving this because you authored the thread.Message ID: @.***>

clo-yunhee avatar Apr 06 '22 14:04 clo-yunhee

spectrogram_l3rsnRDBw7

clo-yunhee avatar Apr 06 '22 15:04 clo-yunhee

Okay I got it to display something by inverting x to 1-x in the fragment shader. I found it out completely by accident when I set xmin to something extremely low like 1e-10 and it started displaying things. The scale is still wrong but at least it's displaying something...

The issue is not present for horizontal axes, so I think it's related to the fact that vertical texture coords start from 1 at the top

clo-yunhee avatar Apr 06 '22 16:04 clo-yunhee

The issue is not present for horizontal axes, so I think it's related to the fact that vertical texture coords start from 1 at the top

Yeah, I'm also testing it and I found out the same thing. 100% agree, there seems to be a bug related to the Y axis.

marcizhu avatar Apr 06 '22 16:04 marcizhu

I can't find where the bug is. Inverting the input value into the fragment shader does seem to fix the issue, but inverting the UV coordinates (which should be the same) does not. Furthermore, any other sample application does work as expected... I'm starting to think there is something wrong with this example.

marcizhu avatar Apr 06 '22 17:04 marcizhu

I managed to fix it by completely redoing the way log scale transforms are done. It's slightly more costly on the CPU side but it also allows to use other scales. I did this on my fork where I added support for alternate frequency scales but I'll make a PR upstream at some point

But more or less, in ImPlotAxis::UpdateTransformCache() :

LogM = IsLog() ? ImLog10(Range.Min) : 0;
LogR = IsLog() ? ImLog10(Range.Max) - LogM : 0;

In PixelsToPlot() and PlotToPixels() :

if (IsLog()) {
    double t = (plt - Range.Min) / Range.Size();
    plt = ImPow(10, t * LogR + LogM);
}

if (IsLog()) {
    plt      = plt <= 0.0 ? IMPLOT_LOG_ZERO : plt;
    double t = (ImLog10(plt) - LogM) / LogR;
    plt      = ImLerp(Range.Min, Range.Max, (float)t);
}

And similar in TransformerLog, and it renders as expected.

It uncovered another bug though, where the very last row of the heatmap isn't rendered properly:

spectrogram_IN78scs3T6

clo-yunhee avatar Apr 06 '22 19:04 clo-yunhee

Wow, awesome! So now you basically do the transformation on the CPU side and then you render the heatmap on the GPU without applying any transformation? Is that correct? I'm just curious, as this seems like a really good solution. And according to the framerate shown on the images, it looks like the performance is unchanged. Good job! 😄

It uncovered another bug though, where the very last row of the heatmap isn't rendered properly

This looks like it is wrapping around (which it should, since it is an OpenGL texture after all...). I might take a look at it in the coming days and open a PR if I find out what's wrong. Thanks for reporting!

marcizhu avatar Apr 06 '22 20:04 marcizhu

So now you basically do the transformation on the CPU side and then you render the heatmap on the GPU without applying any transformation? Is that correct?

Nah, it's pretty much the same process as before, transform from linear on [min,max] to log on the CPU side, transform from log to linear on [0,1] on the GPU side.

The "only" thing I changed was the way that the CPU-side log transform was being done. The quotient method seemed to have too many tricky edge cases.

You can look at the code on the fork if you want, though it does also have extra code to include other scales than log.

The general formula that I used is that for any scale transformation function Tr, the forward transform from [min,max] to [0,1] is (Tr(x) - Tr(min)) / (Tr(max) - Tr(min)) and the inverse transform from [0,1] to [min,max] is InvTr(x * (Tr(max) - Tr(min)) + Tr(min))

clo-yunhee avatar Apr 06 '22 20:04 clo-yunhee

The "only" thing I changed was the way that the CPU-side log transform was being done. The quotient method seemed to have too many tricky edge cases.

Oh! That explains why I didn't find the bug in the shader 😂 Thanks for sharing the fork though, will definitely take a look at it :)

marcizhu avatar Apr 06 '22 20:04 marcizhu

Could this log scaling be extended to include negative values? (as described in J Beau W Webber 2013 Meas. Sci. Technol. 24 027001 https://doi.org/10.1088/0957-0233/24/2/027001)

rowlesmr avatar Apr 08 '22 14:04 rowlesmr