Scaling a rotating image for consistent size on screen

Hello,

I need a better way of scaling an image on the screen so it’s size remains constant despite changing zoom from the wheel-zoom.

The goal is to have the image size remain the same on the screen despite axes scale changes.

The example below works but is a somewhat disappointing solution that uses a zoom_scale variable changed by a callback when the axes scale changes. It works but is a bit wonky and also places cpu load on the server side rather than client side, in the browser.

Starting from the example code in this post, there’s an additional zoom_callback that changes the zoom_scale variable.

Is there a better way to do this??? I cannot migrate to more than just a couple of lines of Javascript. Best solution will be purely python.

# bokeh serve --show 
rotate_img_code_decode_image_format_and_scaling_example_FOR_POSTING.py
from base64 import b64decode
import sys

import cv2
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, Button
from bokeh.plotting import figure

import imutils # rotate_bound()

T_update = 10 # (ms) browser update rate specified in add_periodic_callback()
current_angle = 0

img_jpeg = b64decode('iVBORw0KGgoAAAANSUhEUgAAADIAAAAOBAMAAACfqVJUAAAAHlBMVEUAAAAAAwCKLY3jF17xWyICq65Qlc8js1T2qhmnzTbxGUeMAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAACVSURBVBjTpY89DsIwDIWfRRF087sB6sCMyhHIAbp078TMgroy9gq5LXYSooqOfYqsF3+Jf0BU6c9IcjvI6bUlJiDO78EJIXYVJRXptHEeRyP22PPC7Cy2EUcnKQOmkPrwHIHnpNQNWT5OvLi1oa7/LMB1ouZqqES0CUCXm6+Iz9aER9/5bKyYZZ9w7y91n0KKDjf86QvVrhIPSsvLCwAAAABJRU5ErkJggg==')
arr = np.fromstring(img_jpeg, np.uint8)
frame = cv2.imdecode(arr, cv2.IMREAD_UNCHANGED)
orig_img = frame[::-1].copy().view(dtype=np.uint32)


pw = 5*orig_img.shape[0]
ph = 5*orig_img.shape[1]
current_angle = 0
p = figure(aspect_ratio=1, x_range=(0, pw), y_range=(0, ph))


def mk_data(image,zoom_scale):
    ih, iw, _ = image.shape
    return dict(image=[image], dw=[int(zoom_scale*iw)], dh=[int(zoom_scale*ih)], x=[(pw - iw) / 2], y=[(ph - ih) / 2])

src = ColumnDataSource(data=mk_data(orig_img,1.0))
p.image_rgba(image='image', x='x', y='y', dw='dw', dh='dh', source=src)



# --------- image icon Zoom scaling ------------------
myXrange_o = p.x_range.end - p.x_range.start
# scale icon images when zoom changes
def zoom_callback(attr,new,old):
    global zoom_scale
    myXrange = p.x_range.end - p.x_range.start
    zoom_scale = myXrange/myXrange_o
    if zoom_scale<0.1: zoom_scale=0.1
    if zoom_scale>100.0 : zoom_scale=100.0
    print('zoom_scale={0}, myXrange_o={1}, myXrange{2}'.format(zoom_scale,myXrange_o,myXrange))     
p.x_range.on_change('end', zoom_callback)
zoom_scale=1.0 # initial zoom scaling factor

b1 = Button(label='Rotate once!')

def rotate_img_by_15_deg():
    global current_angle
    current_angle += 15
    img = imutils.rotate_bound(orig_img.view(dtype=np.uint8), current_angle) # use imutils with cv2 
format image
    src.data = mk_data(img.view(dtype=np.uint32),zoom_scale) # re-create attributes for bokeh using 
updated image

def callback_button():
    global periodic_cb_id
    if b2.label == 'Press to: > Play':
        print('adding add_periodic_callback()')
        b2.label = 'Press to: = Pause'
        periodic_cb_id = curdoc().add_periodic_callback(callback_update_data, T_update) # <-- this 
controls update frequency
    else:
        print('removing add_periodic_callback()')
        b2.label = 'Press to: > Play'
        curdoc().remove_periodic_callback( periodic_cb_id )

def callback_update_data():
    global current_angle
    #global zoom_scale
    current_angle += 1
    sys.stdout.flush()
    img = imutils.rotate_bound(orig_img.view(dtype=np.uint8), current_angle) # use imutils with cv2 
formated (.view'ed) image
    src.data = mk_data(img.view(dtype=np.uint32),zoom_scale) # re-create all image attributes for 
bokeh using updated img


b1.on_click(rotate_img_by_15_deg)

b2 = Button(label='Press to: > Play')
b2.on_click(callback_button)


curdoc().add_root(column(p, b1, b2))

Marc

I need a better way of scaling an image on the screen so it’s size remains constant despite changing zoom from the wheel-zoom.

I’m trying to understand the actual interaction and use-case here. Is this for having a fixed background image, or a watermark? I can’t really image what else it would be useful to have a fixed image that doesn’t change with axis scale for.

Just double checking assumptions: Are you saying that setting e.g. dw_units to "screen" and specifying a size in pixels does not work? Or that you don’t want use pixels as units? The linked issue concerns preserving size through rotation but that is not a concern here?

@Bryan, certainly, this is a need to have glyphs in screen units, as you suggested, while allowing movement over a map that can scale for zooming in or out.

the solution you suggested about dh_units='screen' (and dw_units) works well for achieving constant size glyphs. but now the rotation is a little wonky. it doesn’t stay in one place. I’ll update the original thread and perhaps @p-himik can make a suggestion.

Thanks. Eliminating a scale_factor and callback was the goal here. that’s been accomplished, although with a side effect.