dgc-image-embedding 1.0.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,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: dgc_image_embedding
3
+ Version: 1.0.0
4
+ Author-email: velopace <velopace7@gmail.com>
5
+ License: MIT
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: pillow==12.2.0
8
+ Requires-Dist: torch==2.12.0
9
+ Requires-Dist: torchvision==0.27.0
10
+ Requires-Dist: open-clip-torch==3.3.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: build>=1.3.0; extra == "dev"
13
+ Requires-Dist: coverage>=7.10.2; extra == "dev"
14
+ Requires-Dist: pytest>=8.4.1; extra == "dev"
15
+ Requires-Dist: pytest-cov>=6.2.1; extra == "dev"
16
+ Requires-Dist: twine>=6.1.0; extra == "dev"
17
+ Requires-Dist: toml>=0.10.2; extra == "dev"
@@ -0,0 +1,13 @@
1
+ from .embedding import embedding_metadata, generate_embedding, generate_embeddings_batch, get_device, get_model
2
+ from .image import load_image_from_bytes, prepare_image_from_bytes, resize_and_pad
3
+
4
+ __all__ = [
5
+ "embedding_metadata",
6
+ "generate_embedding",
7
+ "generate_embeddings_batch",
8
+ "get_device",
9
+ "get_model",
10
+ "load_image_from_bytes",
11
+ "prepare_image_from_bytes",
12
+ "resize_and_pad",
13
+ ]
@@ -0,0 +1,13 @@
1
+ TARGET_IMAGE_SIZE = 224
2
+
3
+ MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024
4
+ MIN_SOURCE_SIDE = 100
5
+ MAX_SOURCE_SIDE = 3000
6
+
7
+ PAD_COLOR = (255, 255, 255)
8
+
9
+ MODEL_NAME = "ViT-B-32"
10
+ PRETRAINED = "laion2b_s34b_b79k"
11
+
12
+ CLIP_MEAN = (0.48145466, 0.4578275, 0.40821073)
13
+ CLIP_STD = (0.26862954, 0.26130258, 0.27577711)
@@ -0,0 +1,89 @@
1
+ import threading
2
+
3
+ import open_clip
4
+ import torch
5
+ from PIL import Image
6
+ from torchvision import transforms
7
+
8
+ from .config import (
9
+ CLIP_MEAN,
10
+ CLIP_STD,
11
+ MODEL_NAME,
12
+ PRETRAINED,
13
+ )
14
+
15
+
16
+ _model = None
17
+ _device = "cuda" if torch.cuda.is_available() else "cpu"
18
+ _model_lock = threading.Lock()
19
+
20
+
21
+ def get_device() -> str:
22
+ return _device
23
+
24
+
25
+ def get_model():
26
+ global _model
27
+
28
+ if _model is not None:
29
+ return _model
30
+
31
+ with _model_lock:
32
+ if _model is None:
33
+ model, _, _ = open_clip.create_model_and_transforms(
34
+ MODEL_NAME,
35
+ pretrained=PRETRAINED,
36
+ device=_device,
37
+ )
38
+ model.eval()
39
+ _model = model
40
+
41
+ return _model
42
+
43
+
44
+ def image_to_tensor(image: Image.Image) -> torch.Tensor:
45
+ transform = transforms.Compose(
46
+ [
47
+ transforms.ToTensor(),
48
+ transforms.Normalize(mean=CLIP_MEAN, std=CLIP_STD),
49
+ ]
50
+ )
51
+
52
+ return transform(image).unsqueeze(0)
53
+
54
+
55
+ def generate_embedding(processed_image: Image.Image) -> list[float]:
56
+ model = get_model()
57
+ tensor = image_to_tensor(processed_image).to(_device)
58
+
59
+ with torch.no_grad():
60
+ embedding = model.encode_image(tensor)
61
+ embedding = embedding / embedding.norm(dim=-1, keepdim=True)
62
+
63
+ return embedding.squeeze(0).cpu().tolist()
64
+
65
+
66
+ def generate_embeddings_batch(processed_images: list[Image.Image]) -> list[list[float]]:
67
+ if not processed_images:
68
+ return []
69
+
70
+ model = get_model()
71
+
72
+ tensors = torch.cat(
73
+ [image_to_tensor(image) for image in processed_images],
74
+ dim=0,
75
+ ).to(_device)
76
+
77
+ with torch.no_grad():
78
+ embeddings = model.encode_image(tensors)
79
+ embeddings = embeddings / embeddings.norm(dim=-1, keepdim=True)
80
+
81
+ return embeddings.cpu().tolist()
82
+
83
+
84
+ def embedding_metadata() -> dict:
85
+ return {
86
+ "modelName": MODEL_NAME,
87
+ "pretrained": PRETRAINED,
88
+ "device": _device,
89
+ }
@@ -0,0 +1,73 @@
1
+ from io import BytesIO
2
+
3
+ from PIL import Image, ImageOps, UnidentifiedImageError
4
+
5
+ from .config import (
6
+ MAX_FILE_SIZE_BYTES,
7
+ MAX_SOURCE_SIDE,
8
+ MIN_SOURCE_SIDE,
9
+ PAD_COLOR,
10
+ TARGET_IMAGE_SIZE,
11
+ )
12
+
13
+
14
+ def load_image_from_bytes(image_bytes: bytes) -> Image.Image:
15
+ if not image_bytes:
16
+ raise ValueError("Image file is empty")
17
+
18
+ if len(image_bytes) > MAX_FILE_SIZE_BYTES:
19
+ raise ValueError("Image file is too large")
20
+
21
+ try:
22
+ image = Image.open(BytesIO(image_bytes))
23
+ image = ImageOps.exif_transpose(image)
24
+ except UnidentifiedImageError as err:
25
+ raise ValueError("Invalid image file") from err
26
+
27
+ validate_image_size(image)
28
+
29
+ return image
30
+
31
+
32
+ def validate_image_size(image: Image.Image) -> None:
33
+ width, height = image.size
34
+
35
+ if width < MIN_SOURCE_SIDE or height < MIN_SOURCE_SIDE:
36
+ raise ValueError(f"Image is too small: {width}x{height}")
37
+
38
+ if width > MAX_SOURCE_SIDE or height > MAX_SOURCE_SIDE:
39
+ raise ValueError(f"Image is too large: {width}x{height}")
40
+
41
+
42
+ def resize_and_pad(
43
+ image: Image.Image,
44
+ target_size: int = TARGET_IMAGE_SIZE,
45
+ pad_color: tuple[int, int, int] = PAD_COLOR,
46
+ ) -> Image.Image:
47
+ image = ImageOps.exif_transpose(image)
48
+ image = image.convert("RGB")
49
+
50
+ width, height = image.size
51
+ scale = target_size / max(width, height)
52
+
53
+ new_width = max(1, round(width * scale))
54
+ new_height = max(1, round(height * scale))
55
+
56
+ resized = image.resize(
57
+ (new_width, new_height),
58
+ Image.Resampling.LANCZOS,
59
+ )
60
+
61
+ padded = Image.new("RGB", (target_size, target_size), pad_color)
62
+
63
+ paste_x = (target_size - new_width) // 2
64
+ paste_y = (target_size - new_height) // 2
65
+
66
+ padded.paste(resized, (paste_x, paste_y))
67
+
68
+ return padded
69
+
70
+
71
+ def prepare_image_from_bytes(image_bytes: bytes) -> Image.Image:
72
+ image = load_image_from_bytes(image_bytes)
73
+ return resize_and_pad(image)
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: dgc_image_embedding
3
+ Version: 1.0.0
4
+ Author-email: velopace <velopace7@gmail.com>
5
+ License: MIT
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: pillow==12.2.0
8
+ Requires-Dist: torch==2.12.0
9
+ Requires-Dist: torchvision==0.27.0
10
+ Requires-Dist: open-clip-torch==3.3.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: build>=1.3.0; extra == "dev"
13
+ Requires-Dist: coverage>=7.10.2; extra == "dev"
14
+ Requires-Dist: pytest>=8.4.1; extra == "dev"
15
+ Requires-Dist: pytest-cov>=6.2.1; extra == "dev"
16
+ Requires-Dist: twine>=6.1.0; extra == "dev"
17
+ Requires-Dist: toml>=0.10.2; extra == "dev"
@@ -0,0 +1,10 @@
1
+ pyproject.toml
2
+ dgc_image_embedding/__init__.py
3
+ dgc_image_embedding/config.py
4
+ dgc_image_embedding/embedding.py
5
+ dgc_image_embedding/image.py
6
+ dgc_image_embedding.egg-info/PKG-INFO
7
+ dgc_image_embedding.egg-info/SOURCES.txt
8
+ dgc_image_embedding.egg-info/dependency_links.txt
9
+ dgc_image_embedding.egg-info/requires.txt
10
+ dgc_image_embedding.egg-info/top_level.txt
@@ -0,0 +1,12 @@
1
+ pillow==12.2.0
2
+ torch==2.12.0
3
+ torchvision==0.27.0
4
+ open-clip-torch==3.3.0
5
+
6
+ [dev]
7
+ build>=1.3.0
8
+ coverage>=7.10.2
9
+ pytest>=8.4.1
10
+ pytest-cov>=6.2.1
11
+ twine>=6.1.0
12
+ toml>=0.10.2
@@ -0,0 +1 @@
1
+ dgc_image_embedding
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = [ "setuptools", "wheel",]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "dgc_image_embedding"
7
+ version = "1.0.0"
8
+ description = ""
9
+ readme = "README.md"
10
+ dependencies = [ "pillow==12.2.0", "torch==2.12.0", "torchvision==0.27.0", "open-clip-torch==3.3.0",]
11
+ [[project.authors]]
12
+ name = "velopace"
13
+ email = "velopace7@gmail.com"
14
+
15
+ [project.optional-dependencies]
16
+ dev = [ "build>=1.3.0", "coverage>=7.10.2", "pytest>=8.4.1", "pytest-cov>=6.2.1", "twine>=6.1.0", "toml>=0.10.2",]
17
+
18
+ [project.license]
19
+ text = "MIT"
20
+
21
+ [tool.setuptools.package-data]
22
+ dgc_image_embedding = [ "py.typed",]
23
+
24
+ [tool.pytest.ini_options]
25
+ testpaths = [ "tests",]
26
+
27
+ [tool.coverage.run]
28
+ branch = true
29
+ source = [ "dgc_image_embedding",]
30
+
31
+ [tool.coverage.report]
32
+ show_missing = true
33
+ skip_covered = true
34
+
35
+ [tool.setuptools.packages.find]
36
+ where = [ ".",]
37
+ include = [ "dgc_image_embedding*",]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+