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