Skip to content

Commit 2ffe0e1

Browse files
committed
refactor: decouple detection from drawing annotations
Preparing externalizing the code to a dedicated library
1 parent 4a74879 commit 2ffe0e1

File tree

5 files changed

+140
-71
lines changed

5 files changed

+140
-71
lines changed

src/FormFieldsDetection.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as pdfjsLib from "pdfjs-dist";
55
import { detectFormFields } from "./lib/formFieldDetection";
66
import { applyAcroFields } from "./lib/applyAcroFields";
77
import { ensureValidPDF } from "./lib/ensureValidPDF";
8+
import { drawDetections } from "./lib/drawDetections";
89
import { ModelSelection, type ModelType, type ModelOption } from "./components/ModelSelection";
910
import { DetectionResults, type ProcessingResult } from "./components/DetectionResults";
1011
import { ProcessingSteps } from "./components/ProcessingSteps";
@@ -178,8 +179,10 @@ export function FormFieldsDetection() {
178179
});
179180
const pdfWithAcroFieldsBlobUrl = URL.createObjectURL(pdfBlob);
180181

182+
const detectionDataWithDrawings = drawDetections(detectionResult.data);
183+
181184
setResult({
182-
pages: detectionResult.data.pages,
185+
pages: detectionDataWithDrawings.pages,
183186
processingTime: detectionResult.data.processingTime,
184187
modelInfo: detectionResult.data.modelInfo,
185188
pdfWithAcroFieldsBlobUrl,

src/components/DetectionResults.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import { useState } from "react";
22
import { useTranslation } from "react-i18next";
3-
4-
interface DetectedField {
5-
type: string;
6-
bbox: [number, number, number, number];
7-
confidence: number;
8-
}
3+
import { type DetectedField } from "../workers/inference.worker";
4+
import { FIELD_COLORS } from "../lib/drawDetections";
95

106
interface PageResult {
117
fields: DetectedField[];
@@ -60,15 +56,15 @@ export function DetectionResults({ result }: DetectionResultsProps) {
6056
<div className="mt-4 md:mt-6 flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
6157
<div className="flex flex-wrap gap-3 md:gap-4 text-sm">
6258
<div className="flex items-center gap-2">
63-
<div className="w-4 h-4 rounded" style={{ backgroundColor: "#3B82F6" }}></div>
59+
<div className="w-4 h-4 rounded" style={{ backgroundColor: FIELD_COLORS.TextBox.label }}></div>
6460
<span>TextBox</span>
6561
</div>
6662
<div className="flex items-center gap-2">
67-
<div className="w-4 h-4 rounded" style={{ backgroundColor: "#10B981" }}></div>
63+
<div className="w-4 h-4 rounded" style={{ backgroundColor: FIELD_COLORS.ChoiceButton.label }}></div>
6864
<span>ChoiceButton</span>
6965
</div>
7066
<div className="flex items-center gap-2">
71-
<div className="w-4 h-4 rounded" style={{ backgroundColor: "#F59E0B" }}></div>
67+
<div className="w-4 h-4 rounded" style={{ backgroundColor: FIELD_COLORS.Signature.label }}></div>
7268
<span>Signature</span>
7369
</div>
7470
</div>
@@ -137,4 +133,4 @@ export function DetectionResults({ result }: DetectionResultsProps) {
137133
);
138134
}
139135

140-
export type { ProcessingResult, DetectedField };
136+
export type { ProcessingResult };

src/lib/drawDetections.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { type DetectedField, type FieldType } from "../workers/inference.worker";
2+
3+
export interface FieldColors {
4+
background: string;
5+
label: string;
6+
}
7+
8+
export const FIELD_COLORS: Record<FieldType, FieldColors> = {
9+
ChoiceButton: {
10+
background: "#a4dcf891",
11+
label: "#10B981",
12+
},
13+
Signature: {
14+
background: "#a4dcf891",
15+
label: "#F59E0B",
16+
},
17+
TextBox: {
18+
background: "#a4dcf891",
19+
label: "#3B82F6",
20+
},
21+
};
22+
23+
const LABEL_BAR_HEIGHT = 12.5;
24+
const LABEL_FONT = "10px Arial";
25+
const LABEL_TEXT_COLOR = "white";
26+
const LABEL_PADDING_X = 3;
27+
const LABEL_PADDING_Y = 3;
28+
29+
const drawWidgets = (canvas: HTMLCanvasElement, fields: DetectedField[]): void => {
30+
const context = canvas.getContext("2d");
31+
if (!context) {
32+
return;
33+
}
34+
35+
fields.forEach((field) => {
36+
const [normalizedX, normalizedY, normalizedWidth, normalizedHeight] = field.bbox;
37+
const absoluteX = normalizedX * canvas.width;
38+
const absoluteY = normalizedY * canvas.height;
39+
const absoluteWidth = normalizedWidth * canvas.width;
40+
const absoluteHeight = normalizedHeight * canvas.height;
41+
42+
const fieldColors = FIELD_COLORS[field.type];
43+
44+
context.fillStyle = fieldColors.background;
45+
context.fillRect(absoluteX, absoluteY, absoluteWidth, absoluteHeight);
46+
47+
context.fillStyle = fieldColors.label;
48+
context.fillRect(absoluteX, absoluteY - LABEL_BAR_HEIGHT, absoluteWidth, LABEL_BAR_HEIGHT);
49+
50+
context.fillStyle = LABEL_TEXT_COLOR;
51+
context.font = LABEL_FONT;
52+
const confidencePercentage = (field.confidence * 100).toFixed(0);
53+
context.fillText(
54+
`${field.type} (${confidencePercentage}%)`,
55+
absoluteX + LABEL_PADDING_X,
56+
absoluteY - LABEL_PADDING_Y
57+
);
58+
});
59+
};
60+
61+
interface PageDetectionDataInput {
62+
fields: DetectedField[];
63+
imageData: ImageData;
64+
pdfMetadata: {
65+
originalWidth: number;
66+
originalHeight: number;
67+
canvasSize: number;
68+
offsetX: number;
69+
offsetY: number;
70+
};
71+
}
72+
73+
interface PageDetectionDataOutput {
74+
fields: DetectedField[];
75+
imageData: string;
76+
pdfMetadata: {
77+
originalWidth: number;
78+
originalHeight: number;
79+
canvasSize: number;
80+
offsetX: number;
81+
offsetY: number;
82+
};
83+
}
84+
85+
interface DetectionDataInput {
86+
pages: PageDetectionDataInput[];
87+
processingTime: number;
88+
modelInfo: string;
89+
}
90+
91+
interface DetectionDataOutput {
92+
pages: PageDetectionDataOutput[];
93+
processingTime: number;
94+
modelInfo: string;
95+
}
96+
97+
export const drawDetections = (detectionData: DetectionDataInput): DetectionDataOutput => {
98+
const pagesWithDrawings = detectionData.pages.map((page) => {
99+
const canvas = document.createElement("canvas");
100+
canvas.width = page.imageData.width;
101+
canvas.height = page.imageData.height;
102+
const ctx = canvas.getContext("2d")!;
103+
ctx.putImageData(page.imageData, 0, 0);
104+
105+
drawWidgets(canvas, page.fields);
106+
107+
return {
108+
...page,
109+
imageData: canvas.toDataURL(),
110+
};
111+
});
112+
113+
return {
114+
...detectionData,
115+
pages: pagesWithDrawings,
116+
};
117+
};

src/lib/formFieldDetection.ts

Lines changed: 4 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
import * as pdfjsLib from "pdfjs-dist";
22
import InferenceWorker from "../workers/inference.worker.ts?worker";
3+
import { type DetectedField } from "../workers/inference.worker";
34

45
pdfjsLib.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${pdfjsLib.version}/build/pdf.worker.min.mjs`;
56

67
const TARGET_SIZE = 1216;
78

8-
interface DetectedField {
9-
type: string;
10-
bbox: [number, number, number, number];
11-
confidence: number;
12-
}
13-
149
interface PageDetectionData {
1510
fields: DetectedField[];
16-
imageData: string;
11+
imageData: ImageData;
1712
pdfMetadata: {
1813
originalWidth: number;
1914
originalHeight: number;
@@ -52,43 +47,6 @@ export type DetectionResult =
5247
| { success: true; data: DetectionData }
5348
| { success: false; error: { code: ErrorCode; message: string } };
5449

55-
const COLORS = {
56-
TextBox: {
57-
label: "#3B82F6",
58-
background: "#a4dcf891",
59-
},
60-
ChoiceButton: {
61-
label: "#10B981",
62-
background: "#a4dcf891",
63-
},
64-
Signature: {
65-
label: "#F59E0B",
66-
background: "#a4dcf891",
67-
},
68-
};
69-
70-
const drawDetections = (canvas: HTMLCanvasElement, fields: DetectedField[]): void => {
71-
const ctx = canvas.getContext("2d")!;
72-
73-
fields.forEach((field) => {
74-
const [x, y, w, h] = field.bbox;
75-
const absX = x * canvas.width;
76-
const absY = y * canvas.height;
77-
const absW = w * canvas.width;
78-
const absH = h * canvas.height;
79-
80-
const fieldColors = COLORS[field.type as keyof typeof COLORS];
81-
ctx.fillStyle = fieldColors.background;
82-
ctx.fillRect(absX, absY, absW, absH);
83-
84-
ctx.fillStyle = fieldColors.label;
85-
ctx.fillRect(absX, absY - 12.5, absW, 12.5);
86-
ctx.fillStyle = "white";
87-
ctx.font = "10px Arial";
88-
ctx.fillText(`${field.type} (${(field.confidence * 100).toFixed(0)}%)`, absX + 3, absY - 3);
89-
});
90-
};
91-
9250
const renderPdfPageToImageData = async (
9351
page: pdfjsLib.PDFPageProxy
9452
): Promise<{
@@ -205,17 +163,9 @@ export const detectFormFields = async (parameters: DetectionParameters): Promise
205163
});
206164
});
207165

208-
const canvas = document.createElement("canvas");
209-
canvas.width = TARGET_SIZE;
210-
canvas.height = TARGET_SIZE;
211-
const ctx = canvas.getContext("2d")!;
212-
ctx.putImageData(imageData, 0, 0);
213-
214-
drawDetections(canvas, inferenceResult.fields);
215-
216166
pages.push({
217167
fields: inferenceResult.fields,
218-
imageData: canvas.toDataURL(),
168+
imageData,
219169
pdfMetadata,
220170
});
221171
}
@@ -247,3 +197,4 @@ export const detectFormFields = async (parameters: DetectionParameters): Promise
247197
};
248198
}
249199
};
200+

src/workers/inference.worker.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@ import { applyNonMaximumSuppression } from "../lib/utils";
33

44
ort.env.wasm.wasmPaths = "https://cdn.jsdelivr.net/npm/[email protected]/dist/";
55

6-
const CLASS_NAMES = ["TextBox", "ChoiceButton", "Signature"];
6+
export type FieldType = "ChoiceButton" | "Signature" | "TextBox";
7+
8+
export interface DetectedField {
9+
type: FieldType;
10+
bbox: [number, number, number, number];
11+
confidence: number;
12+
}
13+
14+
const CLASS_NAMES: readonly FieldType[] = ["TextBox", "ChoiceButton", "Signature"];
715
const IOU_THRESHOLD = 0.45;
816
const TARGET_SIZE = 1216;
917

@@ -16,12 +24,6 @@ interface InferenceWorkerInput {
1624
isFirstPage: boolean;
1725
}
1826

19-
interface DetectedField {
20-
type: string;
21-
bbox: [number, number, number, number];
22-
confidence: number;
23-
}
24-
2527
interface InferenceWorkerOutput {
2628
success: true;
2729
fields: DetectedField[];

0 commit comments

Comments
 (0)