feat: Add anonymisation issue summary rendering for flagged series in upload process
This commit is contained in:
@@ -71,6 +71,7 @@
|
||||
|
||||
<div class="alert alert-warning d-none anon-alert" role="alert">
|
||||
<p class="mb-2">Potential anonymisation issues were detected. Flagged series are highlighted below.</p>
|
||||
<div id="anon-issue-summary" class="small"></div>
|
||||
<details>
|
||||
<summary>Override anonymisation check</summary>
|
||||
<p class="mt-2 mb-2">Only override if you are certain the warnings are false positives.</p>
|
||||
@@ -210,6 +211,12 @@
|
||||
return String(value).trim();
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = value === undefined || value === null ? "" : String(value);
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
async function parseDicomMetadata(file) {
|
||||
try {
|
||||
const buffer = await file.arrayBuffer();
|
||||
@@ -319,6 +326,108 @@
|
||||
});
|
||||
}
|
||||
|
||||
function buildAnonymisationIssueSummary() {
|
||||
const selectedRecords = window.uploadPreview.records.filter((record) => record.include);
|
||||
if (!selectedRecords.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const seriesMap = {};
|
||||
let tagWarningFiles = 0;
|
||||
let ocrWarningFiles = 0;
|
||||
|
||||
for (const record of selectedRecords) {
|
||||
const hasTagWarnings = record.tagWarnings.length > 0;
|
||||
const hasOcrWarning = !!record.ocrWarning;
|
||||
if (!hasTagWarnings && !hasOcrWarning) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!seriesMap[record.seriesUid]) {
|
||||
seriesMap[record.seriesUid] = {
|
||||
uid: record.seriesUid,
|
||||
description: record.seriesDescription || "(no series description)",
|
||||
tagFiles: 0,
|
||||
ocrFiles: 0,
|
||||
details: new Set(),
|
||||
};
|
||||
}
|
||||
|
||||
const entry = seriesMap[record.seriesUid];
|
||||
if (hasTagWarnings) {
|
||||
tagWarningFiles += 1;
|
||||
entry.tagFiles += 1;
|
||||
record.tagWarnings.forEach((warning) => entry.details.add(warning));
|
||||
}
|
||||
if (hasOcrWarning) {
|
||||
ocrWarningFiles += 1;
|
||||
entry.ocrFiles += 1;
|
||||
entry.details.add("Possible burned-in patient text detected by OCR");
|
||||
}
|
||||
}
|
||||
|
||||
const seriesIssues = Object.values(seriesMap);
|
||||
if (!seriesIssues.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
seriesIssues.sort((a, b) => {
|
||||
const aScore = a.tagFiles + a.ocrFiles;
|
||||
const bScore = b.tagFiles + b.ocrFiles;
|
||||
return bScore - aScore;
|
||||
});
|
||||
|
||||
return {
|
||||
selectedFiles: selectedRecords.length,
|
||||
flaggedSeries: seriesIssues.length,
|
||||
tagWarningFiles,
|
||||
ocrWarningFiles,
|
||||
issues: seriesIssues,
|
||||
};
|
||||
}
|
||||
|
||||
function renderAnonymisationIssueSummary() {
|
||||
const summary = document.getElementById("anon-issue-summary");
|
||||
if (!summary) return;
|
||||
|
||||
const data = buildAnonymisationIssueSummary();
|
||||
if (!data) {
|
||||
summary.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const issueItems = data.issues.slice(0, 8).map((issue) => {
|
||||
const issueBits = [];
|
||||
if (issue.tagFiles) issueBits.push(`Tag warnings in ${issue.tagFiles} file${issue.tagFiles === 1 ? "" : "s"}`);
|
||||
if (issue.ocrFiles) issueBits.push(`OCR warnings in ${issue.ocrFiles} file${issue.ocrFiles === 1 ? "" : "s"}`);
|
||||
const detailPreview = Array.from(issue.details).slice(0, 2).join("; ");
|
||||
|
||||
return `
|
||||
<li>
|
||||
<strong>${escapeHtml(issue.description)}</strong>
|
||||
<span class="text-muted">(${escapeHtml(issue.uid)})</span><br>
|
||||
${escapeHtml(issueBits.join(" \u00b7 "))}
|
||||
${detailPreview ? `<br><span class="text-muted">${escapeHtml(detailPreview)}</span>` : ""}
|
||||
</li>
|
||||
`;
|
||||
}).join("");
|
||||
|
||||
const extraCount = Math.max(0, data.issues.length - 8);
|
||||
const extraLine = extraCount ? `<p class="mb-0 mt-2 text-muted">+ ${extraCount} more flagged series</p>` : "";
|
||||
|
||||
summary.innerHTML = `
|
||||
<p class="mb-2">
|
||||
<strong>Summary:</strong>
|
||||
${data.flaggedSeries} flagged series across ${data.selectedFiles} selected files.
|
||||
Tag warnings: ${data.tagWarningFiles}. OCR warnings: ${data.ocrWarningFiles}.
|
||||
</p>
|
||||
<ul class="mb-0 ps-3">
|
||||
${issueItems}
|
||||
</ul>
|
||||
${extraLine}
|
||||
`;
|
||||
}
|
||||
|
||||
function updateUploadButtonState() {
|
||||
const uploadButton = document.getElementById("uploadButton");
|
||||
const anonAlert = document.querySelector(".anon-alert");
|
||||
@@ -329,6 +438,8 @@
|
||||
const hasPendingDuplicateChecks = duplicatesEnabled && hasSelected &&
|
||||
window.uploadPreview.records.some((r) => r.include && !r.duplicateChecked);
|
||||
|
||||
renderAnonymisationIssueSummary();
|
||||
|
||||
if (!hasSelected) {
|
||||
uploadButton.disabled = true;
|
||||
anonAlert.classList.add("d-none");
|
||||
|
||||
Reference in New Issue
Block a user