Repository navigation
Add Tiled streaming API specific to Bluesky specs - #64
Conversation
Assisted-by: oh-my-pi:gpt-5.6-terra
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #64 +/- ##
==========================================
+ Coverage 78.23% 78.38% +0.14%
==========================================
Files 15 16 +1
Lines 2343 2387 +44
==========================================
+ Hits 1833 1871 +38
- Misses 510 516 +6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
I like that it's a context manager.
I think we should aim to include this in the first iteration of this - I think the most common use case here will be to spin up a service that makes this subscription, that will only process a subset of runs. So some kind of filtering I think is key. Now that #60 is merged and any release with this streaming feature should include that, I think just adding a way to filter by specs should be sufficient (and easy to implement I think?) |
Assisted-by: oh-my-pi:gpt-5.6-terra
|
Another API for this might simply be a notification channel. For example, I may want to be able to simply know when a Bluesky run ended and capture its UID, so I can query Tiled normally for the data. The alternative to this would be to repeatedly poll the catalog for new runs and not use websockets at all. Question for Tiled developers reviewing this work: does a |
|
Tested this out in the linked PR to tst-sim-tools and can show that the thread cleanup is behaving nicely. I've run ~20 runs all from the same initial subscription. If cleanup wasn't working, then I would expect there to be many more active threads with tiled subscriptions. This was done with a local Tiled server, as you can tell from the thread name. This image shows during a run: This image shows after the run has stopped (still one catalog-level subscription): |
|
This is great to see, @thopkins32! Ever since you opened #6 I've agreed this is very needed. Thank you for taking it on. The API design looks great. Seconding @jwlodek, I love the context manager. I left one question in line. It's based on an admittedly speedy read, so I've missed the point feel free to disregard. Regarding a "notification" channel, it's interesting to consider how this would complement the web hooks feature added in Spring 2026. Webhooks serve the notification use case well because it does not require a long-lived connection (like a web socket). However, it does require a running HTTP endpoint that the server can reach. If the recipient is, say, running in a Jupyter notebook, a websocket option would be good to have.
It would be nice! But, indeed, unfortunately not. To be precise:
|
|
@danielballan thanks!
Ah, I was always told not to emit these docs until the data was actually written. I get that Tiled can't assume this to be true, generally speaking. Overall, I think it's fine, it typically shouldn't be a long delay between seeing that a data stream is closed and the data being persisted to Tiled's queryable storage. And if there is a long delay, then it's likely there is a wider problem with file writing or network issues. |
|
In ophyd async at least we guarantee for ADs that the docs are only emitted once the data has been flushed to disk, although the storage side of things of course still remains a consideration. |
|
That is good to know. Does ophyd-async block [on] WaitForPlugins before adding a stream datum to the cache returned by collect_asset_docs? |
| run_uid: str | ||
| stream_name: str | ||
| data_keys: tuple[str, ...] | ||
| update: LiveArrayData | LiveArrayRef | LiveTableData |
There was a problem hiding this comment.
One of the key ideas of bluesky-tiled-plugins is to blur the distinction between data_keys stored in tables (i.e. those values coming from Event documents, written to the internal table) and arrays (either internal or external). I wonder if we should be converting LiveTableData into LiveArrayData too.
There was a problem hiding this comment.
I'm not sure I like that approach. If things are already bundled into discrete events then I would like them to maintain that coupling. If we split into LiveArrayData for every field in LiveTableData, I would likely put it back into that same table structure as the first step of my callback.
There was a problem hiding this comment.
I agree that it's good to keep the bundling. I just want to flag that the user should not be able to see (and will not, with the plugins client) the internal table directly. Therefore, it might be confusing to receive table updates.
Perhaps, we need to rething how we structure bluesky data in the BlueskyRun (consolidated) container.
We only Similarly we emit watcher updates on frames written to disk rather than for frames collected by the detector. |
jwlodek
left a comment
There was a problem hiding this comment.
Generally looks really good to me. I have a use case at IOS that I plan on giving this a spin with.
One thing I'd also be interested in is the ability to just have the stream fetch all the datakeys in the stream without me having to know them ahead of time. Or at least to have a way to determine which keys are interesting automatically somehow (something like hints).
|
Thanks for all the feedback, it has really helped me think through this in more detail. I overfit my implementation to my specific use-case a bit too much (single Bluesky data stream, metadata context required through updates) Here is what I will do before the next review round:
|
… Update docs with less repititive information, clearer content; Add None as select-all options; Add tests for ragged and bytes nodes Assisted-by: oh-my-pi:gpt-5.6-sol


Assisted-by: oh-my-pi:gpt-5.6-terra
See #64 (comment) for changes discussed based on review.
Summary
Closes #6
Implements a
bluesky_tiled_plugins.streamingmodule that exposes public interfaces:BlueskyStreamUpdate: adataclasswrapping a Tiled update that contains additional informationBlueskyStreamSubscriptionis a manager class for handling the recursive subscriptions and complicated disconnect behavior, it is acontextmanagersubscribe_to_streamis the primary entry-point, returning aBlueskyStreamSubscriptionsubscribe_to_stream_by_metadatais the same function with explicit filtering by metadatasubscribe_to_stream_by_specsis the same function with explicit filtering by non-empty spec set intersectionsubscribe_to_stream_filteredis the same function for more custom filteringTiledWriter.stopnow closes the stream tree of all nodes it created in order to notify consumers that no more updates are expected, the websocket connection can be closed, and subscriber threads can be released.Tests are written in
tests/test_streaming.py.Docs are written in
docs/tutorials/streaming.md.There were other miscellaneous formatting fixes applied for some reason.
Design Choices
Subscriptions are created as follows:
There is no buffering, synthetic joining, row alignment, or missing-value policy. That is up to the implementer of the callback to deal with.
Lifecycle is handled via TiledWriter explicitly declaring a stream closed, an explicit
disconnect, or the context manager (which callsdisconnecton exit).disconnectblocks until all subscriptions disconnected, done in reverse creation order, however Tiled's client natively handles that.Testing
Future Work
A narrower notification streaming API that simply notifies when a run starts and stops (or when a stream opens and closes), this could be useful when some code needs to react to a run stopping and act on the full batch of run data (which can be loaded directly from
.read())