Microscope Server 2.9 and new SD images

Today we’re releasing our Microscope Server v2.9, and new Raspberry Pi SD images.

Server 2.9

To upgrade your microscope to the latest application, please run

sudo ofm update
sudo ofm upgrade

from your microscope shell.

New SD images

You can download our latest SD images from https://openflexure.org/projects/microscope/build


New features

  • Log viewer now includes a level filter

  • Added a persistent option to invert keyboard movement directions

  • Added a persistent stage backlash and settle time settings

  • Added stream resolution and capture JPEG quality settings

  • Added advanced stream bitrate and framerate controls

  • Added functionality to download a lens-shading table as a PNG image

Developer notes

A large part of this release is a deep cleanup of the code. While the changes run deep into the code, there are 3 key elements worth developers noting:

Typing and static analysis

We now make extensive use of static type analysis within our Python code, to catch potential bugs early on and enforce best practices. Merge requests will fail if mypy or pylint fail.

The full suite of checks can be run locally with poetry run poe check. This will also perform automatic code formatting.

Removed internal background threads

Previously, our live stream was handled by a background thread in charge of processing new frames, and synchronising with clients. In the last release, we removed the use of threading in our fast-autofocus method by using a custom file-like object to store frame data. This has now been extended to the rest of the server code. Live stream frames are written to a custom stream object which handles synchronisation, frame size measurements for autofocus, and discarding old data automatically.

Simplified extension loader

In previous releases, extensions were loaded by implicitly searching for instances of an extension class and attaching them to the server. This promoted bad practices as extension files needed to hold a global instance of the extension object.

In this release, we are promoting the development of extensions as subclasses of BaseExtension. A new global LABTHINGS_EXTENSIONS list in the root of the extension file contains the extension classes themselves, not instances. When the server loads an extension, it searches for extension classes in this list, and creates an instance at runtime.

Documentation has been updated to reflect this pattern, though this release remains fully backwards compatible (a warning will be logged for each extension loaded using the old system).


Developer changes

Extension loader

  • Updated default extensions to subclass structure (8d0759e)
    • Updated documentation to subclass-based extensions (bacc111)
    • Added type information to LABTHINGS_EXTENSIONS (b8354d3)
    • Clarified LABTHINGS_EXTENSIONS in docs (f849afd)


  • Added basic unit tests of non-integrated functions (5137d1b)

Static analysis

  • Added extra type hints (7ba3f44)
  • Added numpy types (63b633b)
  • Added type hints to CSM extension (311366c)
  • Fixed types in move_rel method (ac667c3)
  • Static type analysis (7866ec0)
  • Stricter runtime type checks (f2a2d88)


  • Added better developer notes (a3b1b8a)
  • Added changelog generator (de6dbe4)


  • Added eslint and cache to CI (f5012cd)
  • Added job explanation comments (c1e17de)
  • Added poetry to script environment (3814e7d)
  • Allow code quality jobs to retry (f15a5c7)
  • Fixed JS artifact path (3a90a42)

API functionality

  • Added API route to convert LST to PNG (4d40e81)

JS Client

New functionality

  • Added log level filter (4d1d0a1)
  • Added options to invert navigation steps (d49b34e)
  • Added backlash and settle time settings (c0fcd22)
  • Added capture quality settings (0f7cecb)
  • Added MJPEG bitrate setting (42e0dfd)
  • Added Advanced bitrate controls (d2488c5)


  • Fixed global state and scrolltotop (ef9f257)
  • Fixed individual captures creating ‘undefined’ dataset (213dec3)
  • Fixed onError propagation (08f6532)
  • Fixed onScanError (1de6b2a)
  • globalUpdateCaptures after scan (599c315)
  • Handle missing this.$refs.textboxKey ref (9d04842)
  • Fixed IHI tab icon duplication bug (e17366d)


  • Added custom h4 formatting (4ba4403)
  • Rearranged element layout (455b868)
  • Rearranged settings and added LST download (f0a3127)

Internal cleanup

  • Common watch format (3e783c5)
  • Removed unneeded plugin:vue/essential (358d441)
  • Removed unused webcomponent support (f187a3a)
    • Moved backlash settings (a2f8158)



  • Added mjpeg bitrate to settings (3e2f876)
  • Watch for broken frames using JPEG end bytes, and log error (6838038)
  • Added API route to convert LST to PNG (4d40e81)


  • Fixed “classic” autofocus ‘PiCameraStreamer’ object has no attribute ‘annotate_text’ error (58b2967)
  • Fixed broken dataset rendering if key exists but is empty (fd42e2e)
  • Fixed camera settings read (25d3b15)
  • Fixed get_locations method reference (d288484)
  • Fixed SangaDeltaStage type annotations (9659c45)
  • Fixed tile method reference (18a0eed)
  • Handle missing endpoints (bc39277)

Internal API changes

  • Deprecated camera start_worker and get_frame methods
    • Added start_worker and get_frame aliases for compatibility (bc9b80d)
  • Replace top-level actions View with builtin LabThings (421a2e3)
  • Updated to LabThings 1.2.1 (249e301)

Internal cleanup

  • Remove separate camera stream thread (021745d)
  • Cleaned up code layout (da62126)
  • Cleaned up main app setup (e464086)
  • Cleaned up store and removed unused FoV setting (a1ae947)
  • Code formatting (dd81640)
  • Fixed unused imports (b2192b2)
  • Integrated CSMExtension (9203545)
  • Moved frames_iterator scope (3058c67)
  • Moved gen() into streams.py (c9c29a7)
  • Only use CompositeLock for microscope lock (e433a89)
  • Reduced logging level of some mock camera notices (8eff136)
  • Removed old console logs (2e34722)
  • Removed old picamera_lst_path attribute (85f77fa)
  • Removed pointless abstract method implementations (6d1f019)
  • Removed unused pynpm package (6819ded)
  • Tweaked deprecation warnings (a844efc)
  • Updated dependencies (3c3ecd7)

A quick note to contributors on the v2.9.1 patch release:

We had some reports of the camera feed breaking for detailed samples: Camera feed going white - Request Help - OpenFlexure Forum

This looks a lot like a bandwidth issue. Originally I added an option to change the MJPEG quality setting, however after some testing this doesn’t actually seem to do anything, regardless of if bitrate control is enabled or not. The quality parameter has no effect on the frame data size.

Because of this, I’ve replaced that option in the JS client with one to change the MJPEG bitrate. Below a certain limit, a warning is displayed that it may impact fast autofocus performance. @r.w.bowman did some testing with this way back when fast AF was new and found bitrate limits sometimes caused issues.

From my (brief) testing, it seems like high explicit bitrates still work very well, and low bitrates will still focus, but perhaps less reliably.

My intention is that most users will leave the bitrate on maximum (passing -1 to the picamera parameter, and removing bitrate control) and so this will not be an issue. In cases where bandwidth is causing people grief though, this is the only way to reduce the data transfer of the stream, so hopefully it’s a good tradeoff!