-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from panel-extensions/migrate_app
Migrating NeuroglancerNB class to dedicated repo
- Loading branch information
Showing
6 changed files
with
200 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |
This file was deleted.
Oops, something went wrong.