feat: Enhance OCR processing with increased sample counts and improved candidate selection logic
This commit is contained in:
@@ -194,8 +194,8 @@
|
||||
};
|
||||
|
||||
const SITE_PREFIXES = ["ref", "rk9", "ra9", "rh8", "rbz", "rba"];
|
||||
const OCR_SERIES_SAMPLE_COUNT = 3;
|
||||
const OCR_MAX_SAMPLES = 120;
|
||||
const OCR_SERIES_SAMPLE_COUNT = 6;
|
||||
const OCR_MAX_SAMPLES = 180;
|
||||
const OCR_CANVAS_MAX = 768;
|
||||
const DUPLICATE_BATCH_SIZE = 500;
|
||||
|
||||
@@ -751,6 +751,11 @@
|
||||
return window.uploadPreview.ocrWorker;
|
||||
}
|
||||
|
||||
async function waitForPaint() {
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
}
|
||||
|
||||
function getOcrPhiReasons(text) {
|
||||
const value = (text || "").toLowerCase();
|
||||
const reasons = [];
|
||||
@@ -787,19 +792,15 @@
|
||||
const samples = [];
|
||||
for (const seriesRecords of Object.values(seriesMap)) {
|
||||
if (!seriesRecords.length) continue;
|
||||
|
||||
const candidateIndexes = [
|
||||
0,
|
||||
Math.floor((seriesRecords.length - 1) / 2),
|
||||
seriesRecords.length - 1,
|
||||
];
|
||||
|
||||
const sampleCount = Math.min(OCR_SERIES_SAMPLE_COUNT, seriesRecords.length);
|
||||
const used = new Set();
|
||||
for (const index of candidateIndexes) {
|
||||
for (let i = 0; i < sampleCount; i++) {
|
||||
const index = sampleCount === 1
|
||||
? 0
|
||||
: Math.floor((i * (seriesRecords.length - 1)) / (sampleCount - 1));
|
||||
if (used.has(index)) continue;
|
||||
used.add(index);
|
||||
samples.push(seriesRecords[index]);
|
||||
if (used.size >= OCR_SERIES_SAMPLE_COUNT) break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -873,6 +874,7 @@
|
||||
cornerstone.enable(element);
|
||||
const image = await cornerstone.loadAndCacheImage(imageId);
|
||||
cornerstone.displayImage(element, image);
|
||||
await waitForPaint();
|
||||
|
||||
const sourceCanvas = element.querySelector("canvas");
|
||||
if (!sourceCanvas) throw new Error("No canvas");
|
||||
@@ -894,6 +896,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
function buildOcrCandidateCanvases(baseCanvas, enhancedCanvas) {
|
||||
const candidates = [
|
||||
{ name: "full", canvas: baseCanvas },
|
||||
{ name: "full-enhanced", canvas: enhancedCanvas },
|
||||
];
|
||||
|
||||
const makeCrop = (src, sx, sy, sw, sh) => {
|
||||
const c = document.createElement("canvas");
|
||||
c.width = Math.max(1, sw);
|
||||
c.height = Math.max(1, sh);
|
||||
const ctx = c.getContext("2d", { alpha: false });
|
||||
ctx.drawImage(src, sx, sy, sw, sh, 0, 0, c.width, c.height);
|
||||
return c;
|
||||
};
|
||||
|
||||
const addRegions = (src, suffix) => {
|
||||
const w = src.width;
|
||||
const h = src.height;
|
||||
const headerH = Math.max(24, Math.floor(h * 0.23));
|
||||
const footerH = Math.max(24, Math.floor(h * 0.18));
|
||||
const cornerW = Math.max(32, Math.floor(w * 0.45));
|
||||
|
||||
candidates.push({ name: `top-left${suffix}`, canvas: makeCrop(src, 0, 0, cornerW, headerH) });
|
||||
candidates.push({ name: `top-right${suffix}`, canvas: makeCrop(src, Math.max(0, w - cornerW), 0, cornerW, headerH) });
|
||||
candidates.push({ name: `bottom-left${suffix}`, canvas: makeCrop(src, 0, Math.max(0, h - footerH), cornerW, footerH) });
|
||||
candidates.push({ name: `bottom-right${suffix}`, canvas: makeCrop(src, Math.max(0, w - cornerW), Math.max(0, h - footerH), cornerW, footerH) });
|
||||
candidates.push({ name: `header-strip${suffix}`, canvas: makeCrop(src, 0, 0, w, headerH) });
|
||||
candidates.push({ name: `footer-strip${suffix}`, canvas: makeCrop(src, 0, Math.max(0, h - footerH), w, footerH) });
|
||||
};
|
||||
|
||||
addRegions(baseCanvas, "");
|
||||
addRegions(enhancedCanvas, "-enhanced");
|
||||
return candidates;
|
||||
}
|
||||
|
||||
async function runOcrChecks() {
|
||||
syncOcrDebugPanelVisibility();
|
||||
if (!document.getElementById("check-anonymisation-ocr").checked) {
|
||||
@@ -935,26 +972,37 @@
|
||||
setProgress(`OCR ${index} of ${samples.length}: ${record.path}`);
|
||||
try {
|
||||
const { outCanvas, enhancedCanvas } = await getDownscaledOcrCanvases(record.file);
|
||||
const baseBlob = await canvasToBlob(outCanvas);
|
||||
const baseResult = await worker.recognize(baseBlob);
|
||||
const ocrCandidates = buildOcrCandidateCanvases(outCanvas, enhancedCanvas);
|
||||
|
||||
const baseText = normaliseOcrText(baseResult?.data?.text || "");
|
||||
let bestText = baseText;
|
||||
let bestConfidence = Number(baseResult?.data?.confidence || 0);
|
||||
let bestText = "";
|
||||
let bestConfidence = 0;
|
||||
let bestCandidateName = "none";
|
||||
let bestReasons = [];
|
||||
|
||||
// Fallback pass with high-contrast image when OCR output is weak/blank.
|
||||
if (bestText.length < 6) {
|
||||
const enhancedBlob = await canvasToBlob(enhancedCanvas);
|
||||
const enhancedResult = await worker.recognize(enhancedBlob);
|
||||
const enhancedText = normaliseOcrText(enhancedResult?.data?.text || "");
|
||||
const enhancedConfidence = Number(enhancedResult?.data?.confidence || 0);
|
||||
if (enhancedText.length > bestText.length || enhancedConfidence > bestConfidence) {
|
||||
bestText = enhancedText;
|
||||
bestConfidence = enhancedConfidence;
|
||||
for (const candidate of ocrCandidates) {
|
||||
const blob = await canvasToBlob(candidate.canvas);
|
||||
const result = await worker.recognize(blob);
|
||||
const text = normaliseOcrText(result?.data?.text || "");
|
||||
const confidence = Number(result?.data?.confidence || 0);
|
||||
const reasons = getOcrPhiReasons(text);
|
||||
|
||||
const isBetter = reasons.length > bestReasons.length
|
||||
|| (reasons.length === bestReasons.length && text.length > bestText.length)
|
||||
|| (reasons.length === bestReasons.length && text.length === bestText.length && confidence > bestConfidence);
|
||||
|
||||
if (isBetter) {
|
||||
bestText = text;
|
||||
bestConfidence = confidence;
|
||||
bestCandidateName = candidate.name;
|
||||
bestReasons = reasons;
|
||||
}
|
||||
|
||||
if (reasons.length > 0 && confidence >= 40 && text.length >= 6) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const reasons = getOcrPhiReasons(bestText);
|
||||
const reasons = bestReasons;
|
||||
const flagged = reasons.length > 0;
|
||||
if (flagged) {
|
||||
flaggedSeriesUids.add(record.seriesUid);
|
||||
@@ -966,7 +1014,7 @@
|
||||
path: record.path,
|
||||
flagged,
|
||||
reasons,
|
||||
preview: `${preview} (conf ${Math.round(bestConfidence)}%)`,
|
||||
preview: `${preview} (region ${bestCandidateName}, conf ${Math.round(bestConfidence)}%)`,
|
||||
});
|
||||
} catch (err) {
|
||||
// Ignore per-image OCR errors and continue with other samples.
|
||||
|
||||
Reference in New Issue
Block a user