girder-dashboards
A Girder 5 plugin that adds a new class of entity: Dashboards — lightweight, interactive UIs operating on the data gathered in a Girder instance.
A dashboard is a small JS app. It gets a card in a gallery reachable from a Dashboards entry in the left sidebar, and when opened it takes over the whole window: none of Girder's usual header, navigation or footer, just the dashboard and a top bar with the way back.
What you get
- Sidebar entry —
Dashboardsin the global navigation, routing to#dashboards. - Card gallery (
#dashboards) — image, name, description, an Open action for users withREADaccess, and a settings gear for users withADMINaccess. - Config page (
#plugins/dashboards/config, linked from Admin console → Plugins) — a site admin enables/disables each installed dashboard, edits its card, edits its settings, and controls who may open it. - Full-window runner (
#dashboard/<id>) — the dashboard renders under Girder'sLayout.EMPTY, with a Dashboards back link in its own top navbar. - Two bundled dashboards — Data Overview, a small worked example, and Precipitate Analysis, a real analysis pipeline (see below).
The Precipitate Analysis dashboard
An implementation of the precipitate-detection and inter-particle-spacing pipeline from Taheri-Mousavi Laboratory / Image-analysis-precipitate-detection-and-particle-spacing-estimation, turned into something you can run from a browser:
- Upload an SEM/TEM micrograph (TIFF, including the LZW and 16-bit variants instrument software emits). The backend reads what the file says about itself: the pixel scale, from the vendor header or from the scale bar drawn on the image, and the info panel across the bottom, which is excluded from the analysis (see below).
- Check the scale — the length of the image's scale bar in µm and how many pixels it spans, filled in already if either could be read, with a note saying where it came from.
- Choose the spacing measure — centre-to-centre or edge-to-edge.
- Select regions of interest by dragging on the image, as many as you like, or select none and the whole image is analysed as one region. Each region is detected and measured on its own and then pooled, exactly as the original treated its three separate ROI files.
- Wait — the computation is a Celery task, reported as a normal Girder job with progress.
- Read the numbers — size and spacing histograms with mean/median rules, a spacing map, the detection and nearest-neighbour overlays on the micrograph itself, and pooled and per-region statistics tables. Everything is drawn in the browser from stored numbers; the backend produces no figures.
Every input and output is a Girder object in a folder of the user's own — Precipitate Analysis/<run>/ in their user space, holding the uploaded micrograph, the preview the backend
rendered for region selection, and results.json, which carries per-particle arrays
(x, y, diameterNm, spacingNm, nnIndex, …) plus per-region and pooled statistics.
The scale, and the info panel
Both are things the instrument already recorded, so neither should have to be typed in.
The scale is looked for in two places, in order. A vendor header — TESCAN's private tag
50431, or FEI/Thermo's tag 34682 — states the pixel size outright, and the form is filled in
with the scale bar the image itself is printed with (50 µm = 370.656 px, not 1 µm = 7.4 px),
so the number on screen is one you can check against the number on the image. Failing that, the
bar drawn in the info panel is measured: that gives the pixel count but not the length
printed beside it, which is text, so the pixel count is filled in and you are asked for the
length. Either way the dashboard says which happened, marks the bar it measured on the image,
and offers the detected value back if you change it. The standard TIFF resolution tags are
deliberately not consulted: on every real micrograph tested they held a leftover screen or
print DPI, which would be a confidently wrong answer.
The info panel — the strip of instrument readings across the bottom — is found from the same header where it states one, and otherwise from the pixels, and excluded from the analysis by default. It is not specimen: its text and drawn scale bar are the brightest, roundest, most compact things in the file, and are detected as precipitates. On the sample micrograph the research code ships, leaving it in invents 35 particles and shifts the mean diameter by 2.6%; excluding it reproduces the hand-cropped file the published analysis used to within 0.02%. It is also what decides the 0-255 stretch on a 16-bit image, which is why the crop happens before the grey conversion rather than after. The panel is dimmed on the preview rather than hidden — it is where the scale bar is printed — and the exclusion is a checkbox with an editable height.
The two detection tunings published with the research code are offered as presets: fine for
small dim precipitates (725 °C, 1 hr) and coarse for large bright ones (725 °C, 5 hr). The
port is numerically faithful — see test/fidelity/compare_to_original.py, which compares every
reported statistic against the original scripts.
Installing it
The analysis stack is an extra, so a Girder that only wants the other dashboards is not made to carry scikit-image:
pip install 'girder-dashboards[precipitate]'
Install it in both the Girder environment and the Celery worker environment. The dashboard
reports missing dependencies on its own page rather than failing a run, and falls back to running
the computation in the Girder process when no Celery worker is consuming the local queue — so it
also works on a plain girder serve.
How a dashboard is put together
A dashboard has two halves that meet at a shared key:
| Half | Where | Responsibility |
|---|---|---|
| Declaration | Python, registerDashboard() |
That the dashboard exists, and what its card says |
| Implementation | web_client, registerDashboard() |
The Backbone view that renders it |
The Python half is what makes dashboards discoverable server-side, so the config page can list them and cards can render without every dashboard's JS having to be parsed first.
Persistent state lives in a dashboard document, one per registered key. It holds the parts an
admin owns — enabled, the ACL, the card metadata, and a free-form settings object handed to
the view at runtime. Documents are created by an atomic upsert at plugin load, so a restart or a
redeploy never clobbers an admin's edits, and concurrent workers can't race into duplicates.
New dashboards start disabled but publicly readable: enabling one is the only step needed to offer it to everyone, and narrowing the ACL is how you restrict it.
Adding a dashboard from your own plugin
Server side, from your plugin's load():
from girder.plugin import GirderPlugin, getPlugin
from girder_dashboards import registerDashboard
class MyPlugin(GirderPlugin):
def load(self, info):
getPlugin("dashboards").load(info)
registerDashboard(
"sample-throughput",
name="Sample Throughput",
description="Samples registered per week, by instrument.",
image="https://example.org/card.png", # optional; falls back to `icon`
icon="icon-chart-line",
settings={"weeks": 12},
)
Client side, from your web_client entry point:
import SampleThroughputView from './dashboards/SampleThroughputView';
girder.plugins.dashboards.registerDashboard('sample-throughput', {
view: SampleThroughputView
});
The view is instantiated with {el, parentView, dashboard, settings}, where dashboard is the
DashboardModel and settings is its (admin-editable) settings object. Render into el and you
own the viewport below the top bar.
Registering after this plugin has loaded is fine — new registrations are provisioned immediately rather than at the next restart.
REST API
Dashboards
All routes are under /api/v1/dashboard.
| Route | Access | Purpose |
|---|---|---|
GET /dashboard |
public | Dashboards readable by the caller. includeDisabled / includeUnavailable are site-admin only. |
GET /dashboard/{id} |
READ |
A single dashboard. |
PUT /dashboard/{id} |
ADMIN |
Change name, description, image, icon, enabled, settings. |
PUT /dashboard/{id}/reset |
ADMIN |
Restore the card metadata and settings the plugin declared. |
DELETE /dashboard/{id} |
site admin | Prune a document whose implementation is gone. |
GET/PUT /dashboard/{id}/access |
ADMIN |
Read/set the ACL. |
Every response carries an extra available flag: false means the document's key no longer has a
registered implementation, e.g. the plugin that shipped it was uninstalled. Such dashboards are
hidden from the gallery but still listed on the config page so an admin can remove them.
Precipitate analysis runs
Under /api/v1/precipitate. Every route requires a signed-in user with READ on the dashboard,
and refuses to work while the dashboard is disabled.
| Route | Purpose |
|---|---|
GET /precipitate/capability |
Whether the analysis dependencies are installed, whether a Celery worker is available, the detection presets, and the admin-set form defaults |
GET/POST /precipitate/run |
List runs; create a folder for a new one |
GET/DELETE /precipitate/run/{id} |
One run's state; delete it and its contents |
POST /precipitate/run/{id}/prepare |
Schedule the decode + preview step for an uploaded image, which also reports the pixel scale and info panel it found under state.detected |
POST /precipitate/run/{id}/analyze |
Schedule the analysis: scale, spacing mode, preset, regions, excludeBottomPx |
Both POSTs return a Girder job to follow. The micrograph is uploaded with Girder's own file
endpoints, and the preview and results are downloaded with them too — this resource never proxies
bytes that core already serves with the right ACL checks.
Development
Server side:
tox -e lint # ruff check .
tox -e pytest # needs a running MongoDB
Web client, from girder_dashboards/web_client/:
npm ci
npm run build # vite build -> dist/, required before the server can serve the assets
npm run dev # vite build --watch
registerPluginStaticContent in girder_dashboards/__init__.py serves the built
dist/girder-plugin-dashboards.umd.cjs and dist/style.css, so rebuild after changing
web_client source and reload Girder.
Build the web client before running the Python tests: the plugin hashes the files in
web_client/dist at load time, so without them every test that starts a server fails.
End-to-end browser check, against a running Girder:
(cd test/browser && npm ci && npx playwright install chromium) # once
python3 test/browser/seed.py # admin, assetstore, enabled dashboards, sample data
node test/browser/verify.cjs
It drives headless Chrome through the gallery, the runner and the config page as both an anonymous
and an admin user, then runs a whole precipitate analysis through the UI — upload, region
selection, job, plots and tables — and fails on any console error or failed request. Screenshots
are written to test/browser/screenshots/. Configure with GIRDER_URL, GIRDER_ADMIN,
GIRDER_PASSWORD. Both scripts are idempotent, so they can be re-run against the same instance.
To exercise the Celery path rather than the in-process fallback, point Girder and a worker at the same broker before seeding:
export GIRDER_WORKER_BROKER=redis://127.0.0.1:6379/1
export GIRDER_WORKER_BACKEND=redis://127.0.0.1:6379/1
girder serve --host 127.0.0.1 --port 8989 &
celery -A girder_worker.app worker -Q local -c 2 -l INFO &
The dashboard says which path it is using, and the harness asserts on that line either way.
CI (.github/workflows/build-test.yaml) runs lint, the Python tests, and the browser check on
every push to main and every pull request.
License
BSD-3-Clause — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file girder_dashboards-0.1.1.tar.gz.
File metadata
- Download URL: girder_dashboards-0.1.1.tar.gz
- Upload date:
- Size: 131.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
02e6f311a8109b5f28a0fd8eaa78353ff1f8e37feb2963c73238973d363cf29e
|
|
| MD5 |
f93250881c2184d56b6ae38da1a88245
|
|
| BLAKE2b-256 |
d29483cae3ee80481031b6943b1ef364ebb9fb1ede875215b35b1357c4370a9a
|
Provenance
The following attestation bundles were made for girder_dashboards-0.1.1.tar.gz:
Publisher:
release.yml on Xarthisius/girder-dashboards
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
girder_dashboards-0.1.1.tar.gz -
Subject digest:
02e6f311a8109b5f28a0fd8eaa78353ff1f8e37feb2963c73238973d363cf29e - Sigstore transparency entry: 2256642327
- Sigstore integration time:
-
Permalink:
Xarthisius/girder-dashboards@415097c8a687e1a9f2b863a37ebd1acc10470888 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Xarthisius
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@415097c8a687e1a9f2b863a37ebd1acc10470888 -
Trigger Event:
push
-
Statement type:
File details
Details for the file girder_dashboards-0.1.1-py3-none-any.whl.
File metadata
- Download URL: girder_dashboards-0.1.1-py3-none-any.whl
- Upload date:
- Size: 133.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
00dcb2e4148d917a8cf7bccc47da14e2cfca3446adb780799df3680153ee1270
|
|
| MD5 |
8f5a4b75fe5c46ccbd348d9faf1296c9
|
|
| BLAKE2b-256 |
91d6a2689fb48b60c6bd6e88b8307b8fdc04243432b5286037cb1016ad0ece0b
|
Provenance
The following attestation bundles were made for girder_dashboards-0.1.1-py3-none-any.whl:
Publisher:
release.yml on Xarthisius/girder-dashboards
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
girder_dashboards-0.1.1-py3-none-any.whl -
Subject digest:
00dcb2e4148d917a8cf7bccc47da14e2cfca3446adb780799df3680153ee1270 - Sigstore transparency entry: 2256642332
- Sigstore integration time:
-
Permalink:
Xarthisius/girder-dashboards@415097c8a687e1a9f2b863a37ebd1acc10470888 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Xarthisius
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@415097c8a687e1a9f2b863a37ebd1acc10470888 -
Trigger Event:
push
-
Statement type: