Skip to content

Commit

Permalink
Merge pull request #3 from panel-extensions/migrate_app
Browse files Browse the repository at this point in the history
Migrating NeuroglancerNB class to dedicated repo
  • Loading branch information
d-v-b authored Dec 3, 2024
2 parents 0c47240 + 21a81fe commit 0afe642
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 10 deletions.
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
# `panel-neuroglancer`
Use [`panel`](https://panel.holoviz.org/) to embed [neuroglancer](https://www.github.com/google/neuroglancer) in Jupyter notebooks
Use [`panel`](https://panel.holoviz.org/) to embed [neuroglancer](https://www.github.com/google/neuroglancer) in Jupyter notebooks.

## Usage
### Option 1 - Load from a neuroglancer url:
Either use `NeuroglancerNB(source=<URL>)` or just run `NeuroglancerNB()` and input the URL in the GUI:
![](assets/demo_fromurl.png)


### Option 2 - Load from `neuroglancer.Viewer` instance:

```python
viewer = neuroglancer.Viewer()

with viewer.txn() as s:
# Add an image layer from a precomputed data source
s.layers["image"] = neuroglancer.ImageLayer(
source="precomputed://gs://neuroglancer-janelia-flyem-hemibrain/emdata/clahe_yz/jpeg",
)
# Add a segmentation layer
s.layers["segmentation"] = neuroglancer.SegmentationLayer(
source="precomputed://gs://neuroglancer-janelia-flyem-hemibrain/v1.1/segmentation",
)
NeuroglancerNB(source=viewer, show_state=True)
```
![](assets/demo_fromviewer.png)
Binary file added assets/demo_fromurl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/demo_fromviewer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/panel_neuroglancer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from panel_neuroglancer.neuroglancer_nbv import NeuroglancerNbViewer
from panel_neuroglancer.neuroglancer_nb import NeuroglancerNb

__all__ = ["NeuroglancerNbViewer"]
__all__ = ["NeuroglancerNb"]
174 changes: 174 additions & 0 deletions src/panel_neuroglancer/neuroglancer_nb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import panel as pn
import neuroglancer

pn.extension()
class NeuroglancerNB(pn.viewable.Viewer):
"""
A HoloViz Panel app for visualizing and interacting with Neuroglancer viewers
within a Jupyter Notebook.
This app supports loading from a parameterized Neuroglancer URL or an existing
`neuroglancer.viewer.Viewer` instance.
"""

DEMO_URL = "https://neuroglancer-demo.appspot.com/#!%7B%22dimensions%22%3A%7B%22x%22%3A%5B6.000000000000001e-9%2C%22m%22%5D%2C%22y%22%3A%5B6.000000000000001e-9%2C%22m%22%5D%2C%22z%22%3A%5B3.0000000000000004e-8%2C%22m%22%5D%7D%2C%22position%22%3A%5B5029.42333984375%2C6217.5849609375%2C1182.5%5D%2C%22crossSectionScale%22%3A3.7621853549999242%2C%22projectionOrientation%22%3A%5B-0.05179581791162491%2C-0.8017329573631287%2C0.0831851214170456%2C-0.5895944833755493%5D%2C%22projectionScale%22%3A4699.372698097029%2C%22layers%22%3A%5B%7B%22type%22%3A%22image%22%2C%22source%22%3A%22precomputed%3A%2F%2Fgs%3A%2F%2Fneuroglancer-public-data%2Fkasthuri2011%2Fimage%22%2C%22tab%22%3A%22source%22%2C%22name%22%3A%22original-image%22%7D%2C%7B%22type%22%3A%22image%22%2C%22source%22%3A%22precomputed%3A%2F%2Fgs%3A%2F%2Fneuroglancer-public-data%2Fkasthuri2011%2Fimage_color_corrected%22%2C%22tab%22%3A%22source%22%2C%22name%22%3A%22corrected-image%22%7D%2C%7B%22type%22%3A%22segmentation%22%2C%22source%22%3A%22precomputed%3A%2F%2Fgs%3A%2F%2Fneuroglancer-public-data%2Fkasthuri2011%2Fground_truth%22%2C%22tab%22%3A%22source%22%2C%22selectedAlpha%22%3A0.63%2C%22notSelectedAlpha%22%3A0.14%2C%22segments%22%3A%5B%223208%22%2C%224901%22%2C%2213%22%2C%224965%22%2C%224651%22%2C%222282%22%2C%223189%22%2C%223758%22%2C%2215%22%2C%224027%22%2C%223228%22%2C%22444%22%2C%223207%22%2C%223224%22%2C%223710%22%5D%2C%22name%22%3A%22ground_truth%22%7D%5D%2C%22layout%22%3A%224panel%22%7D"

def __init__(
self,
source=None,
aspect_ratio=2.75,
show_state=False,
load_demo=False,
**params,
):
"""
Initializes the NeuroglancerNB app.
Parameters
----------
source : str or neuroglancer.viewer.Viewer, optional
Source for the initial state of the viewer, which can be a URL string or an existing `neuroglancer.viewer.Viewer` instance.
If None, a new viewer will be initialized without a predefined state.
aspect_ratio : float, optional
The width-to-height ratio for the window-responsive Neuroglancer viewer. Default is 2.75.
show_state : bool, optional
Provides a collapsible card widget under the viewer that displays the viewer's state.
Useful for debugging. Default is False.
load_demo : bool, optional
If True, loads the demo dataset upon initialization. Default is False.
**params
Additional parameters passed to the parent class.
"""
super().__init__(**params)
self.source_not_provided = False if source else True
self.show_state = show_state
self.viewer = (
source
if isinstance(source, neuroglancer.viewer.Viewer)
else neuroglancer.Viewer()
)

self._setup_ui_components(aspect_ratio=aspect_ratio)
self._configure_viewer()
self._setup_callbacks()

if source and not isinstance(source, neuroglancer.viewer.Viewer):
self._initialize_viewer_from_url(source)

if load_demo:
self.demo_button.clicks += 1

def _initialize_viewer_from_url(self, source: str):
self.url_input.value = source
self._load_state_from_url(source)

def _setup_ui_components(self, aspect_ratio):
self.url_input = pn.widgets.TextInput(
placeholder="Enter a Neuroglancer URL and click Load",
name="Input URL",
width=700,
)

self.load_button = pn.widgets.Button(
name="Load", button_type="primary", width=75
)
self.demo_button = pn.widgets.Button(
name="Demo", button_type="warning", width=75
)

self.json_pane = pn.pane.JSON(
{}, theme="light", depth=2, name="Viewer State", height=800, width=350
)

self.shareable_url_pane = pn.pane.Markdown("**Shareable URL:**")
self.local_url_pane = pn.pane.Markdown("**Local URL:**")

self.iframe = pn.pane.HTML(
sizing_mode="stretch_both",
aspect_ratio=aspect_ratio,
min_height=800,
styles={"resize": "both", "overflow": "hidden"},
)

def _configure_viewer(self):
self._update_local_url()
self._update_iframe_with_local_url()

def _setup_callbacks(self):
self.load_button.on_click(self._on_load_button_clicked)
self.demo_button.on_click(self._on_demo_button_clicked)
self.viewer.shared_state.add_changed_callback(self._on_viewer_state_changed)

def _on_demo_button_clicked(self, event):
self.url_input.value = self.DEMO_URL
self._load_state_from_url(self.url_input.value)

def _on_load_button_clicked(self, event):
self._load_state_from_url(self.url_input.value)

def _load_state_from_url(self, url):
try:
new_state = self._parse_state_from_url(url)
self.viewer.set_state(new_state)
except Exception as e:
print(f"Error loading Neuroglancer state: {e}")

def _parse_state_from_url(self, url):
return neuroglancer.parse_url(url)

def _on_viewer_state_changed(self):
self._update_shareable_url()
self._update_json_pane()

def _update_shareable_url(self):
shareable_url = neuroglancer.to_url(self.viewer.state)
self.shareable_url_pane.object = self._generate_dropdown_markup(
"Shareable URL", shareable_url
)

def _update_local_url(self):
self.local_url_pane.object = self._generate_dropdown_markup(
"Local URL", self.viewer.get_viewer_url()
)

def _update_iframe_with_local_url(self):
iframe_style = (
'frameborder="0" scrolling="no" marginheight="0" marginwidth="0" '
'style="width:100%; height:100%; min-width:500px; min-height:500px;"'
)
self.iframe.object = (
f'<iframe src="{self.viewer.get_viewer_url()}" {iframe_style}></iframe>'
)

def _update_json_pane(self):
self.json_pane.object = self.viewer.state.to_json()

def _generate_dropdown_markup(self, title, url):
return f"""
<details>
<summary><b>{title}:</b></summary>
<a href="{url}" target="_blank">{url}</a>
</details>
"""

def __panel__(self):
# only visible if no source is provided
controls_layout = pn.Column(
pn.Row(self.demo_button, self.load_button),
pn.Row(self.url_input),
visible=self.source_not_provided,
)
links_layout = pn.Column(self.local_url_pane, self.shareable_url_pane)

state_widget = pn.Card(
self.json_pane,
title="State",
collapsed=False,
visible=self.show_state,
styles={"background": "WhiteSmoke"},
max_width=350
)
return pn.Column(
controls_layout,
links_layout,
pn.Row(state_widget, self.iframe))
7 changes: 0 additions & 7 deletions src/panel_neuroglancer/neuroglancer_nbv.py

This file was deleted.

0 comments on commit 0afe642

Please sign in to comment.