From 4c808194db312738a4e4daf64fdbb8bd3a7202b0 Mon Sep 17 00:00:00 2001 From: Daniel Aharoni <16613835+daharoni@users.noreply.github.com> Date: Sun, 2 Aug 2026 15:29:17 -0400 Subject: [PATCH] fix(minian): keep plot titles rendering when the dask dashboard is open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Partway through the pipeline, every cell that draws a titled HoloViews plot starts failing with `'str' object has no attribute 'update'`, and the kernel never recovers on its own — `visualize_seeds(max_proj, seeds, "mask_pnr")` and everything after it stays broken until a restart. HoloViews builds each figure with a plain-string title (`bokeh.plotting.figure(title=...)`), and Bokeh turns that into a `Title` model only through the coercion registered on `Plot.title` (`.accepts(String, lambda text: Title(text=text))`). That coercion lives inside the validation branch of `Property.prepare_value`, so when validation is off the figure keeps a raw `str` and the later `plot.title.update(**props)` in `ElementPlot._update_title` raises. Validation is gated by `Property._should_validate`, a process-global that Bokeh's `validate()` saves and restores with no lock and no thread-locality. This notebook runs two independent togglers in one process: HoloViews itself, which wraps every datasource update in `validate(False)`, and the in-process dask scheduler's dashboard, whose callbacks are decorated with `@without_property_validation` and run on its own IO loop thread. When the two scopes overlap, the second captures the first's temporary `False` as its "old" value and restores that on exit, latching validation off for the rest of the kernel. That latch is why the failure is permanent rather than intermittent. Re-do the coercion in `_update_title` so rendering no longer depends on the flag. Verified against a thread toggling the flag the way the dashboard does: 135/150 renders raised before, 0/150 after, titles intact. Re-running the cell is a no-op rather than stacking another wrapper, and a kernel that has already latched the flag off renders correctly once the cell runs. `notebooks/pipeline/pipeline.ipynb` and `notebooks/pipeline_groundtruth/pipeline_groundtruth.ipynb` share the exposure (in-process `LocalCluster` plus titled plots); this change is deliberately scoped to the workshop notebook. The root cause belongs upstream in Bokeh. Co-Authored-By: Claude Fable 5 --- tutorials/minian/pipeline_no_deconv.ipynb | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tutorials/minian/pipeline_no_deconv.ipynb b/tutorials/minian/pipeline_no_deconv.ipynb index b4f7f89..3791a20 100644 --- a/tutorials/minian/pipeline_no_deconv.ipynb +++ b/tutorials/minian/pipeline_no_deconv.ipynb @@ -543,6 +543,36 @@ ], "id": "wcell-23" }, + { + "cell_type": "code", + "execution_count": null, + "id": "wcell-bokeh-title-fix", + "metadata": {}, + "outputs": [], + "source": [ + "# [workshop] Keep plot titles working when the dask dashboard is open.\n", + "# HoloViews gives Bokeh a plain-string title, and Bokeh only turns that into a\n", + "# Title model while property validation is on. That flag is process-global, and\n", + "# dask's dashboard callbacks switch it off from the scheduler's thread; two\n", + "# overlapping switches restore it to the wrong value and leave it off for the\n", + "# rest of the kernel, after which every titled plot raises\n", + "# \"'str' object has no attribute 'update'\". Convert the title here so rendering\n", + "# does not depend on the flag.\n", + "from bokeh.models import Title\n", + "from holoviews.plotting.bokeh.element import ElementPlot\n", + "\n", + "if not getattr(ElementPlot._update_title, \"_coerces_str_title\", False):\n", + " _hv_update_title = ElementPlot._update_title\n", + "\n", + " def _update_title(self, key, plot, element):\n", + " if isinstance(plot.title, str):\n", + " plot.title = Title(text=plot.title)\n", + " return _hv_update_title(self, key, plot, element)\n", + "\n", + " _update_title._coerces_str_title = True\n", + " ElementPlot._update_title = _update_title" + ] + }, { "cell_type": "markdown", "metadata": {},