This is a simple live image viewer using matplotlib to display the images. The goals of this design are:
- Can display 512 x 512 images at 10 fps or faster
- Depends only on numpy and matplotlib
- Only show the most up-to-date image; we don't need to display every one
- Data acquisition and display should be independent due to differences in the rates at which we grab frames and display them.
- In-process concurrency in Python requires that the frame grabber library explicitly releases the GIL in the C layer. We can only hope this is the case.
- Concurrency with multiple processes requires data de/serialization . This will likely be a bottleneck for large images at high frame rates.
The design is based around the following components:
- A numpy array backed by a shared memory buffer to share data between processes
- A main process that puts camera frame buffers into the shared numpy array
- A display process that displays the images
- Two unidirectional pipes between the processes; one from the main process to the display, and vice-versa
The shared memory buffer is just a 1D numpy array with a custom put
method. Calling it will insert the data at the current index of the buffer. Once the end of the buffer is reached, it wraps around to the beginning. The shared memory avoids de/serializing image data. Instead, the image's start and stop indexes are shared through the main-to-display pipe.
Backpressure is applied by the display process by sending a Ready
message to indicate that it is ready to display a new frame. The main process polls this pipe and only sends index data when it receives a Ready
message.
There is no lock on the shared memory; instead, I assume there is only ever one writer (the main process). Any process that reads from this array must be fast enough to read a block of data before the main process arrives back at the same block after looping through the buffer. This means that you should set the capacity based on your use case to avoid this race condition.
Another process could be added to save the frames, but in this case care must be taken to ensure that every frame is saved. (The display process only shows the most recent frame.) The buffer capacity should therefore be large enough so that the data acquisition cannot loop around the buffer and pass the current location in the buffer of the save process.
You would also likely need to use a Queue
to pass messages between the main and save processes since messages might need to be buffered as well.
I have done minimal testing of this script as it was good enough for my purposes. On a Windows 11 machine, I could simulate 512 x 512 images at about 20 fps with one dropped frame every ~10 frames. Your mileage may vary.