svs-viewer-annotator
Four standalone Tkinter tools for viewing and annotating slides outside QuPath — including one that drives the mouse of whatever application is on screen, which is a different and more fragile idea than the other three.
Purpose
These are attempts at a lightweight slide viewer and annotator that does not require a QuPath project: open an SVS, pan it, draw regions, export them as JSON. The obvious question is why, given that QuPath Annotation Workflow already exists and works — and the answer implied by the code is reach. A small Tkinter tool can be handed to someone who will not install QuPath, and wsi-annotator.py at 1534 lines is a serious attempt at that rather than a sketch.
A plain directory inside Digital Pathology Drafts, one commit, 2025-04-11. Nothing here has been touched since.
Data used
None committed. The tools open SVS files through OpenSlide from wherever they are pointed; annotations are written as JSON beside them.
Methods
Four scripts, all Tkinter and PIL, three of them genuine viewers and one an outlier:
| Script | Lines | What it is | State |
|---|---|---|---|
wsi-annotator.py |
1534 | The largest: PyQt5 app, annotation types as an Enum, JSON persistence |
Crashes on first annotation |
roi-viewer-implementation.py |
1248 | Not standalone — a mixin for an app that is not in this repository | Cannot be instantiated |
megaslide.py |
221 | Tkinter/OpenCV multi-slide aligner with a Canny edge overlay | Works except Save |
smear_navigator.py |
92 | Not a viewer — see below | The only one that runs |
The largest one does not work, and that inverts the obvious reading of this folder. wsi-annotator.py uses three Qt names it never imports — QSizeF in every rectangle, ellipse and select-region draw call, QRadioButton in the tile-export dialog, and QGraphicsTextItem in the right-click label editor. Each raises NameError the moment it is reached, so the window opens and pans and zooms, and then drawing any annotation, opening the export dialog, or editing a label crashes immediately. Every feature the file exists for is unreachable.
megaslide.py is not the “minimal viewer” its size suggests either — it is a manual multi-slide alignment tool, which is a different and more interesting thing, closer to Cross-Stain Registration done by hand. Its _save_configuration() references an undefined SlidePosition class, so it aligns but cannot save the result.
roi-viewer-implementation.py calls back into self.parent_app.results_df, .output_folder and .run_analysis() — an application that is not here. The vocabulary matches the stain analyser in Stain Quality, so this was almost certainly drafted for that project and stranded. It also uses list_frame fifty lines before creating it and calls datetime.now() without importing datetime.
smear_navigator.py automates the mouse rather than reading a slide. It imports pyautogui and time and defines a SlideNavigator that moves the pointer in a pattern. It does not open a slide file at all; it drives whatever viewer happens to be in front, so a cytology smear can be walked in a systematic raster the way one would at a microscope stage.
That is worth separating from the other three because its failure modes are entirely different. Screen automation depends on window position, screen resolution and the target application not moving; it cannot know whether it is looking at tissue; and it leaves no record in image coordinates. Reading-Behaviour Capture sets out the two properties that decide whether recorded viewing data can answer anything — image-space positions with zoom, and preserved visit order — and a pointer-mover satisfies neither. As a way of driving a systematic search it is reasonable; as a way of recording one it would produce nothing analysable.
So of four scripts, one is a stranded fragment of another project, one crashes on every feature it was written for, one works but cannot save, and the one that runs cleanly is the one that never touches a slide file.
Current state / open questions
Paused, and the honest reading is that this is exploratory rather than a tool anyone depends on. Two things make it worth a page anyway.
- Archiving is now the better-supported option. The consolidation question would be “which of these is the base” — but nothing here is a working annotator, and the three import errors are the kind that surface on the first run, which means none of this was used. That is a cleaner signal than any code review: if a lightweight annotator were needed, these would have been fixed. Declaring QuPath Annotation Workflow the only route costs nothing.
megaslide.pyis the exception worth keeping. Manual multi-slide alignment with an edge overlay is not something the QuPath route provides, and it is 221 lines with one undefined class standing between it and working.- The interoperability question that decides it. These export their own JSON. QuPath exchanges GeoJSON. If the annotations produced here cannot be read back into the group’s main workflow, the tools are a dead end regardless of how good the interface is, and if they can, they are a genuinely useful front door for collaborators without QuPath.
[unverified]whether any of the three exports GeoJSON or a convertible schema — that is the one check worth doing before spending anything more here.
Related: QuPath Annotation Workflow — the established route these compete with; the GeoJSON interchange question above is what decides whether they can coexist with it. Related: Reading-Behaviour Capture — why smear_navigator.py cannot produce analysable viewing data even though it moves through a slide systematically. Related: Whole Slide Imaging — the OpenSlide layer all four sit on. Related: Digital Pathology Drafts — the umbrella this is a plain directory in.
Derived from: repository source read 2026-07-27 — import and class structure of wsi-annotator.py, roi-viewer-implementation.py, megaslide.py and smear_navigator.py, with smear_navigator.py read in detail; git log for currency.