Skip to content

Make get_movie setup eager, acquisition lazy; optimize serval - #166

Merged
Baharis merged 132 commits into
instamatic-dev:mainfrom
Baharis:sped-serval-rework
Aug 29, 2026
Merged

Make get_movie setup eager, acquisition lazy; optimize serval#166
Baharis merged 132 commits into
instamatic-dev:mainfrom
Baharis:sped-serval-rework

Conversation

@Baharis

@Baharis Baharis commented Aug 7, 2026

Copy link
Copy Markdown
Member

Eager setup – lazy acquisition

Instamatic has two methods to get camera feed, get_image and get_movie. The latter is underutilized to the degree where many cameras implement it as loop over get_image. In my work, I strive to flesh out get_movie as 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 general get_image. For this reason, I modified get_movie to return iterator in #121 and updated/supported it since (#137, #154, #160). Here I suggest another improvement to get_movie that will make it more responsive.

In python, every callable can have two exit points, return or yield. When called, methods that return run immediately, but methods that yield do not; instead they return a Generator that executes only once it is iterated using next. 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. Because get_movie always 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_movie in the following way:

  • When get_movie is called: videostream is blocked, camera is configured for a movie, and a generator is created.
  • When generator is iterated: camera immediately starts collecting images.
  • When iteration is over: camera is configured back for get_image and 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:

n_frames, exposure = initial_setup()
movie = ctrl.get_movie(n_frames, exposure)  # stream is stopped, camera is configured here
following_setup()
start_tem_change()
for image, header in movie:                 # trigger: image acquisition is started here
    modify_header_or_tem()                  # cleanup is run automatically after the loop

In order to get this behavior, two changes are required. Firstly, LiveVideoStream and MediaGrabber responsible for streaming need a separate callable with yield that 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 call get_movie immediately 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_movie in 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 then get_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 = True in camera_serval.py or stream_movies_via_tcp = true in camera.yaml.

Changes

  • file: change done;
  • camera_serval.py: add option to STREAM_MOVIES_VIA_TCP, default mechanism remains unchanged; make get_movie setup eager but iteration lazy;
  • serval_movie_deserializer.py: define deserializer to read flat ASI jsonimage from TCP;
  • videostream.py: Add new acquireIterateEvent and modify get_movie to allow easy setup / lazy iteration (default behavior unchanged); add documentation;
  • serval.yaml: Add stream_movies_via_tcp: false to inform user about possibility
  • banchmark_movie_rates.py: Add a script for testing movie speed;
  • controller.py: Fix get_movie return signature
  • test_serval_movie.py: Add tests for the new ServalMovieDeserializer from TCP.

Notes

  • With this change, the default behavior remains basically unchanged: if get_movie is initialized and iterated immediately, it will behave the same as before. Otherwise iteration may now have a lower delay.
  • This branch was split from a larger development branch and contains irrelevant history. Therefore, the PR should be merged to not include this history.
  • While this PR looks like a big change in numbers (+721/-131) please mind that a lot of additions are tests (+59), benchmark (+437), and several lines of documentation.

@Baharis
Baharis requested a review from stefsmeets August 7, 2026 18:34
@Baharis Baharis self-assigned this Aug 7, 2026
@Baharis
Baharis marked this pull request as ready for review August 26, 2026 11:40
@Baharis

Baharis commented Aug 26, 2026

Copy link
Copy Markdown
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.

@Baharis
Baharis merged commit 3396973 into instamatic-dev:main Aug 29, 2026
6 checks passed
@Baharis
Baharis deleted the sped-serval-rework branch August 29, 2026 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant