Search code examples
pythonaltairvega-litepython-interactive

Altair - plot on background image


I would like to display an image and plot markers on it. However, the image does not appear to align with the axis well, resulting in the markers in wrong places. Here is my code:

import altair as alt
import pandas as pd
from PIL import Image
import numpy as np
from io import BytesIO
import base64


def toBase64(np_img):
    # convert numpy image to base64 string
    with BytesIO() as buffer:
        Image.fromarray(np_img).save(buffer, 'png')
        img_str = base64.encodebytes(buffer.getvalue()).decode('utf-8')
    return f"data:image/png;base64,{img_str}"


h,w = 250,410 
im0 = np.zeros((h,w,3), dtype=np.uint8)
tmp = pd.DataFrame([
    {"x": w//2, "y": h//2, "img": toBase64(im0)}
])
backgrd = alt.Chart(tmp).mark_image(
    width=w,
    height=h
).encode(x='x', y='y', url='img')
b0_df = pd.DataFrame({'x': [0,0,w,w], 'y': [0,h,0,h]})
b0 = alt.Chart(b0_df).mark_circle(size=30, color='red').encode(x='x', y='y').properties(width=w, height=h)
(backgrd+b0)

The results look like this:

enter image description here

The markers are plotted correctly but not the image. Also it seems that the image has been stretched a bit. On plotly I can set xref and yref to make the image scale the same as axis scale, how to do it on altair?


Solution

  • You can try adjusting the axis limits to ensure they don't exceed the x/y maximums :

    # top of your code
    # ...
    
    xmax, ymax = b0_df.max().add(1)
    
    backgrd.encoding.x.scale = alt.Scale(domain=[0, xmax])
    backgrd.encoding.y.scale = alt.Scale(domain=[0, ymax])
    
    b0.encoding.x.scale = alt.Scale(domain=[0, xmax])
    b0.encoding.y.scale = alt.Scale(domain=[0, ymax])
    
    (backgrd+b0)
    

    Output :

    enter image description here