ocdkit 0.0.2__tar.gz → 0.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ocdkit-0.0.2 → ocdkit-0.0.4}/.github/workflows/test_and_deploy.yml +5 -1
- {ocdkit-0.0.2 → ocdkit-0.0.4}/.gitignore +4 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/MANIFEST.in +4 -0
- {ocdkit-0.0.2/src/ocdkit.egg-info → ocdkit-0.0.4}/PKG-INFO +44 -7
- ocdkit-0.0.2/PKG-INFO → ocdkit-0.0.4/README.md +28 -28
- ocdkit-0.0.4/badges/coverage.svg +1 -0
- ocdkit-0.0.4/badges/tests.svg +1 -0
- ocdkit-0.0.4/docs/plot-backend-roadmap.md +96 -0
- ocdkit-0.0.4/docs/plugin-authoring.md +220 -0
- ocdkit-0.0.4/docs/pywebview-desktop-integration.md +689 -0
- ocdkit-0.0.4/pyproject.toml +94 -0
- ocdkit-0.0.4/scripts/bench_contour_alignment.py +82 -0
- ocdkit-0.0.4/scripts/bench_vector_contours.py +311 -0
- ocdkit-0.0.4/scripts/bench_vector_contours_tier2.py +746 -0
- ocdkit-0.0.4/src/ocdkit/__init__.py +40 -0
- ocdkit-0.0.4/src/ocdkit/__main__.py +6 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/array/convert.py +38 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/array/ops.py +25 -3
- ocdkit-0.0.4/src/ocdkit/array/parallel.py +164 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/array/spatial.py +4 -1
- ocdkit-0.0.4/src/ocdkit/cli/__init__.py +14 -0
- ocdkit-0.0.4/src/ocdkit/cli/__main__.py +6 -0
- ocdkit-0.0.4/src/ocdkit/cli/main.py +41 -0
- ocdkit-0.0.4/src/ocdkit/cli/migrate.py +77 -0
- ocdkit-0.0.4/src/ocdkit/cli/paths.py +170 -0
- ocdkit-0.0.4/src/ocdkit/desktop/__init__.py +8 -0
- ocdkit-0.0.4/src/ocdkit/desktop/pinning.py +1007 -0
- ocdkit-0.0.4/src/ocdkit/io/__init__.py +3 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/io/image.py +6 -3
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/load/__init__.py +1 -1
- ocdkit-0.0.4/src/ocdkit/load/module.py +192 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/logging/handler.py +43 -2
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/measure/bbox.py +57 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/measure/medoid.py +1 -58
- ocdkit-0.0.4/src/ocdkit/plot/__init__.py +11 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/plot/defaults.py +17 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/plot/display.py +75 -0
- ocdkit-0.0.4/src/ocdkit/testing/__init__.py +55 -0
- ocdkit-0.0.4/src/ocdkit/testing/collisions.py +211 -0
- ocdkit-0.0.4/src/ocdkit/testing/imports.py +237 -0
- ocdkit-0.0.4/src/ocdkit/tls/__init__.py +142 -0
- ocdkit-0.0.4/src/ocdkit/tls/external_ca.py +104 -0
- ocdkit-0.0.4/src/ocdkit/tls/hostnames.py +110 -0
- ocdkit-0.0.4/src/ocdkit/tls/imports.py +11 -0
- ocdkit-0.0.4/src/ocdkit/tls/local_ca.py +288 -0
- ocdkit-0.0.4/src/ocdkit/tls/paths.py +26 -0
- ocdkit-0.0.4/src/ocdkit/tls/trust.py +205 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/utils/gpu.py +29 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/utils/kwargs.py +16 -0
- ocdkit-0.0.4/src/ocdkit/utils/paths.py +186 -0
- ocdkit-0.0.4/src/ocdkit/viewer/__init__.py +29 -0
- ocdkit-0.0.4/src/ocdkit/viewer/__main__.py +8 -0
- ocdkit-0.0.4/src/ocdkit/viewer/app.py +830 -0
- ocdkit-0.0.4/src/ocdkit/viewer/assets.py +640 -0
- ocdkit-0.0.4/src/ocdkit/viewer/cli.py +162 -0
- ocdkit-0.0.4/src/ocdkit/viewer/demo.html +253 -0
- ocdkit-0.0.4/src/ocdkit/viewer/dependencies.py +73 -0
- ocdkit-0.0.4/src/ocdkit/viewer/exceptions.py +125 -0
- ocdkit-0.0.4/src/ocdkit/viewer/masks.py +71 -0
- ocdkit-0.0.4/src/ocdkit/viewer/middleware.py +81 -0
- ocdkit-0.0.4/src/ocdkit/viewer/model_registry.py +96 -0
- ocdkit-0.0.4/src/ocdkit/viewer/plugins/__init__.py +25 -0
- ocdkit-0.0.4/src/ocdkit/viewer/plugins/base.py +259 -0
- ocdkit-0.0.4/src/ocdkit/viewer/plugins/registry.py +160 -0
- ocdkit-0.0.4/src/ocdkit/viewer/plugins/schema.py +178 -0
- ocdkit-0.0.4/src/ocdkit/viewer/plugins/threshold.py +104 -0
- ocdkit-0.0.4/src/ocdkit/viewer/routers/__init__.py +9 -0
- ocdkit-0.0.4/src/ocdkit/viewer/routers/index.py +61 -0
- ocdkit-0.0.4/src/ocdkit/viewer/routers/log.py +36 -0
- ocdkit-0.0.4/src/ocdkit/viewer/routers/mask.py +46 -0
- ocdkit-0.0.4/src/ocdkit/viewer/routers/plugin.py +129 -0
- ocdkit-0.0.4/src/ocdkit/viewer/routers/segment.py +39 -0
- ocdkit-0.0.4/src/ocdkit/viewer/routers/session_routes.py +192 -0
- ocdkit-0.0.4/src/ocdkit/viewer/routers/system.py +41 -0
- ocdkit-0.0.4/src/ocdkit/viewer/routers/trust.py +298 -0
- ocdkit-0.0.4/src/ocdkit/viewer/routes.py +217 -0
- ocdkit-0.0.4/src/ocdkit/viewer/sample_image.py +113 -0
- ocdkit-0.0.4/src/ocdkit/viewer/schemas.py +150 -0
- ocdkit-0.0.4/src/ocdkit/viewer/segmentation.py +270 -0
- ocdkit-0.0.4/src/ocdkit/viewer/session.py +311 -0
- ocdkit-0.0.4/src/ocdkit/viewer/system.py +260 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/app.js +11415 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/css/controls.css +2309 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/css/layout.css +276 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/css/tools.css +383 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/css/viewer.css +85 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/html/left-panel.html +253 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/html/sidebar.html +34 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/html/viewer.html +7 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/icons/affinity.svg +25 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/icons/arrow-back-up.svg +1 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/icons/arrow-forward-up.svg +1 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/icons/dbscan-nested-arcs.svg +6 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/icons/download.svg +1 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/icons/droplet-half-2.svg +1 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/icons/eraser.svg +1 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/icons/home-2.svg +1 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/icons/minus.svg +1 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/icons/palette.svg +1 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/icons/pencil.svg +1 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/icons/plus.svg +1 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/icons/rotate-rectangle.svg +1 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/icons/topology-star.svg +1 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/index.html +16 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/brush.js +330 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/colormap.js +462 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/debug-apple-material.js +133 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/file-navigation.js +620 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/history.js +143 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/interactions.js +379 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/logging.js +166 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/mask-pipeline.js +176 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/painting.js +2347 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/plugin-panel.js +891 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/pointer-state.js +115 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/state-persistence.js +379 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/tooltip-editor.js +342 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/ui-utils.js +1419 -0
- ocdkit-0.0.4/src/ocdkit/viewer/web/js/wasm_fill.c +101 -0
- ocdkit-0.0.4/src/ocdkit.egg-info/PKG-INFO +103 -0
- ocdkit-0.0.4/src/ocdkit.egg-info/SOURCES.txt +192 -0
- ocdkit-0.0.4/src/ocdkit.egg-info/entry_points.txt +6 -0
- ocdkit-0.0.4/src/ocdkit.egg-info/requires.txt +33 -0
- ocdkit-0.0.4/tests/e2e/__init__.py +0 -0
- ocdkit-0.0.4/tests/e2e/conftest.py +125 -0
- ocdkit-0.0.4/tests/e2e/test_browser_smoke.py +93 -0
- ocdkit-0.0.4/tests/e2e/test_pywebview_snapshot.py +81 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_array.py +191 -0
- ocdkit-0.0.4/tests/test_import_cycles.py +10 -0
- ocdkit-0.0.4/tests/test_module_collisions.py +10 -0
- ocdkit-0.0.4/tests/test_module_discovery.py +11 -0
- ocdkit-0.0.4/tests/test_paths_migration.py +114 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_plot_figure.py +1 -1
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_plot_notebook.py +1 -1
- ocdkit-0.0.4/tests/viewer/__init__.py +0 -0
- ocdkit-0.0.4/tests/viewer/test_active_plugin_cache.py +70 -0
- ocdkit-0.0.4/tests/viewer/test_app.py +158 -0
- ocdkit-0.0.4/tests/viewer/test_async_dispatch.py +97 -0
- ocdkit-0.0.4/tests/viewer/test_envelope_and_middleware.py +144 -0
- ocdkit-0.0.4/tests/viewer/test_plugin_contract.py +204 -0
- ocdkit-0.0.4/tests/viewer/test_session_eviction.py +68 -0
- ocdkit-0.0.4/tests/viewer/test_title_config.py +71 -0
- ocdkit-0.0.4/tests/viewer/test_ui_mode.py +108 -0
- ocdkit-0.0.2/README.md +0 -39
- ocdkit-0.0.2/pyproject.toml +0 -51
- ocdkit-0.0.2/src/ocdkit/__init__.py +0 -10
- ocdkit-0.0.2/src/ocdkit/load/module.py +0 -132
- ocdkit-0.0.2/src/ocdkit/plot/__init__.py +0 -5
- ocdkit-0.0.2/src/ocdkit/utils/__init__.py +0 -3
- ocdkit-0.0.2/src/ocdkit.egg-info/SOURCES.txt +0 -77
- ocdkit-0.0.2/src/ocdkit.egg-info/requires.txt +0 -17
- {ocdkit-0.0.2 → ocdkit-0.0.4}/LICENSE +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/scripts/bench_colorize.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/scripts/coverage_cross_device.sh +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/setup.cfg +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/array/__init__.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/array/filters.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/array/imports.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/array/index.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/array/morphology.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/array/normalize.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/array/transform.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/array/union_find.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/array/warp.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/imports.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/io/files.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/io/imports.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/io/path.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/io/result.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/load/object.py +0 -0
- {ocdkit-0.0.2/src/ocdkit/io → ocdkit-0.0.4/src/ocdkit/logging}/__init__.py +0 -0
- {ocdkit-0.0.2/src/ocdkit/logging → ocdkit-0.0.4/src/ocdkit/measure}/__init__.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/measure/diameter.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/measure/imports.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/measure/metrics.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/plot/color.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/plot/contour.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/plot/export.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/plot/figure.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/plot/grid.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/plot/imports.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/plot/label.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/plot/ncolor.py +0 -0
- {ocdkit-0.0.2/src/ocdkit/measure → ocdkit-0.0.4/src/ocdkit/utils}/__init__.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit/utils/collections.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit.egg-info/dependency_links.txt +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/src/ocdkit.egg-info/top_level.txt +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/fixtures/multichan_3c_4x4.czi +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/fixtures/tiny_8x8.czi +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_gpu.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_io.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_measure.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_morphology.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_plot_color.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_plot_contour.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_plot_display.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_plot_export.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_plot_grid.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_plot_label.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_registration.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_slice.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.4}/tests/test_spatial.py +0 -0
|
@@ -13,7 +13,7 @@ jobs:
|
|
|
13
13
|
fail-fast: false
|
|
14
14
|
matrix:
|
|
15
15
|
os: [ubuntu-latest, windows-latest, macos-latest] # macos-latest = Apple Silicon (MPS)
|
|
16
|
-
python_version: ['3.11', '3.12']
|
|
16
|
+
python_version: ['3.11', '3.12', '3.13']
|
|
17
17
|
steps:
|
|
18
18
|
- uses: actions/checkout@v4
|
|
19
19
|
with:
|
|
@@ -85,8 +85,12 @@ jobs:
|
|
|
85
85
|
runs-on: ubuntu-latest
|
|
86
86
|
needs: test
|
|
87
87
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
88
|
+
permissions:
|
|
89
|
+
contents: write
|
|
88
90
|
steps:
|
|
89
91
|
- uses: actions/checkout@v4
|
|
92
|
+
with:
|
|
93
|
+
ref: main
|
|
90
94
|
|
|
91
95
|
- name: Download test artifacts
|
|
92
96
|
uses: actions/download-artifact@v4
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
include README.md
|
|
2
2
|
include LICENSE
|
|
3
3
|
recursive-include src *.py
|
|
4
|
+
recursive-include src/ocdkit/viewer/web *.html *.css *.js *.svg *.c
|
|
4
5
|
recursive-include tests *.py
|
|
5
6
|
recursive-include tests/fixtures *
|
|
7
|
+
prune src/ocdkit/viewer/web/icons/_unused
|
|
6
8
|
prune tests/__pycache__
|
|
7
9
|
prune **/__pycache__
|
|
8
10
|
global-exclude ._*
|
|
9
11
|
global-exclude .DS_Store
|
|
12
|
+
global-exclude *.bak
|
|
13
|
+
global-exclude .!*
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ocdkit
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: Obsessive Coder's Dependency Toolkit — Python utilities for array manipulation, GPU dispatch, image I/O, morphology, and plotting.
|
|
5
5
|
License: BSD-3-Clause
|
|
6
|
-
Requires-Python: >=3.
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
|
-
Requires-Dist: numpy
|
|
9
|
+
Requires-Dist: numpy>=2.0
|
|
10
10
|
Requires-Dist: scipy
|
|
11
|
-
Requires-Dist: scikit-image
|
|
11
|
+
Requires-Dist: scikit-image>=0.26
|
|
12
12
|
Requires-Dist: tifffile
|
|
13
13
|
Requires-Dist: imagecodecs
|
|
14
14
|
Requires-Dist: matplotlib
|
|
@@ -18,16 +18,26 @@ Requires-Dist: torch>=1.12
|
|
|
18
18
|
Requires-Dist: dask[array]
|
|
19
19
|
Requires-Dist: natsort
|
|
20
20
|
Requires-Dist: numba
|
|
21
|
-
Requires-Dist:
|
|
22
|
-
Requires-Dist: bioio-czi
|
|
21
|
+
Requires-Dist: aicspylibczi
|
|
23
22
|
Requires-Dist: ncolor>=1.5.1
|
|
24
23
|
Requires-Dist: cmap
|
|
25
24
|
Requires-Dist: tqdm
|
|
25
|
+
Requires-Dist: platformdirs
|
|
26
|
+
Requires-Dist: cryptography
|
|
27
|
+
Provides-Extra: viewer
|
|
28
|
+
Requires-Dist: fastapi; extra == "viewer"
|
|
29
|
+
Requires-Dist: uvicorn[standard]; extra == "viewer"
|
|
30
|
+
Requires-Dist: imageio; extra == "viewer"
|
|
31
|
+
Requires-Dist: python-multipart; extra == "viewer"
|
|
32
|
+
Provides-Extra: desktop
|
|
33
|
+
Requires-Dist: pywebview; extra == "desktop"
|
|
34
|
+
Requires-Dist: pywin32; sys_platform == "win32" and extra == "desktop"
|
|
35
|
+
Requires-Dist: pyobjc-framework-Cocoa; sys_platform == "darwin" and extra == "desktop"
|
|
26
36
|
Dynamic: license-file
|
|
27
37
|
|
|
28
38
|
# ocdkit
|
|
29
39
|
|
|
30
|
-
|
|
40
|
+
A toolkit for array manipulation, GPU dispatch, image I/O, spatial operations, morphology, and plotting.
|
|
31
41
|
|
|
32
42
|
## Install
|
|
33
43
|
|
|
@@ -61,6 +71,33 @@ from ocdkit.plot import figure, image_grid
|
|
|
61
71
|
device = resolve_device() # auto-detect CUDA / MPS / CPU
|
|
62
72
|
```
|
|
63
73
|
|
|
74
|
+
## Performance tips
|
|
75
|
+
|
|
76
|
+
### Pin numba's JIT cache to local disk
|
|
77
|
+
|
|
78
|
+
If your project source lives on a network filesystem (SMB / NFS), set
|
|
79
|
+
`NUMBA_CACHE_DIR` to a local-disk location. By default numba writes its
|
|
80
|
+
JIT cache to `__pycache__` next to the source file, which on a
|
|
81
|
+
NAS-mounted tree means dozens of small SMB ops per fresh subprocess —
|
|
82
|
+
several seconds of overhead on every cold import.
|
|
83
|
+
|
|
84
|
+
ocdkit auto-applies `$HOME/.cache/numba` as the default if you haven't
|
|
85
|
+
set it (see `src/ocdkit/__init__.py`), but for shells, test runners, and
|
|
86
|
+
non-ocdkit code, set it explicitly:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Linux / macOS — add to ~/.zshrc, ~/.bashrc, or ~/.profile
|
|
90
|
+
export NUMBA_CACHE_DIR="$HOME/.cache/numba"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```powershell
|
|
94
|
+
# Windows — add to $PROFILE
|
|
95
|
+
[Environment]::SetEnvironmentVariable('NUMBA_CACHE_DIR', "$env:USERPROFILE\.cache\numba", 'User')
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Compiled artifacts are machine-local anyway (CPU- and Python-version
|
|
99
|
+
specific), so they don't belong on shared NAS regardless of perf.
|
|
100
|
+
|
|
64
101
|
## License
|
|
65
102
|
|
|
66
103
|
BSD-3-Clause
|
|
@@ -1,33 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: ocdkit
|
|
3
|
-
Version: 0.0.2
|
|
4
|
-
Summary: Obsessive Coder's Dependency Toolkit — Python utilities for array manipulation, GPU dispatch, image I/O, morphology, and plotting.
|
|
5
|
-
License: BSD-3-Clause
|
|
6
|
-
Requires-Python: >=3.9
|
|
7
|
-
Description-Content-Type: text/markdown
|
|
8
|
-
License-File: LICENSE
|
|
9
|
-
Requires-Dist: numpy
|
|
10
|
-
Requires-Dist: scipy
|
|
11
|
-
Requires-Dist: scikit-image
|
|
12
|
-
Requires-Dist: tifffile
|
|
13
|
-
Requires-Dist: imagecodecs
|
|
14
|
-
Requires-Dist: matplotlib
|
|
15
|
-
Requires-Dist: fastremap
|
|
16
|
-
Requires-Dist: edt
|
|
17
|
-
Requires-Dist: torch>=1.12
|
|
18
|
-
Requires-Dist: dask[array]
|
|
19
|
-
Requires-Dist: natsort
|
|
20
|
-
Requires-Dist: numba
|
|
21
|
-
Requires-Dist: bioio
|
|
22
|
-
Requires-Dist: bioio-czi
|
|
23
|
-
Requires-Dist: ncolor>=1.5.1
|
|
24
|
-
Requires-Dist: cmap
|
|
25
|
-
Requires-Dist: tqdm
|
|
26
|
-
Dynamic: license-file
|
|
27
|
-
|
|
28
1
|
# ocdkit
|
|
29
2
|
|
|
30
|
-
|
|
3
|
+
A toolkit for array manipulation, GPU dispatch, image I/O, spatial operations, morphology, and plotting.
|
|
31
4
|
|
|
32
5
|
## Install
|
|
33
6
|
|
|
@@ -61,6 +34,33 @@ from ocdkit.plot import figure, image_grid
|
|
|
61
34
|
device = resolve_device() # auto-detect CUDA / MPS / CPU
|
|
62
35
|
```
|
|
63
36
|
|
|
37
|
+
## Performance tips
|
|
38
|
+
|
|
39
|
+
### Pin numba's JIT cache to local disk
|
|
40
|
+
|
|
41
|
+
If your project source lives on a network filesystem (SMB / NFS), set
|
|
42
|
+
`NUMBA_CACHE_DIR` to a local-disk location. By default numba writes its
|
|
43
|
+
JIT cache to `__pycache__` next to the source file, which on a
|
|
44
|
+
NAS-mounted tree means dozens of small SMB ops per fresh subprocess —
|
|
45
|
+
several seconds of overhead on every cold import.
|
|
46
|
+
|
|
47
|
+
ocdkit auto-applies `$HOME/.cache/numba` as the default if you haven't
|
|
48
|
+
set it (see `src/ocdkit/__init__.py`), but for shells, test runners, and
|
|
49
|
+
non-ocdkit code, set it explicitly:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Linux / macOS — add to ~/.zshrc, ~/.bashrc, or ~/.profile
|
|
53
|
+
export NUMBA_CACHE_DIR="$HOME/.cache/numba"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```powershell
|
|
57
|
+
# Windows — add to $PROFILE
|
|
58
|
+
[Environment]::SetEnvironmentVariable('NUMBA_CACHE_DIR', "$env:USERPROFILE\.cache\numba", 'User')
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Compiled artifacts are machine-local anyway (CPU- and Python-version
|
|
62
|
+
specific), so they don't belong on shared NAS regardless of perf.
|
|
63
|
+
|
|
64
64
|
## License
|
|
65
65
|
|
|
66
66
|
BSD-3-Clause
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="114" height="20" role="img" aria-label="coverage: 43.71%"><title>coverage: 43.71%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="114" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="53" height="20" fill="#e05d44"/><rect width="114" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">coverage</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text><text aria-hidden="true" x="865" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">43.71%</text><text x="865" y="140" transform="scale(.1)" fill="#fff" textLength="430">43.71%</text></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="68" height="20" role="img" aria-label="tests: 400"><title>tests: 400</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="68" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="37" height="20" fill="#555"/><rect x="37" width="31" height="20" fill="#4c1"/><rect width="68" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="195" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">tests</text><text x="195" y="140" transform="scale(.1)" fill="#fff" textLength="270">tests</text><text aria-hidden="true" x="515" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">400</text><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="210">400</text></g></svg>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Plot backend roadmap
|
|
2
|
+
|
|
3
|
+
**Status:** Design discussion / pending decision.
|
|
4
|
+
**Owner:** kevin@kanvasbio.com
|
|
5
|
+
**Driver:** matplotlib accounts for ~80% of warm-call time in `hiprpy.plot.plot_spectra` (~115 ms of ~140 ms) and ~1.5 s of cold-import overhead. We also want native hover/tooltip behavior like the classification-debugger GUI, which matplotlib can't deliver inline.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
`ocdkit.plot` and `hiprpy.plot` already own ~10 K LOC of plotting code:
|
|
10
|
+
|
|
11
|
+
| Package | LOC |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `ocdkit.plot` (figure, grid, label, color, defaults, display, contour, export) | 1,935 |
|
|
14
|
+
| `hiprpy.plot` non-WGPU (datashade, cell, text, barcode, line, background, …) | 4,641 |
|
|
15
|
+
| `hiprpy.plot.wgpu` (lines, scatter, aggregators — already custom GPU primitives) | 4,783 |
|
|
16
|
+
|
|
17
|
+
What we still depend on matplotlib for is narrow: 2D Cartesian axes, ticks, labels, legends, image display, save-to-PNG/PDF/SVG, inline-in-Jupyter. **Not** 3D, animation, multi-backend abstraction (Qt/GTK/MacOSX), most chart types, complex tick locators/formatters, or any of the other ~180 K LOC of matplotlib's surface.
|
|
18
|
+
|
|
19
|
+
The driver question: replace matplotlib with what?
|
|
20
|
+
|
|
21
|
+
## Library survey (2026-05-07)
|
|
22
|
+
|
|
23
|
+
Shallow-cloned each candidate, counted Python LOC excluding tests, sphinx, sample data, and auto-generated validators:
|
|
24
|
+
|
|
25
|
+
| Library | Repo | Relevant Py LOC | Native code | Notes |
|
|
26
|
+
|---|---|---|---|---|
|
|
27
|
+
| matplotlib | — | 186 K | C extensions | What we're replacing |
|
|
28
|
+
| bokeh | 120 MB | 64 K | 150 K TypeScript | UI lives in TS frontend; Py is scene graph + serialization |
|
|
29
|
+
| plotly | 66 MB | 13 K relevant (+ ~600 K autogen) | JS bundle as data | Most LOC is auto-generated trait validators |
|
|
30
|
+
| datoviz | 137 MB | 8 K Py wrapper | 140 K C++ Vulkan engine | Heaviest binary footprint, fastest renderer |
|
|
31
|
+
| vispy | 11 MB | 32 K | None — pure Py + OpenGL | OpenGL stack, not WGPU |
|
|
32
|
+
| pygfx | 84 MB | 20 K | None — uses `wgpu-py` | Same backend ocdkit/hiprpy already use |
|
|
33
|
+
|
|
34
|
+
## Tradeoff summary
|
|
35
|
+
|
|
36
|
+
**Bokeh** — toolbar/logo *can* be removed (`figure(toolbar_location=None)`, `fig.toolbar.logo = None`), but the interaction model lives in a separate TypeScript frontend. To customize hover/zoom UX we'd be writing or forking TypeScript, not Python. The 150 K-LOC TS frontend is essentially the part we'd want to control, and it's the part we can't easily touch.
|
|
37
|
+
|
|
38
|
+
**Plotly** — same shape. Most LOC is auto-generated validators; the actual rendering and interaction is in the JS bundle. `displayModeBar=False` removes the toolbar but customizing hover beyond what their config exposes means reaching into JS.
|
|
39
|
+
|
|
40
|
+
**Datoviz** — Vulkan engine is fast and well-architected, but adds a 137 MB binary dependency and a different graphics stack from our existing wgpu-py code. Two graphics backends to maintain instead of one.
|
|
41
|
+
|
|
42
|
+
**Vispy** — pure Python and has scene + visuals modules that overlap heavily with what we want. But it's OpenGL via PyOpenGL, not WebGPU. We'd be running a second graphics stack alongside our wgpu-py code.
|
|
43
|
+
|
|
44
|
+
**pygfx** — uses *exactly* the `wgpu-py` we already depend on. Its `gfx.Lines`, `gfx.Mesh`, `gfx.Text`, `gfx.OrthographicCamera` primitives compose directly with our `DensityLineRenderer`. The most architecturally compatible third-party option.
|
|
45
|
+
|
|
46
|
+
**Roll our own** — given that we already have ~10 K LOC of plotting infrastructure, including a working WGPU line/scatter rasterizer, the increment to "complete 2D plotting library covering our actual needs" is roughly 3–5 K LOC. The scope is well-defined: axes, ticks, labels, legend, image display, hover, export.
|
|
47
|
+
|
|
48
|
+
## Recommendation
|
|
49
|
+
|
|
50
|
+
**Two-step.**
|
|
51
|
+
|
|
52
|
+
### Step 1: pygfx prototype (1 day)
|
|
53
|
+
|
|
54
|
+
Spike a `plot_spectra_pygfx` that uses `pygfx.Lines` + `pygfx.Text` + an `OrthographicCamera` to reproduce the current spectra layout. This is cheap because pygfx and our existing WGPU code share the same `wgpu-py` device — they can literally run in the same process without backend conflict.
|
|
55
|
+
|
|
56
|
+
What to evaluate:
|
|
57
|
+
- Visual quality vs. our current WGPU-rendered density lines (especially anti-aliasing)
|
|
58
|
+
- Text rendering quality (pygfx uses FreeType-rendered SDFs)
|
|
59
|
+
- Hover/picking — does pygfx's built-in picking suffice for our tooltip needs?
|
|
60
|
+
- Export — pygfx renders to a wgpu canvas; PNG export is straightforward, PDF/SVG would need our own rasterize-and-vectorize path
|
|
61
|
+
|
|
62
|
+
If pygfx covers ≥80% of our needs at ≥80% of the visual quality, **adopt pygfx**. We get hover, zoom, pan, picking essentially for free, plus the `pygfx` ecosystem (geometries, materials, post-processing).
|
|
63
|
+
|
|
64
|
+
### Step 2 (only if pygfx is insufficient): roll our own
|
|
65
|
+
|
|
66
|
+
Estimated scope, building on existing `hiprpy.plot.wgpu.lines`:
|
|
67
|
+
|
|
68
|
+
| Module | LOC est. | Notes |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `ocdkit.plot.figure_v2` | 400 | New `Figure` class owning the wgpu canvas + axes layout; coexists with current matplotlib `figure()` so migration is piecewise |
|
|
71
|
+
| `ocdkit.plot.axis` | 800 | `LinearAxis`, `LogAxis` — tick locator + formatter + render-time placement. The matplotlib equivalent is ~3 K LOC; we don't need most of it |
|
|
72
|
+
| `hiprpy.plot.wgpu.text` | 600 | FreeType-rendered glyph atlas + WGSL textured-quad shader. The one piece of genuinely new rasterization work |
|
|
73
|
+
| `ocdkit.plot.legend` | 200 | Boxed layout: text + marker swatches |
|
|
74
|
+
| `ocdkit.plot.hover` | 300 | Mouse → data-coord lookup → DOM/Jupyter tooltip overlay. Generalize the classification-debugger pattern |
|
|
75
|
+
| `ocdkit.plot.export` | 400 | PNG (numpy→PIL), SVG (string templates), maybe PDF (skip if PIL→PNG covers science exports) |
|
|
76
|
+
| Migration: rewrite `plot_spectra`, `plot_image_grid`, `key_slice_grid`, `label_axes` against new primitives | ~600 | Mostly drop-in replacements at the call sites |
|
|
77
|
+
| **Total** | **~3.3 K** | New code, all in our control |
|
|
78
|
+
|
|
79
|
+
Plus: deletion of matplotlib-specific code paths in `hiprpy.plot` (~1 K LOC saved) and removal of matplotlib runtime dep.
|
|
80
|
+
|
|
81
|
+
### Why not pygfx + own axes?
|
|
82
|
+
|
|
83
|
+
A hybrid path is also viable: use pygfx for the rasterization layer (lines/text/transform/camera) and write our own axis/tick/legend/hover layer on top. That'd be the best of both — pygfx handles the rendering substrate, we own the plotting semantics. Estimated ~1.5 K LOC of new code on top of pygfx.
|
|
84
|
+
|
|
85
|
+
## Open questions
|
|
86
|
+
|
|
87
|
+
1. **Hover semantics** — does the classification-debugger tooltip pattern generalize cleanly, or do different plots need different hover content models?
|
|
88
|
+
2. **Export fidelity** — do we need true vector PDF, or is high-DPI PNG enough? Vector requires re-rasterizing axes/text on the CPU side, which is non-trivial.
|
|
89
|
+
3. **Notebook + standalone parity** — pygfx renders to a wgpu canvas that displays inline in Jupyter via `wgpu_jupyter`. Does that path work in VS Code's Jupyter extension and in standalone scripts (`savefig`-equivalent)?
|
|
90
|
+
4. **Remoting** — if we ever want a server-side render pipeline (for cloud GUIs), pygfx's WGPU backend can render headlessly; matplotlib `Agg` does the same. Bokeh/plotly's JS-frontend assumption is harder to remote.
|
|
91
|
+
|
|
92
|
+
## Concrete next action
|
|
93
|
+
|
|
94
|
+
Spike `plot_spectra_pygfx` using `scope.mixed_spectra[-1]` from `notebooks/hiprpy_demo_notebook.ipynb` as the test case. Compare visual output side-by-side with `plot_spectra_wgpu` and `plot_spectra_cpu`. Decide pygfx-vs-roll-own from that single comparison.
|
|
95
|
+
|
|
96
|
+
If the answer ends up being "pygfx + own axis layer", the new module structure would live in `ocdkit.plot.gfx_*` (parallel to the current matplotlib-based modules) so the migration is a per-call-site flip rather than a big-bang rewrite.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Authoring an ocdkit.viewer plugin
|
|
2
|
+
|
|
3
|
+
This document is the source-of-truth contract for adding a new segmentation
|
|
4
|
+
backend to the ocdkit viewer. It is written so an LLM can read it and produce
|
|
5
|
+
a working plugin in one shot.
|
|
6
|
+
|
|
7
|
+
## What a plugin is
|
|
8
|
+
|
|
9
|
+
A plugin wires a segmentation tool (Cellpose, StarDist, SAM, your own model,
|
|
10
|
+
…) into the ocdkit viewer. The viewer handles:
|
|
11
|
+
|
|
12
|
+
- Image loading, display, panning, zooming
|
|
13
|
+
- Mask rendering (n-coloring, opacity, color tables)
|
|
14
|
+
- Manual mask editing (brush, fill, split, merge)
|
|
15
|
+
- Affinity-graph editing
|
|
16
|
+
- Session persistence
|
|
17
|
+
|
|
18
|
+
Your plugin only needs to:
|
|
19
|
+
|
|
20
|
+
1. Declare the parameters the user can tune.
|
|
21
|
+
2. Provide a `run(image, params) -> mask` function.
|
|
22
|
+
3. (Optionally) declare lifecycle hooks: model loading, GPU toggle, cache.
|
|
23
|
+
|
|
24
|
+
## The `SegmentationPlugin` contract
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from ocdkit.viewer import SegmentationPlugin, WidgetSpec
|
|
28
|
+
|
|
29
|
+
plugin = SegmentationPlugin(
|
|
30
|
+
name="my_tool", # stable plugin id (lowercase, no spaces)
|
|
31
|
+
version="0.1.0",
|
|
32
|
+
description="Brief one-line description.",
|
|
33
|
+
homepage="https://github.com/you/my_tool",
|
|
34
|
+
widgets=[
|
|
35
|
+
WidgetSpec(
|
|
36
|
+
name="threshold", # key passed into params
|
|
37
|
+
label="Threshold", # UI label
|
|
38
|
+
kind="slider", # widget kind (see below)
|
|
39
|
+
default=0.5,
|
|
40
|
+
min=0.0, max=1.0, step=0.01,
|
|
41
|
+
help="Pixels above this become foreground.",
|
|
42
|
+
group="Detection", # subsection header
|
|
43
|
+
),
|
|
44
|
+
# ... more WidgetSpec entries ...
|
|
45
|
+
],
|
|
46
|
+
run=my_segmentation_function,
|
|
47
|
+
# optional:
|
|
48
|
+
load_models=lambda: ["model_a", "model_b"],
|
|
49
|
+
warmup=lambda model_id: None,
|
|
50
|
+
set_use_gpu=lambda enabled: None,
|
|
51
|
+
clear_cache=lambda: None,
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## `run(image, params) -> mask` contract
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
def my_segmentation_function(image: np.ndarray, params: dict) -> np.ndarray:
|
|
59
|
+
"""
|
|
60
|
+
image: uint8 numpy array.
|
|
61
|
+
- Shape (H, W) for grayscale.
|
|
62
|
+
- Shape (H, W, C) for multichannel/RGB. C is 1, 2, 3, or 4.
|
|
63
|
+
params: dict whose keys are the WidgetSpec.name strings you declared.
|
|
64
|
+
Values are typed: numbers for slider/number, bool for toggle, str for
|
|
65
|
+
dropdown/text/file/color/colormap.
|
|
66
|
+
returns: 2D int32 (or smaller int) numpy array, shape (H, W).
|
|
67
|
+
0 = background. Positive integers are instance ids.
|
|
68
|
+
The viewer takes care of n-coloring and rendering.
|
|
69
|
+
"""
|
|
70
|
+
...
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Widget kinds
|
|
74
|
+
|
|
75
|
+
| `kind` | Type | Required fields | Notes |
|
|
76
|
+
| ------------- | ------- | -------------------------- | ----------------------------------------------- |
|
|
77
|
+
| `slider` | float | `min`, `max` (`step`) | Continuous range with handle |
|
|
78
|
+
| `slider_log` | float | `min` > 0, `max` (`step`) | Log-scaled slider |
|
|
79
|
+
| `number` | float | `min`, `max` (`step`) | Plain number input box |
|
|
80
|
+
| `toggle` | bool | `default` must be bool | Checkbox / switch |
|
|
81
|
+
| `dropdown` | str | `choices` | Single-select from list of strings |
|
|
82
|
+
| `text` | str | — | Free-form text input |
|
|
83
|
+
| `file` | str | — | File path picker |
|
|
84
|
+
| `color` | str | — | Hex color string (`"#ff0000"`) |
|
|
85
|
+
| `colormap` | str | — | Colormap name from ocdkit.cmap |
|
|
86
|
+
|
|
87
|
+
### Conditional visibility
|
|
88
|
+
|
|
89
|
+
A widget can be hidden until other widgets have specific values:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
WidgetSpec(
|
|
93
|
+
name="manual_value",
|
|
94
|
+
label="Manual value",
|
|
95
|
+
kind="slider", default=0.5, min=0.0, max=1.0,
|
|
96
|
+
visible_when={"method": "manual"}, # show only when method == "manual"
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Multiple keys are AND-ed: the widget shows only when all entries match.
|
|
101
|
+
|
|
102
|
+
### Grouping
|
|
103
|
+
|
|
104
|
+
`group="Detection"` puts widgets under a collapsible "Detection" section in
|
|
105
|
+
the pane. Widgets without `group` go into a default top section.
|
|
106
|
+
|
|
107
|
+
## Lifecycle hooks
|
|
108
|
+
|
|
109
|
+
All optional. Skip the ones you don't need.
|
|
110
|
+
|
|
111
|
+
| Hook | Signature | When called |
|
|
112
|
+
| --------------- | ---------------------- | ---------------------------------------------------- |
|
|
113
|
+
| `load_models` | `() -> list[str]` | Once on plugin selection — populates the model list. |
|
|
114
|
+
| `warmup` | `(model_id) -> None` | When the user picks a model — preload it. |
|
|
115
|
+
| `set_use_gpu` | `(enabled: bool) -> None` | When the user toggles the GPU switch. |
|
|
116
|
+
| `clear_cache` | `() -> None` | When the user clicks "Clear cache". |
|
|
117
|
+
|
|
118
|
+
If `load_models` is provided, the viewer renders a model dropdown at the top
|
|
119
|
+
of the pane and passes the chosen `model` value in `params["model"]` on each
|
|
120
|
+
`run()` call — you do not need to declare a `WidgetSpec` for it.
|
|
121
|
+
|
|
122
|
+
## Registering the plugin
|
|
123
|
+
|
|
124
|
+
### Option A — entry point (recommended for installed packages)
|
|
125
|
+
|
|
126
|
+
In your `pyproject.toml`:
|
|
127
|
+
|
|
128
|
+
```toml
|
|
129
|
+
[project.entry-points."ocdkit.plugins"]
|
|
130
|
+
my_tool = "my_tool.ocdkit_plugin:plugin"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Where `my_tool/ocdkit_plugin.py` contains:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from ocdkit.viewer import SegmentationPlugin, WidgetSpec
|
|
137
|
+
plugin = SegmentationPlugin(name="my_tool", ..., run=...)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The viewer auto-discovers all `ocdkit.plugins` entry points at startup.
|
|
141
|
+
|
|
142
|
+
### Option B — explicit registration (for in-process or tests)
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from ocdkit.viewer import register_plugin
|
|
146
|
+
register_plugin(plugin)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Validation
|
|
150
|
+
|
|
151
|
+
Both `WidgetSpec(...)` and `SegmentationPlugin(...)` validate their arguments
|
|
152
|
+
in `__post_init__`. Common errors raised at construction time:
|
|
153
|
+
|
|
154
|
+
- `WidgetSpec.name` empty or non-string → `ValueError`
|
|
155
|
+
- numeric kinds without `min`/`max` → `ValueError`
|
|
156
|
+
- `slider_log` with `min <= 0` → `ValueError`
|
|
157
|
+
- `dropdown` without `choices` → `ValueError`
|
|
158
|
+
- `dropdown` whose `default` is not in `choices` → `ValueError`
|
|
159
|
+
- `toggle` with non-bool `default` → `ValueError`
|
|
160
|
+
- duplicate widget `name` within one plugin → `ValueError`
|
|
161
|
+
|
|
162
|
+
Programmatic schemas for tools and tests:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from ocdkit.viewer.plugins.schema import (
|
|
166
|
+
widget_spec_schema, # JSON Schema for one WidgetSpec
|
|
167
|
+
plugin_manifest_schema, # JSON Schema for plugin.manifest()
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Minimal complete example
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
# my_tool/ocdkit_plugin.py
|
|
175
|
+
import numpy as np
|
|
176
|
+
from skimage.filters import threshold_otsu
|
|
177
|
+
from skimage.measure import label
|
|
178
|
+
from ocdkit.viewer import SegmentationPlugin, WidgetSpec
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _run(image: np.ndarray, params: dict) -> np.ndarray:
|
|
182
|
+
if image.ndim == 3:
|
|
183
|
+
image = image.mean(axis=-1).astype(image.dtype)
|
|
184
|
+
cutoff = float(params["threshold"]) * 255.0
|
|
185
|
+
if params.get("method") == "otsu":
|
|
186
|
+
cutoff = float(threshold_otsu(image))
|
|
187
|
+
binary = (image > cutoff)
|
|
188
|
+
if params.get("invert"):
|
|
189
|
+
binary = ~binary
|
|
190
|
+
return label(binary).astype(np.int32)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
plugin = SegmentationPlugin(
|
|
194
|
+
name="simple_threshold",
|
|
195
|
+
version="0.1.0",
|
|
196
|
+
description="Otsu / manual threshold + connected components.",
|
|
197
|
+
widgets=[
|
|
198
|
+
WidgetSpec("method", "Method", "dropdown",
|
|
199
|
+
default="otsu", choices=["otsu", "manual"]),
|
|
200
|
+
WidgetSpec("threshold", "Threshold", "slider",
|
|
201
|
+
default=0.5, min=0.0, max=1.0, step=0.01,
|
|
202
|
+
visible_when={"method": "manual"}),
|
|
203
|
+
WidgetSpec("invert", "Invert", "toggle", default=False),
|
|
204
|
+
],
|
|
205
|
+
run=_run,
|
|
206
|
+
)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
That's the entire plugin. Once entry-pointed, it appears in the viewer's
|
|
210
|
+
plugin dropdown automatically.
|
|
211
|
+
|
|
212
|
+
## Conventions
|
|
213
|
+
|
|
214
|
+
- Plugin `name` is lowercase, snake_case. It appears in URLs.
|
|
215
|
+
- Widget `name`s are also snake_case and are the keys in `params`.
|
|
216
|
+
- Don't mutate `image` in `run()`; return a new array.
|
|
217
|
+
- Return masks as 2D `int32` (or `uint16`/`int64` work too). Background = 0.
|
|
218
|
+
- Lifecycle hooks should be cheap or fork to a worker thread internally.
|
|
219
|
+
- Heavy imports (torch, your model library) belong **inside** `run()` or
|
|
220
|
+
inside `warmup()`, not at module top — keeps viewer startup fast.
|