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.
Files changed (187) hide show
  1. {ocdkit-0.0.2 → ocdkit-0.0.3}/.github/workflows/test_and_deploy.yml +5 -1
  2. {ocdkit-0.0.2 → ocdkit-0.0.3}/.gitignore +4 -0
  3. {ocdkit-0.0.2 → ocdkit-0.0.3}/MANIFEST.in +4 -0
  4. {ocdkit-0.0.2/src/ocdkit.egg-info → ocdkit-0.0.3}/PKG-INFO +43 -6
  5. ocdkit-0.0.2/PKG-INFO → ocdkit-0.0.3/README.md +28 -28
  6. ocdkit-0.0.3/docs/plugin-authoring.md +220 -0
  7. ocdkit-0.0.3/docs/pywebview-desktop-integration.md +689 -0
  8. ocdkit-0.0.3/pyproject.toml +90 -0
  9. ocdkit-0.0.3/src/ocdkit/__init__.py +40 -0
  10. ocdkit-0.0.3/src/ocdkit/__main__.py +6 -0
  11. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/convert.py +38 -0
  12. ocdkit-0.0.3/src/ocdkit/array/parallel.py +164 -0
  13. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/spatial.py +4 -1
  14. ocdkit-0.0.3/src/ocdkit/cli/__init__.py +14 -0
  15. ocdkit-0.0.3/src/ocdkit/cli/__main__.py +6 -0
  16. ocdkit-0.0.3/src/ocdkit/cli/main.py +41 -0
  17. ocdkit-0.0.3/src/ocdkit/cli/migrate.py +77 -0
  18. ocdkit-0.0.3/src/ocdkit/cli/paths.py +170 -0
  19. ocdkit-0.0.3/src/ocdkit/desktop/__init__.py +8 -0
  20. ocdkit-0.0.3/src/ocdkit/desktop/pinning.py +1007 -0
  21. ocdkit-0.0.3/src/ocdkit/io/__init__.py +3 -0
  22. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/io/image.py +6 -3
  23. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/load/__init__.py +1 -1
  24. ocdkit-0.0.3/src/ocdkit/load/module.py +192 -0
  25. ocdkit-0.0.3/src/ocdkit/plot/__init__.py +11 -0
  26. ocdkit-0.0.3/src/ocdkit/testing/__init__.py +27 -0
  27. ocdkit-0.0.3/src/ocdkit/testing/collisions.py +211 -0
  28. ocdkit-0.0.3/src/ocdkit/tls.py +792 -0
  29. ocdkit-0.0.3/src/ocdkit/utils/paths.py +186 -0
  30. ocdkit-0.0.3/src/ocdkit/viewer/__init__.py +29 -0
  31. ocdkit-0.0.3/src/ocdkit/viewer/__main__.py +8 -0
  32. ocdkit-0.0.3/src/ocdkit/viewer/app.py +830 -0
  33. ocdkit-0.0.3/src/ocdkit/viewer/assets.py +640 -0
  34. ocdkit-0.0.3/src/ocdkit/viewer/cli.py +162 -0
  35. ocdkit-0.0.3/src/ocdkit/viewer/demo.html +253 -0
  36. ocdkit-0.0.3/src/ocdkit/viewer/dependencies.py +73 -0
  37. ocdkit-0.0.3/src/ocdkit/viewer/exceptions.py +125 -0
  38. ocdkit-0.0.3/src/ocdkit/viewer/masks.py +71 -0
  39. ocdkit-0.0.3/src/ocdkit/viewer/middleware.py +81 -0
  40. ocdkit-0.0.3/src/ocdkit/viewer/model_registry.py +96 -0
  41. ocdkit-0.0.3/src/ocdkit/viewer/plugins/__init__.py +25 -0
  42. ocdkit-0.0.3/src/ocdkit/viewer/plugins/base.py +259 -0
  43. ocdkit-0.0.3/src/ocdkit/viewer/plugins/registry.py +160 -0
  44. ocdkit-0.0.3/src/ocdkit/viewer/plugins/schema.py +178 -0
  45. ocdkit-0.0.3/src/ocdkit/viewer/plugins/threshold.py +104 -0
  46. ocdkit-0.0.3/src/ocdkit/viewer/routers/__init__.py +9 -0
  47. ocdkit-0.0.3/src/ocdkit/viewer/routers/index.py +61 -0
  48. ocdkit-0.0.3/src/ocdkit/viewer/routers/log.py +36 -0
  49. ocdkit-0.0.3/src/ocdkit/viewer/routers/mask.py +46 -0
  50. ocdkit-0.0.3/src/ocdkit/viewer/routers/plugin.py +129 -0
  51. ocdkit-0.0.3/src/ocdkit/viewer/routers/segment.py +39 -0
  52. ocdkit-0.0.3/src/ocdkit/viewer/routers/session_routes.py +192 -0
  53. ocdkit-0.0.3/src/ocdkit/viewer/routers/system.py +41 -0
  54. ocdkit-0.0.3/src/ocdkit/viewer/routers/trust.py +298 -0
  55. ocdkit-0.0.3/src/ocdkit/viewer/routes.py +217 -0
  56. ocdkit-0.0.3/src/ocdkit/viewer/sample_image.py +113 -0
  57. ocdkit-0.0.3/src/ocdkit/viewer/schemas.py +150 -0
  58. ocdkit-0.0.3/src/ocdkit/viewer/segmentation.py +270 -0
  59. ocdkit-0.0.3/src/ocdkit/viewer/session.py +311 -0
  60. ocdkit-0.0.3/src/ocdkit/viewer/system.py +260 -0
  61. ocdkit-0.0.3/src/ocdkit/viewer/web/app.js +11415 -0
  62. ocdkit-0.0.3/src/ocdkit/viewer/web/css/controls.css +2309 -0
  63. ocdkit-0.0.3/src/ocdkit/viewer/web/css/layout.css +276 -0
  64. ocdkit-0.0.3/src/ocdkit/viewer/web/css/tools.css +383 -0
  65. ocdkit-0.0.3/src/ocdkit/viewer/web/css/viewer.css +85 -0
  66. ocdkit-0.0.3/src/ocdkit/viewer/web/html/left-panel.html +253 -0
  67. ocdkit-0.0.3/src/ocdkit/viewer/web/html/sidebar.html +34 -0
  68. ocdkit-0.0.3/src/ocdkit/viewer/web/html/viewer.html +7 -0
  69. ocdkit-0.0.3/src/ocdkit/viewer/web/icons/affinity.svg +25 -0
  70. ocdkit-0.0.3/src/ocdkit/viewer/web/icons/arrow-back-up.svg +1 -0
  71. ocdkit-0.0.3/src/ocdkit/viewer/web/icons/arrow-forward-up.svg +1 -0
  72. ocdkit-0.0.3/src/ocdkit/viewer/web/icons/dbscan-nested-arcs.svg +6 -0
  73. ocdkit-0.0.3/src/ocdkit/viewer/web/icons/download.svg +1 -0
  74. ocdkit-0.0.3/src/ocdkit/viewer/web/icons/droplet-half-2.svg +1 -0
  75. ocdkit-0.0.3/src/ocdkit/viewer/web/icons/eraser.svg +1 -0
  76. ocdkit-0.0.3/src/ocdkit/viewer/web/icons/home-2.svg +1 -0
  77. ocdkit-0.0.3/src/ocdkit/viewer/web/icons/minus.svg +1 -0
  78. ocdkit-0.0.3/src/ocdkit/viewer/web/icons/palette.svg +1 -0
  79. ocdkit-0.0.3/src/ocdkit/viewer/web/icons/pencil.svg +1 -0
  80. ocdkit-0.0.3/src/ocdkit/viewer/web/icons/plus.svg +1 -0
  81. ocdkit-0.0.3/src/ocdkit/viewer/web/icons/rotate-rectangle.svg +1 -0
  82. ocdkit-0.0.3/src/ocdkit/viewer/web/icons/topology-star.svg +1 -0
  83. ocdkit-0.0.3/src/ocdkit/viewer/web/index.html +16 -0
  84. ocdkit-0.0.3/src/ocdkit/viewer/web/js/brush.js +330 -0
  85. ocdkit-0.0.3/src/ocdkit/viewer/web/js/colormap.js +462 -0
  86. ocdkit-0.0.3/src/ocdkit/viewer/web/js/debug-apple-material.js +133 -0
  87. ocdkit-0.0.3/src/ocdkit/viewer/web/js/file-navigation.js +620 -0
  88. ocdkit-0.0.3/src/ocdkit/viewer/web/js/history.js +143 -0
  89. ocdkit-0.0.3/src/ocdkit/viewer/web/js/interactions.js +379 -0
  90. ocdkit-0.0.3/src/ocdkit/viewer/web/js/logging.js +166 -0
  91. ocdkit-0.0.3/src/ocdkit/viewer/web/js/mask-pipeline.js +176 -0
  92. ocdkit-0.0.3/src/ocdkit/viewer/web/js/painting.js +2347 -0
  93. ocdkit-0.0.3/src/ocdkit/viewer/web/js/plugin-panel.js +891 -0
  94. ocdkit-0.0.3/src/ocdkit/viewer/web/js/pointer-state.js +115 -0
  95. ocdkit-0.0.3/src/ocdkit/viewer/web/js/state-persistence.js +379 -0
  96. ocdkit-0.0.3/src/ocdkit/viewer/web/js/tooltip-editor.js +342 -0
  97. ocdkit-0.0.3/src/ocdkit/viewer/web/js/ui-utils.js +1419 -0
  98. ocdkit-0.0.3/src/ocdkit/viewer/web/js/wasm_fill.c +101 -0
  99. ocdkit-0.0.3/src/ocdkit.egg-info/PKG-INFO +103 -0
  100. ocdkit-0.0.3/src/ocdkit.egg-info/SOURCES.txt +177 -0
  101. ocdkit-0.0.3/src/ocdkit.egg-info/entry_points.txt +6 -0
  102. ocdkit-0.0.3/src/ocdkit.egg-info/requires.txt +33 -0
  103. ocdkit-0.0.3/tests/e2e/__init__.py +0 -0
  104. ocdkit-0.0.3/tests/e2e/conftest.py +125 -0
  105. ocdkit-0.0.3/tests/e2e/test_browser_smoke.py +93 -0
  106. ocdkit-0.0.3/tests/e2e/test_pywebview_snapshot.py +81 -0
  107. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_array.py +156 -0
  108. ocdkit-0.0.3/tests/test_module_collisions.py +10 -0
  109. ocdkit-0.0.3/tests/test_paths_migration.py +114 -0
  110. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_figure.py +1 -1
  111. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_notebook.py +1 -1
  112. ocdkit-0.0.3/tests/viewer/__init__.py +0 -0
  113. ocdkit-0.0.3/tests/viewer/test_active_plugin_cache.py +70 -0
  114. ocdkit-0.0.3/tests/viewer/test_app.py +158 -0
  115. ocdkit-0.0.3/tests/viewer/test_async_dispatch.py +97 -0
  116. ocdkit-0.0.3/tests/viewer/test_envelope_and_middleware.py +144 -0
  117. ocdkit-0.0.3/tests/viewer/test_plugin_contract.py +204 -0
  118. ocdkit-0.0.3/tests/viewer/test_session_eviction.py +68 -0
  119. ocdkit-0.0.3/tests/viewer/test_title_config.py +71 -0
  120. ocdkit-0.0.3/tests/viewer/test_ui_mode.py +108 -0
  121. ocdkit-0.0.2/README.md +0 -39
  122. ocdkit-0.0.2/pyproject.toml +0 -51
  123. ocdkit-0.0.2/src/ocdkit/__init__.py +0 -10
  124. ocdkit-0.0.2/src/ocdkit/load/module.py +0 -132
  125. ocdkit-0.0.2/src/ocdkit/plot/__init__.py +0 -5
  126. ocdkit-0.0.2/src/ocdkit/utils/__init__.py +0 -3
  127. ocdkit-0.0.2/src/ocdkit.egg-info/SOURCES.txt +0 -77
  128. ocdkit-0.0.2/src/ocdkit.egg-info/requires.txt +0 -17
  129. {ocdkit-0.0.2 → ocdkit-0.0.3}/LICENSE +0 -0
  130. {ocdkit-0.0.2 → ocdkit-0.0.3}/scripts/bench_colorize.py +0 -0
  131. {ocdkit-0.0.2 → ocdkit-0.0.3}/scripts/coverage_cross_device.sh +0 -0
  132. {ocdkit-0.0.2 → ocdkit-0.0.3}/setup.cfg +0 -0
  133. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/__init__.py +0 -0
  134. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/filters.py +0 -0
  135. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/imports.py +0 -0
  136. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/index.py +0 -0
  137. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/morphology.py +0 -0
  138. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/normalize.py +0 -0
  139. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/ops.py +0 -0
  140. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/transform.py +0 -0
  141. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/union_find.py +0 -0
  142. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/array/warp.py +0 -0
  143. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/imports.py +0 -0
  144. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/io/files.py +0 -0
  145. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/io/imports.py +0 -0
  146. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/io/path.py +0 -0
  147. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/io/result.py +0 -0
  148. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/load/object.py +0 -0
  149. {ocdkit-0.0.2/src/ocdkit/io → ocdkit-0.0.3/src/ocdkit/logging}/__init__.py +0 -0
  150. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/logging/handler.py +0 -0
  151. {ocdkit-0.0.2/src/ocdkit/logging → ocdkit-0.0.3/src/ocdkit/measure}/__init__.py +0 -0
  152. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/measure/bbox.py +0 -0
  153. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/measure/diameter.py +0 -0
  154. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/measure/imports.py +0 -0
  155. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/measure/medoid.py +0 -0
  156. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/measure/metrics.py +0 -0
  157. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/color.py +0 -0
  158. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/contour.py +0 -0
  159. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/defaults.py +0 -0
  160. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/display.py +0 -0
  161. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/export.py +0 -0
  162. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/figure.py +0 -0
  163. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/grid.py +0 -0
  164. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/imports.py +0 -0
  165. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/label.py +0 -0
  166. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/plot/ncolor.py +0 -0
  167. {ocdkit-0.0.2/src/ocdkit/measure → ocdkit-0.0.3/src/ocdkit/utils}/__init__.py +0 -0
  168. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/utils/collections.py +0 -0
  169. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/utils/gpu.py +0 -0
  170. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit/utils/kwargs.py +0 -0
  171. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit.egg-info/dependency_links.txt +0 -0
  172. {ocdkit-0.0.2 → ocdkit-0.0.3}/src/ocdkit.egg-info/top_level.txt +0 -0
  173. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/fixtures/multichan_3c_4x4.czi +0 -0
  174. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/fixtures/tiny_8x8.czi +0 -0
  175. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_gpu.py +0 -0
  176. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_io.py +0 -0
  177. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_measure.py +0 -0
  178. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_morphology.py +0 -0
  179. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_color.py +0 -0
  180. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_contour.py +0 -0
  181. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_display.py +0 -0
  182. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_export.py +0 -0
  183. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_grid.py +0 -0
  184. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_plot_label.py +0 -0
  185. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_registration.py +0 -0
  186. {ocdkit-0.0.2 → ocdkit-0.0.3}/tests/test_slice.py +0 -0
  187. {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
@@ -6,6 +6,10 @@
6
6
  # Local tooling
7
7
  .claude/
8
8
 
9
+ # Editor / NAS junk
10
+ *.bak
11
+ .!*
12
+
9
13
  # C extensions
10
14
  *.so
11
15
 
@@ -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.2
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.9
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: bioio
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
- **Obsessive Coder's Dependency Toolkit** — Python utilities for array manipulation, GPU dispatch, image I/O, spatial operations, morphology, and plotting.
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
- **Obsessive Coder's Dependency Toolkit** — Python utilities for array manipulation, GPU dispatch, image I/O, spatial operations, morphology, and plotting.
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.