Skip to content

Commit 92a00c1

Browse files
authored
Merge pull request #105 from PytorchConnectomics/leander/incorporate-user-feedback
Incorporate User Feedback from the Case and User Studies
2 parents bb650bf + d85acdc commit 92a00c1

26 files changed

Lines changed: 614 additions & 592 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,6 @@ datasets/
5353
flask_session
5454
synAnno.json
5555
secrets/
56+
tmp/
5657

5758
.vscode/

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# SynAnno
22

3+
## Live Demo
4+
A demo is available [here](http://16.170.214.77/)
5+
6+
## Table of Contents
7+
38
SynAnno is a tool designed for proofreading and correcting synaptic polarity annotation from electron microscopy (EM) volumes - specifically the [H01](https://h01-release.storage.googleapis.com/landing.html) dataset. SynAnno is aimed for integration with CAVE (Connectome Annotation Versioning Engine).
49

510
- [Key Components and Subjects](#key-components-and-subjects)

synanno/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def initialize_global_variables(app):
8585
app.source = None
8686
app.cz1, app.cz2, app.cz, app.cy, app.cx = 0, 0, 0, 0, 0
8787
app.n_pages = 0
88-
app.per_page = 24 # Number of images per page
88+
app.tiles_per_page = 24 # Number of images per page
8989
# Neuron skeleton info/data
9090
app.sections = None
9191
app.neuron_ready = None

synanno/backend/processing.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -787,22 +787,24 @@ def calculate_number_of_pages(n_images: int) -> int:
787787
788788
Args:
789789
n_images: Total number of images.
790-
per_page: Number of images per page.
790+
tiles_per_page: Number of images per page.
791791
792792
Returns:
793793
Number of pages.
794794
"""
795-
number_pages = n_images // current_app.per_page
796-
if n_images % current_app.per_page != 0:
795+
number_pages = n_images // current_app.tiles_per_page
796+
if n_images % current_app.tiles_per_page != 0:
797797
number_pages += 1
798798

799799
current_app.synapse_data["page"] = -1 # Initialize pages to -1
800800

801801
# assign pages to synapses
802-
for i, start_idx in enumerate(range(0, n_images, current_app.per_page), start=1):
802+
for i, start_idx in enumerate(
803+
range(0, n_images, current_app.tiles_per_page), start=1
804+
):
803805
current_app.synapse_data.loc[
804806
current_app.synapse_data.iloc[
805-
start_idx : start_idx + current_app.per_page # noqa: E203
807+
start_idx : start_idx + current_app.tiles_per_page # noqa: E203
806808
].index,
807809
"page",
808810
] = i
@@ -816,7 +818,7 @@ def calculate_number_of_pages_for_neuron_section_based_loading():
816818
817819
This function:
818820
- Groups synapses by `section_index`.
819-
- Assigns page numbers within each section based on `current_app.per_page`.
821+
- Assigns page numbers within each section based on `current_app.tiles_per_page`.
820822
- Adds an extra empty page per section.
821823
822824
Returns:
@@ -839,11 +841,11 @@ def calculate_number_of_pages_for_neuron_section_based_loading():
839841

840842
# Assign pages to synapses within the section
841843
for i, start_idx in enumerate(
842-
range(0, total_synapses, current_app.per_page), start=1
844+
range(0, total_synapses, current_app.tiles_per_page), start=1
843845
):
844846
current_app.synapse_data.loc[
845847
synapse_group.iloc[
846-
start_idx : start_idx + current_app.per_page # noqa: E203
848+
start_idx : start_idx + current_app.tiles_per_page # noqa: E203
847849
].index,
848850
"page",
849851
] = (
@@ -856,7 +858,7 @@ def calculate_number_of_pages_for_neuron_section_based_loading():
856858
)
857859

858860
# Increment by the number of pages needed
859-
number_of_pages += int(np.ceil(total_synapses / current_app.per_page))
861+
number_of_pages += int(np.ceil(total_synapses / current_app.tiles_per_page))
860862

861863
# Add one empty page for the section
862864
number_of_pages += 1

synanno/routes/false_negatives.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,12 @@ def create_new_item(request) -> dict:
122122
if current_page > -1:
123123
item["Page"] = current_page
124124
else:
125-
if not (len(current_app.df_metadata) % current_app.per_page == 0):
126-
item["Page"] = len(current_app.df_metadata) // current_app.per_page + 1
125+
if not (len(current_app.df_metadata) % current_app.tiles_per_page == 0):
126+
item["Page"] = (
127+
len(current_app.df_metadata) // current_app.tiles_per_page + 1
128+
)
127129
else:
128-
item["Page"] = len(current_app.df_metadata) // current_app.per_page
130+
item["Page"] = len(current_app.df_metadata) // current_app.tiles_per_page
129131

130132
coordinate_order = list(current_app.coordinate_order.keys())
131133

synanno/routes/finish.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import logging
55
import zipfile
6+
from typing import Optional
67

78
from flask import (
89
Blueprint,
@@ -131,7 +132,7 @@ def create_zip_with_masks() -> io.BytesIO:
131132
return zip_buffer
132133

133134

134-
def get_metadata_for_image_index(img_index: str) -> dict | None:
135+
def get_metadata_for_image_index(img_index: str) -> Optional[dict]:
135136
"""Retrieve metadata for a given image index."""
136137
img_index_int = int(img_index) # noqa: F841
137138
if current_app.df_metadata.query("Image_Index == @img_index_int").to_dict(

synanno/routes/manual_annotate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def save_canvas() -> dict:
7979
image = decode_image(request.form["imageBase64"])
8080
page = int(request.form["page"])
8181
index = int(request.form["data_id"])
82-
viewed_instance_slice = int(request.form["viewed_instance_slice"])
82+
viewed_instance_slice = int(request.form["viewedInstanceSlice"])
8383
canvas_type = str(request.form["canvas_type"])
8484

8585
crop_axes = (

synanno/routes/opendata.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,8 @@ def upload_file():
374374
"""
375375
current_app.view_style = request.form.get("view_style")
376376

377+
current_app.tiles_per_page = int(request.form.get("tiles_per_page"))
378+
377379
save_coordinate_order_and_crop_size(request.form)
378380

379381
source_url = request.form.get("source_url")

synanno/static/annotation_module.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ $(document).ready(function () {
4646
});
4747

4848
$('[data-bs-target="#neuroModel"]').on('click', function () {
49+
$('#synapse-id-annotation').text(dataId);
4950
toggleInert($detailsModal, true);
5051
$neuroModel.modal('show');
5152
});
@@ -120,6 +121,13 @@ $(document).ready(function () {
120121
isModalScrollingLocked = true;
121122

122123
const newSlice = currentSlice + (event.originalEvent.deltaY > 0 ? 1 : -1);
124+
125+
// Restrict scrolling beyond available slices
126+
if (newSlice < dataJson.Min_Slice || newSlice > dataJson.Max_Slice) {
127+
isModalScrollingLocked = false;
128+
return;
129+
}
130+
123131
try {
124132
const exists = await $.get(dataJson.Error_Description === "False Negative" ?
125133
`/source_img_exists/${dataId}/${newSlice}` :

synanno/static/css/style.css

Lines changed: 92 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,32 @@
11
#base-content-container {
2-
max-height: 80vh;
2+
max-height: 100vh;
33
max-width: 100vw;
44
}
55

66
#image-tile-container {
7-
max-height: 70vh;
8-
max-width: 70vw;
9-
}
10-
11-
.correct {
12-
background: #abebc6;
7+
min-width: 60vw;
8+
width: fit-content;
9+
margin: 0 auto;
1310
}
1411

1512
.annotate-item {
16-
width: 16.5%;
1713
text-align: center;
1814
}
1915

20-
.card>.correct {
16+
.correct,
17+
.card > .correct {
2118
background: #abebc6;
2219
}
2320

24-
.incorrect {
25-
background: #f08080;
26-
}
2721

22+
.incorrect,
2823
.card>.incorrect {
2924
background: #f08080;
3025
}
3126

32-
.unsure {
33-
background: #d7dbdd;
34-
}
35-
27+
.unsure,
3628
.card>.unsure {
37-
background: #d7dbdd;
29+
background: #f6f699;
3830
}
3931

4032
.card {
@@ -47,13 +39,13 @@
4739
}
4840

4941
.legend {
50-
position: sticky;
51-
top: 0;
52-
z-index: 900;
42+
width: 100%;
43+
flex-wrap: wrap;
5344
display: flex;
5445
gap: 1rem;
5546
background-color: white;
56-
padding: 1rem;
47+
padding: 0.5rem;
48+
box-sizing: border-box;
5749
}
5850

5951
.legend-item {
@@ -71,10 +63,7 @@
7163
.card-body-proof-read {
7264
max-height: 23%;
7365
max-width: 23%;
74-
margin-left: 1%;
75-
margin-right: 1%;
76-
margin-top: 0.5%;
77-
margin-bottom: 0.5%;
66+
margin: 0.5% 1%;
7867
}
7968

8069
.card-block>.main-image-categorize>.img_categorize {
@@ -94,8 +83,11 @@
9483

9584
.outsideWrapper {
9685
margin: auto;
97-
width: 384px;
98-
height: 384px;
86+
width: 452px;
87+
height: 452px;
88+
background-color: #fff;
89+
border-radius: 12px;
90+
overflow: hidden;
9991
}
10092

10193
.insideWrapper {
@@ -108,8 +100,8 @@
108100
width: 100%;
109101
height: 100%;
110102
position: absolute;
111-
top: 0px;
112-
left: 0px;
103+
top: 0;
104+
left: 0;
113105
pointer-events: none;
114106
z-index: 0;
115107
}
@@ -122,6 +114,60 @@
122114
left: 0;
123115
}
124116

117+
/* drawing modal content */
118+
.modal-content {
119+
background: #ffffff;
120+
border-radius: 1rem;
121+
padding: 0.5rem;
122+
border: none;
123+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
124+
}
125+
126+
.modal-dialog {
127+
border: none !important;
128+
box-shadow: none !important;
129+
}
130+
131+
.modal-header,
132+
.card-header {
133+
background: #f8f9fa;
134+
border-bottom: none !important;
135+
border-top-left-radius: 1rem;
136+
border-top-right-radius: 1rem;
137+
border-bottom-left-radius: 0.5rem;
138+
border-bottom-right-radius: 0.5rem;
139+
box-shadow: none;
140+
}
141+
142+
.btn {
143+
transition: all 0.2s ease-in-out;
144+
}
145+
146+
.btn:hover {
147+
transform: translateY(-1px);
148+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
149+
}
150+
151+
.modal-footer {
152+
padding: 0rem 1rem;
153+
max-height: 40px;
154+
border-top: none;
155+
}
156+
157+
#centralBadge {
158+
z-index: 1; /* Ensure it's above the image, but below the modal header */
159+
pointer-events: none; /* Prevent interaction issues */
160+
font-size: 0.75rem;
161+
opacity: 0.7;
162+
}
163+
164+
#neuron-id-draw .badge.disabled {
165+
opacity: 0.8;
166+
cursor: default;
167+
pointer-events: none;
168+
padding: 10px;
169+
}
170+
125171
/* css for the open data form */
126172
.syn-id-container {
127173
display: flex;
@@ -182,11 +228,11 @@
182228
/* Initially hidden */
183229
position: relative;
184230
/* Default inside flex container */
185-
width: 33vw;
231+
width: 40vw;
186232
/* Always take 33% of the screen width */
187-
height: 90vh;
233+
height: 91vh;
188234
/* Always take 80% of the screen height */
189-
margin-top: 5px;
235+
margin-top: 4px;
190236
background: #fff;
191237
border: 1px solid #ccc;
192238
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2);
@@ -278,3 +324,17 @@
278324
.toggle-btn:hover {
279325
background: #888;
280326
}
327+
328+
.metadata-overlay {
329+
position: absolute;
330+
top: 5px;
331+
right: 5px;
332+
background: rgba(0, 0, 0, 0.55);
333+
color: white;
334+
font-size: 0.65rem;
335+
padding: 2px 4px;
336+
border-radius: 3px;
337+
z-index: 2;
338+
font-family: monospace;
339+
pointer-events: none;
340+
}

0 commit comments

Comments
 (0)