jdeskew 0.2.3__tar.gz → 0.4.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.4.0/.codacy.yaml +3 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/.github/workflows/python-package.yml +1 -1
- {jdeskew-0.2.3 → jdeskew-0.4.0}/Dockerfile +2 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/PKG-INFO +29 -11
- {jdeskew-0.2.3 → jdeskew-0.4.0}/README.md +13 -9
- {jdeskew-0.2.3 → jdeskew-0.4.0}/estimator.py +1 -0
- jdeskew-0.4.0/icip_paper.pdf +0 -0
- jdeskew-0.4.0/jdeskew/estimator.py +137 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/jdeskew/utility.py +22 -6
- {jdeskew-0.2.3 → jdeskew-0.4.0}/jdeskew.egg-info/PKG-INFO +29 -11
- {jdeskew-0.2.3 → jdeskew-0.4.0}/jdeskew.egg-info/SOURCES.txt +4 -0
- jdeskew-0.4.0/jdeskew.egg-info/scm_file_list.json +33 -0
- jdeskew-0.4.0/jdeskew.egg-info/scm_version.json +8 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/main.py +1 -1
- {jdeskew-0.2.3 → jdeskew-0.4.0}/pyproject.toml +17 -1
- jdeskew-0.2.3/jdeskew/estimator.py +0 -112
- {jdeskew-0.2.3 → jdeskew-0.4.0}/.circleci/config.yml +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/.dockerignore +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/.flake8 +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/.github/workflows/codeql-analysis.yml +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/.github/workflows/dependency-review.yml +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/.github/workflows/docker-build-and-push.yml +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/.github/workflows/greetings.yml +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/.github/workflows/label.yml +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/.github/workflows/python-publish.yml +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/.github/workflows/stale.yml +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/.gitignore +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/.pre-commit-config.yaml +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/LICENSE +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/cog.yaml +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/jdeskew/__init__.py +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/jdeskew.egg-info/dependency_links.txt +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/jdeskew.egg-info/requires.txt +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/jdeskew.egg-info/top_level.txt +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/reproduce.ipynb +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/setup.cfg +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/setup.py +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/tests/test.png +0 -0
- {jdeskew-0.2.3 → jdeskew-0.4.0}/tests/test.py +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
FROM ubuntu:20.04
|
|
2
2
|
ARG DEBIAN_FRONTEND=noninteractive
|
|
3
3
|
|
|
4
|
+
# hadolint ignore=DL3008
|
|
4
5
|
RUN apt-get update && apt-get install --no-install-recommends -y python3.8 python3.8-dev python3-pip ffmpeg\
|
|
5
6
|
&& apt-get clean \
|
|
6
7
|
&& rm -rf /var/lib/apt/lists/*
|
|
@@ -11,6 +12,7 @@ RUN pip install pip==22.0.3
|
|
|
11
12
|
WORKDIR /app
|
|
12
13
|
COPY . /app/
|
|
13
14
|
|
|
15
|
+
# hadolint ignore=DL3013
|
|
14
16
|
RUN pip install .[dev]
|
|
15
17
|
EXPOSE 80
|
|
16
18
|
ENTRYPOINT ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: jdeskew
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -25,6 +25,19 @@ License: MIT License
|
|
|
25
25
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
|
|
28
|
+
Classifier: Intended Audience :: Developers
|
|
29
|
+
Classifier: Intended Audience :: Science/Research
|
|
30
|
+
Classifier: Operating System :: OS Independent
|
|
31
|
+
Classifier: Programming Language :: Python :: 3
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
39
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
40
|
+
Requires-Python: >=3.8
|
|
28
41
|
Description-Content-Type: text/markdown
|
|
29
42
|
License-File: LICENSE
|
|
30
43
|
Requires-Dist: numpy
|
|
@@ -38,6 +51,7 @@ Requires-Dist: pytest-xdist; extra == "dev"
|
|
|
38
51
|
Requires-Dist: fastapi; extra == "dev"
|
|
39
52
|
Requires-Dist: uvicorn[standard]; extra == "dev"
|
|
40
53
|
Requires-Dist: python-multipart; extra == "dev"
|
|
54
|
+
Dynamic: license-file
|
|
41
55
|
|
|
42
56
|
# Document Image Skew Estimation
|
|
43
57
|
|
|
@@ -46,11 +60,9 @@ Requires-Dist: python-multipart; extra == "dev"
|
|
|
46
60
|
[](https://www.codacy.com/gh/phamquiluan/jdeskew/dashboard?utm_source=github.com&utm_medium=referral&utm_content=phamquiluan/jdeskew&utm_campaign=Badge_Coverage)
|
|
47
61
|
[](https://www.codacy.com/gh/phamquiluan/jdeskew/dashboard?utm_source=github.com&utm_medium=referral&utm_content=phamquiluan/jdeskew&utm_campaign=Badge_Grade)
|
|
48
62
|
[](https://pepy.tech/project/jdeskew)
|
|
49
|
-

|
|
50
63
|

|
|
51
|
-
](https://paperswithcode.com/sota/document-image-skew-estimation-on-dise-2021?p=adaptive-radial-projection-on-fourier)
|
|
64
|
+
[](https://huggingface.co/papers/2603.05942)
|
|
65
|
+
[](https://huggingface.co/datasets/phamquiluan/DISE-2021)
|
|
54
66
|
|
|
55
67
|
|
|
56
68
|

|
|
@@ -81,6 +93,12 @@ Requires-Dist: python-multipart; extra == "dev"
|
|
|
81
93
|
pip install jdeskew
|
|
82
94
|
```
|
|
83
95
|
|
|
96
|
+
### conda
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
conda create --name jdeskew -c conda-forge jdeskew
|
|
100
|
+
```
|
|
101
|
+
|
|
84
102
|
## How-to-use
|
|
85
103
|
|
|
86
104
|
### using python
|
|
@@ -131,6 +149,8 @@ Link1: https://ieeexplore.ieee.org/document/9897910
|
|
|
131
149
|
|
|
132
150
|
Link2: https://www.researchgate.net/publication/364320913_ADAPTIVE_RADIAL_PROJECTION_ON_FOURIER_MAGNITUDE_SPECTRUM_FOR_DOCUMENT_IMAGE_SKEW_ESTIMATION
|
|
133
151
|
|
|
152
|
+
Link3: https://huggingface.co/papers/2603.05942
|
|
153
|
+
|
|
134
154
|
|
|
135
155
|
## Performance Comparison on DISE 2021
|
|
136
156
|
|
|
@@ -161,7 +181,9 @@ This datasets are built upon three other datasets: DISEC 2013, RVL-CDIP, RDCL 20
|
|
|
161
181
|
| DISE 2021 (45 degree) | https://drive.google.com/file/d/1a-a6aOqdsghjeHGLnCLsDs7NoJIus-Pw/view?usp=sharing |
|
|
162
182
|
| DISE 2021 (15 degree) | https://drive.google.com/file/d/1BLiuu-j28dbuPFi4n3C0KuV6vXGmB0qS/view?usp=sharing |
|
|
163
183
|
|
|
164
|
-
Can also download from
|
|
184
|
+
Can also download from Hugging Face: https://huggingface.co/datasets/phamquiluan/DISE-2021
|
|
185
|
+
|
|
186
|
+
Or from Zenodo: https://zenodo.org/records/12570649
|
|
165
187
|
|
|
166
188
|
## Reproducibility and Evaluation Code
|
|
167
189
|
|
|
@@ -180,7 +202,3 @@ L. Pham, H. Hoang, X.T. Mai, T. A. Tran, "Adaptive Radial Projection on Fourier
|
|
|
180
202
|
organization={IEEE}
|
|
181
203
|
}
|
|
182
204
|
```
|
|
183
|
-
|
|
184
|
-
## Star History
|
|
185
|
-
|
|
186
|
-
[](https://star-history.com/#phamquiluan/jdeskew&Date)
|
|
@@ -5,11 +5,9 @@
|
|
|
5
5
|
[](https://www.codacy.com/gh/phamquiluan/jdeskew/dashboard?utm_source=github.com&utm_medium=referral&utm_content=phamquiluan/jdeskew&utm_campaign=Badge_Coverage)
|
|
6
6
|
[](https://www.codacy.com/gh/phamquiluan/jdeskew/dashboard?utm_source=github.com&utm_medium=referral&utm_content=phamquiluan/jdeskew&utm_campaign=Badge_Grade)
|
|
7
7
|
[](https://pepy.tech/project/jdeskew)
|
|
8
|
-

|
|
9
8
|

|
|
10
|
-
](https://paperswithcode.com/sota/document-image-skew-estimation-on-dise-2021?p=adaptive-radial-projection-on-fourier)
|
|
9
|
+
[](https://huggingface.co/papers/2603.05942)
|
|
10
|
+
[](https://huggingface.co/datasets/phamquiluan/DISE-2021)
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|

|
|
@@ -40,6 +38,12 @@
|
|
|
40
38
|
pip install jdeskew
|
|
41
39
|
```
|
|
42
40
|
|
|
41
|
+
### conda
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
conda create --name jdeskew -c conda-forge jdeskew
|
|
45
|
+
```
|
|
46
|
+
|
|
43
47
|
## How-to-use
|
|
44
48
|
|
|
45
49
|
### using python
|
|
@@ -90,6 +94,8 @@ Link1: https://ieeexplore.ieee.org/document/9897910
|
|
|
90
94
|
|
|
91
95
|
Link2: https://www.researchgate.net/publication/364320913_ADAPTIVE_RADIAL_PROJECTION_ON_FOURIER_MAGNITUDE_SPECTRUM_FOR_DOCUMENT_IMAGE_SKEW_ESTIMATION
|
|
92
96
|
|
|
97
|
+
Link3: https://huggingface.co/papers/2603.05942
|
|
98
|
+
|
|
93
99
|
|
|
94
100
|
## Performance Comparison on DISE 2021
|
|
95
101
|
|
|
@@ -120,7 +126,9 @@ This datasets are built upon three other datasets: DISEC 2013, RVL-CDIP, RDCL 20
|
|
|
120
126
|
| DISE 2021 (45 degree) | https://drive.google.com/file/d/1a-a6aOqdsghjeHGLnCLsDs7NoJIus-Pw/view?usp=sharing |
|
|
121
127
|
| DISE 2021 (15 degree) | https://drive.google.com/file/d/1BLiuu-j28dbuPFi4n3C0KuV6vXGmB0qS/view?usp=sharing |
|
|
122
128
|
|
|
123
|
-
Can also download from
|
|
129
|
+
Can also download from Hugging Face: https://huggingface.co/datasets/phamquiluan/DISE-2021
|
|
130
|
+
|
|
131
|
+
Or from Zenodo: https://zenodo.org/records/12570649
|
|
124
132
|
|
|
125
133
|
## Reproducibility and Evaluation Code
|
|
126
134
|
|
|
@@ -139,7 +147,3 @@ L. Pham, H. Hoang, X.T. Mai, T. A. Tran, "Adaptive Radial Projection on Fourier
|
|
|
139
147
|
organization={IEEE}
|
|
140
148
|
}
|
|
141
149
|
```
|
|
142
|
-
|
|
143
|
-
## Star History
|
|
144
|
-
|
|
145
|
-
[](https://star-history.com/#phamquiluan/jdeskew&Date)
|
|
Binary file
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Skew Estimator."""
|
|
2
|
+
import cv2
|
|
3
|
+
import numpy as np
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _ensure_gray(image: np.ndarray) -> np.ndarray:
|
|
8
|
+
try:
|
|
9
|
+
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
10
|
+
except cv2.error:
|
|
11
|
+
pass
|
|
12
|
+
return image
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _ensure_optimal_square(image: np.ndarray) -> np.ndarray:
|
|
16
|
+
if image is None:
|
|
17
|
+
raise ValueError("image must not be None")
|
|
18
|
+
nw = nh = cv2.getOptimalDFTSize(max(image.shape[:2]))
|
|
19
|
+
output_image = cv2.copyMakeBorder(
|
|
20
|
+
src=image,
|
|
21
|
+
top=0,
|
|
22
|
+
bottom=nh - image.shape[0],
|
|
23
|
+
left=0,
|
|
24
|
+
right=nw - image.shape[1],
|
|
25
|
+
borderType=cv2.BORDER_CONSTANT,
|
|
26
|
+
value=255,
|
|
27
|
+
)
|
|
28
|
+
return output_image
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _get_fft_magnitude(image: np.ndarray) -> np.ndarray:
|
|
32
|
+
gray = _ensure_gray(image)
|
|
33
|
+
opt_gray = _ensure_optimal_square(gray)
|
|
34
|
+
|
|
35
|
+
# thresh
|
|
36
|
+
opt_gray = cv2.adaptiveThreshold(
|
|
37
|
+
~opt_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, -10
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# perform fft - using fft2 to ensure square output
|
|
41
|
+
dft = np.fft.fft2(opt_gray)
|
|
42
|
+
shifted_dft = np.fft.fftshift(dft)
|
|
43
|
+
|
|
44
|
+
# get the magnitude (module)
|
|
45
|
+
magnitude = np.abs(shifted_dft)
|
|
46
|
+
return magnitude
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _get_angle_radial_projection(m: np.ndarray, angle_max: Optional[float] = None, num: Optional[int] = None) -> float:
|
|
50
|
+
"""
|
|
51
|
+
Get angle via radial projection.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
m : np.ndarray
|
|
56
|
+
Square FFT magnitude spectrum of the image.
|
|
57
|
+
angle_max : float, optional
|
|
58
|
+
Maximum angle to search for, in degrees.
|
|
59
|
+
num : int, optional
|
|
60
|
+
Number of angles to generate between 1 degree.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
float
|
|
65
|
+
Estimated skew angle in degrees.
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
if m.shape[0] != m.shape[1]:
|
|
69
|
+
raise ValueError(f"expected a square matrix, got shape {m.shape}")
|
|
70
|
+
r = c = m.shape[0] // 2
|
|
71
|
+
|
|
72
|
+
if angle_max is None:
|
|
73
|
+
angle_max = 15.0
|
|
74
|
+
|
|
75
|
+
if num is None:
|
|
76
|
+
num = 20
|
|
77
|
+
|
|
78
|
+
tr = np.linspace(-1 * angle_max, angle_max, int(angle_max * num * 2)) / 180 * np.pi
|
|
79
|
+
|
|
80
|
+
# Pre-allocate array for better performance
|
|
81
|
+
li = np.zeros_like(tr)
|
|
82
|
+
|
|
83
|
+
for i, t in enumerate(tr):
|
|
84
|
+
x = np.arange(0, r)
|
|
85
|
+
y = c + np.int32(x * np.cos(t))
|
|
86
|
+
x = c + np.int32(-1 * x * np.sin(t))
|
|
87
|
+
# Use boolean indexing for faster computation
|
|
88
|
+
valid_indices = (y >= 0) & (y < m.shape[0]) & (x >= 0) & (x < m.shape[1])
|
|
89
|
+
li[i] = np.sum(m[y[valid_indices], x[valid_indices]])
|
|
90
|
+
|
|
91
|
+
a = tr[np.argmax(li)] / np.pi * 180
|
|
92
|
+
|
|
93
|
+
if a == -1 * angle_max:
|
|
94
|
+
return 0.0
|
|
95
|
+
return float(a)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_angle(
|
|
99
|
+
image: np.ndarray,
|
|
100
|
+
vertical_image_shape: Optional[int] = None,
|
|
101
|
+
angle_max: Optional[float] = None
|
|
102
|
+
) -> float:
|
|
103
|
+
"""
|
|
104
|
+
Get angle from a given document image.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
image : np.ndarray
|
|
109
|
+
Input image as numpy array.
|
|
110
|
+
vertical_image_shape : int, optional
|
|
111
|
+
Resize height for preprocessing.
|
|
112
|
+
angle_max : float, optional
|
|
113
|
+
Maximum angle to search for, in degrees.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
float
|
|
118
|
+
Estimated skew angle in degrees.
|
|
119
|
+
|
|
120
|
+
"""
|
|
121
|
+
if not isinstance(image, np.ndarray):
|
|
122
|
+
raise TypeError(f"image must be a numpy array, got {type(image)}")
|
|
123
|
+
|
|
124
|
+
# if vertical_image_shape is None:
|
|
125
|
+
# vertical_image_shape = 512
|
|
126
|
+
|
|
127
|
+
if angle_max is None:
|
|
128
|
+
angle_max = 15.0
|
|
129
|
+
|
|
130
|
+
# resize
|
|
131
|
+
if vertical_image_shape is not None:
|
|
132
|
+
ratio = vertical_image_shape / image.shape[0]
|
|
133
|
+
image = cv2.resize(image, None, fx=ratio, fy=ratio)
|
|
134
|
+
|
|
135
|
+
m = _get_fft_magnitude(image)
|
|
136
|
+
a = _get_angle_radial_projection(m, angle_max=angle_max)
|
|
137
|
+
return a
|
|
@@ -3,13 +3,29 @@ import cv2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def rotate(image, angle, resize=True, border_mode=None, border_value=None, flags=None):
|
|
6
|
-
"""
|
|
6
|
+
"""
|
|
7
|
+
Rotate input image with respect to a given angle.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
image : np.ndarray
|
|
12
|
+
Input image.
|
|
13
|
+
angle : float
|
|
14
|
+
Rotation angle in degrees.
|
|
15
|
+
resize : bool
|
|
16
|
+
Resize output to the input image shape.
|
|
17
|
+
border_mode : int, optional
|
|
18
|
+
cv2.BORDER_REPLICATE or cv2.BORDER_CONSTANT.
|
|
19
|
+
border_value : optional
|
|
20
|
+
Border value used when border_mode is cv2.BORDER_CONSTANT.
|
|
21
|
+
flags : int, optional
|
|
22
|
+
OpenCV interpolation flags.
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
np.ndarray
|
|
27
|
+
Rotated image.
|
|
7
28
|
|
|
8
|
-
Params:
|
|
9
|
-
--------
|
|
10
|
-
resize (Bool) : resize to input image shape
|
|
11
|
-
border_mode : cv2.BORDER_REPLICATE, cv2.BORDER_CONSTANT
|
|
12
|
-
border_value : when border_mode == cv2.BORDER_CONSTANT
|
|
13
29
|
"""
|
|
14
30
|
if border_mode is None:
|
|
15
31
|
border_mode = cv2.BORDER_CONSTANT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: jdeskew
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -25,6 +25,19 @@ License: MIT License
|
|
|
25
25
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
|
|
28
|
+
Classifier: Intended Audience :: Developers
|
|
29
|
+
Classifier: Intended Audience :: Science/Research
|
|
30
|
+
Classifier: Operating System :: OS Independent
|
|
31
|
+
Classifier: Programming Language :: Python :: 3
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
39
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
40
|
+
Requires-Python: >=3.8
|
|
28
41
|
Description-Content-Type: text/markdown
|
|
29
42
|
License-File: LICENSE
|
|
30
43
|
Requires-Dist: numpy
|
|
@@ -38,6 +51,7 @@ Requires-Dist: pytest-xdist; extra == "dev"
|
|
|
38
51
|
Requires-Dist: fastapi; extra == "dev"
|
|
39
52
|
Requires-Dist: uvicorn[standard]; extra == "dev"
|
|
40
53
|
Requires-Dist: python-multipart; extra == "dev"
|
|
54
|
+
Dynamic: license-file
|
|
41
55
|
|
|
42
56
|
# Document Image Skew Estimation
|
|
43
57
|
|
|
@@ -46,11 +60,9 @@ Requires-Dist: python-multipart; extra == "dev"
|
|
|
46
60
|
[](https://www.codacy.com/gh/phamquiluan/jdeskew/dashboard?utm_source=github.com&utm_medium=referral&utm_content=phamquiluan/jdeskew&utm_campaign=Badge_Coverage)
|
|
47
61
|
[](https://www.codacy.com/gh/phamquiluan/jdeskew/dashboard?utm_source=github.com&utm_medium=referral&utm_content=phamquiluan/jdeskew&utm_campaign=Badge_Grade)
|
|
48
62
|
[](https://pepy.tech/project/jdeskew)
|
|
49
|
-

|
|
50
63
|

|
|
51
|
-
](https://paperswithcode.com/sota/document-image-skew-estimation-on-dise-2021?p=adaptive-radial-projection-on-fourier)
|
|
64
|
+
[](https://huggingface.co/papers/2603.05942)
|
|
65
|
+
[](https://huggingface.co/datasets/phamquiluan/DISE-2021)
|
|
54
66
|
|
|
55
67
|
|
|
56
68
|

|
|
@@ -81,6 +93,12 @@ Requires-Dist: python-multipart; extra == "dev"
|
|
|
81
93
|
pip install jdeskew
|
|
82
94
|
```
|
|
83
95
|
|
|
96
|
+
### conda
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
conda create --name jdeskew -c conda-forge jdeskew
|
|
100
|
+
```
|
|
101
|
+
|
|
84
102
|
## How-to-use
|
|
85
103
|
|
|
86
104
|
### using python
|
|
@@ -131,6 +149,8 @@ Link1: https://ieeexplore.ieee.org/document/9897910
|
|
|
131
149
|
|
|
132
150
|
Link2: https://www.researchgate.net/publication/364320913_ADAPTIVE_RADIAL_PROJECTION_ON_FOURIER_MAGNITUDE_SPECTRUM_FOR_DOCUMENT_IMAGE_SKEW_ESTIMATION
|
|
133
151
|
|
|
152
|
+
Link3: https://huggingface.co/papers/2603.05942
|
|
153
|
+
|
|
134
154
|
|
|
135
155
|
## Performance Comparison on DISE 2021
|
|
136
156
|
|
|
@@ -161,7 +181,9 @@ This datasets are built upon three other datasets: DISEC 2013, RVL-CDIP, RDCL 20
|
|
|
161
181
|
| DISE 2021 (45 degree) | https://drive.google.com/file/d/1a-a6aOqdsghjeHGLnCLsDs7NoJIus-Pw/view?usp=sharing |
|
|
162
182
|
| DISE 2021 (15 degree) | https://drive.google.com/file/d/1BLiuu-j28dbuPFi4n3C0KuV6vXGmB0qS/view?usp=sharing |
|
|
163
183
|
|
|
164
|
-
Can also download from
|
|
184
|
+
Can also download from Hugging Face: https://huggingface.co/datasets/phamquiluan/DISE-2021
|
|
185
|
+
|
|
186
|
+
Or from Zenodo: https://zenodo.org/records/12570649
|
|
165
187
|
|
|
166
188
|
## Reproducibility and Evaluation Code
|
|
167
189
|
|
|
@@ -180,7 +202,3 @@ L. Pham, H. Hoang, X.T. Mai, T. A. Tran, "Adaptive Radial Projection on Fourier
|
|
|
180
202
|
organization={IEEE}
|
|
181
203
|
}
|
|
182
204
|
```
|
|
183
|
-
|
|
184
|
-
## Star History
|
|
185
|
-
|
|
186
|
-
[](https://star-history.com/#phamquiluan/jdeskew&Date)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
.codacy.yaml
|
|
1
2
|
.dockerignore
|
|
2
3
|
.flake8
|
|
3
4
|
.gitignore
|
|
@@ -7,6 +8,7 @@ LICENSE
|
|
|
7
8
|
README.md
|
|
8
9
|
cog.yaml
|
|
9
10
|
estimator.py
|
|
11
|
+
icip_paper.pdf
|
|
10
12
|
main.py
|
|
11
13
|
pyproject.toml
|
|
12
14
|
reproduce.ipynb
|
|
@@ -27,6 +29,8 @@ jdeskew.egg-info/PKG-INFO
|
|
|
27
29
|
jdeskew.egg-info/SOURCES.txt
|
|
28
30
|
jdeskew.egg-info/dependency_links.txt
|
|
29
31
|
jdeskew.egg-info/requires.txt
|
|
32
|
+
jdeskew.egg-info/scm_file_list.json
|
|
33
|
+
jdeskew.egg-info/scm_version.json
|
|
30
34
|
jdeskew.egg-info/top_level.txt
|
|
31
35
|
tests/test.png
|
|
32
36
|
tests/test.py
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
"main.py",
|
|
4
|
+
".dockerignore",
|
|
5
|
+
"icip_paper.pdf",
|
|
6
|
+
".pre-commit-config.yaml",
|
|
7
|
+
"Dockerfile",
|
|
8
|
+
"setup.py",
|
|
9
|
+
"pyproject.toml",
|
|
10
|
+
".codacy.yaml",
|
|
11
|
+
"LICENSE",
|
|
12
|
+
"reproduce.ipynb",
|
|
13
|
+
"README.md",
|
|
14
|
+
".flake8",
|
|
15
|
+
".gitignore",
|
|
16
|
+
"estimator.py",
|
|
17
|
+
"cog.yaml",
|
|
18
|
+
".github/workflows/dependency-review.yml",
|
|
19
|
+
".github/workflows/docker-build-and-push.yml",
|
|
20
|
+
".github/workflows/greetings.yml",
|
|
21
|
+
".github/workflows/codeql-analysis.yml",
|
|
22
|
+
".github/workflows/python-publish.yml",
|
|
23
|
+
".github/workflows/label.yml",
|
|
24
|
+
".github/workflows/python-package.yml",
|
|
25
|
+
".github/workflows/stale.yml",
|
|
26
|
+
"tests/test.py",
|
|
27
|
+
"tests/test.png",
|
|
28
|
+
"jdeskew/utility.py",
|
|
29
|
+
"jdeskew/__init__.py",
|
|
30
|
+
"jdeskew/estimator.py",
|
|
31
|
+
".circleci/config.yml"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -11,6 +11,21 @@ name = "jdeskew"
|
|
|
11
11
|
description = "Document Image Skew Estimation using Adaptive Radial Projection"
|
|
12
12
|
authors = [{name = "Luan Pham", email = "phamquiluan@gmail.com"}]
|
|
13
13
|
license = {file = "LICENSE"}
|
|
14
|
+
requires-python = ">=3.8"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.8",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
27
|
+
"Topic :: Scientific/Engineering :: Image Processing",
|
|
28
|
+
]
|
|
14
29
|
dependencies = [
|
|
15
30
|
"numpy",
|
|
16
31
|
"opencv-python-headless"
|
|
@@ -64,4 +79,5 @@ exclude = '''
|
|
|
64
79
|
[tool.pytest.ini_options]
|
|
65
80
|
minversion = "6.0"
|
|
66
81
|
addopts = "-ra -q"
|
|
67
|
-
testpaths = [ "tests
|
|
82
|
+
testpaths = [ "tests" ]
|
|
83
|
+
python_files = [ "test.py", "test_*.py" ]
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
"""Skew Estimator."""
|
|
2
|
-
import cv2
|
|
3
|
-
import numpy as np
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def _ensure_gray(image):
|
|
7
|
-
try:
|
|
8
|
-
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
9
|
-
except cv2.error:
|
|
10
|
-
pass
|
|
11
|
-
return image
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _ensure_optimal_square(image):
|
|
15
|
-
assert image is not None, image
|
|
16
|
-
nw = nh = cv2.getOptimalDFTSize(max(image.shape[:2]))
|
|
17
|
-
output_image = cv2.copyMakeBorder(
|
|
18
|
-
src=image,
|
|
19
|
-
top=0,
|
|
20
|
-
bottom=nh - image.shape[0],
|
|
21
|
-
left=0,
|
|
22
|
-
right=nw - image.shape[1],
|
|
23
|
-
borderType=cv2.BORDER_CONSTANT,
|
|
24
|
-
value=255,
|
|
25
|
-
)
|
|
26
|
-
return output_image
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def _get_fft_magnitude(image):
|
|
30
|
-
gray = _ensure_gray(image)
|
|
31
|
-
opt_gray = _ensure_optimal_square(gray)
|
|
32
|
-
|
|
33
|
-
# thresh
|
|
34
|
-
opt_gray = cv2.adaptiveThreshold(
|
|
35
|
-
~opt_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, -10
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
# perform fft
|
|
39
|
-
dft = np.fft.fft2(opt_gray)
|
|
40
|
-
shifted_dft = np.fft.fftshift(dft)
|
|
41
|
-
|
|
42
|
-
# get the magnitude (module)
|
|
43
|
-
magnitude = np.abs(shifted_dft)
|
|
44
|
-
return magnitude
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _get_angle_radial_projection(m, angle_max=None, num=None, W=None):
|
|
48
|
-
"""Get angle via radial projection.
|
|
49
|
-
|
|
50
|
-
Arguments:
|
|
51
|
-
------------
|
|
52
|
-
angle_max : float
|
|
53
|
-
num : int
|
|
54
|
-
number of angles to generate between 1 degree
|
|
55
|
-
"""
|
|
56
|
-
assert m.shape[0] == m.shape[1]
|
|
57
|
-
r = c = m.shape[0] // 2
|
|
58
|
-
|
|
59
|
-
if angle_max is None:
|
|
60
|
-
pass
|
|
61
|
-
|
|
62
|
-
if num is None:
|
|
63
|
-
num = 20
|
|
64
|
-
|
|
65
|
-
tr = np.linspace(-1 * angle_max, angle_max, int(angle_max * num * 2)) / 180 * np.pi
|
|
66
|
-
profile_arr = tr.copy()
|
|
67
|
-
|
|
68
|
-
def f(t):
|
|
69
|
-
_f = np.vectorize(
|
|
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
|
|
75
|
-
|
|
76
|
-
vf = np.vectorize(f)
|
|
77
|
-
li = vf(profile_arr)
|
|
78
|
-
|
|
79
|
-
a = tr[np.argmax(li)] / np.pi * 180
|
|
80
|
-
|
|
81
|
-
if a == -1 * angle_max:
|
|
82
|
-
return 0
|
|
83
|
-
return a
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def get_angle(
|
|
87
|
-
image: np.ndarray, vertical_image_shape: int = None, angle_max: float = None
|
|
88
|
-
):
|
|
89
|
-
"""Getting angle from a given document image.
|
|
90
|
-
|
|
91
|
-
image : np.ndarray
|
|
92
|
-
vertical_image_shape : int
|
|
93
|
-
resize image as preprocessing
|
|
94
|
-
angle_max : float
|
|
95
|
-
maximum angle to searching
|
|
96
|
-
"""
|
|
97
|
-
assert isinstance(image, np.ndarray), image
|
|
98
|
-
|
|
99
|
-
# if vertical_image_shape is None:
|
|
100
|
-
# vertical_image_shape = 512
|
|
101
|
-
|
|
102
|
-
if angle_max is None:
|
|
103
|
-
angle_max = 15
|
|
104
|
-
|
|
105
|
-
# resize
|
|
106
|
-
if vertical_image_shape is not None:
|
|
107
|
-
ratio = vertical_image_shape / image.shape[0]
|
|
108
|
-
image = cv2.resize(image, None, fx=ratio, fy=ratio)
|
|
109
|
-
|
|
110
|
-
m = _get_fft_magnitude(image)
|
|
111
|
-
a = _get_angle_radial_projection(m, angle_max=angle_max)
|
|
112
|
-
return a
|
|
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
|