tien_nemo

demo2

......@@ -22,6 +22,7 @@ class OcrController extends Controller
'customer_name_xy' => 'required|string',
]);
$dataDetail = $request->fields ?? [];
$tableColumns = $request->table_columns ?? [];
try {
$masterTemplate = MstTemplate::updateOrCreate(
['tpl_name' => $request->template_name],
......@@ -49,6 +50,24 @@ class OcrController extends Controller
);
}
// Lưu mapping cột bảng (lưu col index vào field_xy với field_name đặc biệt)
if (!empty($tableColumns) && is_array($tableColumns)) {
foreach ($tableColumns as $name => $colIdx) {
if ($colIdx === null || $colIdx === '' || $colIdx === false) {
continue;
}
DtTemplate::updateOrInsert(
[
'tpl_id' => $masterTemplate->id,
'field_name' => '__table_col__' . $name,
],
[
'field_xy' => (string) $colIdx,
]
);
}
}
return response()->json([
'success' => true,
'message' => 'Lưu template thành công',
......@@ -70,8 +89,8 @@ class OcrController extends Controller
$templateName = $request->get('template_name', '');
// Giả sử file OCR JSON & ảnh nằm trong storage/app/public/image/
$jsonPath = public_path("image/3_1757295841_with_table.json");
$imgPath = ("image/3_1757295841.jpg");
$jsonPath = public_path("image/nemo_new_1757393338_with_table.json");
$imgPath = ("image/nemo_new_1757393338.jpg");
if (!file_exists($jsonPath)) {
return response()->json(['error' => 'File OCR JSON không tìm thấy'], 404);
......@@ -100,7 +119,13 @@ class OcrController extends Controller
// Lấy detail của template
$details = DtTemplate::where('tpl_id', $mst->id)->get();
$tableColumnMapping = [];
foreach ($details as $detail) {
if (strpos($detail->field_name, '__table_col__') === 0) {
$name = substr($detail->field_name, strlen('__table_col__'));
$tableColumnMapping[$name] = is_numeric($detail->field_xy) ? intval($detail->field_xy) : null;
continue;
}
$coords = array_map('intval', explode(',', $detail->field_xy));
// coords = [x1, y1, x2, y2]
......@@ -130,6 +155,7 @@ class OcrController extends Controller
'pdfImageUrl' => $imgPath,
'dataMapping' => $dataMapping,
'is_template' => $is_template,
'tableColumnMapping' => $tableColumnMapping ?? [], //?? new \stdClass(),
'fieldOptions' => [
[ 'value' => 'template_name', 'label' => 'Tên Mẫu PDF' ],
[ 'value' => 'customer_name', 'label' => 'Tên khách hàng' ],
......
......@@ -11,14 +11,14 @@ from PIL import Image, ImageEnhance
# ==== Config ====
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
# PDF_NAME = 'aaaa'
PDF_NAME = 'nemo_new'
# PDF path
pdf_path = Path(BASE_DIR) / "storage" / "pdf" / "3.pdf"
pdf_path = Path(BASE_DIR) / "storage" / "pdf" / "2.pdf"
# Output folder
output_folder = Path(BASE_DIR) / "public" / "image"
PDF_NAME = pdf_path.stem # Get the stem of the PDF file
# PDF_NAME = pdf_path.stem # Get the stem of the PDF file
#print(PDF_NAME)
os.makedirs(output_folder, exist_ok=True)
......@@ -151,9 +151,31 @@ for table in table_info:
x1, y1, x2, y2 = cell["cell"]
cell_texts = []
# Helper: compute overlap ratio of bbox against cell
def overlap_ratio(bbox, cell_box):
ix1 = max(bbox[0], cell_box[0])
iy1 = max(bbox[1], cell_box[1])
ix2 = min(bbox[2], cell_box[2])
iy2 = min(bbox[3], cell_box[3])
iw = max(0, ix2 - ix1)
ih = max(0, iy2 - iy1)
inter = iw * ih
bbox_area = max(1, (bbox[2] - bbox[0]) * (bbox[3] - bbox[1]))
return inter / float(bbox_area)
# Helper: check center inside cell
def center_inside(bbox, cell_box):
cx = (bbox[0] + bbox[2]) / 2.0
cy = (bbox[1] + bbox[3]) / 2.0
return (cx >= cell_box[0] and cx <= cell_box[2] and
cy >= cell_box[1] and cy <= cell_box[3])
cell_box = [x1, y1, x2, y2]
for item in ocr_data_list:
bx1, by1, bx2, by2 = item["bbox"]
if bx1 >= x1 and by1 >= y1 and bx2 <= x2 and by2 <= y2:
bbox = [bx1, by1, bx2, by2]
# Accept if bbox is largely inside the cell, or its center lies inside the cell
if overlap_ratio(bbox, cell_box) >= 0.3 or center_inside(bbox, cell_box):
cell_texts.append(item["text"])
# thêm vào cell gốc
......
......@@ -2,46 +2,73 @@ import cv2
import numpy as np
import os
def detect_tables(image_path):
def filter_horizontal_lines(lines_h, img_width, min_h_len_ratio=0.8, tol_y=10):
if lines_h is None:
return [], []
ys_candidates = []
for l in lines_h:
x1, y1, x2, y2 = l[0]
if abs(y1 - y2) <= 3: # ngang
line_len = abs(x2 - x1)
y_mid = int(round((y1 + y2) / 2))
ys_candidates.append((y_mid, line_len, x1, x2))
ys_candidates.sort(key=lambda x: x[0])
filtered_lines, line_segments, current_group = [], [], []
for y, length, x1, x2 in ys_candidates:
if not current_group:
current_group.append((y, length, x1, x2))
else:
if abs(y - current_group[-1][0]) <= tol_y:
current_group.append((y, length, x1, x2))
else:
longest = max(current_group, key=lambda x: x[1])
if longest[1] >= min_h_len_ratio * img_width:
filtered_lines.append(longest[0])
line_segments.append((longest[2], longest[3], longest[0]))
else:
break
current_group = [(y, length, x1, x2)]
if current_group:
longest = max(current_group, key=lambda x: x[1])
if longest[1] >= min_h_len_ratio * img_width:
filtered_lines.append(longest[0])
line_segments.append((longest[2], longest[3], longest[0]))
total_rows = max(0, len(filtered_lines) - 1)
print(f"Detected {total_rows} rows")
return filtered_lines, line_segments
def detect_tables(image_path, gap_threshold=50):
img = cv2.imread(image_path)
if img is None:
raise FileNotFoundError(f"Không đọc được ảnh: {image_path}")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3, 3), 0)
# Edge detection
edges = cv2.Canny(blur, 50, 150, apertureSize=3)
# --- Horizontal lines ---
lines_h = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=120,
minLineLength=int(img.shape[1] * 0.6), maxLineGap=20)
ys_candidates, line_segments = [], []
if lines_h is not None:
for l in lines_h:
x1, y1, x2, y2 = l[0]
if abs(y1 - y2) <= 3: # ngang
y_mid = int(round((y1 + y2) / 2))
ys_candidates.append(y_mid)
line_segments.append((x1, x2, y_mid))
# gom nhóm y
ys, tol_y = [], 10
for y in sorted(ys_candidates):
if not ys or abs(y - ys[-1]) > tol_y:
ys.append(y)
img_height, img_width = img.shape[:2]
ys, line_segments = filter_horizontal_lines(lines_h, img_width, min_h_len_ratio=0.8, tol_y=10)
total_rows = max(0, len(ys) - 1)
# --- Vertical lines ---
lines_v = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=100,
minLineLength=int(img.shape[0] * 0.5), maxLineGap=20)
xs = []
minLineLength=int(img.shape[0] * 0.4), maxLineGap=20)
v_lines, xs = [], []
if lines_v is not None:
for l in lines_v:
x1, y1, x2, y2 = l[0]
if abs(x1 - x2) <= 3:
xs.append(int(round((x1 + x2) / 2)))
v_lines.append((int(round((x1 + x2) / 2)), min(y1, y2), max(y1, y2)))
# gom nhóm x
x_pos, tol_v = [], 10
......@@ -50,26 +77,66 @@ def detect_tables(image_path):
x_pos.append(v)
total_cols = max(0, len(x_pos) - 1)
tables = []
if total_rows > 0 and total_cols > 0:
y_min, y_max = ys[0], ys[-1]
x_min, x_max = x_pos[0], x_pos[-1]
table_box = (x_min, y_min, x_max, y_max)
# build cells
rows_data = []
for i in range(total_rows):
row_cells = []
for j in range(total_cols):
j = 0
while j < total_cols:
cell_box = (x_pos[j], ys[i], x_pos[j+1], ys[i+1])
row_height = cell_box[3] - cell_box[1]
# Check vertical line coverage (>=70% chiều cao hàng)
has_left = any(
abs(x - cell_box[0]) <= tol_v and
(min(y_end, cell_box[3]) - max(y_start, cell_box[1])) >= 0.7 * row_height
for x, y_start, y_end in v_lines
)
has_right = any(
abs(x - cell_box[2]) <= tol_v and
(min(y_end, cell_box[3]) - max(y_start, cell_box[1])) >= 0.7 * row_height
for x, y_start, y_end in v_lines
)
if has_left and has_right:
col_start = j
col_end = j
# nếu cột tiếp theo không có line → merge
while col_end + 1 < total_cols:
next_box = (x_pos[col_end+1], ys[i], x_pos[col_end+2], ys[i+1])
has_next_left = any(
abs(x - next_box[0]) <= tol_v and
(min(y_end, next_box[3]) - max(y_start, next_box[1])) >= 0.7 * row_height
for x, y_start, y_end in v_lines
)
if not has_next_left: # merge tiếp
col_end += 1
else:
break
merged_box = (x_pos[col_start], ys[i], x_pos[col_end+1], ys[i+1])
if col_start == col_end:
col_id = col_start
else:
col_id = f"{col_start}-{col_end}"
row_cells.append({
"cell": cell_box,
"cell": merged_box,
"row_idx": i,
"col_idx": j
"col_idx": col_id
})
# Vẽ ô
cv2.rectangle(img, (cell_box[0], cell_box[1]), (cell_box[2], cell_box[3]), (0, 255, 255), 1)
cv2.rectangle(img, (merged_box[0], merged_box[1]),
(merged_box[2], merged_box[3]), (0, 255, 255), 1)
j = col_end + 1
else:
j += 1 # skip ô lỗi (không có line đầy đủ)
rows_data.append(row_cells)
tables.append({
......@@ -78,11 +145,9 @@ def detect_tables(image_path):
"table_box": table_box,
"cells": rows_data
})
# vẽ viền bảng
cv2.rectangle(img, (x_min, y_min), (x_max, y_max), (255, 0, 0), 2)
debug_path = os.path.splitext(image_path)[0] + "_debug.jpg"
debug_path = os.path.splitext(image_path)[0] + "_fix_debug.jpg"
cv2.imwrite(debug_path, img)
return tables
......