Subpixel precision for dash Graphs showing an img with plotly.express.imshow

Hi,

I am trying to figure out how I can get sub pixel precision with click data from dash graphs, when I click on a graph showing an Image

the graph is created as dash.graph and then I have the figure loaded with fig = plotly.express.imshow then adding the scatter

fig.add_trace(
plotly.graph_objs.Scatter(x=x, y=y, mode=“markers”, marker=dict(color=“red”, size=5))
)

and here is the input setting

 Input("image-graph", "clickData"),

so, when I click I have always integer coordinates and I need to have sub pixel precision selected coordinates similarly to what we can get with matplotlib

here is my partial code


points = []
config = {
    "modeBarButtonsToAdd": [
        "drawline",
        "drawopenpath",
        "drawclosedpath",
        "drawcircle",
        "drawrect",
        "eraseshape",
    ],
    'scrollZoom': True
}
fig = go.Figure()


controls = dbc.Card(
    [
        html.Div(
            [
                dbc.Label("UUID"),
                dcc.Dropdown(
                    id="uuid",
                    options=[
                        {"label": uuid, "value": uuid}
                        for uuid in dataset.values("UUID")
                    ],
                    value=dataset.first()["UUID"],
                    searchable=True
                ),
            ]
        ),
        html.Div(
            [
                dbc.Label("Slice"),
                dcc.Dropdown(
                    id="slice",
                    options=[
                        {"label": _slice, "value": _slice}
                        for _slice in dataset.group_slices
                    ],
                    value=dataset.default_group_slice,
                ),
            ]
        ),
    ],
    body=True,
    className="mb-3"
)

app.layout = dbc.Container(
    [
        html.H1("Image Point Selector", style={"textAlign": "center"}),
        html.Hr(),
        dbc.Row(
            [
                dbc.Col(
                    [
                        controls,
                        dcc.Graph(
                            figure=fig,
                            id="image-graph", 
                            style={"width": "100%", "height": "70vh"},
                            config=config
                        ),
                        html.Div(
                            [
                                dbc.Button(
                                    "Save Points", id="save-btn", className="me-1"
                                ),
                            ],
                            className="d-grid gap-2 col-6 mx-auto mt-3",
                        ),
                    ],
                    width={"size": 10, "offset": 1},  # Centering and stretching the graph
                ),
            ],
            align="center",
        ),
        dcc.Store(id="points-store", data=[]),
        html.Hr(),
    ],
    fluid=True,
)



# Update the graph with the image and points
@app.callback(
    Output("image-graph", "figure"),
    Output("points-store", "data"),
    Input("image-graph", "clickData"),

    Input("save-btn", "n_clicks"),
    Input("uuid", "value"),
    Input("slice", "value"),
    State("points-store", "data"),
)
def update_image(clickData, save_clicks, uuid, _slice, stored_points):
    global width,height, points, sample , img_str, summary_points
    ctx = dash.callback_context

    #This block should run only on initial load or when slice or uuid is changed
    if  ctx.triggered[0]["prop_id"]== "." :   
        points = []
        summary_points = []
        img_str, image_size = load_image(sample.filepath)
        width, height = image_size  

    if ctx.triggered and "clickData" in ctx.triggered[0]["prop_id"]:
        points = stored_points or []
        #print(clickData)

    fig = px.imshow(np.array(Image.open(io.BytesIO(base64.b64decode(img_str)))))
    
    if points:
        x, y = zip(*points)
        fig.add_trace(
            go.Scatter(x=x, y=y, mode="markers", marker=dict(color="red", size=5))
        )
    graph_output = {"data": fig.data, "layout": {"uirevision": f"{uuid}-{_slice}"}}

    return graph_output, points


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("--host", type=str, default="0.0.0.0")
    parser.add_argument("--port", type=int, default=8050)
    parser.add_argument("--debug", action="store_true")
    return parser.parse_args()


if __name__ == "__main__":
    args = parse_args()
    app.run_server(**vars(args))


Thank you! and sorry if I am in the wrong category

HI @hichem welcome to the forums.

Just using the clickData from plotly will not work as no events are emitted “between” two pixels. What I could imagine is using a combination of the plotly clickData event and an eventListener (dash-extensions).