dzakybd commited on
Commit
05406fa
·
verified ·
1 Parent(s): 7bdba7a

floorplan-parser showcase: synthetic drawings and held-out results

Browse files
.gitattributes CHANGED
@@ -33,3 +33,10 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ data/synth-2-diagonal/raster-150.png filter=lfs diff=lfs merge=lfs -text
37
+ data/synth-2-diagonal/raster-300.png filter=lfs diff=lfs merge=lfs -text
38
+ data/synth-2-diagonal/raster-600.png filter=lfs diff=lfs merge=lfs -text
39
+ data/synth-2-diagonal/vector.png filter=lfs diff=lfs merge=lfs -text
40
+ data/synth-4-stair/raster-150.png filter=lfs diff=lfs merge=lfs -text
41
+ data/synth-4-stair/raster-300.png filter=lfs diff=lfs merge=lfs -text
42
+ data/synth-4-stair/raster-600.png filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,10 +1,20 @@
1
  ---
2
- title: Floorplan Parser
3
- emoji: 🔥
4
- colorFrom: yellow
5
- colorTo: blue
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: floorplan-parser
3
+ emoji: 📐
4
+ colorFrom: blue
5
+ colorTo: gray
6
  sdk: static
7
  pinned: false
8
+ license: mit
9
+ short_description: "Read lines from a drawing PDF: vector paths vs pixels"
10
  ---
11
 
12
+ # floorplan-parser showcase
13
+
14
+ Do you need computer vision to read construction drawings? floorplan-parser pulls line geometry out of a drawing PDF two ways: by reading the vector paths the PDF already stores (PyMuPDF), and by rendering the page to pixels and finding the lines with classic computer vision (OpenCV threshold and Hough lines). Both are scored against the drawing's own geometry.
15
+
16
+ The pictures are synthetic drawings made for this project; pick a drawing and a method to see what was found (blue), missed (red) and added (orange). The table also lists measured results on three held-out FloorPlanCAD drawings, as numbers only. Everything was computed ahead of time; the page runs no model and no server.
17
+
18
+ Code (MIT): [github.com/dzakybd/floorplan-parser](https://github.com/dzakybd/floorplan-parser). Write-up: [dzaky.ai.id](https://dzaky.ai.id/en/blog/floorplan-parser-vector-vs-raster).
19
+
20
+ FloorPlanCAD: Fan et al., "FloorPlanCAD: A Large-Scale CAD Drawing Dataset for Panoptic Symbol Spotting", ICCV 2021, https://floorplancad.github.io/ (annotations CC BY-NC 4.0). No FloorPlanCAD drawing is included in this Space.
data/results.json ADDED
@@ -0,0 +1,832 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "drawings": [
3
+ {
4
+ "id": "synth-1-walls",
5
+ "title": "Walls and door gaps",
6
+ "scores": {
7
+ "vector": {
8
+ "precision": 1.0,
9
+ "recall": 1.0,
10
+ "f1": 1.0,
11
+ "endpoint_err_mean": 0.0,
12
+ "endpoint_err_median": 0.0,
13
+ "endpoint_match_rate": 1.0,
14
+ "length_error_pct": 0.0,
15
+ "gt_length": 8376.0,
16
+ "pred_length": 8376.0,
17
+ "elements": 20,
18
+ "seconds": 0.0021
19
+ },
20
+ "raster-150": {
21
+ "precision": 1.0,
22
+ "recall": 0.9982,
23
+ "f1": 0.9991,
24
+ "endpoint_err_mean": 0.5,
25
+ "endpoint_err_median": 0.3213,
26
+ "endpoint_match_rate": 1.0,
27
+ "length_error_pct": 0.5814,
28
+ "gt_length": 8376.0,
29
+ "pred_length": 8327.306,
30
+ "elements": 25,
31
+ "seconds": 0.0323
32
+ },
33
+ "raster-300": {
34
+ "precision": 1.0,
35
+ "recall": 1.0,
36
+ "f1": 1.0,
37
+ "endpoint_err_mean": 0.3529,
38
+ "endpoint_err_median": 0.3272,
39
+ "endpoint_match_rate": 1.0,
40
+ "length_error_pct": 0.1171,
41
+ "gt_length": 8376.0,
42
+ "pred_length": 8366.192,
43
+ "elements": 20,
44
+ "seconds": 0.1233
45
+ },
46
+ "raster-600": {
47
+ "precision": 1.0,
48
+ "recall": 1.0,
49
+ "f1": 1.0,
50
+ "endpoint_err_mean": 0.2375,
51
+ "endpoint_err_median": 0.2131,
52
+ "endpoint_match_rate": 1.0,
53
+ "length_error_pct": 0.0811,
54
+ "gt_length": 8376.0,
55
+ "pred_length": 8382.797,
56
+ "elements": 21,
57
+ "seconds": 0.5643
58
+ }
59
+ }
60
+ },
61
+ {
62
+ "id": "synth-2-diagonal",
63
+ "title": "Diagonal walls",
64
+ "scores": {
65
+ "vector": {
66
+ "precision": 1.0,
67
+ "recall": 1.0,
68
+ "f1": 1.0,
69
+ "endpoint_err_mean": 0.0,
70
+ "endpoint_err_median": 0.0,
71
+ "endpoint_match_rate": 1.0,
72
+ "length_error_pct": 0.0,
73
+ "gt_length": 9919.9999,
74
+ "pred_length": 9919.9999,
75
+ "elements": 16,
76
+ "seconds": 0.0021
77
+ },
78
+ "raster-150": {
79
+ "precision": 1.0,
80
+ "recall": 1.0,
81
+ "f1": 1.0,
82
+ "endpoint_err_mean": 0.4088,
83
+ "endpoint_err_median": 0.3201,
84
+ "endpoint_match_rate": 1.0,
85
+ "length_error_pct": 0.1074,
86
+ "gt_length": 9919.9999,
87
+ "pred_length": 9909.3425,
88
+ "elements": 16,
89
+ "seconds": 0.0381
90
+ },
91
+ "raster-300": {
92
+ "precision": 1.0,
93
+ "recall": 1.0,
94
+ "f1": 1.0,
95
+ "endpoint_err_mean": 0.3026,
96
+ "endpoint_err_median": 0.3048,
97
+ "endpoint_match_rate": 1.0,
98
+ "length_error_pct": 0.0623,
99
+ "gt_length": 9919.9999,
100
+ "pred_length": 9913.8201,
101
+ "elements": 16,
102
+ "seconds": 0.1311
103
+ },
104
+ "raster-600": {
105
+ "precision": 1.0,
106
+ "recall": 1.0,
107
+ "f1": 1.0,
108
+ "endpoint_err_mean": 0.2016,
109
+ "endpoint_err_median": 0.1952,
110
+ "endpoint_match_rate": 1.0,
111
+ "length_error_pct": 0.0188,
112
+ "gt_length": 9919.9999,
113
+ "pred_length": 9918.13,
114
+ "elements": 16,
115
+ "seconds": 0.7226
116
+ }
117
+ }
118
+ },
119
+ {
120
+ "id": "synth-3-doors",
121
+ "title": "Door swings and small circles",
122
+ "scores": {
123
+ "vector": {
124
+ "precision": 1.0,
125
+ "recall": 1.0,
126
+ "f1": 1.0,
127
+ "endpoint_err_mean": 0.0,
128
+ "endpoint_err_median": 0.0,
129
+ "endpoint_match_rate": 1.0,
130
+ "length_error_pct": 0.0075,
131
+ "gt_length": 8492.2482,
132
+ "pred_length": 8492.8824,
133
+ "elements": 307,
134
+ "seconds": 0.0101
135
+ },
136
+ "raster-150": {
137
+ "precision": 0.9826,
138
+ "recall": 0.9622,
139
+ "f1": 0.9723,
140
+ "endpoint_err_mean": 0.8458,
141
+ "endpoint_err_median": 0.5115,
142
+ "endpoint_match_rate": 0.96,
143
+ "length_error_pct": 1.7599,
144
+ "gt_length": 8492.2482,
145
+ "pred_length": 8342.7971,
146
+ "elements": 119,
147
+ "seconds": 0.0416
148
+ },
149
+ "raster-300": {
150
+ "precision": 0.968,
151
+ "recall": 0.9801,
152
+ "f1": 0.974,
153
+ "endpoint_err_mean": 0.5712,
154
+ "endpoint_err_median": 0.3394,
155
+ "endpoint_match_rate": 1.0,
156
+ "length_error_pct": 4.7551,
157
+ "gt_length": 8492.2482,
158
+ "pred_length": 8896.0626,
159
+ "elements": 160,
160
+ "seconds": 0.1353
161
+ },
162
+ "raster-600": {
163
+ "precision": 0.9502,
164
+ "recall": 0.9776,
165
+ "f1": 0.9637,
166
+ "endpoint_err_mean": 0.5353,
167
+ "endpoint_err_median": 0.3328,
168
+ "endpoint_match_rate": 1.0,
169
+ "length_error_pct": 8.9647,
170
+ "gt_length": 8492.2482,
171
+ "pred_length": 9253.5532,
172
+ "elements": 175,
173
+ "seconds": 0.6254
174
+ }
175
+ }
176
+ },
177
+ {
178
+ "id": "synth-4-stair",
179
+ "title": "Stair treads and hatch",
180
+ "scores": {
181
+ "vector": {
182
+ "precision": 1.0,
183
+ "recall": 1.0,
184
+ "f1": 1.0,
185
+ "endpoint_err_mean": 0.0,
186
+ "endpoint_err_median": 0.0,
187
+ "endpoint_match_rate": 1.0,
188
+ "length_error_pct": 0.0,
189
+ "gt_length": 22766.3376,
190
+ "pred_length": 22766.3376,
191
+ "elements": 96,
192
+ "seconds": 0.0046
193
+ },
194
+ "raster-150": {
195
+ "precision": 1.0,
196
+ "recall": 1.0,
197
+ "f1": 1.0,
198
+ "endpoint_err_mean": 0.3724,
199
+ "endpoint_err_median": 0.3306,
200
+ "endpoint_match_rate": 1.0,
201
+ "length_error_pct": 0.2037,
202
+ "gt_length": 22766.3376,
203
+ "pred_length": 22719.9517,
204
+ "elements": 96,
205
+ "seconds": 0.0876
206
+ },
207
+ "raster-300": {
208
+ "precision": 1.0,
209
+ "recall": 1.0,
210
+ "f1": 1.0,
211
+ "endpoint_err_mean": 0.2884,
212
+ "endpoint_err_median": 0.2524,
213
+ "endpoint_match_rate": 1.0,
214
+ "length_error_pct": 0.0425,
215
+ "gt_length": 22766.3376,
216
+ "pred_length": 22756.6599,
217
+ "elements": 96,
218
+ "seconds": 0.2858
219
+ },
220
+ "raster-600": {
221
+ "precision": 1.0,
222
+ "recall": 1.0,
223
+ "f1": 1.0,
224
+ "endpoint_err_mean": 0.277,
225
+ "endpoint_err_median": 0.297,
226
+ "endpoint_match_rate": 1.0,
227
+ "length_error_pct": 0.1824,
228
+ "gt_length": 22766.3376,
229
+ "pred_length": 22807.8533,
230
+ "elements": 97,
231
+ "seconds": 1.2312
232
+ }
233
+ }
234
+ },
235
+ {
236
+ "id": "synth-5-near-parallel",
237
+ "title": "Near-parallel lines",
238
+ "scores": {
239
+ "vector": {
240
+ "precision": 1.0,
241
+ "recall": 1.0,
242
+ "f1": 1.0,
243
+ "endpoint_err_mean": 0.0,
244
+ "endpoint_err_median": 0.0,
245
+ "endpoint_match_rate": 1.0,
246
+ "length_error_pct": 0.0,
247
+ "gt_length": 14720.0,
248
+ "pred_length": 14720.0,
249
+ "elements": 24,
250
+ "seconds": 0.0021
251
+ },
252
+ "raster-150": {
253
+ "precision": 1.0,
254
+ "recall": 1.0,
255
+ "f1": 1.0,
256
+ "endpoint_err_mean": 0.4201,
257
+ "endpoint_err_median": 0.3156,
258
+ "endpoint_match_rate": 1.0,
259
+ "length_error_pct": 0.2125,
260
+ "gt_length": 14720.0,
261
+ "pred_length": 14751.2871,
262
+ "elements": 25,
263
+ "seconds": 0.048
264
+ },
265
+ "raster-300": {
266
+ "precision": 1.0,
267
+ "recall": 1.0,
268
+ "f1": 1.0,
269
+ "endpoint_err_mean": 0.3072,
270
+ "endpoint_err_median": 0.3246,
271
+ "endpoint_match_rate": 1.0,
272
+ "length_error_pct": 0.0743,
273
+ "gt_length": 14720.0,
274
+ "pred_length": 14709.0582,
275
+ "elements": 24,
276
+ "seconds": 0.1606
277
+ },
278
+ "raster-600": {
279
+ "precision": 1.0,
280
+ "recall": 1.0,
281
+ "f1": 1.0,
282
+ "endpoint_err_mean": 0.1889,
283
+ "endpoint_err_median": 0.1782,
284
+ "endpoint_match_rate": 1.0,
285
+ "length_error_pct": 0.0047,
286
+ "gt_length": 14720.0,
287
+ "pred_length": 14719.304,
288
+ "elements": 25,
289
+ "seconds": 0.7727
290
+ }
291
+ }
292
+ },
293
+ {
294
+ "id": "synth-text-paths",
295
+ "title": "Label drawn as lines",
296
+ "scores": {
297
+ "vector": {
298
+ "precision": 0.9625,
299
+ "recall": 1.0,
300
+ "f1": 0.9809,
301
+ "endpoint_err_mean": 0.0,
302
+ "endpoint_err_median": 0.0,
303
+ "endpoint_match_rate": 1.0,
304
+ "length_error_pct": 3.8971,
305
+ "gt_length": 7744.0,
306
+ "pred_length": 8045.789,
307
+ "elements": 37,
308
+ "seconds": 0.0023
309
+ },
310
+ "raster-150": {
311
+ "precision": 0.9722,
312
+ "recall": 1.0,
313
+ "f1": 0.9859,
314
+ "endpoint_err_mean": 0.4034,
315
+ "endpoint_err_median": 0.3213,
316
+ "endpoint_match_rate": 1.0,
317
+ "length_error_pct": 2.7573,
318
+ "gt_length": 7744.0,
319
+ "pred_length": 7957.5247,
320
+ "elements": 28,
321
+ "seconds": 0.0323
322
+ },
323
+ "raster-300": {
324
+ "precision": 0.9636,
325
+ "recall": 1.0,
326
+ "f1": 0.9815,
327
+ "endpoint_err_mean": 0.3698,
328
+ "endpoint_err_median": 0.3818,
329
+ "endpoint_match_rate": 1.0,
330
+ "length_error_pct": 3.7096,
331
+ "gt_length": 7744.0,
332
+ "pred_length": 8031.275,
333
+ "elements": 35,
334
+ "seconds": 0.1132
335
+ },
336
+ "raster-600": {
337
+ "precision": 0.966,
338
+ "recall": 1.0,
339
+ "f1": 0.9827,
340
+ "endpoint_err_mean": 0.2248,
341
+ "endpoint_err_median": 0.2317,
342
+ "endpoint_match_rate": 1.0,
343
+ "length_error_pct": 3.5056,
344
+ "gt_length": 7744.0,
345
+ "pred_length": 8015.4756,
346
+ "elements": 30,
347
+ "seconds": 0.5419
348
+ }
349
+ }
350
+ }
351
+ ],
352
+ "held_out": [
353
+ {
354
+ "drawing": "0674-0822",
355
+ "gt_lines": 114,
356
+ "vector": {
357
+ "precision": 1.0,
358
+ "recall": 1.0,
359
+ "f1": 1.0,
360
+ "endpoint_err_mean": 0.0,
361
+ "endpoint_err_median": 0.0,
362
+ "endpoint_match_rate": 1.0,
363
+ "length_error_pct": 0.0,
364
+ "gt_length": 19279.7168,
365
+ "pred_length": 19279.7165,
366
+ "elements": 114,
367
+ "seconds": 0.0051
368
+ },
369
+ "raster-150": {
370
+ "precision": 0.9997,
371
+ "recall": 0.9972,
372
+ "f1": 0.9985,
373
+ "endpoint_err_mean": 0.3278,
374
+ "endpoint_err_median": 0.2426,
375
+ "endpoint_match_rate": 0.9649,
376
+ "length_error_pct": 1.3194,
377
+ "gt_length": 19279.7168,
378
+ "pred_length": 19025.3308,
379
+ "elements": 110,
380
+ "seconds": 0.0568
381
+ },
382
+ "raster-300": {
383
+ "precision": 0.9999,
384
+ "recall": 0.9996,
385
+ "f1": 0.9998,
386
+ "endpoint_err_mean": 0.2776,
387
+ "endpoint_err_median": 0.235,
388
+ "endpoint_match_rate": 0.9386,
389
+ "length_error_pct": 0.5922,
390
+ "gt_length": 19279.7168,
391
+ "pred_length": 19165.5487,
392
+ "elements": 102,
393
+ "seconds": 0.2468
394
+ },
395
+ "raster-600": {
396
+ "precision": 0.9995,
397
+ "recall": 0.9996,
398
+ "f1": 0.9996,
399
+ "endpoint_err_mean": 0.3271,
400
+ "endpoint_err_median": 0.331,
401
+ "endpoint_match_rate": 0.9123,
402
+ "length_error_pct": 0.0006,
403
+ "gt_length": 19279.7168,
404
+ "pred_length": 19279.837,
405
+ "elements": 104,
406
+ "seconds": 1.2957
407
+ }
408
+ },
409
+ {
410
+ "drawing": "1010-0022",
411
+ "gt_lines": 229,
412
+ "vector": {
413
+ "precision": 1.0,
414
+ "recall": 1.0,
415
+ "f1": 1.0,
416
+ "endpoint_err_mean": 0.0,
417
+ "endpoint_err_median": 0.0,
418
+ "endpoint_match_rate": 1.0,
419
+ "length_error_pct": 0.0798,
420
+ "gt_length": 15796.2999,
421
+ "pred_length": 15783.6873,
422
+ "elements": 667,
423
+ "seconds": 0.0207
424
+ },
425
+ "raster-150": {
426
+ "precision": 0.8418,
427
+ "recall": 0.9168,
428
+ "f1": 0.8777,
429
+ "endpoint_err_mean": 0.677,
430
+ "endpoint_err_median": 0.5329,
431
+ "endpoint_match_rate": 0.9105,
432
+ "length_error_pct": 2.8209,
433
+ "gt_length": 15796.2999,
434
+ "pred_length": 15350.6952,
435
+ "elements": 1217,
436
+ "seconds": 0.1521
437
+ },
438
+ "raster-300": {
439
+ "precision": 0.8382,
440
+ "recall": 0.9494,
441
+ "f1": 0.8903,
442
+ "endpoint_err_mean": 0.6401,
443
+ "endpoint_err_median": 0.4498,
444
+ "endpoint_match_rate": 0.9192,
445
+ "length_error_pct": 11.3764,
446
+ "gt_length": 15796.2999,
447
+ "pred_length": 17593.3446,
448
+ "elements": 1371,
449
+ "seconds": 0.3888
450
+ },
451
+ "raster-600": {
452
+ "precision": 0.8174,
453
+ "recall": 0.9539,
454
+ "f1": 0.8804,
455
+ "endpoint_err_mean": 0.6173,
456
+ "endpoint_err_median": 0.4727,
457
+ "endpoint_match_rate": 0.9148,
458
+ "length_error_pct": 25.142,
459
+ "gt_length": 15796.2999,
460
+ "pred_length": 19767.8109,
461
+ "elements": 1675,
462
+ "seconds": 1.4362
463
+ }
464
+ },
465
+ {
466
+ "drawing": "0393-0004",
467
+ "gt_lines": 807,
468
+ "vector": {
469
+ "precision": 1.0,
470
+ "recall": 1.0,
471
+ "f1": 1.0,
472
+ "endpoint_err_mean": 0.0156,
473
+ "endpoint_err_median": 0.0,
474
+ "endpoint_match_rate": 0.9994,
475
+ "length_error_pct": 0.1407,
476
+ "gt_length": 28516.7645,
477
+ "pred_length": 28476.6349,
478
+ "elements": 7967,
479
+ "seconds": 0.3848
480
+ },
481
+ "raster-150": {
482
+ "precision": 0.9983,
483
+ "recall": 0.9544,
484
+ "f1": 0.9758,
485
+ "endpoint_err_mean": 1.3126,
486
+ "endpoint_err_median": 0.7283,
487
+ "endpoint_match_rate": 0.7807,
488
+ "length_error_pct": 9.0432,
489
+ "gt_length": 28516.7645,
490
+ "pred_length": 25937.9321,
491
+ "elements": 439,
492
+ "seconds": 0.1111
493
+ },
494
+ "raster-300": {
495
+ "precision": 0.9949,
496
+ "recall": 0.9734,
497
+ "f1": 0.984,
498
+ "endpoint_err_mean": 1.2637,
499
+ "endpoint_err_median": 0.6476,
500
+ "endpoint_match_rate": 0.8383,
501
+ "length_error_pct": 4.1195,
502
+ "gt_length": 28516.7645,
503
+ "pred_length": 27342.0243,
504
+ "elements": 515,
505
+ "seconds": 0.4006
506
+ },
507
+ "raster-600": {
508
+ "precision": 0.989,
509
+ "recall": 0.9805,
510
+ "f1": 0.9847,
511
+ "endpoint_err_mean": 1.1791,
512
+ "endpoint_err_median": 0.6151,
513
+ "endpoint_match_rate": 0.9182,
514
+ "length_error_pct": 0.8456,
515
+ "gt_length": 28516.7645,
516
+ "pred_length": 28275.6325,
517
+ "elements": 641,
518
+ "seconds": 1.9175
519
+ }
520
+ }
521
+ ],
522
+ "robustness": [
523
+ {
524
+ "drawing": "synth-1-walls",
525
+ "setting": "raster-300",
526
+ "precision": "1.0",
527
+ "recall": "1.0",
528
+ "f1": "1.0",
529
+ "endpoint_err_mean": "0.3529",
530
+ "endpoint_err_median": "0.3272",
531
+ "endpoint_match_rate": "1.0",
532
+ "length_error_pct": "0.1171",
533
+ "gt_length": "8376.0",
534
+ "pred_length": "8366.192"
535
+ },
536
+ {
537
+ "drawing": "synth-1-walls",
538
+ "setting": "raster-300-rot2",
539
+ "precision": "0.9731",
540
+ "recall": "1.0",
541
+ "f1": "0.9863",
542
+ "endpoint_err_mean": "0.2663",
543
+ "endpoint_err_median": "0.2695",
544
+ "endpoint_match_rate": "1.0",
545
+ "length_error_pct": "4.0589",
546
+ "gt_length": "8376.0",
547
+ "pred_length": "8715.9775"
548
+ },
549
+ {
550
+ "drawing": "synth-1-walls",
551
+ "setting": "raster-300-scan",
552
+ "precision": "1.0",
553
+ "recall": "1.0",
554
+ "f1": "1.0",
555
+ "endpoint_err_mean": "0.3486",
556
+ "endpoint_err_median": "0.2889",
557
+ "endpoint_match_rate": "1.0",
558
+ "length_error_pct": "0.5648",
559
+ "gt_length": "8376.0",
560
+ "pred_length": "8423.3088"
561
+ },
562
+ {
563
+ "drawing": "synth-2-diagonal",
564
+ "setting": "raster-300",
565
+ "precision": "1.0",
566
+ "recall": "1.0",
567
+ "f1": "1.0",
568
+ "endpoint_err_mean": "0.3026",
569
+ "endpoint_err_median": "0.3048",
570
+ "endpoint_match_rate": "1.0",
571
+ "length_error_pct": "0.0623",
572
+ "gt_length": "9919.9999",
573
+ "pred_length": "9913.8201"
574
+ },
575
+ {
576
+ "drawing": "synth-2-diagonal",
577
+ "setting": "raster-300-rot2",
578
+ "precision": "0.9799",
579
+ "recall": "1.0",
580
+ "f1": "0.9898",
581
+ "endpoint_err_mean": "0.2737",
582
+ "endpoint_err_median": "0.2904",
583
+ "endpoint_match_rate": "1.0",
584
+ "length_error_pct": "4.6714",
585
+ "gt_length": "9919.9999",
586
+ "pred_length": "10383.401"
587
+ },
588
+ {
589
+ "drawing": "synth-2-diagonal",
590
+ "setting": "raster-300-scan",
591
+ "precision": "1.0",
592
+ "recall": "1.0",
593
+ "f1": "1.0",
594
+ "endpoint_err_mean": "0.3419",
595
+ "endpoint_err_median": "0.3331",
596
+ "endpoint_match_rate": "1.0",
597
+ "length_error_pct": "0.0834",
598
+ "gt_length": "9919.9999",
599
+ "pred_length": "9911.7247"
600
+ },
601
+ {
602
+ "drawing": "synth-3-doors",
603
+ "setting": "raster-300",
604
+ "precision": "0.968",
605
+ "recall": "0.9801",
606
+ "f1": "0.974",
607
+ "endpoint_err_mean": "0.5712",
608
+ "endpoint_err_median": "0.3394",
609
+ "endpoint_match_rate": "1.0",
610
+ "length_error_pct": "4.7551",
611
+ "gt_length": "8492.2482",
612
+ "pred_length": "8896.0626"
613
+ },
614
+ {
615
+ "drawing": "synth-3-doors",
616
+ "setting": "raster-300-rot2",
617
+ "precision": "0.9518",
618
+ "recall": "0.9764",
619
+ "f1": "0.9639",
620
+ "endpoint_err_mean": "0.689",
621
+ "endpoint_err_median": "0.3458",
622
+ "endpoint_match_rate": "1.0",
623
+ "length_error_pct": "8.4203",
624
+ "gt_length": "8492.2482",
625
+ "pred_length": "9207.3237"
626
+ },
627
+ {
628
+ "drawing": "synth-3-doors",
629
+ "setting": "raster-300-scan",
630
+ "precision": "0.9676",
631
+ "recall": "0.9679",
632
+ "f1": "0.9677",
633
+ "endpoint_err_mean": "0.7411",
634
+ "endpoint_err_median": "0.4152",
635
+ "endpoint_match_rate": "0.96",
636
+ "length_error_pct": "3.0379",
637
+ "gt_length": "8492.2482",
638
+ "pred_length": "8750.2351"
639
+ },
640
+ {
641
+ "drawing": "synth-4-stair",
642
+ "setting": "raster-300",
643
+ "precision": "1.0",
644
+ "recall": "1.0",
645
+ "f1": "1.0",
646
+ "endpoint_err_mean": "0.2884",
647
+ "endpoint_err_median": "0.2524",
648
+ "endpoint_match_rate": "1.0",
649
+ "length_error_pct": "0.0425",
650
+ "gt_length": "22766.3376",
651
+ "pred_length": "22756.6599"
652
+ },
653
+ {
654
+ "drawing": "synth-4-stair",
655
+ "setting": "raster-300-rot2",
656
+ "precision": "0.9943",
657
+ "recall": "1.0",
658
+ "f1": "0.9971",
659
+ "endpoint_err_mean": "0.2916",
660
+ "endpoint_err_median": "0.2768",
661
+ "endpoint_match_rate": "1.0",
662
+ "length_error_pct": "0.9523",
663
+ "gt_length": "22766.3376",
664
+ "pred_length": "22983.1316"
665
+ },
666
+ {
667
+ "drawing": "synth-4-stair",
668
+ "setting": "raster-300-scan",
669
+ "precision": "1.0",
670
+ "recall": "1.0",
671
+ "f1": "1.0",
672
+ "endpoint_err_mean": "0.3083",
673
+ "endpoint_err_median": "0.2854",
674
+ "endpoint_match_rate": "1.0",
675
+ "length_error_pct": "0.0788",
676
+ "gt_length": "22766.3376",
677
+ "pred_length": "22748.3868"
678
+ },
679
+ {
680
+ "drawing": "synth-5-near-parallel",
681
+ "setting": "raster-300",
682
+ "precision": "1.0",
683
+ "recall": "1.0",
684
+ "f1": "1.0",
685
+ "endpoint_err_mean": "0.3072",
686
+ "endpoint_err_median": "0.3246",
687
+ "endpoint_match_rate": "1.0",
688
+ "length_error_pct": "0.0743",
689
+ "gt_length": "14720.0",
690
+ "pred_length": "14709.0582"
691
+ },
692
+ {
693
+ "drawing": "synth-5-near-parallel",
694
+ "setting": "raster-300-rot2",
695
+ "precision": "0.9522",
696
+ "recall": "1.0",
697
+ "f1": "0.9755",
698
+ "endpoint_err_mean": "0.31",
699
+ "endpoint_err_median": "0.3042",
700
+ "endpoint_match_rate": "1.0",
701
+ "length_error_pct": "7.8782",
702
+ "gt_length": "14720.0",
703
+ "pred_length": "15879.664"
704
+ },
705
+ {
706
+ "drawing": "synth-5-near-parallel",
707
+ "setting": "raster-300-scan",
708
+ "precision": "1.0",
709
+ "recall": "1.0",
710
+ "f1": "1.0",
711
+ "endpoint_err_mean": "0.3081",
712
+ "endpoint_err_median": "0.3374",
713
+ "endpoint_match_rate": "1.0",
714
+ "length_error_pct": "0.0792",
715
+ "gt_length": "14720.0",
716
+ "pred_length": "14708.3383"
717
+ }
718
+ ],
719
+ "text_paths": {
720
+ "vector": {
721
+ "vs_drawing_without_text": {
722
+ "precision": 0.9625,
723
+ "recall": 1.0,
724
+ "f1": 0.9809,
725
+ "endpoint_err_mean": 0.0,
726
+ "endpoint_err_median": 0.0,
727
+ "endpoint_match_rate": 1.0,
728
+ "length_error_pct": 3.8971,
729
+ "gt_length": 7744.0,
730
+ "pred_length": 8045.789
731
+ }
732
+ },
733
+ "raster-150": {
734
+ "vs_drawing_without_text": {
735
+ "precision": 0.9722,
736
+ "recall": 1.0,
737
+ "f1": 0.9859,
738
+ "endpoint_err_mean": 0.4034,
739
+ "endpoint_err_median": 0.3213,
740
+ "endpoint_match_rate": 1.0,
741
+ "length_error_pct": 2.7573,
742
+ "gt_length": 7744.0,
743
+ "pred_length": 7957.5247
744
+ }
745
+ },
746
+ "raster-300": {
747
+ "vs_drawing_without_text": {
748
+ "precision": 0.9636,
749
+ "recall": 1.0,
750
+ "f1": 0.9815,
751
+ "endpoint_err_mean": 0.3698,
752
+ "endpoint_err_median": 0.3818,
753
+ "endpoint_match_rate": 1.0,
754
+ "length_error_pct": 3.7096,
755
+ "gt_length": 7744.0,
756
+ "pred_length": 8031.275
757
+ }
758
+ },
759
+ "raster-600": {
760
+ "vs_drawing_without_text": {
761
+ "precision": 0.966,
762
+ "recall": 1.0,
763
+ "f1": 0.9827,
764
+ "endpoint_err_mean": 0.2248,
765
+ "endpoint_err_median": 0.2317,
766
+ "endpoint_match_rate": 1.0,
767
+ "length_error_pct": 3.5056,
768
+ "gt_length": 7744.0,
769
+ "pred_length": 8015.4756
770
+ }
771
+ },
772
+ "glyph_stroke_length_pt": 301.79
773
+ },
774
+ "config": {
775
+ "page_pt": 800.0,
776
+ "dpis": [
777
+ 150,
778
+ 300,
779
+ 600
780
+ ],
781
+ "held_out": [
782
+ "0674-0822",
783
+ "1010-0022",
784
+ "0393-0004"
785
+ ],
786
+ "synthetic_tuning": [
787
+ "synth-1-walls",
788
+ "synth-2-diagonal",
789
+ "synth-3-doors",
790
+ "synth-4-stair",
791
+ "synth-5-near-parallel"
792
+ ],
793
+ "text_fixture": "synth-text-paths",
794
+ "rotate_deg": 2.0,
795
+ "scan": {
796
+ "noise_sigma": 8.0,
797
+ "blur_px": 1,
798
+ "seed": 0
799
+ },
800
+ "scoring": {
801
+ "tessellation_tol_pt": 0.1,
802
+ "dist_tol_pt": 2.0,
803
+ "angle_tol_deg": 5.0,
804
+ "sample_step_pt": 1.0,
805
+ "endpoint_radius_pt": 5.0,
806
+ "short_segment_pt": 4.0
807
+ },
808
+ "raster_params": {
809
+ "threshold": 100,
810
+ "close_px": 0,
811
+ "hough_votes_pt": 3.0,
812
+ "min_line_pt": 1.5,
813
+ "max_gap_pt": 1.0,
814
+ "merge_gap_pt": 2.0
815
+ },
816
+ "timing": "median of 3 runs of the extract call (open + read/decode + detect + clean-up); PDF preparation is not timed",
817
+ "packages": {
818
+ "pymupdf": "1.28.2",
819
+ "opencv-python-headless": "5.0.0.93",
820
+ "shapely": "2.2.0",
821
+ "svgelements": "1.9.6",
822
+ "numpy": "2.5.4"
823
+ },
824
+ "python": "3.12.12",
825
+ "hardware": {
826
+ "platform": "macOS-26.7.1-arm64-arm-64bit",
827
+ "cpu": "Apple M3 Pro",
828
+ "cpu_count": 12,
829
+ "ram_gb": 36
830
+ }
831
+ }
832
+ }
data/synth-1-walls/raster-150.png ADDED
data/synth-1-walls/raster-300.png ADDED
data/synth-1-walls/raster-600.png ADDED
data/synth-1-walls/vector.png ADDED
data/synth-2-diagonal/raster-150.png ADDED

Git LFS Details

  • SHA256: 779264f7a4ed314d3981fdcd7597bdbca591b90b3b3535506f2acb4282e44b84
  • Pointer size: 131 Bytes
  • Size of remote file: 116 kB
data/synth-2-diagonal/raster-300.png ADDED

Git LFS Details

  • SHA256: 7a912cae8a92fe70292808a4a5dc4004b715cff580a4e9ecbdcc822c95a0bede
  • Pointer size: 131 Bytes
  • Size of remote file: 117 kB
data/synth-2-diagonal/raster-600.png ADDED

Git LFS Details

  • SHA256: 791256a66ae6e632fd2767c8d9d661d2266d7959d930f33f5bc44a2ef7fd2d53
  • Pointer size: 131 Bytes
  • Size of remote file: 116 kB
data/synth-2-diagonal/vector.png ADDED

Git LFS Details

  • SHA256: 2f87c6ffebf6e9d980240afb03a8d94b414c6a900856c721ab9cbe50a6616722
  • Pointer size: 131 Bytes
  • Size of remote file: 116 kB
data/synth-3-doors/raster-150.png ADDED
data/synth-3-doors/raster-300.png ADDED
data/synth-3-doors/raster-600.png ADDED
data/synth-3-doors/vector.png ADDED
data/synth-4-stair/raster-150.png ADDED

Git LFS Details

  • SHA256: 56504d5b0f4faabcb77e7661a31c12573700070bb1c85cd31ed1cb968e207ee5
  • Pointer size: 131 Bytes
  • Size of remote file: 190 kB
data/synth-4-stair/raster-300.png ADDED

Git LFS Details

  • SHA256: 60ea606f6266827be7b80f768c41b364645c18b2231d79ef69940835271438e7
  • Pointer size: 131 Bytes
  • Size of remote file: 143 kB
data/synth-4-stair/raster-600.png ADDED

Git LFS Details

  • SHA256: 3c48ddf2a2c6927e8c65d546fd5d8bcdfaf2f9c8d835739fd3749e5611335d88
  • Pointer size: 131 Bytes
  • Size of remote file: 175 kB
data/synth-4-stair/vector.png ADDED
data/synth-5-near-parallel/raster-150.png ADDED
data/synth-5-near-parallel/raster-300.png ADDED
data/synth-5-near-parallel/raster-600.png ADDED
data/synth-5-near-parallel/vector.png ADDED
data/synth-text-paths/raster-150.png ADDED
data/synth-text-paths/raster-300.png ADDED
data/synth-text-paths/raster-600.png ADDED
data/synth-text-paths/vector.png ADDED
index.html CHANGED
@@ -1,19 +1,125 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
  <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>floorplan-parser showcase</title>
7
+ <meta name="description" content="Line geometry from construction drawing PDFs, two ways: read the vector paths, or render to pixels and use classic computer vision. Measured against the drawing's own geometry.">
8
+ <style>
9
+ :root {
10
+ --bg: #f7f7f5; --panel: #ffffff; --fg: #1d1d1b; --muted: #6b6b66; --line: #e2e2dc;
11
+ --accent: #1f5f9e; --found: #1f77b4; --missed: #d62728; --extra: #ff7f0e;
12
+ }
13
+ @media (prefers-color-scheme: dark) {
14
+ :root:not([data-theme="light"]) { --bg: #141413; --panel: #1d1d1b; --fg: #ecece6; --muted: #a3a39b; --line: #33332f; --accent: #8bb8e8; }
15
+ }
16
+ :root[data-theme="dark"] { --bg: #141413; --panel: #1d1d1b; --fg: #ecece6; --muted: #a3a39b; --line: #33332f; --accent: #8bb8e8; }
17
+ * { box-sizing: border-box; }
18
+ body { margin: 0; background: var(--bg); color: var(--fg); font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
19
+ main { max-width: 1200px; margin: 0 auto; padding: 24px 16px 48px; }
20
+ h1 { font-size: 26px; margin: 0 0 4px; }
21
+ h2 { font-size: 17px; margin: 0 0 10px; }
22
+ p { margin: 0 0 8px; }
23
+ a { color: var(--accent); }
24
+ .muted { color: var(--muted); }
25
+ code, pre { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; }
26
+ pre { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; overflow-x: auto; margin: 8px 0 0; }
27
+ .panel { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 16px; min-width: 0; }
28
+ .row { display: grid; grid-template-columns: 3fr 2fr; gap: 16px; margin-top: 16px; }
29
+ @media (max-width: 860px) { .row { grid-template-columns: 1fr; } }
30
+ .pills { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
31
+ .pills button { border: 1px solid var(--line); background: var(--panel); color: var(--fg); border-radius: 999px; padding: 6px 14px; font: inherit; font-size: 14px; cursor: pointer; }
32
+ .pills button[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--bg); }
33
+ .label { font-size: 12px; color: var(--muted); margin-top: 16px; text-transform: uppercase; letter-spacing: .04em; }
34
+ .stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }
35
+ .stat { border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; }
36
+ .stat b { display: block; font-size: 22px; font-variant-numeric: tabular-nums; }
37
+ .stat span { font-size: 12px; color: var(--muted); }
38
+ #overlay { width: 100%; height: auto; display: block; background: #fff; border-radius: 6px; }
39
+ .legend { font-size: 12px; color: var(--muted); margin-top: 8px; display: flex; gap: 14px; flex-wrap: wrap; }
40
+ .legend i { display: inline-block; width: 18px; height: 3px; vertical-align: middle; margin-right: 4px; }
41
+ .scroll { overflow-x: auto; }
42
+ table { width: 100%; border-collapse: collapse; font-size: 13px; font-variant-numeric: tabular-nums; }
43
+ th, td { text-align: right; padding: 5px 8px; border-bottom: 1px solid var(--line); white-space: nowrap; }
44
+ th:first-child, td:first-child { text-align: left; }
45
+ th { color: var(--muted); font-weight: 600; }
46
+ main > section.panel + section.panel { margin-top: 16px; }
47
+ footer { margin-top: 28px; font-size: 13px; color: var(--muted); }
48
+ </style>
49
+ </head>
50
+ <body>
51
+ <main>
52
+ <h1>Do you need computer vision to read construction drawings?</h1>
53
+ <p class="muted">floorplan-parser pulls line geometry out of a drawing PDF two ways: it reads the vector paths the PDF already stores, or it renders the page to pixels and finds the lines with classic computer vision (threshold and Hough lines, no neural network). Both are scored against the drawing's own geometry. This recovers lines, not meaning: it does not know a wall from a door.</p>
54
+ <pre>uv run python -m floorplan_parser --input plan.pdf --method vector --output out/plan</pre>
55
+ <p class="muted" style="margin-top:8px">Code (MIT): <a href="https://github.com/dzakybd/floorplan-parser">github.com/dzakybd/floorplan-parser</a>. Write-up: <a href="https://dzaky.ai.id/en/blog/floorplan-parser-vector-vs-raster">dzaky.ai.id</a>. Results were computed ahead of time; this page runs no model.</p>
56
+
57
+ <div class="row">
58
+ <section class="panel">
59
+ <h2>Synthetic drawing</h2>
60
+ <div class="pills" id="drawings" role="group" aria-label="Drawing"></div>
61
+ <div class="pills" id="settings" role="group" aria-label="Method"></div>
62
+ <img id="overlay" alt="" style="margin-top:12px">
63
+ <div class="legend"><span><i style="background:var(--found)"></i>extracted</span><span><i style="background:var(--missed)"></i>missed</span><span><i style="background:var(--extra)"></i>extra (not in the drawing's geometry)</span></div>
64
+ </section>
65
+ <section class="panel">
66
+ <h2 id="scoreTitle">Score</h2>
67
+ <div class="stats" id="stats"></div>
68
+ <p class="muted" style="font-size:12px;margin-top:10px">Precision and recall are measured by length: how much of the extracted linework lies on the true lines (within 2 pt and 5°), and how much of the true linework was found. Time is the extraction call on an Apple M3 Pro, median of 3 runs.</p>
69
+ </section>
70
+ </div>
71
+
72
+ <section class="panel" style="margin-top:16px">
73
+ <h2>Held-out FloorPlanCAD drawings (numbers only)</h2>
74
+ <p class="muted" style="font-size:13px">Three real CAD drawings from the FloorPlanCAD test set, picked simple / medium / dense by file size. The raster settings were tuned on the synthetic drawings only, then frozen and run once on these. F1 by length, with the number of extracted pieces in brackets.</p>
75
+ <div class="scroll"><table id="heldout"></table></div>
76
+ </section>
77
+ <section class="panel">
78
+ <h2>Raster under a skewed or noisy scan (synthetic, 300 DPI)</h2>
79
+ <div class="scroll"><table id="robust"></table></div>
80
+ </section>
81
+
82
+ <footer>FloorPlanCAD: Fan et al., ICCV 2021, <a href="https://floorplancad.github.io/">floorplancad.github.io</a> (annotations CC BY-NC 4.0). No FloorPlanCAD drawing is shown or included here; every picture is a synthetic drawing made for this project.</footer>
83
+ </main>
84
+ <script>
85
+ const SETTINGS = { "vector": "Vector paths", "raster-150": "Pixels 150 DPI", "raster-300": "Pixels 300 DPI", "raster-600": "Pixels 600 DPI" };
86
+ const LABELS = { "0674-0822": "Simple: structural grid", "1010-0022": "Medium: doors, stair, double lines", "0393-0004": "Dense: apartment plan" };
87
+ let data, cur = { d: 0, s: "vector" };
88
+ const pct = v => (v * 100).toFixed(1) + "%";
89
+ function pills(el, items, isOn, onClick) {
90
+ el.innerHTML = "";
91
+ items.forEach(([key, text]) => {
92
+ const b = document.createElement("button");
93
+ b.textContent = text; b.setAttribute("aria-pressed", isOn(key)); b.onclick = () => onClick(key);
94
+ el.appendChild(b);
95
+ });
96
+ }
97
+ function render() {
98
+ const d = data.drawings[cur.d], sc = d.scores[cur.s];
99
+ pills(document.getElementById("drawings"), data.drawings.map((x, i) => [i, x.title]), i => i === cur.d, i => { cur.d = i; render(); });
100
+ pills(document.getElementById("settings"), Object.entries(SETTINGS), k => k === cur.s, k => { cur.s = k; render(); });
101
+ const img = document.getElementById("overlay");
102
+ img.src = `data/${d.id}/${cur.s}.png`;
103
+ img.alt = `${d.title}, ${SETTINGS[cur.s]}: extracted lines in blue, missed in red, extra in orange`;
104
+ document.getElementById("scoreTitle").textContent = `${d.title} · ${SETTINGS[cur.s]}`;
105
+ document.getElementById("stats").innerHTML = [
106
+ [pct(sc.recall), "recall (true lines found)"], [pct(sc.precision), "precision (found lines that are real)"],
107
+ [sc.elements, "pieces extracted"], [(sc.seconds * 1000).toFixed(0) + " ms", "time"],
108
+ ].map(([b, s]) => `<div class="stat"><b>${b}</b><span>${s}</span></div>`).join("");
109
+ }
110
+ function tables() {
111
+ const head = "<tr><th>Drawing</th><th>Lines</th>" + Object.values(SETTINGS).map(s => `<th>${s}</th>`).join("") + "</tr>";
112
+ document.getElementById("heldout").innerHTML = head + data.held_out.map(r =>
113
+ `<tr><td>${LABELS[r.drawing] || r.drawing}</td><td>${r.gt_lines}</td>` +
114
+ Object.keys(SETTINGS).map(s => `<td>${r[s].f1.toFixed(3)} <span class="muted">(${r[s].elements})</span></td>`).join("") + "</tr>").join("");
115
+ const titles = Object.fromEntries(data.drawings.map(x => [x.id, x.title]));
116
+ const names = { "raster-300": "clean", "raster-300-rot2": "rotated 2°", "raster-300-scan": "noise + blur" };
117
+ const by = {};
118
+ data.robustness.forEach(r => { (by[r.drawing] ||= {})[r.setting] = +r.f1; });
119
+ document.getElementById("robust").innerHTML = "<tr><th>Drawing</th>" + Object.values(names).map(n => `<th>F1, ${n}</th>`).join("") + "</tr>" +
120
+ Object.entries(by).map(([d, v]) => `<tr><td>${titles[d]}</td>` + Object.keys(names).map(k => `<td>${v[k].toFixed(3)}</td>`).join("") + "</tr>").join("");
121
+ }
122
+ fetch("data/results.json").then(r => r.json()).then(j => { data = j; render(); tables(); });
123
+ </script>
124
+ </body>
125
  </html>