jdeskew 0.2.2__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jdeskew-0.2.2 → jdeskew-0.3.0}/.circleci/config.yml +1 -1
- {jdeskew-0.2.2 → jdeskew-0.3.0}/.github/workflows/python-package.yml +1 -1
- {jdeskew-0.2.2 → jdeskew-0.3.0}/PKG-INFO +20 -5
- {jdeskew-0.2.2 → jdeskew-0.3.0}/README.md +6 -2
- jdeskew-0.3.0/icip_paper.pdf +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/jdeskew/estimator.py +30 -26
- {jdeskew-0.2.2 → jdeskew-0.3.0}/jdeskew/utility.py +5 -3
- {jdeskew-0.2.2 → jdeskew-0.3.0}/jdeskew.egg-info/PKG-INFO +20 -5
- {jdeskew-0.2.2 → jdeskew-0.3.0}/jdeskew.egg-info/SOURCES.txt +1 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/tests/test.py +0 -2
- {jdeskew-0.2.2 → jdeskew-0.3.0}/.dockerignore +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/.flake8 +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/.github/workflows/codeql-analysis.yml +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/.github/workflows/dependency-review.yml +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/.github/workflows/docker-build-and-push.yml +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/.github/workflows/greetings.yml +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/.github/workflows/label.yml +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/.github/workflows/python-publish.yml +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/.github/workflows/stale.yml +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/.gitignore +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/.pre-commit-config.yaml +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/Dockerfile +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/LICENSE +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/cog.yaml +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/estimator.py +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/jdeskew/__init__.py +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/jdeskew.egg-info/dependency_links.txt +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/jdeskew.egg-info/requires.txt +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/jdeskew.egg-info/top_level.txt +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/main.py +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/pyproject.toml +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/reproduce.ipynb +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/setup.cfg +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/setup.py +0 -0
- {jdeskew-0.2.2 → jdeskew-0.3.0}/tests/test.png +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: jdeskew
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Document Image Skew Estimation using Adaptive Radial Projection
|
|
5
5
|
Author-email: Luan Pham <phamquiluan@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -26,13 +26,22 @@ License: MIT License
|
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
|
-
Provides-Extra: dev
|
|
30
29
|
License-File: LICENSE
|
|
30
|
+
Requires-Dist: numpy
|
|
31
|
+
Requires-Dist: opencv-python-headless
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: black; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest; extra == "dev"
|
|
35
|
+
Requires-Dist: coverage; extra == "dev"
|
|
36
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-xdist; extra == "dev"
|
|
38
|
+
Requires-Dist: fastapi; extra == "dev"
|
|
39
|
+
Requires-Dist: uvicorn[standard]; extra == "dev"
|
|
40
|
+
Requires-Dist: python-multipart; extra == "dev"
|
|
41
|
+
Dynamic: license-file
|
|
31
42
|
|
|
32
43
|
# Document Image Skew Estimation
|
|
33
44
|
|
|
34
|
-
*To my love, Jenna*
|
|
35
|
-
|
|
36
45
|
[](https://pypi.org/project/jdeskew)
|
|
37
46
|
[](https://dl.circleci.com/status-badge/redirect/gh/phamquiluan/jdeskew/tree/master)
|
|
38
47
|
[](https://www.codacy.com/gh/phamquiluan/jdeskew/dashboard?utm_source=github.com&utm_medium=referral&utm_content=phamquiluan/jdeskew&utm_campaign=Badge_Coverage)
|
|
@@ -153,6 +162,8 @@ This datasets are built upon three other datasets: DISEC 2013, RVL-CDIP, RDCL 20
|
|
|
153
162
|
| DISE 2021 (45 degree) | https://drive.google.com/file/d/1a-a6aOqdsghjeHGLnCLsDs7NoJIus-Pw/view?usp=sharing |
|
|
154
163
|
| DISE 2021 (15 degree) | https://drive.google.com/file/d/1BLiuu-j28dbuPFi4n3C0KuV6vXGmB0qS/view?usp=sharing |
|
|
155
164
|
|
|
165
|
+
Can also download from Zenodo: https://zenodo.org/records/12570649
|
|
166
|
+
|
|
156
167
|
## Reproducibility and Evaluation Code
|
|
157
168
|
|
|
158
169
|
Check the [reproduce.ipynb](reproduce.ipynb) file
|
|
@@ -170,3 +181,7 @@ L. Pham, H. Hoang, X.T. Mai, T. A. Tran, "Adaptive Radial Projection on Fourier
|
|
|
170
181
|
organization={IEEE}
|
|
171
182
|
}
|
|
172
183
|
```
|
|
184
|
+
|
|
185
|
+
## Star History
|
|
186
|
+
|
|
187
|
+
[](https://star-history.com/#phamquiluan/jdeskew&Date)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# Document Image Skew Estimation
|
|
2
2
|
|
|
3
|
-
*To my love, Jenna*
|
|
4
|
-
|
|
5
3
|
[](https://pypi.org/project/jdeskew)
|
|
6
4
|
[](https://dl.circleci.com/status-badge/redirect/gh/phamquiluan/jdeskew/tree/master)
|
|
7
5
|
[](https://www.codacy.com/gh/phamquiluan/jdeskew/dashboard?utm_source=github.com&utm_medium=referral&utm_content=phamquiluan/jdeskew&utm_campaign=Badge_Coverage)
|
|
@@ -122,6 +120,8 @@ This datasets are built upon three other datasets: DISEC 2013, RVL-CDIP, RDCL 20
|
|
|
122
120
|
| DISE 2021 (45 degree) | https://drive.google.com/file/d/1a-a6aOqdsghjeHGLnCLsDs7NoJIus-Pw/view?usp=sharing |
|
|
123
121
|
| DISE 2021 (15 degree) | https://drive.google.com/file/d/1BLiuu-j28dbuPFi4n3C0KuV6vXGmB0qS/view?usp=sharing |
|
|
124
122
|
|
|
123
|
+
Can also download from Zenodo: https://zenodo.org/records/12570649
|
|
124
|
+
|
|
125
125
|
## Reproducibility and Evaluation Code
|
|
126
126
|
|
|
127
127
|
Check the [reproduce.ipynb](reproduce.ipynb) file
|
|
@@ -139,3 +139,7 @@ L. Pham, H. Hoang, X.T. Mai, T. A. Tran, "Adaptive Radial Projection on Fourier
|
|
|
139
139
|
organization={IEEE}
|
|
140
140
|
}
|
|
141
141
|
```
|
|
142
|
+
|
|
143
|
+
## Star History
|
|
144
|
+
|
|
145
|
+
[](https://star-history.com/#phamquiluan/jdeskew&Date)
|
|
Binary file
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Skew Estimator."""
|
|
2
2
|
import cv2
|
|
3
3
|
import numpy as np
|
|
4
|
+
from typing import Optional
|
|
4
5
|
|
|
5
6
|
|
|
6
|
-
def _ensure_gray(image):
|
|
7
|
+
def _ensure_gray(image: np.ndarray) -> np.ndarray:
|
|
7
8
|
try:
|
|
8
9
|
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
9
10
|
except cv2.error:
|
|
@@ -11,7 +12,7 @@ def _ensure_gray(image):
|
|
|
11
12
|
return image
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
def _ensure_optimal_square(image):
|
|
15
|
+
def _ensure_optimal_square(image: np.ndarray) -> np.ndarray:
|
|
15
16
|
assert image is not None, image
|
|
16
17
|
nw = nh = cv2.getOptimalDFTSize(max(image.shape[:2]))
|
|
17
18
|
output_image = cv2.copyMakeBorder(
|
|
@@ -26,7 +27,7 @@ def _ensure_optimal_square(image):
|
|
|
26
27
|
return output_image
|
|
27
28
|
|
|
28
29
|
|
|
29
|
-
def _get_fft_magnitude(image):
|
|
30
|
+
def _get_fft_magnitude(image: np.ndarray) -> np.ndarray:
|
|
30
31
|
gray = _ensure_gray(image)
|
|
31
32
|
opt_gray = _ensure_optimal_square(gray)
|
|
32
33
|
|
|
@@ -35,7 +36,7 @@ def _get_fft_magnitude(image):
|
|
|
35
36
|
~opt_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, -10
|
|
36
37
|
)
|
|
37
38
|
|
|
38
|
-
# perform fft
|
|
39
|
+
# perform fft - using fft2 to ensure square output
|
|
39
40
|
dft = np.fft.fft2(opt_gray)
|
|
40
41
|
shifted_dft = np.fft.fftshift(dft)
|
|
41
42
|
|
|
@@ -44,7 +45,7 @@ def _get_fft_magnitude(image):
|
|
|
44
45
|
return magnitude
|
|
45
46
|
|
|
46
47
|
|
|
47
|
-
def _get_angle_radial_projection(m, angle_max=None, num=
|
|
48
|
+
def _get_angle_radial_projection(m: np.ndarray, angle_max: Optional[float] = None, num: Optional[int] = None) -> float:
|
|
48
49
|
"""Get angle via radial projection.
|
|
49
50
|
|
|
50
51
|
Arguments:
|
|
@@ -57,42 +58,45 @@ def _get_angle_radial_projection(m, angle_max=None, num=None, W=None):
|
|
|
57
58
|
r = c = m.shape[0] // 2
|
|
58
59
|
|
|
59
60
|
if angle_max is None:
|
|
60
|
-
|
|
61
|
+
angle_max = 15.0
|
|
61
62
|
|
|
62
63
|
if num is None:
|
|
63
64
|
num = 20
|
|
64
65
|
|
|
65
66
|
tr = np.linspace(-1 * angle_max, angle_max, int(angle_max * num * 2)) / 180 * np.pi
|
|
66
|
-
profile_arr = tr.copy()
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
lambda x: m[c + int(x * np.cos(t)), c + int(-1 * x * np.sin(t))]
|
|
71
|
-
)
|
|
72
|
-
_l = _f(range(0, r))
|
|
73
|
-
val_init = np.sum(_l)
|
|
74
|
-
return val_init
|
|
68
|
+
# Pre-allocate array for better performance
|
|
69
|
+
li = np.zeros_like(tr)
|
|
75
70
|
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
for i, t in enumerate(tr):
|
|
72
|
+
x = np.arange(0, r)
|
|
73
|
+
y = c + np.int32(x * np.cos(t))
|
|
74
|
+
x = c + np.int32(-1 * x * np.sin(t))
|
|
75
|
+
# Use boolean indexing for faster computation
|
|
76
|
+
valid_indices = (y >= 0) & (y < m.shape[0]) & (x >= 0) & (x < m.shape[1])
|
|
77
|
+
li[i] = np.sum(m[y[valid_indices], x[valid_indices]])
|
|
78
78
|
|
|
79
79
|
a = tr[np.argmax(li)] / np.pi * 180
|
|
80
80
|
|
|
81
81
|
if a == -1 * angle_max:
|
|
82
|
-
return 0
|
|
83
|
-
return a
|
|
82
|
+
return 0.0
|
|
83
|
+
return float(a)
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
def get_angle(
|
|
87
|
-
image: np.ndarray,
|
|
88
|
-
|
|
87
|
+
image: np.ndarray,
|
|
88
|
+
vertical_image_shape: Optional[int] = None,
|
|
89
|
+
angle_max: Optional[float] = None
|
|
90
|
+
) -> float:
|
|
89
91
|
"""Getting angle from a given document image.
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
Args:
|
|
94
|
+
image: Input image as numpy array
|
|
95
|
+
vertical_image_shape: Optional resize height for preprocessing
|
|
96
|
+
angle_max: Maximum angle to search for
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
float: Estimated skew angle in degrees
|
|
96
100
|
"""
|
|
97
101
|
assert isinstance(image, np.ndarray), image
|
|
98
102
|
|
|
@@ -100,7 +104,7 @@ def get_angle(
|
|
|
100
104
|
# vertical_image_shape = 512
|
|
101
105
|
|
|
102
106
|
if angle_max is None:
|
|
103
|
-
angle_max = 15
|
|
107
|
+
angle_max = 15.0
|
|
104
108
|
|
|
105
109
|
# resize
|
|
106
110
|
if vertical_image_shape is not None:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import cv2
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
def rotate(image, angle, resize=True, border_mode=None, border_value=None):
|
|
5
|
+
def rotate(image, angle, resize=True, border_mode=None, border_value=None, flags=None):
|
|
6
6
|
"""Rotate input image respect to a given angle.
|
|
7
7
|
|
|
8
8
|
Params:
|
|
@@ -13,6 +13,9 @@ def rotate(image, angle, resize=True, border_mode=None, border_value=None):
|
|
|
13
13
|
"""
|
|
14
14
|
if border_mode is None:
|
|
15
15
|
border_mode = cv2.BORDER_CONSTANT
|
|
16
|
+
if flags is None:
|
|
17
|
+
# flags=cv2.INTER_NEAREST
|
|
18
|
+
flags = cv2.INTER_LINEAR
|
|
16
19
|
|
|
17
20
|
h, w = image.shape[:2]
|
|
18
21
|
M = cv2.getRotationMatrix2D(center=(w // 2, h // 2), angle=angle, scale=1.0)
|
|
@@ -20,8 +23,7 @@ def rotate(image, angle, resize=True, border_mode=None, border_value=None):
|
|
|
20
23
|
src=image,
|
|
21
24
|
M=M,
|
|
22
25
|
dsize=(w, h),
|
|
23
|
-
|
|
24
|
-
flags=cv2.INTER_NEAREST,
|
|
26
|
+
flags=flags,
|
|
25
27
|
borderMode=border_mode,
|
|
26
28
|
borderValue=None if border_mode == cv2.BORDER_REPLICATE else border_value,
|
|
27
29
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: jdeskew
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Document Image Skew Estimation using Adaptive Radial Projection
|
|
5
5
|
Author-email: Luan Pham <phamquiluan@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -26,13 +26,22 @@ License: MIT License
|
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
|
-
Provides-Extra: dev
|
|
30
29
|
License-File: LICENSE
|
|
30
|
+
Requires-Dist: numpy
|
|
31
|
+
Requires-Dist: opencv-python-headless
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: black; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest; extra == "dev"
|
|
35
|
+
Requires-Dist: coverage; extra == "dev"
|
|
36
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-xdist; extra == "dev"
|
|
38
|
+
Requires-Dist: fastapi; extra == "dev"
|
|
39
|
+
Requires-Dist: uvicorn[standard]; extra == "dev"
|
|
40
|
+
Requires-Dist: python-multipart; extra == "dev"
|
|
41
|
+
Dynamic: license-file
|
|
31
42
|
|
|
32
43
|
# Document Image Skew Estimation
|
|
33
44
|
|
|
34
|
-
*To my love, Jenna*
|
|
35
|
-
|
|
36
45
|
[](https://pypi.org/project/jdeskew)
|
|
37
46
|
[](https://dl.circleci.com/status-badge/redirect/gh/phamquiluan/jdeskew/tree/master)
|
|
38
47
|
[](https://www.codacy.com/gh/phamquiluan/jdeskew/dashboard?utm_source=github.com&utm_medium=referral&utm_content=phamquiluan/jdeskew&utm_campaign=Badge_Coverage)
|
|
@@ -153,6 +162,8 @@ This datasets are built upon three other datasets: DISEC 2013, RVL-CDIP, RDCL 20
|
|
|
153
162
|
| DISE 2021 (45 degree) | https://drive.google.com/file/d/1a-a6aOqdsghjeHGLnCLsDs7NoJIus-Pw/view?usp=sharing |
|
|
154
163
|
| DISE 2021 (15 degree) | https://drive.google.com/file/d/1BLiuu-j28dbuPFi4n3C0KuV6vXGmB0qS/view?usp=sharing |
|
|
155
164
|
|
|
165
|
+
Can also download from Zenodo: https://zenodo.org/records/12570649
|
|
166
|
+
|
|
156
167
|
## Reproducibility and Evaluation Code
|
|
157
168
|
|
|
158
169
|
Check the [reproduce.ipynb](reproduce.ipynb) file
|
|
@@ -170,3 +181,7 @@ L. Pham, H. Hoang, X.T. Mai, T. A. Tran, "Adaptive Radial Projection on Fourier
|
|
|
170
181
|
organization={IEEE}
|
|
171
182
|
}
|
|
172
183
|
```
|
|
184
|
+
|
|
185
|
+
## Star History
|
|
186
|
+
|
|
187
|
+
[](https://star-history.com/#phamquiluan/jdeskew&Date)
|
|
@@ -19,7 +19,6 @@ def test_basic():
|
|
|
19
19
|
def test_text_image_angle_range_10(angle: int):
|
|
20
20
|
"""Test_text_image_angle_range_10."""
|
|
21
21
|
image = cv2.imread(path.join(path.dirname(__file__), "test.png"))
|
|
22
|
-
# for angle in range(-10, 10):
|
|
23
22
|
skew_image = rotate(image, angle=angle, resize=False)
|
|
24
23
|
estimated_angle = get_angle(skew_image, vertical_image_shape=512)
|
|
25
24
|
assert abs(angle + estimated_angle) < 0.5, f"{angle} - {estimated_angle}"
|
|
@@ -29,7 +28,6 @@ def test_text_image_angle_range_10(angle: int):
|
|
|
29
28
|
def test_text_image_angle_range_44(angle: int):
|
|
30
29
|
"""Test_text_image_angle_range_44."""
|
|
31
30
|
image = cv2.imread(path.join(path.dirname(__file__), "test.png"))
|
|
32
|
-
# for angle in range(-44, 44):
|
|
33
31
|
skew_image = rotate(image, angle=angle, resize=False)
|
|
34
32
|
estimated_angle = get_angle(skew_image, vertical_image_shape=512, angle_max=44.9)
|
|
35
33
|
assert abs(angle + estimated_angle) < 0.5, f"{angle} - {estimated_angle}"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|