nafrec 0.3.0__py3-none-any.whl
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.
- nafrec/__init__.py +22 -0
- nafrec/image_processing.py +237 -0
- nafrec/main.py +1240 -0
- nafrec/model_types.py +119 -0
- nafrec/nafrec.py +184 -0
- nafrec/paddle_layout.py +425 -0
- nafrec/ppocr.py +381 -0
- nafrec/reading_order_graph.py +177 -0
- nafrec/script_classifier.py +146 -0
- nafrec/seg_inference.py +714 -0
- nafrec/trocr.py +308 -0
- nafrec/utils.py +347 -0
- nafrec/xml_output.py +487 -0
- nafrec-0.3.0.dist-info/METADATA +283 -0
- nafrec-0.3.0.dist-info/RECORD +17 -0
- nafrec-0.3.0.dist-info/WHEEL +4 -0
- nafrec-0.3.0.dist-info/entry_points.txt +2 -0
nafrec/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from .main import load_rfdetr_model, load_trocr_model, get_text_predictions, classify_and_recognize, run, make_args
|
|
2
|
+
from .nafrec import NAFRec
|
|
3
|
+
from .seg_inference import predict_polygons
|
|
4
|
+
from .paddle_layout import load_paddle_layout_model, PaddleLayoutDetector
|
|
5
|
+
from .utils import get_default_region, get_line_regions, order_regions_lines, get_iou
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"NAFRec",
|
|
9
|
+
"load_rfdetr_model",
|
|
10
|
+
"load_trocr_model",
|
|
11
|
+
"get_text_predictions",
|
|
12
|
+
"classify_and_recognize",
|
|
13
|
+
"run",
|
|
14
|
+
"make_args",
|
|
15
|
+
"predict_polygons",
|
|
16
|
+
"load_paddle_layout_model",
|
|
17
|
+
"PaddleLayoutDetector",
|
|
18
|
+
"get_default_region",
|
|
19
|
+
"get_line_regions",
|
|
20
|
+
"order_regions_lines",
|
|
21
|
+
"get_iou"
|
|
22
|
+
]
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
from torchvision.io import read_image, ImageReadMode
|
|
2
|
+
import numpy as np
|
|
3
|
+
from torchvision.transforms import v2 as transforms_v2
|
|
4
|
+
import torch
|
|
5
|
+
import cv2
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def load_with_torchvision(img_path):
|
|
9
|
+
"""
|
|
10
|
+
Load an image using torchvision and convert to numpy array.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
img_path (str or Path): Path to the image file
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
numpy.ndarray: Image array in RGB format with shape (H, W, C)
|
|
17
|
+
"""
|
|
18
|
+
# Read as tensor
|
|
19
|
+
img_tensor = read_image(str(img_path), mode=ImageReadMode.RGB)
|
|
20
|
+
# Convert to numpy: (C, H, W) -> (H, W, C)
|
|
21
|
+
img_np = img_tensor.permute(1, 2, 0).numpy()
|
|
22
|
+
return img_np
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def preprocess_resize_smallerof_wh_torch_transform(image, max_size=1024, normalize=True):
|
|
26
|
+
"""
|
|
27
|
+
Resize so that the *smaller* of (width, height) is at most max_size.
|
|
28
|
+
The larger dimension is allowed to grow arbitrarily.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
image: torch.Tensor (C, H, W) or PIL Image
|
|
32
|
+
max_size: maximum size for the *smaller* dimension
|
|
33
|
+
normalize: whether to normalize to [0, 1] range
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
torch.Tensor (C, H, W) or PIL Image (same type as input)
|
|
37
|
+
"""
|
|
38
|
+
# Convert numpy -> torch
|
|
39
|
+
if isinstance(image, np.ndarray):
|
|
40
|
+
image = torch.from_numpy(image)
|
|
41
|
+
if image.ndim == 3 and image.shape[2] in [1, 3]:
|
|
42
|
+
image = image.permute(2, 0, 1)
|
|
43
|
+
|
|
44
|
+
# Extract H, W
|
|
45
|
+
if isinstance(image, torch.Tensor):
|
|
46
|
+
_, h, w = image.shape
|
|
47
|
+
else: # PIL
|
|
48
|
+
w, h = image.size
|
|
49
|
+
|
|
50
|
+
min_dim = min(h, w)
|
|
51
|
+
transform_list = []
|
|
52
|
+
|
|
53
|
+
# Resize ONLY if the *smaller* dimension is too large
|
|
54
|
+
if min_dim > max_size:
|
|
55
|
+
scale = max_size / min_dim
|
|
56
|
+
new_h = int(round(h * scale))
|
|
57
|
+
new_w = int(round(w * scale))
|
|
58
|
+
transform_list.append(
|
|
59
|
+
transforms_v2.Resize(size=[new_h, new_w], antialias=True)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Normalize
|
|
63
|
+
if normalize:
|
|
64
|
+
transform_list.append(transforms_v2.ToDtype(torch.float32, scale=True))
|
|
65
|
+
|
|
66
|
+
# Apply transforms
|
|
67
|
+
if transform_list:
|
|
68
|
+
transform = transforms_v2.Compose(transform_list)
|
|
69
|
+
resized = transform(image)
|
|
70
|
+
else:
|
|
71
|
+
resized = image
|
|
72
|
+
|
|
73
|
+
return resized
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def preprocess_resize_torch_transform(image, max_size=1024, normalize=True):
|
|
77
|
+
"""
|
|
78
|
+
Resize using torchvision.transforms.v2 (most concise, PyTorch only).
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
image: torch.Tensor (C, H, W) or PIL Image
|
|
82
|
+
max_size: maximum size for the longer dimension
|
|
83
|
+
normalize: whether to normalize to [0, 1] range
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
torch.Tensor (C, H, W) or PIL Image (same type as input)
|
|
87
|
+
"""
|
|
88
|
+
# Convert to tensor if numpy
|
|
89
|
+
if isinstance(image, np.ndarray):
|
|
90
|
+
image = torch.from_numpy(image)
|
|
91
|
+
if image.ndim == 3 and image.shape[2] in [1, 3]:
|
|
92
|
+
image = image.permute(2, 0, 1)
|
|
93
|
+
|
|
94
|
+
c, h, w = image.shape if isinstance(
|
|
95
|
+
image, torch.Tensor) else (None, *image.size[::-1])
|
|
96
|
+
|
|
97
|
+
# Build transform pipeline
|
|
98
|
+
transform_list = []
|
|
99
|
+
|
|
100
|
+
# Add resize if needed
|
|
101
|
+
if h > max_size or w > max_size:
|
|
102
|
+
transform_list.append(transforms_v2.Resize(
|
|
103
|
+
size=None, max_size=max_size, antialias=True))
|
|
104
|
+
|
|
105
|
+
# Add normalization
|
|
106
|
+
if normalize:
|
|
107
|
+
transform_list.append(transforms_v2.ToDtype(torch.float32, scale=True))
|
|
108
|
+
|
|
109
|
+
# Apply transforms
|
|
110
|
+
if transform_list:
|
|
111
|
+
transform = transforms_v2.Compose(transform_list)
|
|
112
|
+
resized = transform(image)
|
|
113
|
+
else:
|
|
114
|
+
resized = image
|
|
115
|
+
|
|
116
|
+
return resized
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def upscale_mask_opencv(mask, bbox, upscaled_bbox_shape):
|
|
120
|
+
"""Upscale using OpenCV resize with nearest neighbor."""
|
|
121
|
+
x1, y1, x2, y2 = map(int, bbox)
|
|
122
|
+
cropped_mask = mask[y1:y2, x1:x2]
|
|
123
|
+
mask_uint8 = cropped_mask.astype(np.uint8)
|
|
124
|
+
upscaled = cv2.resize(mask_uint8,
|
|
125
|
+
upscaled_bbox_shape,
|
|
126
|
+
interpolation=cv2.INTER_NEAREST)
|
|
127
|
+
|
|
128
|
+
return upscaled * 255
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def upscale_bbox(bbox, original_shape, mask_shape):
|
|
132
|
+
"""
|
|
133
|
+
Upscale bounding box coordinates from mask resolution to original image resolution.
|
|
134
|
+
|
|
135
|
+
Parameters:
|
|
136
|
+
-----------
|
|
137
|
+
bbox : np.ndarray or list
|
|
138
|
+
Bounding box coordinates in format [x_min, y_min, x_max, y_max]
|
|
139
|
+
in the mask's coordinate system
|
|
140
|
+
original_shape : tuple
|
|
141
|
+
Original image shape (H, W) or (H, W, C) - e.g., (4545, 5527, 3)
|
|
142
|
+
mask_shape : tuple
|
|
143
|
+
Mask shape (H, W) - e.g., (631, 768)
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
--------
|
|
147
|
+
np.ndarray
|
|
148
|
+
Upscaled bounding box as integer coordinates [x_min, y_min, x_max, y_max]
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
# Ensure bbox is a numpy array
|
|
152
|
+
bbox = np.array(bbox)
|
|
153
|
+
|
|
154
|
+
# Extract height and width from shapes
|
|
155
|
+
original_h, original_w = original_shape[0], original_shape[1]
|
|
156
|
+
mask_h, mask_w = mask_shape[0], mask_shape[1]
|
|
157
|
+
|
|
158
|
+
# Calculate scale factors
|
|
159
|
+
scale_x = original_w / mask_w # Width scaling
|
|
160
|
+
scale_y = original_h / mask_h # Height scaling
|
|
161
|
+
|
|
162
|
+
# Unpack bbox coordinates
|
|
163
|
+
x_min, y_min, x_max, y_max = bbox
|
|
164
|
+
|
|
165
|
+
# Scale coordinates
|
|
166
|
+
x_min_scaled = x_min * scale_x
|
|
167
|
+
y_min_scaled = y_min * scale_y
|
|
168
|
+
x_max_scaled = x_max * scale_x
|
|
169
|
+
y_max_scaled = y_max * scale_y
|
|
170
|
+
|
|
171
|
+
# limit to range 0 to original width/height
|
|
172
|
+
if x_min_scaled < 0:
|
|
173
|
+
x_min_scaled = 0
|
|
174
|
+
if y_min_scaled < 0:
|
|
175
|
+
y_min_scaled = 0
|
|
176
|
+
if x_max_scaled > original_w:
|
|
177
|
+
x_max_scaled = original_w
|
|
178
|
+
if y_max_scaled > original_h:
|
|
179
|
+
y_max_scaled = original_h
|
|
180
|
+
|
|
181
|
+
# Convert to integers (rounding to nearest)
|
|
182
|
+
bbox_scaled = np.array([
|
|
183
|
+
x_min_scaled,
|
|
184
|
+
y_min_scaled,
|
|
185
|
+
x_max_scaled,
|
|
186
|
+
y_max_scaled
|
|
187
|
+
]).astype(np.int32)
|
|
188
|
+
|
|
189
|
+
return bbox_scaled
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def crop_line(image, polygon):
|
|
193
|
+
"""Crops predicted text line based on the polygon coordinates
|
|
194
|
+
and returns pasted text line image with white background.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
image: Input image array.
|
|
198
|
+
polygon: List of coordinate pairs defining the text line polygon.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
numpy.ndarray: Cropped text line image with white background around polygon.
|
|
202
|
+
"""
|
|
203
|
+
polygon = np.array([[int(lst[0]), int(lst[1])]
|
|
204
|
+
for lst in polygon], dtype=np.int32)
|
|
205
|
+
rect = cv2.boundingRect(polygon)
|
|
206
|
+
cropped_image = image[rect[1]: rect[1] +
|
|
207
|
+
rect[3], rect[0]: rect[0] + rect[2]]
|
|
208
|
+
mask = np.zeros(
|
|
209
|
+
[cropped_image.shape[0], cropped_image.shape[1]], dtype=np.uint8)
|
|
210
|
+
cv2.drawContours(mask, [
|
|
211
|
+
polygon - np.array([[rect[0], rect[1]]])], -1, (255, 255, 255), -1, cv2.LINE_AA)
|
|
212
|
+
res = cv2.bitwise_and(cropped_image, cropped_image, mask=mask)
|
|
213
|
+
wbg = np.ones_like(cropped_image, np.uint8)*255
|
|
214
|
+
cv2.bitwise_not(wbg, wbg, mask=mask)
|
|
215
|
+
# Overlap the resulted cropped image on the white background
|
|
216
|
+
dst = wbg+res
|
|
217
|
+
return dst
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def crop_lines(polygons, image):
|
|
221
|
+
"""Returns a list of line images cropped following the
|
|
222
|
+
detected polygon coordinates.
|
|
223
|
+
|
|
224
|
+
Crop multiple text lines from an image based on polygon coordinates.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
polygons: List of polygons, each containing coordinate pairs.
|
|
228
|
+
image: Input image array.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
list: List of cropped text line images.
|
|
232
|
+
"""
|
|
233
|
+
cropped_lines = []
|
|
234
|
+
for polygon in polygons:
|
|
235
|
+
cropped_line = crop_line(image, polygon)
|
|
236
|
+
cropped_lines.append(cropped_line)
|
|
237
|
+
return cropped_lines
|