Skip to content

Commit 425ba07

Browse files
authored
Adds native spelunker support (#414)
* adds spelunker as main neuroglancer distro * adds native spelunker support * run precommit * bug fixes and readability improvements * adds inspect for spelunker tasks * updates email and disables debug
1 parent d67b095 commit 425ba07

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+19655
-10614
lines changed

.codespellrc

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[codespell]
22
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
3-
skip = .git*,*.svg,package-lock.json,*.css,*-min.*,.codespellrc,*.bundle.*,*.map
3+
skip = .git*,*.svg,package-lock.json,*.css,*-min.*,.codespellrc,*.bundle.*,*.map, *.js
44
check-hidden = true
5-
# ignore-regex =
5+
# ignore-regex =
66
# some favorite albeit unfortunate variable names
77
ignore-words-list = te

.pre-commit-config.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,3 @@ repos:
1414
rev: v2.3.0
1515
hooks:
1616
- id: codespell
17-

neuvue_project/neuvue/settings.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
For the full list of settings and their values, see
1010
https://docs.djangoproject.com/en/3.2/ref/settings/
1111
"""
12+
1213
import os
1314

1415
# Build paths inside the project like this: BASE_DIR / 'subdir'.
@@ -24,6 +25,8 @@
2425
# SECURITY WARNING: don't run with debug turned on in production!
2526
DEBUG = False
2627

28+
SITE_ID = 2
29+
2730
ALLOWED_HOSTS = [
2831
".neuvue.io",
2932
".elasticbeanstalk.com",
@@ -242,4 +245,7 @@
242245

243246
mimetypes.add_type("application/javascript", ".js", True)
244247

245-
STATIC_NG_FILES = os.listdir(os.path.join(BASE_DIR, "workspace", "static", "workspace"))
248+
STATIC_NG_FILES = os.listdir(
249+
os.path.join(BASE_DIR, "workspace", "static", "workspace")
250+
) + os.listdir(os.path.join(BASE_DIR, "workspace", "static", "spelunker-workspace"))
251+
# STATIC_NG_FILES = os.listdir(os.path.join(BASE_DIR, "workspace", "static", "workspace-spelunker"))

neuvue_project/neuvue/urls.py

+5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@
4747
path("tasks/", TaskView.as_view(), name="tasks"),
4848
path("getting-started/", GettingStartedView.as_view(), name="getting-started"),
4949
path("workspace/<str:namespace>", WorkspaceView.as_view(), name="workspace"),
50+
path(
51+
"spelunker-workspace/<str:namespace>",
52+
WorkspaceView.as_view(),
53+
name="spelunker-workspace",
54+
),
5055
path("admin/", admin.site.urls),
5156
path("__debug__/", include("debug_toolbar.urls")),
5257
path("accounts/", include("allauth.urls")),

neuvue_project/neuvueDB.sqlite3

0 Bytes
Binary file not shown.

neuvue_project/templates/about.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ <h2 class="pt-5 pb-3"> Collaborators </h2>
8282
<h2 class="pt-5 pb-3"> Contact Us </h2>
8383

8484
<p>
85-
We'd love to hear from you! Reach out at <a class="text-secondary-color-activated" href="mailto:[email protected]">info@neuvue.io</a> with questions or if you are interested in collaborating with us.
85+
We'd love to hear from you! Reach out at <a class="text-secondary-color-activated" href="mailto:[email protected]">info@bossdb.org</a> with questions or if you are interested in collaborating with us.
8686
</p>
8787

8888
</div>

neuvue_project/templates/inspect.html

+35-8
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,11 @@ <h3 class="text-white mb-3"> Inspect Task </h3>
101101
</div>
102102
</div>
103103
</div>
104-
<script type="text/javascript" src="{% static "workspace/main.bundle.js" %}"></script>
104+
{% if ng_host == "spelunker" %}
105+
<script type="module" src="{% static 'spelunker-workspace/562.1d0cecad9cc0725955f0.js' %}"></script><script type="module" src="{% static 'spelunker-workspace/993.ca23372ceee17151541a.js' %}"></script><script type="module" src="{% static 'spelunker-workspace/367.ad6071abcab399305157.js' %}"></script><script type="module" src="{% static 'spelunker-workspace/main.cb26101b553ed6b82856.js' %}"></script><link href="{% static 'spelunker-workspace/main.cb26101b553ed6b82856.css' %}" rel="stylesheet">
106+
{% else %}
107+
<script type="text/javascript" src="{% static 'workspace/main.bundle.js' %}"></script>
108+
{% endif %}
105109
<script type="text/javascript" src="{% static "js/utils.js" %}"></script>
106110
{% endif%}
107111
<style> .overlay-hidden { display:none; } </style>
@@ -111,18 +115,41 @@ <h3 class="text-white mb-3"> Inspect Task </h3>
111115
<script type="text/javascript">
112116

113117
function getLink() {
114-
viewer.postJsonState(true, undefined, true, function() {
115-
let url_prefix = "https://neuroglancer.neuvue.io/?json_url="
116-
copyToClipboard(url_prefix.concat(viewer.saver.savedUrl));
117-
});
118+
{% if ng_host == "neuvue" %}
119+
viewer.postJsonState(true, undefined, true, function() {
120+
let url_prefix = "https://neuroglancer.neuvue.io/?json_url="
121+
copyToClipboard(url_prefix.concat(viewer.saver.savedUrl));
122+
});
123+
{% elif ng_host == "spelunker" %}
124+
let url_prefix = "https://spelunker.cave-explorer.org/#!";
125+
copyToClipboard(url_prefix.concat(JSON.stringify(viewer.state.toJSON())));
126+
{% else %}
127+
//pass
128+
{% endif %}
129+
}
130+
131+
// Function to attempt restoring the viewer state
132+
function tryRestoreState() {
133+
if (typeof viewer !== 'undefined') {
134+
// Viewer is defined, attempt to restore the state
135+
try {
136+
var received_data = {{ ng_state|safe }};
137+
viewer.state.restoreState(received_data);
138+
console.log('Viewer state restored successfully.');
139+
} catch (error) {
140+
console.error('Error restoring viewer state:', error);
141+
}
142+
} else {
143+
// Viewer is not yet defined, retry after a short delay
144+
setTimeout(tryRestoreState, 100); // Retry every 100 milliseconds
145+
}
118146
}
119147

120148
// Neuroglancer State Load
121149
$(document).ready(function() {
122150
{% if ng_state %}
123-
const state = {{ ng_state|safe }};
124-
openSideMenu();
125-
viewer.state.restoreState(state);
151+
openSideMenu();
152+
tryRestoreState();
126153
{% endif %}
127154
})
128155

neuvue_project/templates/tasks.html

+8
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@
4141
aria-expanded="false">
4242
<span class="sr-only header-box-text">Toggle Dropdown</span>
4343
</button>
44+
{% if context.ng_host == "spelunker" %}
45+
<span class="header-button header-outline rightBorderRounded" onclick="document.location='{% url 'spelunker-workspace' namespace=namespace %}'">
46+
{% else %}
4447
<span class="header-button header-outline rightBorderRounded" onclick="document.location='{% url 'workspace' namespace=namespace %}'">
48+
{% endif %}
4549
<button class="header-box-text"
4650
onclick="triggerLoadingSpinner('{{namespace}}NgSpinner')">
4751
<div id='{{namespace}}NgSpinner'>
@@ -77,7 +81,11 @@
7781
</div>
7882
</div>
7983
{% else %}
84+
{% if context.ng_host == "spelunker" %}
85+
<div class="header-sizing header-outline rightBorderRounded" onclick="document.location='{% url 'spelunker-workspace' namespace=namespace %}'">
86+
{% else %}
8087
<div class="header-sizing header-outline rightBorderRounded" onclick="document.location='{% url 'workspace' namespace=namespace %}'">
88+
{% endif %}
8189
<button class="header-box-text"
8290
onclick="triggerLoadingSpinner('{{namespace}}NgSpinner')">
8391
<div id='{{namespace}}NgSpinner'>

neuvue_project/templates/workspace.html

+63-11
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,13 @@
278278
<script src="https://cdn.jsdelivr.net/npm/@yaireo/tagify/dist/tagify.polyfills.min.js"></script>
279279
<script type="text/javascript" src="{% static 'js/browser-interaction-time.umd.js' %}"></script>
280280
<script type="text/javascript" src="{% static 'js/utils.js' %}"></script>
281+
282+
{% if ng_host == "spelunker" %}
283+
<script type="module" src="{% static 'spelunker-workspace/562.1d0cecad9cc0725955f0.js' %}"></script><script type="module" src="{% static 'spelunker-workspace/993.ca23372ceee17151541a.js' %}"></script><script type="module" src="{% static 'spelunker-workspace/367.ad6071abcab399305157.js' %}"></script><script type="module" src="{% static 'spelunker-workspace/main.cb26101b553ed6b82856.js' %}"></script><link href="{% static 'spelunker-workspace/main.cb26101b553ed6b82856.css' %}" rel="stylesheet">
284+
{% else %}
281285
<script type="text/javascript" src="{% static 'workspace/main.bundle.js' %}"></script>
286+
{% endif %}
287+
282288
<script type="text/javascript">
283289

284290
// initialize Tagify on the above input node reference
@@ -299,14 +305,23 @@
299305
})
300306

301307
function getLink() {
302-
viewer.postJsonState(true, undefined, true, function() {
303-
let url_prefix = "https://neuroglancer.neuvue.io/?json_url="
304-
copyToClipboard(url_prefix.concat(viewer.saver.savedUrl));
305-
});
308+
{% if ng_host == "neuvue" %}
309+
viewer.postJsonState(true, undefined, true, function() {
310+
let url_prefix = "https://neuroglancer.neuvue.io/?json_url="
311+
copyToClipboard(url_prefix.concat(viewer.saver.savedUrl));
312+
triggerToast("Copied link to clipboard");
313+
});
314+
{% elif ng_host == "spelunker" %}
315+
let url_prefix = "https://spelunker.cave-explorer.org/#!";
316+
copyToClipboard(url_prefix.concat(JSON.stringify(viewer.state.toJSON())));
317+
triggerToast("Copied link to clipboard");
318+
{% else %}
319+
triggerToast("Copy Link unavailable on embedded Neuroglancer task type");
320+
{% endif %}
306321
}
307322

308323
function saveState() {
309-
{% if not ng_url %}
324+
{% if ng_host == "neuvue" %}
310325
viewer.postJsonState(true, undefined, true, function() {
311326
let state = viewer.saver.savedUrl;
312327
let post_body = JSON.stringify({'task_id':"{{task_id}}", 'ng_state' : state});
@@ -320,7 +335,8 @@
320335
});
321336
});
322337
{% else %}
323-
triggerToast("Autosave disabled for embedded neuroglancer");
338+
triggerToast("State saving disabled for non-native Neuroglancer");
339+
removeLoadingSpinner("save_state", "Save State");
324340
{% endif %}
325341
}
326342

@@ -373,6 +389,16 @@
373389
}
374390
}
375391

392+
function getOperationIdsFromSpelunker(){
393+
for (let i = 0; i < viewer.layerManager.managedLayers.length; i++) {
394+
const layer = viewer.layerManager.managedLayers[i].layer_;
395+
if (layer.type === 'segmentation') {
396+
const operationIds = layer.graphConnection.value.operationIds.toString();
397+
return operationIds.split(',').map(Number);
398+
}
399+
}
400+
}
401+
376402
function submitForm(value, form='#mainForm') {
377403
window.removeEventListener('beforeunload', exitAlert);
378404
let duration = Math.round(browserTimer.getTimeInMilliseconds()/1000);
@@ -383,8 +409,16 @@
383409
$('<input>').attr('type', 'hidden').attr('name', 'duration').attr('value', duration).appendTo(form);
384410
$(form).submit();
385411

386-
{% else %}
412+
{% elif ng_host == "spelunker" %}
413+
updateTrackedOperations(getOperationIdsFromSpelunker())
414+
let state = JSON.stringify(viewer.state.toJSON());
415+
$('<input>').attr('type', 'hidden').attr('name', 'ngState').attr('value', state).appendTo(form);
416+
$('<input>').attr('type', 'hidden').attr('name', 'button').attr('value', value).appendTo(form);
417+
$('<input>').attr('type', 'hidden').attr('name', 'duration').attr('value', duration).appendTo(form);
418+
419+
$(form).submit();
387420

421+
{% else %}
388422
updateTrackedOperations(operation_ids)
389423
viewer.postJsonState(true, undefined, true, function() {
390424
let state = viewer.saver.savedUrl;
@@ -453,6 +487,23 @@
453487
selected_button.classList.toggle("active");
454488
}
455489

490+
// Function to attempt restoring the viewer state
491+
function tryRestoreState() {
492+
if (typeof viewer !== 'undefined') {
493+
// Viewer is defined, attempt to restore the state
494+
try {
495+
var received_data = {{ ng_state|safe }};
496+
viewer.state.restoreState(received_data);
497+
console.log('Viewer state restored successfully.');
498+
} catch (error) {
499+
console.error('Error restoring viewer state:', error);
500+
}
501+
} else {
502+
// Viewer is not yet defined, retry after a short delay
503+
setTimeout(tryRestoreState, 100); // Retry every 100 milliseconds
504+
}
505+
}
506+
456507
// Button Submission
457508
$(document).ready(function() {
458509

@@ -468,13 +519,14 @@
468519

469520
// Specific to built-in NG
470521
{% if not ng_url%}
471-
var received_data = {{ ng_state|safe }};
472-
viewer.state.restoreState(received_data);
473-
474-
522+
tryRestoreState()
475523
// Track operation IDs every three seconds
476524
window.setInterval(function() {
525+
{% if ng_host == "spelunker" %}
526+
updateTrackedOperations(getOperationIdsFromSpelunker());
527+
{% else %}
477528
updateTrackedOperations(operation_ids);
529+
{% endif %}
478530
}, 3000);
479531
{% endif %}
480532

neuvue_project/workspace/analytics.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
# import the logging library
88
import logging
9-
9+
import warnings
1010
logging.basicConfig(level=logging.DEBUG)
1111
# Get an instance of a logger
1212
logger = logging.getLogger(__name__)
@@ -111,8 +111,10 @@ def create_stats_table(pending_tasks, closed_tasks):
111111
for namespace, namespace_df in df.groupby("namespace"):
112112
n_tasks = len(namespace_df)
113113
m = namespace_df[status].min()
114-
average_event_time = (m + (namespace_df[status] - m)).mean().to_pydatetime()
115-
changelog.append((average_event_time, n_tasks, status, namespace))
114+
with warnings.catch_warnings():
115+
warnings.simplefilter("ignore")
116+
average_event_time = (m + (namespace_df[status] - m)).mean().to_pydatetime()
117+
changelog.append((average_event_time, n_tasks, status, namespace))
116118

117119
# Sort changelogs by datetime
118120
daily_changelog_items = sorted(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Generated by Django 4.2.11 on 2024-10-21 20:47
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("workspace", "0021_alter_namespace_ng_host"),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name="namespace",
15+
name="ng_host",
16+
field=models.CharField(
17+
choices=[
18+
("neuvue", "NeuVue Legacy"),
19+
("spelunker", "Spelunker Native"),
20+
("https://neuroglancer.bossdb.io", "neuroglancer.bossdb.io"),
21+
(
22+
"https://spelunker.cave-explorer.org/",
23+
"spelunker.cave-explorer.org",
24+
),
25+
],
26+
default="neuvue",
27+
max_length=100,
28+
),
29+
),
30+
]

neuvue_project/workspace/models.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@ class NeuroglancerHost(models.TextChoices):
3939
spelunker.cave-explorer.org -> embedded neuroglancer developed by AIBS.
4040
"""
4141

42-
NEUVUE = "neuvue", _("NeuVue Built-in")
42+
NEUVUE = "neuvue", _("NeuVue Legacy")
43+
SPELUNKER = "spelunker", _("Spelunker Native")
4344
BOSSDB = "https://neuroglancer.bossdb.io", _("neuroglancer.bossdb.io")
44-
# CLIO = "https://clio-ng.janelia.org", _("clio-ng.janelia.org")
45-
SPELUNKER = "https://spelunker.cave-explorer.org/", _("spelunker.cave-explorer.org")
45+
SPELUNKER_URL = "https://spelunker.cave-explorer.org/", _(
46+
"spelunker.cave-explorer.org"
47+
)
4648

4749

4850
class ForcedChoiceButtonGroup(models.Model):

neuvue_project/workspace/neuroglancer.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,13 @@
2525
ChainedStateBuilder,
2626
)
2727

28-
from .models import Namespace, NeuroglancerLinkType, PcgChoices, ImageChoices
28+
from .models import (
29+
Namespace,
30+
NeuroglancerLinkType,
31+
PcgChoices,
32+
ImageChoices,
33+
NeuroglancerHost,
34+
)
2935

3036

3137
logging.basicConfig(level=logging.DEBUG)
@@ -273,7 +279,10 @@ def construct_proofreading_state(task_df, points, return_as="json"):
273279

274280

275281
def construct_url_from_existing(state: str, ng_host: str):
276-
return ng_host + "/#!" + state
282+
if ng_host in [NeuroglancerHost.SPELUNKER, NeuroglancerHost.SPELUNKER_URL]:
283+
return ng_host + "/#!middleauth+" + state
284+
else:
285+
return ng_host + "/#!" + state
277286

278287

279288
@backoff.on_exception(backoff.expo, Exception, max_tries=3)
@@ -285,6 +294,7 @@ def get_from_state_server(url: str):
285294
Returns:
286295
(str): JSON String
287296
"""
297+
url = url.replace("middleauth+", "")
288298
headers = {
289299
"content-type": "application/json",
290300
"Authorization": f"Bearer {os.environ['CAVECLIENT_TOKEN']}",

0 commit comments

Comments
 (0)