Skip to content

Commit d15843b

Browse files
authored
Merge pull request #109 from PytorchConnectomics/leander/ieee-vis-revision
updated hotkey support for opacity toggling and drawing tools
2 parents a36c267 + 702d2c0 commit d15843b

11 files changed

Lines changed: 225 additions & 30 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ SynAnno is a tool designed for interactive guided proofreading and correction of
44

55
## Live Demo
66

7-
A demo is available [here](http://16.170.214.77/reset).
7+
A simplified version of the app, starting directly in the Error Categorization view, is available [here](http://16.170.214.77/demo).
8+
9+
For the full functionality and configuration options of SynAnno, try the complete version [here](http://16.170.214.77/reset).
810

911
## Table of Contents
1012

synanno/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ def register_routes(app):
182182
from synanno.routes.annotation import blueprint as annotation_blueprint
183183
from synanno.routes.auto_annotate import blueprint as auto_annotate_blueprint
184184
from synanno.routes.categorize import blueprint as categorize_blueprint
185+
from synanno.routes.demo import blueprint as demo_blueprint
185186
from synanno.routes.false_negatives import blueprint as fn_blueprint
186187
from synanno.routes.file_access import blueprint as file_access_blueprint
187188
from synanno.routes.finish import blueprint as finish_blueprint
@@ -199,6 +200,7 @@ def register_routes(app):
199200
app.register_blueprint(manual_annotate_blueprint)
200201
app.register_blueprint(auto_annotate_blueprint)
201202
app.register_blueprint(fn_blueprint)
203+
app.register_blueprint(demo_blueprint)
202204

203205

204206
def setup_context_processors(app):

synanno/backend/ng_util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ def setup_ng(
263263
# if a neuron id is already set in the app context, explicitly select it
264264
if getattr(app, "selected_neuron_id", None) is not None:
265265
with app.ng_viewer.txn() as s:
266-
s.layer.layers["neuropil"].segments = frozenset(
266+
s.layers["neuropil"].segments = frozenset(
267267
[getattr(app, "selected_neuron_id", None)]
268268
)
269269

synanno/routes/demo.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import logging
2+
3+
import pandas as pd
4+
from flask import Blueprint, current_app, render_template, session
5+
6+
import synanno.backend.ng_util as ng_util
7+
from synanno import initialize_global_variables
8+
from synanno.backend.processing import (
9+
calculate_number_of_pages_for_neuron_section_based_loading,
10+
determine_volume_dimensions,
11+
load_cloud_volumes,
12+
)
13+
from synanno.routes.opendata import (
14+
calculate_scale_factor,
15+
handle_neuron_view,
16+
set_coordinate_resolution,
17+
)
18+
19+
logging.basicConfig(level="INFO")
20+
logger = logging.getLogger(__name__)
21+
22+
blueprint = Blueprint("demo", __name__)
23+
24+
25+
@blueprint.route("/demo", methods=["GET"])
26+
def demo():
27+
"""Displays a loading page and resets the session storage.
28+
29+
Returns:
30+
Renders the loading page.
31+
"""
32+
33+
# Reset logic
34+
session.clear()
35+
initialize_global_variables(current_app)
36+
37+
return render_template("demo_setup.html", next_route="/demo_annotation")
38+
39+
40+
@blueprint.route("/demo_annotation", methods=["GET"])
41+
def demo_init():
42+
"""Initializes the demo data and renders the annotation view.
43+
44+
Returns:
45+
Renders the annotation view after initializing the demo data.
46+
"""
47+
# Upload logic
48+
current_app.view_style = "view_style"
49+
current_app.tiles_per_page = 12
50+
51+
current_app.crop_size_x = 256
52+
current_app.crop_size_y = 256
53+
current_app.crop_size_z = 6
54+
55+
current_app.coordinate_order = {"x": (4, 8), "y": (4, 8), "z": (33, 33)}
56+
57+
source_url = "gs://h01-release/data/20210601/4nm_raw"
58+
target_url = "gs://h01-release/data/20210729/c3/synapses/whole_ei_onlyvol"
59+
neuropil_url = "gs://h01-release/data/20210601/proofread_104"
60+
61+
current_app.synapse_data = pd.read_csv("/app/h01/h01_104_materialization.csv")
62+
63+
load_cloud_volumes(source_url, target_url, neuropil_url, "~/.cloudvolume/secrets")
64+
65+
current_app.vol_dim = determine_volume_dimensions()
66+
67+
set_coordinate_resolution()
68+
calculate_scale_factor()
69+
70+
current_app.vol_dim_scaled = tuple(
71+
int(a * b) for a, b in zip(current_app.vol_dim, current_app.scale.values())
72+
)
73+
74+
current_app.selected_neuron_id = 2325998949
75+
76+
handle_neuron_view(neuropil_url)
77+
current_app.neuron_ready = "true"
78+
current_app.n_pages = calculate_number_of_pages_for_neuron_section_based_loading()
79+
80+
if current_app.ng_version is None:
81+
ng_util.setup_ng(
82+
app=current_app._get_current_object(),
83+
source="precomputed://" + source_url,
84+
target="precomputed://" + target_url,
85+
neuropil="precomputed://" + neuropil_url,
86+
)
87+
88+
page = 1
89+
return render_template(
90+
"annotation.html",
91+
page=page,
92+
n_pages=current_app.n_pages,
93+
grid_opacity=current_app.grid_opacity,
94+
neuron_id=current_app.selected_neuron_id,
95+
neuronReady=current_app.neuron_ready,
96+
neuronSections=current_app.sections,
97+
synapsePointCloud=current_app.snapped_point_cloud,
98+
activeNeuronSection=(
99+
current_app.page_section_mapping[page][0]
100+
if page in current_app.page_section_mapping
101+
else 0
102+
),
103+
activeSynapseIDs=current_app.synapse_data.query("page == @page").index.tolist(),
104+
)

synanno/static/annotation.js

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,22 @@ let currentRequestController = new AbortController();
33
$(document).ready(() => {
44
const currentPage = $("script[src*='annotation.js']").data("current-page");
55
reloadImages(currentPage);
6+
7+
// Bind hotkeys for label opacity and toggle-label button
8+
$(document).on("keydown", (event) => {
9+
const hotkey = event.key.toLowerCase();
10+
const $button = $(`[data-hotkey="${hotkey}"]`);
11+
12+
if ($button.length && !$button.prop("disabled")) {
13+
event.preventDefault();
14+
$button.trigger("click");
15+
}
16+
});
17+
18+
window.check_gt = function () {
19+
$imgTarget.toggle();
20+
$toggleLabel.toggleClass("btn-secondary");
21+
};
622
});
723

824
$(document).on("shown.bs.modal", "#drawModalFN", () => {
@@ -11,7 +27,7 @@ $(document).on("shown.bs.modal", "#drawModalFN", () => {
1127

1228
const handleSaveBboxClick = async () => {
1329
const currentPage = $("script[src*='annotation.js']").data("current-page");
14-
$('#loading-bar').css('display', 'flex');
30+
$("#loading-bar").css("display", "flex");
1531

1632
$(".text-white").text("Saving new instance...");
1733

@@ -21,15 +37,15 @@ const handleSaveBboxClick = async () => {
2137
z1: $("#d_z1").val(),
2238
z2: $("#d_z2").val(),
2339
my: $("#m_y").val(),
24-
mx: $("#m_x").val()
40+
mx: $("#m_x").val(),
2541
});
2642

2743
$("#drawModalFNSave, #drawModalFN").modal("hide");
2844
location.reload();
2945
} catch (error) {
3046
console.error("Error saving bbox:", error);
3147
}
32-
$('#loading-bar').css('display', 'none');
48+
$("#loading-bar").css("display", "none");
3349
};
3450

3551
const reloadImages = async (currentPage) => {
@@ -97,6 +113,16 @@ const hideCardGroupLoadingBar = () => {
97113
};
98114

99115
const toggleNavigationButtons = (disable) => {
100-
$("#prev-page, #next-page").toggleClass("disabled", disable)
101-
.find("a").attr("aria-disabled", disable ? "true" : null);
116+
$("#prev-page, #next-page")
117+
.toggleClass("disabled", disable)
118+
.find("a")
119+
.attr("aria-disabled", disable ? "true" : null);
120+
};
121+
122+
window.toggleAllSources = function () {
123+
const $allImgSources = $("[id^='imgTarget-']");
124+
$allImgSources.each((_, img) => {
125+
const $img = $(img);
126+
$img.toggle();
127+
});
102128
};

synanno/static/draw.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,17 @@ $(document).ready(() => {
475475
}
476476
}
477477

478+
// Bind hotkeys to buttons
479+
$(document).on("keydown", (event) => {
480+
const hotkey = event.key.toLowerCase();
481+
const $button = $(`[data-hotkey="${hotkey}"]`);
482+
483+
if ($button.length && !$button.prop("disabled")) {
484+
event.preventDefault();
485+
$button.trigger("click");
486+
}
487+
});
488+
478489
$("canvas.curveCanvas").mousemove((event) => {
479490
if (split_mask) {
480491
if ((mousePosition.x != event.clientX || mousePosition.y != event.clientY) && event.buttons == 1) {

synanno/templates/annotation.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,20 @@
4040
<span id="value-opacity-grid" type="button" class="btn btn-light" value="{{grid_opacity or '0.5'}}">{{grid_opacity or '0.5'}}</span>
4141
<button id="inc-opacity-grid" type="button" class="btn btn-secondary" onclick="add_opacity_grid()">+</button>
4242
</div>
43+
44+
<div class="legend-item">
45+
<button
46+
id="toggle-label-opacity"
47+
type="button"
48+
class="btn btn-secondary"
49+
onclick="toggleAllSources();"
50+
data-hotkey="q"
51+
title="Toggle Label Opacity"
52+
hidden
53+
>
54+
Toggle Label Opacity
55+
</button>
56+
</div>
4357
</div>
4458

4559

@@ -78,7 +92,7 @@
7892

7993
<!-- Error Processing Button -->
8094
<div class="position-absolute" style="bottom: 1.5rem; right: 2.5rem; z-index: 1000;">
81-
<a class="btn btn-secondary" href="{{ url_for('categorize.categorize') }}">Error Processing</a>
95+
<a class="btn btn-secondary" href="{{ url_for('categorize.categorize') }}">Error Categorization</a>
8296
</div>
8397
</div>
8498
</div>

synanno/templates/annotation_single.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
id="toggle-label"
4949
class="btn btn-secondary rounded-start"
5050
onclick="check_gt();"
51+
data-hotkey="q"
52+
title="Toggle label"
5153
>
5254
Label
5355
</button>

synanno/templates/demo_setup.html

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{% extends "templatebase.html" %}
2+
3+
{% block help_content %}
4+
<h1 class="mt-5 text-center text-white">Resetting Data and Loading Demo...</h1>
5+
<p class="text-center text-white">Please wait while the demo data is being initialized.</p>
6+
{% endblock %}
7+
8+
{% block content %}
9+
<div class="d-flex justify-content-center align-items-center" style="height: 100vh;">
10+
<div id="loading-bar">
11+
<div>
12+
<p class="text-white">Resetting Data and Loading Demo...</p>
13+
<div class="progress">
14+
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 100%;"></div>
15+
</div>
16+
</div>
17+
</div>
18+
</div>
19+
<script>
20+
// Clear session storage and redirect to the next route
21+
sessionStorage.clear();
22+
console.log('Session storage cleared.');
23+
setTimeout(() => {
24+
window.location.href = "{{ next_route }}";
25+
}, 2000);
26+
</script>
27+
{% endblock %}

synanno/templates/draw_modal.html

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,28 +40,27 @@
4040
<div class="text-center">
4141
<div class="d-flex justify-content-center px-2">
4242
<div class="btn-group w-100 flex-wrap gap-1 justify-content-between" role="group" aria-label="Canvas Actions">
43-
44-
<button type="button" class="btn btn-secondary" id="canvasButtonPreCRD" title="Pre-Synaptic Marker">🟢</button>
45-
<button type="button" class="btn btn-secondary" id="canvasButtonPostCRD" title="Post-Synaptic Marker">🔵</button>
46-
<button type="button" class="btn btn-secondary" id="canvasButtonDrawMask" title="Draw Mask">
47-
<i class="bi bi-pencil"></i>
48-
</button>
49-
<button type="button" class="btn btn-secondary" id="canvasButtonFill" title="Fill">
50-
<i class="bi bi-paint-bucket"></i>
51-
</button>
52-
<button type="button" class="btn btn-secondary" id="canvasButtonRevise" title="Revise">
53-
<i class="bi bi-eraser"></i>
54-
</button>
55-
<button type="button" class="btn btn-success" id="canvasButtonSave" title="Save">
56-
<i class="bi bi-save"></i>
57-
</button>
58-
<button type="button" class="btn btn-warning" id="canvasButtonAuto" title="Auto Fill">
59-
<i class="bi bi-magic"></i>
60-
</button>
61-
<button class="btn btn-secondary" id="ng-link-draw" type="button" data-bs-target="#drawModalFN" data-bs-toggle="modal" data-bs-dismiss="modal" title="View in Neuroglancer">
62-
<i class="bi bi-diagram-3"></i>
63-
</button>
64-
</div>
43+
<button type="button" class="btn btn-secondary" id="canvasButtonPreCRD" title="Pre-Synaptic Marker" data-hotkey="q">🟢</button>
44+
<button type="button" class="btn btn-secondary" id="canvasButtonPostCRD" title="Post-Synaptic Marker" data-hotkey="w">🔵</button>
45+
<button type="button" class="btn btn-secondary" id="canvasButtonDrawMask" title="Draw Mask" data-hotkey="e">
46+
<i class="bi bi-pencil"></i>
47+
</button>
48+
<button type="button" class="btn btn-secondary" id="canvasButtonFill" title="Fill" data-hotkey="r">
49+
<i class="bi bi-paint-bucket"></i>
50+
</button>
51+
<button type="button" class="btn btn-secondary" id="canvasButtonRevise" title="Revise" data-hotkey="t">
52+
<i class="bi bi-eraser"></i>
53+
</button>
54+
<button type="button" class="btn btn-success" id="canvasButtonSave" title="Save" data-hotkey="y">
55+
<i class="bi bi-save"></i>
56+
</button>
57+
<button type="button" class="btn btn-warning" id="canvasButtonAuto" title="Auto Fill" data-hotkey="u">
58+
<i class="bi bi-magic"></i>
59+
</button>
60+
<button class="btn btn-secondary" id="ng-link-draw" type="button" data-bs-target="#drawModalFN" data-bs-toggle="modal" data-bs-dismiss="modal" title="View in Neuroglancer" data-hotkey="i">
61+
<i class="bi bi-diagram-3"></i>
62+
</button>
63+
</div>
6564
</div>
6665
</div>
6766

0 commit comments

Comments
 (0)