FLIMKit QuPath bridge
Direct image and ROI exchange between FLIMKit and QuPath.
Status
v0.1.1. Used end to end against real FLIMKit sessions on a Leica SP8 FALCON: images and ROIs move in both directions, the FLIMKit images open in a QuPath project, and co-registration works through the alignment extension.
The endpoint shapes come from flimkit-fiji-bridge, where they were designed first. The two have since diverged and are not interchangeable: they report different protocol identifiers, and this one adds a discovery file for pairing and refuses requests whose Host header is not localhost.
The bigger difference is on the other side. This one ships a QuPath extension that runs inside a live session, so it works with the image you have open and the annotations you have drawn, and it can add the FLIMKit images to the open project. The Fiji bridge drives Fiji through a Groovy script.
Installing
The Python half goes into the environment FLIMKit runs in:
pip install flimkit-qupath-bridge
That pulls FLIMKit with it. The bridge then starts with FLIMKit, and flimkit-bridge is on the path for the headless server.
The QuPath half is a jar. Take qupath-extension-flimkit-bridge-*.jar from the release and drop it in QuPath's extensions directory, normally ~/QuPath/v0.7/extensions. It appears under Extensions > FLIMKit bridge.
Why QuPath as well as Fiji
The FLIM workflow that matters here is drawing ROIs on a co-registered brightfield image and sending them back to FLIMKit for analysis. QuPath is built for that on tissue, and its annotation handling and built-in cell detection are stronger than Fiji's ROI Manager.
QuPath also treats GeoJSON as a first-class format, so the ROI half of the bridge is less work than it was for Fiji.
Pairing
FLIMKit writes its address and a freshly generated token to ~/.flimkit/qupath-bridge.json when it starts, owner-readable only where the platform supports it. QuPath reads that file, so Extensions > FLIMKit bridge > Connect needs nothing typed in.
If the file names a FLIMKit that is no longer running, QuPath says so rather than failing with a connection error. Connect to a different address... is there for the case where the file cannot be reached, such as FLIMKit running in a container or on the other end of an SSH tunnel.
The bridge listens on 127.0.0.1 only and refuses any request whose Host header is not localhost, which stops a web page reaching it by pointing its own hostname at your machine. If port 8765 is busy it takes an ephemeral one and records it in the same file, so nothing needs reconfiguring.
Without the desktop GUI
The bridge does not need the FLIMKit window. flimkit-bridge starts the same server on its own, writes the same discovery file, and serves everything except the routes that read the open session:
flimkit-bridge # 127.0.0.1:8765, or an ephemeral port if that is busy
flimkit-bridge --port 9000
flimkit-bridge --no-announce # do not write the discovery file, print the token instead
It refuses to start if another bridge is already serving, since both would write the same discovery file and QuPath would pair with whichever wrote last. Pass --force to take it over.
What the desktop bridge adds is the live session: the images and ROIs currently on screen. Everything else, opening files, fitting regions, phasor plots and stitching, works the same either way.
Stitching and fitting from QuPath
FLIMKit's own tile pipelines are reachable over the bridge, so a mosaic can be stitched and fitted without touching the FLIMKit window. Point it at the .lif or .xlif that carries the tile positions:
POST /v1/pipeline {"container": "/path/R 2.xlif", "params": {"n_exp": 2}}
GET /v1/pipeline/defaults
The tiles do not have to sit beside the container. The bridge looks in the directory you name, then beside the container, then in the directories next to it, which is where Leica puts them.
pipeline chooses between stitch_fit, which stitches the canvas and fits it, and tile_fit, which fits each tile after a global summed fit and assembles the maps. Both run as jobs, so GET /v1/jobs/{id} reports progress and DELETE cancels. Cancelling stops the run at the next tile, or at the next stage boundary once fitting is done.
Outputs are written to disk, and the output directory can be reopened as a dataset:
POST /v1/datasets {"path": "/path/R_2_flimkit"}
A region drawn on that canvas is then fitted from the photons in the stitched cube, not from the displayed image.
Acknowledgement
The wire protocol used here was designed and first implemented in flimkit-fiji-bridge by Zhen Yuan Yeo (https://doi.org/10.5281/zenodo.21951612). This bridge reuses it unchanged, so a client written against one works against the other.
Licensing
Two licences, because the two halves link against different things. The Python package is MIT. The QuPath extension is GPL-3.0, because it links QuPath, which is GPL-3.0. See LICENSING.md.
Requirements
- QuPath 0.7.0 or newer.
- FLIMKit 0.12.0 or newer, which pip pulls in.
- Python 3.12 or newer.
- The QuPath alignment extension, for co-registration.
Align on the intensity image, not the lifetime map. Photon counts are integers, so intensity crosses as 16-bit whenever it fits losslessly, which the alignment extension can open. The lifetime map has to stay 32-bit float to carry real nanoseconds, and the alignment extension throws on 32-bit float rather than declining politely. The transform you get from the intensity image is valid for the lifetime map anyway, because they share one pixel grid.
The alignment extension is not optional for the intended workflow and it does not ship with QuPath. QuPath 0.7.0 does not bundle interactive image alignment, and neither did 0.6.0, so it has to be downloaded and dropped into QuPath's extensions directory separately.
Without it you can still move images and ROIs, but only between images that already share a coordinate system. Aligning a brightfield or histology image to the FLIM field of view, which is the reason this bridge exists, needs that extension installed.
Co-registration
FLIMKit receives ROIs in FLIM image-pixel coordinates. Anything drawn on another image has to be transformed into that space before it is sent, and the transform is produced on the QuPath side, the same division of labour the Fiji bridge uses.
The intended sequence:
- Open the brightfield image and add the FLIM intensity map to the same QuPath project.
- Align them with the alignment extension and transfer the annotations onto the FLIM image.
- Send the annotations on the FLIM image to FLIMKit.
This bridge deliberately contains no alignment code of its own. Reimplementing it would mean maintaining a copy of something QuPath's own developers already maintain.
Verified against QuPath 0.7.0
The plan is built on API behaviour confirmed by running it on 2026-08-15, not on documentation alone:
- Headless Groovy scripts run with no image and no project.
GsonTools.getInstance().toJson(obj)emits a GeoJSONFeature, andGsonTools.parseObjectsFromGeoJSON(String)reads one back.- Float32 TIFF opens through Bio-Formats with pixel values intact.
- Script arguments arrive as a positional
String[]namedargs. - QuPath 0.7.0 runs on Java 25, so there is no Java 8 problem of the kind the Fiji bridge hit.
Two behaviours shape the design. QuPath reads images from a path rather than a stream, so the client writes fetched bytes to a temporary file first. QuPath also normalises polygon winding order, so tests compare geometry rather than an exact coordinate sequence.
Release files for flimkit-qupath-bridge 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| flimkit_qupath_bridge-0.2.0.tar.gz | 56.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flimkit_qupath_bridge-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 89.3 kB
Release files / flimkit_qupath_bridge-0.2.0.tar.gz
| Download URL | flimkit_qupath_bridge-0.2.0.tar.gz |
|---|---|
| Size | 56.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4fc27a7da8893c21ee3c24463c4c0b2b99b4ebc495c2665b8d9b111d0f5f8604
|
|
BLAKE2b-256 checksum How to use checksums |
e22d3c3c9518a821b2249ca4f3b4708716f9dfe4d78d1ebfe4061db0f7b8a390
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.
Transparency logRelease files / flimkit_qupath_bridge-0.2.0-py3-none-any.whl
| Download URL | flimkit_qupath_bridge-0.2.0-py3-none-any.whl |
|---|---|
| Size | 33.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
20b7087bfd89b966a9a4137170968d88c1e68a0e6134216cc78c6920bfa2abfe
|
|
BLAKE2b-256 checksum How to use checksums |
6e87eee41a2e6762155b4ed5c55fd0ea2aa393d562fa5cbceded67a919677dab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.
Transparency log