diff --git a/src/js/science_plugins.js b/src/js/science_plugins.js index 8e195dc8130c2e6e0e9eb46745eb1dd475d7bd3c..53697fd81f933f20665cbae5c7c09c9afdf4bfaf 100644 --- a/src/js/science_plugins.js +++ b/src/js/science_plugins.js @@ -189,7 +189,7 @@ Science_plugins.prototype.updateSliceRegion = async function (img_id = _via_imag } _via_img_metadata[_via_image_id].updateHash(hash); this.GroupingWorker.postMessage({ - image_id : img_id, + image_id: img_id, slice_regions_id: JSON.stringify(groupBy), regions: JSON.stringify(_via_img_metadata[img_id].regions), grouped_regions: JSON.stringify(_via_img_metadata[img_id].groupedRegions.groups, Project.replacer), @@ -219,20 +219,20 @@ Science_plugins.prototype.groupingWorkerOnMessage = async function (e) { Science_plugins.prototype.getGroupingProgress = function (imageId, timeout = 5000) { return new Promise((resolve) => { const startTime = Date.now(); - + const checkProgress = () => { - // If no longer processing, resolve success - if (!this.workerSignal.has(imageId)) { + // If no longer processing, resolve success + if (!this.workerSignal.has(imageId)) { resolve(true); return; } - + // If timeout reached, resolve false if (Date.now() - startTime > timeout) { resolve(false); return; } - + // Check again after small delay setTimeout(checkProgress, 100); }; @@ -335,7 +335,7 @@ Science_plugins.prototype.calcPolygonArea = function (xCoordinates, yCoordinates // Export the region sizes to a csv file Science_plugins.prototype.ExportArea = async function () { - let rows = [ + const rows = [ ["Filename", "File_ID", "Treatment_ID", "Slice_ID", "Slice_area", "Infract_area", "Risk_area", "Slice_score", "Infract_score", "Risk_score"] ]; let done = 0; @@ -344,130 +344,119 @@ Science_plugins.prototype.ExportArea = async function () { if (_via_img_metadata[img_id].regions.length === 0) { continue; } - if (!_via_img_metadata[img_id].groupedRegions.groups.size > 0) { - if (!this.updateSliceRegion(img_id, false)) { - console.log("Slice regions updated but not grouped yet. Skipping image: " + img_id); - continue; - } + this.updateSliceRegion(_via_image_id, false); + await this.getGroupingProgress(_via_image_id); + + let slices = []; + if (_via_img_metadata[img_id].groupedRegions.groups === 0) { + message.showError("No groups found! Ensure that there are slice regions in the image: " + _via_image_filename_list[img_index]); + return; } - await new Promise((resolve) => { - let interval = setInterval(() => { - if (_via_img_metadata[img_id].groupedRegions.groups.size > 0) { - clearInterval(interval); - resolve(); - } - }, 100); - }).then(() => { - let slices = []; - if (_via_img_metadata[img_id].groupedRegions.groups === 0) { - return; + for (let i = 0; i < _via_img_metadata[img_id].groupedRegions.groupBy.length; i++) { + let element = _via_img_metadata[img_id].groupedRegions.groupBy[i]; + let slice = { + ID: i + 1, + Area: 0, + InfarctArea: 0, + RiskArea: 0, + SliceScore: "n/a", + InfarctScore: "n/a", + RiskScore: "n/a", + }; + let scores = { + score_exists: false, + Slice: 0, + Slice_no: 0, + Infarct: 0, + Infarct_no: 0, + Risk: 0, + Risk_no: 0, + }; + if (_via_img_metadata[img_id].groupedRegions.groupIDs.has(element)) { + slice.ID = _via_img_metadata[img_id].groupedRegions.groupIDs.get(element); } - for (let i = 0; i < _via_img_metadata[img_id].groupedRegions.groupBy.length; i++) { - let element = _via_img_metadata[img_id].groupedRegions.groupBy[i]; - let slice = { - ID: i + 1, - Area: 0, - InfarctArea: 0, - RiskArea: 0, - SliceScore: "n/a", - InfarctScore: "n/a", - RiskScore: "n/a", - }; - let scores = { - score_exists: false, - Slice: 0, - Slice_no: 0, - Infarct: 0, - Infarct_no: 0, - Risk: 0, - Risk_no: 0, - }; - if (_via_img_metadata[img_id].groupedRegions.groupIDs.has(element)) { - slice.ID = _via_img_metadata[img_id].groupedRegions.groupIDs.get(element); - } - slice.Area = this.calcPolygonArea(_via_img_metadata[img_id].regions[element].shape_attributes.all_points_x, _via_img_metadata[img_id].regions[element].shape_attributes.all_points_y); - if (_via_img_metadata[img_id].regions[element].hasOwnProperty("score")) { + slice.Area = this.calcPolygonArea(_via_img_metadata[img_id].regions[element].shape_attributes.all_points_x, _via_img_metadata[img_id].regions[element].shape_attributes.all_points_y); + if (_via_img_metadata[img_id].regions[element].hasOwnProperty("score")) { + scores.score_exists = true; + scores.Slice += _via_img_metadata[img_id].regions[element].score; + scores.Slice_no++; + } + for (let i = 0; i < _via_img_metadata[img_id].getGroupedRegion(element).length; i++) { + let region = _via_img_metadata[img_id].regions[_via_img_metadata[img_id].getGroupedRegion(element)[i]]; + let Area = this.calcPolygonArea(region.shape_attributes.all_points_x, region.shape_attributes.all_points_y); + if (region.hasOwnProperty("score")) { scores.score_exists = true; - scores.Slice += _via_img_metadata[img_id].regions[element].score; - scores.Slice_no++; - } - for (let i = 0; i < _via_img_metadata[img_id].getGroupedRegion(element).length; i++) { - let region = _via_img_metadata[img_id].regions[_via_img_metadata[img_id].getGroupedRegion(element)[i]]; - let Area = this.calcPolygonArea(region.shape_attributes.all_points_x, region.shape_attributes.all_points_y); - if (region.hasOwnProperty("score")) { - scores.score_exists = true; - switch (region.region_attributes.Type) { - case "Slice": - if (region.score === "n/a") { - break; - } - scores.Slice += region.score; - scores.Slice_no++; - break; - case "Risk": - if (region.score === "n/a") { - break; - } - scores.Risk += region.score; - scores.Risk_no++; - break; - case "Infarct": - if (region.score === "n/a") { - break; - } - scores.Infarct += region.score; - scores.Infarct_no++; - break; - } - } switch (region.region_attributes.Type) { case "Slice": - slice.Area -= Area; + if (region.score === "n/a") { + break; + } + scores.Slice += region.score; + scores.Slice_no++; break; case "Risk": - slice.RiskArea += Area; + if (region.score === "n/a") { + break; + } + scores.Risk += region.score; + scores.Risk_no++; break; case "Infarct": - slice.InfarctArea += Area; + if (region.score === "n/a") { + break; + } + scores.Infarct += region.score; + scores.Infarct_no++; break; } } - if (scores.score_exists) { - if (scores.Slice_no > 0 && scores.Slice > 0) { - slice.SliceScore = scores.Slice / scores.Slice_no; - } else { - slice.SliceScore = "n/a"; - } - if (scores.Infarct_no > 0 && scores.Infarct > 0) { - slice.InfarctScore = scores.Infarct / scores.Infarct_no; - } else { - slice.InfarctScore = "n/a"; - } - if (scores.Risk_no > 0 && scores.Risk > 0) { - slice.RiskScore = scores.Risk / scores.Risk_no; - } else { - slice.RiskScore = "n/a"; - } + switch (region.region_attributes.Type) { + case "Slice": + slice.Area -= Area; + break; + case "Risk": + slice.RiskArea += Area; + break; + case "Infarct": + slice.InfarctArea += Area; + break; + } + } + if (scores.score_exists) { + if (scores.Slice_no > 0 && scores.Slice > 0) { + slice.SliceScore = scores.Slice / scores.Slice_no; + } else { + slice.SliceScore = "n/a"; + } + if (scores.Infarct_no > 0 && scores.Infarct > 0) { + slice.InfarctScore = scores.Infarct / scores.Infarct_no; + } else { + slice.InfarctScore = "n/a"; + } + if (scores.Risk_no > 0 && scores.Risk > 0) { + slice.RiskScore = scores.Risk / scores.Risk_no; + } else { + slice.RiskScore = "n/a"; } - slices.push(slice); } - let img_file = _via_image_filename_list[img_index]; - let file_id = _via_img_metadata[img_id].file_attributes.ID || ""; - slices.forEach((slice, i) => { - rows.push([ - img_file, - file_id, - slice.ID, - slice.Area, - slice.InfarctArea, - slice.RiskArea, - slice.SliceScore, - slice.InfarctScore, - slice.RiskScore - ]); - }); - done++; + slices.push(slice); + } + let img_file = _via_image_filename_list[img_index]; + let file_id = _via_img_metadata[img_id].file_attributes.ID || ""; + slices.forEach((slice, i) => { + rows.push([ + img_file, + file_id, + slice.ID, + slice.Area, + slice.InfarctArea, + slice.RiskArea, + slice.SliceScore, + slice.InfarctScore, + slice.RiskScore + ]); }); + done++; } let csvContent = "data:text/csv;charset=utf-8," + rows.map(e => e.join(",")).join("\n"); diff --git a/src/js/sidebar.js b/src/js/sidebar.js index baf0a182af4a916bdaaf6c937dcb7283962d6dea..7ee6d0db7f83eee2a21be4cf0810d15986de8078 100644 --- a/src/js/sidebar.js +++ b/src/js/sidebar.js @@ -696,7 +696,7 @@ class Sidebar { this.ae.empty(); this.annotation_editor_update_header_html(); this.annotation_editor_update_metadata_html(); - this.ae.removeClass("d-none"); // show the annotation editor + this.ae.removeClass("d-none"); drawing.updateCheckedLockHtml(); } } catch (err) { @@ -882,117 +882,145 @@ class Sidebar { } } - async annotation_editor_update_metadata_html() { - if (!_via_img_count) { - return; + ensureTableBodyExists() { + if (!this.ae.find("tbody").length) { + const tbody = $('<tbody id="annotation_editor_content"></tbody>'); + this.ae.append(tbody); + this.aec = tbody; } + } - if (!this.ae.has("tbody").length) { - this.ae.append('<tbody id="annotation_editor_content"></tbody>'); - this.aec = $("#annotation_editor_content"); - } + createGroupRow(groupIndex, groupId) { + const rowId = `ae_row_${groupId}`; + const row = $("<tr>", { + id: rowId, + onclick: "plugin.selectAllRegionsInGroup(this)", + style: "height: 40px;", + class: "align-bottom", + }); + + const header = $("<th>", { + scope: "row", + id: `ae_${groupId}_rid`, + html: "O", + }); + + const placeholder = _via_img_metadata[_via_image_id].groupedRegions.groupIDs.has(groupId) + ? _via_img_metadata[_via_image_id].groupedRegions.groupIDs.get(groupId) + : `Group: ${groupIndex + 1}`; + + const cell = $("<td>", { + id: `ae_row_${groupId}_group`, + colspan: 4, + }); + + const input = $("<input>", { + class: 'form-control form-control-sm', + onchange: 'plugin.changeGroupIdentifier(this)', + type: 'text', + placeholder: placeholder, + 'aria-label': 'group identifier', + id: `group_${groupId}` + }); + + cell.append(input); + row.append(header, cell); + + return row; + } + - switch (_via_metadata_being_updated) { - case "region": - let rindex; - if (_via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID) { - this.aec.append(this.annotation_editor_get_metadata_row_html(0)); + async updateRegionMetadata(){ + let rindex; + if (_via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID) { + this.aec.append(this.annotation_editor_get_metadata_row_html(0)); + } else { + if (_via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE) { + if (_via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION) { + this.aec.append( + this.annotation_editor_get_metadata_row_html( + drawing.userSelRegionId() + ) + ); } else { - if (_via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE) { - if (_via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION) { - this.aec.append( - this.annotation_editor_get_metadata_row_html( - drawing.userSelRegionId() - ) + if (_via_img_metadata[_via_image_id].isGroupable()) { + plugin.updateSliceRegion(_via_image_id, false); + await plugin.getGroupingProgress(_via_image_id); + let tmp = []; + let colspan = 4; + let done_ids = []; + const fragment = document.createDocumentFragment(); + for (rindex = 0; rindex < _via_img_metadata[_via_image_id].groupedRegions.groupBy.length; ++rindex) { + if (!_via_img_metadata[_via_image_id].isGroupedKey(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex])) { + continue; + } + this.createGroupRow(rindex, _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]).appendTo(fragment); + $(fragment).append( + this.annotation_editor_get_metadata_row_html(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]) ); - } else { - if (_via_img_metadata[_via_image_id].isGroupable()) { - plugin.updateSliceRegion(_via_image_id); - await plugin.getGroupingProgress(_via_image_id); - let tmp = []; - let colspan = 4; - let done_ids = []; - for (rindex = 0; rindex < _via_img_metadata[_via_image_id].groupedRegions.groupBy.length; ++rindex) { - if (!_via_img_metadata[_via_image_id].isGroupedKey(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex])) { - continue; - } - $("<tr>", { - id: "ae_row_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex], - onclick: "plugin.selectAllRegionsInGroup(this)", - style: "height: 40px;", - class: "align-bottom", - }).appendTo(this.aec); - $("<th>", { - scope: "row", - id: "ae_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex] + "_rid", - html: "O", - }).appendTo("#ae_row_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]); - if (_via_img_metadata[_via_image_id].groupedRegions.groupIDs.has(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex])) { - $("<td> ", { - id: "ae_row_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex] + "_group", - colspan: colspan, - html: "<input class='form-control form-control-sm' onchange='plugin.changeGroupIdentifier(this)' type='text' placeholder='" + _via_img_metadata[_via_image_id].groupedRegions.groupIDs.get(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]) + "' aria-label='group identifier' id='group_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex] + "'>" - }).appendTo("#ae_row_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]); - } else { - $("<td> ", { - id: "ae_row_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex] + "_group", - colspan: colspan, - html: "<input class='form-control form-control-sm' onchange='plugin.changeGroupIdentifier(this)' type='text' placeholder='" + "Group: " + (rindex + 1) + "' aria-label='group identifier' id='group_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex] + "'>" - }).appendTo("#ae_row_" + _via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]); - } - - this.aec.append( - this.annotation_editor_get_metadata_row_html(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]) - ); - done_ids.push(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]); - tmp = _via_img_metadata[_via_image_id].getGroupedRegion(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]); - for (const element of tmp) { - this.aec.append( - this.annotation_editor_get_metadata_row_html(element) - ); - done_ids.push(element); - } - } - $("<tr>", { - id: "ae_no_group", - style: "height: 40px;", - class: "align-bottom", - }).appendTo(this.aec); - $("<th>", { - scope: "row", - id: "ae_no_group_rid", - html: "X", - }).appendTo("#ae_no_group"); - $("<td> ", { - id: "ae_row_no_group", - colspan: colspan, - html: "Other", - }).appendTo("#ae_no_group"); - for ( - rindex = 0; - rindex < _via_img_metadata[_via_image_id].regions.length; - ++rindex - ) { - if (!done_ids.includes(rindex)) { - this.aec.append( - this.annotation_editor_get_metadata_row_html(rindex) - ); - } - } - } else { - for ( - rindex = 0; - rindex < _via_img_metadata[_via_image_id].regions.length; - ++rindex - ) { - this.aec.append( - this.annotation_editor_get_metadata_row_html(rindex) - ); - } + done_ids.push(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]); + let tmp = _via_img_metadata[_via_image_id].getGroupedRegion(_via_img_metadata[_via_image_id].groupedRegions.groupBy[rindex]); + for (const element of tmp) { + $(fragment).append( + this.annotation_editor_get_metadata_row_html(element) + ); + done_ids.push(element); } } + this.aec.append(fragment); + const row = $("<tr>", { + id: "ae_no_group", + style: "height: 40px;", + class: "align-bottom", + }); + const header = $("<th>", { + scope: "row", + id: "ae_no_group_rid", + html: "X", + }) + const cell = $("<td> ", { + id: "ae_row_no_group", + colspan: colspan, + html: "Other", + }); + row.append(header, cell); + $(fragment).append(row); + for (rindex = 0; rindex < _via_img_metadata[_via_image_id].regions.length; ++rindex) { + if (!done_ids.includes(rindex)) { + $(fragment).append( + this.annotation_editor_get_metadata_row_html(rindex) + ); + } + } + this.aec.append(fragment); + } else { + const fragment = document.createDocumentFragment(); + for ( + rindex = 0; + rindex < _via_img_metadata[_via_image_id].regions.length; + ++rindex + ) { + $(fragment).append( + this.annotation_editor_get_metadata_row_html(rindex) + ); + } + this.aec.append(fragment); } } + } + } + } + + async annotation_editor_update_metadata_html() { + if (!_via_img_count) { + return; + } + + this.ensureTableBodyExists(); // experiment + + switch (_via_metadata_being_updated) { + case "region": + await this.updateRegionMetadata(); break; case "file": @@ -1002,10 +1030,8 @@ class Sidebar { } annotation_editor_update_row(row_id) { - if (!this.ae.has("tbody").length) { - this.ae.append('<tbody id="annotation_editor_content"></tbody>'); - this.aec = $("#annotation_editor_content"); - } + + this.ensureTableBodyExists(); // experiment let new_row = this.annotation_editor_get_metadata_row_html(row_id); let id = new_row.attr("id"); @@ -1016,10 +1042,7 @@ class Sidebar { annotation_editor_add_row(row_id) { if (this.is_annotation_editor_visible()) { - if (!this.ae.has("tbody").length) { - this.ae.append('<tbody id="annotation_editor_content"></tbody>'); - this.aec = $("#annotation_editor_content"); - } + this.ensureTableBodyExists(); // experiment let new_row = this.annotation_editor_get_metadata_row_html(row_id); let penultimate_row_id = parseInt(row_id) - 1; if (penultimate_row_id >= 0) {