gshield-pic-sdk 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: gshield-pic-sdk
3
+ Version: 0.1.0
4
+ Summary: Unified SDK for Face detection, Faiss vector match, and Triton gRPC inference
5
+ Author-email: Gentel <jiaweiye@gentel.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/your-org/your-repo
8
+ Project-URL: Repository, https://github.com/your-org/your-repo
9
+ Project-URL: Documentation, https://github.com/your-org/your-repo#readme
10
+ Keywords: face-detection,faiss,triton,inference,sdk
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering :: Image Recognition
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: requests>=2.28.0
25
+ Requires-Dist: numpy>=1.20.0
26
+ Requires-Dist: Pillow>=9.0.0
27
+ Requires-Dist: opencv-python-headless>=4.5.0
28
+ Requires-Dist: tritonclient[grpc]>=2.30.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0; extra == "dev"
31
+ Requires-Dist: responses>=0.23.0; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # GshieldPicSDK
35
+
36
+ 统一调用 **Face 检测**、**Faiss 向量匹配**、**Triton gRPC 推理** 的 Python SDK。所有服务地址由用户提供,SDK 内不写死默认 URL。
37
+
38
+ ## 安装
39
+
40
+ **从 PyPI 安装(发布后):**
41
+
42
+ ```bash
43
+ pip install gshield-pic-sdk
44
+ ```
45
+
46
+ **从本地源码安装(开发或未发布时):**
47
+
48
+ ```bash
49
+ pip install -e api_server/GshieldPicSDK
50
+ ```
51
+
52
+ 依赖:`requests`, `numpy`, `Pillow`, `opencv-python-headless`, `tritonclient[grpc]`。
53
+
54
+ **发布到 PyPI**:参见 [docs/PYPI_PUBLISHING.md](docs/PYPI_PUBLISHING.md)。
55
+
56
+ ## 配置说明
57
+
58
+ - **所有 URL 由用户提供**:`face_base_url`、`faiss_base_url`、`triton_url` 仅通过构造函数传入,或通过环境变量 `GSHIELD_FACE_URL`、`GSHIELD_FAISS_URL`、`GSHIELD_TRITON_URL` 指定;构造函数参数优先。
59
+ - **gRPC 标签目录**:调用 Triton 推理时必须提供 `label_dir`(存放各模型对应的 `.txt` 标签文件),也可通过环境变量 `GSHIELD_LABEL_DIR` 指定。
60
+ - **mode 快速配置**:gRPC 支持的模型列表(mode)、标签文件映射、精度配置集中在 `gshield_pic_sdk.grpc_config` 中;新增/修改 mode 时只需改该模块或通过 `get_grpc_config(custom_label_map=..., custom_data_types=..., custom_modes_list=...)` 覆盖。
61
+
62
+ ## 使用示例
63
+
64
+ ```python
65
+ from gshield_pic_sdk import GshieldPicClient
66
+
67
+ # 按需传入各服务地址与标签目录(无默认写死地址)
68
+ client = GshieldPicClient(
69
+ face_base_url="http://localhost:8001",
70
+ faiss_base_url="http://localhost:8000",
71
+ triton_url="localhost:8501",
72
+ label_dir="/path/to/model_inference/labels",
73
+ )
74
+
75
+ # 图片支持:文件路径(str)、bytes、numpy ndarray (BGR/RGB 均可,SDK 内部统一处理)
76
+ image_path = "/path/to/photo.jpg"
77
+
78
+ # 人脸检测
79
+ resp = client.face.detect(image_path)
80
+ print(resp["faces"], resp["count"])
81
+
82
+ # Faiss 向量匹配
83
+ match_result = client.faiss.match(image_path)
84
+ print(match_result["allowed"], match_result["in_blacklist"], match_result["in_whitelist"])
85
+
86
+ # 黑名单/白名单 增删
87
+ # client.faiss.blacklist_add(image_path, description="样例")
88
+ # client.faiss.whitelist_add(image_path, description="允许")
89
+
90
+ # Triton 单模型推理
91
+ label = client.grpc.infer("fire", image_path)
92
+ print(label)
93
+
94
+ # 全量 mode 推理
95
+ all_labels = client.grpc.infer_all(image_path)
96
+ print(all_labels)
97
+ ```
98
+
99
+ 仅使用部分能力时,只传对应 URL 即可;未配置的客户端在访问时会抛出 `GshieldValueError`(如 `face_base_url not configured`)。
100
+
101
+ ## 图片输入
102
+
103
+ - 支持 **str**(文件路径)、**bytes**、**numpy ndarray**(HWC,BGR 或 RGB 均可,SDK 内部会统一为 Triton 所需格式)。
104
+
105
+ ## 异常
106
+
107
+ - `GshieldConnectionError`:网络或服务连接失败(HTTP 4xx/5xx、Triton 不可用等)。
108
+ - `GshieldValueError`:参数错误(未配置 URL、mode 不存在、图片格式无效等)。
109
+ - `GshieldInferError`:推理过程失败。
110
+
111
+ ## gRPC mode 列表与配置
112
+
113
+ 与现有 `client_test_grpc` 行为一致,支持的 mode 包括:fire, politics, qrcode, prohibitlogo, chinamap, discomfort, discomfort_excrement, gamble, tattoo, tiananmen, trypophobia, blood, military, flag_detect, flag_classification, porn_classification, nsfw, emblem, religion_symbol, religion_activity, religion_clothes, media1, media2, porn, flag 等。完整列表与标签/精度配置见 `gshield_pic_sdk.grpc_config`;后续更新 mode 只需改该处或通过 `get_grpc_config(custom_*)` 覆盖。
@@ -0,0 +1,80 @@
1
+ # GshieldPicSDK
2
+
3
+ 统一调用 **Face 检测**、**Faiss 向量匹配**、**Triton gRPC 推理** 的 Python SDK。所有服务地址由用户提供,SDK 内不写死默认 URL。
4
+
5
+ ## 安装
6
+
7
+ **从 PyPI 安装(发布后):**
8
+
9
+ ```bash
10
+ pip install gshield-pic-sdk
11
+ ```
12
+
13
+ **从本地源码安装(开发或未发布时):**
14
+
15
+ ```bash
16
+ pip install -e api_server/GshieldPicSDK
17
+ ```
18
+
19
+ 依赖:`requests`, `numpy`, `Pillow`, `opencv-python-headless`, `tritonclient[grpc]`。
20
+
21
+ **发布到 PyPI**:参见 [docs/PYPI_PUBLISHING.md](docs/PYPI_PUBLISHING.md)。
22
+
23
+ ## 配置说明
24
+
25
+ - **所有 URL 由用户提供**:`face_base_url`、`faiss_base_url`、`triton_url` 仅通过构造函数传入,或通过环境变量 `GSHIELD_FACE_URL`、`GSHIELD_FAISS_URL`、`GSHIELD_TRITON_URL` 指定;构造函数参数优先。
26
+ - **gRPC 标签目录**:调用 Triton 推理时必须提供 `label_dir`(存放各模型对应的 `.txt` 标签文件),也可通过环境变量 `GSHIELD_LABEL_DIR` 指定。
27
+ - **mode 快速配置**:gRPC 支持的模型列表(mode)、标签文件映射、精度配置集中在 `gshield_pic_sdk.grpc_config` 中;新增/修改 mode 时只需改该模块或通过 `get_grpc_config(custom_label_map=..., custom_data_types=..., custom_modes_list=...)` 覆盖。
28
+
29
+ ## 使用示例
30
+
31
+ ```python
32
+ from gshield_pic_sdk import GshieldPicClient
33
+
34
+ # 按需传入各服务地址与标签目录(无默认写死地址)
35
+ client = GshieldPicClient(
36
+ face_base_url="http://localhost:8001",
37
+ faiss_base_url="http://localhost:8000",
38
+ triton_url="localhost:8501",
39
+ label_dir="/path/to/model_inference/labels",
40
+ )
41
+
42
+ # 图片支持:文件路径(str)、bytes、numpy ndarray (BGR/RGB 均可,SDK 内部统一处理)
43
+ image_path = "/path/to/photo.jpg"
44
+
45
+ # 人脸检测
46
+ resp = client.face.detect(image_path)
47
+ print(resp["faces"], resp["count"])
48
+
49
+ # Faiss 向量匹配
50
+ match_result = client.faiss.match(image_path)
51
+ print(match_result["allowed"], match_result["in_blacklist"], match_result["in_whitelist"])
52
+
53
+ # 黑名单/白名单 增删
54
+ # client.faiss.blacklist_add(image_path, description="样例")
55
+ # client.faiss.whitelist_add(image_path, description="允许")
56
+
57
+ # Triton 单模型推理
58
+ label = client.grpc.infer("fire", image_path)
59
+ print(label)
60
+
61
+ # 全量 mode 推理
62
+ all_labels = client.grpc.infer_all(image_path)
63
+ print(all_labels)
64
+ ```
65
+
66
+ 仅使用部分能力时,只传对应 URL 即可;未配置的客户端在访问时会抛出 `GshieldValueError`(如 `face_base_url not configured`)。
67
+
68
+ ## 图片输入
69
+
70
+ - 支持 **str**(文件路径)、**bytes**、**numpy ndarray**(HWC,BGR 或 RGB 均可,SDK 内部会统一为 Triton 所需格式)。
71
+
72
+ ## 异常
73
+
74
+ - `GshieldConnectionError`:网络或服务连接失败(HTTP 4xx/5xx、Triton 不可用等)。
75
+ - `GshieldValueError`:参数错误(未配置 URL、mode 不存在、图片格式无效等)。
76
+ - `GshieldInferError`:推理过程失败。
77
+
78
+ ## gRPC mode 列表与配置
79
+
80
+ 与现有 `client_test_grpc` 行为一致,支持的 mode 包括:fire, politics, qrcode, prohibitlogo, chinamap, discomfort, discomfort_excrement, gamble, tattoo, tiananmen, trypophobia, blood, military, flag_detect, flag_classification, porn_classification, nsfw, emblem, religion_symbol, religion_activity, religion_clothes, media1, media2, porn, flag 等。完整列表与标签/精度配置见 `gshield_pic_sdk.grpc_config`;后续更新 mode 只需改该处或通过 `get_grpc_config(custom_*)` 覆盖。
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "gshield-pic-sdk"
7
+ version = "0.1.0"
8
+ description = "Unified SDK for Face detection, Faiss vector match, and Triton gRPC inference"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.8"
12
+ authors = [
13
+ {name = "Gentel", email = "jiaweiye@gentel.com"}
14
+ ]
15
+ keywords = ["face-detection", "faiss", "triton", "inference", "sdk"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.8",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Topic :: Scientific/Engineering :: Image Recognition",
27
+ ]
28
+ dependencies = [
29
+ "requests>=2.28.0",
30
+ "numpy>=1.20.0",
31
+ "Pillow>=9.0.0",
32
+ "opencv-python-headless>=4.5.0",
33
+ "tritonclient[grpc]>=2.30.0",
34
+ ]
35
+
36
+ [project.optional-dependencies]
37
+ dev = ["pytest>=7.0", "responses>=0.23.0"]
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/your-org/your-repo"
41
+ Repository = "https://github.com/your-org/your-repo"
42
+ Documentation = "https://github.com/your-org/your-repo#readme"
43
+
44
+ [tool.setuptools.packages.find]
45
+ where = ["src"]
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]
49
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,24 @@
1
+ # -*- coding: utf-8 -*-
2
+ """GshieldPicSDK: 统一调用 Face、Faiss、Triton gRPC 的 Python SDK。"""
3
+
4
+ from gshield_pic_sdk.client import GshieldPicClient
5
+ from gshield_pic_sdk.face_client import FaceClient
6
+ from gshield_pic_sdk.faiss_client import FaissClient
7
+ from gshield_pic_sdk.grpc_client import GrpcInferClient
8
+ from gshield_pic_sdk.exceptions import (
9
+ GshieldError,
10
+ GshieldConnectionError,
11
+ GshieldValueError,
12
+ GshieldInferError,
13
+ )
14
+
15
+ __all__ = [
16
+ "GshieldPicClient",
17
+ "FaceClient",
18
+ "FaissClient",
19
+ "GrpcInferClient",
20
+ "GshieldError",
21
+ "GshieldConnectionError",
22
+ "GshieldValueError",
23
+ "GshieldInferError",
24
+ ]
@@ -0,0 +1,85 @@
1
+ # -*- coding: utf-8 -*-
2
+ """图片入参统一:path/bytes/ndarray 转为 HTTP 上传用 bytes 或 gRPC 用 RGB ndarray。"""
3
+ import os
4
+ from typing import Union
5
+
6
+ import cv2
7
+ import numpy as np
8
+
9
+ ImageInput = Union[str, bytes, np.ndarray]
10
+
11
+
12
+ def to_bytes(image: ImageInput) -> bytes:
13
+ """
14
+ 将图片转为 HTTP 上传用的 bytes。
15
+ :param image: 文件路径(str)、bytes、或 HWC BGR/RGB ndarray (uint8)
16
+ :return: JPEG 编码的 bytes
17
+ """
18
+ if isinstance(image, str):
19
+ if not os.path.isfile(image):
20
+ raise ValueError(f"image path not found: {image}")
21
+ with open(image, "rb") as f:
22
+ data = f.read()
23
+ if not data:
24
+ raise ValueError("image file is empty")
25
+ return data
26
+ if isinstance(image, bytes):
27
+ if not image:
28
+ raise ValueError("image bytes is empty")
29
+ return image
30
+ if isinstance(image, np.ndarray):
31
+ if image.size == 0:
32
+ raise ValueError("image ndarray is empty")
33
+ if image.ndim != 3 or image.shape[-1] != 3:
34
+ raise ValueError("image ndarray must be HWC with 3 channels")
35
+ # cv2.imencode 期望 BGR;若已是 RGB 则先转 BGR 再编码
36
+ if _is_likely_rgb(image):
37
+ image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
38
+ ok, buf = cv2.imencode(".jpg", image)
39
+ if not ok:
40
+ raise ValueError("failed to encode ndarray as jpeg")
41
+ return buf.tobytes()
42
+ raise ValueError(f"image must be str, bytes or ndarray, got {type(image)}")
43
+
44
+
45
+ def _is_likely_rgb(arr: np.ndarray) -> bool:
46
+ """启发式:若均值 R > B 则视为 RGB(通常自然图 R>=B)。"""
47
+ if arr.shape[-1] != 3:
48
+ return False
49
+ r, g, b = arr[..., 0].mean(), arr[..., 1].mean(), arr[..., 2].mean()
50
+ return r >= b
51
+
52
+
53
+ def to_rgb_ndarray(image: ImageInput) -> np.ndarray:
54
+ """
55
+ 将图片转为 HWC RGB uint8 ndarray,供 gRPC 预处理使用。
56
+ :param image: 文件路径(str)、bytes、或 HWC BGR/RGB ndarray
57
+ :return: HWC RGB uint8, contiguous
58
+ """
59
+ if isinstance(image, str):
60
+ if not os.path.isfile(image):
61
+ raise ValueError(f"image path not found: {image}")
62
+ img = cv2.imread(image)
63
+ if img is None:
64
+ raise ValueError(f"failed to read image: {image}")
65
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
66
+ return np.ascontiguousarray(img)
67
+ if isinstance(image, bytes):
68
+ if not image:
69
+ raise ValueError("image bytes is empty")
70
+ buf = np.frombuffer(image, dtype=np.uint8)
71
+ img = cv2.imdecode(buf, cv2.IMREAD_COLOR)
72
+ if img is None:
73
+ raise ValueError("failed to decode image bytes")
74
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
75
+ return np.ascontiguousarray(img)
76
+ if isinstance(image, np.ndarray):
77
+ if image.size == 0 or image.ndim != 3 or image.shape[-1] != 3:
78
+ raise ValueError("image ndarray must be HWC with 3 channels")
79
+ # 约定:ndarray 按 OpenCV 惯例视为 BGR,统一转为 RGB
80
+ out = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
81
+ out = np.ascontiguousarray(out)
82
+ if out.dtype != np.uint8:
83
+ out = out.astype(np.uint8)
84
+ return out
85
+ raise ValueError(f"image must be str, bytes or ndarray, got {type(image)}")
@@ -0,0 +1,63 @@
1
+ # -*- coding: utf-8 -*-
2
+ """统一入口:Face、Faiss、gRPC 子客户端组装。"""
3
+ import os
4
+ from typing import Optional
5
+
6
+ from gshield_pic_sdk.exceptions import GshieldValueError
7
+ from gshield_pic_sdk.face_client import FaceClient
8
+ from gshield_pic_sdk.faiss_client import FaissClient
9
+ from gshield_pic_sdk.grpc_client import GrpcInferClient
10
+ from gshield_pic_sdk.grpc_config import get_grpc_config
11
+
12
+
13
+ def _env(key: str) -> Optional[str]:
14
+ v = os.environ.get(key, "").strip()
15
+ return v or None
16
+
17
+
18
+ class GshieldPicClient:
19
+ """
20
+ 统一客户端:所有 URL 由用户传入(或从环境变量读取),不写死默认地址。
21
+ 仅配置了的服务才有对应子客户端,未配置时访问会报错。
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ face_base_url: Optional[str] = None,
27
+ faiss_base_url: Optional[str] = None,
28
+ triton_url: Optional[str] = None,
29
+ label_dir: Optional[str] = None,
30
+ grpc_config: Optional[dict] = None,
31
+ ):
32
+ face_base_url = face_base_url or _env("GSHIELD_FACE_URL")
33
+ faiss_base_url = faiss_base_url or _env("GSHIELD_FAISS_URL")
34
+ triton_url = triton_url or _env("GSHIELD_TRITON_URL")
35
+ label_dir = label_dir or _env("GSHIELD_LABEL_DIR")
36
+
37
+ self._face = FaceClient(face_base_url) if face_base_url else None
38
+ self._faiss = FaissClient(faiss_base_url) if faiss_base_url else None
39
+ self._grpc = None
40
+ if triton_url and label_dir:
41
+ self._grpc = GrpcInferClient(
42
+ triton_url,
43
+ label_dir=label_dir,
44
+ grpc_config=grpc_config or get_grpc_config(),
45
+ )
46
+
47
+ @property
48
+ def face(self) -> FaceClient:
49
+ if self._face is None:
50
+ raise GshieldValueError("face_base_url not configured")
51
+ return self._face
52
+
53
+ @property
54
+ def faiss(self) -> FaissClient:
55
+ if self._faiss is None:
56
+ raise GshieldValueError("faiss_base_url not configured")
57
+ return self._faiss
58
+
59
+ @property
60
+ def grpc(self) -> GrpcInferClient:
61
+ if self._grpc is None:
62
+ raise GshieldValueError("triton_url or label_dir not configured")
63
+ return self._grpc
@@ -0,0 +1,18 @@
1
+ # -*- coding: utf-8 -*-
2
+ """SDK 自定义异常。"""
3
+
4
+
5
+ class GshieldError(Exception):
6
+ """Base exception for GshieldPicSDK."""
7
+
8
+
9
+ class GshieldConnectionError(GshieldError):
10
+ """网络或服务连接失败(HTTP 4xx/5xx、gRPC 连接失败等)。"""
11
+
12
+
13
+ class GshieldValueError(GshieldError, ValueError):
14
+ """参数错误(如未配置 URL、mode 不存在、图片格式无效)。"""
15
+
16
+
17
+ class GshieldInferError(GshieldError):
18
+ """推理失败(Triton 推理异常等)。"""
@@ -0,0 +1,43 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Face 检测 HTTP 客户端。"""
3
+ from typing import Union
4
+
5
+ import requests
6
+
7
+ from gshield_pic_sdk._image import to_bytes
8
+ from gshield_pic_sdk.exceptions import GshieldConnectionError
9
+
10
+ ImageInput = Union[str, bytes, "np.ndarray"]
11
+
12
+
13
+ class FaceClient:
14
+ """人脸检测服务客户端,调用 face 服务的 POST /detect。"""
15
+
16
+ def __init__(self, face_base_url: str, timeout: int = 30):
17
+ self.base_url = face_base_url.rstrip("/")
18
+ self.timeout = timeout
19
+
20
+ def detect(self, image: ImageInput) -> dict:
21
+ """
22
+ 上传图片做人脸检测与识别。
23
+ :param image: 图片路径(str)、bytes 或 ndarray
24
+ :return: {"faces": [...], "count": N}
25
+ """
26
+ data = to_bytes(image)
27
+ url = f"{self.base_url}/detect"
28
+ try:
29
+ r = requests.post(
30
+ url,
31
+ files={"image": ("image.jpg", data, "image/jpeg")},
32
+ timeout=self.timeout,
33
+ )
34
+ except requests.RequestException as e:
35
+ raise GshieldConnectionError(f"face detect request failed: {e}") from e
36
+ if r.status_code >= 400:
37
+ raise GshieldConnectionError(
38
+ f"face detect returned {r.status_code}: {r.text}"
39
+ )
40
+ out = r.json()
41
+ if "faces" not in out or "count" not in out:
42
+ raise GshieldConnectionError(f"unexpected face response: {out}")
43
+ return out
@@ -0,0 +1,81 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Faiss 向量匹配 HTTP 客户端。"""
3
+ from typing import Union
4
+
5
+ import requests
6
+
7
+ from gshield_pic_sdk._image import to_bytes
8
+ from gshield_pic_sdk.exceptions import GshieldConnectionError
9
+
10
+ ImageInput = Union[str, bytes, "np.ndarray"]
11
+
12
+
13
+ class FaissClient:
14
+ """Faiss 向量库匹配服务客户端。"""
15
+
16
+ def __init__(self, faiss_base_url: str, timeout: int = 30):
17
+ self.base_url = faiss_base_url.rstrip("/")
18
+ self.timeout = timeout
19
+
20
+ def _post_image(self, path: str, image: ImageInput, description: str = "") -> dict:
21
+ data = to_bytes(image)
22
+ files = {"image": ("image.jpg", data, "image/jpeg")}
23
+ payload = {"description": description} if description else {}
24
+ try:
25
+ r = requests.post(
26
+ f"{self.base_url}{path}",
27
+ files=files,
28
+ data=payload,
29
+ timeout=self.timeout,
30
+ )
31
+ except requests.RequestException as e:
32
+ raise GshieldConnectionError(f"faiss request failed: {e}") from e
33
+ if r.status_code >= 400:
34
+ raise GshieldConnectionError(f"faiss returned {r.status_code}: {r.text}")
35
+ return r.json()
36
+
37
+ def _get(self, path: str) -> dict:
38
+ try:
39
+ r = requests.get(f"{self.base_url}{path}", timeout=self.timeout)
40
+ except requests.RequestException as e:
41
+ raise GshieldConnectionError(f"faiss request failed: {e}") from e
42
+ if r.status_code >= 400:
43
+ raise GshieldConnectionError(f"faiss returned {r.status_code}: {r.text}")
44
+ return r.json()
45
+
46
+ def _delete(self, path: str) -> dict:
47
+ try:
48
+ r = requests.delete(f"{self.base_url}{path}", timeout=self.timeout)
49
+ except requests.RequestException as e:
50
+ raise GshieldConnectionError(f"faiss request failed: {e}") from e
51
+ if r.status_code >= 400:
52
+ raise GshieldConnectionError(f"faiss returned {r.status_code}: {r.text}")
53
+ return r.json() if r.content else {}
54
+
55
+ def match(self, image: ImageInput) -> dict:
56
+ """向量匹配,返回 allowed / in_whitelist / in_blacklist 等。"""
57
+ return self._post_image("/match", image)
58
+
59
+ def blacklist_add(self, image: ImageInput, description: str = "") -> dict:
60
+ """添加黑名单,返回 {id, message}。"""
61
+ return self._post_image("/blacklist/add", image, description)
62
+
63
+ def blacklist_remove(self, image_id: int) -> dict:
64
+ """删除黑名单条目。"""
65
+ return self._delete(f"/blacklist/{image_id}")
66
+
67
+ def blacklist_list(self) -> dict:
68
+ """列出黑名单。"""
69
+ return self._get("/blacklist")
70
+
71
+ def whitelist_add(self, image: ImageInput, description: str = "") -> dict:
72
+ """添加白名单。"""
73
+ return self._post_image("/whitelist/add", image, description)
74
+
75
+ def whitelist_remove(self, image_id: int) -> dict:
76
+ """删除白名单条目。"""
77
+ return self._delete(f"/whitelist/{image_id}")
78
+
79
+ def whitelist_list(self) -> dict:
80
+ """列出白名单。"""
81
+ return self._get("/whitelist")
@@ -0,0 +1,24 @@
1
+ # -*- coding: utf-8 -*-
2
+ from gshield_pic_sdk.grpc.inference import (
3
+ load_labels,
4
+ preprocess_image_raw,
5
+ preprocess_numpy,
6
+ infer_classification,
7
+ infer_yolo,
8
+ infer_trt_nms_yolo,
9
+ infer_porn,
10
+ infer_flag,
11
+ run_task_with_client,
12
+ )
13
+
14
+ __all__ = [
15
+ "load_labels",
16
+ "preprocess_image_raw",
17
+ "preprocess_numpy",
18
+ "infer_classification",
19
+ "infer_yolo",
20
+ "infer_trt_nms_yolo",
21
+ "infer_porn",
22
+ "infer_flag",
23
+ "run_task_with_client",
24
+ ]