Using a DynamicMap - how do i return a cropped image to another plot, column or row to display the cropped image

here is the code i have to date.


from PIL import Image

import requests
from io import BytesIO
import numpy as np
import logging
import sys


import holoviews as hv
from holoviews import opts
from holoviews import streams

import panel as pn




pn.extension(sizing_mode='stretch_both')
pn.extension('terminal')
hv.extension('bokeh')


FORMAT = "** %(asctime)s | %(levelname)s | %(name)s | %(message)s"

def reconfig_basic_config(format_=FORMAT, level=logging.INFO):
    """(Re-)configure logging"""
    logging.basicConfig(format=format_, level=level, force=True)
    logging.info("Logging.basicConfig completed successfully")

reconfig_basic_config()

logger = logging.getLogger(name="app")

def box_cropper(bounds):
    if bounds is None:
        b = hv.Rectangles((0, 0, 0, 0))
    else:
        b = hv.Rectangles(bounds)
    b.opts(alpha=0.3, line_color='red')

    info = imagev._get_selection_expr_for_stream_value(bounds=bounds)


    (dim_expr, bbox, region_element) = info

    logger.info(bbox)
    
    if bbox:
        cartesian = {"x0": bbox["x"][0], "y0": abs(bbox["y"][1]), "x1": bbox["x"][1], "y1": abs(bbox["y"][0])}
        logger.info(f"cartesian = {cartesian}")

        
        # Define the crop box (left, upper, right, lower)
        crop_box = (cartesian["x0"], cartesian["y0"], cartesian["x1"], cartesian["y1"])  # You can change these values
        logger.info(f"crop_box = {crop_box}")
    
    
        cropped_image = theimage.crop(crop_box)
        logger.info(f"cropped_image.size={cropped_image.size}")        

        **# how to return and display this image in a separate plot in a different pn.Row, pn.Column**
**        # image_crop = hv.Image(np.array(cropped_image))**

    
    return b

url = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/%D0%9F%D0%BE%D1%80%D1%82%D1%80%D0%B5%D1%82_%D0%BE%D0%BB%D0%B5%D0%BD%D1%8F.jpg/500px-%D0%9F%D0%BE%D1%80%D1%82%D1%80%D0%B5%D1%82_%D0%BE%D0%BB%D0%B5%D0%BD%D1%8F.jpg"

response = requests.get(url)
theimage = Image.open(BytesIO(response.content))

(w,h) = theimage.size

logger.info(f"w={w} h={h}")
bounds=(0,-h,w,0)   # Coordinate system: (left, bottom, right, top)

imagev = hv.Image(np.array(theimage), bounds=bounds)
 
main_style1 = [''' :host { overflow-y: scroll;  } ''']
raw_css = [''' #main { background-color:white; padding: 0px !important; }  ''']


xy_stream = hv.streams.BoundsXY() 

dmap_cropper = hv.DynamicMap(box_cropper, streams=[xy_stream])

w_container = imagev.opts(cmap='gray',responsive=True, tools=["box_select"], active_tools=["box_select"]) * dmap_cropper 

image_viewer = pn.Column(w_container, min_height=h)


template = pn.template.FastListTemplate(
    title="Image Browser - Select n Copy/Crop" ,
    sidebar=[],
    main=[image_viewer],
    collapsed_sidebar=True,
    raw_css=raw_css
)

template.servable();



import panel as pn
import holoviews as hv
from holoviews import streams
import numpy as np
from PIL import Image
pn.extension()

# Function to crop the image based on bounds
def crop_image(bounds):
    if bounds is None:
        return None
    x0, y0, x1, y1 = bounds
    x0, x1 = int(x0), int(x1)
    y0, y1 = int(y0), int(y1)

    # Clip to image dimensions
    x0, x1 = max(0, x0), min(image_data.shape[1], x1)
    y0, y1 = max(0, y0), min(image_data.shape[0], y1)

    cropped = image_data[y0:y1, x0:x1]
    if cropped.size == 0:
        return None
    return cropped


def update_cropped_image(bounds):
    cropped = crop_image(bounds)
    if cropped is None:
        return None
    img_pil = Image.fromarray((cropped * 255).astype("uint8"))
    return pn.pane.Image(
        img_pil,
        width=200,
        height=200,
        caption="Cropped",
        styles={"background": "black"},
    )

image_data = np.random.rand(256, 256)  # Replace with your own image data
hv_image = hv.Image(image_data, bounds=(0, 0, 256, 256)).opts(
    cmap="gray",
    width=500,
    height=500,
    tools=["box_select"],
    active_tools=["box_select"],
)
bounds_stream = streams.BoundsXY(source=hv_image)
layout = pn.Column(
    hv_image,
    pn.bind(update_cropped_image, bounds_stream.param.bounds),
)
layout.show()

Export-1733451011134