diff --git a/.gitignore b/.gitignore index c08a7b3..663d41e 100644 --- a/.gitignore +++ b/.gitignore @@ -53,5 +53,6 @@ datasets/ flask_session synAnno.json secrets/ +tmp/ .vscode/ diff --git a/README.md b/README.md index c564852..81c288e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # SynAnno +## Live Demo +A demo is available [here](http://16.170.214.77/) + +## Table of Contents + 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). - [Key Components and Subjects](#key-components-and-subjects) diff --git a/synanno/__init__.py b/synanno/__init__.py index 6895185..9900514 100644 --- a/synanno/__init__.py +++ b/synanno/__init__.py @@ -85,7 +85,7 @@ def initialize_global_variables(app): app.source = None app.cz1, app.cz2, app.cz, app.cy, app.cx = 0, 0, 0, 0, 0 app.n_pages = 0 - app.per_page = 24 # Number of images per page + app.tiles_per_page = 24 # Number of images per page # Neuron skeleton info/data app.sections = None app.neuron_ready = None diff --git a/synanno/backend/processing.py b/synanno/backend/processing.py index c1dcde9..64f167f 100644 --- a/synanno/backend/processing.py +++ b/synanno/backend/processing.py @@ -787,22 +787,24 @@ def calculate_number_of_pages(n_images: int) -> int: Args: n_images: Total number of images. - per_page: Number of images per page. + tiles_per_page: Number of images per page. Returns: Number of pages. """ - number_pages = n_images // current_app.per_page - if n_images % current_app.per_page != 0: + number_pages = n_images // current_app.tiles_per_page + if n_images % current_app.tiles_per_page != 0: number_pages += 1 current_app.synapse_data["page"] = -1 # Initialize pages to -1 # assign pages to synapses - for i, start_idx in enumerate(range(0, n_images, current_app.per_page), start=1): + for i, start_idx in enumerate( + range(0, n_images, current_app.tiles_per_page), start=1 + ): current_app.synapse_data.loc[ current_app.synapse_data.iloc[ - start_idx : start_idx + current_app.per_page # noqa: E203 + start_idx : start_idx + current_app.tiles_per_page # noqa: E203 ].index, "page", ] = i @@ -816,7 +818,7 @@ def calculate_number_of_pages_for_neuron_section_based_loading(): This function: - Groups synapses by `section_index`. - - Assigns page numbers within each section based on `current_app.per_page`. + - Assigns page numbers within each section based on `current_app.tiles_per_page`. - Adds an extra empty page per section. Returns: @@ -839,11 +841,11 @@ def calculate_number_of_pages_for_neuron_section_based_loading(): # Assign pages to synapses within the section for i, start_idx in enumerate( - range(0, total_synapses, current_app.per_page), start=1 + range(0, total_synapses, current_app.tiles_per_page), start=1 ): current_app.synapse_data.loc[ synapse_group.iloc[ - start_idx : start_idx + current_app.per_page # noqa: E203 + start_idx : start_idx + current_app.tiles_per_page # noqa: E203 ].index, "page", ] = ( @@ -856,7 +858,7 @@ def calculate_number_of_pages_for_neuron_section_based_loading(): ) # Increment by the number of pages needed - number_of_pages += int(np.ceil(total_synapses / current_app.per_page)) + number_of_pages += int(np.ceil(total_synapses / current_app.tiles_per_page)) # Add one empty page for the section number_of_pages += 1 diff --git a/synanno/routes/false_negatives.py b/synanno/routes/false_negatives.py index fc1dc32..4bf06a5 100644 --- a/synanno/routes/false_negatives.py +++ b/synanno/routes/false_negatives.py @@ -122,10 +122,12 @@ def create_new_item(request) -> dict: if current_page > -1: item["Page"] = current_page else: - if not (len(current_app.df_metadata) % current_app.per_page == 0): - item["Page"] = len(current_app.df_metadata) // current_app.per_page + 1 + if not (len(current_app.df_metadata) % current_app.tiles_per_page == 0): + item["Page"] = ( + len(current_app.df_metadata) // current_app.tiles_per_page + 1 + ) else: - item["Page"] = len(current_app.df_metadata) // current_app.per_page + item["Page"] = len(current_app.df_metadata) // current_app.tiles_per_page coordinate_order = list(current_app.coordinate_order.keys()) diff --git a/synanno/routes/finish.py b/synanno/routes/finish.py index 4ba229a..9a201eb 100644 --- a/synanno/routes/finish.py +++ b/synanno/routes/finish.py @@ -3,6 +3,7 @@ import json import logging import zipfile +from typing import Optional from flask import ( Blueprint, @@ -131,7 +132,7 @@ def create_zip_with_masks() -> io.BytesIO: return zip_buffer -def get_metadata_for_image_index(img_index: str) -> dict | None: +def get_metadata_for_image_index(img_index: str) -> Optional[dict]: """Retrieve metadata for a given image index.""" img_index_int = int(img_index) # noqa: F841 if current_app.df_metadata.query("Image_Index == @img_index_int").to_dict( diff --git a/synanno/routes/manual_annotate.py b/synanno/routes/manual_annotate.py index ea80c27..ea40b26 100644 --- a/synanno/routes/manual_annotate.py +++ b/synanno/routes/manual_annotate.py @@ -79,7 +79,7 @@ def save_canvas() -> dict: image = decode_image(request.form["imageBase64"]) page = int(request.form["page"]) index = int(request.form["data_id"]) - viewed_instance_slice = int(request.form["viewed_instance_slice"]) + viewed_instance_slice = int(request.form["viewedInstanceSlice"]) canvas_type = str(request.form["canvas_type"]) crop_axes = ( diff --git a/synanno/routes/opendata.py b/synanno/routes/opendata.py index 65008f8..c5c9dd6 100644 --- a/synanno/routes/opendata.py +++ b/synanno/routes/opendata.py @@ -374,6 +374,8 @@ def upload_file(): """ current_app.view_style = request.form.get("view_style") + current_app.tiles_per_page = int(request.form.get("tiles_per_page")) + save_coordinate_order_and_crop_size(request.form) source_url = request.form.get("source_url") diff --git a/synanno/static/annotation_module.js b/synanno/static/annotation_module.js index 5e869a4..494d517 100755 --- a/synanno/static/annotation_module.js +++ b/synanno/static/annotation_module.js @@ -46,6 +46,7 @@ $(document).ready(function () { }); $('[data-bs-target="#neuroModel"]').on('click', function () { + $('#synapse-id-annotation').text(dataId); toggleInert($detailsModal, true); $neuroModel.modal('show'); }); @@ -120,6 +121,13 @@ $(document).ready(function () { isModalScrollingLocked = true; const newSlice = currentSlice + (event.originalEvent.deltaY > 0 ? 1 : -1); + + // Restrict scrolling beyond available slices + if (newSlice < dataJson.Min_Slice || newSlice > dataJson.Max_Slice) { + isModalScrollingLocked = false; + return; + } + try { const exists = await $.get(dataJson.Error_Description === "False Negative" ? `/source_img_exists/${dataId}/${newSlice}` : diff --git a/synanno/static/css/style.css b/synanno/static/css/style.css index 010bd66..e88ca2d 100755 --- a/synanno/static/css/style.css +++ b/synanno/static/css/style.css @@ -1,40 +1,32 @@ #base-content-container { - max-height: 80vh; + max-height: 100vh; max-width: 100vw; } #image-tile-container { - max-height: 70vh; - max-width: 70vw; -} - -.correct { - background: #abebc6; + min-width: 60vw; + width: fit-content; + margin: 0 auto; } .annotate-item { - width: 16.5%; text-align: center; } -.card>.correct { +.correct, +.card > .correct { background: #abebc6; } -.incorrect { - background: #f08080; -} +.incorrect, .card>.incorrect { background: #f08080; } -.unsure { - background: #d7dbdd; -} - +.unsure, .card>.unsure { - background: #d7dbdd; + background: #f6f699; } .card { @@ -47,13 +39,13 @@ } .legend { - position: sticky; - top: 0; - z-index: 900; + width: 100%; + flex-wrap: wrap; display: flex; gap: 1rem; background-color: white; - padding: 1rem; + padding: 0.5rem; + box-sizing: border-box; } .legend-item { @@ -71,10 +63,7 @@ .card-body-proof-read { max-height: 23%; max-width: 23%; - margin-left: 1%; - margin-right: 1%; - margin-top: 0.5%; - margin-bottom: 0.5%; + margin: 0.5% 1%; } .card-block>.main-image-categorize>.img_categorize { @@ -94,8 +83,11 @@ .outsideWrapper { margin: auto; - width: 384px; - height: 384px; + width: 452px; + height: 452px; + background-color: #fff; + border-radius: 12px; + overflow: hidden; } .insideWrapper { @@ -108,8 +100,8 @@ width: 100%; height: 100%; position: absolute; - top: 0px; - left: 0px; + top: 0; + left: 0; pointer-events: none; z-index: 0; } @@ -122,6 +114,60 @@ left: 0; } +/* drawing modal content */ +.modal-content { + background: #ffffff; + border-radius: 1rem; + padding: 0.5rem; + border: none; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); +} + +.modal-dialog { + border: none !important; + box-shadow: none !important; +} + +.modal-header, +.card-header { + background: #f8f9fa; + border-bottom: none !important; + border-top-left-radius: 1rem; + border-top-right-radius: 1rem; + border-bottom-left-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; + box-shadow: none; +} + +.btn { + transition: all 0.2s ease-in-out; +} + +.btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.modal-footer { + padding: 0rem 1rem; + max-height: 40px; + border-top: none; +} + +#centralBadge { + z-index: 1; /* Ensure it's above the image, but below the modal header */ + pointer-events: none; /* Prevent interaction issues */ + font-size: 0.75rem; + opacity: 0.7; +} + +#neuron-id-draw .badge.disabled { + opacity: 0.8; + cursor: default; + pointer-events: none; + padding: 10px; +} + /* css for the open data form */ .syn-id-container { display: flex; @@ -182,11 +228,11 @@ /* Initially hidden */ position: relative; /* Default inside flex container */ - width: 33vw; + width: 40vw; /* Always take 33% of the screen width */ - height: 90vh; + height: 91vh; /* Always take 80% of the screen height */ - margin-top: 5px; + margin-top: 4px; background: #fff; border: 1px solid #ccc; box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2); @@ -278,3 +324,17 @@ .toggle-btn:hover { background: #888; } + +.metadata-overlay { + position: absolute; + top: 5px; + right: 5px; + background: rgba(0, 0, 0, 0.55); + color: white; + font-size: 0.65rem; + padding: 2px 4px; + border-radius: 3px; + z-index: 2; + font-family: monospace; + pointer-events: none; +} diff --git a/synanno/static/draw.js b/synanno/static/draw.js index ea43c99..1e4c7cb 100755 --- a/synanno/static/draw.js +++ b/synanno/static/draw.js @@ -12,8 +12,9 @@ $(document).ready(() => { let rect_curve, rect_circle; let draw_mask = false, split_mask = false; let points = [], pointsQBez = []; - const thickness = 15; + const thickness = 10; const pink = "rgba(255, 0, 255, 0.7)"; + const turquoise = "rgba(21, 229, 239, 0.92)"; let page, data_id, label; let pre_CRD = false, post_CRD = false; let x_syn_crd = null, y_syn_crd = null; @@ -39,16 +40,17 @@ $(document).ready(() => { type: 'HEAD', success: function (data, textStatus, xhr) { if (xhr.status === 200) { // Only execute if status is 200 (image exists) + console.log("Auto curve image exists"); $(canvas_target_image_curve).attr("src", "/get_auto_curve_image/" + data_id + "/" + middle_slice); $(canvas_target_image_curve).removeClass('d-none'); } else if (xhr.status === 204) { // Only execute if status is 404 (image does not exist) $.ajax({ - url: "/get_auto_curve_image/" + data_id + "/" + middle_slice, + url: "/get_curve_image/" + data_id + "/" + middle_slice, type: 'HEAD', success: function (data, textStatus, xhr) { if (xhr.status === 200) { // Only execute if status is 200 (image exists) - $(canvas_target_image_curve).attr("src", "/get_auto_curve_image/" + data_id + "/" + middle_slice); + $(canvas_target_image_curve).attr("src", "/get_curve_image/" + data_id + "/" + middle_slice); $(canvas_target_image_curve).removeClass('d-none'); } } @@ -96,10 +98,10 @@ $(document).ready(() => { $('[id^="drawButton-"]').click(async function () { [page, data_id, label] = $(this).attr("id").replace(/drawButton-/, "").split("-"); - $("#canvasButtonDrawMask").text("Draw Mask").prop("disabled", false); - $("#canvasButtonPreCRD").text("Pre-Synaptic CRD").prop("disabled", false); - $("#canvasButtonPostCRD").text("Post-Synaptic CRD").prop("disabled", false); - $("#canvasButtonAuto, #rangeSlices").prop("disabled", false); + $("#canvasButtonDrawMask").html('') + .attr("title", "Draw Mask").prop("disabled", false); + $("#canvasButtonPreCRD").prop("disabled", false); + $("#canvasButtonPostCRD").prop("disabled", false); $("#canvasButtonFill, #canvasButtonRevise, #canvasButtonSave").prop("disabled", true); ctx_curve.restore(); clear_canvas(ctx_curve, canvas_curve); @@ -194,35 +196,31 @@ $(document).ready(() => { ctx_circle_pre.arc(x_syn_crd, y_syn_crd, 10, 0, 2 * Math.PI); ctx_circle_pre.fill(); - // Add a text label with improved readability + // Add a text label with modern styling const text = "Pre"; const textX = x_syn_crd; - const textY = y_syn_crd - 20; - - // Draw background with stronger opacity for better contrast - ctx_circle_pre.fillStyle = "rgba(0, 0, 0, 0.9)"; - ctx_circle_pre.fillRect( - textX - 24, // Fixed width for consistency - textY - 16, - 48, // Fixed width for consistency - 22 // Slightly taller - ); - - // Draw text with brighter green for better visibility - ctx_circle_pre.fillStyle = "rgb(150, 255, 150)"; // Lighter green for better contrast - ctx_circle_pre.font = "bold 16px Arial"; + const textY = y_syn_crd - 24; + + // Draw rounded background with semi-transparent dark tone + ctx_circle_pre.fillStyle = "rgba(33, 37, 41, 0.85)"; // Bootstrap dark (same as Post) + ctx_circle_pre.beginPath(); + ctx_circle_pre.roundRect(textX - 30, textY - 16, 60, 28, 6); + ctx_circle_pre.fill(); + + // Optional: soft shadow for modern depth + ctx_circle_pre.shadowColor = "rgba(0, 0, 0, 0.3)"; + ctx_circle_pre.shadowBlur = 4; + + // Draw text with modern font and light green color + ctx_circle_pre.fillStyle = "#b8fcb8"; // Light green, soft tone + ctx_circle_pre.font = "600 14px 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif"; ctx_circle_pre.textAlign = "center"; - ctx_circle_pre.fillText(text, textX, textY); - - // Draw border around text background for additional contrast - ctx_circle_pre.strokeStyle = "white"; - ctx_circle_pre.lineWidth = 1; - ctx_circle_pre.strokeRect( - textX - 24, - textY - 16, - 48, - 22 - ); + ctx_circle_pre.textBaseline = "middle"; + ctx_circle_pre.fillText(text, textX, textY - 1); // 1px up + + // Reset shadow after use + ctx_circle_pre.shadowColor = "transparent"; + ctx_circle_pre.shadowBlur = 0; save_canvas(canvas_circle_pre, "circlePre"); $("#canvasButtonPreCRD, #canvasButtonPostCRD, #canvasButtonDrawMask, #canvasButtonAuto").prop("disabled", false); @@ -230,6 +228,7 @@ $(document).ready(() => { } }); + $("#canvasButtonPostCRD").on("click", () => { ctx_circle_post.restore(); if ($("canvas.circleCanvasPost").hasClass("d-none")) { @@ -273,35 +272,31 @@ $(document).ready(() => { ctx_circle_post.arc(x_syn_crd, y_syn_crd, 10, 0, 2 * Math.PI); ctx_circle_post.fill(); - // Add a text label with improved readability + // Add a text label with modern styling const text = "Post"; const textX = x_syn_crd; - const textY = y_syn_crd - 20; - - // Draw background with stronger opacity for better contrast - ctx_circle_post.fillStyle = "rgba(0, 0, 0, 0.9)"; - ctx_circle_post.fillRect( - textX - 24, // Wider background - textY - 16, - 48, // Fixed width for consistency - 22 // Slightly taller - ); - - // Draw text with brighter blue for better visibility - ctx_circle_post.fillStyle = "rgb(100, 200, 255)"; // Lighter blue for better contrast - ctx_circle_post.font = "bold 16px Arial"; + const textY = y_syn_crd - 24; + + // Draw rounded background with semi-transparent dark tone + ctx_circle_post.fillStyle = "rgba(33, 37, 41, 0.85)"; + ctx_circle_post.beginPath(); + ctx_circle_post.roundRect(textX - 30, textY - 16, 60, 28, 6); + ctx_circle_post.fill(); + + // Optional: subtle shadow for depth + ctx_circle_post.shadowColor = "rgba(0, 0, 0, 0.3)"; + ctx_circle_post.shadowBlur = 4; + + // Draw text with clean modern font and slightly lighter blue + ctx_circle_post.fillStyle = "#d0ebff"; // Light Bootstrap blue-ish + ctx_circle_post.font = "600 14px 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif"; ctx_circle_post.textAlign = "center"; - ctx_circle_post.fillText(text, textX, textY); - - // Draw border around text background for additional contrast - ctx_circle_post.strokeStyle = "white"; - ctx_circle_post.lineWidth = 1; - ctx_circle_post.strokeRect( - textX - 24, - textY - 16, - 48, - 22 - ); + ctx_circle_post.textBaseline = "middle"; + ctx_circle_post.fillText(text, textX, textY - 1); // nudged up by 1px + + // Reset shadow after use + ctx_circle_post.shadowColor = "transparent"; + ctx_circle_post.shadowBlur = 0; save_canvas(canvas_circle_post, "circlePost"); $("#canvasButtonPreCRD, #canvasButtonPostCRD, #canvasButtonDrawMask, #canvasButtonAuto").prop("disabled", false); @@ -309,6 +304,7 @@ $(document).ready(() => { } }); + $("#canvasButtonDrawMask").on("click", () => { clear_canvas(ctx_curve, canvas_curve); points = []; @@ -316,7 +312,9 @@ $(document).ready(() => { ctx_curve.restore(); if ($("canvas.curveCanvas").hasClass("d-none")) { $("#imgDetails-EM-GT-curve").addClass("d-none"); - $("#canvasButtonDrawMask").text("Reset"); + $("#canvasButtonDrawMask") + .html('') + .attr("title", "Reset"); $("canvas.curveCanvas").removeClass("d-none"); rect_curve = $("#imgDetails-EM")[0].getBoundingClientRect(); $("canvas.curveCanvas")[0].width = rect_curve.width; @@ -326,11 +324,13 @@ $(document).ready(() => { $("canvas.circleCanvasPre").css("z-index", 1); split_mask = false; draw_mask = true; - $("#canvasButtonPreCRD, #canvasButtonPostCRD, #canvasButtonAuto, #rangeSlices").prop("disabled", true); + $("#canvasButtonPreCRD, #canvasButtonPostCRD, #canvasButtonAuto").prop("disabled", true); } else { $("canvas.curveCanvas").addClass("d-none"); - $("#canvasButtonDrawMask").text("Draw Mask"); - $("#canvasButtonPreCRD, #canvasButtonPostCRD, #canvasButtonAuto, #rangeSlices").prop("disabled", false); + $("#canvasButtonDrawMask") + .html('') + .attr("title", "Draw Mask"); + $("#canvasButtonPreCRD, #canvasButtonPostCRD, #canvasButtonAuto").prop("disabled", false); $("#canvasButtonFill, #canvasButtonRevise, #canvasButtonSave").prop("disabled", true); points = []; pointsQBez = []; @@ -366,7 +366,7 @@ $(document).ready(() => { async function save_canvas(canvas, canvas_type) { const dataURL = canvas.toDataURL(); - const viewed_instance_slice = parseInt($("#rangeSlices").data("viewed_instance_slice")); + const viewedInstanceSlice = parseInt($("#drawModal").data("viewed-instance-slice")); try { const response = await $.ajax({ @@ -376,7 +376,7 @@ $(document).ready(() => { imageBase64: dataURL, data_id: data_id, page: page, - viewed_instance_slice: viewed_instance_slice, + viewedInstanceSlice: viewedInstanceSlice, canvas_type: canvas_type, }, }); @@ -386,17 +386,17 @@ $(document).ready(() => { if (canvas_type === 'curve') { $.ajax({ - url: "/get_curve_image/" + data_id + "/" + viewed_instance_slice, + url: "/get_curve_image/" + data_id + "/" + viewedInstanceSlice, type: 'HEAD', success: function (data, textStatus, xhr) { if (xhr.status === 200) { // Only execute if status is 200 (image exists) - if (viewed_instance_slice === parseInt(data_json.Middle_Slice, 10)) { - $(new Image()).attr("src", "/get_curve_image/" + data_id + "/" + viewed_instance_slice).on("load", function () { + if (viewedInstanceSlice === parseInt(data_json.Middle_Slice, 10)) { + $(new Image()).attr("src", "/get_curve_image/" + data_id + "/" + viewedInstanceSlice).on("load", function () { $(canvas_target_image).attr("src", this.src); }); $(canvas_target_image).removeClass('d-none'); } - $("#canvasButtonPreCRD, #canvasButtonPostCRD, #canvasButtonAuto, #rangeSlices").prop("disabled", false); + $("#canvasButtonPreCRD, #canvasButtonPostCRD, #canvasButtonAuto").prop("disabled", false); $("#canvasButtonSave, #canvasButtonFill, #canvasButtonRevise").prop("disabled", true); } else if (xhr.status === 204) { @@ -414,7 +414,7 @@ $(document).ready(() => { data: { x: x_syn_crd, y: y_syn_crd, - z: viewed_instance_slice, + z: viewedInstanceSlice, data_id: data_id, page: page, id: id, @@ -423,14 +423,14 @@ $(document).ready(() => { if (canvas_type === 'circlePre') { $.ajax({ - url: "/get_circle_pre_image/" + data_id + "/" + viewed_instance_slice, + url: "/get_circle_pre_image/" + data_id + "/" + viewedInstanceSlice, type: "HEAD", success: function (data, textStatus, xhr) { if (xhr.status === 200) { // Only execute if status is 200 (image exists) - $(new Image()).attr("src", "/get_circle_pre_image/" + data_id + "/" + viewed_instance_slice).on("load", function () { + $(new Image()).attr("src", "/get_circle_pre_image/" + data_id + "/" + viewedInstanceSlice).on("load", function () { $(canvas_target_image).attr("src", this.src); }); - if (viewed_instance_slice === parseInt(data_json.Middle_Slice, 10)) { + if (viewedInstanceSlice === parseInt(data_json.Middle_Slice, 10)) { $(canvas_target_image).removeClass('d-none'); } else { $(canvas_target_image).addClass('d-none'); @@ -444,14 +444,14 @@ $(document).ready(() => { if (canvas_type === 'circlePost') { $.ajax({ - url: "/get_circle_post_image/" + data_id + "/" + viewed_instance_slice, + url: "/get_circle_post_image/" + data_id + "/" + viewedInstanceSlice, type: "HEAD", success: function (data, textStatus, xhr) { if (xhr.status === 200) { // Only execute if status is 200 (image exists) - $(new Image()).attr("src", "/get_circle_post_image/" + data_id + "/" + viewed_instance_slice).on("load", function () { + $(new Image()).attr("src", "/get_circle_post_image/" + data_id + "/" + viewedInstanceSlice).on("load", function () { $(canvas_target_image).attr("src", this.src); }); - if (viewed_instance_slice === parseInt(data_json.Middle_Slice, 10)) { + if (viewedInstanceSlice === parseInt(data_json.Middle_Slice, 10)) { $(canvas_target_image).removeClass('d-none'); } else { $(canvas_target_image).addClass('d-none'); @@ -466,7 +466,7 @@ $(document).ready(() => { const curve_src = $(`#img-target-curve${base}`).attr('src'); const custom_mask = curve_src.includes("curve_image"); - if (!custom_mask && viewed_instance_slice === parseInt(data_json.Middle_Slice, 10)) { + if (!custom_mask && viewedInstanceSlice === parseInt(data_json.Middle_Slice, 10)) { $(`#img-target-curve${base}`).addClass('d-none'); } @@ -482,7 +482,7 @@ $(document).ready(() => { const pos = getXY(canvas_curve, event, rect_curve); const x = pos.x; const y = pos.y; - ctx_curve.strokeStyle = "#000"; + ctx_curve.strokeStyle = pink; ctx_curve.beginPath(); ctx_curve.ellipse(x, y, thickness, Math.floor(thickness / 2), 0, 0, Math.PI * 2); ctx_curve.stroke(); @@ -494,28 +494,37 @@ $(document).ready(() => { $("canvas.curveCanvas").on("click", (e) => { if (draw_mask) { clear_canvas(ctx_curve, canvas_curve); - ctx_curve.beginPath(); const pos = getXY(canvas_curve, e, rect_curve); const x = pos.x; const y = pos.y; points.push({ x, y }); + if (points.length > 2) { draw_quad_line(points, 3); $("#canvasButtonFill").prop("disabled", false); } else if (points.length > 1) { ctx_curve.lineWidth = 3; + ctx_curve.strokeStyle = pink; + + // Draw line + ctx_curve.beginPath(); ctx_curve.moveTo(points[0].x, points[0].y); - ctx_curve.fillStyle = 'red'; - ctx_curve.fillRect(points[0].x, points[0].y, 4, 4); - ctx_curve.fillRect(points[1].x, points[1].y, 4, 4); - ctx_curve.fillStyle = 'black'; - ctx_curve.lineTo(points[0].x, points[0].y); ctx_curve.lineTo(points[1].x, points[1].y); ctx_curve.stroke(); + + // Draw turquoise points as squares (or change to circles if you prefer) + ctx_curve.fillStyle = turquoise; + ctx_curve.beginPath(); + ctx_curve.arc(points[0].x, points[0].y, 4, 0, Math.PI * 2); + ctx_curve.fill(); + ctx_curve.beginPath(); + ctx_curve.arc(points[1].x, points[1].y, 4, 0, Math.PI * 2); + ctx_curve.fill(); } } }); + function clear_canvas(ctx, canvas) { ctx.beginPath(); ctx.clearRect(0, 0, canvas.width, canvas.height); @@ -565,40 +574,40 @@ $(document).ready(() => { ctx_curve.rect(0, 0, canvas_curve.width, canvas_curve.height); ctx_curve.fill(); ctx_curve.restore(); + + // Deactivate buttons after spline is filled + $("#canvasButtonFill, #canvasButtonRevise").prop("disabled", true); } else { console.log("A mask needs at least 3 points"); } } function draw_quad_line(points, lineWidth = 3, draw_points = true) { - ctx_curve.beginPath(); - ctx_curve.strokeStyle = 'black'; + ctx_curve.strokeStyle = pink; ctx_curve.lineWidth = lineWidth; + + ctx_curve.beginPath(); ctx_curve.moveTo(points[0].x, points[0].y); - if (draw_points) { - ctx_curve.fillStyle = 'red'; - ctx_curve.fillRect(points[0].x, points[0].y, 4, 4); - if (points.length < 4) { - ctx_curve.fillRect(points[0].x, points[0].y, 4, 4); - ctx_curve.fillRect(points[1].x, points[1].y, 4, 4); - } else { - ctx_curve.fillRect(points[0].x, points[0].y, 4, 4); - } - } - for (i = 1; i < points.length - 2; i++) { - var xend = (points[i].x + points[i + 1].x) / 2; - var yend = (points[i].y + points[i + 1].y) / 2; + + for (let i = 1; i < points.length - 2; i++) { + const xend = (points[i].x + points[i + 1].x) / 2; + const yend = (points[i].y + points[i + 1].y) / 2; ctx_curve.quadraticCurveTo(points[i].x, points[i].y, xend, yend); - if (draw_points) { - ctx_curve.fillRect(points[i].x, points[i].y, 4, 4); - ctx_curve.fillRect(points[i + 1].x, points[i + 1].y, 4, 4); - } } + + const i = points.length - 2; ctx_curve.quadraticCurveTo(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y); + + ctx_curve.stroke(); + if (draw_points) { - ctx_curve.fillRect(points[i + 1].x, points[i + 1].y, 4, 4); + ctx_curve.fillStyle = turquoise; + for (let p of points) { + ctx_curve.beginPath(); + ctx_curve.arc(p.x, p.y, 4, 0, Math.PI * 2); + ctx_curve.fill(); + } } - ctx_curve.stroke(); } function _getQBezierValue(t, p1, p2, p3) { diff --git a/synanno/static/draw_module.js b/synanno/static/draw_module.js index 262eddf..ef43d5a 100755 --- a/synanno/static/draw_module.js +++ b/synanno/static/draw_module.js @@ -1,345 +1,228 @@ -$(document).ready(function () { - +$(document).ready(() => { const currentPage = parseInt($("script[src*='draw_module.js']").data("current-page")) || -1; - const current_view = $("script[src*='draw_module.js']").data("current-view") || "draw"; - - $('[id^="drawButton-"]').click(async function () { - var [page, data_id, label] = $(this) - .attr("id") - .replace(/drawButton-/, "") - .split("-"); - - // we are currently in drawing mode - var mode = "draw"; - - // we require the information about the whole instance - var load = "full"; - - let req_data = $.ajax({ - url: "/get_instance", - type: "POST", - data: { - mode: mode, - load: load, - data_id: data_id, - page: page, + const currentView = $("script[src*='draw_module.js']").data("current-view") || "draw"; + + const tooltipTriggerList = $('[title]').toArray(); + tooltipTriggerList.forEach(el => new bootstrap.Tooltip(el)); + + let page = 0; + let dataId = 0; + let label = ""; + let currentSlice = 0; + let dataJson = null; + let initialCoordinates = { cz: null, cy: null, cx: null }; + + const $drawModal = $("#drawModal"); + const $imgSource = $("#imgDetails-EM"); + const $imgTarget = $("#imgDetails-EM-GT-curve"); + const $imgPreCircle = $("#imgDetails-EM-GT-circlePre"); + const $imgPostCircle = $("#imgDetails-EM-GT-circlePost"); + + const updateModalHeader = () => { + const viewedSlice = $drawModal.data("viewed-instance-slice"); + const middle = dataJson.Middle_Slice; + + // Center badge visibility + if (viewedSlice === middle) { + $("#centralBadge").removeClass("d-none"); + } else { + $("#centralBadge").addClass("d-none"); + } + + // Coordinate display from loaded data + const { cx0, cy0, _ } = dataJson; + $("#neuron-id-draw-module").text(`cx: ${parseInt(cx0)} - cy: ${parseInt(cy0)} - cz: ${parseInt(viewedSlice)}`); + }; + + const loadImage = (url, $element) => { + $.ajax({ + url: url, + type: "HEAD", + success: function (data, textStatus, xhr) { + if (xhr.status === 200) { + $(new Image()) + .attr("src", url) + .on("load", function () { + $element.attr("src", this.src).removeClass("d-none"); + }); + } else if (xhr.status === 204) { + console.info("Status 204: No content found for image:", url); + $element.addClass("d-none"); + } }, + error: function (xhr) { + console.error("Error loading image:", url, "Status:", xhr.status); + $element.addClass("d-none"); + } }); + }; - let cz0 = 0; - let cy0 = 0; - let cx0 = 0; + const updateImages = async (dataId, slice) => { + try { + await loadImage(`/get_auto_curve_image/${dataId}/${slice}`, $imgTarget); + if ($imgTarget.hasClass("d-none")) { + await loadImage(`/get_curve_image/${dataId}/${slice}`, $imgTarget); + } - // set the base image to the center slice - await req_data.done(function (data) { - let data_json = JSON.parse(data.data); - $("#imgDetails-EM").addClass(label.toLowerCase()); - $("#imgDetails-EM").attr("src", "/get_source_image/" + data_id + "/" + data_json.Middle_Slice); + await loadImage(`/get_circle_pre_image/${dataId}/${slice}`, $imgPreCircle); + await loadImage(`/get_circle_post_image/${dataId}/${slice}`, $imgPostCircle); + } catch (error) { + console.error("Error updating images:", error); + } + }; - if (mode === "draw") { - $.ajax({ - url: "/get_curve_image/" + data_id + "/" + data_json.Middle_Slice, - type: "HEAD", - success: function (data, textStatus, xhr) { - if (xhr.status === 200) { // Only execute if status is 200 (image exists) - $(new Image()) - .attr("src", "/get_curve_image/" + data_id + "/" + data_json.Middle_Slice) - .load(function () { - $("#imgDetails-EM-GT-curve").attr("src", this.src); - }); - $("#imgDetails-EM-GT-curve").removeClass("d-none"); - } - else if (xhr.status === 204) { // Only execute if image does not exist) - console.log("Curve image does not exist"); - - $.ajax({ - url: "/get_auto_curve_image/" + data_id + "/" + data_json.Middle_Slice, - type: "HEAD", - success: function (data, textStatus, xhr) { - if (xhr.status === 200) { // Only execute if status is 200 (image exists) - $(new Image()) - .attr("src", "/get_auto_curve_image/" + data_id + "/" + data_json.Middle_Slice) - .load(function () { - $("#imgDetails-EM-GT-curve").attr("src", this.src); - }); - $("#imgDetails-EM-GT-curve").removeClass("d-none"); - } - else if (xhr.status === 204) { // Only execute if image does not exist) - console.log("Curve image does not exist"); - $("#imgDetails-EM-GT-curve").addClass("d-none"); - } - }, - }); - } - }, - }); + $('[id^="drawButton-"]').click(async function () { + [page, dataId, label] = $(this).attr("id").replace(/drawButton-/, "").split("-"); - $.ajax({ - url: "/get_circle_pre_image/" + data_id + "/" + data_json.Middle_Slice, - type: "HEAD", - success: function (data, textStatus, xhr) { - if (xhr.status === 200) { // Only execute if status is 200 (image exists) - $(new Image()) - .attr("src", "/get_circle_pre_image/" + data_id + "/" + data_json.Middle_Slice) - .on("load", function () { - $("#imgDetails-EM-GT-circlePre").attr("src", this.src); - }); - $("#imgDetails-EM-GT-circlePre").removeClass("d-none"); - } - else if (xhr.status === 204) { // Only execute if status is 404 (image does not exist) - console.log("Circle Pre image does not exist"); - $("#imgDetails-EM-GT-circlePre").addClass("d-none"); - } - }, - error: function () { - $("#imgDetails-EM-GT-circlePre").addClass("d-none"); - }, - }); + const mode = "draw"; + const load = "full"; - $.ajax({ - url: "/get_circle_post_image/" + data_id + "/" + data_json.Middle_Slice, - type: "HEAD", - success: function (data, textStatus, xhr) { - if (xhr.status === 200) { // Only execute if status is 200 (image exists) - $(new Image()) - .attr("src", "/get_circle_post_image/" + data_id + "/" + data_json.Middle_Slice) - .load(function () { - $("#imgDetails-EM-GT-circlePost").attr("src", this.src); - }); - $("#imgDetails-EM-GT-circlePost").removeClass("d-none"); - } - else if (xhr.status === 204) { // Only execute if status is 404 (image does not exist) - console.log("Circle Post image does not exist"); - $("#imgDetails-EM-GT-circlePost").addClass("d-none"); - } - } - }); - } + try { + const reqData = await $.ajax({ + url: "/get_instance", + type: "POST", + data: { mode, load, data_id: dataId, page }, + }); - // update the range slider - $("#rangeSlices").attr("min", data.range_min); - $("#rangeSlices").attr("max", data.range_min + data.number_of_slices - 1); - $("#rangeSlices").val(data.halflen); - $("#rangeSlices").attr("data_id", data_id); - $("#rangeSlices").attr("page", page); + dataJson = JSON.parse(reqData.data); + currentSlice = dataJson.Middle_Slice; - $("#minSlice").html(0); - $("#maxSlice").html(data.number_of_slices - 1); + $drawModal.data("viewed-instance-slice", currentSlice); - cz0 = data_json.cz0; - cy0 = data_json.cy0; - cx0 = data_json.cx0; + $imgSource.addClass(label.toLowerCase()).attr( + "src", + `/get_source_image/${dataId}/${dataJson.Middle_Slice}` + ); - $("#rangeSlices").data("viewed_instance_slice", data_json.Middle_Slice); - }); + if (mode === "draw") { + await updateImages(dataId, dataJson.Middle_Slice); + } - // retrieve the updated NG link - let req_ng = $.ajax({ - url: "/neuro", - type: "POST", - // we set mode to 'annotate' as we would like to set the focus on the particular instance - data: { cz0: cz0, cy0: cy0, cx0: cx0, mode: "annotate" }, - }); + const { cz0, cy0, cx0 } = dataJson; + const reqNg = await $.ajax({ + url: "/neuro", + type: "POST", + data: { cz0, cy0, cx0, mode: "annotate" }, + }); - req_ng.done(function (data) { - let ng_link = data.ng_link; - $("#ng-iframe-draw").attr("src", ng_link); - }); + $("#ng-iframe-draw").attr("src", reqNg.ng_link); + + updateModalHeader(); + } catch (error) { + console.error("Error loading instance:", error); + } }); - // with in the modal view retrieve the instance specific data - // to switch between the slices - $("#rangeSlices").on("input", function () { - // set the visibility of the canvases to hidden - // this will trigger the deletion of the canvas - $("canvas.curveCanvas").addClass("d-none"); - $("canvas.circleCanvasPre").addClass("d-none"); - $("canvas.circleCanvasPost").addClass("d-none"); - - // reset all buttons - $("#canvasButtonPreCRD").text("Pre-Synaptic CRD"); - $("#canvasButtonPostCRD").text("Post-Synaptic CRD"); - $("#canvasButtonPreCRD").prop("disabled", false); - $("#canvasButtonPostCRD").prop("disabled", false); - - // reset the draw mask button - $("#canvasButtonDrawMask").text("Draw Mask"); - $("#canvasButtonDrawMask").prop("disabled", false); + let isModalScrollingLocked = false; + let modalScrollDelta = 0; + const MODAL_SCROLL_THRESHOLD = 65; - // disable all options except the activate canvas button - $("#canvasButtonFill").prop("disabled", true); - $("#canvasButtonRevise").prop("disabled", true); - $("#canvasButtonSave").prop("disabled", true); - - // get the current slice slice index - let viewed_instance_slice = $(this).val(); - // update the appropriate attribute to be used by the draw.js script - $(this).data("viewed_instance_slice", viewed_instance_slice); - - // the instance identifiers - var data_id = $(this).attr("data_id"); - var page = $(this).attr("page"); - - // we are currently in drawing mode - var mode = "draw"; - - // we only require the path to load a single slice and the corresponding GT - var load = "single"; - - // retrieve the information from the backend - let req = $.ajax({ - url: "/get_instance", - type: "POST", - data: { - mode: mode, - load: load, - data_id: data_id, - page: page, - viewed_instance_slice: viewed_instance_slice, - }, - }); + $drawModal.on("wheel", async (event) => { + event.preventDefault(); + if (isModalScrollingLocked) return; - // update the slice and GT that is depicted - req.done(function (data) { - $("#imgDetails-EM").attr("src", "/get_source_image/" + data_id + "/" + viewed_instance_slice); + modalScrollDelta += event.originalEvent.deltaY; - if (mode === "draw") { - $.ajax({ - url: "/get_auto_curve_image/" + data_id + "/" + viewed_instance_slice, - type: 'HEAD', - success: function (data, textStatus, xhr) { - if (xhr.status === 200) { // Only execute if status is 200 (image exists) - $(new Image()) - .attr("src", "/get_auto_curve_image/" + data_id + "/" + viewed_instance_slice) - .load(function () { - $("#imgDetails-EM-GT-curve").attr("src", this.src); - }); - $("#imgDetails-EM-GT-curve").removeClass('d-none'); - } - else if (xhr.status === 204) { // Only execute if status is 404 (image does not exist) - $.ajax({ - url: "/get_curve_image/" + data_id + "/" + viewed_instance_slice, - type: 'HEAD', - success: function (data, textStatus, xhr) { - if (xhr.status === 200) { // Only execute if status is 200 (image exists) - $(new Image()) - .attr("src", "/get_curve_image/" + data_id + "/" + viewed_instance_slice) - .load(function () { - $("#imgDetails-EM-GT-curve").attr("src", this.src); - }); - $("#imgDetails-EM-GT-curve").removeClass('d-none'); - } - else if (xhr.status === 204) { // Only execute if status is 404 (image does not exist) - console.log("Curve image does not exist"); - $("#imgDetails-EM-GT-curve").addClass('d-none'); - } - } - }); - } - } - }); + if (Math.abs(modalScrollDelta) < MODAL_SCROLL_THRESHOLD) return; - $.ajax({ - url: "/get_circle_pre_image/" + data_id + "/" + viewed_instance_slice, - type: "HEAD", - success: function (data, textStatus, xhr) { - if (xhr.status === 200) { // Only execute if status is 200 (image exists) - $(new Image()) - .attr("src", "/get_circle_pre_image/" + data_id + "/" + viewed_instance_slice) - .load(function () { - $("#imgDetails-EM-GT-circlePre").attr("src", this.src); - }); - $("#imgDetails-EM-GT-circlePre").removeClass("d-none"); - } - else if (xhr.status === 204) { // Only execute if status is 404 (image does not exist) - console.log("Circle Pre image does not exist"); - $("#imgDetails-EM-GT-circlePre").addClass("d-none"); - } - }, - }); + modalScrollDelta = 0; + isModalScrollingLocked = true; - $.ajax({ - url: "/get_circle_post_image/" + data_id + "/" + viewed_instance_slice, - type: "HEAD", - success: function (data, textStatus, xhr) { - if (xhr.status === 200) { // Only execute if status is 200 (image exists) - $(new Image()) - .attr("src", "/get_circle_post_image/" + data_id + "/" + viewed_instance_slice) - .load(function () { - $("#imgDetails-EM-GT-circlePost").attr("src", this.src); - }); - $("#imgDetails-EM-GT-circlePost").removeClass("d-none"); - } - else if (xhr.status === 204) { // Only execute if status is 404 (image does not exist) - console.log("Circle Post image does not exist"); - $("#imgDetails-EM-GT-circlePost").addClass("d-none"); - } - }, - }); + $("canvas.curveCanvas, canvas.circleCanvasPre, canvas.circleCanvasPost").addClass("d-none"); + $("#canvasButtonPreCRD, #canvasButtonPostCRD").prop("disabled", false); + $("#canvasButtonFill, #canvasButtonRevise, #canvasButtonSave").prop("disabled", true); + $("#canvasButtonDrawMask").prop("disabled", false); + $("#canvasButtonDrawMask") + .html('') + .attr("title", "Draw Mask"); + + const newSlice = currentSlice + (event.originalEvent.deltaY > 0 ? 1 : -1); + + if (newSlice < dataJson.Min_Slice || newSlice > dataJson.Max_Slice) { + isModalScrollingLocked = false; + return; + } + + try { + const req = await $.ajax({ + url: "/get_instance", + type: "POST", + data: { + mode: "draw", + load: "single", + data_id: dataId, + page, + viewedInstanceSlice: newSlice, + }, + }); + + const exists = await $.get(`/source_img_exists/${dataId}/${newSlice}`); + if (exists) { + $imgSource.attr("src", `/get_source_image/${dataId}/${newSlice}`); + await updateImages(dataId, newSlice); + currentSlice = newSlice; + $drawModal.data("viewed-instance-slice", newSlice); + updateModalHeader(); } - }); + } catch (error) { + console.error("Error loading slice:", error); + } finally { + isModalScrollingLocked = false; + } }); - $("#add_new_instance").click(async function () { - // open a new Neuroglancer view - let req_ng = $.ajax({ - url: "/neuro", - type: "POST", - data: { cz0: 0, cy0: 0, cx0: 0, mode: "draw" }, - }); - - req_ng.done(function (data) { - let ng_link = data.ng_link; - $("#ng-iframe-draw").attr("src", ng_link); - }); - - $("#review_bbox").show(); - $("#back_to_2d").hide(); + $("#add_new_instance").click(async () => { + try { + const reqNg = await $.ajax({ + url: "/neuro", + type: "POST", + data: { cz0: 0, cy0: 0, cx0: 0, mode: "draw" }, + }); + + $("#ng-iframe-draw").attr("src", reqNg.ng_link); + $("#review_bbox").show(); + $("#back_to_2d").hide(); + } catch (error) { + console.error("Error adding new instance:", error); + } }); - // toggle the functionality of the NG module based on the current use-case: select slice for drawing - $("#ng-link-draw").on("click", function () { + $("#ng-link-draw").click(() => { $("#review_bbox").hide(); $("#back_to_2d").show(); }); - // ensure that the canvas is depicted when returning to the 2D view - $("#back_to_2d").on("click", function () { - $("canvas.curveCanvas").removeClass("d-none"); // change the visibility of the canvas - // TODO if I check the ng before drawing any thing we will see the picture logo instead of the canvas - // Have to also check this logic for the circle canvas + $("#back_to_2d").click(() => { + $("canvas.curveCanvas").removeClass("d-none"); }); - $("#review_bbox").click(async function (e) { - // retrieve the bb information from the backend - $.ajax({ - url: "/ng_bbox_fn", - type: "POST", - data: { z1: 0, z2: 0, my: 0, mx: 0 }, - }).done(function (data) { + $("#review_bbox").click(async () => { + try { + const data = await $.ajax({ + url: "/ng_bbox_fn", + type: "POST", + data: { z1: 0, z2: 0, my: 0, mx: 0 }, + }); + $("#m_x").val(data.mx); $("#m_y").val(data.my); - $("#d_z1").val(data.z1); $("#d_z2").val(data.z2); - }); + } catch (error) { + console.error("Error reviewing bbox:", error); + } }); - // add event listener if current view is draw - if (current_view === "draw") { - $("#save_bbox").click(async function () { - - // show loading-bar - $('#loading-bar').css('display', 'flex'); + if (currentView === "draw") { + $("#save_bbox").click(async () => { + $("#loading-bar").css("display", "flex"); try { - // update the bb information with the manual corrections and pass them to the backend - // trigger the processing/save to the pandas df in the backend await $.ajax({ url: "/ng_bbox_fn_save", type: "POST", data: { - currentPage: currentPage, + currentPage, z1: $("#d_z1").val(), z2: $("#d_z2").val(), my: $("#m_y").val(), @@ -347,18 +230,13 @@ $(document).ready(function () { }, }); - // hide modules $("#drawModalFNSave, #drawModalFN").modal("hide"); - - // refresh page location.reload(); } catch (error) { - console.error('Error saving bbox:', error); + console.error("Error saving bbox:", error); } finally { - // Hide the loading bar after the operation completes - $('#loading-bar').css('display', 'none'); + $("#loading-bar").css("display", "none"); } }); } - }); diff --git a/synanno/static/pull_ng_coordinates.js b/synanno/static/pull_ng_coordinates.js index 1113d35..cb4f2df 100644 --- a/synanno/static/pull_ng_coordinates.js +++ b/synanno/static/pull_ng_coordinates.js @@ -28,7 +28,10 @@ function checkCoordinates() { .done(response => { const { cz, cy, cx } = response; if (cz !== initialCoordinates.cz || cy !== initialCoordinates.cy || cx !== initialCoordinates.cx) { - $('#neuron-id-draw').text(`cx: ${parseInt(cx)}, cy: ${parseInt(cy)}, cz: ${parseInt(cz)}`); + console.log("Old:", initialCoordinates, "New:", { cz, cy, cx }); + console.log($("#neuron-id-draw").length); // Should be 1 + + $('#neuron-id-draw').text(`cx: ${parseInt(cx)} - cy: ${parseInt(cy)} - cz: ${parseInt(cz)}`); initialCoordinates = { cz, cy, cx }; } }) diff --git a/synanno/static/viewer.js b/synanno/static/viewer.js index 0b18852..a8bb57a 100644 --- a/synanno/static/viewer.js +++ b/synanno/static/viewer.js @@ -41,8 +41,8 @@ window.onload = async () => { processSwcFile(swcTxt, sectionArrays, activeNeuronSection, activeSynapseIDs); if (synapsePointCloud) { - updateLoadingBar(parseInt(synapsePointCloud.length / 3), activeSynapseIDs); processSynapseCloudData(synapsePointCloud, maxVolumeSize, activeSynapseIDs, initialLoad); + updateLoadingBar(parseInt(synapsePointCloud.length / 3), activeSynapseIDs); } else { console.error("No synapse cloud path provided."); } @@ -176,9 +176,9 @@ function processSynapseCloudData(data, maxVolumeSize, activeSynapseIDs, initialL colors[i * 4 + 3] = 1.0; if (activeSynapseIDs.length > 0) { - sizes[i] = activeSynapseIDs.includes(i) ? maxVolumeSize : 10; + sizes[i] = activeSynapseIDs.includes(i) ? maxVolumeSize*maxVolumeSize : 10; - window.synapseColors[i] === "green" || window.synapseColors[i] === "red" ? 1.0 : 0.4; + alphas[i] = window.synapseColors[i] === "green" || window.synapseColors[i] === "red" ? 0.8 : 0.4; alphas[i] = activeSynapseIDs.includes(i) ? 1.0 : 0.4; } else if (initialLoad) { sizes[i] = 10; @@ -613,16 +613,17 @@ function createMetadataElement(metadata, colors, activeNeuronSection) { } -function updateLoadingBar(synapse_count, activeSynapseIDs) { - if (!Array.isArray(activeSynapseIDs)) { - console.error("activeSynapseIDs is not an array!", activeSynapseIDs); - activeSynapseIDs = []; +function updateLoadingBar(synapse_count) { + if (!window.synapseColors || typeof window.synapseColors !== "object") { + console.error("synapseColors is not defined or invalid!", window.synapseColors); + return; } - if (activeSynapseIDs.length === 0) return; + const highlightedSynapseCount = Object.values(window.synapseColors).filter( + (color) => color === "green" || color === "red" + ).length; - const lowestDisplayedSectionIdx = Math.min(...activeSynapseIDs); - const progressPercent = (lowestDisplayedSectionIdx / synapse_count) * 100; + const progressPercent = (highlightedSynapseCount / synapse_count) * 100; document.getElementById("loading_progress").style.width = `${progressPercent}%`; } diff --git a/synanno/templates/annotation.html b/synanno/templates/annotation.html index 1a59996..bd3177d 100644 --- a/synanno/templates/annotation.html +++ b/synanno/templates/annotation.html @@ -15,71 +15,87 @@ {% block content %}