niivue / ipyniivue Goto Github PK
View Code? Open in Web Editor NEWA WebGL-powered Jupyter Widget for Niivue based on anywidget
License: BSD 2-Clause "Simplified" License
A WebGL-powered Jupyter Widget for Niivue based on anywidget
License: BSD 2-Clause "Simplified" License
Please review code to ensure Pythonic paradigms.
This "issue" is more of an administrative log to help us manage the initial development of this project. We can close it and move this to other more appropriate management tools down the line. For now, I suggest the following target for ipyniivue-experimental based on the AnyWidget approach:
Develop a widget allowing us to replicate, in JupyterNotebook, with Python code, the demo already prepared for NiiVue. The functionalities must be usable from the Python side (i.e., we don't want just to be able to display JS demos in Jupyter Notebooks; we want to be able to implement them in Python using the JS wrapper). I suggest we implement these are different notebooks and cross them off the following list when done:
Besides ensuring these functionalities (and by integrating as notebooks ran by the CI on PR, ensuring that there is no code regression on these functionalities), I think we need to target the following objectives:
Final objective:
Describe the bug
When displaying the same Niivue widget twice, interactions with the 1st Niivue widget do not update the display of the 2nd Niivue widget. Instead, what occurs is that the 1st Niivue widget freezes and does not respond to any interactions.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
Interactions in the 1st Niivue widget display should update both the 1st and 2nd Niivue widget displays (and all other displays of the same widget).
Currently, the NiivueOptions for a Niivue widget can be accessed like so: nv.text_height
.
However, this does not exactly mirror the method of accessing an option from a js Niivue object, which is like so: nv.opts.textHeight
.
The naming difference (snake case vs camel case) is normal, however, the widget version is missing the "opts." text.
Ideally, a Niivue widget could have one of its options modified like so:
from ipyniivue import Niivue
nv = Niivue()
nv.opts.text_height = 0.05
Dear ipyniivue team,
I am trying to use ipyniivue in a jupyter notebook. The installation works without any problems, but when following the example the viewer doesn't show up:
Am I missing anything?
These are the packages installed:
aiofiles 22.1.0
aiohttp 3.8.4
aiosignal 1.3.1
aiosqlite 0.19.0
alembic 1.10.4
annexremote 1.6.0
anyio 3.6.2
argon2-cffi 21.3.0
argon2-cffi-bindings 21.2.0
arrow 1.2.3
asttokens 2.2.1
async-generator 1.10
async-timeout 4.0.2
attrs 22.2.0
Babel 2.12.1
backcall 0.2.0
backports.functools-lru-cache 1.6.4
beautifulsoup4 4.12.2
bleach 6.0.0
blinker 1.6.2
boltons 23.0.0
boto 2.49.0
brotlipy 0.7.0
certifi 2022.12.7
certipy 0.1.3
cffi 1.15.1
chardet 5.1.0
charset-normalizer 3.1.0
colorama 0.4.6
comm 0.1.3
conda 23.3.1
conda-package-handling 2.0.2
conda_package_streaming 0.7.0
cryptography 40.0.2
datalad 0.18.3
datalad-container 1.1.9
datalad-osf 0.2.3.1
debugpy 1.6.7
decorator 5.1.1
defusedxml 0.7.1
distro 1.8.0
entrypoints 0.4
exceptiongroup 1.1.1
executing 1.2.0
fasteners 0.18
fastjsonschema 2.16.3
flit_core 3.8.0
fqdn 1.5.1
frozenlist 1.3.3
greenlet 2.0.2
humanize 4.6.0
idna 3.4
importlib-metadata 6.6.0
importlib-resources 5.12.0
iniconfig 2.0.0
ipykernel 6.22.0
ipyniivue 1.0.2
ipython 8.13.1
ipython-genutils 0.2.0
ipywidgets 8.0.6
iso8601 1.1.0
isoduration 20.11.0
jaraco.classes 3.2.3
jedi 0.18.2
jeepney 0.8.0
Jinja2 3.1.2
json5 0.9.5
jsonpatch 1.32
jsonpointer 2.0
jsonschema 4.17.3
jupyter_client 8.2.0
jupyter_core 5.3.0
jupyter-events 0.6.3
jupyter_server 2.5.0
jupyter_server_fileid 0.9.0
jupyter_server_proxy 4.0.0
jupyter_server_terminals 0.4.4
jupyter_server_ydoc 0.8.0
jupyter-telemetry 0.1.0
jupyter-ui-poll 0.2.2
jupyter-ydoc 0.2.3
jupyterhub 4.0.0
jupyterlab 3.6.3
jupyterlab-pygments 0.2.2
jupyterlab_server 2.22.1
jupyterlab-widgets 3.0.7
jupyterlmod 4.0.3
keyring 23.13.1
keyrings.alt 4.2.0
libmambapy 1.4.2
looseversion 1.1.2
Mako 1.2.4
mamba 1.4.2
MarkupSafe 2.1.2
matplotlib-inline 0.1.6
mistune 2.0.5
more-itertools 9.1.0
msgpack 1.0.5
multidict 6.0.4
nbclassic 0.5.6
nbclient 0.7.4
nbconvert 7.3.1
nbformat 5.8.0
nest-asyncio 1.5.6
nibabel 5.1.0
notebook 6.5.4
notebook_shim 0.2.3
numpy 1.24.3
oauthlib 3.2.2
osfclient 0.0.5
packaging 23.1
pamela 1.0.0
pandocfilters 1.5.0
parso 0.8.3
patool 1.12
pexpect 4.8.0
pickleshare 0.7.5
pip 23.1.2
pkgutil_resolve_name 1.3.10
platformdirs 3.5.0
pluggy 1.0.0
prometheus-client 0.16.0
prompt-toolkit 3.0.38
psutil 5.9.5
ptyprocess 0.7.0
pure-eval 0.2.2
pycosat 0.6.4
pycparser 2.21
pycurl 7.45.1
Pygments 2.15.1
PyJWT 2.6.0
pyOpenSSL 23.1.1
pyrsistent 0.19.3
PySocks 1.7.1
pytest 7.3.1
python-dateutil 2.8.2
python-gitlab 3.14.0
python-json-logger 2.0.7
pytz 2023.3
PyYAML 6.0
pyzmq 25.0.2
requests 2.29.0
requests-toolbelt 1.0.0
rfc3339-validator 0.1.4
rfc3986-validator 0.1.1
ruamel.yaml 0.17.21
ruamel.yaml.clib 0.2.7
SecretStorage 3.3.3
Send2Trash 1.8.2
setuptools 67.7.2
simpervisor 0.4
six 1.16.0
sniffio 1.3.0
soupsieve 2.3.2.post1
SQLAlchemy 2.0.11
stack-data 0.6.2
terminado 0.17.1
tinycss2 1.2.1
tomli 2.0.1
toolz 0.12.0
tornado 6.3
tqdm 4.65.0
traitlets 5.9.0
typing_extensions 4.5.0
uri-template 1.2.0
urllib3 1.26.15
wcwidth 0.2.6
webcolors 1.13
webencodings 0.5.1
websocket-client 1.5.1
wheel 0.40.0
widgetsnbextension 4.0.7
y-py 0.5.9
yarl 1.9.2
ypy-websocket 0.8.2
zipp 3.15.0
zstandard 0.19.0
Thank you for any hint
Steffen
Ok, I've been trying to go through the examples listed in #50 to understand the usage of APIs exposed by Niivue.
For the time being, our priorities are motivated by basic multi-planar, envisioning a Pythonic API for this:
volumes = [
{ "path": "../images/mini152.nii.gz", "colormap": "gray", "visible": True, "opacity": 1.0 },
{ "path": "../images/hippo.nii.gz", "colormap": "red", "visible": True, "opacity": 1 },
]
nv = Niivue(opts={ "sliceType": SLICE_TYPE.MULTIPLANAR })
nv.load_volumes(volumes)
nv # displays widget
Additionally, updating properties on the images should happen through getters/setters for the properties on the created volumes (rather than inconsistent nv.setColormap(volumeId, ...)
or nv.setOpacity(index, ...)
. For the viewer rendered above should update with:
nv.volumes[0].colormap = "blue"
nv.volumes[0].opacity = 0.2
nv.volumes[0].visible = False
This primarily focuses on Volume for now, but we will do a similar audit of features necessary for Meshes. The following are the TODOs:
opts
traitlet for all options (can later add methods to update opts
specifically in Python) niivue/ipyniivue-experimental#4load_volumes
with _volumes
traitlet niivue/ipyniivue-experimental#4nv.volumes[idx]
reactive for opacity
, colormap
, visible
More reactive APIs like add_volume
can be added on top of this infrastructure, but complicate implementing the essential features.
Find a similar example to basic multi-planar
and audit the necessary APIs.
I took a look in the demos/
directory and a very high-level summary of the APIs in use from nv
instances:
rg -o 'nv\d?\.(?:opts\.)?\w+' | sed 's/.*:nv[0-9]\.//' | sort | uniq -c | sort -r
229 meshes
107 setSliceType
96 setMeshLayerProperty
88 volumes
87 attachTo
73 loadVolumes
65 drawScene
61 updateGLVolume
57 opts.dragMode
56 dragModes
55 setMeshProperty
50 sliceTypeMultiplanar
48 setClipPlane
46 opts.multiplanarForceRender
44 setMeshShader
44 opts.isColorbar
42 moveCrosshairInVox
41 setPenValue
38 loadMeshes
32 sliceTypeRender
30 graph
29 setRadiologicalConvention
24 setSliceMM
21 meshShaderNames
20 opts.backColor
19 setOpacity
18 setInterpolation
17 setHighResolutionCapable
16 drawOpacity
12 setFrame4D
12 setDrawingEnabled
12 scene
12 opts.meshXRay
11 setRenderAzimuthElevation
11 drawFillOverwrites
10 opts.show3Dcrosshair
10 opts.isRadiologicalConvention
8 getFrame4D
7 sliceTypeAxial
7 setDrawColormap
7 saveScene
7 drawUndo
7 drawGrowCut
6 setModulationImage
6 overlayOutlineWidth
6 loadMatCapTexture
6 loadDrawingFromUrl
6 addLabel
5 sliceTypeSagittal
5 sliceTypeCoronal
5 setSelectionBoxColor
5 opts.yoke3Dto2DZoom
5 opts.isHighResolutionCapable
4 setSliceMosaicString
4 setMultiplanarPadPixels
4 setMultiplanarLayout
4 setDrawOpacity
4 setColormapNegative
4 saveImage
4 saveHTML
4 saveDocument
4 resizeListener
4 removeHaze
4 opts.penValue
4 opts.isSliceMM
4 opts.isOrientCube
4 opts.isNearestInterpolation
4 loadFont
4 loadConnectome
4 isAlphaClipDark
4 gl
4 drawOtsu
4 colormaps
4 attachToCanvas
3 setVolumeRenderIllumination
3 setMeshThicknessOn2D
3 setCrosshairWidth
3 setClipPlaneColor
3 removeVolumeByUrl
3 opts.clipPlaneColor
3 onDragRelease
3 getDescriptives
3 broadcastTo
3 addVolumeFromUrl
2 thumbnailVisible
2 setGamma
2 setCustomMeshShader
2 setCrosshairColor
2 setColormap
2 setAtlasOutline
2 setAdditiveBlend
2 removeMesh
2 overlayAlphaShader
2 opts.sagittalNoseLeft
2 frac2mm
1 vox2frac
1 syncWith
1 setScale
1 setRenderDrawAmbientOcclusion
1 setDefaults
1 setCornerOrientationText
1 reverseFaces
1 refreshDrawing
1 processImage
1 overlayOutlineAlpha
1 opts.multiplanarPadPixels
1 opts.isV1SliceShader
1 opts.isRuler
1 opts.isMeshXRay
1 opts.isForceMouseClickToVoxelCenters
1 onMouseUp
1 mm2frac
1 loadFreeSurferConnectomeFromUrl
1 loadDocument
1 getMediaByUrl
1 generateLoadDocumentJavaScript
1 drawPt
1 drawFloodFill
1 drawBitmap
1 createOnLocationChange
1 createEmptyDrawing
1 closeDrawing
1 backgroundMasksOverlays
1 addMeshFromUrl
1 addMesh
1 addColormap
I would recommend making decisions about next steps based on the prevalence and usage of each of these APIs. In the context of Python, I don't know if many of them make sense and many are essentially aliases for nv.opts.<property> = x
and re-rendering, which should be covered by any opts
traitlet we have.
Finish adding docstrings in the code and update readme to include more information (getting started, overview, usage, development, etc)
edit: closing as this is a duplicate of issue #5
I accidentally committed some big files, here's a list of all files and sizes in this repo:
jan-hendrik@JanHendriksAir ipyniivue-experimental % git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | sed -n 's/^blob //p' | sort --numeric-sort --key=2
1ad618fc39005318c9a392b46901004044294244 16 js/widget.css
efdcc56c8a58a4208dd29536eb2a4f8b4ad759b7 24 README.md
714f35567ffdcff97389ead90105be11ec4ec695 71 README.md
5601e9b79b8f42cc72318f819ff80db2a83ed357 100 .gitignore
c3f92a3dd22d77be732b83918d4cb1b291952e9f 112 .gitignore
a30e105b63c1aa7099af5c961eba804989bf7275 146 old_demos/info.md
40a32078e2df8fce33502c5cf3700786f4cb6368 178 js/hmr.js
9897e5427ea77a506cb74e7bcdf7d9c1bbc102db 230 src/ipyniivue_experimental/__init__.py
0187c4b6b0469ad723215765442c15176003f0d6 255 package.json
5acdce3ad4f73e9fcd9b90976e9a31933eaab613 282 js/types.ts
4b92487dff9edc234492d559645d950fe2351810 283 package.json
ba09a4ff8198c20cc4370517bb9041da4afc038b 285 src/ipyniivue_experimental/__init__.py
3630bc6760d8dbecd6b96bc3341938635beb6d58 288 package.json
e7d37c5449b2da1f2144ca3fcf6b0e7c87b5476f 305 example.ipynb
9ab0d482c6d8bcd02c4cb32b7ca85e7ff002765a 315 package.json
bc4a6c8309657d472412fad9875d813d946e04fb 331 demo2.ipynb
4db0a1b4f6c8297b6dbc765d568d44f7d9b84553 333 demo1.ipynb
d7612426da30e1d6b62cc9ff155b6d036fd235f6 335 demo3.ipynb
a78d7c140065bee15b51302e89713c4b22970b95 342 demo4.ipynb
3f0e07105caafda3edd4e671a5e87cf18f6b30e8 351 js/types.ts
750f779758c56ffd8e13c5b86cf55b0a67279e90 359 src/ipyniivue_experimental/_constants.py
8e5905e5287c27fdf10bc7d69d8a5c2e26e7c6c9 370 js/types.ts
b8012832a1e50586f78400f3292036714056e97b 444 src/ipyniivue_experimental/__init__.py
5cc22b635fd005c196d1845b8e8e3215169da6cc 449 js/widget.css
db2b35d89c4dee85f72a96b77aafb7b3373a18df 450 js/widget.js
4d8e827c49548e3b8e3cf579b47a286e0c964b61 551 js/widget_send.js
9d5bc85b8cb37b45c20eafb559a9fd3b8d2ec0bd 564 js/widget_traitlet.js
614585333b7a141e1bdfa312c9fa6c2f8dec0096 570 js/widget_send.js
24b2b081cebc3a684a746a0bf7e943374cbf7bd3 572 js/widget_send.js
249bd4ac524c2811d411dfeb9f673d1fcb0656ee 573 js/widget_send.js
e78300e9ebf0bfdf4d821d1150887409d4dfaa11 582 README.md
340c38c3315df0a2fa603ba9ee18510eb58ba590 592 src/ipyniivue_experimental/_constants.py
7c43a69848da14107070b30974bbc79ebb79b7d4 598 js/widget.js
fdc645dc378d2cc0e2e92a078cde730e657e3b5e 611 README.md
efc8e78fd8cc10589ac9532e85658fae663a944f 618 js/widget_send.js
7803675fa04442cd1d9fbbacbc5f7f8362d557b7 625 js/widget_traitlet.js
1145bdfc358198a28b62a3f6e7fcded97cc95c21 634 js/widget_traitlet.js
1d49346de582e4bf9587232338dbe79a8c6afaa5 645 src/ipyniivue_experimental/_utils.py
cd5e0a5c931a3bf3b83dd4acbea6b3c380648d2d 646 js/widget_vscode.js
57fdf24faac6bf8dc66bec706fa78874dcafd3d6 663 js/widget_traitlet.js
f28c793c752d8aa73f64bb747793084c8450d1ee 663 js/slider.js
00d9059af86260077945d615d523dc2fd86fa360 677 pyproject.toml
40f1781679ce58f9b8a1b9c86f5be2d61cdbe765 677 pyproject.toml
95b9e0ab05ad02963e78087f0e1bb2bb7a8e6ed6 677 pyproject.toml
ad8fe8c79b95142f7ba97f92dd50db40b140af09 677 pyproject.toml
68586f5119c1952cdcbdc6d449253195ac520b88 683 js/widget_traitlet.js
48a1686836f395b19352b16e7911f5db06048763 699 pyproject.toml
3befb22355a27ad09954ab7e7863d2b8f2eb09ad 722 js/widget_traitlet.js
4b3942917e6cc54054e87037b58506bb4ef75e16 741 src/ipyniivue_experimental/__init__.py
097a66715192d519790ddbfda61524d9ae376877 782 js/widget_traitlet.js
03090c4c093059cb249391392f393ba1e516b587 785 js/widget_traitlet.js
01ef6478a00ab110876f1b01ee068cef0578ac19 808 pyproject.toml
9f096d5bc5190c040a5a7f32855df273c9394d81 808 pyproject.toml
e46b90f1b49e6dafd602527e013c7648bb4a8eb2 826 src/ipyniivue_experimental/__init__.py
0988c7a871df2a00ef429f3217a59434c3f1f64b 871 js/widget_traitlet.js
d514cf28adc6287b0eae1273160ae27ddc171eba 892 src/ipyniivue_experimental/__init__.py
20c4ba36656f20f7f5b5fffd1c1bbbe62d877420 923 README.md
e55b03e2dd7830d3d704f3983b33895c8f7e61f9 986 src/ipyniivue_experimental/__init__.py
cc2148b639352314e859f7daed03d8291b1a3a90 1001 README.md
4f63e128c1bd457e6264a5d419ecaffd9aeedb1c 1043 old_demos/demo5.ipynb
03a5211ab2a68eee4ca1f357e6c2644d8ebe7ae2 1055 js/widget_traitlet.js
b75cb9091c359ce7de3c878b4ef23c3eb1209f60 1063 js/widget_traitlet.js
dc2cda0b12013ac421d9229d29c9ef7661a3a05a 1076 LICENSE
99d59d3ddf1065e911a1bcde3cd5af7497312c2b 1102 old_demos/demo4.ipynb
b03615003955af1e83e8203e3a8ce68abc030475 1104 nb2.ipynb
1f5805c514b7c75beb73d694203e1cac9e4da75b 1107 old_demos/demo1.ipynb
967bdc735a883d97ea75c3284b71c1589a2f88bc 1136 nb3_traitlet_slider.ipynb
094782676a3c14e87035866ce3e213e4ffca67ce 1168 README.md
1befd58d6e68b5dadf4b8243245a769d6944b313 1194 old_demos/demo3.ipynb
8576773c440e3aa94b8210da3c2b1cff60ab8c8c 1212 README.md
fa1a54afbaede7b07f2344ef6be65a93f0a3a358 1212 demo1.ipynb
d266f531f123c98927c5beba941a55bfcb345e13 1221 demo1.ipynb
5edecf26225e1066c2b8a3e892c9ddb9b6d45c94 1236 README.md
285ad45863f5f160e15ae270b89e9402ca93580c 1248 js/widget_send.js
9122d4b343584c721a5462f60a35fbb796b92840 1308 demo3.ipynb
16dc1cb185ec1f3896ebe44540e18ece793394b0 1315 LICENSE
c8511333792520d7deba26c3773f3061d842a469 1315 nb2.ipynb
86537a2cb43eadab3247ec8e82dca5e73f77d622 1334 js/widget_traitlet.js
3bf4b1ea20df184c2ae271cf300d2d065c0ff5c6 1348 nb3_traitlet_slider.ipynb
5883bdc5d35fbafecd19240393c5e0e130bc963b 1357 nb3_traitlet_slider.ipynb
2ddf68d1bef849cf0f82f586a0fadf0580fac69e 1368 hmr.ipynb
ae3bdc354df0fa002051a19714c6a55c21965b7d 1374 js/widget_traitlet.js
3b68c338226a1146297e217d44c40612f1901038 1375 js/widget_vscode.js
5c2b563f57d5154d0b958f4321a886fe13adc673 1387 src/ipyniivue_experimental/__init__.py
2875b546f8a8eeba61cff8c1b0661bfd89027e25 1392 src/ipyniivue_experimental/_widget.py
4003d19588b364458972fdf11987a58f970af326 1394 old_demos/demo2.ipynb
62f4caf378d301d3ea87c571e781f5f3f05544a9 1447 js/widget_traitlet.js
7720dd43cb17439e693d85c920859c88e2c8e70e 1474 old_demos/minimal_verison_esm.ipynb
7be7df999d7b7c6fc5f0d479acda0e2f4f179ce5 1482 nb2_traitlet_simple.ipynb
fc8b43dd0cf0e64351381b1e55f7c2c8c2bbe961 1488 nb2_using_sliders.ipynb
8b803cfd0dc2f9fe3606986cec6eda17d2357340 1507 nb2.ipynb
4d7aa19566b87fc793ec2811677b5030fe79af2e 1539 js/widget_traitlet.js
d998e2d4d71e131e8a45f755a9e4673b6284acb9 1546 demo/additive_voxels.ipynb
43d3ab1e1d8efbeba0fbd80e7ff402fddd4ca1ec 1548 js/widget_traitlet.js
4f4a0efd569a4534d340a01b5d7be8505fd0f9d4 1563 src/ipyniivue_experimental/_widget.py
6d59c1fb9d70ed3f9fac95a6017fcf0ee3bccf47 1605 src/ipyniivue_experimental/_widget.py
0eaaccbbe044694d8159abb14f2dbe1ddffff332 1613 demo2.ipynb
2c973a174ab8838a441360b654ef61bb95603416 1624 nb2_traitlet_simple.ipynb
f50d9e9ee4f833facc7a40f23a93d5d258221140 1624 demo/basic_multiplanar.ipynb
afb605992916ba46d6129607b3e572de5a528b34 1625 demo/basic_multiplanar.ipynb
1ebf5b5dcb784710bc25d97002aaaa8d9aacf1db 1646 old_demos/blogpost.ipynb
f9eb3824b0ce6f8ccb05f154fadffffb3cbc7591 1646 nb1_traitlet_base54_data.ipynb
949bec8bd6363f5d628d8e1f3630a77c6f9c072f 1648 src/ipyniivue_experimental/_widget.py
8cd5e3a34a10c5bb2cb82b43e604500c98dbf346 1650 nb1_traitlet_base54_data.ipynb
10909fe38651d3334a05270db4ffa9839ab4576f 1670 nb2.ipynb
631dff3207c10b982d6910b976ce676b66993db2 1712 nb1_traitlet_base54_data.ipynb
78e7b782ab3b52be984f46dcbb69e9e8a4c5c7c1 1717 nb3_traitlet_slider.ipynb
d94346180c1ddb20300f03f4688880deb21a6577 1744 nb1b_send_return_colormap.ipynb
1fcbbe09c319b1409db257c89cb350d0e9de9f7d 1748 nb1_traitlet_base54_data.ipynb
b799988ff391a4e28a4982a0d3705084063f75c7 1750 js/widget_traitlet.js
0a603a9532f7b7fdd297a79e6374f0032584da63 1806 nb1_traitlet_base54_data.ipynb
32b9f6598186281cf89e1964319492cbc2c0be9d 1824 Untitled.ipynb
3d05caaa8f683bc9237497eb680f57cf10eae731 1824 Untitled.ipynb
70e6e288f92509a3e394f60eb0e56b016ccd0479 1824 Untitled.ipynb
8d5150c15f1df37580f34590057dc52f104651bd 1850 from_url_timing.ipynb
4bcc0b39390e80edac4ddffd41f2e5ecb564b873 1862 from_url_timing.ipynb
778b5f50fc54afa38bab4dd6e7a8fc9d7c9d95d5 1862 from_url_timing.ipynb
1ce6fd5466de6ded3aed2fef060550403f7dd012 1882 nb2.ipynb
4ae47a81fd19c05c1646be3ff9e821b8d9beaa6c 1919 nilearn_example1.ipynb
92e76ff8174f30835d2cf62627a9561114cd3a2f 2042 example_notebook.ipynb
0343e494eeecbe6631a32422347230ee7660c17d 2187 js/widget_traitlet.js
55ded97131185ddc17cba008fcaf110cce3f7a73 2190 js/widget_traitlet.js
292c3efa6f1486fcc2a7227de33f1cd7cc9b93a0 2201 nb2_traitlet_simple.ipynb
107957032598bfcfb23a7e32fc9bce68fb66cf12 2328 nb3_traitlet_slider.ipynb
bc0cd9312491408090b723306de3ec595077f68f 2328 nb2_traitlet_simple.ipynb
a1b41597a5b0a0b9f2af270487aef0d53a82cf8e 2330 nb3_traitlet_slider.ipynb
d14e7898237cc8c4b01c608150c09444b478a9d6 2330 nb3_traitlet_slider.ipynb
52e4aa022f69bd777dfc541d8d82e0c57df1a3d9 2404 nb2_traitlet_simple.ipynb
ccfbe0f858c88ad7038365f9c7a77b87abd67f9b 2414 nb1_send_api.ipynb
fc6286863791db919b910b591ac82131f3265472 2458 src/ipyniivue_experimental/__init__.py
c3a9568e4e7fc74ea64d8308253b87a8795e8bb0 2470 old_demos/blogpost.ipynb
6fc5c267f24587dc1b44088215133487549170d5 2479 nb2_traitlet_simple.ipynb
8b9686ac9cfdebe1c1df54fdda481bae9e4fdcea 2521 src/ipyniivue_experimental/__init__.py
be5a172c3db1293177004e1447f5a562636c8220 2567 nb1_send_api.ipynb
e41937878fd71a1e7c2d8478309e420e20fa3b78 2602 src/ipyniivue_experimental/__init__.py
e748f661fd8126d8fdbe4502351480dda5cf6c88 2613 src/ipyniivue_experimental/__init__.py
31bfa798b4b63c4ece93e0a95d8bd1913167c9b4 2644 example_notebook.ipynb
170d9ec1da4b1afdda2a315d93950a675f1060e5 2646 src/ipyniivue_experimental/__init__.py
41e8fcbbe85da7624731b84f121a5ea85e535364 2680 nb1_send_api.ipynb
91329e3f4472c81ceb0f9103a4950c0fa4cc6bf6 2701 demo/additive_voxels.ipynb
3f73d0b603b49b5cddc597c2436e765a41566cbc 2711 src/ipyniivue_experimental/__init__.py
9076fe7a9534cd6f46669a8b0b2100b9abd0ceb1 2903 nb1_send_api.ipynb
f3b86764ff0520e31f7df36f3a06d33d9710ece9 2940 js/widget_traitlet.js
68bc17f9ff2104a9d7b6777058bb4c343ca72609 3078 .gitignore
d703877ae17943fa502bdba93bcabd169c4e07d5 3193 issue_with_execution_oder.ipynb
96aa1f1149c93e28f0dc272f7b4bc6c30a7541a7 3280 js/widget.js
843fe670eec0cc9a6a7487e17ed36160be7b80e7 3308 js/widget.js
3e3742a64bf2f81ac6ae4b113c102fff0e1c0403 3349 old_demos/send_load_data.ipynb
1bd9c0ad1b5b78719ec282ba209a420d56026681 3543 nb2_traitlet_simple.ipynb
50cd1762080f46d9585f0a2c421bbaa70b2f7d19 3740 nb1_traitlet_base54_data.ipynb
f23307772460bd96c33f79c490c37cbbe6a53715 3806 nb1_send_api.ipynb
61a875a09adf5ef4d80f13f4724bdc7ae70c407c 3977 nb1_traitlet_base54_data.ipynb
971b8395341edaee532106e5657f1e46de51afdc 4000 images/hippo.nii.gz
c2cff21f27ff36b2b71d33541054ece853c55e75 4144 old_demos/send_load_data.ipynb
6cf64fc863f589f355b986b0daf6f690e4b68282 4379 scripts/generate_options_mixin.py
9c1557cdfafc19f1c0ea96c558adf216b67d8b22 4516 scripts/generate_options_mixin.py
8369902e6eddc62c5f7442fe567b912e8c5b0778 4639 old_demos/send_more_api_ideas.ipynb
83bf27c1e11b747c5ca1ffb8d8b07cde1ff1902c 4951 nb1_full_api.ipynb
9c97e876eef3f98895c093d6729e67335c32f6ab 4951 nb1_full_api.ipynb
f7951abbee3d48a46e8c19ecd4ef39b97bbeb2c2 5032 js/widget.js
30385e4220c03f6ffff6fee99af84dff481b3b0e 6538 js/widget.js
1d6044afa8d0b77f84c7b2b5e66e90feb3bf51c8 6785 js/widget.js
b7afe2ff2ef5ce4fe1e7b3faf0ba60442714ccd4 14420 src/ipyniivue_experimental/_options_mixin.py
4abde72100b09affd7bf7bc0eecb5e6f9cd721b9 14423 src/ipyniivue_experimental/_options_mixin.py
e98ed24e1e2d3c1475ea420cf02253db735d74b4 16200 package-lock.json
aac88985a7dd90cb94f293a0376c58de949aafd4 40755 package-lock.json
8f0c9e27ee3885caf106c603b8a40926b7f5180f 57688 nilearn_example2.ipynb
1f319ce82ec9ac8e176d7174f79fe069e3b3d762 150304 images/BrainMesh_ICBM152.lh.motor.mz3
7034f15c0f0095141f4b19bd8fd9d00981a0469c 163863 images/BrainMesh_ICBM152.lh.curv
08569935df453a5cfced100e1bdf671f76f43fc7 170571 images/Human.colin.R.FUNCTIONAL.71723.func.gii
1762283f5679ae6e8c95704a2e665fdb91bbf5ee 192337 original_gallery.md
501ef4141bc44c09b1ab8bd29a40a171dbc70086 779119 images/BrainMesh_ICBM152.lh.mz3
d33bcc0ca37688bab1cede1bb410d93a405041f4 1871424 images/Human.colin.Cerebral.R.VERY_INFLATED.71723.surf.gii
8faf08cb4b020dafa9fed1d81836207d18a7d089 4332921 images/mni152.nii.gz
34efdcd97193dd1f3a143f40844851ac7afe56ce 11574934 VSCode_version.ipynb
891f7b1f6406d99a7d888679a9e3ab00c82922f9 11575048 nb2_traitlet_simple.ipynb
521b7335d3721a37fea5321e2d456b647ac90a80 11578554 nb_VSCode_version.ipynb
0617b1a8ddade57726cb04737efbf35c8eba9a27 17355613 example_notebook.ipynb
cd5aa816a46af1467ad4f88c235cae378892113e 17355613 nb_example_notebook.ipynb
408b9fd6e83027ec0b7ac770a03ce9e61a0baa67 17356836 nb_example_notebook.ipynb
94a54dfecf0a1cbec5406276fe8f5affe1e26dbf 17356836 nb_example_notebook.ipynb
9ea14802ebe515ad863a24ead63f86a344a4e9ce 17357661 nb_example_notebook.ipynb
2163783c36c4263e715d9c516fb4bbd85245b094 34719279 nb2.ipynb
f6f683a005e68a0b104b30ef34d66c588f1f813b 34722149 nb2_traitlet_simple.ipynb
c2b88423f8ea9655b45ab1d7622e4cb3f15332ec 34722153 old_demos/old_traitet_load_from_jsnb2_traitlet_simple.ipynb
e57c72a9a1d6e78d0a4291979e656c58faf158e5 34722560 nb2_traitlet_simple.ipynb
8eb72db68b4976965482f2ff912eddaf63b7989e 92583773 send_from_url_timing.ipynb
therefore I'm deleting the 19 biggest files with git filter-branch :
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch \
images/BrainMesh_ICBM152.lh.motor.mz3 \
images/BrainMesh_ICBM152.lh.curv \
images/Human.colin.R.FUNCTIONAL.71723.func.gii \
images/BrainMesh_ICBM152.lh.mz3 \
images/Human.colin.Cerebral.R.VERY_INFLATED.71723.surf.gii \
images/mni152.nii.gz \
VSCode_version.ipynb \
nb2_traitlet_simple.ipynb \
nb_VSCode_version.ipynb \
example_notebook.ipynb \
nb_example_notebook.ipynb \
old_demos/old_traitet_load_from_jsnb2_traitlet_simple.ipynb \
send_from_url_timing.ipynb" \
--prune-empty --tag-name-filter cat -- --all
and then force pushed with
git push origin --force --all
If an ipyniivue.Niivue instance has already been displayed and then is displayed again in a different cell, the older display becomes non-interactive. The reason for the current implementation is multifold:
The reason for marking this as enhancement is for others to provide ideas for this feature (if there are use cases that require syncing behavior).
When initializing a new Niivue widget using the following script
from ipyniivue import Niivue
w = Niivue()
w.load_volumes([])
w
the canvas shows up as blank with a black background. The expected result would be to see red text on the black background saying "Waiting for images...".
The expected result shows only after the window has been resized.
edit: mybinder () can be used to view the bug in jupyter online
The following code
import ipyniivue
import time
nv = ipyniivue.Niivue()
t1 = time.time()
is_pial = nv.run_custom_code('this.nv.isMeshExt("test.pial")')
print(time.time() - t1)
launched on Google Colab takes about 60s to run. My understanding is that this code just checks if "pial" is a mesh extension, which should be a very fast operation. All similar calls seem to be that slow. This can be tested using the example notebook on running custom code.
Currently passing a Path object to Niivue.add_volume()
crashes. As simple path = str(path)
should fix that.
Describe the bug
When running the example code, the canvas is blank.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
Normal NiiVue display: The NiiVue instance should attach to the canvas that is created in the cell.
A blank canvas is created.
Currently, ipyniivue uses niivue version 0.24.2.
Updating to the latest niivue version currently breaks the widget (canvas height does not update).
In a notebook with cell #1
:
import ipyniivue
nv = ipyniivue.Niivue(crosshair_color=[0,1,0,1])
Cell #2
:
display(nv)
Cell #3
:
nv.add_volume("https://niivue.github.io/niivue/images/mni152.nii.gz")
nv.set_slice_type(nv.slice_type.render)
If I run in order cells #1
, #2
, #3
, restart the kernel, run cells #1
, #2
. At this point, the display from when I ran cell #3 before restarting the kernel is still displayed and responsive.
Note, this issue is not there if I have
Cell #1
:
import ipyniivue
nv = ipyniivue.Niivue(crosshair_color=[0,1,0,1])
display(nv)
Cell #2
:
nv.add_volume("https://niivue.github.io/niivue/images/mni152.nii.gz")
nv.set_slice_type(nv.slice_type.render)
And run #1
, #2
, restart, #1
I just tried exporting a jupyter notebook comprising a cell generated with ipyniivue. I exported it as an html file in order to see what kind of information would be embedded in the document.
In place of the output of the widget, there is a textual description of the widget. This was what I expected would happen, but I'd be interested to hear if you have plans for users to export static HTML files in the future!
Thank you for the nice library anyways! ๐
When running yarn run watch
, the watch:labextension
command fails (jupyter labextension watch .
) with the following error:
Building extension in .
/usr/local/anaconda3/envs/ipyniivue-dev/lib/python3.11/site-packages/jupyterlab/debuglog.py:56: UserWarning: An error occurred.
warnings.warn("An error occurred.")
/usr/local/anaconda3/envs/ipyniivue-dev/lib/python3.11/site-packages/jupyterlab/debuglog.py:57: UserWarning: ValueError: Extensions require a devDependency on @jupyterlab/builder@^4.0.0, you have a dependency on 3.6.3
warnings.warn(msg[-1].strip())
/usr/local/anaconda3/envs/ipyniivue-dev/lib/python3.11/site-packages/jupyterlab/debuglog.py:58: UserWarning: See the log file for details: /var/folders/hp/folder/T/jupyterlab-debug-zv28m1mq.log
warnings.warn(f"See the log file for details: {log_path!s}")
ERROR: "watch:labextension" exited with 1.
Editing "@jupyterlab/builder": "^3.0.0",
to "@jupyterlab/builder": "^4.0.0",
in the package.json file resolves the error, without having to rerun yarn install
. So, the "@jupyterlab/builder ^4.0.0" library isn't actually needed for the library to build.
Thus, one of the dependencies might have changed their requirements for @jupyterlab/builder
in a recent update.
It's worth noting that editing "@jupyterlab/builder": "^3.0.0",
to `"@jupyterlab/builder": "^4.0.0" breaks the pip install step. The widget requires the "@jupyterlab/builder ^3.0.0" to install.
Currently, the addVolume function only accepts 1 argument: the path of the volume. However, as can be seen in the niivue docs, addVolume should accept an NVImage object, which includes multiple attributes. The question is how allow for these multiple attributes in a pythonic way.
There are 2 possible methods that immediately come to mind:
class NVImage:
def __init__(
self,
data_buffer,
name,
color_map,
opacity,
paired_img_data,
cal_min,
cal_max,
trust_cal_min_max,
percentile_frac,
ignore_zero_voxels,
visible,
use_qform_not_sform,
color_map_negative,
frame_4D,
on_color_map_change,
on_opacity_change
):
self.data_buffer = data_buffer
self.name = name
self.color_map = color_map
self.opacity = opacity
self.paired_img_data = paired_img_data
self.cal_min = cal_min
self.cal_max = cal_max
self.trust_cal_min_max = trust_cal_min_max
self.percentile_frac = percentile_frac
self.ignore_zero_voxels = ignore_zero_voxels
self.visible = visible
self.use_qform_not_sform = use_qform_not_sform
self.color_map_negative = color_map_negative
self.frame_4D = frame_4D
self.on_color_map_change = on_color_map_change
self.on_opacity_change = on_opacity_change
class Niivue:
...
def addVolume(self, image):
#unpack NVImage object or send the object and somehow deserialize
nv = Niivue()
image = NVImage(name='test.nii.gz', data_buffer='[binary data here]')
nv.addVolume(image)
or
2) spread out the attributes of the NVImage class. This solution might look like:
class Niivue:
...
def addVolume(
self,
data_buffer,
name,
color_map,
opacity,
paired_img_data,
cal_min,
cal_max,
trust_cal_min_max,
percentile_frac,
ignore_zero_voxels,
visible,
use_qform_not_sform,
color_map_negative,
frame_4D,
on_color_map_change,
on_opacity_change,
):
#send over arguments to niivue
nv = Niivue()
nv.addVolume('[binary data here]', 'test.nii.gz')
setting default values for each of the attributes might also be good to do
The goal of this enhancement is to integrate more tightly NiiVue with MNE-Python. This was submitted to the MNE development team for further discussion in mne-tools/mne-python#12030.
bug when calling load_meshes() function. Error thrown due to gl property of niivue not being initialized
will investigate further
(issue brought up through mail)
add ability to add custom colormaps (https://github.com/niivue/niivue/blob/fd1d35ce853b41e35860d67d1de6a847e47614d7/demos/features/colormaps.html#L45)
possible usage if implemented:
cmap = {
"R": [0, 255, 22, 127],
"G": [0, 20, 192, 187],
"B": [0, 152, 80, 255],
"A": [0, 255, 255, 255],
"I": [0, 22, 222, 255],
};
key = "Custom";
nv.add_colormap(key, cmap);
Describe the bug
When a Niivue widget is created, 2 columns of turquoise can be seen on the left and right sides.
To Reproduce
Steps to reproduce the behavior:
npm install -g yarn
jupyter lab
Expected behavior
No turquoise should be visible unless the background color is set to turquoise. Since the default background color is black, the background should just consist of black.
Screenshots
Current display. The arrows at the bottom point to the thin strips of turquoise.
Describe the bug
When opening an .ipynb file that has already been run, the results section shows the error message:
Error displaying widget: model not found
To Reproduce
Steps to reproduce the behavior:
npm install -g yarn
jupyter lab
Error displaying widget: model not found
Expected behavior
A black background canvas with the red text "Waiting for images..." should display. This should be cached somehow.
Graph features need to be added into ipyniivue.
This time series demo shows the graph features in NiiVue: https://niivue.github.io/niivue/features/timeseries.html
nv1.graph.autoSizeMultiplanar = true;
nv1.opts.multiplanarForceRender = true;
nv1.graph.normalizeValues = false;
nv1.graph.opacity = 1.0;
Hi all, thanks for this really wonderful package! This is exactly what I always wished for (see this thread on neurostars). Wonder if it's already possible to add statistical images as overlays over the MNI-template?
The example code already works like a charm:
import ipyniivue
nv = ipyniivue.Niivue()
nv.add_volume('https://niivue.github.io/niivue/images/mni152.nii.gz')
nv
Wonder if I now could overlay it with a statistical map and save everything as an .html file? At least the niivue demos suggest that this should be possible in theory? Just not sure if it's already implemented in ipyniivue:
https://niivue.github.io/niivue/features/alphathreshold.html
https://niivue.github.io/niivue/features/save.html.html
Hello, I am new to the jupyter ecosystem, but I'm working on packaging Python projects for nixpkgs. As much as possible, we try to build a consistent package set using only the latest versions of each package. Do you know what it would take to support or upgrade to jupyterlab 4?
Currently, the Niivue widget (python) can get initialized with a set of options.
On the typescript side, Niivue first gets initialized with 0 inputs, and then the Niivue options are modified after.
Even though updateGLVolume
is called after each option modification, some options still do not get modified correctly.
The following example:
import numpy as np
import matplotlib.pyplot as plt
import mne
from mne.datasets import sample, fetch_hcp_mmp_parcellation
from mne.minimum_norm import apply_inverse, read_inverse_operator
from mne import read_evokeds
data_path = sample.data_path()
meg_path = data_path / 'MEG' / 'sample'
subjects_dir = data_path / 'subjects'
fname_evoked = meg_path / 'sample_audvis-ave.fif'
fname_stc = meg_path / 'sample_audvis-meg'
fetch_hcp_mmp_parcellation(subjects_dir)
nv = ipyniivue.Niivue(crosshair_color=[0,1,0,1])
nv.add_volume(str(fname_stc) + "-lh.stc")
fails to display the sources stored in the .stc format. According to https://github.com/niivue/niivue, NiiVue supports this format. Here is the results from this code when ran on Google Colab:
I set set up a GitHub workflow to build the docs page (GitHub pages still needs to be set up with the documentation branch on this repo, but that can be done later). The GitHub workflow runs the Sphynx build commands in the /docs folder and then deploys the built docs in a new branch called "documentation". GitHub pages then reads the documentation branch.
The GitHub workflow: https://github.com/niivue/ipyniivue/blob/main/.github/workflows/docs.yml
The documentation branch: https://github.com/niivue/ipyniivue/tree/documentation
Hosted docs site on my fork (for testing purposes): https://anthonyandroulakis.github.io/ipyniivue
I decided to use GitHub pages since the niivue docs are in https://niivue.github.io/niivue, so it'd make sense to then have the ipyniivue docs in https://niivue.github.io/ipyniivue.
I am having an issue with the Sphinx documentation on GitHub Pages. Specifically, the CSS is not rendering properly and some files are inaccessible. For example, clicking on "Show Source" at the bottom of the API Reference page leads to a 404 page.
You can view these problems here: https://anthonyandroulakis.github.io/ipyniivue
I just noticed this git repository and was wondering if this has yet started? I think this could be a great addition to jupyter for neuroimaging analyses.
I was wondering if there are any plans to make this a reality?
Describe the bug
Each NiivueView is a div element with a canvas as its child. The expected behavior is that the div element gets constrained to the same dimensions as the canvas (instead of taking the full width of the cell).
To Reproduce
Expected behavior
The widget dimensions should be constrained. If no width and height inputs are given, then the dimensions of the div element should be the default width and height (640 and 480).
Screenshots
As you can see in the screenshot, the canvas dimensions are constrained correctly, but the div dimensions are not.
from AnthonyAndroulakis/ipyniivue_ts#2 by @christian-oreilly
Thanks for putting this notebook integration together. It looks great and promising! I tried this tool using the use cases of visualizing 1) a NIfTI file I have and 2) EEG sources computed from MNE-Python. The widget displays as expected using
from ipyniivue import Niivue w = Niivue() wHowever, I am afraid that is where my use cases hit a dead end. The first python-programmer reflex, I think, would be to check the docstring for the Niivue class. Checking it through the IPython
?
operator:Init signature: Niivue(*args, **kwargs) Docstring: Widget that can be inserted into the DOM Init docstring: Public constructor File: ~/Library/CloudStorage/[email protected]/My Drive/Code/ipyniivue_ts/ipyniivue/widget.py Type: MetaHasTraits Subclasses:
This does not help me as there are no named arguments. I cannot introspect the bells and whistles available on this widget. The second reflex: going through the code on GitHub. Looking at it, it appears there is about no Python coding in the wrapper on this widget. It is an extremely thin wrapper, exposing no public methods and attributes. This means that I would need to dig into javascript or ts which, as a Python programmer (I suppose, the intended target), is a no-go.
Third reflex, running
dir(Niivue)
which lists the attributes and methods of the class. That is a bit more useful as it at least give me some methods to explore, but these do not seem to be methods specific to the functionalities of that specific widget... I could search my way in the dark and teach myself what is traitlets and how to use this framework, but the bottom line is that to attract a significant Python user base, I think this project will need to :1 - Have a deeper richer wrapper, exposing useful methods and attributes for programmatic interaction in Python.
2 - Provide additional documentation to illustrate how most common operations (loading a file, panning, rotating, zooming, changing color map, etc.) can be performed.
Hi there!
@jens-ox reached out to me on Twitter and asked for tips to connect nivvue to Jupyter.
I just had some time playing around with this library and came up with this prototype using anywidget and react:
https://github.com/kolibril13/anywidget-ipyniivue (example notebook in repo)
pip install anywidget_ipyniivue
Here's how the widget backend is defined:
https://github.com/kolibril13/anywidget-ipyniivue/blob/0d39d180b1dcb1d9b139e11a914366174436e513/src/anywidget_ipyniivue/__init__.py#L13-L17
and here's how the frontend is defined:
https://github.com/kolibril13/anywidget-ipyniivue/blob/main/js/widget.jsx
As @alexisthual suggested in today's meeting, it would be nice to expose nv.saveScene
in order to generate static iamges.
Here's an example on how nv.saveScene
is used in niivue:
https://github.com/niivue/ipyniivue-experimental/blob/main/original_gallery.md#color-maps-for-voxels
Umbrella issue tracking "reactive" properties for Volume:
opacity
niivue/ipyniivue-experimental#4colormap
niivue/ipyniivue-experimental#4frame_4d
?cal_min
niivue/ipyniivue-experimental#6cal_max
niivue/ipyniivue-experimental#6As can be seen from https://github.com/niivue/ipyniivue/actions/runs/5906991594/job/16024117873?pr=37, Windows CI works fine but not MacOS and Linux. The problem seems to be due to that part of the notebook tractography.ipynb
Apparently, in these two OS, the call to nv.load_meshes
fails to add the mesh to nv.meshes
so the indexing of that list at [0]
fails. Looking under the hood a bit, what seems to fail is nv._send_custom(['loadMeshes', [mesh_list]])
which just seems to use the send
method inherited from the ipywidgets
DOMWidget
class to send the command to NiiVue... so could the problem be on the NiiVue/JS side? @cdrake or @AnthonyAndroulakis , do you mind having a peak at it?
I have been working on documenting a bit better the Google Colab examples and adding at least one new. In doing so, it came to mind that these need to be part of the CI tests so that updates breaking these are flagged by the CI and blocked from merging. These notebooks can be automatically converted in Python script easily with:
jupyter nbconvert --to script [YOUR_NOTEBOOK].ipynb
I guess that these could be then integrated in pytest
with something like (untested):
from pathlib import Path
import runpy
def test_notebooks():
notebooks = Path([PATH_TO_NOTEBOOKS]).glob("*.ipynb")
for notebook in notebooks:
command = f'jupyter nbconvert --to script {notebook}`
subprocess.call([command],shell=True)
runpy.run_path(path_name=notebook.with_suffix(".py"))
update ipyniivue to use the latest version of widget-ts-cookiecutter
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.