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

[BUG] image tick positions misplaced when specifying image axes #4158

Open
sefffal opened this issue Mar 28, 2022 · 9 comments
Open

[BUG] image tick positions misplaced when specifying image axes #4158

sefffal opened this issue Mar 28, 2022 · 9 comments
Labels

Comments

@sefffal
Copy link
Contributor

sefffal commented Mar 28, 2022

Details

See #4087 and #4088, and tagging @t-bltg.

With GR, the tick positions of images are not placed correctly when the axes of the image are passed to plot:

using Images, Plots
i = rand(RGB, 10, 10)
plot(i) # Fixed by #4088
plot(1:10, 1:10, i) # Still broken

Image plot without specifying axes:

Image plot with axes specified:

It's subtle, but to match the normal behaviour of images and other backends, the +/- 5s should be at the centres of their pixels. The +/-2.5s are also slightly off.
Thanks!

Backends

This bug occurs on ( insert x below )

Backend yes no untested
gr (default) x
pyplot x
plotlyjs x
pgfplotsx x
unicodeplots x
inspectdr x
gaston x

Versions

Plots.jl version:
[91a5bcdd] Plots v1.27.0
Backend version (]st -m <backend(s)>):
[28b8d3ca] GR v0.64.0
Output of versioninfo():

Julia Version 1.7.2
Commit bf53498635 (2022-02-06 15:21 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-12.0.1 (ORCJIT, skylake)
Environment:
  JULIA_NUM_THREADS = 4
  JULIA_EDITOR = code
@sefffal sefffal added the bug label Mar 28, 2022
@t-bltg t-bltg added the GR label Mar 28, 2022
@t-bltg
Copy link
Member

t-bltg commented Mar 28, 2022

I'll have a look in the next few days.

In the mean time does plot(..., xticks=1:10, yticks=1:10) as specified in #4088 (comment) work ?

@sefffal
Copy link
Contributor Author

sefffal commented Mar 28, 2022

Thanks @t-bltg!

i = rand(RGB, 10, 10)
plot(i, xticks=1:10, yticks=1:10)

Works!

But

plot(1:10, 1:10, i, xticks=1:10, yticks=1:10)

does not.

@t-bltg
Copy link
Member

t-bltg commented Mar 29, 2022

I've had a look. The function involved in the example is

@recipe function f(x::AVec, y::AVec, mat::AMat{T}) where {T<:Colorant}

Since the explicit x and y vectors are given, I'm afraid we can't tweak them to fit, because other use cases will be broken.
The following patch could work but it is only increasing complexity, and it does not cover all possible cases:

 
--- a/Plots.jl/src/recipes.jl	2022-03-29 12:37:35.927406939 +0200
+++ b/Plots.jl/src/recipes.jl	2022-03-29 12:34:27.166126958 +0200
@@ -1450,18 +1450,31 @@
 # 3 arguments
 # --------------------------------------------------------------------
 
+function check_image_axes(x, y, z)
+    # fix for github.com/JuliaPlots/Plots.jl/issues/4158
+    if x isa AbstractRange && length(x) == size(z, 2)
+        half_step = step(x) / 2
+        x = range(first(x) - half_step, stop = last(x) + half_step)
+    end
+    if y isa AbstractRange && length(y) == size(z, 1)
+        half_step = step(y) / 2
+        y = range(first(y) - half_step, stop = last(y) + half_step)
+    end
+    x, y, z
+end
+
 # images - grays
 @recipe function f(x::AVec, y::AVec, mat::AMat{T}) where {T<:Gray}
     if is_seriestype_supported(:image)
         seriestype := :image
         yflip --> true
-        SliceIt, x, y, Surface(mat)
+        (SliceIt, check_image_axes(x, y, Surface(mat))...)
     else
         seriestype := :heatmap
         yflip --> true
         colorbar --> false
         fillcolor --> cgrad([:black, :white])
-        SliceIt, x, y, Surface(convert(Matrix{Float64}, mat))
+        (SliceIt, check_image_axes(x, y, Surface(convert(Matrix{Float64}, mat)))...)
     end
 end
 
@@ -1470,13 +1483,13 @@
     if is_seriestype_supported(:image)
         seriestype := :image
         yflip --> true
-        SliceIt, x, y, Surface(mat)
+        (SliceIt, check_image_axes(x, y, Surface(mat))...)
     else
         seriestype := :heatmap
         yflip --> true
         colorbar --> false
         z, plotattributes[:fillcolor] = replace_image_with_heatmap(mat)
-        SliceIt, x, y, Surface(z)
+        (SliceIt, check_image_axes(x, y, Surface(z))...)
     end
 end

I'd suggest to be explicit about what you want to be drawn by using either:

i = rand(RGB, 12, 10)
plot(i, xticks=axes(i, 2), yticks=axes(i, 1))  # should be preferred
plot(1:13, 1:11, i)  # nodes (un-shifted, but confuses the ticking algorithm) [1]
plot(.5:12.5, .5:10.5, i)  # cell centers (un-shifted, but confuses the ticking algorithm) [2]

Note that the plot(x, y, z) syntax plots at x, y coordinates which is different from setting the appearing ticks using plot(..., xticks=...). If you use the former, then you should follow the internal convention of [1,2] which uses the pixel borders (hence nodes) and not the pixel center (cells).

It's very easy to use a helper in the form of:

plot_img(img) = plot(img, xticks=axes(img, 2), yticks=axes(img, 1))

@t-bltg t-bltg added wontfix and removed bug labels Mar 29, 2022
@t-bltg t-bltg closed this as completed Mar 29, 2022
@sefffal
Copy link
Contributor Author

sefffal commented Apr 8, 2022

Hi @t-bltg, here's a motivating example for why I believe this is a bug. The root issue is not the tick positions but the location the image is rendered on the plot.
First, as background I'm working on the AstroImages package, so I'm trying to find a general solution rather than fixing a single plot: JuliaAstro/AstroImages.jl#30

using Images
using Plots
i = rand(RGB, 4,4)

p = plot()
# Plot image
plot!(p, 4:2:10, 4:2:10, i)
# Plot other series
plot!(p, 3:10, 3:10)

In GR, this produces:
image

This behaviour is necessary to be able to offset the position of the image in addition to modifying the tick marks.

As you can see, the tick location still drifts from the left of the pixels to the right of the pixels.

The behaviour is correct in plotly:
image

And also correct in PyPlot:
image

@t-bltg t-bltg added discussion and removed wontfix labels Apr 9, 2022
@t-bltg t-bltg reopened this Apr 9, 2022
@dorn-gerhard
Copy link

the wrong behaviour still exists for RGB / RGBA input:

heatmap(1:4, 1:4, [RGBA(rand(),rand(),rand(),rand()) for i = 1:4, j = 1:4])

image

@sefffal
Copy link
Contributor Author

sefffal commented Apr 24, 2023

1 year bump on this issue

@JeffFessler
Copy link
Contributor

Also facing this issue and hoping to help resolve it or find a work-around.
I want to emphasize that it works fine for numeric data, but not for RGB data.

using Plots
using ColorTypes: RGB
p1 = heatmap(1:6, 1:4, rand(4,6); aspect_ratio=:equal, yticks=1:4, color=:grays, widen=true)
p2 = heatmap(1:6, 1:4, rand(RGB{Float32}, 4,6); aspect_ratio=:equal, yticks=1:4, widen=true)

p1 with numeric data is perfect:
Screenshot 2023-06-14 at 10 03 40 PM

but p2 with RGB data has wrong tick locations:
Screenshot 2023-06-14 at 10 04 52 PM

(Using Julia 1.9.1 with Plots v1.38.16 on Intel Mac.)

@jusack
Copy link

jusack commented Oct 10, 2023

During my bug search in #4833 I identified the recipe for colored matrices as the cause for this issue. Since the gr backend supports the image seriestype and plotly does not, the recipe overrides the series type to image for the gr backend. Therefore, the adjustment step of the indices (heatmap_edges) does not happen causing the undesired behaviour. I will look into a better definition of this recipe and open a PR. The default of using image if no seriestype is specified and it is available is probably fine, even though it will produce different positioning in the backends supporting images or not. But it should not override the seriestype if heatmap is explicitly specified by the user.

@sefffal
Copy link
Contributor Author

sefffal commented Feb 25, 2024

Bump on this!
This is still an issue with the GR backend of Plots.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants