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

Replace radial_distortion_threshhold with directly setting radius_at_origin #3381

Merged
merged 17 commits into from
Nov 20, 2023

Conversation

ffreyer
Copy link
Collaborator

@ffreyer ffreyer commented Nov 17, 2023

Description

Fixes #3375 by allowing users to set a radial offset that can be used to map a negative rmin to 0. E.g.:

pow2db(x) = 20log10(x)    
theta_prime = pi
angle(a,b) = min(abs(a-b), 2π-abs(a-b))
gain(theta) = abs(sinc(2.0*angle(theta,theta_prime)))

thetas = range(0, 2pi, length=361)
rs = pow2db.(gain.(thetas))

f_polar = Figure()
ax_polar_lin = PolarAxis(f_polar[1,1], title="Gain")
lines!(ax_polar_lin, thetas, gain.(thetas))
ax_polar_dB = PolarAxis(f_polar[1,2], title="Gain in dB", radius_at_origin = -80)
lines!(ax_polar_dB, thetas, rs)
f_polar

Screenshot from 2023-11-17 14-46-38

Some open questions:

  • Should negative rlimits automatically result in radii getting shifted?
  • Should zooming be allowed to change radius_at_origin?
  • Should we adjust theta_0 to work like r0/radius_at_origin? Currently we have theta_out = theta_in + theta_0 and r_out = r_in - r0

Note that offsetting radii results in distortions:

phis = range(pi/4, 9pi/4, length=201)
rs = 1.0 ./ sin.(range(pi/4, 3pi/4, length=51)[1:end-1])
rs = vcat(rs, rs, rs, rs, rs[1])

fig = Figure()
ax = PolarAxis(fig[1, 1], radius_at_origin = -2)
lines!(ax, phis, rs)
ax = PolarAxis(fig[1, 2])
lines!(ax, phis, rs)
ax = PolarAxis(fig[1, 3], radius_at_origin = 0.5)
lines!(ax, phis, rs)
fig

Screenshot from 2023-11-17 14-57-01

Changes

  • replace radial_distortion_threshold with radius_at_origin which allows you to set an offset for radii
  • add clip_r::Bool to PolarAxis and Polar transform, allowing you to set whether to enforce $r \ge 0$ or not
  • for clip_r = true (old behavior) return NaN rather than r = 0 if $r < 0$.

Type of change

  • (Somewhat minor) Breaking change (fix or feature that would cause existing functionality to not work as expected)

Checklist

  • Added an entry in NEWS.md (for new features and breaking changes)
  • Added or changed relevant sections in the documentation
  • Added unit tests for new algorithms, conversion methods, etc.
  • Added reference image tests for new plotting functions, recipes, visual options, etc.

@MakieBot
Copy link
Collaborator

MakieBot commented Nov 17, 2023

Compile Times benchmark

Note, that these numbers may fluctuate on the CI servers, so take them with a grain of salt. All benchmark results are based on the mean time and negative percent mean faster than the base branch. Note, that GLMakie + WGLMakie run on an emulated GPU, so the runtime benchmark is much slower. Results are from running:

using_time = @ctime using Backend
# Compile time
create_time = @ctime fig = scatter(1:4; color=1:4, colormap=:turbo, markersize=20, visible=true)
display_time = @ctime Makie.colorbuffer(display(fig))
# Runtime
create_time = @benchmark fig = scatter(1:4; color=1:4, colormap=:turbo, markersize=20, visible=true)
display_time = @benchmark Makie.colorbuffer(fig)
using create display create display
GLMakie 3.33s (3.31, 3.36) 0.02+- 383.56ms (379.51, 386.13) 1.98+- 666.38ms (654.17, 706.86) 18.11+- 7.17ms (7.07, 7.28) 0.07+- 25.25ms (25.17, 25.29) 0.04+-
sd/beta-20 3.34s (3.32, 3.35) 0.01+- 438.49ms (383.39, 515.89) 68.21+- 654.86ms (646.84, 659.44) 4.82+- 7.18ms (6.99, 7.28) 0.11+- 25.30ms (25.20, 25.80) 0.22+-
evaluation 1.00x invariant, -0.01s (-0.41d, 0.46p, 0.01std) 1.14x noisy🤷‍♀️, -54.93ms (-1.14d, 0.08p, 35.09std) 0.98x invariant, 11.52ms (0.87d, 0.15p, 11.46std) 1.00x invariant, -0.01ms (-0.06d, 0.91p, 0.09std) 1.00x invariant, -0.06ms (-0.35d, 0.54p, 0.13std)
CairoMakie 3.02s (2.99, 3.07) 0.03+- 340.11ms (336.28, 345.99) 3.24+- 149.32ms (147.34, 151.30) 1.38+- 7.54ms (7.45, 7.62) 0.06+- 606.26μs (601.20, 612.05) 3.85+-
sd/beta-20 3.01s (2.98, 3.03) 0.02+- 339.81ms (332.09, 347.06) 5.22+- 150.50ms (147.34, 155.89) 2.61+- 7.51ms (7.41, 7.64) 0.07+- 605.52μs (602.36, 609.27) 2.55+-
evaluation 1.00x invariant, 0.01s (0.46d, 0.41p, 0.02std) 1.00x invariant, 0.3ms (0.07d, 0.90p, 4.23std) 1.01x invariant, -1.18ms (-0.57d, 0.32p, 1.99std) 1.00x invariant, 0.03ms (0.42d, 0.44p, 0.06std) 1.00x invariant, 0.75μs (0.23d, 0.68p, 3.20std)
WGLMakie 3.72s (3.67, 3.75) 0.03+- 339.31ms (325.92, 364.15) 15.10+- 8.81s (8.70, 8.90) 0.08+- 10.02ms (9.60, 10.89) 0.45+- 75.72ms (68.80, 86.83) 6.85+-
sd/beta-20 3.72s (3.66, 3.79) 0.04+- 340.94ms (323.34, 351.89) 9.68+- 8.92s (8.61, 9.17) 0.17+- 9.81ms (9.65, 10.05) 0.15+- 70.28ms (68.23, 79.66) 4.15+-
evaluation 1.00x invariant, -0.0s (-0.11d, 0.84p, 0.04std) 1.00x invariant, -1.63ms (-0.13d, 0.81p, 12.39std) 1.01x invariant, -0.11s (-0.82d, 0.16p, 0.12std) 0.98x invariant, 0.22ms (0.64d, 0.27p, 0.30std) 0.93x noisy🤷‍♀️, 5.44ms (0.96d, 0.10p, 5.50std)

Base automatically changed from sd/beta-20 to master November 17, 2023 15:17
@ffreyer
Copy link
Collaborator Author

ffreyer commented Nov 17, 2023

Some open questions:

  1. Should negative rlimits automatically result in radii getting shifted?
  2. Should zooming be allowed to change radius_at_origin?
    3, Should we adjust theta_0 to work like r0/radius_at_origin? Currently we have theta_out = theta_in + theta_0 and r_out = r_in - r0

Personally I think:

  1. is a good idea, since otherwise data is just not shown.
  2. is not something we should do. It feels a bit weird for zooming to get "stuck" at radius_at_origin, but the other choice would be to allow zooming to distort plots. Probably not good default behavior...
  3. is probably good in the long run.

@mikeingold @anowacki @jkrumbiegel @SimonDanisch Maybe any of you have some more thoughts on this?

@ffreyer ffreyer marked this pull request as ready for review November 17, 2023 17:01
@mikeingold
Copy link

Wow! Thanks for the swift response and PR.

  • I would personally prefer PolarAxis automatically adjust radius_at_origin to accomodate negative values, when present. If I'm plotting a line with negative values, I'm not sure why I'd intend to generate a plot with them cropped out by default.
  • Zooming behavior is an interesting question since it's kind of intrinsically a one-dimensional operation (in vs out) but needs to control two parameters (min, max). I don't have much experience with interactive Makie plots and don't have an interactive PolarAxis in front of me at the moment, but maybe zoom-level could just proportionally scale the min-max range, and a mouse click-drag could be used to slide the radius_at_origin to offset the whole range?
  • Unless I'm mistaken, the distortion you showed above is an artifact of the transformed coordinate space, and all of the data appears to be plotted at the correct locations, right? Essentially, the offset r min limit causes a spatial transform such that the space isn't visually-equivalent to a Euclidean space, so shapes won't be preserved. Could be worth noting in the docs, but "it's a feature, not a bug".
  • Side note: I was also a bit surprised to see that the default behavior wasn't to try to plot negative r values on the opposite theta, i.e. (r, theta) -> (-r, theta+pi). I've seen this occasionally, e.g. functions like this.

@ffreyer
Copy link
Collaborator Author

ffreyer commented Nov 18, 2023

We had quite a bit of discussion in #3154 about having a positive r shift as a default and decided not to. You're right that the placement is still correct, but having shapes distort may mislead people when they're not aware of it. Having users explicitly adjust some attribute can be a useful barrier here, I think.

From that point of view we should have radius_at_origin default to 0 to avoid distorting shapes and not have r_out < 0 draw since $r = \sqrt{x^2 + y^2} \ge 0$. (Currently there is a max(0.0, r_out) so some plots will still draw.)

@mikeingold
Copy link

It sounds like the core principles we'd like to respect are:

  • Plot all data that the user asked to be plotted
  • Maintain a geometry that is visually consistent with the Euclidean plane

In the case of strictly non-negative data, both principles are satisfied by just pinning radius_at_origin to zero, so that seems like a fairly logical default. If a user sees the result and feels like they'd prefer a tighter limit range, then it should probably be configurable to allow this as a non-default option.

In the presence of negative data, though, we can't simultaneously satisfy both principles by default. So then the question in my mind is: which do most users expect should take precedence? In my experience, polar plots with negative values are very common, and the data isn't expected to have a literal physical interpretation.

As a mostly-casual Makie user, my experience with this was roughly: plot with a default PolarAxis, get confused by the result, troubleshoot whether my data was bad, see if lines! was setup wrong, look for options to manually set limits, etc. The change in this PR that allows non-default negative radius_at_origin would at least allow users like me to generate these plots, but I'd argue that if I'm asking to plot negative data, then I'm probably already expecting to see negative data. At the very least, I'd appreciate a @warning that explains why I'm not seeing what I expected.

tl;dr, In my opinion:

  • This PR improves the Makie experience by enabling polar plots to show negative data, a very common need for (at least) EE's.
  • In the case of strictly-positive data, configuring radius_at_origin = 0 is a good default.
  • However, in the presence of negative data, the default should be to show the data offsetting radius_at_origin.

Comment on lines +244 to +255
function data_limits(p::Triplot{<:Tuple{<:Vector{<:Point}}})
if transform_func(p) isa Polar
# Because the Polar transform is handled explicitly we cannot rely
# on the default data_limits. (data limits are pre transform)
iter = (to_ndim(Point3f, p, 0f0) for p in p.converted[1][])
limits_from_transformed_points(iter)
else
# First component is either another Voronoiplot or a poly plot. Both
# cases span the full limits of the plot
data_limits(p.plots[1])
end
end
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Test failures come from this adjusting PolarAxis limits a little. It's the same code as we already have for voronoiplots

@jkrumbiegel
Copy link
Member

In the presence of negative data, though, we can't simultaneously satisfy both principles by default. So then the question in my mind is: which do most users expect should take precedence? In my experience, polar plots with negative values are very common, and the data isn't expected to have a literal physical interpretation.

I've never used polar plots for real (not common at all in neuroscience / psychology) so I don't have much of an opinion. But this line of reasoning sounds valid to me, and we could adjust r to include all data in case there's negative data.

How are other plotting libraries handling this?

@SimonDanisch SimonDanisch reopened this Nov 20, 2023
@SimonDanisch SimonDanisch merged commit 7e72ed9 into master Nov 20, 2023
21 of 28 checks passed
@SimonDanisch SimonDanisch deleted the ff/radius_at_origin branch November 20, 2023 16:40
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.

PolarAxis unable to set negative rlimits
5 participants