Make get_movie setup eager, acquisition lazy; optimize serval - #166
Merged
Conversation
…. TODO: investigate why.
…s (up to a few hundred)
…d up to ~70 if diffraction
…nd up to ~70 if diffraction
…dexed, plot correctly
# Conflicts: # src/instamatic/camera/camera_serval.py
Baharis
marked this pull request as ready for review
August 26, 2026 11:40
Member
Author
|
Since I have further changes that I would like to make public lined up, I plan to merge this PR ASAP, preferably by the end of the week. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Eager setup – lazy acquisition
Instamatic has two methods to get camera feed,
get_imageandget_movie. The latter is underutilized to the degree where many cameras implement it as loop overget_image. In my work, I strive to flesh outget_movieas a distinct alternative that allows receives images as they are collected, minimizes dead time, and in general allows collecting short burst-series in contrast to the more generalget_image. For this reason, I modifiedget_movieto return iterator in #121 and updated/supported it since (#137, #154, #160). Here I suggest another improvement toget_moviethat will make it more responsive.In python, every callable can have two exit points,
returnoryield. When called, methods thatreturnrun immediately, but methods thatyielddo not; instead they return aGeneratorthat executes only once it is iterated usingnext. This creates lazy iterables, executed only when demanded, which is very useful e.g. for demanding or infinite loops.The lazy/eager distinction of return/yield can be used to minimize
get_movie's dead time. Becauseget_moviealways returns an iterable, it can receive two unique signals to start: one when it is called, and one when it is iterated over. From my experience working with Instamatic I realized that this can be used to create really fine control over timing.Here I suggest utilizing the double-start mechanism of
get_moviein the following way:get_movieis called: videostream is blocked, camera is configured for a movie, and ageneratoris created.generatoris iterated: camera immediately starts collecting images.get_imageand videostream is unblocked.This behavior allows for a really fine control over timing. When writing multi-threaded code I noticed that I often want to start camera and some TEM change concurrently. With proposed mechanism, this can be done the following way:
In order to get this behavior, two changes are required. Firstly,
LiveVideoStreamandMediaGrabberresponsible for streaming need a separate callable withyieldthat is called it as late as possible. Secondly, individual implementations of cameras need to do the same. This allows cameras to "prepare" for movie acquisition, and then start it quickly, which makes synchronizing camera and TEM much easier.Finally, suggested change is non-invasive. If any existing script already uses
get_movie, it will behave just as expected. Ultimately, most persons interested in movies will callget_movieimmediately before iterating them (for i, h in ctrl.get_movie(n, e)), in which case separating startup and iteration will have little to no effect. No changes to other implementations are also strictly needed, unless someone desired to reap benefits of this eager setup / lazy acquisition mechanism.Serval optimization
The current implementation of
get_moviein serval is nice and easy but falls off at high data rates. As confirmed by ASI itself, it was designed as a convenient utility, not with high throughput in mind. Serval client first asks camera for N frames, and thenget_requests every single frame individually. In my tests I noticed that this increased communication introduces delays, to the degree where I could not get more than 8 images per second.To counter it, I implemented a custom TCP stream reader that circumvents ASI package. With it in place, all movie data is streamed by the server continuously. The client does not need to request each image individually. Frames are read in milliseconds. The mechanism is completely optional and can be switched on by setting
STREAM_MOVIES_VIA_TCP = Truein camera_serval.py orstream_movies_via_tcp = trueincamera.yaml.Changes
file: change done;camera_serval.py: add option toSTREAM_MOVIES_VIA_TCP, default mechanism remains unchanged; makeget_moviesetup eager but iteration lazy;serval_movie_deserializer.py: define deserializer to read flat ASI jsonimage from TCP;videostream.py: Add newacquireIterateEventand modifyget_movieto allow easy setup / lazy iteration (default behavior unchanged); add documentation;serval.yaml: Addstream_movies_via_tcp: falseto inform user about possibilitybanchmark_movie_rates.py: Add a script for testing movie speed;controller.py: Fixget_moviereturn signaturetest_serval_movie.py: Add tests for the newServalMovieDeserializerfrom TCP.Notes
get_movieis initialized and iterated immediately, it will behave the same as before. Otherwise iteration may now have a lower delay.+721/-131) please mind that a lot of additions are tests (+59), benchmark (+437), and several lines of documentation.