ocdkit 0.0.2__tar.gz → 0.0.3__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.3}/.github/workflows/test_and_deploy.yml +5 -1
- {ocdkit-0.0.2 → ocdkit-0.0.3}/.gitignore +4 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/MANIFEST.in +4 -0
- {ocdkit-0.0.2/src/ocdkit.egg-info → ocdkit-0.0.3}/PKG-INFO +43 -6
- ocdkit-0.0.2/PKG-INFO → ocdkit-0.0.3/README.md +28 -28
- ocdkit-0.0.3/docs/plugin-authoring.md +220 -0
- ocdkit-0.0.3/docs/pywebview-desktop-integration.md +689 -0
- ocdkit-0.0.3/pyproject.toml +90 -0
- ocdkit-0.0.3/src/ocdkit/__init__.py +40 -0
- ocdkit-0.0.3/src/ocdkit/__main__.py +6 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/convert.py +38 -0
- ocdkit-0.0.3/src/ocdkit/array/parallel.py +164 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/spatial.py +4 -1
- ocdkit-0.0.3/src/ocdkit/cli/__init__.py +14 -0
- ocdkit-0.0.3/src/ocdkit/cli/__main__.py +6 -0
- ocdkit-0.0.3/src/ocdkit/cli/main.py +41 -0
- ocdkit-0.0.3/src/ocdkit/cli/migrate.py +77 -0
- ocdkit-0.0.3/src/ocdkit/cli/paths.py +170 -0
- ocdkit-0.0.3/src/ocdkit/desktop/__init__.py +8 -0
- ocdkit-0.0.3/src/ocdkit/desktop/pinning.py +1007 -0
- ocdkit-0.0.3/src/ocdkit/io/__init__.py +3 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/io/image.py +6 -3
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/load/__init__.py +1 -1
- ocdkit-0.0.3/src/ocdkit/load/module.py +192 -0
- ocdkit-0.0.3/src/ocdkit/plot/__init__.py +11 -0
- ocdkit-0.0.3/src/ocdkit/testing/__init__.py +27 -0
- ocdkit-0.0.3/src/ocdkit/testing/collisions.py +211 -0
- ocdkit-0.0.3/src/ocdkit/tls.py +792 -0
- ocdkit-0.0.3/src/ocdkit/utils/paths.py +186 -0
- ocdkit-0.0.3/src/ocdkit/viewer/__init__.py +29 -0
- ocdkit-0.0.3/src/ocdkit/viewer/__main__.py +8 -0
- ocdkit-0.0.3/src/ocdkit/viewer/app.py +830 -0
- ocdkit-0.0.3/src/ocdkit/viewer/assets.py +640 -0
- ocdkit-0.0.3/src/ocdkit/viewer/cli.py +162 -0
- ocdkit-0.0.3/src/ocdkit/viewer/demo.html +253 -0
- ocdkit-0.0.3/src/ocdkit/viewer/dependencies.py +73 -0
- ocdkit-0.0.3/src/ocdkit/viewer/exceptions.py +125 -0
- ocdkit-0.0.3/src/ocdkit/viewer/masks.py +71 -0
- ocdkit-0.0.3/src/ocdkit/viewer/middleware.py +81 -0
- ocdkit-0.0.3/src/ocdkit/viewer/model_registry.py +96 -0
- ocdkit-0.0.3/src/ocdkit/viewer/plugins/__init__.py +25 -0
- ocdkit-0.0.3/src/ocdkit/viewer/plugins/base.py +259 -0
- ocdkit-0.0.3/src/ocdkit/viewer/plugins/registry.py +160 -0
- ocdkit-0.0.3/src/ocdkit/viewer/plugins/schema.py +178 -0
- ocdkit-0.0.3/src/ocdkit/viewer/plugins/threshold.py +104 -0
- ocdkit-0.0.3/src/ocdkit/viewer/routers/__init__.py +9 -0
- ocdkit-0.0.3/src/ocdkit/viewer/routers/index.py +61 -0
- ocdkit-0.0.3/src/ocdkit/viewer/routers/log.py +36 -0
- ocdkit-0.0.3/src/ocdkit/viewer/routers/mask.py +46 -0
- ocdkit-0.0.3/src/ocdkit/viewer/routers/plugin.py +129 -0
- ocdkit-0.0.3/src/ocdkit/viewer/routers/segment.py +39 -0
- ocdkit-0.0.3/src/ocdkit/viewer/routers/session_routes.py +192 -0
- ocdkit-0.0.3/src/ocdkit/viewer/routers/system.py +41 -0
- ocdkit-0.0.3/src/ocdkit/viewer/routers/trust.py +298 -0
- ocdkit-0.0.3/src/ocdkit/viewer/routes.py +217 -0
- ocdkit-0.0.3/src/ocdkit/viewer/sample_image.py +113 -0
- ocdkit-0.0.3/src/ocdkit/viewer/schemas.py +150 -0
- ocdkit-0.0.3/src/ocdkit/viewer/segmentation.py +270 -0
- ocdkit-0.0.3/src/ocdkit/viewer/session.py +311 -0
- ocdkit-0.0.3/src/ocdkit/viewer/system.py +260 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/app.js +11415 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/css/controls.css +2309 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/css/layout.css +276 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/css/tools.css +383 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/css/viewer.css +85 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/html/left-panel.html +253 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/html/sidebar.html +34 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/html/viewer.html +7 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/icons/affinity.svg +25 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/icons/arrow-back-up.svg +1 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/icons/arrow-forward-up.svg +1 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/icons/dbscan-nested-arcs.svg +6 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/icons/download.svg +1 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/icons/droplet-half-2.svg +1 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/icons/eraser.svg +1 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/icons/home-2.svg +1 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/icons/minus.svg +1 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/icons/palette.svg +1 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/icons/pencil.svg +1 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/icons/plus.svg +1 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/icons/rotate-rectangle.svg +1 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/icons/topology-star.svg +1 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/index.html +16 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/brush.js +330 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/colormap.js +462 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/debug-apple-material.js +133 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/file-navigation.js +620 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/history.js +143 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/interactions.js +379 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/logging.js +166 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/mask-pipeline.js +176 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/painting.js +2347 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/plugin-panel.js +891 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/pointer-state.js +115 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/state-persistence.js +379 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/tooltip-editor.js +342 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/ui-utils.js +1419 -0
- ocdkit-0.0.3/src/ocdkit/viewer/web/js/wasm_fill.c +101 -0
- ocdkit-0.0.3/src/ocdkit.egg-info/PKG-INFO +103 -0
- ocdkit-0.0.3/src/ocdkit.egg-info/SOURCES.txt +177 -0
- ocdkit-0.0.3/src/ocdkit.egg-info/entry_points.txt +6 -0
- ocdkit-0.0.3/src/ocdkit.egg-info/requires.txt +33 -0
- ocdkit-0.0.3/tests/e2e/__init__.py +0 -0
- ocdkit-0.0.3/tests/e2e/conftest.py +125 -0
- ocdkit-0.0.3/tests/e2e/test_browser_smoke.py +93 -0
- ocdkit-0.0.3/tests/e2e/test_pywebview_snapshot.py +81 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_array.py +156 -0
- ocdkit-0.0.3/tests/test_module_collisions.py +10 -0
- ocdkit-0.0.3/tests/test_paths_migration.py +114 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_figure.py +1 -1
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_notebook.py +1 -1
- ocdkit-0.0.3/tests/viewer/__init__.py +0 -0
- ocdkit-0.0.3/tests/viewer/test_active_plugin_cache.py +70 -0
- ocdkit-0.0.3/tests/viewer/test_app.py +158 -0
- ocdkit-0.0.3/tests/viewer/test_async_dispatch.py +97 -0
- ocdkit-0.0.3/tests/viewer/test_envelope_and_middleware.py +144 -0
- ocdkit-0.0.3/tests/viewer/test_plugin_contract.py +204 -0
- ocdkit-0.0.3/tests/viewer/test_session_eviction.py +68 -0
- ocdkit-0.0.3/tests/viewer/test_title_config.py +71 -0
- ocdkit-0.0.3/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.3}/LICENSE +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/scripts/bench_colorize.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/scripts/coverage_cross_device.sh +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/setup.cfg +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/__init__.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/filters.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/imports.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/index.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/morphology.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/normalize.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/ops.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/transform.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/union_find.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/warp.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/imports.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/io/files.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/io/imports.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/io/path.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/io/result.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/load/object.py +0 -0
- {ocdkit-0.0.2/src/ocdkit/io → ocdkit-0.0.3/src/ocdkit/logging}/__init__.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/logging/handler.py +0 -0
- {ocdkit-0.0.2/src/ocdkit/logging → ocdkit-0.0.3/src/ocdkit/measure}/__init__.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/measure/bbox.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/measure/diameter.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/measure/imports.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/measure/medoid.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/measure/metrics.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/color.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/contour.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/defaults.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/display.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/export.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/figure.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/grid.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/imports.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/label.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/ncolor.py +0 -0
- {ocdkit-0.0.2/src/ocdkit/measure → ocdkit-0.0.3/src/ocdkit/utils}/__init__.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/utils/collections.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/utils/gpu.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/utils/kwargs.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit.egg-info/dependency_links.txt +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit.egg-info/top_level.txt +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/fixtures/multichan_3c_4x4.czi +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/fixtures/tiny_8x8.czi +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_gpu.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_io.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_measure.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_morphology.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_color.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_contour.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_display.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_export.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_grid.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_label.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_registration.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_slice.py +0 -0
- {ocdkit-0.0.2 → ocdkit-0.0.3}/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.3
|
|
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
9
|
Requires-Dist: numpy
|
|
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,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.
|