gomyck-tools 1.5.5__py3-none-any.whl → 1.5.7__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.
- ctools/authcode_validator.py +379 -372
- ctools/ml/__init__.py +4 -0
- ctools/ml/interface/__init__.py +4 -0
- ctools/ml/interface/image_processor.py +42 -0
- ctools/ml/model_loader.py +36 -0
- ctools/util/file_crypto.py +92 -0
- {gomyck_tools-1.5.5.dist-info → gomyck_tools-1.5.7.dist-info}/METADATA +5 -4
- {gomyck_tools-1.5.5.dist-info → gomyck_tools-1.5.7.dist-info}/RECORD +11 -9
- ctools/ml/image_process.py +0 -121
- ctools/ml/img_extractor.py +0 -59
- ctools/ml/ppi.py +0 -276
- {gomyck_tools-1.5.5.dist-info → gomyck_tools-1.5.7.dist-info}/WHEEL +0 -0
- {gomyck_tools-1.5.5.dist-info → gomyck_tools-1.5.7.dist-info}/licenses/LICENSE +0 -0
- {gomyck_tools-1.5.5.dist-info → gomyck_tools-1.5.7.dist-info}/top_level.txt +0 -0
ctools/ml/ppi.py
DELETED
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
from io import BytesIO
|
|
2
|
-
from queue import Queue
|
|
3
|
-
|
|
4
|
-
import numpy as np
|
|
5
|
-
import yaml
|
|
6
|
-
from PIL import Image
|
|
7
|
-
from paddle.inference import Config, create_predictor
|
|
8
|
-
|
|
9
|
-
from ctools import path_info
|
|
10
|
-
from ctools.ml.image_process import preprocess
|
|
11
|
-
from ctools.ml.img_extractor import ClassRegionBase64ExtractorPIL
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class PaddlePredictorPool:
|
|
15
|
-
"""Predictor 池,用于多线程安全推理"""
|
|
16
|
-
def __init__(self, config_path, pool_size: int = 4):
|
|
17
|
-
"""
|
|
18
|
-
初始化预测器池
|
|
19
|
-
Note: 每个 Config 对象只能创建一个 predictor,所以我们需要保存 config_path
|
|
20
|
-
"""
|
|
21
|
-
self.config_path = config_path
|
|
22
|
-
self.pool = Queue()
|
|
23
|
-
self._init_pool(pool_size)
|
|
24
|
-
|
|
25
|
-
def _load_config_yaml(self):
|
|
26
|
-
"""加载 yaml 配置"""
|
|
27
|
-
with open(self.config_path, "r", encoding="utf-8") as f:
|
|
28
|
-
return yaml.safe_load(f)
|
|
29
|
-
|
|
30
|
-
def _create_config(self):
|
|
31
|
-
"""为新的 predictor 创建一个新的 Config 对象"""
|
|
32
|
-
cfg = self._load_config_yaml()
|
|
33
|
-
model_dir = cfg.get("MODEL_DIR", "")
|
|
34
|
-
model_file = cfg.get("MODEL_FILE", "")
|
|
35
|
-
if not model_file:
|
|
36
|
-
model_dir = path_info.get_app_path('mod/model.pdmodel')
|
|
37
|
-
params_file = cfg.get("PARAMS_FILE", "")
|
|
38
|
-
if not params_file:
|
|
39
|
-
model_dir = path_info.get_app_path('mod/model.pdiparams')
|
|
40
|
-
use_gpu = cfg.get("USE_GPU", False)
|
|
41
|
-
|
|
42
|
-
if model_dir:
|
|
43
|
-
config = Config(model_dir)
|
|
44
|
-
else:
|
|
45
|
-
config = Config(model_file, params_file)
|
|
46
|
-
|
|
47
|
-
config.enable_memory_optim()
|
|
48
|
-
|
|
49
|
-
if use_gpu:
|
|
50
|
-
config.enable_use_gpu(1000, 0)
|
|
51
|
-
else:
|
|
52
|
-
config.set_cpu_math_library_num_threads(4)
|
|
53
|
-
config.enable_mkldnn()
|
|
54
|
-
|
|
55
|
-
return config
|
|
56
|
-
|
|
57
|
-
def _init_pool(self, pool_size: int):
|
|
58
|
-
"""初始化池中的所有 predictor"""
|
|
59
|
-
for _ in range(pool_size):
|
|
60
|
-
config = self._create_config()
|
|
61
|
-
predictor = create_predictor(config)
|
|
62
|
-
self.pool.put(predictor)
|
|
63
|
-
|
|
64
|
-
def acquire(self, timeout=None):
|
|
65
|
-
"""从池中获取一个 predictor"""
|
|
66
|
-
return self.pool.get(timeout=timeout)
|
|
67
|
-
|
|
68
|
-
def release(self, predictor):
|
|
69
|
-
"""将 predictor 放回池中"""
|
|
70
|
-
self.pool.put(predictor)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class PaddleInferenceEngine:
|
|
74
|
-
def __init__(self, config_path, pool_size=4):
|
|
75
|
-
self.config_path = config_path
|
|
76
|
-
self.cfg = self._load_config(config_path)
|
|
77
|
-
self.predictor_pool = PaddlePredictorPool(config_path, pool_size=pool_size)
|
|
78
|
-
|
|
79
|
-
def _load_config(self, config_path):
|
|
80
|
-
with open(config_path, "r", encoding="utf-8") as f:
|
|
81
|
-
return yaml.safe_load(f)
|
|
82
|
-
|
|
83
|
-
def predict(self, inputs, timeout=None):
|
|
84
|
-
"""线程安全预测"""
|
|
85
|
-
predictor = self.predictor_pool.acquire(timeout=timeout)
|
|
86
|
-
try:
|
|
87
|
-
input_names = predictor.get_input_names()
|
|
88
|
-
for name in input_names:
|
|
89
|
-
if name not in inputs:
|
|
90
|
-
raise ValueError(f"缺少模型输入: {name}")
|
|
91
|
-
tensor = predictor.get_input_handle(name)
|
|
92
|
-
data = inputs[name]
|
|
93
|
-
tensor.reshape(data.shape)
|
|
94
|
-
tensor.copy_from_cpu(data)
|
|
95
|
-
predictor.run()
|
|
96
|
-
outputs = []
|
|
97
|
-
for name in predictor.get_output_names():
|
|
98
|
-
out = predictor.get_output_handle(name)
|
|
99
|
-
outputs.append(out.copy_to_cpu())
|
|
100
|
-
return outputs
|
|
101
|
-
finally:
|
|
102
|
-
self.predictor_pool.release(predictor)
|
|
103
|
-
|
|
104
|
-
def predict_image(self, img, im_size=320):
|
|
105
|
-
if isinstance(img, bytes):
|
|
106
|
-
img = Image.open(BytesIO(img)).convert("RGB")
|
|
107
|
-
elif isinstance(img, Image.Image):
|
|
108
|
-
img = img.convert("RGB")
|
|
109
|
-
elif isinstance(img, np.ndarray):
|
|
110
|
-
pass
|
|
111
|
-
else:
|
|
112
|
-
raise ValueError("Unsupported image type for predict_image")
|
|
113
|
-
orig_img_np = np.array(img) if not isinstance(img, np.ndarray) else img
|
|
114
|
-
data = preprocess(orig_img_np, im_size)
|
|
115
|
-
scale_factor = np.array([im_size * 1. / orig_img_np.shape[0], im_size * 1. / orig_img_np.shape[1]]).reshape((1, 2)).astype(np.float32)
|
|
116
|
-
im_shape = np.array([im_size, im_size]).reshape((1, 2)).astype(np.float32)
|
|
117
|
-
outputs = self.predict({"image": data, "im_shape": im_shape, "scale_factor": scale_factor})
|
|
118
|
-
return outputs, scale_factor, im_size
|
|
119
|
-
|
|
120
|
-
def predict_image_and_extract(self, img, im_size=320, class_names=None, target_classes=None, threshold=0.3):
|
|
121
|
-
"""预测并提取检测区域"""
|
|
122
|
-
raw_outputs, scale_factor, im_size_ret = self.predict_image(img, im_size=im_size)
|
|
123
|
-
|
|
124
|
-
# 转换为 PIL Image
|
|
125
|
-
if isinstance(img, bytes):
|
|
126
|
-
pil_img = Image.open(BytesIO(img)).convert("RGB")
|
|
127
|
-
elif isinstance(img, Image.Image):
|
|
128
|
-
pil_img = img.convert("RGB")
|
|
129
|
-
elif isinstance(img, np.ndarray):
|
|
130
|
-
pil_img = Image.fromarray(img.astype('uint8')).convert("RGB")
|
|
131
|
-
else:
|
|
132
|
-
raise ValueError("Unsupported image type")
|
|
133
|
-
|
|
134
|
-
# 提取检测区域
|
|
135
|
-
extractor = ClassRegionBase64ExtractorPIL(class_names or [], target_classes=target_classes, threshold=threshold)
|
|
136
|
-
detection_results = raw_outputs[0]
|
|
137
|
-
return extractor.extract(pil_img, detection_results, scale_factor=scale_factor, im_size=im_size_ret)
|
|
138
|
-
|
|
139
|
-
@staticmethod
|
|
140
|
-
def _nms_detections(detections, iou_threshold=0.5):
|
|
141
|
-
if len(detections) == 0:
|
|
142
|
-
return detections
|
|
143
|
-
dets = np.array(detections, dtype=np.float32)
|
|
144
|
-
scores = dets[:, 1]
|
|
145
|
-
sorted_idx = np.argsort(-scores)
|
|
146
|
-
keep = []
|
|
147
|
-
while len(sorted_idx) > 0:
|
|
148
|
-
current_idx = sorted_idx[0]
|
|
149
|
-
keep.append(current_idx)
|
|
150
|
-
if len(sorted_idx) == 1:
|
|
151
|
-
break
|
|
152
|
-
current_box = dets[current_idx, 2:6]
|
|
153
|
-
other_boxes = dets[sorted_idx[1:], 2:6]
|
|
154
|
-
x1_inter = np.maximum(current_box[0], other_boxes[:, 0])
|
|
155
|
-
y1_inter = np.maximum(current_box[1], other_boxes[:, 1])
|
|
156
|
-
x2_inter = np.minimum(current_box[2], other_boxes[:, 2])
|
|
157
|
-
y2_inter = np.minimum(current_box[3], other_boxes[:, 3])
|
|
158
|
-
inter_area = np.maximum(0, x2_inter - x1_inter) * np.maximum(0, y2_inter - y1_inter)
|
|
159
|
-
area_current = (current_box[2] - current_box[0]) * (current_box[3] - current_box[1])
|
|
160
|
-
area_others = (other_boxes[:, 2] - other_boxes[:, 0]) * (other_boxes[:, 3] - other_boxes[:, 1])
|
|
161
|
-
union_area = area_current + area_others - inter_area
|
|
162
|
-
iou = inter_area / (union_area + 1e-6)
|
|
163
|
-
valid_idx = np.where(iou < iou_threshold)[0] + 1
|
|
164
|
-
sorted_idx = sorted_idx[valid_idx]
|
|
165
|
-
return dets[keep].tolist()
|
|
166
|
-
|
|
167
|
-
def predict_image_tiled(self, img, im_size=320, tile_overlap=0.2, class_names=None, target_classes=None, threshold=0.3, nms_iou=0.5):
|
|
168
|
-
"""
|
|
169
|
-
Tiled prediction for large images (2K, 4K, etc).
|
|
170
|
-
Splits image into overlapping tiles, predicts each tile, merges results.
|
|
171
|
-
|
|
172
|
-
Args:
|
|
173
|
-
img: PIL Image, numpy array, or bytes
|
|
174
|
-
im_size: model training resolution (e.g., 320)
|
|
175
|
-
tile_overlap: overlap ratio between tiles (0.0-1.0), default 0.2
|
|
176
|
-
class_names: list of class names
|
|
177
|
-
target_classes: list of target classes to extract
|
|
178
|
-
threshold: confidence threshold
|
|
179
|
-
nms_iou: IoU threshold for NMS merging
|
|
180
|
-
|
|
181
|
-
Returns:
|
|
182
|
-
extracted_outputs: list of dicts with extracted regions (with coordinates mapped to original image)
|
|
183
|
-
all_detections: list of raw detections [cat_id, score, xmin, ymin, xmax, ymax] (original image coords)
|
|
184
|
-
"""
|
|
185
|
-
# Convert input to numpy array
|
|
186
|
-
if isinstance(img, bytes):
|
|
187
|
-
pil_img = Image.open(BytesIO(img)).convert("RGB")
|
|
188
|
-
img_np = np.array(pil_img)
|
|
189
|
-
elif isinstance(img, Image.Image):
|
|
190
|
-
img_np = np.array(img.convert("RGB"))
|
|
191
|
-
elif isinstance(img, np.ndarray):
|
|
192
|
-
img_np = img
|
|
193
|
-
else:
|
|
194
|
-
raise ValueError("Unsupported image type for predict_image_tiled")
|
|
195
|
-
orig_h, orig_w = img_np.shape[:2]
|
|
196
|
-
# Calculate tile parameters
|
|
197
|
-
stride = int(im_size * (1 - tile_overlap))
|
|
198
|
-
stride = max(1, stride)
|
|
199
|
-
# Generate tile coordinates
|
|
200
|
-
tiles = []
|
|
201
|
-
y_start = 0
|
|
202
|
-
while y_start < orig_h:
|
|
203
|
-
y_end = min(y_start + im_size, orig_h)
|
|
204
|
-
# If last tile doesn't cover the bottom, adjust
|
|
205
|
-
if y_end == orig_h and y_start > 0:
|
|
206
|
-
y_start = max(0, orig_h - im_size)
|
|
207
|
-
y_end = orig_h
|
|
208
|
-
x_start = 0
|
|
209
|
-
while x_start < orig_w:
|
|
210
|
-
x_end = min(x_start + im_size, orig_w)
|
|
211
|
-
# If last tile doesn't cover the right, adjust
|
|
212
|
-
if x_end == orig_w and x_start > 0:
|
|
213
|
-
x_start = max(0, orig_w - im_size)
|
|
214
|
-
x_end = orig_w
|
|
215
|
-
tiles.append((x_start, y_start, x_end, y_end))
|
|
216
|
-
x_start += stride
|
|
217
|
-
if x_end == orig_w:
|
|
218
|
-
break
|
|
219
|
-
y_start += stride
|
|
220
|
-
if y_end == orig_h:
|
|
221
|
-
break
|
|
222
|
-
# Predict each tile and collect detections
|
|
223
|
-
all_detections = []
|
|
224
|
-
for x_start, y_start, x_end, y_end in tiles:
|
|
225
|
-
tile_img = img_np[y_start:y_end, x_start:x_end]
|
|
226
|
-
# Pad tile if smaller than im_size
|
|
227
|
-
if tile_img.shape[0] < im_size or tile_img.shape[1] < im_size:
|
|
228
|
-
pad_h = im_size - tile_img.shape[0]
|
|
229
|
-
pad_w = im_size - tile_img.shape[1]
|
|
230
|
-
tile_img = np.pad(tile_img, ((0, pad_h), (0, pad_w), (0, 0)), mode='constant', constant_values=0)
|
|
231
|
-
# Run inference on tile
|
|
232
|
-
try:
|
|
233
|
-
tile_outputs, _, _ = self.predict_image(tile_img, im_size=im_size)
|
|
234
|
-
tile_detections = tile_outputs[0]
|
|
235
|
-
# Map coordinates back to original image
|
|
236
|
-
for det in tile_detections:
|
|
237
|
-
cat_id, score = det[0], det[1]
|
|
238
|
-
xmin, ymin, xmax, ymax = det[2], det[3], det[4], det[5]
|
|
239
|
-
# Scale from model resolution to tile resolution
|
|
240
|
-
tile_h, tile_w = y_end - y_start, x_end - x_start
|
|
241
|
-
xmin_tile = xmin * tile_w / im_size
|
|
242
|
-
ymin_tile = ymin * tile_h / im_size
|
|
243
|
-
xmax_tile = xmax * tile_w / im_size
|
|
244
|
-
ymax_tile = ymax * tile_h / im_size
|
|
245
|
-
# Translate to original image coordinates
|
|
246
|
-
xmin_orig = xmin_tile + x_start
|
|
247
|
-
ymin_orig = ymin_tile + y_start
|
|
248
|
-
xmax_orig = xmax_tile + x_start
|
|
249
|
-
ymax_orig = ymax_tile + y_start
|
|
250
|
-
# Clip to image bounds
|
|
251
|
-
xmin_orig = max(0, min(xmin_orig, orig_w))
|
|
252
|
-
ymin_orig = max(0, min(ymin_orig, orig_h))
|
|
253
|
-
xmax_orig = max(0, min(xmax_orig, orig_w))
|
|
254
|
-
ymax_orig = max(0, min(ymax_orig, orig_h))
|
|
255
|
-
all_detections.append([cat_id, score, xmin_orig, ymin_orig, xmax_orig, ymax_orig])
|
|
256
|
-
except Exception as e:
|
|
257
|
-
print(f"Error processing tile {(x_start, y_start, x_end, y_end)}: {e}")
|
|
258
|
-
continue
|
|
259
|
-
# Apply NMS to merge duplicate detections
|
|
260
|
-
merged_detections = self._nms_detections(all_detections, iou_threshold=nms_iou)
|
|
261
|
-
|
|
262
|
-
# Extract regions using the merged detections
|
|
263
|
-
if isinstance(img, bytes):
|
|
264
|
-
pil_img = Image.open(BytesIO(img)).convert("RGB")
|
|
265
|
-
elif isinstance(img, Image.Image):
|
|
266
|
-
pil_img = img.convert("RGB")
|
|
267
|
-
elif isinstance(img, np.ndarray):
|
|
268
|
-
pil_img = Image.fromarray(img_np.astype('uint8')).convert("RGB")
|
|
269
|
-
else:
|
|
270
|
-
raise ValueError("Unsupported image type")
|
|
271
|
-
|
|
272
|
-
# Create a dummy scale_factor (1:1 since we're already in original coordinates)
|
|
273
|
-
scale_factor = np.array([[1.0, 1.0]], dtype=np.float32)
|
|
274
|
-
extractor = ClassRegionBase64ExtractorPIL(class_names or [], target_classes=target_classes, threshold=threshold)
|
|
275
|
-
extracted = extractor.extract(pil_img, merged_detections, scale_factor=scale_factor, im_size=orig_h)
|
|
276
|
-
return extracted, merged_detections
|
|
File without changes
|
|
File without changes
|
|
File without changes
|