Skip to content

Commit c8e180f

Browse files
feat: XR viewer (#18)
* feat: XR viewer using ipyreact & threejs * add caddyfile * feat: make determining selected points more efficient * feat: improve build process We now separate three.js and the @react-three packages into their own bundles to improve extendability. * feat, build: include js bundles in package * chore: update dependencies Co-authored-by: Maarten Breddels <[email protected]> --------- Co-authored-by: Maarten Breddels <[email protected]>
1 parent a44b7a8 commit c8e180f

33 files changed

+3738
-7
lines changed

.github/workflows/test.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ jobs:
2525
with:
2626
python-version: "3.10"
2727

28+
- name: Install NodeJS
29+
uses: actions/setup-node@v4
30+
2831
- name: Install uv
2932
uses: astral-sh/setup-uv@v3
3033

Caddyfile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
192.168.189.140 {
2+
reverse_proxy localhost:8765
3+
}

README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# glue-solara
22

3-
development installation:
3+
development installation requires **npm/nodejs**:
44

55
$ pip install -e ".[dev]"
66
$ pre-commit install
7+
8+
## Serve on local network
9+
10+
For this, we use [caddy](https://caddyserver.com/) as a reverse proxy. After [installing caddy](https://caddyserver.com/docs/install), run
11+
12+
```bash
13+
$ caddy run
14+
```

glue_solara/app.py

+96-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import glue.core.hub
66
import glue.core.message as msg
77
import glue_jupyter as gj
8-
import glue_jupyter.app
98
import glue_jupyter.bqplot.histogram
109
import glue_jupyter.bqplot.image
1110
import glue_jupyter.bqplot.scatter
@@ -15,6 +14,7 @@
1514
from glue.viewers.common.viewer import Viewer
1615
from glue_jupyter.data import require_data
1716
from glue_jupyter.registries import viewer_registry
17+
from glue_jupyter.utils import validate_data_argument
1818
from solara import Reactive
1919

2020
from .hooks import ClosedMessage, use_glue_watch, use_glue_watch_close, use_layers_watch
@@ -23,14 +23,88 @@
2323
from .viewers import GridViewers, MdiViewers, TabbedViewers
2424
from .viewers.mdi import MDI_HEADER_SIZES, MdiWindow
2525

26+
# Initialize our viewer, since it isn't a 'proper' plugin (yet)
27+
if "xr" not in viewer_registry.members:
28+
from .viewers.xr import setup as xr_setup
29+
30+
xr_setup()
31+
32+
33+
class JupyterApplicationWithXR(gj.JupyterApplication):
34+
def xr_scatter3d(self, *, data=None, x=None, y=None, z=None, widget="xr", show=True):
35+
"""
36+
Open an interactive 3d scatter plot viewer.
37+
38+
Parameters
39+
----------
40+
data : str or `~glue.core.data.Data`, optional
41+
The initial dataset to show in the viewer. Additional
42+
datasets can be added later using the ``add_data`` method on
43+
the viewer object.
44+
x : str or `~glue.core.component_id.ComponentID`, optional
45+
The attribute to show on the x axis.
46+
y : str or `~glue.core.component_id.ComponentID`, optional
47+
The attribute to show on the y axis.
48+
z : str or `~glue.core.component_id.ComponentID`, optional
49+
The attribute to show on the z axis.
50+
widget : {'ipyvolume', 'vispy', 'xr'}
51+
Whether to use ipyvolume, VisPy, or ThreeJS as the front-end.
52+
show : bool, optional
53+
Whether to show the view immediately (`True`) or whether to only
54+
show it later if the ``show()`` method is called explicitly
55+
(`False`).
56+
"""
57+
58+
if widget == "ipyvolume":
59+
from glue_jupyter.ipyvolume import IpyvolumeScatterView
60+
61+
viewer_cls = IpyvolumeScatterView
62+
elif widget == "vispy":
63+
from glue_vispy_viewers.scatter.jupyter import JupyterVispyScatterViewer
64+
65+
viewer_cls = JupyterVispyScatterViewer
66+
elif widget == "xr":
67+
from glue_solara.viewers.xr.viewer import XRBaseView
68+
69+
viewer_cls = XRBaseView
70+
else:
71+
raise ValueError("widget= should be 'ipyvolume', 'vispy', or 'xr'")
72+
73+
data = validate_data_argument(self.data_collection, data)
74+
75+
view = self.new_data_viewer(viewer_cls, data=data, show=show)
76+
if x is not None:
77+
x = data.id[x]
78+
view.state.x_att = x
79+
if y is not None:
80+
y = data.id[y]
81+
view.state.y_att = y
82+
if z is not None:
83+
z = data.id[z]
84+
view.state.z_att = z
85+
86+
if data.label == "boston_planes_6h":
87+
view.state.x_att = data.id["x"]
88+
view.state.y_att = data.id["y"]
89+
view.state.z_att = data.id["altitude"]
90+
91+
if data.label == "w5_psc":
92+
view.state.x_att = data.id["RAJ2000"]
93+
view.state.y_att = data.id["DEJ2000"]
94+
view.state.z_att = data.id["Jmag"]
95+
96+
return view
97+
98+
2699
# logging.basicConfig(level="INFO", force=True)
27100
# logging.getLogger("glue").setLevel("DEBUG")
28101

29102
if not Path("w5.fits").exists():
30103
require_data("Astronomy/W5/w5.fits")
31104
if not Path("w5_psc.csv").exists():
32105
require_data("Astronomy/W5/w5_psc.csv")
33-
106+
if not Path("boston_planes_6h.csv").exists():
107+
require_data("Planes/boston_planes_6h.csv")
34108

35109
main_color = "#d0413e"
36110
nice_colors = [
@@ -49,7 +123,12 @@
49123

50124
VIEWER_TYPES = list(map(lambda k: k.title(), viewer_registry.members.keys()))
51125

52-
VIEWER_METHODS = {"Histogram": "histogram1d", "Scatter": "scatter2d", "Image": "imshow"}
126+
VIEWER_METHODS = {
127+
"Histogram": "histogram1d",
128+
"Scatter": "scatter2d",
129+
"Image": "imshow",
130+
"Xr": "xr_scatter3d",
131+
}
53132

54133
TITLE_TRANSLATIONS = {
55134
"BqplotScatterView": "2d Scatter",
@@ -70,7 +149,7 @@ def Page():
70149
"""This component is used by default in solara server (standalone app)"""
71150

72151
def create_glue_application() -> gj.JupyterApplication:
73-
app = glue_jupyter.app.JupyterApplication()
152+
app = JupyterApplicationWithXR()
74153
return app
75154

76155
# make the app only once
@@ -422,6 +501,19 @@ def LoadData(app: gj.JupyterApplication):
422501
)
423502
solara.Button("Upload data", text=True, icon_name="mdi-cloud-upload", disabled=True)
424503

504+
def add_plane_data():
505+
path = "boston_planes_6h.csv"
506+
data_image = app.load_data(path)
507+
data_image.style.color = "blue"
508+
509+
solara.Button(
510+
"Add Boston planes data",
511+
on_click=add_plane_data,
512+
text=True,
513+
icon_name="mdi-airplane",
514+
style={"width": "100%", "justify-content": "left"},
515+
)
516+
425517
def add_w5():
426518
data_image = app.load_data("w5.fits")
427519
data_catalog = app.load_data("w5_psc.csv")

glue_solara/viewers/xr/__init__.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Add the viewer as a plugin to glue-jupyter, see
2+
# https://glue-jupyter.readthedocs.io/en/latest/developer_notes.html#adding-new-viewers-via-plug-ins
3+
def setup():
4+
from glue_jupyter.registries import viewer_registry
5+
6+
from .tools import XRHullSelectionTool as XRHullSelectionTool
7+
from .viewer import XRBaseView
8+
9+
viewer_registry.add("xr", XRBaseView)

0 commit comments

Comments
 (0)