Skip to content

Commit

Permalink
feat: XR viewer (#18)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
iisakkirotko and maartenbreddels authored Jan 29, 2025
1 parent a44b7a8 commit c8e180f
Show file tree
Hide file tree
Showing 33 changed files with 3,738 additions and 7 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ jobs:
with:
python-version: "3.10"

- name: Install NodeJS
uses: actions/setup-node@v4

- name: Install uv
uses: astral-sh/setup-uv@v3

Expand Down
3 changes: 3 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
192.168.189.140 {
reverse_proxy localhost:8765
}
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# glue-solara

development installation:
development installation requires **npm/nodejs**:

$ pip install -e ".[dev]"
$ pre-commit install

## Serve on local network

For this, we use [caddy](https://caddyserver.com/) as a reverse proxy. After [installing caddy](https://caddyserver.com/docs/install), run

```bash
$ caddy run
```
100 changes: 96 additions & 4 deletions glue_solara/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import glue.core.hub
import glue.core.message as msg
import glue_jupyter as gj
import glue_jupyter.app
import glue_jupyter.bqplot.histogram
import glue_jupyter.bqplot.image
import glue_jupyter.bqplot.scatter
Expand All @@ -15,6 +14,7 @@
from glue.viewers.common.viewer import Viewer
from glue_jupyter.data import require_data
from glue_jupyter.registries import viewer_registry
from glue_jupyter.utils import validate_data_argument
from solara import Reactive

from .hooks import ClosedMessage, use_glue_watch, use_glue_watch_close, use_layers_watch
Expand All @@ -23,14 +23,88 @@
from .viewers import GridViewers, MdiViewers, TabbedViewers
from .viewers.mdi import MDI_HEADER_SIZES, MdiWindow

# Initialize our viewer, since it isn't a 'proper' plugin (yet)
if "xr" not in viewer_registry.members:
from .viewers.xr import setup as xr_setup

xr_setup()


class JupyterApplicationWithXR(gj.JupyterApplication):
def xr_scatter3d(self, *, data=None, x=None, y=None, z=None, widget="xr", show=True):
"""
Open an interactive 3d scatter plot viewer.
Parameters
----------
data : str or `~glue.core.data.Data`, optional
The initial dataset to show in the viewer. Additional
datasets can be added later using the ``add_data`` method on
the viewer object.
x : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the x axis.
y : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the y axis.
z : str or `~glue.core.component_id.ComponentID`, optional
The attribute to show on the z axis.
widget : {'ipyvolume', 'vispy', 'xr'}
Whether to use ipyvolume, VisPy, or ThreeJS as the front-end.
show : bool, optional
Whether to show the view immediately (`True`) or whether to only
show it later if the ``show()`` method is called explicitly
(`False`).
"""

if widget == "ipyvolume":
from glue_jupyter.ipyvolume import IpyvolumeScatterView

viewer_cls = IpyvolumeScatterView
elif widget == "vispy":
from glue_vispy_viewers.scatter.jupyter import JupyterVispyScatterViewer

viewer_cls = JupyterVispyScatterViewer
elif widget == "xr":
from glue_solara.viewers.xr.viewer import XRBaseView

viewer_cls = XRBaseView
else:
raise ValueError("widget= should be 'ipyvolume', 'vispy', or 'xr'")

data = validate_data_argument(self.data_collection, data)

view = self.new_data_viewer(viewer_cls, data=data, show=show)
if x is not None:
x = data.id[x]
view.state.x_att = x
if y is not None:
y = data.id[y]
view.state.y_att = y
if z is not None:
z = data.id[z]
view.state.z_att = z

if data.label == "boston_planes_6h":
view.state.x_att = data.id["x"]
view.state.y_att = data.id["y"]
view.state.z_att = data.id["altitude"]

if data.label == "w5_psc":
view.state.x_att = data.id["RAJ2000"]
view.state.y_att = data.id["DEJ2000"]
view.state.z_att = data.id["Jmag"]

return view


# logging.basicConfig(level="INFO", force=True)
# logging.getLogger("glue").setLevel("DEBUG")

if not Path("w5.fits").exists():
require_data("Astronomy/W5/w5.fits")
if not Path("w5_psc.csv").exists():
require_data("Astronomy/W5/w5_psc.csv")

if not Path("boston_planes_6h.csv").exists():
require_data("Planes/boston_planes_6h.csv")

main_color = "#d0413e"
nice_colors = [
Expand All @@ -49,7 +123,12 @@

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

VIEWER_METHODS = {"Histogram": "histogram1d", "Scatter": "scatter2d", "Image": "imshow"}
VIEWER_METHODS = {
"Histogram": "histogram1d",
"Scatter": "scatter2d",
"Image": "imshow",
"Xr": "xr_scatter3d",
}

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

def create_glue_application() -> gj.JupyterApplication:
app = glue_jupyter.app.JupyterApplication()
app = JupyterApplicationWithXR()
return app

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

def add_plane_data():
path = "boston_planes_6h.csv"
data_image = app.load_data(path)
data_image.style.color = "blue"

solara.Button(
"Add Boston planes data",
on_click=add_plane_data,
text=True,
icon_name="mdi-airplane",
style={"width": "100%", "justify-content": "left"},
)

def add_w5():
data_image = app.load_data("w5.fits")
data_catalog = app.load_data("w5_psc.csv")
Expand Down
9 changes: 9 additions & 0 deletions glue_solara/viewers/xr/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Add the viewer as a plugin to glue-jupyter, see
# https://glue-jupyter.readthedocs.io/en/latest/developer_notes.html#adding-new-viewers-via-plug-ins
def setup():
from glue_jupyter.registries import viewer_registry

from .tools import XRHullSelectionTool as XRHullSelectionTool
from .viewer import XRBaseView

viewer_registry.add("xr", XRBaseView)
Loading

0 comments on commit c8e180f

Please sign in to comment.