├── .nvmrc ├── style ├── base.css ├── index.js └── index.css ├── pybabylonjs ├── tests │ ├── __init__.py │ ├── test_babylonjs.py │ ├── test_nbextension_path.py │ └── conftest.py ├── _frontend.py ├── __init__.py ├── nbextension │ └── extension.js ├── _version.py ├── babylonjs.py ├── data.py ├── show.py └── args.py ├── .eslintignore ├── src ├── embed.ts ├── version.ts ├── plugin.ts ├── index.ts └── widget.ts ├── .yarnrc.yml ├── .prettierignore ├── pybabylonjs.json ├── css └── widget.css ├── docs ├── source │ ├── examples │ │ ├── introduction.nblink │ │ └── index.rst │ ├── introduction.rst │ ├── _static │ │ ├── helper.js │ │ └── embed-bundle.js.LICENSE.txt │ ├── index.rst │ ├── develop-install.rst │ ├── installing.rst │ └── conf.py ├── environment.yml ├── Makefile └── make.bat ├── .prettierrc ├── webpack.lab.config.js ├── tsconfig.eslint.json ├── .npmignore ├── pytest.ini ├── CHANGELOG.md ├── readthedocs.yml ├── install.json ├── codecov.yml ├── setup.cfg ├── tests ├── tsconfig.json └── src │ ├── index.spec.ts │ └── utils.spec.ts ├── MANIFEST.in ├── amd-public-path.js ├── pyproject.toml ├── tsconfig.json ├── scripts └── publish.sh ├── LICENSE ├── LICENSE.txt ├── .eslintrc.js ├── webpack.config.js ├── .gitignore ├── RELEASE.md ├── examples ├── autzen-streaming.ipynb ├── bristol-streaming.ipynb ├── santorini-streaming.ipynb ├── boulder-slice.ipynb ├── netherland-image.ipynb ├── autzen-slice.ipynb └── point-cloud-parameters.ipynb ├── setup.py ├── package.json ├── azure-pipelines.yaml └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v18 -------------------------------------------------------------------------------- /style/base.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pybabylonjs/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /style/index.css: -------------------------------------------------------------------------------- 1 | @import url('base.css'); 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | **/*.d.ts 5 | tests -------------------------------------------------------------------------------- /src/embed.ts: -------------------------------------------------------------------------------- 1 | export * from './version'; 2 | export * from './widget'; 3 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableImmutableInstalls: false 2 | 3 | nodeLinker: node-modules 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | **/lib 4 | **/package.json 5 | pybabylonjs 6 | -------------------------------------------------------------------------------- /pybabylonjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "load_extensions": { 3 | "pybabylonjs/extension": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /css/widget.css: -------------------------------------------------------------------------------- 1 | #renderCanvas { 2 | width: 100%; 3 | height: 100%; 4 | touch-action: none; 5 | } 6 | -------------------------------------------------------------------------------- /docs/source/examples/introduction.nblink: -------------------------------------------------------------------------------- 1 | { 2 | "path": "../../../examples/introduction.ipynb" 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "arrowParens": "avoid" 5 | } 6 | -------------------------------------------------------------------------------- /webpack.lab.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resolve: { 3 | fallback: { 4 | os: false, 5 | }, 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/**/*.ts", "src/**/*.tsx"], 4 | "exclude": [] 5 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | tests/ 4 | .jshintrc 5 | # Ignore any build output from python: 6 | dist/*.tar.gz 7 | dist/*.wheel 8 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = pybabylonjs/tests examples 3 | norecursedirs = node_modules .ipynb_checkpoints 4 | addopts = --nbval --current-env 5 | -------------------------------------------------------------------------------- /docs/source/introduction.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Introduction 3 | ============= 4 | 5 | .. todo:: 6 | 7 | add prose explaining project purpose and usage here 8 | -------------------------------------------------------------------------------- /docs/source/_static/helper.js: -------------------------------------------------------------------------------- 1 | var cache_require = window.require; 2 | 3 | window.addEventListener('load', function() { 4 | window.require = cache_require; 5 | }); 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | ## v0.2.0 5 | 6 | - Compatible with Jupyterlab v3.0 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /readthedocs.yml: -------------------------------------------------------------------------------- 1 | type: sphinx 2 | python: 3 | version: 3.5 4 | pip_install: true 5 | extra_requirements: 6 | - examples 7 | - docs 8 | conda: 9 | file: docs/environment.yml 10 | -------------------------------------------------------------------------------- /install.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "python", 3 | "packageName": "pybabylonjs", 4 | "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package pybabylonjs" 5 | } 6 | -------------------------------------------------------------------------------- /docs/environment.yml: -------------------------------------------------------------------------------- 1 | 2 | name: pybabylonjs_docs 3 | channels: 4 | - conda-forge 5 | dependencies: 6 | - python=3.* 7 | - nodejs 8 | - jupyter_sphinx 9 | - sphinx 10 | - sphinx_rtd_theme 11 | - nbsphinx 12 | - nbsphinx-link 13 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | # show coverage in CI status, but never consider it a failure 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: 0% 8 | patch: 9 | default: 10 | target: 0% 11 | ignore: 12 | - "pybabylonjs/tests" 13 | -------------------------------------------------------------------------------- /pybabylonjs/tests/test_babylonjs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # Copyright (c) TileDB, Inc.. 5 | # Distributed under the terms of the Modified BSD License. 6 | 7 | import pytest 8 | 9 | from ..pybabylonjs import BabylonJS 10 | 11 | 12 | def test_babylonjs_creation_blank(): 13 | w = BabylonJS() 14 | assert w.value == "Hello World" 15 | -------------------------------------------------------------------------------- /pybabylonjs/_frontend.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # Copyright (c) TileDB, Inc.. 5 | # Distributed under the terms of the Modified BSD License. 6 | from ._version import __npm_version__ 7 | 8 | """ 9 | Information about the frontend package of the widgets. 10 | """ 11 | 12 | module_name = "@tiledb-inc/pybabylonjs" 13 | module_version = __npm_version__ 14 | -------------------------------------------------------------------------------- /pybabylonjs/__init__.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | from .show import BabylonJS, Show, ImageFeatureType 4 | from ._version import __version__ 5 | 6 | HERE = Path(__file__).parent.resolve() 7 | 8 | with (HERE / "labextension" / "package.json").open() as fid: 9 | data = json.load(fid) 10 | 11 | 12 | def _jupyter_labextension_paths(): 13 | return [{"src": "labextension", "dest": data["name"]}] 14 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | 4 | [metadata] 5 | description_file = README.md 6 | long_description_content_type=text/markdown 7 | 8 | [flake8] 9 | ignore=E203, W503 10 | max-line-length=200 11 | 12 | [manifix] 13 | known-excludes = 14 | .git* 15 | .git/**/* 16 | .travis.yml 17 | **/node_modules/**/* 18 | **/__py_cache__/**/* 19 | **/*.pyc 20 | js/package-lock.json 21 | js/lib 22 | build 23 | dist 24 | Untitled*.ipynb -------------------------------------------------------------------------------- /docs/source/examples/index.rst: -------------------------------------------------------------------------------- 1 | 2 | Examples 3 | ======== 4 | 5 | This section contains several examples generated from Jupyter notebooks. 6 | The widgets have been embedded into the page for demonstrative pruposes. 7 | 8 | .. todo:: 9 | 10 | Add links to notebooks in examples folder similar to the initial 11 | one. This is a manual step to ensure only those examples that 12 | are suited for inclusion are used. 13 | 14 | 15 | .. toctree:: 16 | :glob: 17 | 18 | * 19 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "noImplicitAny": true, 5 | "lib": ["dom", "es5", "es2015.promise", "es2015.iterable"], 6 | "noEmitOnError": true, 7 | "strictNullChecks": true, 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "target": "ES5", 11 | "outDir": "build", 12 | "skipLibCheck": true, 13 | "sourceMap": true 14 | }, 15 | "include": [ 16 | "src/*.ts", 17 | "../src/**/*.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /pybabylonjs/nbextension/extension.js: -------------------------------------------------------------------------------- 1 | // Entry point for the notebook bundle containing custom model definitions. 2 | // 3 | define(function() { 4 | "use strict"; 5 | 6 | window['requirejs'].config({ 7 | map: { 8 | '*': { 9 | 'PyBabylonJS': 'nbextensions/pybabylonjs/index', 10 | }, 11 | } 12 | }); 13 | // Export the required load_ipython_extension function 14 | return { 15 | load_ipython_extension : function() {} 16 | }; 17 | }); -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include *.md 3 | include pyproject.toml 4 | 5 | include package.json 6 | include install.json 7 | include ts*.json 8 | include yarn.lock 9 | 10 | graft pybabylonjs/labextension 11 | 12 | # Javascript files 13 | graft src 14 | graft style 15 | prune **/node_modules 16 | prune lib 17 | prune binder 18 | 19 | # Patterns to exclude from any directory 20 | global-exclude *~ 21 | global-exclude *.pyc 22 | global-exclude *.pyo 23 | global-exclude .git 24 | global-exclude .ipynb_checkpoints 25 | -------------------------------------------------------------------------------- /pybabylonjs/tests/test_nbextension_path.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # Copyright (c) TileDB, Inc.. 5 | # Distributed under the terms of the Modified BSD License. 6 | 7 | 8 | def test_nbextension_path(): 9 | # Check that magic function can be imported from package root: 10 | from pybabylonjs import _jupyter_nbextension_paths 11 | 12 | # Ensure that it can be called without incident: 13 | path = _jupyter_nbextension_paths() 14 | # Some sanity checks: 15 | assert len(path) == 1 16 | assert isinstance(path[0], dict) 17 | -------------------------------------------------------------------------------- /amd-public-path.js: -------------------------------------------------------------------------------- 1 | // In an AMD module, we set the public path using the magic requirejs 'module' dependency 2 | // See https://github.com/requirejs/requirejs/wiki/Differences-between-the-simplified-CommonJS-wrapper-and-standard-AMD-define#module 3 | // Since 'module' is a requirejs magic module, we must include 'module' in the webpack externals configuration. 4 | import * as module from 'module'; 5 | const url = new URL(module.uri, document.location); 6 | 7 | url.pathname = url.pathname.slice(0, url.pathname.lastIndexOf('/voila/')); 8 | __webpack_public_path__ = `${url.origin}${url.pathname}/voila/templates/tiledb/static/babylonjs/`; 9 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["jupyter_packaging~=0.10,<2", "jupyterlab~=3.1"] 3 | build-backend = "jupyter_packaging.build_api" 4 | 5 | [tool.jupyter-packaging.options] 6 | skip-if-exists = ["pybabylonjs/labextension/static/style.js"] 7 | ensured-targets = ["pybabylonjs/labextension/static/style.js", "pybabylonjs/labextension/package.json"] 8 | 9 | [tool.jupyter-packaging.builder] 10 | factory = "jupyter_packaging.npm_builder" 11 | 12 | [tool.jupyter-packaging.build-args] 13 | build_cmd = "build:prod" 14 | npm = ["jlpm"] 15 | 16 | [tool.check-manifest] 17 | ignore = ["pybabylonjs/labextension/**", "yarn.lock", ".*", "package-lock.json"] -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) TileDB, Inc. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 5 | // @ts-ignore 6 | // eslint-disable-next-line @typescript-eslint/no-var-requires 7 | const data = require('../package.json'); 8 | 9 | /** 10 | * The _model_module_version/_view_module_version this package implements. 11 | * 12 | * The html widget manager assumes that this is the same as the npm package 13 | * version number. 14 | */ 15 | export const MODULE_VERSION = data.version; 16 | 17 | /* 18 | * The current package name. 19 | */ 20 | export const MODULE_NAME = data.name; 21 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = pybabylonjs 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /pybabylonjs/_version.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | 4 | __all__ = ["__version__"] 5 | 6 | 7 | def _fetchVersion(): 8 | HERE = Path(__file__).parent.resolve() 9 | 10 | for settings in HERE.rglob("package.json"): 11 | try: 12 | with settings.open() as f: 13 | version = json.load(f)["version"] 14 | return version 15 | except FileNotFoundError: 16 | pass 17 | 18 | raise FileNotFoundError(f"Could not find package.json under dir {HERE!s}") 19 | 20 | 21 | __npm_version__ = _fetchVersion() 22 | 23 | __version__ = ( 24 | __npm_version__.replace("-alpha.", "a").replace("-beta.", "b").replace("-rc.", "rc") 25 | ) 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "composite": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "incremental": true, 8 | "jsx": "react", 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "noEmitOnError": true, 12 | "noImplicitAny": true, 13 | "noUnusedLocals": true, 14 | "preserveWatchOutput": true, 15 | "resolveJsonModule": true, 16 | "outDir": "lib", 17 | "rootDir": "src", 18 | "strict": true, 19 | "strictNullChecks": true, 20 | "skipLibCheck": true, 21 | "target": "es2022", 22 | "types": [] 23 | }, 24 | "include": ["src/*"], 25 | "exclude": ["src/**/__tests__"] 26 | } 27 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | 2 | pybabylonjs 3 | ===================================== 4 | 5 | Version: |release| 6 | 7 | BabylonJS widget 8 | 9 | 10 | Quickstart 11 | ---------- 12 | 13 | To get started with pybabylonjs, install with pip:: 14 | 15 | pip install pybabylonjs 16 | 17 | or with conda:: 18 | 19 | conda install pybabylonjs 20 | 21 | 22 | Contents 23 | -------- 24 | 25 | .. toctree:: 26 | :maxdepth: 2 27 | :caption: Installation and usage 28 | 29 | installing 30 | introduction 31 | 32 | .. toctree:: 33 | :maxdepth: 1 34 | 35 | examples/index 36 | 37 | 38 | .. toctree:: 39 | :maxdepth: 2 40 | :caption: Development 41 | 42 | develop-install 43 | 44 | 45 | .. links 46 | 47 | .. _`Jupyter widgets`: https://jupyter.org/widgets.html 48 | 49 | .. _`notebook`: https://jupyter-notebook.readthedocs.io/en/latest/ 50 | -------------------------------------------------------------------------------- /docs/source/_static/embed-bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! ***************************************************************************** 2 | Copyright (c) Microsoft Corporation. 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | ***************************************************************************** */ 15 | -------------------------------------------------------------------------------- /src/plugin.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) TileDB, Inc. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { IJupyterWidgetRegistry } from '@jupyter-widgets/base'; 5 | 6 | import * as widgetExports from './widget'; 7 | 8 | import { MODULE_NAME, MODULE_VERSION } from './version'; 9 | 10 | const EXTENSION_ID = 'PyBabylonJS:plugin'; 11 | 12 | /** 13 | * The pybabylonjs plugin. 14 | */ 15 | const pybabylonjsPlugin: any = { 16 | id: EXTENSION_ID, 17 | requires: [IJupyterWidgetRegistry], 18 | activate: activateWidgetExtension, 19 | autoStart: true 20 | }; 21 | 22 | export default pybabylonjsPlugin; 23 | 24 | /** 25 | * Activate the widget extension. 26 | */ 27 | function activateWidgetExtension( 28 | __: any, 29 | registry: IJupyterWidgetRegistry 30 | ): void { 31 | registry.registerWidget({ 32 | name: MODULE_NAME, 33 | version: MODULE_VERSION, 34 | exports: widgetExports 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | set SPHINXPROJ=pybabylonjs 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | JupyterFrontEnd, 3 | JupyterFrontEndPlugin 4 | } from '@jupyterlab/application'; 5 | import { IJupyterWidgetRegistry } from '@jupyter-widgets/base'; 6 | import * as widgetExports from './widget'; 7 | import { MODULE_NAME, MODULE_VERSION } from './version'; 8 | 9 | /** 10 | * Initialization data for the @tiledb-inc/pybabylonjs extension. 11 | */ 12 | const plugin: JupyterFrontEndPlugin = { 13 | id: '@tiledb-inc/pybabylonjs:plugin', 14 | requires: [IJupyterWidgetRegistry as any], 15 | autoStart: true, 16 | activate: activateWidgetExtension 17 | }; 18 | 19 | export default plugin; 20 | 21 | /** 22 | * Activate the widget extension. 23 | */ 24 | function activateWidgetExtension( 25 | __: JupyterFrontEnd, 26 | registry: IJupyterWidgetRegistry 27 | ): void { 28 | registry.registerWidget({ 29 | name: MODULE_NAME, 30 | version: MODULE_VERSION, 31 | exports: widgetExports 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /docs/source/develop-install.rst: -------------------------------------------------------------------------------- 1 | 2 | Developer install 3 | ================= 4 | 5 | 6 | To install a developer version of pybabylonjs, you will first need to clone 7 | the repository:: 8 | 9 | git clone https://github.com/TileDB-Inc/PyBabylonJS 10 | cd PyBabylonJS 11 | 12 | Next, install it with a develop install using pip:: 13 | 14 | pip install -e . 15 | 16 | 17 | If you are planning on working on the JS/frontend code, you should also do 18 | a link installation of the extension:: 19 | 20 | jupyter nbextension install [--sys-prefix / --user / --system] --symlink --py pybabylonjs 21 | 22 | jupyter nbextension enable [--sys-prefix / --user / --system] --py pybabylonjs 23 | 24 | with the `appropriate flag`_. Or, if you are using Jupyterlab:: 25 | 26 | jupyter labextension install . 27 | 28 | 29 | .. links 30 | 31 | .. _`appropriate flag`: https://jupyter-notebook.readthedocs.io/en/stable/extending/frontend_extensions.html#installing-and-enabling-extensions 32 | -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | LATEST_GIT_TAG=$(git describe --tags --abbrev=0) 4 | # Remove "v" from the start of the tag e.g. v1.0.0 -> 1.0.0 5 | LATEST_GIT_TAG="${LATEST_GIT_TAG:1}" 6 | # Update package.json's version field 7 | npm version $LATEST_GIT_TAG --no-commit-hooks --no-git-tag-version 8 | 9 | echo "Updating package.json with version $LATEST_GIT_TAG" 10 | if [[ $LATEST_GIT_TAG == *"beta"* ]]; 11 | then 12 | echo "Publishing beta version $LATEST_GIT_TAG to npm"; 13 | yarn publish --new-version $LATEST_GIT_TAG --no-git-tag-version --no-commit-hooks --access public --tag beta 14 | elif [[ $LATEST_GIT_TAG == *"alpha"* ]]; 15 | then 16 | echo "Publishing alpha version $LATEST_GIT_TAG to npm"; 17 | yarn publish --new-version $LATEST_GIT_TAG --no-git-tag-version --no-commit-hooks --access public --tag alpha 18 | else 19 | echo "Publishing new version $LATEST_GIT_TAG to npm"; 20 | yarn publish --new-version $LATEST_GIT_TAG --no-git-tag-version --no-commit-hooks --access public 21 | fi -------------------------------------------------------------------------------- /tests/src/index.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | // Add any needed widget imports here (or from controls) 5 | // import {} from '@jupyter-widgets/base'; 6 | 7 | import expect = require('expect.js'); 8 | 9 | import { 10 | createTestModel 11 | } from './utils.spec'; 12 | 13 | import { BabylonJSModel } from '../../src'; 14 | 15 | 16 | describe('BabylonJS', () => { 17 | 18 | describe('BabylonJSModel', () => { 19 | 20 | it('should be createable', () => { 21 | let model = createTestModel(BabylonJSModel); 22 | expect(model).to.be.an(BabylonJSModel); 23 | expect(model.get('value')).to.be('Hello World'); 24 | }); 25 | 26 | it('should be createable with a value', () => { 27 | let state = { value: 'Foo Bar!' } 28 | let model = createTestModel(BabylonJSModel, state); 29 | expect(model).to.be.an(BabylonJSModel); 30 | expect(model.get('value')).to.be('Foo Bar!'); 31 | }); 32 | 33 | }); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TileDB, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TileDB, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/source/installing.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _installation: 3 | 4 | Installation 5 | ============ 6 | 7 | 8 | The simplest way to install pybabylonjs is via pip:: 9 | 10 | pip install pybabylonjs 11 | 12 | or via conda:: 13 | 14 | conda install pybabylonjs 15 | 16 | 17 | If you installed via pip, and notebook version < 5.3, you will also have to 18 | install / configure the front-end extension as well. If you are using classic 19 | notebook (as opposed to Jupyterlab), run:: 20 | 21 | jupyter nbextension install [--sys-prefix / --user / --system] --py pybabylonjs 22 | 23 | jupyter nbextension enable [--sys-prefix / --user / --system] --py pybabylonjs 24 | 25 | with the `appropriate flag`_. If you are using Jupyterlab, install the extension 26 | with:: 27 | 28 | jupyter labextension install PyBabylonJS 29 | 30 | If you are installing using conda, these commands should be unnecessary, but If 31 | you need to run them the commands should be the same (just make sure you choose the 32 | `--sys-prefix` flag). 33 | 34 | 35 | .. links 36 | 37 | .. _`appropriate flag`: https://jupyter-notebook.readthedocs.io/en/stable/extending/frontend_extensions.html#installing-and-enabling-extensions 38 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:@typescript-eslint/eslint-recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:prettier/recommended' 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { 10 | project: 'tsconfig.json', 11 | sourceType: 'module' 12 | }, 13 | plugins: ['@typescript-eslint'], 14 | rules: { 15 | '@typescript-eslint/explicit-module-boundary-types': 'off', 16 | '@typescript-eslint/no-this-alias': 'off', 17 | '@typescript-eslint/naming-convention': [ 18 | 'error', 19 | { 20 | 'selector': 'interface', 21 | 'format': ['PascalCase'], 22 | 'custom': { 23 | 'regex': '^I[A-Z]', 24 | 'match': true 25 | } 26 | } 27 | ], 28 | '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], 29 | '@typescript-eslint/no-explicit-any': 'off', 30 | '@typescript-eslint/no-namespace': 'off', 31 | '@typescript-eslint/no-use-before-define': 'off', 32 | '@typescript-eslint/quotes': [ 33 | 'error', 34 | 'single', 35 | { avoidEscape: true, allowTemplateLiterals: false } 36 | ], 37 | curly: ['error', 'all'], 38 | eqeqeq: 'error', 39 | 'prefer-arrow-callback': 'error' 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // Custom webpack rules 4 | const rules = [ 5 | { test: /\.ts$/, loader: 'ts-loader', type: 'javascript/auto' }, 6 | { test: /\.js$/, loader: 'source-map-loader', type: 'javascript/auto' }, 7 | { test: /\.css$/, use: ['style-loader', 'css-loader'], type: 'javascript/auto'} 8 | ]; 9 | 10 | // Packages that shouldn't be bundled but loaded at runtime 11 | const externals = ['@jupyter-widgets/base', 'module']; 12 | 13 | const resolve = { 14 | // Add '.ts' and '.tsx' as resolvable extensions. 15 | extensions: [".webpack.js", ".web.js", ".ts", ".js"], 16 | fallback: { 17 | os: false 18 | } 19 | }; 20 | 21 | module.exports = [ 22 | /** 23 | * Embeddable @tiledb-inc/pybabylonjs bundle 24 | * 25 | * This bundle is almost identical to the notebook extension bundle. The only 26 | * difference is in the configuration of the webpack public path for the 27 | * static assets. 28 | * 29 | * The target bundle is always `dist/index.js`, which is the path required by 30 | * the custom widget embedder. 31 | */ 32 | { 33 | entry: ['./amd-public-path.js', './src/embed.ts'], 34 | output: { 35 | filename: 'index.js', 36 | path: path.resolve(__dirname, 'dist'), 37 | libraryTarget: 'amd', 38 | library: "@tiledb-inc/pybabylonjs", 39 | publicPath: '', // Set in amd-public-path.js 40 | }, 41 | devtool: 'source-map', 42 | module: { 43 | rules: rules 44 | }, 45 | externals, 46 | resolve, 47 | } 48 | ]; 49 | -------------------------------------------------------------------------------- /pybabylonjs/babylonjs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # Copyright 2023 TileDB Inc. 5 | # Licensed under the MIT License. 6 | 7 | """ 8 | BabylonJS Jupyter Widget 9 | """ 10 | import logging 11 | 12 | logger = logging.Logger("logger") 13 | 14 | from ipywidgets import DOMWidget, register 15 | 16 | from traitlets import Dict, Unicode 17 | from ._frontend import module_name, module_version 18 | 19 | 20 | class BabylonBase(DOMWidget): 21 | """Base class for all Babylon derived widgets""" 22 | 23 | _model_module = Unicode(module_name).tag(sync=True) 24 | _model_module_version = Unicode(module_version).tag(sync=True) 25 | _view_module_version = Unicode(module_version).tag(sync=True) 26 | _view_module = Unicode(module_name).tag(sync=True) 27 | 28 | 29 | @register 30 | class BabylonPointCloud(BabylonBase): 31 | """3D point cloud with BabylonJS""" 32 | 33 | _model_name = Unicode("BabylonPointCloudModel").tag(sync=True) 34 | _view_name = Unicode("BabylonPointCloudView").tag(sync=True) 35 | value = Dict().tag(sync=True) 36 | 37 | 38 | @register 39 | class BabylonMBRS(BabylonBase): 40 | """MBRS outlines with BabylonJS""" 41 | 42 | _model_name = Unicode("BabylonMBRSModel").tag(sync=True) 43 | _view_name = Unicode("BabylonMBRSView").tag(sync=True) 44 | value = Dict().tag(sync=True) 45 | 46 | 47 | @register 48 | class BabylonImage(BabylonBase): 49 | """Images with BabylonJS""" 50 | 51 | _model_name = Unicode("BabylonTileImageModel").tag(sync=True) 52 | _view_name = Unicode("BabylonTileImageView").tag(sync=True) 53 | value = Dict().tag(sync=True) 54 | -------------------------------------------------------------------------------- /pybabylonjs/tests/conftest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # Copyright (c) TileDB, Inc.. 5 | # Distributed under the terms of the Modified BSD License. 6 | 7 | import pytest 8 | 9 | from ipykernel.comm import Comm 10 | from ipywidgets import Widget 11 | 12 | 13 | class MockComm(Comm): 14 | """A mock Comm object. 15 | 16 | Can be used to inspect calls to Comm's open/send/close methods. 17 | """ 18 | 19 | comm_id = "a-b-c-d" 20 | kernel = "Truthy" 21 | 22 | def __init__(self, *args, **kwargs): 23 | self.log_open = [] 24 | self.log_send = [] 25 | self.log_close = [] 26 | super(MockComm, self).__init__(*args, **kwargs) 27 | 28 | def open(self, *args, **kwargs): 29 | self.log_open.append((args, kwargs)) 30 | 31 | def send(self, *args, **kwargs): 32 | self.log_send.append((args, kwargs)) 33 | 34 | def close(self, *args, **kwargs): 35 | self.log_close.append((args, kwargs)) 36 | 37 | 38 | _widget_attrs = {} 39 | undefined = object() 40 | 41 | 42 | @pytest.fixture 43 | def mock_comm(): 44 | _widget_attrs["_comm_default"] = getattr(Widget, "_comm_default", undefined) 45 | Widget._comm_default = lambda self: MockComm() 46 | _widget_attrs["_ipython_display_"] = Widget._ipython_display_ 47 | 48 | def raise_not_implemented(*args, **kwargs): 49 | raise NotImplementedError() 50 | 51 | Widget._ipython_display_ = raise_not_implemented 52 | 53 | yield MockComm() 54 | 55 | for attr, value in _widget_attrs.items(): 56 | if value is undefined: 57 | delattr(Widget, attr) 58 | else: 59 | setattr(Widget, attr, value) 60 | -------------------------------------------------------------------------------- /pybabylonjs/data.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 TileDB Inc. 2 | # Licensed under the MIT License. 3 | """Functions to format data from the arrays to be used in the visualization.""" 4 | 5 | import pandas as pd 6 | import tiledb 7 | 8 | 9 | def create_point_cloud(array_uri: str, bbox): 10 | attrs = ["Red", "Green", "Blue"] 11 | 12 | with tiledb.open(array_uri) as arr: 13 | data = arr.query(attrs=attrs, dims=["X", "Y", "Z"])[ 14 | bbox["X"][0] : bbox["X"][1], 15 | bbox["Y"][0] : bbox["Y"][1], 16 | bbox["Z"][0] : bbox["Z"][1], 17 | ] 18 | 19 | return data 20 | 21 | 22 | def create_mbrs(array_uri: str): 23 | """Create a Dict to be passed on to BabylonMBRS to create MBRS outlines.""" 24 | fragments_info = tiledb.array_fragments(array_uri, include_mbrs=True) 25 | 26 | df = pd.DataFrame() 27 | 28 | f = 0 29 | for fragment in fragments_info.mbrs: 30 | f += 1 31 | b = 0 32 | for box in fragment: 33 | b += 1 34 | box_dict = { 35 | "fragment": f, 36 | "box": b, 37 | "xmin": box[0][0], 38 | "xmax": box[0][1], 39 | "ymin": box[1][0], 40 | "ymax": box[1][1], 41 | "zmin": box[2][0], 42 | "zmax": box[2][1], 43 | } 44 | box_df = pd.DataFrame([box_dict]) 45 | df = pd.concat([df, box_df], ignore_index=True) 46 | 47 | data = { 48 | "Xmin": df["xmin"], 49 | "Xmax": df["xmax"], 50 | "Ymin": df["ymin"], 51 | "Ymax": df["ymax"], 52 | "Zmin": df["zmin"], 53 | "Zmax": df["zmax"], 54 | } 55 | 56 | extents = [ 57 | min(df["xmin"].tolist()), 58 | max(df["xmax"].tolist()), 59 | min(df["ymin"].tolist()), 60 | max(df["ymax"].tolist()), 61 | min(df["zmin"].tolist()), 62 | max(df["zmax"].tolist()), 63 | ] 64 | 65 | return dict(extents=extents, data=data) 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle.* 2 | lib/ 3 | docs/ 4 | node_modules/ 5 | *.egg-info/ 6 | .ipynb_checkpoints 7 | *.tsbuildinfo 8 | pybabylonjs/labextension 9 | pybabylonjs/version.py 10 | 11 | # Created by https://www.gitignore.io/api/python 12 | # Edit at https://www.gitignore.io/?templates=python 13 | 14 | ### Python ### 15 | # Byte-compiled / optimized / DLL files 16 | __pycache__/ 17 | *.py[cod] 18 | *$py.class 19 | 20 | # C extensions 21 | *.so 22 | 23 | # Distribution / packaging 24 | .Python 25 | build/ 26 | develop-eggs/ 27 | dist/ 28 | downloads/ 29 | eggs/ 30 | .eggs/ 31 | lib/ 32 | lib64/ 33 | parts/ 34 | sdist/ 35 | var/ 36 | wheels/ 37 | pip-wheel-metadata/ 38 | share/python-wheels/ 39 | .installed.cfg 40 | *.egg 41 | MANIFEST 42 | 43 | # PyInstaller 44 | # Usually these files are written by a python script from a template 45 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 46 | *.manifest 47 | *.spec 48 | 49 | # Installer logs 50 | pip-log.txt 51 | pip-delete-this-directory.txt 52 | 53 | # Unit test / coverage reports 54 | htmlcov/ 55 | .tox/ 56 | .nox/ 57 | .coverage 58 | .coverage.* 59 | .cache 60 | nosetests.xml 61 | coverage.xml 62 | *.cover 63 | .hypothesis/ 64 | .pytest_cache/ 65 | 66 | # Translations 67 | *.mo 68 | *.pot 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # pyenv 80 | .python-version 81 | 82 | # celery beat schedule file 83 | celerybeat-schedule 84 | 85 | # SageMath parsed files 86 | *.sage.py 87 | 88 | # Spyder project settings 89 | .spyderproject 90 | .spyproject 91 | 92 | # Rope project settings 93 | .ropeproject 94 | 95 | # Mr Developer 96 | .mr.developer.cfg 97 | .project 98 | .pydevproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | .dmypy.json 106 | dmypy.json 107 | 108 | # Pyre type checker 109 | .pyre/ 110 | 111 | # End of https://www.gitignore.io/api/python 112 | 113 | # OSX files 114 | .DS_Store 115 | 116 | # Yarn files 117 | .yarn/ -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Making a new release of pybabylonjs 2 | 3 | The extension can be published to `PyPI` and `npm` manually or using the [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser). 4 | 5 | ## Manual release 6 | 7 | ### Python package 8 | 9 | This extension can be distributed as Python 10 | packages. All of the Python 11 | packaging instructions in the `pyproject.toml` file to wrap your extension in a 12 | Python package. Before generating a package, we first need to install `build`. 13 | 14 | ```bash 15 | pip install build twine 16 | ``` 17 | 18 | To create a Python source package (``.tar.gz``) and the binary package (`.whl`) in the `dist/` directory, do: 19 | 20 | ```bash 21 | python -m build 22 | ``` 23 | 24 | > `python setup.py sdist bdist_wheel` is deprecated and will not work for this package. 25 | 26 | Then to upload the package to PyPI, do: 27 | 28 | ```bash 29 | twine upload dist/* 30 | ``` 31 | 32 | ### NPM package 33 | 34 | To publish the frontend part of the extension as a NPM package, do: 35 | 36 | ```bash 37 | npm login 38 | npm publish --access public 39 | ``` 40 | 41 | ## Automated releases with the Jupyter Releaser 42 | 43 | The extension repository should already be compatible with the Jupyter Releaser. 44 | 45 | Check out the [workflow documentation](https://github.com/jupyter-server/jupyter_releaser#typical-workflow) for more information. 46 | 47 | Here is a summary of the steps to cut a new release: 48 | 49 | - Fork the [`jupyter-releaser` repo](https://github.com/jupyter-server/jupyter_releaser) 50 | - Add `ADMIN_GITHUB_TOKEN`, `PYPI_TOKEN` and `NPM_TOKEN` to the Github Secrets in the fork 51 | - Go to the Actions panel 52 | - Run the "Draft Changelog" workflow 53 | - Merge the Changelog PR 54 | - Run the "Draft Release" workflow 55 | - Run the "Publish Release" workflow 56 | 57 | ## Publishing to `conda-forge` 58 | 59 | If the package is not on conda forge yet, check the documentation to learn how to add it: https://conda-forge.org/docs/maintainer/adding_pkgs.html 60 | 61 | Otherwise a bot should pick up the new version publish to PyPI, and open a new PR on the feedstock repository automatically. 62 | -------------------------------------------------------------------------------- /tests/src/utils.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import * as widgets from '@jupyter-widgets/base'; 5 | import * as services from '@jupyterlab/services'; 6 | import * as Backbone from 'backbone'; 7 | 8 | let numComms = 0; 9 | 10 | export 11 | class MockComm { 12 | target_name = 'dummy'; 13 | 14 | constructor() { 15 | this.comm_id = `mock-comm-id-${numComms}`; 16 | numComms += 1; 17 | } 18 | on_close(fn: Function | null) { 19 | this._on_close = fn; 20 | } 21 | on_msg(fn: Function | null) { 22 | this._on_msg = fn; 23 | } 24 | _process_msg(msg: services.KernelMessage.ICommMsgMsg) { 25 | if (this._on_msg) { 26 | return this._on_msg(msg); 27 | } else { 28 | return Promise.resolve(); 29 | } 30 | } 31 | close(): string { 32 | if (this._on_close) { 33 | this._on_close(); 34 | } 35 | return 'dummy'; 36 | } 37 | send(): string { 38 | return 'dummy'; 39 | } 40 | 41 | open(): string { 42 | return 'dummy'; 43 | } 44 | comm_id: string; 45 | _on_msg: Function | null = null; 46 | _on_close: Function | null = null; 47 | } 48 | 49 | export 50 | class DummyManager extends widgets.ManagerBase { 51 | constructor() { 52 | super(); 53 | this.el = window.document.createElement('div'); 54 | } 55 | 56 | display_view(msg: services.KernelMessage.IMessage, view: Backbone.View, options: any) { 57 | // TODO: make this a spy 58 | // TODO: return an html element 59 | return Promise.resolve(view).then(view => { 60 | this.el.appendChild(view.el); 61 | view.on('remove', () => console.log('view removed', view)); 62 | return view.el; 63 | }); 64 | } 65 | 66 | protected loadClass(className: string, moduleName: string, moduleVersion: string): Promise { 67 | if (moduleName === '@jupyter-widgets/base') { 68 | if ((widgets as any)[className]) { 69 | return Promise.resolve((widgets as any)[className]); 70 | } else { 71 | return Promise.reject(`Cannot find class ${className}`) 72 | } 73 | } else if (moduleName === 'jupyter-datawidgets') { 74 | if (this.testClasses[className]) { 75 | return Promise.resolve(this.testClasses[className]); 76 | } else { 77 | return Promise.reject(`Cannot find class ${className}`) 78 | } 79 | } else { 80 | return Promise.reject(`Cannot find module ${moduleName}`); 81 | } 82 | } 83 | 84 | _get_comm_info() { 85 | return Promise.resolve({}); 86 | } 87 | 88 | _create_comm() { 89 | return Promise.resolve(new MockComm()); 90 | } 91 | 92 | el: HTMLElement; 93 | 94 | testClasses: { [key: string]: any } = {}; 95 | } 96 | 97 | 98 | export 99 | interface Constructor { 100 | new (attributes?: any, options?: any): T; 101 | } 102 | 103 | export 104 | function createTestModel(constructor: Constructor, attributes?: any): T { 105 | let id = widgets.uuid(); 106 | let widget_manager = new DummyManager(); 107 | let modelOptions = { 108 | widget_manager: widget_manager, 109 | model_id: id, 110 | } 111 | 112 | return new constructor(attributes, modelOptions); 113 | } -------------------------------------------------------------------------------- /examples/autzen-streaming.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "ece79f11-ab6e-447e-a843-c646d27e9a46", 6 | "metadata": {}, 7 | "source": [ 8 | "# Stream and visualize the Autzen point cloud\n", 9 | "\n", 10 | "The original data used in this notebook can be [found here](https://github.com/PDAL/data/tree/master/autzen) and has a BSD license as [described here](https://pdal.io/en/latest/copyright.html#overall-pdal-license-bsd)." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "34b3f824-b53c-4d9d-b4d2-8e900ff7252f", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from pybabylonjs import Show as show" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "id": "5c4ec8bd-7b69-48f0-b70d-9977e07d2d7d", 26 | "metadata": {}, 27 | "source": [ 28 | "To stream and view point cloud data from a TileDB array a `token` is needed: \n", 29 | "* [sign up for a TileDB account](https://cloud.tiledb.com/auth/signup)\n", 30 | "\n", 31 | "When running this notebook locally:\n", 32 | "* [create a token as described here](https://docs.tiledb.com/cloud/how-to/account/create-api-tokens)\n", 33 | "* uncomment the below cell and add your token (``)\n", 34 | "* run the below cells\n", 35 | "\n", 36 | "When running this notebook on TileDB Cloud:\n", 37 | "* the token will be automatically loaded\n", 38 | "* remove the token from the list of parameters of `show.point_cloud`\n", 39 | "* run the below cells" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "id": "6ce7f8c7-0805-444b-93ad-3b7554de8304", 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "#token = \"\"" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "id": "19e3204d-d1a1-4ab8-a888-5833ed44e41d", 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "show.point_cloud(streaming=True,\n", 60 | " uri=\"tiledb://TileDB-Inc/autzen-classified\",\n", 61 | " token=token,\n", 62 | " point_size = 3,\n", 63 | " wheel_precision = 0.1,\n", 64 | " color_scheme = 'dark',\n", 65 | " width = 1200,\n", 66 | " height = 800, \n", 67 | " rgb_max = 65535,\n", 68 | " point_budget = 3500000,\n", 69 | " camera_location = 8,\n", 70 | " camera_zoom = [1, 1, 4],\n", 71 | " camera_up = 25, \n", 72 | " move_speed = 4,\n", 73 | " point_type = 'fixed_world_size')" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "id": "4df8948e-95cf-44ed-83fe-f6c5fa1271a4", 80 | "metadata": {}, 81 | "outputs": [], 82 | "source": [] 83 | } 84 | ], 85 | "metadata": { 86 | "kernelspec": { 87 | "display_name": "Python 3 (ipykernel)", 88 | "language": "python", 89 | "name": "python3" 90 | }, 91 | "language_info": { 92 | "codemirror_mode": { 93 | "name": "ipython", 94 | "version": 3 95 | }, 96 | "file_extension": ".py", 97 | "mimetype": "text/x-python", 98 | "name": "python", 99 | "nbconvert_exporter": "python", 100 | "pygments_lexer": "ipython3", 101 | "version": "3.7.10" 102 | } 103 | }, 104 | "nbformat": 4, 105 | "nbformat_minor": 5 106 | } 107 | -------------------------------------------------------------------------------- /examples/bristol-streaming.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "fcba3248-ccd2-456e-ae66-bd66f989d6cd", 6 | "metadata": {}, 7 | "source": [ 8 | "# Stream and visualize the Bristol point cloud\n", 9 | "\n", 10 | "This notebook streams and visualizes a point cloud of the area along the river Avon in Bristol (UK). The original data files are from the [National Lidar Programme](https://environment.data.gov.uk/dataset/2e8d0733-4f43-48b4-9e51-631c25d1b0a9)." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "1ca01f82-4282-4262-a016-4fd0ee70a4e9", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import os\n", 21 | "from pybabylonjs import Show as show" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "id": "c5c89a6e-3240-4e56-8c83-b091170be7c9", 27 | "metadata": {}, 28 | "source": [ 29 | "To stream and view point cloud data from a TileDB array a `token` is needed: \n", 30 | "* [sign up for a TileDB account](https://cloud.tiledb.com/auth/signup)\n", 31 | "\n", 32 | "When running this notebook locally:\n", 33 | "* [create a token as described here](https://docs.tiledb.com/cloud/how-to/account/create-api-tokens)\n", 34 | "* uncomment the below cell and add your token (``)\n", 35 | "* run the below cells\n", 36 | "\n", 37 | "When running this notebook on TileDB Cloud:\n", 38 | "* the token will be automatically loaded\n", 39 | "* remove the token from the list of parameters of `show.point_cloud`\n", 40 | "* run the below cells" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "id": "08e4ec2c-c0e6-4569-a30f-546f94c39cee", 47 | "metadata": {}, 48 | "outputs": [], 49 | "source": [ 50 | "#token = \"\"" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "id": "57d50a63-1a36-45c8-a6c1-b13def92cd1c", 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "show.point_cloud(streaming=True,\n", 61 | " uri=\"tiledb://TileDB-Inc/bristol\",\n", 62 | " token=token, \n", 63 | " point_size = 4,\n", 64 | " wheel_precision = 0.2,\n", 65 | " color_scheme = 'dark',\n", 66 | " width = 1200,\n", 67 | " height = 800, \n", 68 | " rgb_max = 255,\n", 69 | " point_budget = 3500000,\n", 70 | " camera_location = 8,\n", 71 | " camera_zoom = [1, 1, 2],\n", 72 | " camera_up = 50, \n", 73 | " move_speed = 8,\n", 74 | " point_type = 'fixed_world_size')" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": null, 80 | "id": "0550018b-6b97-4484-bb42-2924093d4f5b", 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [] 84 | } 85 | ], 86 | "metadata": { 87 | "kernelspec": { 88 | "display_name": "Python 3 (ipykernel)", 89 | "language": "python", 90 | "name": "python3" 91 | }, 92 | "language_info": { 93 | "codemirror_mode": { 94 | "name": "ipython", 95 | "version": 3 96 | }, 97 | "file_extension": ".py", 98 | "mimetype": "text/x-python", 99 | "name": "python", 100 | "nbconvert_exporter": "python", 101 | "pygments_lexer": "ipython3", 102 | "version": "3.7.10" 103 | } 104 | }, 105 | "nbformat": 4, 106 | "nbformat_minor": 5 107 | } 108 | -------------------------------------------------------------------------------- /examples/santorini-streaming.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0bc65c29-e65c-4f30-b7bd-727ad123c78b", 6 | "metadata": {}, 7 | "source": [ 8 | "# Stream and visualize the Santorini point cloud\n", 9 | "\n", 10 | "This notebook streams and visualizes a point cloud of the Kameni Islands, Santorini (Greece). The original data files were [downloaded from here](https://figshare.com/articles/dataset/2012_Santorini_LiDAR_data/1138718) and more details are in [this paper](https://www.sciencedirect.com/science/article/pii/S2214242814000047)." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "b80510a7-8c5c-4df1-851a-5e0a830280da", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from pybabylonjs import Show as show" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "id": "a1edabed-77d9-4729-b54b-0a8991be192d", 26 | "metadata": {}, 27 | "source": [ 28 | "To stream and view point cloud data from a TileDB array a `token` is needed: \n", 29 | "* [sign up for a TileDB account](https://cloud.tiledb.com/auth/signup)\n", 30 | "\n", 31 | "When running this notebook locally:\n", 32 | "* [create a token as described here](https://docs.tiledb.com/cloud/how-to/account/create-api-tokens)\n", 33 | "* uncomment the below cell and add your token (``)\n", 34 | "* run the below cells\n", 35 | "\n", 36 | "When running this notebook on TileDB Cloud:\n", 37 | "* the token will be automatically loaded\n", 38 | "* remove the token from the list of parameters of `show.point_cloud`\n", 39 | "* run the below cells" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "id": "c2b9f363-f5cb-44f7-b37a-eef820d31386", 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "#token = \"\"" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "id": "59f6b2c2-43cc-447b-8d25-b641626445c8", 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "show.point_cloud(streaming=True,\n", 60 | " uri=\"tiledb://TileDB-Inc/santorini\",\n", 61 | " token=token,\n", 62 | " point_size = 6,\n", 63 | " wheel_precision = 0.2,\n", 64 | " color_scheme = 'dark',\n", 65 | " width = 1200,\n", 66 | " height = 800, \n", 67 | " rgb_max = 255,\n", 68 | " point_budget = 3000000,\n", 69 | " camera_location = 5,\n", 70 | " camera_zoom = [1, 1, 4],\n", 71 | " camera_up = 200, \n", 72 | " move_speed = 6,\n", 73 | " point_type = 'fixed_world_size')" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "id": "26fb01d4-8362-4a98-b835-b7f1a550c870", 80 | "metadata": {}, 81 | "outputs": [], 82 | "source": [] 83 | } 84 | ], 85 | "metadata": { 86 | "kernelspec": { 87 | "display_name": "Python 3 (ipykernel)", 88 | "language": "python", 89 | "name": "python3" 90 | }, 91 | "language_info": { 92 | "codemirror_mode": { 93 | "name": "ipython", 94 | "version": 3 95 | }, 96 | "file_extension": ".py", 97 | "mimetype": "text/x-python", 98 | "name": "python", 99 | "nbconvert_exporter": "python", 100 | "pygments_lexer": "ipython3", 101 | "version": "3.7.10" 102 | } 103 | }, 104 | "nbformat": 4, 105 | "nbformat_minor": 5 106 | } 107 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | pybabylonjs setup 3 | """ 4 | 5 | import json 6 | import sys 7 | from pathlib import Path 8 | 9 | import setuptools 10 | 11 | HERE = Path(__file__).parent.resolve() 12 | 13 | # The name of the project 14 | name = "pybabylonjs" 15 | 16 | lab_path = HERE / name.replace("-", "_") / "labextension" 17 | 18 | # Representative files that should exist after a successful build 19 | ensured_targets = [str(lab_path / "package.json"), str(lab_path / "static/style.js")] 20 | 21 | labext_name = "@tiledb-inc/pybabylonjs" 22 | 23 | data_files_spec = [ 24 | ( 25 | "share/jupyter/labextensions/%s" % labext_name, 26 | str(lab_path.relative_to(HERE)), 27 | "**", 28 | ), 29 | ("share/jupyter/labextensions/%s" % labext_name, str("."), "install.json"), 30 | ] 31 | 32 | long_description = (HERE / "README.md").read_text() 33 | 34 | # Get the package info from package.json 35 | pkg_json = json.loads((HERE / "package.json").read_bytes()) 36 | 37 | version = ( 38 | pkg_json["version"] 39 | .replace("-alpha.", "a") 40 | .replace("-beta.", "b") 41 | .replace("-rc.", "rc") 42 | ) 43 | 44 | setup_args = dict( 45 | name=name, 46 | version=version, 47 | setup_requires=[ 48 | "setuptools-scm>=1.5.4", 49 | "setuptools-scm-git-archive", 50 | ], 51 | extras_require={ 52 | "dev": [ 53 | "matplotlib", 54 | "python-pdal", 55 | "pandas", 56 | "cv2", 57 | ], 58 | }, 59 | url=pkg_json["homepage"], 60 | author=pkg_json["author"]["name"], 61 | author_email=pkg_json["author"]["email"], 62 | description=pkg_json["description"], 63 | license=pkg_json["license"], 64 | license_file="LICENSE", 65 | long_description=long_description, 66 | long_description_content_type="text/markdown", 67 | packages=setuptools.find_packages(), 68 | zip_safe=False, 69 | include_package_data=True, 70 | python_requires=">=3.6", 71 | platforms="Linux, Mac OS X, Windows", 72 | keywords=["Jupyter", "JupyterLab", "JupyterLab3"], 73 | install_requires=[ 74 | "ipywidgets>=7", 75 | ], 76 | classifiers=[ 77 | "License :: OSI Approved :: MIT License", 78 | "Programming Language :: Python", 79 | "Programming Language :: Python :: 3", 80 | "Programming Language :: Python :: 3.6", 81 | "Programming Language :: Python :: 3.7", 82 | "Programming Language :: Python :: 3.8", 83 | "Programming Language :: Python :: 3.9", 84 | "Framework :: Jupyter", 85 | "Framework :: Jupyter :: JupyterLab", 86 | "Framework :: Jupyter :: JupyterLab :: 3", 87 | "Framework :: Jupyter :: JupyterLab :: Extensions", 88 | "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt", 89 | ], 90 | ) 91 | 92 | try: 93 | from jupyter_packaging import wrap_installers, npm_builder, get_data_files 94 | 95 | post_develop = npm_builder( 96 | build_cmd="install:extension", source_dir="src", build_dir=lab_path 97 | ) 98 | setup_args["cmdclass"] = wrap_installers( 99 | post_develop=post_develop, ensured_targets=ensured_targets 100 | ) 101 | setup_args["data_files"] = get_data_files(data_files_spec) 102 | except ImportError as e: 103 | import logging 104 | 105 | logging.basicConfig(format="%(levelname)s: %(message)s") 106 | logging.warning( 107 | "Build tool `jupyter-packaging` is missing. Install it with pip or conda." 108 | ) 109 | if not ("--name" in sys.argv or "--version" in sys.argv): 110 | raise e 111 | 112 | if __name__ == "__main__": 113 | setuptools.setup(**setup_args) 114 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tiledb-inc/pybabylonjs", 3 | "version": "1.0.6", 4 | "description": "BabylonJS widget", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab", 8 | "jupyterlab-extension", 9 | "widgets" 10 | ], 11 | "files": [ 12 | "lib/**/*.js", 13 | "dist/*.js", 14 | "css/*.css", 15 | "style/*.css", 16 | "style/index.js" 17 | ], 18 | "author": { 19 | "name": "TileDB", 20 | "email": "support@tiledb.com" 21 | }, 22 | "homepage": "https://github.com/TileDB-Inc/TileDB-PyBabylonJS", 23 | "bugs": { 24 | "url": "https://github.com/TileDB-Inc/TileDB-PyBabylonJS/issues" 25 | }, 26 | "license": "MIT", 27 | "main": "lib/index.js", 28 | "types": "./lib/index.d.ts", 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/TileDB-Inc/TileDB-PyBabylonJS" 32 | }, 33 | "scripts": { 34 | "build": "jlpm run build:lib && jlpm run build:labextension:dev", 35 | "build:labextension": "jupyter labextension build .", 36 | "build:labextension:dev": "jupyter labextension build --development True .", 37 | "build:lib": "tsc", 38 | "build:dist": "NODE_OPTIONS=--max-old-space-size=4096 webpack --mode=production", 39 | "build:prod": "jlpm run clean && jlpm run build:lib && jlpm run build:labextension && jlpm run build:dist", 40 | "clean": "jlpm run clean:lib", 41 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo", 42 | "clean:labextension": "rimraf pybabylonjs/labextension", 43 | "clean:all": "jlpm run clean:lib && jlpm run clean:labextension", 44 | "clean:nbextension": "rimraf pybabylonjs/nbextension/static/index.js", 45 | "eslint": "eslint . --ext .ts,.tsx --fix", 46 | "eslint:check": "eslint . --ext .ts,.tsx", 47 | "install:extension": "jlpm run build", 48 | "lint": "eslint . --ext .ts,.tsx --fix", 49 | "lint:check": "eslint . --ext .ts,.tsx", 50 | "prepack": "yarn run build:lib", 51 | "test": "echo test", 52 | "watch": "run-p watch:src watch:labextension", 53 | "watch:src": "tsc -w", 54 | "watch:labextension": "jupyter labextension watch .", 55 | "watch:lib": "tsc -w", 56 | "watch:nbextension": "NODE_OPTIONS=--max-old-space-size=4096 webpack --watch --mode=development" 57 | }, 58 | "dependencies": { 59 | "@jupyter-widgets/base": "^2 || ^3 || ^4 || ^5 || ^6", 60 | "@jupyterlab/application": "^3 || ^4", 61 | "@tiledb-inc/viz-core": "^1.0.3-alpha.9" 62 | }, 63 | "devDependencies": { 64 | "@jupyterlab/builder": "^3 || ^4", 65 | "@types/node": "^10.11.6", 66 | "@types/webpack-env": "^1.16.3", 67 | "@typescript-eslint/eslint-plugin": "^4.8.1", 68 | "@typescript-eslint/parser": "^4.8.1", 69 | "crypto": "1.0.1", 70 | "css-loader": "^6.3.0", 71 | "eslint": "^7.14.0", 72 | "eslint-config-prettier": "^6.15.0", 73 | "eslint-plugin-prettier": "^3.1.4", 74 | "expect.js": "^0.3.1", 75 | "fs-extra": "^7.0.0", 76 | "git-tag-version": "^1.3.1", 77 | "mkdirp": "^0.5.1", 78 | "npm-run-all": "^4.1.5", 79 | "prettier": "^2.1.1", 80 | "rimraf": "^3.0.2", 81 | "source-map-loader": "^3.0.0", 82 | "style-loader": "^3.2.1", 83 | "svg-url-loader": "^7.1.1", 84 | "ts-loader": "^9.2.5", 85 | "typescript": "~5.1.6", 86 | "webpack": "^5.88.1", 87 | "webpack-cli": "^5.1.4" 88 | }, 89 | "resolutions": { 90 | "capnp-ts": "0.4.0" 91 | }, 92 | "jupyterlab": { 93 | "extension": "lib/plugin", 94 | "outputDir": "pybabylonjs/labextension", 95 | "webpackConfig": "./webpack.lab.config.js", 96 | "sharedPackages": { 97 | "@jupyter-widgets/base": { 98 | "bundled": false, 99 | "singleton": true 100 | } 101 | } 102 | }, 103 | "styleModule": "style/index.js" 104 | } 105 | -------------------------------------------------------------------------------- /examples/boulder-slice.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "021131af-fdc5-401b-a433-2c95182570da", 6 | "metadata": {}, 7 | "source": [ 8 | "# Boulder Point Cloud Data Visualization\n", 9 | "\n", 10 | "This visualization shows a demo of rendering LiDAR data in an interactive dashboard.\n", 11 | "\n", 12 | "Point cloud data from https://www.pixel8.earth/ is licensed as CC by [2.0](https://creativecommons.org/licenses/by/2.0/legalcode) " 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "id": "871a6409-0a93-4a8c-bf50-40aabed6db9c", 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "from pybabylonjs import Show as show" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "id": "d98d37dc-9f34-4a4f-a730-161423a08671", 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "bbox = {\n", 33 | " 'X': [475425.0, 475428.915],\n", 34 | " 'Y': [4429710.508, 4429713.178],\n", 35 | " 'Z': [1651.678, 1658.245]}" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "id": "27e291a4-a09a-436d-9c5d-64a753f739d3", 41 | "metadata": {}, 42 | "source": [ 43 | "### Cloud array\n", 44 | "\n", 45 | "To view point cloud data from a TileDB cloud array a `token` is needed: \n", 46 | "* [sign up for a TileDB account](https://cloud.tiledb.com/auth/signup)\n", 47 | "\n", 48 | "When running this notebook locally:\n", 49 | "* [create a token as described here](https://docs.tiledb.com/cloud/how-to/account/create-api-tokens)\n", 50 | "* uncomment the below cell and add your token (``)\n", 51 | "* run the below cells\n", 52 | "\n", 53 | "When running this notebook on TileDB Cloud:\n", 54 | "* the token will be automatically loaded\n", 55 | "* remove the token from the list of parameters of `show.point_cloud`\n", 56 | "* run the below cells" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": null, 62 | "id": "6c157357-108a-4d8d-8a97-5f2d75fdb3a2", 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "#token = \"\"" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "id": "84e968ea-c5df-4c6f-9657-b8982d60fb9b", 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [ 76 | "show.point_cloud(source=\"cloud\",\n", 77 | " uri = \"tiledb://TileDB-Inc/boulder\",\n", 78 | " token=token,\n", 79 | " bbox=bbox,\n", 80 | " point_size = 2.5,\n", 81 | " width = 1000,\n", 82 | " height = 500,\n", 83 | " rgb_max = 65280,\n", 84 | " camera_zoom = [5, 5, 2],\n", 85 | " camera_location = 6,\n", 86 | " point_type = 'fixed_world_size')" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": null, 92 | "id": "5d0731fe-e332-416a-87ee-a802b167ab95", 93 | "metadata": {}, 94 | "outputs": [], 95 | "source": [] 96 | } 97 | ], 98 | "metadata": { 99 | "kernelspec": { 100 | "display_name": "Python 3 (ipykernel)", 101 | "language": "python", 102 | "name": "python3" 103 | }, 104 | "language_info": { 105 | "codemirror_mode": { 106 | "name": "ipython", 107 | "version": 3 108 | }, 109 | "file_extension": ".py", 110 | "mimetype": "text/x-python", 111 | "name": "python", 112 | "nbconvert_exporter": "python", 113 | "pygments_lexer": "ipython3", 114 | "version": "3.7.10" 115 | } 116 | }, 117 | "nbformat": 4, 118 | "nbformat_minor": 5 119 | } 120 | -------------------------------------------------------------------------------- /pybabylonjs/show.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 TileDB Inc. 2 | # Licensed under the MIT License. 3 | 4 | """Classes for setting up the visualization.""" 5 | 6 | from IPython.display import display 7 | from typing import Optional 8 | from enum import Enum 9 | 10 | from .args import * 11 | from .data import * 12 | from .babylonjs import BabylonPointCloud, BabylonMBRS, BabylonImage 13 | 14 | 15 | def create_dataviz(dataviz, d, **kwargs): 16 | dataviz.value = {**d, **kwargs} 17 | display(dataviz) 18 | 19 | 20 | class PyBabylonJSError(Exception): 21 | pass 22 | 23 | 24 | class ImageFeatureType(Enum): 25 | NON_RENDERABLE = (0,) 26 | RGB = (1,) 27 | CATEGORICAL = (2,) 28 | FLAT_COLOR = 3 29 | 30 | 31 | class Show: 32 | """Create a N-D visualization.""" 33 | 34 | def __init__(self): 35 | self._value = None 36 | self._dataviz = None 37 | 38 | @classmethod 39 | def point_cloud( 40 | self, 41 | uri: Optional[str] = None, 42 | source: Optional[str] = "cloud", 43 | streaming: Optional[bool] = False, 44 | data: Optional[dict] = {}, 45 | **kwargs, 46 | ): 47 | """ 48 | Returns a point cloud visualization widget 49 | 50 | :param uri: when source is "cloud" or "local" specify the URI for the TileDB array 51 | :param source: location of the data to be visualized, one of "cloud", "local" or "dict" 52 | :param streaming: when true all data will be streamed from the TileDB array 53 | :param data: when source="dict" this dictionary contains the points to be visualized: {"X", "Y", "Z", "Red", "Green", "Blue"} 54 | 55 | """ 56 | 57 | point_cloud_args_in = kwargs 58 | 59 | if source == "dict": 60 | data = check_point_cloud_data_dict(data) 61 | if source == "local": 62 | data = check_point_cloud_data_local(uri, point_cloud_args_in) 63 | if source == "cloud": 64 | point_cloud_args_in = check_point_cloud_data_cloud( 65 | streaming, uri, point_cloud_args_in 66 | ) 67 | 68 | point_cloud_args = check_point_cloud_args( 69 | source, streaming, point_cloud_args_in 70 | ) 71 | 72 | d = { 73 | **point_cloud_args, 74 | "uri": uri, 75 | "data": data, 76 | "streaming": streaming, 77 | "source": source, 78 | } 79 | 80 | dataviz = BabylonPointCloud() 81 | dataviz.value = {**d} 82 | display(dataviz) 83 | 84 | @classmethod 85 | def from_dict( 86 | self, 87 | data: dict, 88 | uri: Optional[str] = None, 89 | **kwargs, 90 | ): 91 | source = "dict" 92 | 93 | data = check_point_cloud_data_dict(data) 94 | 95 | point_cloud_args = check_point_cloud_args(False, kwargs) 96 | 97 | d = { 98 | **point_cloud_args, 99 | "uri": uri, 100 | "data": data, 101 | "streaming": False, 102 | "source": source, 103 | } 104 | 105 | dataviz = BabylonPointCloud() 106 | dataviz.value = {**d} 107 | display(dataviz) 108 | 109 | @classmethod 110 | def image( 111 | self, 112 | **kwargs, 113 | ): 114 | image_args = check_image_args(kwargs) 115 | 116 | # d = {**image_args} 117 | 118 | # d = create_image(array_uri, **kwargs) 119 | dataviz = BabylonImage() 120 | dataviz.value = {**image_args} 121 | display(dataviz) 122 | # create_dataviz(BabylonImage(), **image_args) 123 | 124 | @classmethod 125 | def mbrs( 126 | self, 127 | array_uri: str, 128 | **kwargs, 129 | ): 130 | d = create_mbrs(array_uri) 131 | create_dataviz(BabylonMBRS(), d, **kwargs) 132 | 133 | 134 | class BabylonJS: 135 | """Legacy class for instantiating the widget""" 136 | 137 | def __init__(self): 138 | self.value = None 139 | self.z_scale = None 140 | 141 | def _ipython_display_(self): 142 | kwargs = check_point_cloud_args("dict", False, {}) 143 | 144 | kwargs["source"] = "dict" 145 | 146 | if self.z_scale: 147 | kwargs["z_scale"] = self.z_scale 148 | 149 | d = {"data": self.value} 150 | 151 | create_dataviz(BabylonPointCloud(), d, **kwargs) 152 | -------------------------------------------------------------------------------- /azure-pipelines.yaml: -------------------------------------------------------------------------------- 1 | trigger: 2 | tags: 3 | include: 4 | - v* 5 | branches: 6 | include: 7 | - master 8 | 9 | stages: 10 | - stage: Build 11 | jobs: 12 | - job: Build 13 | strategy: 14 | matrix: 15 | linux_py38_jlab3: 16 | imageName: 'ubuntu-22.04' 17 | python.version: '3.8' 18 | jupyterlab.version: '3.6.5' 19 | linux_py39_jlab3: 20 | imageName: 'ubuntu-22.04' 21 | python.version: '3.9' 22 | jupyterlab.version: '3.6.5' 23 | linux_py38_jlab4: 24 | imageName: 'ubuntu-22.04' 25 | python.version: '3.8' 26 | jupyterlab.version: '4.0.3' 27 | linux_py39_jlab4: 28 | imageName: 'ubuntu-22.04' 29 | python.version: '3.9' 30 | jupyterlab.version: '4.0.3' 31 | pool: 32 | vmImage: $(imageName) 33 | 34 | steps: 35 | - task: UsePythonVersion@0 36 | inputs: 37 | versionSpec: '$(python.version)' 38 | architecture: 'x64' 39 | 40 | - script: python -m pip install --upgrade ipywidgets traitlets pytest build jupyter-packaging==0.10.6 jupyterlab==$(jupyterlab.version) 41 | # pip installs release candidates by default: 42 | # https://github.com/pypa/pip/issues/4969 43 | displayName: 'Install dependencies' 44 | 45 | - script: python -m pip install --upgrade black 46 | displayName: 'Install black (linter)' 47 | condition: eq(variables['python.version'], '3.8') 48 | - script: black --check . 49 | displayName: 'Test Formating' 50 | condition: eq(variables['python.version'], '3.8') 51 | 52 | - task: NodeTool@0 53 | inputs: 54 | versionSpec: '18.x' 55 | - script: jlpm 56 | displayName: 'Install labextension dependencies' 57 | env: 58 | NPM_TOKEN: $(NPM_TOKEN) 59 | 60 | - script: jlpm run build:prod 61 | displayName: 'Build jupyterlab extension' 62 | env: 63 | NPM_TOKEN: $(NPM_TOKEN) 64 | 65 | - task: UsePythonVersion@0 66 | inputs: 67 | versionSpec: '$(python.version)' 68 | architecture: 'x64' 69 | # Build the python distribution from source 70 | - script: python -m build 71 | displayName: 'Build python' 72 | condition: ne(variables['Agent.OS'], 'Windows_NT') 73 | env: 74 | NPM_TOKEN: $(NPM_TOKEN) 75 | 76 | - bash: | 77 | set -xeo pipefail 78 | # Display log files if the build failed 79 | echo "Dumping log files for failed build" 80 | echo "----------------------------------" 81 | for f in $(find $BUILD_REPOSITORY_LOCALPATH/build -name *.log); 82 | do echo "------" 83 | echo $f 84 | echo "======" 85 | cat $f 86 | done; 87 | condition: failed() # only run this job if the build step failed 88 | displayName: "Print log files (failed build only)" 89 | - stage: Deploy 90 | dependsOn: Build 91 | condition: and(succeeded('Build'), startsWith(variables['Build.SourceBranch'], 'refs/tag')) 92 | jobs: 93 | - job: Deploy_package 94 | pool: 95 | vmImage: 'ubuntu-22.04' 96 | 97 | steps: 98 | - task: UsePythonVersion@0 99 | inputs: 100 | versionSpec: '3.8' 101 | architecture: 'x64' 102 | 103 | - script: 'pip install twine build setuptools setuptools_scm jupyter-packaging==0.10.6 jupyterlab==3.6.5' 104 | displayName: 'Install twine/build' 105 | 106 | - task: NodeTool@0 107 | inputs: 108 | versionSpec: '18.x' 109 | - script: | 110 | echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc 111 | jlpm 112 | displayName: 'Save npm token & install labextension dependencies' 113 | env: 114 | NPM_TOKEN: $(NPM_TOKEN) 115 | 116 | - script: | 117 | latestGitTag=$(git describe --tags --abbrev=0) 118 | latestGitTag="${latestGitTag:1}" 119 | npm version $latestGitTag --no-commit-hooks --no-git-tag-version 120 | displayName: 'Update package.json version' 121 | 122 | - script: jlpm run build:prod 123 | displayName: 'Build jupyterlab extension' 124 | env: 125 | NPM_TOKEN: $(NPM_TOKEN) 126 | 127 | # Build the python distribution from source 128 | - script: python -m build 129 | displayName: 'Build python' 130 | env: 131 | NPM_TOKEN: $(NPM_TOKEN) 132 | 133 | - task: TwineAuthenticate@1 134 | displayName: 'Twine Authenticate' 135 | inputs: 136 | pythonUploadServiceConnection: 'pypi-upload' 137 | 138 | - script: python -m twine upload -r "pypi-upload" --config-file $(PYPIRC_PATH) dist/*.whl 139 | displayName: 'Release to Pypi' 140 | 141 | - script: ./scripts/publish.sh 142 | env: 143 | GITHUB_TOKEN: $(GITHUB_TOKEN) 144 | NPM_TOKEN: $(NPM_TOKEN) 145 | displayName: 'Release to npm' -------------------------------------------------------------------------------- /examples/netherland-image.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "ece79f11-ab6e-447e-a843-c646d27e9a46", 6 | "metadata": {}, 7 | "source": [ 8 | "# Stream and visualize the Autzen point cloud\n", 9 | "\n", 10 | "The original data used in this notebook can be [found here](https://github.com/PDAL/data/tree/master/autzen) and has a BSD license as [described here](https://pdal.io/en/latest/copyright.html#overall-pdal-license-bsd)." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "34b3f824-b53c-4d9d-b4d2-8e900ff7252f", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from pybabylonjs import Show as show, ImageFeatureType" 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "id": "5c4ec8bd-7b69-48f0-b70d-9977e07d2d7d", 26 | "metadata": {}, 27 | "source": [ 28 | "To stream and view point cloud data from a TileDB array a `token` is needed: \n", 29 | "* [sign up for a TileDB account](https://cloud.tiledb.com/auth/signup)\n", 30 | "\n", 31 | "When running this notebook locally:\n", 32 | "* [create a token as described here](https://docs.tiledb.com/cloud/how-to/account/create-api-tokens)\n", 33 | "* uncomment the below cell and add your token (``)\n", 34 | "* run the below cells\n", 35 | "\n", 36 | "When running this notebook on TileDB Cloud:\n", 37 | "* the token will be automatically loaded\n", 38 | "* remove the token from the list of parameters of `show.image`\n", 39 | "* run the below cells" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": null, 45 | "id": "6ce7f8c7-0805-444b-93ad-3b7554de8304", 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "# token = \"...\"" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "id": "19e3204d-d1a1-4ab8-a888-5833ed44e41d", 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "show.image(token=token,\n", 60 | " engine_api=\"WEBGPU\",\n", 61 | " name_space=\"TileDB-Inc\",\n", 62 | " array_name=\"tiledb://TileDB-Inc/ee5eae5f-9f68-4471-a762-99e966cada1c\",\n", 63 | " point_group_names=[\"tiledb://TileDB-Inc/a89e17ae-4fc7-433f-a2ee-856ee0ecf216\"],\n", 64 | " tile_uris=[\"https://api.pdok.nl/kadaster/3d-basisvoorziening/ogc/v1_0/collections/terreinen/3dtiles\"],\n", 65 | " default_channels=[{\"index\": 1, \"intensity\": 2000}, {\"index\": 2, \"intensity\": 2000}, {\"index\": 3, \"intensity\": 2000}],\n", 66 | " scene_config={\n", 67 | " \"pointConfigs\":[{\n", 68 | " \"pickable\": False,\n", 69 | " \"features\":[\n", 70 | " {\n", 71 | " \"name\": \"Height\",\n", 72 | " \"type\": ImageFeatureType.RGB.value[0],\n", 73 | " \"interleaved\": True,\n", 74 | " \"attributes\":[\n", 75 | " {\n", 76 | " \"name\": \"Red\",\n", 77 | " \"normalize\": True,\n", 78 | " \"normalizationWindow\": { \"min\": 0, \"max\": 255 }\n", 79 | " },\n", 80 | " {\n", 81 | " \"name\": \"Green\",\n", 82 | " \"normalize\": True,\n", 83 | " \"normalizationWindow\": { \"min\": 0, \"max\": 255 }\n", 84 | " },\n", 85 | " {\n", 86 | " \"name\": \"Blue\",\n", 87 | " \"normalize\": True,\n", 88 | " \"normalizationWindow\": { \"min\": 0, \"max\": 255 }\n", 89 | " }\n", 90 | " ]\n", 91 | " }\n", 92 | " ]\n", 93 | " }]\n", 94 | " },\n", 95 | " width = 1200,\n", 96 | " height = 700)" 97 | ] 98 | } 99 | ], 100 | "metadata": { 101 | "kernelspec": { 102 | "display_name": "Python 3 (ipykernel)", 103 | "language": "python", 104 | "name": "python3" 105 | }, 106 | "language_info": { 107 | "codemirror_mode": { 108 | "name": "ipython", 109 | "version": 3 110 | }, 111 | "file_extension": ".py", 112 | "mimetype": "text/x-python", 113 | "name": "python", 114 | "nbconvert_exporter": "python", 115 | "pygments_lexer": "ipython3", 116 | "version": "3.12.6" 117 | } 118 | }, 119 | "nbformat": 4, 120 | "nbformat_minor": 5 121 | } 122 | -------------------------------------------------------------------------------- /pybabylonjs/args.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 TileDB Inc. 2 | # Licensed under the MIT License. 3 | """Functions to format and check the data and keyword arguments for each data source and visualization mode.""" 4 | 5 | import os 6 | from urllib.parse import urlparse 7 | 8 | from .data import * 9 | 10 | POINT_CLOUD_ARGS_DEFAULTS = { 11 | "width": None, 12 | "height": None, 13 | "wheel_precision": None, 14 | "move_speed": None, 15 | "inspector": None, 16 | "color_scheme": None, 17 | "z_scale": None, 18 | "rgb_max": None, 19 | "bbox": None, 20 | "name_space": None, 21 | "group_name": None, 22 | "array_name": None, 23 | "token": None, 24 | "tiledb_env": None, 25 | "crs": None, 26 | "buffer_size": None, 27 | "streaming": None, 28 | "point_type": None, 29 | "point_size": None, 30 | "point_budget": None, 31 | "camera_location": None, 32 | "camera_zoom": [None, None, None], 33 | "camera_up": None, 34 | "edl_strength": None, 35 | "edl_radius": None, 36 | "edl_neighbours": None, 37 | "use_shader": None, 38 | "use_sps": None, 39 | "debug": False, 40 | "worker_pool_size": None, 41 | } 42 | 43 | IMAGE_ARGS_DEFAULTS = { 44 | "engine_api": None, 45 | "width": None, 46 | "height": None, 47 | "name_space": None, 48 | "array_name": None, 49 | "group_name": None, 50 | "geometry_array_names": None, 51 | "point_group_names": None, 52 | "tile_uris": None, 53 | "base_group": None, 54 | "token": None, 55 | "tiledb_env": None, 56 | "default_channels": None, 57 | "scene_config": None, 58 | } 59 | 60 | 61 | def in_pixels(h, default): 62 | if h is None: 63 | return default 64 | if isinstance(h, str): 65 | if "px" in h: 66 | return h 67 | return h + "px" 68 | if isinstance(h, int): 69 | return str(h) + "px" 70 | if isinstance(h, float): 71 | return str(int(h)) + "px" 72 | 73 | 74 | def check_image_args(image_args_in): 75 | image_args = {} 76 | for key in IMAGE_ARGS_DEFAULTS.keys(): 77 | if key in image_args_in: 78 | if key is not None: 79 | image_args[key] = image_args_in.pop(key) 80 | 81 | image_args["height"] = in_pixels(image_args.get("height"), "500px") 82 | image_args["width"] = in_pixels(image_args.get("width"), "700px") 83 | 84 | if not "token" in image_args: 85 | try: 86 | token = os.getenv("TILEDB_REST_TOKEN") 87 | except: 88 | if token == None: 89 | raise ValueError( 90 | "The TileDB Cloud token needed to access the array is not specified or cannot be accessed" 91 | ) 92 | image_args = {**image_args, "token": token} 93 | 94 | return image_args 95 | 96 | 97 | def check_point_cloud_args(source, streaming, point_cloud_args_in): 98 | point_cloud_args = {} 99 | for key in POINT_CLOUD_ARGS_DEFAULTS.keys(): 100 | if key in point_cloud_args_in: 101 | if key is not None: 102 | point_cloud_args[key] = point_cloud_args_in.pop(key) 103 | 104 | point_cloud_args["height"] = in_pixels(point_cloud_args.get("height"), "500px") 105 | point_cloud_args["width"] = in_pixels(point_cloud_args.get("width"), "700px") 106 | 107 | if not "token" in point_cloud_args: 108 | try: 109 | token = os.getenv("TILEDB_REST_TOKEN") 110 | except: 111 | if source == "cloud" & token == None: 112 | raise ValueError( 113 | "The TileDB Cloud token needed to access the array is not specified or cannot be accessed" 114 | ) 115 | point_cloud_args = {**point_cloud_args, "token": token} 116 | 117 | return point_cloud_args 118 | 119 | 120 | def check_point_cloud_data_dict(data): 121 | for var in ["X", "Y", "Z", "Red", "Green", "Blue"]: 122 | if not var in data: 123 | raise ValueError("Data dictionary does not contain " + var) 124 | if not ( 125 | data["X"].size 126 | == data["Y"].size 127 | == data["Z"].size 128 | == data["Red"].size 129 | == data["Green"].size 130 | == data["Blue"].size 131 | ): 132 | raise ValueError("Attributes in data dictionary do not have the same length.") 133 | 134 | return data 135 | 136 | 137 | def check_point_cloud_data_local(uri, point_cloud_args): 138 | if os.path.isdir(uri) == False: 139 | raise ValueError("uri: " + uri + " does not exist.") 140 | if not "bbox" in point_cloud_args: 141 | raise ValueError("The bbox for slicing data from the array is not specified") 142 | 143 | data = create_point_cloud(uri, point_cloud_args["bbox"]) 144 | 145 | return data 146 | 147 | 148 | def check_point_cloud_data_cloud(streaming, uri, point_cloud_args): 149 | o = urlparse(uri) 150 | 151 | if not streaming: 152 | if not "bbox" in point_cloud_args: 153 | raise ValueError( 154 | "The bbox for slicing data from the array is not specified" 155 | ) 156 | point_cloud_args = { 157 | **point_cloud_args, 158 | "name_space": o.netloc, 159 | "array_name": o.path[1:], 160 | } 161 | else: 162 | point_cloud_args = { 163 | **point_cloud_args, 164 | "name_space": o.netloc, 165 | "group_name": o.path[1:], 166 | } 167 | 168 | return point_cloud_args 169 | -------------------------------------------------------------------------------- /src/widget.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 TileDB Inc. 2 | // Licensed under the MIT License. 3 | 4 | import { 5 | DOMWidgetModel, 6 | DOMWidgetView, 7 | ISerializers 8 | } from '@jupyter-widgets/base'; 9 | 10 | import { MODULE_NAME, MODULE_VERSION } from './version'; 11 | import '../css/widget.css'; 12 | import { 13 | TileDBTileImageVisualization, 14 | TileDBMBRSVisualization, 15 | TileDBPointCloudVisualization, 16 | TileDBVisualization 17 | } from '@tiledb-inc/viz-core'; 18 | 19 | export class BabylonBaseModel extends DOMWidgetModel { 20 | static model_module = MODULE_NAME; 21 | static model_module_version = MODULE_VERSION; 22 | static view_module = MODULE_NAME; 23 | static view_module_version = MODULE_VERSION; 24 | 25 | static serializers: ISerializers = { 26 | ...DOMWidgetModel.serializers 27 | }; 28 | } 29 | 30 | abstract class BabylonBaseView extends DOMWidgetView { 31 | canvas?: HTMLCanvasElement; 32 | visualization?: TileDBVisualization; 33 | values = this.model.get('value'); 34 | width = this.values.width; 35 | height = this.values.height; 36 | wheelPrecision = this.values.wheel_precision; 37 | moveSpeed = this.values.move_speed; 38 | inspector = this.values.inspector; 39 | 40 | protected query_changed(): void { 41 | // TODO 42 | } 43 | 44 | remove() { 45 | this.visualization?.destroy(); 46 | super.remove(); 47 | } 48 | } 49 | 50 | export class BabylonPointCloudModel extends BabylonBaseModel { 51 | defaults(): any { 52 | return { 53 | ...super.defaults(), 54 | _model_name: BabylonPointCloudModel.model_name, 55 | _model_module: BabylonPointCloudModel.model_module, 56 | _model_module_version: BabylonPointCloudModel.model_module_version, 57 | _view_name: BabylonPointCloudModel.view_name, 58 | _view_module: BabylonPointCloudModel.view_module, 59 | _view_module_version: BabylonPointCloudModel.view_module_version 60 | }; 61 | } 62 | 63 | static model_name = 'BabylonPointCloudModel'; 64 | static view_name = 'BabylonPointCloudView'; 65 | } 66 | 67 | export class BabylonPointCloudView extends BabylonBaseView { 68 | render() { 69 | this.visualization = new TileDBPointCloudVisualization({ 70 | width: this.values.width, 71 | height: this.values.height, 72 | wheelPrecision: this.values.wheel_precision, 73 | moveSpeed: this.values.move_speed, 74 | inspector: this.values.inspector, 75 | rootElement: this.el, 76 | //mode: this.values.mode, 77 | colorScheme: this.values.color_scheme, 78 | data: this.values.data, 79 | zScale: this.values.z_scale, 80 | //topoOffset: this.values.topo_offset, 81 | //classes: this.values.classes, 82 | //timeOffset: this.values.time_offset, 83 | source: this.values.source, 84 | //pointShift: this.values.point_shift, 85 | rgbMax: this.values.rgb_max, 86 | bbox: this.values.bbox, 87 | namespace: this.values.name_space, 88 | arrayName: this.values.array_name, 89 | groupName: this.values.group_name, 90 | tiledbEnv: this.values.tiledb_env, 91 | token: this.values.token, 92 | bufferSize: this.values.buffer_size, 93 | streaming: this.values.streaming, 94 | pointType: this.values.point_type, 95 | pointSize: this.values.point_size, 96 | pointBudget: this.values.point_budget, 97 | cameraLocation: this.values.camera_location, 98 | cameraZoomOut: this.values.camera_zoom, 99 | cameraUp: this.values.camera_up, 100 | edlStrength: this.values.edl_strength, 101 | edlRadius: this.values.edl_radius, 102 | edlNeighbours: this.values.edl_neightbours, 103 | useShader: this.values.use_shader, 104 | useSPS: this.values.use_sps, 105 | debug: this.values.debug, 106 | workerPoolSize: this.values.worker_pool_size 107 | }); 108 | this.visualization.render(); 109 | } 110 | } 111 | 112 | export class BabylonMBRSModel extends BabylonBaseModel { 113 | defaults(): any { 114 | return { 115 | ...super.defaults(), 116 | _model_name: BabylonMBRSModel.model_name, 117 | _model_module: BabylonMBRSModel.model_module, 118 | _model_module_version: BabylonMBRSModel.model_module_version, 119 | _view_name: BabylonMBRSModel.view_name, 120 | _view_module: BabylonMBRSModel.view_module, 121 | _view_module_version: BabylonMBRSModel.view_module_version 122 | }; 123 | } 124 | 125 | static model_name = 'BabylonMBRSModel'; 126 | static view_name = 'BabylonMBRSView'; 127 | } 128 | 129 | export class BabylonMBRSView extends BabylonBaseView { 130 | render() { 131 | this.visualization = new TileDBMBRSVisualization({ 132 | data: this.values.data, 133 | extents: this.values.extents, 134 | width: this.values.width, 135 | height: this.values.height, 136 | wheelPrecision: this.values.wheel_precision, 137 | moveSpeed: this.values.move_speed, 138 | zScale: this.values.z_scale, 139 | inspector: this.values.inspector, 140 | rootElement: this.el 141 | }); 142 | this.visualization.render(); 143 | } 144 | } 145 | 146 | export class BabylonTileImageModel extends BabylonBaseModel { 147 | defaults(): any { 148 | return { 149 | ...super.defaults(), 150 | _model_name: BabylonTileImageModel.model_name, 151 | _model_module: BabylonTileImageModel.model_module, 152 | _model_module_version: BabylonTileImageModel.model_module_version, 153 | _view_name: BabylonTileImageModel.view_name, 154 | _view_module: BabylonTileImageModel.view_module, 155 | _view_module_version: BabylonTileImageModel.view_module_version 156 | }; 157 | } 158 | 159 | static model_name = 'BabylonTileImageModel'; 160 | static view_name = 'BabylonTileImageView'; 161 | } 162 | 163 | export class BabylonTileImageView extends BabylonBaseView { 164 | render() { 165 | this.visualization = new TileDBTileImageVisualization({ 166 | engineAPI: this.values.engine_api, 167 | namespace: this.values.name_space, 168 | arrayID: this.values.array_name, 169 | groupID: this.values.group_name, 170 | geometryArrayID: this.values.geometry_array_names, 171 | pointGroupID: this.values.point_group_names, 172 | tileUris: this.values.tile_uris, 173 | baseGroup: this.values.base_group, 174 | token: this.values.token, 175 | tiledbEnv: this.values.tiledb_env, 176 | width: this.values.width, 177 | height: this.values.height, 178 | rootElement: this.el, 179 | defaultChannels: this.values.default_channels, 180 | sceneConfig: this.values.scene_config 181 | }); 182 | 183 | this.visualization.render(); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # pybabylonjs documentation build configuration file 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | 16 | # -- General configuration ------------------------------------------------ 17 | 18 | # If your documentation needs a minimal Sphinx version, state it here. 19 | # 20 | # needs_sphinx = '1.0' 21 | 22 | # Add any Sphinx extension module names here, as strings. They can be 23 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 24 | # ones. 25 | extensions = [ 26 | "sphinx.ext.autodoc", 27 | "sphinx.ext.viewcode", 28 | "sphinx.ext.intersphinx", 29 | "sphinx.ext.napoleon", 30 | "sphinx.ext.todo", 31 | "nbsphinx", 32 | "jupyter_sphinx", 33 | "nbsphinx_link", 34 | ] 35 | 36 | # Set the nbsphinx JS path to empty to avoid showing twice of the widgets 37 | nbsphinx_requirejs_path = "" 38 | nbsphinx_widgets_path = "" 39 | 40 | # Ensure our extension is available: 41 | import sys 42 | from os.path import dirname, join as pjoin 43 | 44 | docs = dirname(dirname(__file__)) 45 | root = dirname(docs) 46 | sys.path.insert(0, root) 47 | sys.path.insert(0, pjoin(docs, "sphinxext")) 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ["_templates"] 51 | 52 | # The suffix(es) of source filenames. 53 | # You can specify multiple suffix as a list of string: 54 | # 55 | # source_suffix = ['.rst', '.md'] 56 | source_suffix = ".rst" 57 | 58 | # The master toctree document. 59 | master_doc = "index" 60 | 61 | # General information about the project. 62 | project = "pybabylonjs" 63 | copyright = "2021, TileDB" 64 | author = "TileDB" 65 | 66 | # The version info for the project you're documenting, acts as replacement for 67 | # |version| and |release|, also used in various other places throughout the 68 | # built documents. 69 | # 70 | # The short X.Y version. 71 | 72 | 73 | # get version from python package: 74 | import os 75 | 76 | here = os.path.dirname(__file__) 77 | repo = os.path.join(here, "..", "..") 78 | _version_py = os.path.join(repo, "pybabylonjs", "_version.py") 79 | version_ns = {} 80 | with open(_version_py) as f: 81 | exec(f.read(), version_ns) 82 | 83 | # The short X.Y version. 84 | version = "%i.%i" % version_ns["version_info"][:2] 85 | # The full version, including alpha/beta/rc tags. 86 | release = version_ns["__version__"] 87 | 88 | # The language for content autogenerated by Sphinx. Refer to documentation 89 | # for a list of supported languages. 90 | # 91 | # This is also used if you do content translation via gettext catalogs. 92 | # Usually you set "language" from the command line for these cases. 93 | language = None 94 | 95 | # List of patterns, relative to source directory, that match files and 96 | # directories to ignore when looking for source files. 97 | # This patterns also effect to html_static_path and html_extra_path 98 | exclude_patterns = ["**.ipynb_checkpoints"] 99 | 100 | # The name of the Pygments (syntax highlighting) style to use. 101 | pygments_style = "sphinx" 102 | 103 | # If true, `todo` and `todoList` produce output, else they produce nothing. 104 | todo_include_todos = False 105 | 106 | 107 | # -- Options for HTML output ---------------------------------------------- 108 | 109 | 110 | # Theme options are theme-specific and customize the look and feel of a theme 111 | # further. For a list of options available for each theme, see the 112 | # documentation. 113 | # 114 | # html_theme_options = {} 115 | 116 | # Add any paths that contain custom static files (such as style sheets) here, 117 | # relative to this directory. They are copied after the builtin static files, 118 | # so a file named "default.css" will overwrite the builtin "default.css". 119 | html_static_path = ["_static"] 120 | 121 | 122 | # -- Options for HTMLHelp output ------------------------------------------ 123 | 124 | # Output file base name for HTML help builder. 125 | htmlhelp_basename = "pybabylonjsdoc" 126 | 127 | 128 | # -- Options for LaTeX output --------------------------------------------- 129 | 130 | latex_elements = { 131 | # The paper size ('letterpaper' or 'a4paper'). 132 | # 133 | # 'papersize': 'letterpaper', 134 | # The font size ('10pt', '11pt' or '12pt'). 135 | # 136 | # 'pointsize': '10pt', 137 | # Additional stuff for the LaTeX preamble. 138 | # 139 | # 'preamble': '', 140 | # Latex figure (float) alignment 141 | # 142 | # 'figure_align': 'htbp', 143 | } 144 | 145 | # Grouping the document tree into LaTeX files. List of tuples 146 | # (source start file, target name, title, 147 | # author, documentclass [howto, manual, or own class]). 148 | latex_documents = [ 149 | (master_doc, "pybabylonjs.tex", "pybabylonjs Documentation", "TileDB", "manual"), 150 | ] 151 | 152 | 153 | # -- Options for manual page output --------------------------------------- 154 | 155 | # One entry per manual page. List of tuples 156 | # (source start file, name, description, authors, manual section). 157 | man_pages = [(master_doc, "pybabylonjs", "pybabylonjs Documentation", [author], 1)] 158 | 159 | 160 | # -- Options for Texinfo output ------------------------------------------- 161 | 162 | # Grouping the document tree into Texinfo files. List of tuples 163 | # (source start file, target name, title, author, 164 | # dir menu entry, description, category) 165 | texinfo_documents = [ 166 | ( 167 | master_doc, 168 | "pybabylonjs", 169 | "pybabylonjs Documentation", 170 | author, 171 | "pybabylonjs", 172 | "BabylonJS widget", 173 | "Miscellaneous", 174 | ), 175 | ] 176 | 177 | 178 | # Example configuration for intersphinx: refer to the Python standard library. 179 | intersphinx_mapping = {"https://docs.python.org/": None} 180 | 181 | # Read The Docs 182 | # on_rtd is whether we are on readthedocs.org, this line of code grabbed from 183 | # docs.readthedocs.org 184 | on_rtd = os.environ.get("READTHEDOCS", None) == "True" 185 | 186 | if not on_rtd: # only import and set the theme if we're building docs locally 187 | import sphinx_rtd_theme 188 | 189 | html_theme = "sphinx_rtd_theme" 190 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 191 | 192 | # otherwise, readthedocs.org uses their theme by default, so no need to specify it 193 | 194 | 195 | # Uncomment this line if you have know exceptions in your included notebooks 196 | # that nbsphinx complains about: 197 | # 198 | nbsphinx_allow_errors = True # exception ipstruct.py ipython_genutils 199 | 200 | from sphinx.util import logging 201 | 202 | logger = logging.getLogger(__name__) 203 | 204 | 205 | def setup(app): 206 | def add_scripts(app): 207 | for fname in ["helper.js", "embed-bundle.js"]: 208 | if not os.path.exists(os.path.join(here, "_static", fname)): 209 | logger.warning("missing javascript file: %s" % fname) 210 | app.add_js_file(fname) 211 | 212 | app.connect("builder-inited", add_scripts) 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # TileDB-PyBabylonJS 3 | 4 | [![codecov](https://codecov.io/gh/TileDB-Inc/PyBabylonJS/branch/master/graph/badge.svg)](https://codecov.io/gh/TileDB-Inc/PyBabylonJS) 5 | [![Build Status](https://dev.azure.com/TileDB-Inc/CI/_apis/build/status/TileDB-Inc.TileDB-PyBabylonJS?branchName=main)](https://dev.azure.com/TileDB-Inc/CI/_build/latest?definitionId=37&branchName=main) 6 | 7 | 8 | The TileDB-PyBabylonJS library is a geospatial data visualization Python library that interactively visualizes TileDB arrays with [Babylon.js](https://www.babylonjs.com) in a Jupyter notebook widget. 9 | 10 | The package is under development and currently contains point cloud visualizations with the option to stream all data from a TileDB array or define a bounding box to load a slice of the array 11 | 12 | ## Installation 13 | 14 | This project is available from [PyPI](https://pypi.org/project/pybabylonjs/) and can be installed with `pip`: 15 | 16 | ```bash 17 | pip install pybabylonjs 18 | ``` 19 | 20 | If you are using Jupyter Notebook 5.2 or earlier, you may also need to enable the nbextension: 21 | ```bash 22 | jupyter nbextension enable --py [--sys-prefix|--user|--system] pybabylonjs 23 | ``` 24 | 25 | ## Development Installation 26 | 27 | Create and activate a development environment: 28 | 29 | ```bash 30 | conda create -n pybabylonjs-dev -c conda-forge nodejs yarn python tree scipy 'pyarrow>2' numpy pandas tiledb-py jupyter-packaging jupyterlab 31 | 32 | conda activate pybabylonjs-dev 33 | 34 | pip install opencv-python 35 | ``` 36 | 37 | Fork or clone the repo and go to the main directory. Install the TileDB-PyBabylonJS Python package that will also build the TypeScript package: 38 | 39 | ```bash 40 | pip install -e ".[test, examples]" 41 | ``` 42 | 43 | When developing extensions you need to manually enable the extensions with the notebook / lab frontend. For jupyter lab this is done by the command: 44 | 45 | ```bash 46 | jupyter labextension install @jupyter-widgets/jupyterlab-manager 47 | yarn run build 48 | jupyter labextension develop . --overwrite 49 | ``` 50 | 51 | For a classic notebook you need to run: 52 | 53 | ```bash 54 | jupyter nbextension install --sys-prefix --symlink --overwrite --py pybabylonjs 55 | jupyter nbextension enable --sys-prefix --py pybabylonjs 56 | ``` 57 | 58 | Note that the `--symlink` flag doesn't work on Windows, so you will here have to run 59 | the `install` command every time that you rebuild your extension. For certain installations 60 | you might also need another flag instead of `--sys-prefix`. 61 | 62 | ### How to see your changes 63 | 64 | #### TypeScript 65 | 66 | The TypeScript code for the visualizations can be found in the [TileDB-Viz](https://github.com/TileDB-Inc/TileDB-Viz) repository. After making changes in TileDB-Viz build the package with: 67 | 68 | `yarn build` 69 | 70 | To then see these changes in TileDB-PyBabylonJS run: 71 | 72 | `yarn add file:/path/to/TileDB-Viz/packages/core` 73 | 74 | `yarn build` 75 | 76 | And restart the notebook kernel. 77 | 78 | #### Python 79 | 80 | When you make a change to the Python code rebuild the package and restart the notebook kernel to see your changes. 81 | 82 | ## Usage 83 | 84 | Jupyter notebooks are provided in the [examples folder](https://github.com/TileDB-Inc/TileDB-PyBabylonJS/tree/main/examples) for the following visualizations: 85 | 86 | * [Point cloud visualization parameters](examples/point-cloud-parameters.ipynb) contains a description of all parameters 87 | * [Slice of the Autzen point cloud](examples/autzen-slice.ipynb) 88 | * [Slice of the Boulder point cloud](examples/boulder-slice.ipynb) 89 | * [Streaming the Autzen point cloud](examples/autzen-streaming.ipynb) 90 | * [Streaming the Bristol point cloud](examples/bristol-streaming.ipynb) 91 | * [Streaming the Santorini point cloud](examples/santorini-streaming.ipynb) 92 | 93 | [Sign up for a TileDB account](https://cloud.tiledb.com/auth/signup) and display a point cloud visualization from a TileDB cloud sparse array by specifying the bounding box of a slice of data: 94 | 95 | ```python 96 | from pybabylonjs import Show as show 97 | 98 | bbox = { 99 | 'X': [636800, 637200], 100 | 'Y': [852800, 853100], 101 | 'Z': [406.14, 615.26] 102 | } 103 | 104 | lidar_array = "autzen-classified" 105 | 106 | show.point_cloud(source="cloud", 107 | uri = "tiledb://TileDB-Inc/autzen_classified_tiledb", 108 | token=token, 109 | bbox = bbox, 110 | point_size = 3, 111 | rgb_max = 65535, 112 | camera_up = 25, 113 | camera_location = 2, 114 | camera_zoom = [2,2,2], 115 | point_type = 'fixed_screen_size', 116 | width=1000, 117 | height=600) 118 | ``` 119 | 120 | Or stream all data from a group of arrays: 121 | 122 | ```python 123 | show.point_cloud(streaming=True, 124 | uri="tiledb://TileDB-Inc/bristol", 125 | token=token, 126 | point_size = 4, 127 | wheel_precision = 0.2, 128 | color_scheme = 'dark', 129 | width = 1200, 130 | height = 800, 131 | rgb_max = 255, 132 | point_budget = 3500000, 133 | camera_location = 8, 134 | camera_zoom = [1, 1, 2], 135 | camera_up = 50, 136 | move_speed = 8, 137 | point_type = 'fixed_world_size') 138 | ``` 139 | 140 | ### Parameters 141 | 142 | The following parameters can be set for a point cloud visualization: 143 | 144 | * `camera_location` is the location of the arcRotateCamera in relation to the centre of the point cloud. 1: south, 2: south-east, 3: east, 4: north-east, 5: north, 6: north-west, 7: west, 8: south-west and 9: looking down from above the centre of the point cloud 145 | * `camera_up` is the height of the initial location of the freeCamera 146 | * `camera_zoom` scales the camera position relative to the centre of the point cloud with `[1,1,1]` being in the default position and `[2,2,2]` is then twice a far away from the centre in the X, Y and Z direction 147 | * `color_scheme` is the initial background color: `dark` (default), `light` or ` blue` 148 | * `data` is the dictionary with the point cloud data when `source = dict`. This dictionary needs to contain values for the location `X`, `Y` and `Z` and the RGB color for each point `Red`, `Green` and `Blue` 149 | * `height` is the height of the display window in pixels 150 | * `point_size` is the size of the points 151 | * `point_type` is the interactive point size type 152 | * `fixed_screen_size` (default): each point has a constant size in pixels regardless of its distance to the camera 153 | * `fixed_world_space`: each point has a constant size in world space. This value should be set accordingly to the spacing of the points in world space 154 | * `adaptive_world_space`: the same as `fixed_world_space` for the below example. But when streaming point cloud data, the point size depends on the locally loaded LODs at each point. The point density across all blocks of the same LOD should be the same and the point density should double at each LOD 155 | * `source` is the data source (`cloud` (default), `local` or `dict`) 156 | * `use_sps=True` displays the points as 3D blocks using a [Solid Particle System](https://doc.babylonjs.com/features/featuresDeepDive/particles/solid_particle_system/sps_intro) 157 | * `use_shader=True` adds the EDL shading 158 | * `edl_strength` is the strenght of the shader 159 | * `wheel_precision` gives control over how fast to zoom with the mouse wheel 160 | * `width` is the width of the display window in pixels 161 | 162 | ### Navigating the point cloud 163 | 164 | There are two different cameras available to navigate the point cloud, the arcRotateCamera and freeCamera. Toggle between them with `c`. The initial camera is always the arcRotateCamera 165 | 166 | **arcRotateCamera** 167 | * Zoom in and out with the scroll wheel 168 | * Rotate by dragging the mouse with left button down 169 | * The parameter `wheel_precision` gives control over how fast to zoom with the mouse wheel 170 | * The camera location and distance from the centre of the points can be changed with `camera_location` and `camera_zoom` 171 | * Rotate through the `camera_locations` with `v` 172 | * Change the background color between dark and light with `b` 173 | 174 | **freeCamera** 175 | * Move forward: `w` or `up` 176 | * Move backward: `s` or `down` 177 | * Move up: `e` 178 | * Move down: `q` 179 | * Move to the left: `a` or `left` 180 | * Move to the right: `d` or `right` 181 | * Rotate by dragging the mouse with left button down 182 | * The initial camera position is the centre of the point cloud, the height of the location can be changed with the parameter `camera_up` 183 | * The camera speed can be changed with the parameter `move_speed` 184 | * Change the background color between dark and light with `b` -------------------------------------------------------------------------------- /examples/autzen-slice.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "8e320b80-f162-4dad-8f5b-17032c782daf", 6 | "metadata": {}, 7 | "source": [ 8 | "# View a slice of the Autzen point cloud\n", 9 | "\n", 10 | "The data used in this notebook can be [found here](https://github.com/PDAL/data/tree/master/autzen) and has a BSD license as [described here](https://pdal.io/en/latest/copyright.html#overall-pdal-license-bsd)." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "9c3469f7-b9c0-4fc2-a142-46cd0fd2ae45", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import pandas as pd\n", 21 | "import tiledb\n", 22 | "from pybabylonjs import Show as show" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "id": "22aa9555-41c0-491d-8f36-1ecaf1c21122", 28 | "metadata": {}, 29 | "source": [ 30 | "## Data sources\n", 31 | "\n", 32 | "A slice of a point cloud can be viewed from three different sources specified by the `source` parameter:\n", 33 | "* a TileDB Cloud array (`source = \"cloud\"`)\n", 34 | "* a local TileDB array (`source = \"local\"`)\n", 35 | "* a dictionary (`source = \"dict\"`)\n", 36 | "\n", 37 | "In all cases a bounding box (`bbox`) with the minimum and maximum values of X, Y and Z is needed to slice the data from the array:" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": null, 43 | "id": "fec9e371-519c-44d5-95b0-7bc2f3093a1c", 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "bbox = {\n", 48 | " 'X': [636800, 637200],\n", 49 | " 'Y': [852800, 853100],\n", 50 | " 'Z': [406.14, 615.26]\n", 51 | "}" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "id": "a149a0e0-c9ac-4b99-b830-4c54e85248fe", 57 | "metadata": {}, 58 | "source": [ 59 | "### Cloud array\n", 60 | "\n", 61 | "To view point cloud data from a TileDB cloud array a `token` is needed: \n", 62 | "* [sign up for a TileDB account](https://cloud.tiledb.com/auth/signup)\n", 63 | "\n", 64 | "When running this notebook locally:\n", 65 | "* [create a token as described here](https://docs.tiledb.com/cloud/how-to/account/create-api-tokens)\n", 66 | "* uncomment the below cell and add your token (``)\n", 67 | "* run the below cells\n", 68 | "\n", 69 | "When running this notebook on TileDB Cloud:\n", 70 | "* the token will be automatically loaded\n", 71 | "* remove the token from the list of parameters of `show.point_cloud`\n", 72 | "* run the below cells" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "id": "70c457fe-5245-4a1c-b1e6-5970d8af0b85", 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "#token = \"\"" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": null, 88 | "id": "6b2f4d85-9704-45c0-888d-5bf94b6a3989", 89 | "metadata": { 90 | "tags": [] 91 | }, 92 | "outputs": [], 93 | "source": [ 94 | "show.point_cloud(source=\"cloud\",\n", 95 | " uri = \"tiledb://TileDB-Inc/autzen_classified_tiledb\",\n", 96 | " token=token,\n", 97 | " bbox = bbox,\n", 98 | " point_size = 3,\n", 99 | " rgb_max = 65535,\n", 100 | " camera_up = 25,\n", 101 | " camera_location = 2,\n", 102 | " camera_zoom = [2,2,2],\n", 103 | " point_type = 'fixed_world_size',\n", 104 | " width=1000,\n", 105 | " height=600)" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "id": "cc1939af-2756-4186-8622-4f30b5b26d49", 111 | "metadata": {}, 112 | "source": [ 113 | "## Optional: create and view a point cloud array from a LAZ file" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": null, 119 | "id": "8f43beb0-5de1-4329-a4bf-f28339b8b0d8", 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "import pdal" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": null, 129 | "id": "fb1f30f8-6c05-48c2-b1af-574ae76e1252", 130 | "metadata": {}, 131 | "outputs": [], 132 | "source": [ 133 | "!wget -nc \"https://github.com/PDAL/data/blob/master/autzen/autzen-classified.laz?raw=true\" -O \"autzen-classified.laz\"" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "id": "a29673a4-798f-444c-915b-ab4bd4a2be21", 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [ 143 | "pipeline = (\n", 144 | " pdal.Reader(\"autzen-classified.laz\") |\n", 145 | " pdal.Filter.stats() |\n", 146 | " pdal.Writer.tiledb(array_name=\"autzen-classified\",chunk_size=100000)\n", 147 | ")\n", 148 | "\n", 149 | "count = pipeline.execute() " 150 | ] 151 | }, 152 | { 153 | "cell_type": "markdown", 154 | "id": "8ef43d2c-9214-4fd1-bddc-04afc9e144e3", 155 | "metadata": {}, 156 | "source": [ 157 | "### Local array\n", 158 | "The point cloud data from the newly created array can now be viewed with the below. Note that a larger slice is loaded than in the cells above." 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": null, 164 | "id": "39fef3ab-8669-4897-a3fb-73ee3bf12b4a", 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [ 168 | "lidar_array = \"autzen-classified\"" 169 | ] 170 | }, 171 | { 172 | "cell_type": "code", 173 | "execution_count": null, 174 | "id": "77f078f4-a600-4fac-a180-fb3e773aa32b", 175 | "metadata": {}, 176 | "outputs": [], 177 | "source": [ 178 | "bbox2 = {\n", 179 | " 'X': [636800, 637800],\n", 180 | " 'Y': [851000, 853000],\n", 181 | " 'Z': [406.14, 615.26]\n", 182 | "}" 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": null, 188 | "id": "b9c04560-76f4-41b3-a658-a9b4f23c15eb", 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [ 192 | "show.point_cloud(source=\"local\",\n", 193 | " uri=lidar_array,\n", 194 | " bbox = bbox2,\n", 195 | " width = 1000,\n", 196 | " height = 800,\n", 197 | " point_size = 3,\n", 198 | " rgb_max = 65535,\n", 199 | " camera_up = 25,\n", 200 | " camera_location = 2,\n", 201 | " camera_zoom = [2,2,2],\n", 202 | " point_type = 'fixed_world_size')" 203 | ] 204 | }, 205 | { 206 | "cell_type": "markdown", 207 | "id": "321874d3-6119-4edc-8250-64fe3e10be4f", 208 | "metadata": {}, 209 | "source": [ 210 | "### Data from a dictionary\n", 211 | "Alternatively data can be loaded into a dictionary first and then displayed. \n", 212 | "\n", 213 | "Load the data directly into a dictionary from the local array:" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": null, 219 | "id": "67b71dbc-d2be-4b2f-9972-f4719a33ea82", 220 | "metadata": {}, 221 | "outputs": [], 222 | "source": [ 223 | "with tiledb.open(lidar_array) as arr:\n", 224 | " data = arr.query(attrs=[\"Red\", \"Green\", \"Blue\"], dims=[\"X\", \"Y\", \"Z\"])[\n", 225 | " bbox2[\"X\"][0] : bbox2[\"X\"][1],\n", 226 | " bbox2[\"Y\"][0] : bbox2[\"Y\"][1],\n", 227 | " bbox2[\"Z\"][0] : bbox2[\"Z\"][1],\n", 228 | " ]" 229 | ] 230 | }, 231 | { 232 | "cell_type": "markdown", 233 | "id": "6953f74c-2cc9-4786-be41-f05ff0da14fc", 234 | "metadata": {}, 235 | "source": [ 236 | "Or first load the data into a pandas DataFrame when for example pre-processing of the data is needed:" 237 | ] 238 | }, 239 | { 240 | "cell_type": "code", 241 | "execution_count": null, 242 | "id": "4bf0e649-5997-4b44-b6f9-f39b9400c6b3", 243 | "metadata": {}, 244 | "outputs": [], 245 | "source": [ 246 | "with tiledb.open(lidar_array) as arr:\n", 247 | " df = pd.DataFrame(arr[\n", 248 | " bbox2[\"X\"][0] : bbox2[\"X\"][1],\n", 249 | " bbox2[\"Y\"][0] : bbox2[\"Y\"][1],\n", 250 | " bbox2[\"Z\"][0] : bbox2[\"Z\"][1]])" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": null, 256 | "id": "6f81d1bc-81d2-4cbb-9c94-17643215c085", 257 | "metadata": {}, 258 | "outputs": [], 259 | "source": [ 260 | "df = df.drop(['ReturnNumber', 'NumberOfReturns', 'ScanDirectionFlag', 'EdgeOfFlightLine', 'ScanAngleRank', 'UserData', 'PointSourceId', 'ScanChannel', 'ClassFlags'], axis=1)" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": null, 266 | "id": "4b024dc4-030b-4054-b2a9-a936077f4e74", 267 | "metadata": {}, 268 | "outputs": [], 269 | "source": [ 270 | "df" 271 | ] 272 | }, 273 | { 274 | "cell_type": "code", 275 | "execution_count": null, 276 | "id": "b1636835-9527-4fc1-ad57-6ac4f3e2407f", 277 | "metadata": {}, 278 | "outputs": [], 279 | "source": [ 280 | "data = {\n", 281 | " 'X': df['X'],\n", 282 | " 'Y': df['Y'],\n", 283 | " 'Z': df['Z'],\n", 284 | " 'Red': df['Red'],\n", 285 | " 'Green': df['Green'],\n", 286 | " 'Blue': df['Blue']\n", 287 | "}" 288 | ] 289 | }, 290 | { 291 | "cell_type": "code", 292 | "execution_count": null, 293 | "id": "a4bcf7ed-af2b-447a-ae3a-66cc67db1a84", 294 | "metadata": {}, 295 | "outputs": [], 296 | "source": [ 297 | "show.point_cloud(source=\"dict\",\n", 298 | " data=data,\n", 299 | " bbox = bbox2,\n", 300 | " point_size = 3,\n", 301 | " width = 1000,\n", 302 | " height = 700,\n", 303 | " rgb_max = 65535,\n", 304 | " camera_up = 25,\n", 305 | " camera_location = 2,\n", 306 | " camera_zoom = [2,2,2])" 307 | ] 308 | }, 309 | { 310 | "cell_type": "code", 311 | "execution_count": null, 312 | "id": "f74a68fc-1a35-45da-b8cd-dfff684822bc", 313 | "metadata": {}, 314 | "outputs": [], 315 | "source": [] 316 | } 317 | ], 318 | "metadata": { 319 | "kernelspec": { 320 | "display_name": "Python 3 (ipykernel)", 321 | "language": "python", 322 | "name": "python3" 323 | }, 324 | "language_info": { 325 | "codemirror_mode": { 326 | "name": "ipython", 327 | "version": 3 328 | }, 329 | "file_extension": ".py", 330 | "mimetype": "text/x-python", 331 | "name": "python", 332 | "nbconvert_exporter": "python", 333 | "pygments_lexer": "ipython3", 334 | "version": "3.7.10" 335 | } 336 | }, 337 | "nbformat": 4, 338 | "nbformat_minor": 5 339 | } 340 | -------------------------------------------------------------------------------- /examples/point-cloud-parameters.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "0998f3eb-1c73-4e34-b406-007e1d795625", 6 | "metadata": {}, 7 | "source": [ 8 | "# Point cloud visualization parameters\n", 9 | "\n", 10 | "This notebook gives an overview of the various parameters that are available to change the appearance of the point cloud visualization and how to navigate through the points with the keyboard and mouse. " 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "0467dca5-2eb0-4dcd-9859-3df15050842c", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import numpy as np\n", 21 | "from pybabylonjs import Show as show" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "id": "4a27ab65-9b45-47ba-9d10-0990da8de86a", 27 | "metadata": {}, 28 | "source": [ 29 | "Create a random point cloud to visualize and explore the parameters with:" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "id": "419814fe-aabc-46e5-9865-dfdf7e63c05a", 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "(minx, maxx), (miny, maxy), (minz, maxz) = ((-80, 80), (-150, 150), (-5, 5))\n", 40 | "extent = 50.\n", 41 | "num_vals = 1000\n", 42 | "\n", 43 | "xs = np.random.default_rng().uniform(minx - extent, maxx + extent, num_vals)\n", 44 | "ys = np.random.default_rng().uniform(miny - extent, maxy + extent, num_vals)\n", 45 | "zs = np.random.default_rng().uniform(minx, maxz, num_vals)\n", 46 | "\n", 47 | "data = {\n", 48 | " \"X\": xs,\n", 49 | " \"Y\": ys,\n", 50 | " \"Z\": zs,\n", 51 | " \"Red\": np.random.default_rng().uniform(0., 1., num_vals),\n", 52 | " \"Green\": np.random.default_rng().uniform(0., 1., num_vals),\n", 53 | " \"Blue\": np.random.default_rng().uniform(0., 1., num_vals)\n", 54 | "}" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "id": "202e301e-4be1-417d-9a77-2ae0717a6d44", 60 | "metadata": {}, 61 | "source": [ 62 | "The below displays a default point cloud visualization with [BabylonJS](https://www.babylonjs.com/), where the points from `data` are used to create a [PointCloudSystem](https://doc.babylonjs.com/features/featuresDeepDive/particles/point_cloud_system). Click the menu in the top right corner to change the background color, and click the menu again to collapse the options. \n", 63 | "\n", 64 | "`source=\"dict\"` specifies that the data will be loaded from the `data` dictionary (`dict`) created above. This dictionary always needs to contain values for the location `X`, `Y` and `Z` and the RGB color for each point `Red`, `Green` and `Blue`, in this case scaled between 0 and 1. " 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "id": "a024e5d3-4198-4ba0-8c81-22ad0112f72e", 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "show.point_cloud(source=\"dict\",\n", 75 | " data=data,\n", 76 | " point_size = 10)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "id": "f577ef0e-e3c4-430d-ac06-4b12c644a2cf", 82 | "metadata": {}, 83 | "source": [ 84 | "The size of the points of defined by the `point_size`parameter. Other parameters that can be used to improve the looks of the visualization are:\n", 85 | "\n", 86 | "* `width` and `height` are the width and height of the display window in pixels\n", 87 | "* `color_scheme` is the initial background color: `dark` (default), `light` or ` blue`\n", 88 | "* `camera_zoom` scales the camera position relative to the centre of the point cloud with `[1,1,1]` being in the default position and `[2,2,6]` is then twice a far away from the centre in the X and Y direction, and 6 times as far away in the Z direction\n", 89 | "* `camera_location` is the location of the camera in relation to the centre of the point cloud. 1: south, 2: south-east, 3: east, 4: north-east, 5: north, 6: north-west, 7: west, 8: south-west and 9: looking down from above the centre of the point cloud" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "id": "022751de-f2c2-4e59-a12f-a33a3badbe37", 96 | "metadata": { 97 | "tags": [] 98 | }, 99 | "outputs": [], 100 | "source": [ 101 | "show.point_cloud(source=\"dict\",\n", 102 | " data=data,\n", 103 | " point_size = 20,\n", 104 | " width=1000,\n", 105 | " height=600,\n", 106 | " color_scheme=\"dark\",\n", 107 | " camera_zoom=[2,2,6],\n", 108 | " camera_location=2)" 109 | ] 110 | }, 111 | { 112 | "attachments": {}, 113 | "cell_type": "markdown", 114 | "id": "e590860a-47d2-411f-ba4e-7605a5440cb1", 115 | "metadata": {}, 116 | "source": [ 117 | "The size of the points can be interactively updated with the `point_type` parameter. Choose one of the below:\n", 118 | "\n", 119 | "* `fixed_screen_size` (default): each point has a constant size in pixels regardless of its distance to the camera\n", 120 | "* `fixed_world_size`: each point has a constant size in world space. This value should be set accordingly to the spacing of the points in world space\n", 121 | "* `adaptive_world_size`: the same as `fixed_world_size` for the below example. But when streaming point cloud data, the point size depends on the locally loaded LODs at each point. The point density across all blocks of the same LOD should be the same and the point density should double at each LOD" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "id": "0c41559c-9243-41c4-98db-024f3b3cb382", 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "show.point_cloud(source=\"dict\",\n", 132 | " data=data,\n", 133 | " point_size = 20,\n", 134 | " width=1000,\n", 135 | " height=600,\n", 136 | " color_scheme=\"dark\",\n", 137 | " camera_zoom=[2,2,6],\n", 138 | " camera_location=2,\n", 139 | " point_type = 'fixed_world_size')" 140 | ] 141 | }, 142 | { 143 | "attachments": {}, 144 | "cell_type": "markdown", 145 | "id": "401bb0f4-1562-478d-a956-db67218b16af", 146 | "metadata": {}, 147 | "source": [ 148 | "It is possible to show each point as a 3D block instead of a 2D square, but note that this is more memory intensive and might slow down the visualization for large point clouds. To see a clearer distinction between the blocks [Eye Dome Lighting (EDL)](https://www.kitware.com/eye-dome-lighting-a-non-photorealistic-shading-technique/) can be added. \n", 149 | "\n", 150 | "* `use_sps=True` displays the points as 3D blocks using a [Solid Particle System](https://doc.babylonjs.com/features/featuresDeepDive/particles/solid_particle_system/sps_intro)\n", 151 | "* `use_shader=True` adds the EDL shading \n", 152 | "* `edl_strength` is the strenght of the shader" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": null, 158 | "id": "28f6c561-f1bd-4409-a6d1-f5c3723dd50e", 159 | "metadata": { 160 | "tags": [] 161 | }, 162 | "outputs": [], 163 | "source": [ 164 | "show.point_cloud(source=\"dict\",\n", 165 | " data=data,\n", 166 | " point_size = 10,\n", 167 | " width=1000,\n", 168 | " height=600,\n", 169 | " camera_location=3,\n", 170 | " camera_zoom=[4,4,6],\n", 171 | " color_scheme=\"light\",\n", 172 | " use_sps=True,\n", 173 | " use_shader=True,\n", 174 | " edl_strength=5)" 175 | ] 176 | }, 177 | { 178 | "cell_type": "markdown", 179 | "id": "b7846c5f-a71b-40a1-94a8-f6e5c3edefca", 180 | "metadata": { 181 | "tags": [] 182 | }, 183 | "source": [ 184 | "## Navigating the point cloud\n", 185 | "\n", 186 | "There are two different cameras available to navigate the point cloud, the arcRotateCamera and freeCamera. Toggle between them with `c`. The initial camera is always the arcRotateCamera\n", 187 | "\n", 188 | "**arcRotateCamera** \n", 189 | "* Zoom in and out with the scroll wheel\n", 190 | "* Rotate by dragging the mouse with left button down\n", 191 | "* The parameter `wheel_precision` gives control over how fast to zoom with the mouse wheel\n", 192 | "* The camera location and distance from the centre of the points can be changed with `camera_location` and `camera_zoom`\n", 193 | "* Rotate through the `camera_locations` with `v`\n", 194 | "* Change the background color between dark and light with `b`\n", 195 | "\n", 196 | "**freeCamera**\n", 197 | "* Move forward: `w` or `up`\n", 198 | "* Move backward: `s` or `down`\n", 199 | "* Move up: `e`\n", 200 | "* Move down: `q`\n", 201 | "* Move to the left: `a` or `left`\n", 202 | "* Move to the right: `d` or `right`\n", 203 | "* Rotate by dragging the mouse with left button down\n", 204 | "* The initial camera position is the centre of the point cloud, the height of the location can be changed with the parameter `camera_up`\n", 205 | "* The camera speed can be changed with the parameter `move_speed`\n", 206 | "* Change the background color between dark and light with `b`\n", 207 | "\n", 208 | "The below example shows some of these options where they are all set to move very fast:" 209 | ] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": null, 214 | "id": "841bdccf-34c2-476f-a2d6-0e41823b9777", 215 | "metadata": {}, 216 | "outputs": [], 217 | "source": [ 218 | "show.point_cloud(source=\"dict\",\n", 219 | " data=data,\n", 220 | " point_size = 10,\n", 221 | " width=1000,\n", 222 | " height=600,\n", 223 | " camera_location=6,\n", 224 | " camera_zoom=[4,4,6],\n", 225 | " color_scheme=\"light\",\n", 226 | " wheel_precision=0.01,\n", 227 | " camera_up=20,\n", 228 | " move_speed=10)" 229 | ] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "execution_count": null, 234 | "id": "f28cd846-a920-4e5f-8b18-e57d70005452", 235 | "metadata": {}, 236 | "outputs": [], 237 | "source": [] 238 | } 239 | ], 240 | "metadata": { 241 | "kernelspec": { 242 | "display_name": "Python 3 (ipykernel)", 243 | "language": "python", 244 | "name": "python3" 245 | }, 246 | "language_info": { 247 | "codemirror_mode": { 248 | "name": "ipython", 249 | "version": 3 250 | }, 251 | "file_extension": ".py", 252 | "mimetype": "text/x-python", 253 | "name": "python", 254 | "nbconvert_exporter": "python", 255 | "pygments_lexer": "ipython3", 256 | "version": "3.7.10" 257 | } 258 | }, 259 | "nbformat": 4, 260 | "nbformat_minor": 5 261 | } 262 | --------------------------------------------------------------------------------