Extending fibsemOS
Generated from
docs/developers/extending.mdin fibsem-os at build time. Edit it there; changes here are overwritten.
fibsemOS has four supported extension points, listed here in increasing order of integration: a script, a plugin, a workflow task, and a microscope backend. Each section describes the interface, the reference implementation, and the constraints that are not obvious from the code. Other approaches are possible; open an issue before relying on internals that are not listed here.
Choosing a route
| Goal | Mechanism | Where it runs |
|---|---|---|
| Read or modify experiment data, offline | A Python script using Experiment.load() | Anywhere; no application |
| Drive the microscope for an acquisition sweep or a one-off procedure | A Python script using utils.setup_session() | Anywhere; no application |
| Run a procedure against the experiment the application has open | A user script in the scripts folder, run from Tools → Scripts | Inside the application |
| Add a milling pattern, a milling strategy or a workflow task that others can install | A plugin package with entry points | Inside the application, discovered at start |
| Add a step to the AutoLamella workflow with supervision and history | A workflow task (as a plugin, or in the repository) | Inside the workflow queue |
| Support an instrument fibsemOS does not drive yet | A FibsemMicroscope implementation | The library; everything else is built on it |
Scripts
Against the library
from fibsem import utils, acquire
from fibsem.structures import BeamType, ImageSettings
microscope, settings = utils.setup_session(manufacturer="Demo")
image = acquire.acquire_image(microscope, ImageSettings(hfw=80e-6, beam_type=BeamType.ION))setup_session() connects using a configuration file (the shipped Demo
configuration by default) and returns the microscope and its settings. The
fibsem.acquire, fibsem.imaging and fibsem.milling modules provide the
higher-level operations, and every FibsemMicroscope method is available
directly. The microscope’s beams, stage and other hardware are devices
(microscope.devices), whose parameters describe their own limits and choices;
Devices covers them. Do not use the string-key
microscope.get("key") and microscope.set(...): they are deprecated. Use acquire.acquire_image(microscope, settings) rather than the
microscope’s own acquire_image: the method returns the raw frame and
ignores save=True; the module function applies autocontrast and gamma and
writes the file.
Experiment.load() gives access to an experiment’s lamellae, their history
and the summary dataframes. SCRIPTING.md covers this
in full, with examples that the test suite executes against real data.
Inside the application
AutoLamella can run a .py file from the scripts folder against the open
experiment, from Tools → Scripts → Manage scripts…. A script is one
module-level function:
"""Export the experiment summary to CSV.""" # the first line becomes the tooltip
def run(ctx):
out = ctx.path / "summary.csv"
ctx.experiment.experiment_summary_dataframe().to_csv(out, index=False)
return outctx is a ScriptContext (fibsem/applications/autolamella/scripting.py)
carrying the experiment, its path, a logger and, if the script declares
uses_microscope = True, the microscope. Declaring writes = True saves
the experiment afterwards. Return values are displayed: a string as a
notification, a DataFrame as a table, a path by opening its folder. Use
ctx rather than the UI object; the UI’s internals change between
versions, and its experiment attribute is replaced on load, so a cached
reference becomes stale without error.
Microscope scripts run on a background thread, must not modify
ctx.experiment, and must call ctx.raise_if_cancelled() between steps for
Stop to take effect. Nothing validates what a script does to the hardware.
The Running scripts from the GUI
and Microscope scripts sections give
the full contract; examples/scripts/ contains three working examples.
Plugins
fibsemOS loads four entry-point groups: fibsem.patterns,
fibsem.strategies, fibsem.tasks and fibsem.drivers. A plugin is a Python package that
declares one or more of these in its pyproject.toml and is installed into
the same environment as fibsemOS. No registration calls or files in the
fibsem tree are needed; the application discovers it on the next start.
The fibsem-plugin-example
repository is a template covering the first three groups. It includes tests that
check the contract, a CI workflow that installs against fibsemOS main so
breaking changes are detected early, and a README describing how to rename
it. Its “When nothing shows up” section covers the diagnostic steps.
Each group contributes one kind of object:
- A pattern (
fibsem.patterns): a subclass ofBasePatternfromfibsem.milling.patterning.patterns2whosedefine()returns the shapes to mill. It appears in the pattern list of every milling stage. - A strategy (
fibsem.strategies): how a pattern is milled, for example in N passes. It appears in the strategy list of every stage. - A task (
fibsem.tasks): anAutoLamellaTaskwith its configuration class, as described in the next section. It appears in the Add Task dialog. - A microscope driver (
fibsem.drivers): a function returning aDriverEntry(fibsem/microscopes/registry.py) that names aFibsemMicroscopesubclass, its manufacturer, its defaults, and how it builds each type of device. A configuration naming that manufacturer connects through it, and ahardware.devicesentry naming it as itsdriveris built by it. A driver can also build devices only, with no microscope class, such as a manipulator on its own controller. Adding a driver shows one that adds a device type.
When a built-in, a runtime registration and a plugin claim the same name, the built-in is used, then the runtime registration, then the plugin.
Three constraints on loading. Each produces no error when violated, so the example’s tests check them:
- Pattern modules are imported while
fibsem.milling.baseis only partly initialised. ImportBasePatternfrompatterns2, not from thefibsem.milling.patterningpackage, and import nothing that depends onfibsem.milling.base, which includes everything underfibsem.applications. A pattern and a task defined in the same module breaks the pattern. Strategies and tasks have no such restriction. - A plugin that fails to import is absent. The application starts
without it and the class does not appear in the list. The reason is
recorded in the log and in the Plugins panel (Tools → Plugins…, or
fibsem-cli plugins), which lists every declared entry point and its outcome, including entry points shadowed by a built-in of the same name.fibsem/plugins/loader.pyis the loader and documents the records it keeps. - Changes to
pyproject.tomltake effect only after reinstalling. Entry points are read from the installed metadata, so runpip install -e .again after editing them. Changes to Python files need no reinstall.
Distances in configuration forms are stored in metres and scaled for
display. A distance field without scale metadata renders as 0.000 m.
Spread DEFAULT_DISTANCE_METADATA from fibsem.milling.properties into
distance fields, as the built-in patterns do; the example’s tests check this
as well.
Workflow tasks
A task is one step applied to one lamella. Subclass AutoLamellaTask
(fibsem/applications/autolamella/workflows/tasks/base.py) with a matching
AutoLamellaTaskConfig, and register both with register_task() in the
repository or through the fibsem.tasks entry point from a plugin.
fiducial.py is a representative task module of moderate complexity.
Four contracts:
- Override
_run(), notrun().run()is the lifecycle wrapper: it callspre_task(), the hooks,_run()andpost_task(). Overridingrun()loses the task’s state, its history entry and every hook, without an error. - Questions go through
ask()(workflows/interaction.py). Build aRequestcarrying everything needed to answer it and block on the responder. Do not access widgets from the workflow thread. This interface is what allows the GUI, the operator and a remote agent to answer the same question, and it is why supervised and unsupervised runs share one code path. - Record outputs on the task’s history entry (
task_state.outputs, mapping role to files), and write images underlamella.path. The Review panel and the reports read from there; unrecorded files are not shown, and files written elsewhere are lost when the experiment is copied off the microscope. - Cancellation is cooperative. Stop sets an event. A task that does not
call
self._check_for_abort(), or a strategy that does not checkstop_event, cannot be stopped.
task_type is written into protocol files and is therefore permanent:
renaming it orphans every protocol that names it. Configuration classes are
serialised into the protocol as well, so they should contain only plain
types that round-trip through YAML.
Supporting a microscope
A backend is two parts: a FibsemMicroscope subclass (fibsem/microscope.py),
the coordinator covering acquisition, movement, milling and state, and the
devices it builds, one class per device type in
fibsem/devices/drivers/<driver>.py (a Beam, a Stage, a Chamber, …).
Once a backend implements both and is registered, the workflows, UI and server
work unchanged.
-
Devices. Subclass the vendor-neutral classes in
fibsem/devices/and implement each parameter asread_<name>/write_<name>/metadata_<name>methods, as Adding a device type describes.fibsem/devices/drivers/demo.pyis the reference. The driver’sDriverEntry.devicesmaps each device type to its build function, and the microscope class builds its devices from the configuration’shardware.devicesentries withfibsem.devices.entries(resolve_system_devices, thenbuild_device_entries), asDemoMicroscope._build_devicesdoes. A string key the backend’s devices cover is routed to them. A new backend needs no_get/_set: the base class’s read a key no device answers as None and do nothing for a write (the Demo has none); do not add keys to them. -
Reference implementations.
DemoMicroscopeinfibsem/microscopes/device_demo.pyis the complete, hardware-free reference.fibsem/microscopes/tescan.pyshows a vendor SDK behind the same interface.microscopes/zeiss.pyis an empty placeholder awaiting a SerialFIB migration; a Zeiss backend should be built there rather than in a new file. -
Registration. Implementing the class is not sufficient. A driver is a
DriverEntryin the registry (fibsem/microscopes/registry.py): a module-levelDRIVERrecord listed in_BUILT_INfor a backend in the fibsem tree, or afibsem.driversentry point for one in its own package. A manufacturer without one raisesNotImplementedErrorat connection time, not at import. The first-run wizard is still code:fibsem/guided_setup.py(MANUFACTURERS, and aMicroscopeModelper instrument), with the constant and alias infibsem/manufacturers.py. -
Configuration. Instruments are described by a YAML file in
fibsem/config/; the setup wizard creates one. For a manufacturer it does not know, start from a Demo configuration and edit it. The devices are listed underhardware.devices; see Configuring devices . -
Verification. Connect with
utils.setup_session()and run the tests that exercise the Demo through the same interface (tests/test_acquire.py,tests/test_movement.py,tests/test_microscope.py, andtests/test_microscope_contract.py, which writes down what every backend promises), then connect through the application. Not every abstract method needs to work at first: acquisition and stage movement are the prerequisites for the rest, and unimplemented methods can raise until their subsystem is addressed.
Fluorescence microscopes
An FM is devices too: a Camera, a LightSource, a FilterSet and an
Objective (fibsem/devices/fm.py), filling the four roles of an FM
device that acquires channels and z-stacks. Implement the four parts; the
FM device and microscope.fm (FluorescenceMicroscope,
fibsem/fm/microscope.py) are built on them. The simulated parts in
fibsem/devices/drivers/demo.py are the hardware-free reference, and
autoscript_fm.py and odemis_fm.py beside it are the two hardware
implementations. An FM on its own computer needs no driver on the
microscope’s side: serve its parts with the device server and configure the
FM as driver: remote (Devices on another computer ).
This interface is under active development and is expected to change; open
an issue before building on it.
Segmentation models
Models live under fibsem/segmentation/. SegmentationModelHuggingFace
loads checkpoints from the Hugging Face Hub, and the local-checkpoint paths
are beside it. A general model library (sidecar metadata, local and Hub
resolution) is planned but not implemented. For now, match the loading
interface of the existing models; this area is expected to change.