ocdkit 0.0.4__tar.gz → 0.0.5__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.4 → ocdkit-0.0.5}/.gitignore +11 -0
- {ocdkit-0.0.4/src/ocdkit.egg-info → ocdkit-0.0.5}/PKG-INFO +3 -1
- {ocdkit-0.0.4 → ocdkit-0.0.5}/badges/coverage.svg +1 -1
- {ocdkit-0.0.4 → ocdkit-0.0.5}/badges/tests.svg +1 -1
- {ocdkit-0.0.4 → ocdkit-0.0.5}/docs/plot-backend-roadmap.md +9 -10
- {ocdkit-0.0.4 → ocdkit-0.0.5}/pyproject.toml +6 -5
- {ocdkit-0.0.4 → ocdkit-0.0.5}/scripts/bench_contour_alignment.py +2 -3
- ocdkit-0.0.5/scripts/bench_hdr_cmap.py +181 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/scripts/bench_vector_contours.py +13 -4
- ocdkit-0.0.5/scripts/bench_vector_contours_tier2.py +108 -0
- ocdkit-0.0.5/scripts/check_compare_pixels.py +77 -0
- ocdkit-0.0.5/scripts/check_hdr_cmap.py +65 -0
- ocdkit-0.0.5/scripts/check_jxl_p3_bytes.py +64 -0
- ocdkit-0.0.5/scripts/check_playwright_render.py +136 -0
- ocdkit-0.0.5/scripts/check_uhdr_sdr_base.py +73 -0
- ocdkit-0.0.5/scripts/coverage_cross_device.env.example +11 -0
- ocdkit-0.0.5/scripts/coverage_cross_device.sh +56 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/array/parallel.py +22 -2
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/cli/paths.py +1 -1
- ocdkit-0.0.5/src/ocdkit/io/figure.py +2897 -0
- ocdkit-0.0.5/src/ocdkit/io/figure_server.py +1210 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/load/module.py +22 -3
- ocdkit-0.0.5/src/ocdkit/plot/__init__.py +58 -0
- ocdkit-0.0.5/src/ocdkit/plot/bench.py +208 -0
- ocdkit-0.0.5/src/ocdkit/plot/composite.py +141 -0
- ocdkit-0.0.5/src/ocdkit/plot/composite_grid.py +371 -0
- ocdkit-0.0.4/scripts/bench_vector_contours_tier2.py → ocdkit-0.0.5/src/ocdkit/plot/contour.py +236 -266
- ocdkit-0.0.5/src/ocdkit/plot/display.py +534 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/plot/figure.py +5 -2
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/plot/grid.py +6 -2
- ocdkit-0.0.5/src/ocdkit/plot/hdr_cmap.py +342 -0
- ocdkit-0.0.5/src/ocdkit/plot/image_grid.py +877 -0
- ocdkit-0.0.5/src/ocdkit/plot/imports.py +19 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/plot/label.py +3 -0
- ocdkit-0.0.5/src/ocdkit/plot/layout.py +589 -0
- ocdkit-0.0.5/src/ocdkit/plot/style.py +132 -0
- ocdkit-0.0.5/src/ocdkit/plot/svg.py +927 -0
- ocdkit-0.0.5/src/ocdkit/plot/text_metrics.py +190 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/testing/__init__.py +1 -1
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/utils/kwargs.py +2 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/utils/paths.py +1 -1
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/app.py +4 -4
- {ocdkit-0.0.4 → ocdkit-0.0.5/src/ocdkit.egg-info}/PKG-INFO +3 -1
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit.egg-info/SOURCES.txt +18 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit.egg-info/requires.txt +2 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_import_cycles.py +1 -1
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_module_collisions.py +1 -1
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_module_discovery.py +1 -1
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_plot_grid.py +12 -12
- ocdkit-0.0.4/scripts/coverage_cross_device.sh +0 -41
- ocdkit-0.0.4/src/ocdkit/plot/__init__.py +0 -11
- ocdkit-0.0.4/src/ocdkit/plot/contour.py +0 -102
- ocdkit-0.0.4/src/ocdkit/plot/display.py +0 -208
- ocdkit-0.0.4/src/ocdkit/plot/imports.py +0 -9
- {ocdkit-0.0.4 → ocdkit-0.0.5}/.github/workflows/test_and_deploy.yml +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/LICENSE +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/MANIFEST.in +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/README.md +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/docs/plugin-authoring.md +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/docs/pywebview-desktop-integration.md +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/scripts/bench_colorize.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/setup.cfg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/__main__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/array/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/array/convert.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/array/filters.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/array/imports.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/array/index.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/array/morphology.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/array/normalize.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/array/ops.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/array/spatial.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/array/transform.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/array/union_find.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/array/warp.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/cli/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/cli/__main__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/cli/main.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/cli/migrate.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/desktop/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/desktop/pinning.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/imports.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/io/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/io/files.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/io/image.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/io/imports.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/io/path.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/io/result.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/load/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/load/object.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/logging/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/logging/handler.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/measure/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/measure/bbox.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/measure/diameter.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/measure/imports.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/measure/medoid.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/measure/metrics.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/plot/color.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/plot/defaults.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/plot/export.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/plot/ncolor.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/testing/collisions.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/testing/imports.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/tls/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/tls/external_ca.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/tls/hostnames.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/tls/imports.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/tls/local_ca.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/tls/paths.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/tls/trust.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/utils/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/utils/collections.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/utils/gpu.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/__main__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/assets.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/cli.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/demo.html +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/dependencies.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/exceptions.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/masks.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/middleware.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/model_registry.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/plugins/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/plugins/base.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/plugins/registry.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/plugins/schema.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/plugins/threshold.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/routers/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/routers/index.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/routers/log.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/routers/mask.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/routers/plugin.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/routers/segment.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/routers/session_routes.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/routers/system.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/routers/trust.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/routes.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/sample_image.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/schemas.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/segmentation.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/session.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/system.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/app.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/css/controls.css +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/css/layout.css +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/css/tools.css +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/css/viewer.css +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/html/left-panel.html +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/html/sidebar.html +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/html/viewer.html +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/icons/affinity.svg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/icons/arrow-back-up.svg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/icons/arrow-forward-up.svg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/icons/dbscan-nested-arcs.svg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/icons/download.svg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/icons/droplet-half-2.svg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/icons/eraser.svg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/icons/home-2.svg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/icons/minus.svg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/icons/palette.svg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/icons/pencil.svg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/icons/plus.svg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/icons/rotate-rectangle.svg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/icons/topology-star.svg +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/index.html +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/brush.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/colormap.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/debug-apple-material.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/file-navigation.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/history.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/interactions.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/logging.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/mask-pipeline.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/painting.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/plugin-panel.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/pointer-state.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/state-persistence.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/tooltip-editor.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/ui-utils.js +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit/viewer/web/js/wasm_fill.c +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit.egg-info/dependency_links.txt +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit.egg-info/entry_points.txt +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/src/ocdkit.egg-info/top_level.txt +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/e2e/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/e2e/conftest.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/e2e/test_browser_smoke.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/e2e/test_pywebview_snapshot.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/fixtures/multichan_3c_4x4.czi +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/fixtures/tiny_8x8.czi +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_array.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_gpu.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_io.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_measure.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_morphology.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_paths_migration.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_plot_color.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_plot_contour.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_plot_display.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_plot_export.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_plot_figure.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_plot_label.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_plot_notebook.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_registration.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_slice.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/test_spatial.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/viewer/__init__.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/viewer/test_active_plugin_cache.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/viewer/test_app.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/viewer/test_async_dispatch.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/viewer/test_envelope_and_middleware.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/viewer/test_plugin_contract.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/viewer/test_session_eviction.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/viewer/test_title_config.py +0 -0
- {ocdkit-0.0.4 → ocdkit-0.0.5}/tests/viewer/test_ui_mode.py +0 -0
|
@@ -6,6 +6,17 @@
|
|
|
6
6
|
# Local tooling
|
|
7
7
|
.claude/
|
|
8
8
|
|
|
9
|
+
# Local machine-specific config for bench / coverage scripts
|
|
10
|
+
scripts/coverage_cross_device.env
|
|
11
|
+
scripts/bench_paths.env
|
|
12
|
+
|
|
13
|
+
# Generated artifacts from bench / demo scripts
|
|
14
|
+
scripts/bench_*.png
|
|
15
|
+
scripts/check_*.png
|
|
16
|
+
scripts/check_*.svg
|
|
17
|
+
scripts/check_*.html
|
|
18
|
+
figures/
|
|
19
|
+
|
|
9
20
|
# Editor / NAS junk
|
|
10
21
|
*.bak
|
|
11
22
|
.!*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ocdkit
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
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
6
|
Requires-Python: >=3.11
|
|
@@ -11,6 +11,7 @@ Requires-Dist: scipy
|
|
|
11
11
|
Requires-Dist: scikit-image>=0.26
|
|
12
12
|
Requires-Dist: tifffile
|
|
13
13
|
Requires-Dist: imagecodecs
|
|
14
|
+
Requires-Dist: opencodecs>=0.1.12
|
|
14
15
|
Requires-Dist: matplotlib
|
|
15
16
|
Requires-Dist: fastremap
|
|
16
17
|
Requires-Dist: edt
|
|
@@ -24,6 +25,7 @@ Requires-Dist: cmap
|
|
|
24
25
|
Requires-Dist: tqdm
|
|
25
26
|
Requires-Dist: platformdirs
|
|
26
27
|
Requires-Dist: cryptography
|
|
28
|
+
Requires-Dist: lxml
|
|
27
29
|
Provides-Extra: viewer
|
|
28
30
|
Requires-Dist: fastapi; extra == "viewer"
|
|
29
31
|
Requires-Dist: uvicorn[standard]; extra == "viewer"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="114" height="20" role="img" aria-label="coverage:
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="114" height="20" role="img" aria-label="coverage: 44.55%"><title>coverage: 44.55%</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">44.55%</text><text x="865" y="140" transform="scale(.1)" fill="#fff" textLength="430">44.55%</text></g></svg>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="68" height="20" role="img" aria-label="tests:
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="68" height="20" role="img" aria-label="tests: 408"><title>tests: 408</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">408</text><text x="515" y="140" transform="scale(.1)" fill="#fff" textLength="210">408</text></g></svg>
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
# Plot backend roadmap
|
|
2
2
|
|
|
3
3
|
**Status:** Design discussion / pending decision.
|
|
4
|
-
**
|
|
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.
|
|
4
|
+
**Driver:** matplotlib accounts for ~80% of warm-call time in a typical spectra-plotting call (~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
5
|
|
|
7
6
|
## Context
|
|
8
7
|
|
|
9
|
-
`ocdkit.plot` and
|
|
8
|
+
`ocdkit.plot` and a downstream plot package together own ~10 K LOC of plotting code:
|
|
10
9
|
|
|
11
10
|
| Package | LOC |
|
|
12
11
|
|---|---|
|
|
13
12
|
| `ocdkit.plot` (figure, grid, label, color, defaults, display, contour, export) | 1,935 |
|
|
14
|
-
|
|
|
15
|
-
|
|
|
13
|
+
| Downstream non-WGPU plot code (datashade, cell, text, barcode, line, background, …) | 4,641 |
|
|
14
|
+
| Downstream WGPU plot code (lines, scatter, aggregators — already custom GPU primitives) | 4,783 |
|
|
16
15
|
|
|
17
16
|
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
17
|
|
|
@@ -29,7 +28,7 @@ Shallow-cloned each candidate, counted Python LOC excluding tests, sphinx, sampl
|
|
|
29
28
|
| plotly | 66 MB | 13 K relevant (+ ~600 K autogen) | JS bundle as data | Most LOC is auto-generated trait validators |
|
|
30
29
|
| datoviz | 137 MB | 8 K Py wrapper | 140 K C++ Vulkan engine | Heaviest binary footprint, fastest renderer |
|
|
31
30
|
| 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
|
|
31
|
+
| pygfx | 84 MB | 20 K | None — uses `wgpu-py` | Same backend ocdkit already uses |
|
|
33
32
|
|
|
34
33
|
## Tradeoff summary
|
|
35
34
|
|
|
@@ -63,20 +62,20 @@ If pygfx covers ≥80% of our needs at ≥80% of the visual quality, **adopt pyg
|
|
|
63
62
|
|
|
64
63
|
### Step 2 (only if pygfx is insufficient): roll our own
|
|
65
64
|
|
|
66
|
-
Estimated scope, building on existing
|
|
65
|
+
Estimated scope, building on the existing WGPU line rasterizer:
|
|
67
66
|
|
|
68
67
|
| Module | LOC est. | Notes |
|
|
69
68
|
|---|---|---|
|
|
70
69
|
| `ocdkit.plot.figure_v2` | 400 | New `Figure` class owning the wgpu canvas + axes layout; coexists with current matplotlib `figure()` so migration is piecewise |
|
|
71
70
|
| `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
|
-
| `
|
|
71
|
+
| `ocdkit.plot.wgpu.text` | 600 | FreeType-rendered glyph atlas + WGSL textured-quad shader. The one piece of genuinely new rasterization work |
|
|
73
72
|
| `ocdkit.plot.legend` | 200 | Boxed layout: text + marker swatches |
|
|
74
73
|
| `ocdkit.plot.hover` | 300 | Mouse → data-coord lookup → DOM/Jupyter tooltip overlay. Generalize the classification-debugger pattern |
|
|
75
74
|
| `ocdkit.plot.export` | 400 | PNG (numpy→PIL), SVG (string templates), maybe PDF (skip if PIL→PNG covers science exports) |
|
|
76
75
|
| 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
76
|
| **Total** | **~3.3 K** | New code, all in our control |
|
|
78
77
|
|
|
79
|
-
Plus: deletion of matplotlib-specific code paths in
|
|
78
|
+
Plus: deletion of matplotlib-specific code paths in the downstream plot package (~1 K LOC saved) and removal of matplotlib runtime dep.
|
|
80
79
|
|
|
81
80
|
### Why not pygfx + own axes?
|
|
82
81
|
|
|
@@ -91,6 +90,6 @@ A hybrid path is also viable: use pygfx for the rasterization layer (lines/text/
|
|
|
91
90
|
|
|
92
91
|
## Concrete next action
|
|
93
92
|
|
|
94
|
-
Spike `plot_spectra_pygfx`
|
|
93
|
+
Spike `plot_spectra_pygfx` against a representative spectra notebook (e.g. `scope.mixed_spectra[-1]` from any existing demo). Compare visual output side-by-side with `plot_spectra_wgpu` and `plot_spectra_cpu`. Decide pygfx-vs-roll-own from that single comparison.
|
|
95
94
|
|
|
96
95
|
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.
|
|
@@ -19,6 +19,10 @@ dependencies = [
|
|
|
19
19
|
"scikit-image>=0.26",
|
|
20
20
|
"tifffile",
|
|
21
21
|
"imagecodecs",
|
|
22
|
+
# opencodecs.uhdr.read_thumbnail_bytes (≥0.1.12) is the fast path
|
|
23
|
+
# in resolve_uhdr_thumb_bytes; ≥0.1.10 has the _fast_log2 fix
|
|
24
|
+
# required for correct gain-map encoding via encode_native.
|
|
25
|
+
"opencodecs>=0.1.12",
|
|
22
26
|
"matplotlib",
|
|
23
27
|
"fastremap",
|
|
24
28
|
"edt",
|
|
@@ -32,6 +36,7 @@ dependencies = [
|
|
|
32
36
|
"tqdm",
|
|
33
37
|
"platformdirs",
|
|
34
38
|
"cryptography",
|
|
39
|
+
"lxml",
|
|
35
40
|
]
|
|
36
41
|
|
|
37
42
|
[project.optional-dependencies]
|
|
@@ -82,11 +87,7 @@ testpaths = ["tests"]
|
|
|
82
87
|
source = ["ocdkit"]
|
|
83
88
|
|
|
84
89
|
[tool.coverage.paths]
|
|
85
|
-
source = [
|
|
86
|
-
"src/ocdkit",
|
|
87
|
-
"/Volumes/DataDrive/ocdkit/src/ocdkit",
|
|
88
|
-
"/home/kcutler/DataDrive/ocdkit/src/ocdkit",
|
|
89
|
-
]
|
|
90
|
+
source = ["src/ocdkit"]
|
|
90
91
|
|
|
91
92
|
[tool.coverage.report]
|
|
92
93
|
show_missing = true
|
|
@@ -24,8 +24,7 @@ import numpy as np
|
|
|
24
24
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
25
25
|
|
|
26
26
|
from bench_vector_contours import vector_contours_fast
|
|
27
|
-
from
|
|
28
|
-
from ocdkit.plot.contour import vector_contours
|
|
27
|
+
from ocdkit.plot.contour import vector_contours, vector_contours_marching
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
def make_mask():
|
|
@@ -73,7 +72,7 @@ def main():
|
|
|
73
72
|
color='red', linewidth=1.5)
|
|
74
73
|
|
|
75
74
|
fig.tight_layout()
|
|
76
|
-
out = Path('
|
|
75
|
+
out = Path(__file__).resolve().parent / 'bench_contour_alignment.png'
|
|
77
76
|
fig.savefig(out, dpi=150, bbox_inches='tight')
|
|
78
77
|
print(f"saved: {out}")
|
|
79
78
|
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Demo: HDR-lifted colormaps through the css-img / SVG image_grid pipe.
|
|
2
|
+
|
|
3
|
+
Renders a grayscale gradient with viridis / magma / inferno / rdbu in:
|
|
4
|
+
- SDR (uint8 P3, current default behavior)
|
|
5
|
+
- HDR (float linear-P3 with values > 1.0, peak ≈ 1600 nits)
|
|
6
|
+
|
|
7
|
+
Saves figures/hdr_cmap_demo.svg — open in Safari to see HDR on an EDR
|
|
8
|
+
display (Chrome on macOS works too). Also prints the lifted LUTs'
|
|
9
|
+
out-of-SDR-range fractions so you can confirm the lift actually used
|
|
10
|
+
the headroom.
|
|
11
|
+
"""
|
|
12
|
+
import sys
|
|
13
|
+
import time
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
import matplotlib.pyplot as plt
|
|
19
|
+
plt.rcParams.update({
|
|
20
|
+
'figure.facecolor': 'none',
|
|
21
|
+
'axes.facecolor': 'none',
|
|
22
|
+
'savefig.facecolor': 'none',
|
|
23
|
+
'axes.edgecolor': 'gray',
|
|
24
|
+
'axes.labelcolor': 'gray',
|
|
25
|
+
'xtick.color': 'gray',
|
|
26
|
+
'ytick.color': 'gray',
|
|
27
|
+
'text.color': 'gray',
|
|
28
|
+
'axes.titlecolor': 'gray',
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
REPO = Path(__file__).resolve().parents[1]
|
|
32
|
+
sys.path.insert(0, str(REPO / 'src'))
|
|
33
|
+
|
|
34
|
+
from ocdkit.plot.hdr_cmap import ( # noqa: E402
|
|
35
|
+
make_hdr_cmap_lut, apply_hdr_cmap,
|
|
36
|
+
SDR_WHITE_NITS, HDR_PEAK_NITS_DEFAULT,
|
|
37
|
+
)
|
|
38
|
+
from ocdkit.plot import imshow # noqa: E402
|
|
39
|
+
|
|
40
|
+
# linear Display-P3 → Y (relative luminance)
|
|
41
|
+
P3_Y_WEIGHTS = np.array([0.2289745641, 0.6917385218, 0.0792869141])
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def lut_stats(name):
|
|
45
|
+
sdr = make_hdr_cmap_lut(name, hdr_jz=0.155,
|
|
46
|
+
hdr_peak_nits=SDR_WHITE_NITS) # SDR baseline
|
|
47
|
+
hdr = make_hdr_cmap_lut(name, hdr_peak_nits=HDR_PEAK_NITS_DEFAULT)
|
|
48
|
+
# both LUTs are linear-P3 normalized so 1.0 ≡ their respective peak.
|
|
49
|
+
# Convert to absolute nits via Y-weighted sum × peak-nits.
|
|
50
|
+
peak_sdr_nits = float((sdr @ P3_Y_WEIGHTS).max() * SDR_WHITE_NITS)
|
|
51
|
+
peak_hdr_nits = float((hdr @ P3_Y_WEIGHTS).max() * HDR_PEAK_NITS_DEFAULT)
|
|
52
|
+
print(
|
|
53
|
+
f" {name:12s} "
|
|
54
|
+
f"SDR peak Y={peak_sdr_nits:6.1f} nits "
|
|
55
|
+
f"HDR peak Y={peak_hdr_nits:6.1f} nits "
|
|
56
|
+
f"lift {peak_hdr_nits / max(peak_sdr_nits, 1e-3):4.2f}x"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def main():
|
|
61
|
+
print("Lift stats (peak_mult=7.88 ≈ 1600 nits):")
|
|
62
|
+
for name in ('viridis', 'magma', 'inferno', 'plasma',
|
|
63
|
+
'cividis', 'RdBu', 'twilight'):
|
|
64
|
+
lut_stats(name)
|
|
65
|
+
|
|
66
|
+
H, W = 64, 1024
|
|
67
|
+
grad = np.tile(np.linspace(0, 1, W, dtype=np.float32), (H, 1))
|
|
68
|
+
|
|
69
|
+
figures_dir = REPO / 'figures'
|
|
70
|
+
figures_dir.mkdir(exist_ok=True)
|
|
71
|
+
|
|
72
|
+
print("\nFull pipeline (HDR + SDR side by side):")
|
|
73
|
+
cmaps = ['viridis', 'magma', 'inferno', 'plasma', 'cividis']
|
|
74
|
+
for name in cmaps:
|
|
75
|
+
t0 = time.perf_counter()
|
|
76
|
+
rgb_hdr = apply_hdr_cmap(grad, name)
|
|
77
|
+
dt = (time.perf_counter() - t0) * 1000
|
|
78
|
+
peak_nits = float((rgb_hdr @ P3_Y_WEIGHTS).max() * HDR_PEAK_NITS_DEFAULT)
|
|
79
|
+
print(f" apply_hdr_cmap({name}) {dt:6.1f} ms "
|
|
80
|
+
f"linear-P3 max={float(rgb_hdr.max()):.3f} "
|
|
81
|
+
f"peak Y={peak_nits:6.1f} nits dtype={rgb_hdr.dtype}")
|
|
82
|
+
|
|
83
|
+
# Two routes through the new `cmap=` kwarg on imshow:
|
|
84
|
+
out_hdr = figures_dir / 'hdr_cmap_demo_hdr.svg'
|
|
85
|
+
out_sdr = figures_dir / 'hdr_cmap_demo_sdr.svg'
|
|
86
|
+
|
|
87
|
+
# HDR route: pre-applied float linear-P3 tiles. (imshow's per-call
|
|
88
|
+
# cmap kwarg applies one cmap to all 2D items, so to show per-cmap
|
|
89
|
+
# tiles in a single grid we apply manually here.)
|
|
90
|
+
hdr_tiles = [apply_hdr_cmap(grad, c) for c in cmaps]
|
|
91
|
+
fig_hdr = imshow(hdr_tiles, figsize=2, titles=cmaps)
|
|
92
|
+
out_hdr.write_text(fig_hdr._inner.to_string())
|
|
93
|
+
print(f"\nSaved HDR demo: {out_hdr}")
|
|
94
|
+
|
|
95
|
+
# SDR baseline through the same `cmap=` kwarg, using hdr=False.
|
|
96
|
+
from cmap import Colormap
|
|
97
|
+
sdr_tiles = []
|
|
98
|
+
for c in cmaps:
|
|
99
|
+
cm = Colormap(c)
|
|
100
|
+
rgba = np.asarray(cm(grad))
|
|
101
|
+
sdr_tiles.append((rgba[..., :3] * 255).astype(np.uint8))
|
|
102
|
+
fig_sdr = imshow(sdr_tiles, figsize=2, titles=cmaps)
|
|
103
|
+
out_sdr.write_text(fig_sdr._inner.to_string())
|
|
104
|
+
print(f"Saved SDR demo: {out_sdr}")
|
|
105
|
+
|
|
106
|
+
# And one figure exercising the new imshow(cmap=, hdr=True) wiring
|
|
107
|
+
# directly on a single 2D array.
|
|
108
|
+
out_kw = figures_dir / 'hdr_cmap_demo_kwarg.svg'
|
|
109
|
+
fig_kw = imshow(grad, cmap='viridis', hdr=True, figsize=4,
|
|
110
|
+
titles='imshow(grad, cmap="viridis", hdr=True)')
|
|
111
|
+
out_kw.write_text(fig_kw._inner.to_string())
|
|
112
|
+
print(f"Saved kwarg demo: {out_kw}")
|
|
113
|
+
|
|
114
|
+
# ─────────────────────────────────────────────────────────────────
|
|
115
|
+
# SDR-fallback A/B test
|
|
116
|
+
# ─────────────────────────────────────────────────────────────────
|
|
117
|
+
# LEFT tile : jxl-p3, uint8 sRGB-curve viridis (native SDR cmap).
|
|
118
|
+
# RIGHT tile : Ultra-HDR JPEG (apply_hdr_cmap → linear-P3 float →
|
|
119
|
+
# libuhdr base + gain map). The SDR base layer of the
|
|
120
|
+
# UHDR JPEG is the *native non-lifted cmap* (via
|
|
121
|
+
# HdrCmapArray._sdr_base_p3_u8), not libuhdr's
|
|
122
|
+
# auto-tone-map — which used to desaturate bright
|
|
123
|
+
# stops (the original reason for switching off PQ-JXL).
|
|
124
|
+
#
|
|
125
|
+
# Expected on HDR display: RIGHT tile glows (~600 nits peak).
|
|
126
|
+
# Expected on SDR display (or HDR display toggled off): LEFT and
|
|
127
|
+
# RIGHT should look pixel-identical — both are the same SDR cmap.
|
|
128
|
+
bumps = np.tile(np.linspace(0, 1, 1024, dtype=np.float32), (192, 1))
|
|
129
|
+
out_sdr_only = figures_dir / 'hdr_cmap_compare_sdr.svg'
|
|
130
|
+
out_hdr_only = figures_dir / 'hdr_cmap_compare_hdr.svg'
|
|
131
|
+
|
|
132
|
+
fig_sdr_only = imshow(bumps, cmap='viridis', hdr=False, figsize=6,
|
|
133
|
+
titles='viridis · jxl-p3 (native SDR cmap)')
|
|
134
|
+
out_sdr_only.write_text(fig_sdr_only._inner.to_string())
|
|
135
|
+
|
|
136
|
+
fig_hdr_only = imshow(bumps, cmap='viridis', hdr=True, figsize=6,
|
|
137
|
+
titles='viridis · UHDR (HDR-lifted, SDR base = native cmap)')
|
|
138
|
+
out_hdr_only.write_text(fig_hdr_only._inner.to_string())
|
|
139
|
+
|
|
140
|
+
out_html = figures_dir / 'hdr_cmap_compare.html'
|
|
141
|
+
out_html.write_text(f"""<!doctype html>
|
|
142
|
+
<html><head><meta charset="utf-8">
|
|
143
|
+
<title>HDR vs SDR cmap A/B</title>
|
|
144
|
+
<style>
|
|
145
|
+
body {{
|
|
146
|
+
background: #111; color: #ccc; font-family: -apple-system, sans-serif;
|
|
147
|
+
margin: 20px; line-height: 1.45;
|
|
148
|
+
}}
|
|
149
|
+
.row {{ display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }}
|
|
150
|
+
.col {{ flex: 1 1 480px; min-width: 380px; }}
|
|
151
|
+
h2 {{ font-size: 14px; color: #888; margin: 6px 0; font-weight: normal; }}
|
|
152
|
+
p {{ font-size: 13px; color: #888; max-width: 80ch; }}
|
|
153
|
+
code {{ color: #ddc; }}
|
|
154
|
+
</style></head>
|
|
155
|
+
<body>
|
|
156
|
+
<h1 style="font-size:16px;color:#aaa">HDR vs SDR cmap A/B (Ultra-HDR + native SDR base)</h1>
|
|
157
|
+
<p>Toggle display HDR (macOS: System Settings → Displays → "High Dynamic
|
|
158
|
+
Range"). On HDR the right tile glows. On SDR the two should be
|
|
159
|
+
pixel-identical — the UHDR's SDR base layer is the same native viridis
|
|
160
|
+
the left tile uses.</p>
|
|
161
|
+
<div class="row">
|
|
162
|
+
<div class="col"><h2>SDR baseline (jxl-p3, uint8 viridis)</h2>
|
|
163
|
+
<object data="hdr_cmap_compare_sdr.svg" type="image/svg+xml"
|
|
164
|
+
style="width:100%"></object>
|
|
165
|
+
</div>
|
|
166
|
+
<div class="col"><h2>HDR UHDR (apply_hdr_cmap viridis)</h2>
|
|
167
|
+
<object data="hdr_cmap_compare_hdr.svg" type="image/svg+xml"
|
|
168
|
+
style="width:100%"></object>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</body></html>""")
|
|
172
|
+
print(f"Saved A/B page: {out_html}")
|
|
173
|
+
print(f" SDR JXL: {out_sdr_only}")
|
|
174
|
+
print(f" HDR UHDR: {out_hdr_only}")
|
|
175
|
+
print(" Open the HTML in Safari, then toggle Displays → HDR off.")
|
|
176
|
+
print(" Tiles should now look identical in SDR mode; HDR mode the")
|
|
177
|
+
print(" right tile gains brightness from the gain map.")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
if __name__ == '__main__':
|
|
181
|
+
main()
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
"""Benchmark current vector_contours pipeline vs a vectorized prototype.
|
|
2
2
|
|
|
3
|
-
Loads
|
|
3
|
+
Loads a label-mask image and renders both the current
|
|
4
4
|
ocdkit.plot.contour.vector_contours output and a prototype implementation
|
|
5
5
|
side by side, with timings.
|
|
6
6
|
|
|
7
|
+
Set ``OCDKIT_BENCH_MASK`` to the path of a label-mask PNG before running.
|
|
8
|
+
|
|
7
9
|
Run:
|
|
8
|
-
python scripts/bench_vector_contours.py
|
|
10
|
+
OCDKIT_BENCH_MASK=/path/to/labels.png python scripts/bench_vector_contours.py
|
|
9
11
|
"""
|
|
10
12
|
|
|
11
13
|
from __future__ import annotations
|
|
12
14
|
|
|
15
|
+
import os
|
|
13
16
|
import time
|
|
14
17
|
from pathlib import Path
|
|
15
18
|
|
|
@@ -245,7 +248,13 @@ def _time_call(fn, repeat=3):
|
|
|
245
248
|
|
|
246
249
|
|
|
247
250
|
def main():
|
|
248
|
-
|
|
251
|
+
mask_path = os.environ.get('OCDKIT_BENCH_MASK')
|
|
252
|
+
if not mask_path:
|
|
253
|
+
raise SystemExit(
|
|
254
|
+
"Set OCDKIT_BENCH_MASK to the path of a label-mask PNG "
|
|
255
|
+
"(e.g. export OCDKIT_BENCH_MASK=/path/to/labels.png)."
|
|
256
|
+
)
|
|
257
|
+
mask = skimage.io.imread(mask_path)
|
|
249
258
|
print(f"mask shape={mask.shape} n_labels={len(np.unique(mask)) - 1}")
|
|
250
259
|
|
|
251
260
|
def run_current():
|
|
@@ -302,7 +311,7 @@ def main():
|
|
|
302
311
|
axes[1, 1].set_title("zoomed crop", fontsize=10)
|
|
303
312
|
fig.tight_layout()
|
|
304
313
|
|
|
305
|
-
out = Path('
|
|
314
|
+
out = Path(__file__).resolve().parent / 'bench_vector_contours.png'
|
|
306
315
|
fig.savefig(out, dpi=150, bbox_inches='tight')
|
|
307
316
|
print(f"\nsaved: {out}")
|
|
308
317
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Bench: marching-squares vector contour pipeline vs legacy.
|
|
2
|
+
|
|
3
|
+
The library implementation now lives in ``ocdkit.plot.contour``
|
|
4
|
+
(``vector_contours_marching`` / ``cells_to_polygons`` / ``cells_to_webgpu_mesh``)
|
|
5
|
+
-- this file is just a bench runner that compares it against the legacy
|
|
6
|
+
``vector_contours`` and the Tier-1 prototype.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import matplotlib.pyplot as plt
|
|
15
|
+
import numpy as np
|
|
16
|
+
import skimage.io
|
|
17
|
+
|
|
18
|
+
from ocdkit.plot.contour import (
|
|
19
|
+
vector_contours,
|
|
20
|
+
vector_contours_marching,
|
|
21
|
+
)
|
|
22
|
+
# Tier 1 prototype kept in scripts/ for comparison only.
|
|
23
|
+
from bench_vector_contours import vector_contours_fast
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _time_call(fn, repeat=3):
|
|
27
|
+
fn() # warm-up (incl numba compile)
|
|
28
|
+
ts = []
|
|
29
|
+
for _ in range(repeat):
|
|
30
|
+
t0 = time.perf_counter(); fn(); ts.append(time.perf_counter() - t0)
|
|
31
|
+
return min(ts), ts
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main():
|
|
35
|
+
mask_path = os.environ.get('OCDKIT_BENCH_MASK')
|
|
36
|
+
if not mask_path:
|
|
37
|
+
raise SystemExit(
|
|
38
|
+
"Set OCDKIT_BENCH_MASK to the path of a label-mask PNG "
|
|
39
|
+
"(e.g. export OCDKIT_BENCH_MASK=/path/to/labels.png)."
|
|
40
|
+
)
|
|
41
|
+
mask = skimage.io.imread(mask_path)
|
|
42
|
+
print(f"mask shape={mask.shape} n_labels={len(np.unique(mask)) - 1}")
|
|
43
|
+
|
|
44
|
+
def run_current():
|
|
45
|
+
fig, ax = plt.subplots(figsize=(5, 5))
|
|
46
|
+
ax.imshow(mask, cmap='gray', interpolation='nearest')
|
|
47
|
+
vector_contours(fig, ax, mask, smooth_factor=5, color='r', linewidth=1.0)
|
|
48
|
+
ax.set_axis_off()
|
|
49
|
+
plt.close(fig)
|
|
50
|
+
|
|
51
|
+
def run_tier1():
|
|
52
|
+
fig, ax = plt.subplots(figsize=(5, 5))
|
|
53
|
+
ax.imshow(mask, cmap='gray', interpolation='nearest')
|
|
54
|
+
vector_contours_fast(fig, ax, mask, smooth_sigma=2.0, color='r', linewidth=1.0)
|
|
55
|
+
ax.set_axis_off()
|
|
56
|
+
plt.close(fig)
|
|
57
|
+
|
|
58
|
+
def run_tier2():
|
|
59
|
+
fig, ax = plt.subplots(figsize=(5, 5))
|
|
60
|
+
ax.imshow(mask, cmap='gray', interpolation='nearest')
|
|
61
|
+
vector_contours_marching(fig, ax, mask, smooth_sigma=2.0, color='r', linewidth=1.0)
|
|
62
|
+
ax.set_axis_off()
|
|
63
|
+
plt.close(fig)
|
|
64
|
+
|
|
65
|
+
print("\nBenchmarking (best of 3) ...")
|
|
66
|
+
cur_best, _ = _time_call(run_current, repeat=3)
|
|
67
|
+
print(f" current : {cur_best*1000:6.1f} ms")
|
|
68
|
+
t1_best, _ = _time_call(run_tier1, repeat=3)
|
|
69
|
+
print(f" tier 1 : {t1_best*1000:6.1f} ms ({cur_best/t1_best:.2f}x)")
|
|
70
|
+
t2_best, _ = _time_call(run_tier2, repeat=3)
|
|
71
|
+
print(f" tier 2 : {t2_best*1000:6.1f} ms ({cur_best/t2_best:.2f}x)")
|
|
72
|
+
|
|
73
|
+
H, W = mask.shape
|
|
74
|
+
crop_y = slice(H // 3, H // 3 + 80)
|
|
75
|
+
crop_x = slice(W // 3, W // 3 + 80)
|
|
76
|
+
|
|
77
|
+
fig, axes = plt.subplots(2, 3, figsize=(15, 11))
|
|
78
|
+
for a in axes[0]:
|
|
79
|
+
a.imshow(mask, cmap='gray', interpolation='nearest')
|
|
80
|
+
a.set_axis_off()
|
|
81
|
+
vector_contours(fig, axes[0, 0], mask, smooth_factor=5, color='r', linewidth=1.0)
|
|
82
|
+
vector_contours_fast(fig, axes[0, 1], mask, smooth_sigma=2.0, color='r', linewidth=1.0)
|
|
83
|
+
vector_contours_marching(fig, axes[0, 2], mask, smooth_sigma=2.0, color='r', linewidth=1.0)
|
|
84
|
+
axes[0, 0].set_title(f"Current vector_contours\n{cur_best*1000:.1f} ms", fontsize=11)
|
|
85
|
+
axes[0, 1].set_title(f"Tier 1 (graph + Gaussian + LineCollection)\n"
|
|
86
|
+
f"{t1_best*1000:.1f} ms ({cur_best/t1_best:.2f}x)", fontsize=11)
|
|
87
|
+
axes[0, 2].set_title(f"Tier 2 (marching squares numba kernel)\n"
|
|
88
|
+
f"{t2_best*1000:.1f} ms ({cur_best/t2_best:.2f}x)", fontsize=11)
|
|
89
|
+
|
|
90
|
+
for a in axes[1]:
|
|
91
|
+
a.imshow(mask[crop_y, crop_x], cmap='gray', interpolation='nearest',
|
|
92
|
+
extent=(crop_x.start, crop_x.stop, crop_y.stop, crop_y.start))
|
|
93
|
+
a.set_xlim(crop_x.start, crop_x.stop)
|
|
94
|
+
a.set_ylim(crop_y.stop, crop_y.start)
|
|
95
|
+
a.set_axis_off()
|
|
96
|
+
a.set_title("zoomed crop", fontsize=10)
|
|
97
|
+
vector_contours(fig, axes[1, 0], mask, smooth_factor=5, color='r', linewidth=1.5)
|
|
98
|
+
vector_contours_fast(fig, axes[1, 1], mask, smooth_sigma=2.0, color='r', linewidth=1.5)
|
|
99
|
+
vector_contours_marching(fig, axes[1, 2], mask, smooth_sigma=2.0, color='r', linewidth=1.5)
|
|
100
|
+
|
|
101
|
+
fig.tight_layout()
|
|
102
|
+
out = Path(__file__).resolve().parent / 'bench_vector_contours_tier2.png'
|
|
103
|
+
fig.savefig(out, dpi=150, bbox_inches='tight')
|
|
104
|
+
print(f"\nsaved: {out}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
if __name__ == '__main__':
|
|
108
|
+
main()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Direct pixel diff between the two A/B SVGs:
|
|
2
|
+
- hdr_cmap_compare_sdr.svg → decode the embedded JXL.
|
|
3
|
+
- hdr_cmap_compare_hdr.svg → decode the UHDR JPEG's SDR base.
|
|
4
|
+
|
|
5
|
+
If the byte diff is below JPEG-q95 noise (~3-5 max, <1 mean), the two
|
|
6
|
+
tiles contain the same SDR pixels, and any visible difference on screen
|
|
7
|
+
is browser/decoder behavior, not our encoder."""
|
|
8
|
+
import base64
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
REPO = Path(__file__).resolve().parents[1]
|
|
16
|
+
sys.path.insert(0, str(REPO / 'src'))
|
|
17
|
+
|
|
18
|
+
import opencodecs
|
|
19
|
+
import opencodecs.uhdr as uhdr
|
|
20
|
+
|
|
21
|
+
figs = REPO / 'figures'
|
|
22
|
+
sdr_svg = (figs / 'hdr_cmap_compare_sdr.svg').read_text()
|
|
23
|
+
hdr_svg = (figs / 'hdr_cmap_compare_hdr.svg').read_text()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def extract_b64(svg, mime):
|
|
27
|
+
m = re.search(fr'data:{re.escape(mime)};base64,([A-Za-z0-9+/=]+)', svg)
|
|
28
|
+
if not m:
|
|
29
|
+
raise SystemExit(f"no {mime} in svg")
|
|
30
|
+
return base64.b64decode(m.group(1))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# SDR tile: plain JXL with display-p3 color tag.
|
|
34
|
+
sdr_jxl_bytes = extract_b64(sdr_svg, 'image/jxl')
|
|
35
|
+
sdr_tile_u8 = opencodecs.read(sdr_jxl_bytes)
|
|
36
|
+
if sdr_tile_u8.shape[-1] == 4:
|
|
37
|
+
sdr_tile_u8 = sdr_tile_u8[..., :3]
|
|
38
|
+
print(f"SDR tile JXL → {sdr_tile_u8.shape} {sdr_tile_u8.dtype}")
|
|
39
|
+
|
|
40
|
+
# HDR tile: UHDR JPEG. Pull the raw SDR base out via libuhdr's api-4
|
|
41
|
+
# decode and decode the inner JPEG with imagecodecs.
|
|
42
|
+
hdr_jpeg = extract_b64(hdr_svg, 'image/jpeg')
|
|
43
|
+
parts = uhdr.decode(hdr_jpeg, want_hdr=False, want_gainmap=False, want_base=True)
|
|
44
|
+
base_jpeg = parts['base_compressed']
|
|
45
|
+
sdr_base_u8 = opencodecs.read(base_jpeg)
|
|
46
|
+
if sdr_base_u8.shape[-1] == 4:
|
|
47
|
+
sdr_base_u8 = sdr_base_u8[..., :3]
|
|
48
|
+
print(f"UHDR SDR base → {sdr_base_u8.shape} {sdr_base_u8.dtype}")
|
|
49
|
+
|
|
50
|
+
if sdr_tile_u8.shape != sdr_base_u8.shape:
|
|
51
|
+
print(f"\nSHAPE MISMATCH — can't diff directly")
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
diff = sdr_tile_u8.astype(np.int16) - sdr_base_u8.astype(np.int16)
|
|
55
|
+
print(f"\nPixel-level diff (SDR-JXL vs UHDR-SDR-base):")
|
|
56
|
+
print(f" max abs: {int(np.abs(diff).max())}")
|
|
57
|
+
print(f" mean abs: {float(np.abs(diff).mean()):.2f}")
|
|
58
|
+
print(f" p99 abs: {int(np.percentile(np.abs(diff), 99))}")
|
|
59
|
+
|
|
60
|
+
# Per-row diff to spot banding (which would alternate across rows in
|
|
61
|
+
# a column-gradient image — i.e. the gradient is column-only, all rows
|
|
62
|
+
# identical).
|
|
63
|
+
mid = sdr_tile_u8.shape[0] // 2
|
|
64
|
+
print(f"\nSample row {mid}, first 10 columns:")
|
|
65
|
+
print(f" SDR JXL: {sdr_tile_u8[mid, :10].tolist()}")
|
|
66
|
+
print(f" UHDR base: {sdr_base_u8[mid, :10].tolist()}")
|
|
67
|
+
|
|
68
|
+
# Banding signature: count unique colors per row. Banding from a
|
|
69
|
+
# 256-stop LUT would compress 1024 columns into ≤256 unique values.
|
|
70
|
+
unique_jxl = len(np.unique(sdr_tile_u8[mid].view(np.dtype((np.void,
|
|
71
|
+
sdr_tile_u8[mid].dtype.itemsize * 3)))))
|
|
72
|
+
unique_uhdr = len(np.unique(sdr_base_u8[mid].view(np.dtype((np.void,
|
|
73
|
+
sdr_base_u8[mid].dtype.itemsize * 3)))))
|
|
74
|
+
print(f"\nUnique colors in mid row "
|
|
75
|
+
f"({sdr_tile_u8.shape[1]} cols wide):")
|
|
76
|
+
print(f" SDR JXL: {unique_jxl}")
|
|
77
|
+
print(f" UHDR base: {unique_uhdr}")
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Quick numpy-only sanity check for ocdkit.plot.hdr_cmap.
|
|
2
|
+
|
|
3
|
+
No matplotlib — imports just the colormap-lift math + cmap package.
|
|
4
|
+
Prints peak Y luminance (in nits) for SDR baseline vs HDR-lifted LUT
|
|
5
|
+
to confirm the lift actually pushes brightness up.
|
|
6
|
+
"""
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
REPO = Path(__file__).resolve().parents[1]
|
|
13
|
+
sys.path.insert(0, str(REPO / 'src'))
|
|
14
|
+
|
|
15
|
+
from ocdkit.plot.hdr_cmap import ( # noqa: E402
|
|
16
|
+
make_hdr_cmap_lut,
|
|
17
|
+
SDR_WHITE_NITS, HDR_PEAK_NITS_DEFAULT,
|
|
18
|
+
_XYZ_FROM_SRGB, _P3_FROM_XYZ, _srgb_to_linear,
|
|
19
|
+
)
|
|
20
|
+
from cmap import Colormap # noqa: E402
|
|
21
|
+
|
|
22
|
+
# Linear Display-P3 → relative Y (BT.2020-ish weights for P3)
|
|
23
|
+
P3_Y_WEIGHTS = np.array([0.2289745641, 0.6917385218, 0.0792869141])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def naive_sdr_lut(name, hdr_peak_nits=HDR_PEAK_NITS_DEFAULT, n=256):
|
|
27
|
+
"""Original SDR cmap converted to linear-P3, normalized to encoder peak.
|
|
28
|
+
No JzAzBz lift — yardstick for what the lifted LUT improves on."""
|
|
29
|
+
cm = Colormap(name)
|
|
30
|
+
rgba = np.asarray(cm(np.linspace(0, 1, n)))
|
|
31
|
+
lin_srgb = _srgb_to_linear(rgba[:, :3])
|
|
32
|
+
XYZ_abs = lin_srgb @ _XYZ_FROM_SRGB.T * SDR_WHITE_NITS
|
|
33
|
+
return np.maximum(XYZ_abs @ _P3_FROM_XYZ.T / hdr_peak_nits, 0)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def peak_nits(lut_normed, peak):
|
|
37
|
+
return float((lut_normed @ P3_Y_WEIGHTS).max() * peak)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
names = ['viridis', 'magma', 'inferno', 'plasma', 'cividis',
|
|
41
|
+
'RdBu', 'twilight']
|
|
42
|
+
print(f"{'cmap':12s} {'SDR peak':>10s} {'HDR peak':>10s} lift "
|
|
43
|
+
f"max linear-P3")
|
|
44
|
+
for nm in names:
|
|
45
|
+
sdr = naive_sdr_lut(nm)
|
|
46
|
+
hdr = make_hdr_cmap_lut(nm)
|
|
47
|
+
p_sdr = peak_nits(sdr, HDR_PEAK_NITS_DEFAULT)
|
|
48
|
+
p_hdr = peak_nits(hdr, HDR_PEAK_NITS_DEFAULT)
|
|
49
|
+
print(
|
|
50
|
+
f" {nm:10s} "
|
|
51
|
+
f"{p_sdr:7.1f} nits {p_hdr:7.1f} nits "
|
|
52
|
+
f"{p_hdr / max(p_sdr, 1e-3):4.2f}x "
|
|
53
|
+
f"SDR={float(sdr.max()):.3f} HDR={float(hdr.max()):.3f}"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Quick visual sanity: how the brightest viridis stop differs.
|
|
57
|
+
sdr_v = naive_sdr_lut('viridis')
|
|
58
|
+
hdr_v = make_hdr_cmap_lut('viridis')
|
|
59
|
+
print("\nviridis brightest stop (last index):")
|
|
60
|
+
print(f" SDR lin-P3 RGB = {sdr_v[-1]} ({sdr_v[-1] @ P3_Y_WEIGHTS * HDR_PEAK_NITS_DEFAULT:.1f} nits)")
|
|
61
|
+
print(f" HDR lin-P3 RGB = {hdr_v[-1]} ({hdr_v[-1] @ P3_Y_WEIGHTS * HDR_PEAK_NITS_DEFAULT:.1f} nits)")
|
|
62
|
+
|
|
63
|
+
print("\nDarkest stop (purple):")
|
|
64
|
+
print(f" SDR {sdr_v[0]} ({sdr_v[0] @ P3_Y_WEIGHTS * HDR_PEAK_NITS_DEFAULT:.2f} nits)")
|
|
65
|
+
print(f" HDR {hdr_v[0]} ({hdr_v[0] @ P3_Y_WEIGHTS * HDR_PEAK_NITS_DEFAULT:.2f} nits)")
|