Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Satini_pvduc
/
ocrpdf
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Authored by
tien_nemo
2025-08-13 11:41:10 +0700
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
Commit
04184f3e3e1dd5c5f392d386b11f0fb08ddd687e
04184f3e
1 parent
80c7d4b4
test load data
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
299 additions
and
27 deletions
resources/views/ocr/index.blade.php
resources/views/ocr/index.blade.php
View file @
04184f3
...
...
@@ -27,8 +27,8 @@
.bbox.focus-highlight
{
animation
:
focusPulse
2s
ease-in-out
;
border-color
:
#ff6b35
!important
;
background-color
:
rgba
(
255
,
107
,
53
,
0.4
)
!important
;
/*border-color: #ff6b35 !important;*/
/*background-color: rgba(255, 107, 53, 0.4) !important;*/
}
@keyframes
focusPulse
{
...
...
@@ -137,6 +137,8 @@
>
</div>
<button
@
click=
"saveTemplate"
>
💾Save
</button>
{{--
<button
@
click=
"debugCoordinates"
style=
"margin-left: 10px; background: #6c757d;"
>
🐛Debug
</button>
--}}
{{--
<button
@
click=
"testManualBox"
style=
"margin-left: 10px; background: #28a745;"
>
🧪Test Box
</button>
--}}
</div>
</div>
...
...
@@ -184,7 +186,7 @@
mapFieldToBox
(
index
,
fieldName
,
text
=
null
)
{
if
(
index
==
null
)
return
;
// Xóa fieldName ở box khác
// Xóa fieldName ở box khác
đảm bảo mỗi field chỉ gán cho 1 box duy nhất
this
.
ocrData
.
forEach
((
box
,
i
)
=>
{
if
(
i
!==
index
&&
box
.
field
===
fieldName
)
{
box
.
field
=
null
;
...
...
@@ -192,7 +194,7 @@
}
});
// Nếu box này từng gán field khác thì bỏ
// Nếu box này từng gán field khác thì bỏ
reset flag và tọa độ liên quan.
const
prev
=
this
.
ocrData
[
index
].
field
;
if
(
prev
&&
prev
!==
fieldName
)
{
if
(
prev
===
'customer_name'
)
{
...
...
@@ -206,7 +208,6 @@
// Gán field mới
const
bbox
=
this
.
ocrData
[
index
].
bbox
;
// tọa độ OCR gốc [x1, y1, x2, y2]
console
.
log
(
'1111111111111111'
,
bbox
);
const
x1
=
bbox
[
0
];
const
y1
=
bbox
[
1
];
const
w
=
bbox
[
2
];
...
...
@@ -231,6 +232,7 @@
// Assign field và set active (dùng khi user tương tác)
assignFieldToBox
(
index
,
fieldName
,
text
=
null
)
{
console
.
log
(
`Assigning field "
${
fieldName
}
" to box at index
${
index
}
with text: "
${
text
}
"`
);
if
(
index
==
null
)
return
;
// Xóa fieldName ở box khác
...
...
@@ -255,7 +257,7 @@
// Gán field mới
const
bbox
=
this
.
ocrData
[
index
].
bbox
;
// tọa độ OCR gốc [x1, y1, x2, y2]
console
.
log
(
'
1111111111111111
'
,
bbox
);
console
.
log
(
'
2222222222222222
'
,
bbox
);
const
x1
=
bbox
[
0
];
const
y1
=
bbox
[
1
];
const
w
=
bbox
[
2
];
...
...
@@ -307,7 +309,7 @@
customer_name_xy
:
this
.
customer_name_xy
,
fields
:
fields
};
console
.
log
(
fields
);
//
console.log(fields);
try
{
const
res
=
await
fetch
(
'/ocr/save-template'
,
{
method
:
'POST'
,
...
...
@@ -360,11 +362,12 @@
async
loadOCRData
()
{
try
{
const
res
=
await
fetch
(
`/ocr/data-list`
);
const
data
=
await
res
.
json
();
if
(
data
.
error
)
{
console
.
error
(
data
.
error
);
console
.
error
(
'Error loading data:'
,
data
.
error
);
return
;
}
...
...
@@ -372,9 +375,31 @@
this
.
pdfImageUrl
=
data
.
pdfImageUrl
;
this
.
formData
=
data
.
formData
;
this
.
fieldOptions
=
data
.
fieldOptions
;
// Đợi image load xong trước khi xử lý
if
(
this
.
$refs
.
pdfImage
&&
this
.
$refs
.
pdfImage
.
complete
)
{
this
.
processLoadedData
();
}
else
{
console
.
log
(
'Image not loaded yet, waiting for onImageLoad'
);
// Image sẽ được xử lý trong onImageLoad
}
}
catch
(
error
)
{
console
.
error
(
'Error in loadOCRData:'
,
error
);
}
},
// Xử lý data sau khi image đã load
processLoadedData
()
{
// Tự động map field cho các box OCR dựa trên formData đã load
this
.
autoMapFieldsFromFormData
();
// Kiểm tra và sửa lại tọa độ của các box manual
// this.validateManualBoxes();
// Force re-render để đảm bảo các box được vẽ
this
.
$nextTick
(()
=>
{
this
.
$forceUpdate
();
});
},
// Tự động map field cho các box OCR dựa trên formData đã load từ DB
...
...
@@ -394,8 +419,10 @@
}
}
});
},
// Tìm box OCR phù hợp nhất để map với field
findBestMatchingBox
(
fieldName
,
fieldValue
)
{
let
bestMatchIndex
=
-
1
;
...
...
@@ -427,7 +454,6 @@
const
t1
=
text1
.
toLowerCase
().
trim
();
const
t2
=
text2
.
toLowerCase
().
trim
();
// Nếu text giống hệt nhau
if
(
t1
===
t2
)
return
1.0
;
...
...
@@ -448,9 +474,18 @@
const
img
=
this
.
$refs
.
pdfImage
;
this
.
imageWidth
=
img
.
naturalWidth
;
this
.
imageHeight
=
img
.
naturalHeight
;
// Nếu đã có data, xử lý ngay
if
(
this
.
ocrData
&&
this
.
ocrData
.
length
>
0
)
{
console
.
log
(
'Image loaded and data exists, processing now'
);
this
.
processLoadedData
();
}
else
{
console
.
log
(
'Image loaded but no data yet'
);
}
},
getBoxStyle
(
item
,
index
)
{
if
(
!
this
.
imageWidth
||
!
this
.
imageHeight
||
!
this
.
$refs
.
pdfImage
)
return
{};
if
(
!
this
.
imageWidth
||
!
this
.
imageHeight
||
!
this
.
$refs
.
pdfImage
)
{
return
{};
}
const
[
x1
,
y1
,
x2
,
y2
]
=
item
.
bbox
;
const
displayedWidth
=
this
.
$refs
.
pdfImage
.
clientWidth
;
...
...
@@ -459,14 +494,18 @@
const
scaleX
=
displayedWidth
/
this
.
imageWidth
;
const
scaleY
=
displayedHeight
/
this
.
imageHeight
;
const
left
=
Math
.
round
(
x1
*
scaleX
);
const
top
=
Math
.
round
(
y1
*
scaleY
);
const
width
=
Math
.
round
((
x2
-
x1
)
*
scaleX
);
const
height
=
Math
.
round
((
y2
-
y1
)
*
scaleY
);
return
{
position
:
'absolute'
,
left
:
`
${
Math
.
round
(
x1
*
scaleX
)
}
px`
,
top
:
`
${
Math
.
round
(
y1
*
scaleY
)
}
px`
,
width
:
`
${
Math
.
round
((
x2
-
x1
)
*
scaleX
)
}
px`
,
height
:
`
${
Math
.
round
((
y2
-
y1
)
*
scaleY
)
}
px`
,
left
:
`
${
left
}
px`
,
top
:
`
${
top
}
px`
,
width
:
`
${
width
}
px`
,
height
:
`
${
height
}
px`
,
border
:
item
.
hideBorder
?
'none'
:
'2px solid '
+
(
index
===
this
.
activeIndex
?
'#199601'
:
'#ff5252'
),
//backgroundColor: item.hideBorder ? 'transparent' : (this.activeIndex === item.field ? 'rgba(33,150,243,0.3)' : 'rgba(255,82,82,0.2)'),
boxSizing
:
'border-box'
,
cursor
:
'pointer'
,
zIndex
:
item
.
isManual
?
30
:
10
...
...
@@ -475,6 +514,7 @@
highlightField
(
field
)
{
let
idx
=
-
1
;
console
.
log
(
`Highlighting field:
${
field
}
`
);
for
(
let
i
=
this
.
ocrData
.
length
-
1
;
i
>=
0
;
i
--
)
{
const
it
=
this
.
ocrData
[
i
];
if
(
!
it
.
isDeleted
&&
it
.
field
===
field
)
{
...
...
@@ -482,6 +522,7 @@
break
;
}
}
console
.
log
(
'ssss'
,
idx
,
this
.
ocrData
[
idx
]);
if
(
idx
!==
-
1
)
{
// Set active index (chuyển trạng thái active và màu xanh)
...
...
@@ -500,10 +541,13 @@
if
(
!
this
.
$refs
.
pdfContainer
||
index
<
0
||
index
>=
this
.
ocrData
.
length
)
return
;
const
item
=
this
.
ocrData
[
index
];
console
.
log
(
`Scrolling to box at index
${
index
}
:`
,
item
);
if
(
!
item
||
item
.
isDeleted
)
return
;
// Tính vị trí hiển thị của box
const
[
x1
,
y1
,
x2
,
y2
]
=
item
.
bbox
;
// const [x1, y1, x2, y2] = item.bbox;
const
[
x1
,
y1
,
x2
,
y2
]
=
item
.
field_xy
.
split
(
','
).
map
(
Number
);
console
.
log
(
`Box coordinates for scrolling: [
${
x1
}
,
${
y1
}
,
${
x2
}
,
${
y2
}
]`
);
if
(
!
this
.
imageWidth
||
!
this
.
imageHeight
||
!
this
.
$refs
.
pdfImage
)
return
;
const
displayedWidth
=
this
.
$refs
.
pdfImage
.
clientWidth
;
...
...
@@ -601,7 +645,7 @@
const
dispX2
=
this
.
selectBox
.
x
+
this
.
selectBox
.
width
;
const
dispY2
=
this
.
selectBox
.
y
+
this
.
selectBox
.
height
;
// scale: displayed -> original
// scale: displayed -> original
(sửa lại để chính xác hơn)
const
displayedWidth
=
this
.
$refs
.
pdfImage
.
clientWidth
;
const
displayedHeight
=
this
.
$refs
.
pdfImage
.
clientHeight
;
const
scaleX
=
this
.
imageWidth
/
displayedWidth
;
...
...
@@ -661,39 +705,76 @@
applyManualMapping
()
{
if
(
!
this
.
manualField
)
return
;
const
manualIndex
=
this
.
manualIndex
;
const
newBbox
=
this
.
ocrData
[
manualIndex
].
bbox
;
console
.
log
(
`33333
${
manualIndex
}
with field "
${
this
.
manualField
}
" new box
${
newBbox
}
`
);
// console.log('Applying manual mapping for field:', this.manualField);
// console.log('Manual bbox:', newBbox);
let
combinedText
=
[];
let
foundItems
=
[];
// Tìm tất cả các box OCR nằm trong vùng manual
this
.
ocrData
.
forEach
(
item
=>
{
if
(
!
item
.
isManual
&&
this
.
isBoxInside
(
item
.
bbox
,
newBbox
)
&&
item
.
text
.
trim
())
{
const
partial
=
this
.
getPartialText
(
item
.
text
,
item
.
bbox
,
newBbox
);
if
(
partial
)
combinedText
.
push
(
partial
);
// combinedText.push(item.text.trim());
foundItems
.
push
({
text
:
item
.
text
,
bbox
:
item
.
bbox
,
index
:
this
.
ocrData
.
indexOf
(
item
)
});
}
});
// console.log('Found OCR items in manual area:', foundItems);
// Sắp xếp các item theo vị trí (từ trái sang phải, từ trên xuống dưới)
foundItems
.
sort
((
a
,
b
)
=>
{
// Ưu tiên theo Y trước (hàng), sau đó theo X (cột)
if
(
Math
.
abs
(
a
.
bbox
[
1
]
-
b
.
bbox
[
1
])
<
20
)
{
// Cùng hàng (tolerance 20px)
return
a
.
bbox
[
0
]
-
b
.
bbox
[
0
];
// Sắp xếp theo X
}
return
a
.
bbox
[
1
]
-
b
.
bbox
[
1
];
// Sắp xếp theo Y
});
// Gộp text theo thứ tự đã sắp xếp
foundItems
.
forEach
(
item
=>
{
combinedText
.
push
(
item
.
text
.
trim
());
});
const
finalText
=
combinedText
.
join
(
" "
);
// this.ocrData[manualIndex].field = this.manualField;
// this.formData[this.manualField] = finalText;
// this.activeIndex = manualIndex;
// console.log('Combined text:', finalText);
// Gán field và text cho box manual
this
.
assignFieldToBox
(
manualIndex
,
this
.
manualField
,
finalText
);
// Reset trạng thái chọn
this
.
isMappingManually
=
false
;
this
.
selectBox
.
show
=
false
;
this
.
selectBox
.
showDropdown
=
false
;
// this.manualField = "";
// this.manualIndex = null;
},
isBoxInside
(
inner
,
outer
)
{
return
!
(
// inner: bbox của OCR item [x1, y1, x2, y2]
// outer: bbox của vùng manual [x1, y1, x2, y2]
// Kiểm tra xem box OCR có nằm hoàn toàn trong vùng manual không
const
isFullyInside
=
(
inner
[
0
]
>=
outer
[
0
]
&&
// left edge
inner
[
1
]
>=
outer
[
1
]
&&
// top edge
inner
[
2
]
<=
outer
[
2
]
&&
// right edge
inner
[
3
]
<=
outer
[
3
]
// bottom edge
);
// Kiểm tra xem box OCR có giao nhau với vùng manual không
const
isOverlapping
=
!
(
inner
[
2
]
<
outer
[
0
]
||
// box bên trái vùng chọn
inner
[
0
]
>
outer
[
2
]
||
// box bên phải vùng chọn
inner
[
3
]
<
outer
[
1
]
||
// box phía trên vùng chọn
inner
[
1
]
>
outer
[
3
]
// box phía dưới vùng chọn
);
// Trả về true nếu box OCR nằm hoàn toàn trong hoặc giao nhau đáng kể
return
isFullyInside
||
isOverlapping
;
},
...
...
@@ -704,6 +785,7 @@
// Chiều rộng box OCR
const
boxWidth
=
x2
-
x1
;
const
boxHeight
=
y2
-
y1
;
// Vị trí start và end tương đối trong text
let
startRatio
=
Math
.
max
(
0
,
(
sx1
-
x1
)
/
boxWidth
);
...
...
@@ -712,7 +794,18 @@
const
startIndex
=
Math
.
floor
(
startRatio
*
text
.
length
);
const
endIndex
=
Math
.
ceil
(
endRatio
*
text
.
length
);
return
text
.
substring
(
startIndex
,
endIndex
).
trim
();
const
partialText
=
text
.
substring
(
startIndex
,
endIndex
).
trim
();
// console.log('Partial text calculation:', {
// originalText: text,
// bbox: bbox,
// selectBbox: selectBbox,
// startRatio, endRatio,
// startIndex, endIndex,
// partialText
// });
return
partialText
;
},
getSelectStyle
(
item
)
{
if
(
!
this
.
imageWidth
)
return
{
position
:
'absolute'
};
...
...
@@ -729,6 +822,185 @@
top
:
`
${
Math
.
round
(
y2
*
scaleY
)}
px`
,
zIndex
:
9999
};
},
// Debug method để kiểm tra tọa độ
debugCoordinates
()
{
console
.
log
(
'=== DEBUG COORDINATES ==='
);
console
.
log
(
'Image dimensions:'
,
{
natural
:
{
width
:
this
.
imageWidth
,
height
:
this
.
imageHeight
},
displayed
:
{
width
:
this
.
$refs
.
pdfImage
?.
clientWidth
,
height
:
this
.
$refs
.
pdfImage
?.
clientHeight
}
});
console
.
log
(
'Scale factors:'
,
{
scaleX
:
this
.
$refs
.
pdfImage
?
this
.
$refs
.
pdfImage
.
clientWidth
/
this
.
imageWidth
:
'N/A'
,
scaleY
:
this
.
$refs
.
pdfImage
?
this
.
$refs
.
pdfImage
.
clientHeight
/
this
.
imageHeight
:
'N/A'
});
console
.
log
(
'OCR Data with coordinates:'
);
this
.
ocrData
.
forEach
((
item
,
index
)
=>
{
if
(
!
item
.
isDeleted
)
{
console
.
log
(
`Item
${
index
}
:`
,
{
text
:
item
.
text
,
bbox
:
item
.
bbox
,
field
:
item
.
field
,
isManual
:
item
.
isManual
});
}
});
console
.
log
(
'=== END DEBUG ==='
);
},
// Kiểm tra và sửa lại tọa độ của các box manual
validateManualBoxes
()
{
if
(
!
this
.
imageWidth
||
!
this
.
imageHeight
)
{
console
.
log
(
'validateManualBoxes: Image not loaded yet'
);
return
;
}
this
.
ocrData
.
forEach
((
item
,
index
)
=>
{
if
(
item
.
isManual
&&
!
item
.
isDeleted
)
{
const
[
x1
,
y1
,
x2
,
y2
]
=
item
.
bbox
;
const
isValid
=
(
x1
>=
0
&&
y1
>=
0
&&
x2
>
x1
&&
y2
>
y1
&&
x2
<=
this
.
imageWidth
&&
y2
<=
this
.
imageHeight
);
if
(
!
isValid
)
{
// Thử sửa lại tọa độ nếu có thể
this
.
fixManualBoxCoordinates
(
item
,
index
);
}
}
});
console
.
log
(
'=== END VALIDATION ==='
);
// Force re-render để đảm bảo các box được vẽ đúng
this
.
$nextTick
(()
=>
{
this
.
$forceUpdate
();
});
},
// Sửa lại tọa độ của box manual nếu bị lỗi
fixManualBoxCoordinates
(
item
,
index
)
{
console
.
log
(
`Attempting to fix manual box
${
index
}
:`
,
item
);
// Nếu tọa độ âm, đặt về 0
let
[
x1
,
y1
,
x2
,
y2
]
=
item
.
bbox
;
if
(
x1
<
0
)
x1
=
0
;
if
(
y1
<
0
)
y1
=
0
;
if
(
x2
<=
x1
)
x2
=
x1
+
100
;
// Tạo width mặc định
if
(
y2
<=
y1
)
y2
=
y1
+
50
;
// Tạo height mặc định
// Đảm bảo không vượt quá image bounds
if
(
x2
>
this
.
imageWidth
)
x2
=
this
.
imageWidth
;
if
(
y2
>
this
.
imageHeight
)
y2
=
this
.
imageHeight
;
const
fixedBbox
=
[
x1
,
y1
,
x2
,
y2
];
console
.
log
(
`Fixed bbox for manual box
${
index
}
:`
,
{
original
:
item
.
bbox
,
fixed
:
fixedBbox
});
// Cập nhật tọa độ
this
.
$set
(
this
.
ocrData
[
index
],
'bbox'
,
fixedBbox
);
},
// Test method để tạo box manual test
testManualBox
()
{
if
(
!
this
.
imageWidth
||
!
this
.
imageHeight
)
{
alert
(
'Image chưa được load. Vui lòng đợi image load xong.'
);
return
;
}
// Tạo một box manual test ở giữa image
const
centerX
=
Math
.
round
(
this
.
imageWidth
/
2
);
const
centerY
=
Math
.
round
(
this
.
imageHeight
/
2
);
const
boxSize
=
100
;
const
testBbox
=
[
centerX
-
boxSize
/
2
,
// x1
centerY
-
boxSize
/
2
,
// y1
centerX
+
boxSize
/
2
,
// x2
centerY
+
boxSize
/
2
// y2
];
console
.
log
(
'Creating test manual box:'
,
{
bbox
:
testBbox
,
imageDimensions
:
{
width
:
this
.
imageWidth
,
height
:
this
.
imageHeight
}
});
// Thêm box test
this
.
ocrData
.
push
({
text
:
"TEST BOX"
,
bbox
:
testBbox
,
field
:
"test_field"
,
isManual
:
true
,
showDelete
:
true
,
isDeleted
:
false
,
hideBorder
:
false
});
// Force re-render
this
.
$forceUpdate
();
},
// Tạo box manual từ tọa độ trong DB
createManualBoxFromDB
(
fieldName
,
coordinates
,
text
)
{
if
(
!
this
.
imageWidth
||
!
this
.
imageHeight
)
{
console
.
log
(
'Cannot create manual box: Image not loaded'
);
return
;
}
// Parse coordinates từ string "x1,y1,x2,y2"
let
coords
;
if
(
typeof
coordinates
===
'string'
)
{
coords
=
coordinates
.
split
(
','
).
map
(
Number
);
}
else
if
(
Array
.
isArray
(
coordinates
))
{
coords
=
coordinates
;
}
else
{
console
.
error
(
'Invalid coordinates format:'
,
coordinates
);
return
;
}
const
[
x1
,
y1
,
x2
,
y2
]
=
coords
;
console
.
log
(
'Creating manual box from DB:'
,
{
fieldName
,
coordinates
,
parsed
:
coords
,
imageDimensions
:
{
width
:
this
.
imageWidth
,
height
:
this
.
imageHeight
}
});
// Kiểm tra tọa độ có hợp lệ không
if
(
x1
>=
0
&&
y1
>=
0
&&
x2
>
x1
&&
y2
>
y1
&&
x2
<=
this
.
imageWidth
&&
y2
<=
this
.
imageHeight
)
{
// Tạo box manual
const
manualBox
=
{
text
:
text
||
''
,
bbox
:
coords
,
field
:
fieldName
,
isManual
:
true
,
showDelete
:
true
,
isDeleted
:
false
,
hideBorder
:
false
};
this
.
ocrData
.
push
(
manualBox
);
console
.
log
(
'Manual box created successfully:'
,
manualBox
);
// Force re-render
this
.
$forceUpdate
();
}
else
{
console
.
warn
(
'Invalid coordinates for manual box:'
,
coords
);
}
}
}
...
...
Please
register
or
sign in
to post a comment