niqun123123 commited on
Commit
7d0de61
·
verified ·
1 Parent(s): 1d55417

Upload local_images.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. local_images.py +97 -0
local_images.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Versioned, bounded local image admission. Never opens a URL or unregistered path."""
2
+ import hashlib
3
+ import io
4
+ from pathlib import Path
5
+ import re
6
+ import stat
7
+ import warnings
8
+ from PIL import Image
9
+
10
+ SCHEMA = 'lemonjev-local-images-v1'
11
+ POLICY = 'registered-local-v1'
12
+ MAX_BYTES = 32 * 1024 * 1024
13
+ MAX_PIXELS = 16_000_000
14
+ MAX_IMAGES = 8
15
+ IMAGE_SIZE = {'shortest_edge': 65536, 'longest_edge': 262144}
16
+
17
+
18
+ def asset_record(record):
19
+ if not isinstance(record, dict) or set(record) != {'path', 'sha256', 'bytes'}:
20
+ raise ValueError('Image assets require exactly path, sha256, bytes')
21
+ path = record['path']
22
+ if not isinstance(path, str) or not Path(path).is_absolute() or str(Path(path)) != path or '..' in Path(path).parts:
23
+ raise ValueError('Image path must be a canonical absolute local path')
24
+ if not isinstance(record['sha256'], str) or not re.fullmatch('[0-9a-f]{64}', record['sha256']):
25
+ raise ValueError('Invalid image SHA256')
26
+ if type(record['bytes']) is not int or not 0 < record['bytes'] <= MAX_BYTES:
27
+ raise ValueError('Image byte bound exceeded')
28
+ return dict(record)
29
+
30
+
31
+ class LocalImageRegistry:
32
+ def __init__(self, manifest):
33
+ if not isinstance(manifest, dict) or set(manifest) != {'schema', 'images'} or manifest['schema'] != SCHEMA:
34
+ raise ValueError('Missing versioned local_images manifest')
35
+ if not isinstance(manifest['images'], list):
36
+ raise ValueError('Image registry must be a list')
37
+ self.assets = {}
38
+ for value in manifest['images']:
39
+ record = asset_record(value)
40
+ if record['path'] in self.assets:
41
+ raise ValueError('Duplicate registered image path')
42
+ self.assets[record['path']] = record
43
+
44
+ def records(self, row):
45
+ paths = row.get('images', [])
46
+ records = row.get('source', {}).get('image_assets', [])
47
+ if not isinstance(paths, list) or len(paths) > MAX_IMAGES or not isinstance(records, list) or len(paths) != len(records):
48
+ raise ValueError('Images/image_assets must correspond one-to-one within image limit')
49
+ result = []
50
+ for path, value in zip(paths, records, strict=True):
51
+ record = asset_record(value)
52
+ if not isinstance(path, str) or path != record['path'] or self.assets.get(path) != record:
53
+ raise ValueError('Unregistered or mismatched row image asset')
54
+ result.append(record)
55
+ return result
56
+
57
+ def decode(self, record):
58
+ record = asset_record(record)
59
+ if self.assets.get(record['path']) != record:
60
+ raise ValueError('Unregistered image')
61
+ path = Path(record['path'])
62
+ # No symlinks, special devices or unbounded streams. Decode these verified
63
+ # bytes, not a second path open, so a replacement cannot evade the hash.
64
+ if path.is_symlink() or any(p.is_symlink() for p in path.parents):
65
+ raise ValueError('Image symlinks are not allowed')
66
+ import os
67
+ fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)
68
+ with os.fdopen(fd, 'rb') as stream:
69
+ info = os.fstat(stream.fileno())
70
+ if not stat.S_ISREG(info.st_mode) or info.st_size != record['bytes']:
71
+ raise ValueError('Image size/type mismatch')
72
+ data = stream.read(MAX_BYTES + 1)
73
+ if len(data) != record['bytes'] or hashlib.sha256(data).hexdigest() != record['sha256']:
74
+ raise ValueError('Image size/hash mismatch')
75
+ try:
76
+ with warnings.catch_warnings():
77
+ warnings.simplefilter('error', Image.DecompressionBombWarning)
78
+ with Image.open(io.BytesIO(data)) as image:
79
+ if image.format not in {'PNG', 'JPEG', 'WEBP'} or image.width * image.height > MAX_PIXELS or getattr(image, 'n_frames', 1) != 1:
80
+ raise ValueError('Unsupported or oversized image')
81
+ image.verify()
82
+ with Image.open(io.BytesIO(data)) as image:
83
+ image.load()
84
+ return image.convert('RGB')
85
+ except (OSError, SyntaxError, Image.DecompressionBombWarning, Image.DecompressionBombError) as exc:
86
+ raise ValueError('Corrupt or oversized registered image') from exc
87
+
88
+ def decode_row(self, row):
89
+ return [self.decode(record) for record in self.records(row)]
90
+
91
+
92
+ def configure_local_images(processor, registry):
93
+ if not hasattr(processor, 'image_processor'):
94
+ raise ValueError('Registered local images require a multimodal processor')
95
+ processor.image_processor.size = dict(IMAGE_SIZE)
96
+ processor.local_image_registry = registry
97
+ return processor