Compare commits
2 Commits
master
...
table_line
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99359596da | ||
|
|
ef02253ad7 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -50,3 +50,5 @@ __pycache__/
|
||||
|
||||
# unignore files
|
||||
!bom.*
|
||||
|
||||
dotted/
|
||||
|
||||
@ -8,7 +8,7 @@ repos:
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
# - id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
args: [--unsafe] # needed for .gitlab-ci.yml
|
||||
- id: check-toml
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
(pkgs.buildFHSUserEnv rec {
|
||||
name = "cv-analysis-service";
|
||||
targetPkgs = pkgs: (with pkgs; [
|
||||
python310
|
||||
poppler_utils
|
||||
zlib
|
||||
poetry
|
||||
|
||||
2461
poetry.lock
generated
2461
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -31,6 +31,7 @@ pdf2img = { version = "0.7.0", source = "gitlab-red" }
|
||||
dvc-azure = "^2.21.2"
|
||||
pymupdf = "^1.24.1"
|
||||
types-pillow = "^10.2.0.20240423"
|
||||
#matplotlib-backend-wezterm = "^2.1.2"
|
||||
|
||||
[tool.poetry.group.test.dependencies]
|
||||
pytest = "^7.0.1"
|
||||
|
||||
8
scripts/grid_search.py
Normal file
8
scripts/grid_search.py
Normal file
@ -0,0 +1,8 @@
|
||||
from cv_analysis.table_inference import infer_lines
|
||||
|
||||
|
||||
def grid_search() -> None: ...
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
grid_search()
|
||||
@ -1,6 +1,6 @@
|
||||
from operator import itemgetter
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional, Tuple
|
||||
from typing import Callable, Iterable, Optional, Tuple
|
||||
|
||||
import cv2
|
||||
import matplotlib.pyplot as plt
|
||||
@ -9,6 +9,8 @@ from kn_utils.logging import logger # type: ignore
|
||||
from numpy import ndarray as Array
|
||||
from scipy.stats import norm # type: ignore
|
||||
|
||||
from .utils.dotted_lines import detect_dotted_from_extrema
|
||||
|
||||
|
||||
def show_multiple(arrs: Tuple[Array], title: str = ""):
|
||||
plt.clf()
|
||||
@ -150,16 +152,65 @@ def filter_fp_col_lines(line_list: list[int], filt_sums: Array) -> list[int]:
|
||||
return line_list
|
||||
|
||||
|
||||
def sharpen_sums(sums: Array) -> Array:
|
||||
sums = sums.astype("int64")
|
||||
shift = 3
|
||||
diffs = abs(sums[shift:-shift] - sums[2 * shift :]) + abs(sums[shift:-shift] - sums[: -2 * shift])
|
||||
f2 = filter_array(sums, FILTERS["col"][2])
|
||||
return diffs
|
||||
|
||||
|
||||
def detect_dotted_lines(
|
||||
image: Array,
|
||||
sums: Iterable,
|
||||
horizontal: bool = True,
|
||||
threshold: float = 1.0,
|
||||
min_distance: int = 2,
|
||||
max_distance: int = 20,
|
||||
) -> bool:
|
||||
key = "row" if horizontal else "col"
|
||||
naive = filter_array(sums, FILTERS[key][1])
|
||||
naive_lines = np.where((naive[1:-1] < naive[:-2]) * (naive[1:-1] < naive[2:]) * (sums[1:-1] < 250))[0] + 1
|
||||
|
||||
bool_array = np.zeros(image.shape[1 - int(horizontal)])
|
||||
for idx in naive_lines:
|
||||
band = image[idx - 1 : idx + 2, :] if horizontal else image[:, idx - 1 : idx + 1]
|
||||
band_sums = np.mean(band, axis=1 - int(horizontal))
|
||||
band_sums = filter_array(band_sums, FILTERS[key][1])
|
||||
extrema = np.where((band_sums[1:-1] < band_sums[:-2]) * (band_sums[1:-1] < band_sums[2:]))[0] + 1
|
||||
|
||||
distances = extrema[1:] - extrema[:-1]
|
||||
mean = np.mean(distances)
|
||||
std = np.std(distances)
|
||||
|
||||
check = "✔" if (ratio := (mean / (std + 0.01))) > 1.5 and mean < 40 else ""
|
||||
print(f"{idx:4} {mean:6.2f} {std:6.2f} {ratio:6.2f} {check}")
|
||||
|
||||
score = std # maybe make more advanced score function later
|
||||
if (min_distance <= mean <= max_distance) and (score < threshold):
|
||||
print(idx)
|
||||
bool_array[idx] = 1
|
||||
return bool_array
|
||||
|
||||
|
||||
def get_lines_either(table_array: Array, horizontal=True) -> list[int]:
|
||||
key = "row" if horizontal else "col"
|
||||
h, w = map(int, table_array.shape)
|
||||
|
||||
table_array = (
|
||||
table_array[:, int(0.1 * w) : int(0.9 * w)] if horizontal else table_array[int(0.1 * h) : int(0.9 * h)]
|
||||
)
|
||||
|
||||
sums = np.mean(table_array, axis=int(horizontal))
|
||||
dotted = detect_dotted_lines(table_array, sums, horizontal=horizontal)
|
||||
|
||||
threshold = 0.3 * 255 # np.mean(sums) - (1 + 2 * horizontal) * np.std(sums)
|
||||
predicate = 1000.0 * (sums < threshold)
|
||||
predicate = 1000.0 * ((sums < threshold) | dotted)
|
||||
sums = np.maximum(
|
||||
np.maximum(sums[1:-1], predicate[1:-1]),
|
||||
np.maximum(predicate[:-2], predicate[2:]),
|
||||
)
|
||||
|
||||
filtered_sums = filter_array(sums, FILTERS[key][1])
|
||||
filtered_sums = filter_array(filtered_sums, FILTERS[key][2])
|
||||
filtered_sums = filter_array(filtered_sums, FILTERS[key][3])
|
||||
@ -179,9 +230,7 @@ def img_bytes_to_array(img_bytes: bytes) -> Array:
|
||||
|
||||
|
||||
def infer_lines(img: Array) -> dict[str, dict[str, int] | list[dict[str, int]]]:
|
||||
cv2.imwrite("/tmp/table.png", img)
|
||||
_, img = cv2.threshold(img, 220, 255, cv2.THRESH_BINARY)
|
||||
cv2.imwrite("/tmp/table_bin.png", img)
|
||||
h, w = map(int, img.shape)
|
||||
row_vals = map(int, get_lines_either(img, horizontal=True))
|
||||
col_vals = map(int, get_lines_either(img, horizontal=False))
|
||||
|
||||
18
src/cv_analysis/utils/dotted_lines.py
Normal file
18
src/cv_analysis/utils/dotted_lines.py
Normal file
@ -0,0 +1,18 @@
|
||||
"""
|
||||
General approach:
|
||||
Get horizontal and vertical pixel sum extrema. Then take a band of k around each minimum (corresponding to darkest), e.g. k=3.
|
||||
Recalculate minima for each band.
|
||||
Compute a list of distances between minima.
|
||||
Compute the mean and standard deviation between minima.
|
||||
If rho:=std/(eta*mean) < phi for some threshold phi, the band contains a dotted line. -> logic: std can be larger for larger mean, i.e. more spaced-out dotted lines
|
||||
|
||||
Pros:
|
||||
Intuitive and efficient.
|
||||
|
||||
Cons:
|
||||
May not work for irregular/mixed dotted lines, such as (possibly) --*--*--*--*--*--*--*--*--*--*--
|
||||
"""
|
||||
|
||||
from typing import Iterable
|
||||
|
||||
import numpy as np
|
||||
Loading…
x
Reference in New Issue
Block a user