Skip to content

Commit 4973363

Browse files
authored
Restructure (#11)
1 parent 01bb16a commit 4973363

File tree

13 files changed

+721
-217
lines changed

13 files changed

+721
-217
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ RUN git clone https://github.com/SpikeInterface/spikeinterface.git && \
3131
# Install spikeinterface-gui from source
3232
RUN git clone https://github.com/alejoe91/spikeinterface-gui.git && \
3333
cd spikeinterface-gui && \
34-
git checkout 3af19c6ac4187354cbd4066fec492afd98a69cc5 && \
34+
git checkout c165cfe91ea8dcfdc69836184db9827304d357f9 && \
3535
pip install . && cd ..
3636

3737

3838
EXPOSE 8000
39-
ENTRYPOINT ["sh", "-c", "panel serve src/aind_ephys_portal/ephys_portal_app.py src/aind_ephys_portal/ephys_gui_app.py --setup src/aind_ephys_portal/setup.py --static-dirs images=src/aind_ephys_portal/images --address 0.0.0.0 --port 8000 --allow-websocket-origin ${ALLOW_WEBSOCKET_ORIGIN} --keep-alive 10000 --index ephys_portal_app.py --num-procs 4 --warm"]
39+
ENTRYPOINT ["sh", "-c", "panel serve src/aind_ephys_portal/ephys_gui_app.py src/aind_ephys_portal/ephys_portal_app.py src/aind_ephys_portal/ephys_launcher_app.py src/aind_ephys_portal/ephys_monitor_app.py --setup src/aind_ephys_portal/setup.py --static-dirs images=src/aind_ephys_portal/images --address 0.0.0.0 --port 8000 --allow-websocket-origin ${ALLOW_WEBSOCKET_ORIGIN} --index ephys_portal_app.py --check-unused-sessions 2000 --unused-session-lifetime 5000 --num-procs 4"]

README.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,24 @@ pip install -e .
2424

2525
## Usage
2626

27-
To run the Ephys Portal:
27+
To run the Ephys Portal you can either use tha `launch.sh` script:
2828

2929
```bash
30-
# Run using Panel CLI (recommended for server deployment)
31-
panel serve src/aind_ephys_portal/ephys_portal_app.py src/aind_ephys_portal/ephys_gui_app.py --setup src/aind_ephys_portal/setup.py --static-dirs images=src/aind_ephys_portal/images --num-procs 8
30+
bash launch.sh
31+
```
32+
33+
or serve panel apps directly:
34+
35+
```bash
36+
panel serve \
37+
src/aind_ephys_portal/ephys_gui_app.py \
38+
src/aind_ephys_portal/ephys_launcher_app.py \
39+
src/aind_ephys_portal/ephys_monitor_app.py \
40+
src/aind_ephys_portal/ephys_log_app.py \
41+
--setup src/aind_ephys_portal/setup.py \
42+
--static-dirs images=src/aind_ephys_portal/images \
43+
--index ephys_portal_app.py \
44+
--num-procs 4
3245
```
3346

3447
This will start a Panel server and make the application available in your web browser.

launch.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/sh
2+
3+
# Clean up old logs
4+
rm -rf /tmp/aind_ephys_logs
5+
6+
panel serve \
7+
src/aind_ephys_portal/ephys_gui_app.py \
8+
src/aind_ephys_portal/ephys_portal_app.py \
9+
src/aind_ephys_portal/ephys_launcher_app.py \
10+
src/aind_ephys_portal/ephys_monitor_app.py \
11+
--setup src/aind_ephys_portal/setup.py \
12+
--static-dirs images=src/aind_ephys_portal/images \
13+
--check-unused-sessions 2000 \
14+
--unused-session-lifetime 5000 \
15+
--num-procs 4

src/aind_ephys_portal/docdb/database.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,27 @@ def get_raw_asset_by_name(asset_name: str):
102102

103103

104104
@pn.cache(ttl=TIMEOUT_1H)
105-
def get_all_ecephys_derived() -> List[Dict[str, Any]]:
105+
def get_all_ecephys_derived(additional_includes_in_name: str | None = None) -> List[Dict[str, Any]]:
106106
"""Get a limited set of all records from the database.
107107
108108
Returns
109109
-------
110110
list[dict]
111111
List of records, limited to 50 entries.
112+
additional_includes_in_name : str, optional
113+
Comma-separated list of additional fields to include in the results, by default None
112114
"""
113115
filter_query = {"data_description.modality.abbreviation": "ecephys", "data_description.data_level": "derived"}
114-
response = client.retrieve_docdb_records(
116+
responses = client.retrieve_docdb_records(
115117
filter_query=filter_query,
116118
)
117-
return response
119+
responses_filt = []
120+
if additional_includes_in_name:
121+
additional_fields = [field.strip() for field in additional_includes_in_name.split(",")]
122+
for record in responses:
123+
for field in additional_fields:
124+
if field in record["name"]:
125+
responses_filt.append(record)
126+
else:
127+
responses_filt = responses
128+
return responses_filt

src/aind_ephys_portal/ephys_gui_app.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import urllib
44
import param
55

6-
76
import panel as pn
7+
88
pn.extension("tabulator", "gridstack")
99

1010
# Import for side effects: registers SpikeInterface GUI custom Panel/Bokeh models/resources
@@ -19,16 +19,19 @@ class Settings(param.Parameterized):
1919

2020
analyzer_path = param.String(default="")
2121
recording_path = param.String(default="")
22+
fast_mode = param.Boolean(
23+
default=False,
24+
doc="Whether to enable fast mode (skips waveforms and principal components)"
25+
)
2226

2327

2428
settings = Settings()
25-
pn.state.location.sync(settings, {"analyzer_path": "analyzer_path", "recording_path": "recording_path"})
29+
pn.state.location.sync(settings, {"analyzer_path": "analyzer_path", "recording_path": "recording_path", "fast_mode": "fast_mode"})
2630

2731
# Manually decode stream_name after syncing
2832
settings.analyzer_path = urllib.parse.unquote(settings.analyzer_path)
2933
settings.recording_path = urllib.parse.unquote(settings.recording_path)
3034

31-
ephys_gui = EphysGuiView(analyzer_path=settings.analyzer_path, recording_path=settings.recording_path)
32-
app_layout = pn.Column(ephys_gui.layout, sizing_mode="stretch_both", min_height=600)
35+
ephys_gui = EphysGuiView(analyzer_path=settings.analyzer_path, recording_path=settings.recording_path, fast_mode=settings.fast_mode)
3336

34-
app_layout.servable(title="AIND Ephys GUI")
37+
ephys_gui.panel().servable(title="AIND Ephys GUI")
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""Standalone launcher page to generate GUI links."""
2+
3+
import urllib.parse
4+
5+
import panel as pn
6+
7+
from aind_ephys_portal.panel.logging import setup_logging
8+
from aind_ephys_portal.panel.utils import EPHYSGUI_LINK_PREFIX
9+
10+
pn.extension()
11+
12+
13+
class EphysLauncher:
14+
def __init__(self):
15+
setup_logging()
16+
17+
self.analyzer_input = pn.widgets.TextInput(
18+
name="Analyzer path",
19+
value="",
20+
height=50,
21+
sizing_mode="stretch_width",
22+
)
23+
self.recording_input = pn.widgets.TextInput(
24+
name="Recording path (optional)",
25+
value="",
26+
height=50,
27+
sizing_mode="stretch_width",
28+
)
29+
30+
self.generate_button = pn.widgets.Button(
31+
name="Generate Link",
32+
button_type="primary",
33+
height=45,
34+
sizing_mode="stretch_width",
35+
)
36+
37+
self.fast_mode_checkbox = pn.widgets.Checkbox(
38+
name="Enable Fast Mode (skip waveforms and PCs)",
39+
value=False,
40+
height=30,
41+
sizing_mode="stretch_width",
42+
)
43+
44+
self.link_pane = pn.pane.HTML("No link generated yet.", sizing_mode="stretch_width")
45+
self.url_output = pn.widgets.TextInput(
46+
name="GUI URL",
47+
value="",
48+
disabled=True,
49+
sizing_mode="stretch_width",
50+
)
51+
52+
self.generate_button.on_click(self._update_link)
53+
54+
self.layout = pn.Column(
55+
pn.pane.Markdown("## AIND Ephys Launcher"),
56+
self.analyzer_input,
57+
self.recording_input,
58+
pn.Row(self.generate_button, self.fast_mode_checkbox, sizing_mode="stretch_width"),
59+
self.link_pane,
60+
self.url_output,
61+
sizing_mode="stretch_width",
62+
)
63+
64+
def _build_gui_url(self):
65+
analyzer_path = self.analyzer_input.value.strip()
66+
recording_path = self.recording_input.value.strip()
67+
if not analyzer_path:
68+
return None
69+
70+
analyzer_path_q = urllib.parse.quote(analyzer_path, safe="")
71+
recording_path_q = urllib.parse.quote(recording_path, safe="") if recording_path else ""
72+
path = EPHYSGUI_LINK_PREFIX.format(analyzer_path_q, recording_path_q)
73+
if self.fast_mode_checkbox.value:
74+
path += "&fast_mode=true"
75+
76+
location = pn.state.location
77+
if location is not None:
78+
href = getattr(location, "href", None)
79+
if href:
80+
parsed = urllib.parse.urlparse(href)
81+
if parsed.scheme and parsed.netloc:
82+
return f"{parsed.scheme}://{parsed.netloc}{path}"
83+
84+
protocol = getattr(location, "protocol", "")
85+
hostname = getattr(location, "hostname", "")
86+
port = getattr(location, "port", "")
87+
if protocol and hostname:
88+
netloc = f"{hostname}:{port}" if port else hostname
89+
return f"{protocol}//{netloc}{path}"
90+
91+
return path
92+
93+
def _update_link(self, event=None):
94+
url = self._build_gui_url()
95+
if not url:
96+
self.link_pane.object = "<span style='color: orange;'>⚠️ Analyzer path is required.</span>"
97+
self.url_output.value = ""
98+
return
99+
print(f"Generated URL: {url}")
100+
101+
self.link_pane.object = f'<a href="{url}" target="_blank">Ephys Curation GUI</a>'
102+
self.url_output.value = url
103+
104+
105+
app = EphysLauncher()
106+
app.layout.servable(title="AIND Ephys Launcher")

0 commit comments

Comments
 (0)