hyper-models 0.3.2__tar.gz → 0.3.4__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.
- {hyper_models-0.3.2 → hyper_models-0.3.4}/PKG-INFO +14 -9
- {hyper_models-0.3.2 → hyper_models-0.3.4}/README.md +13 -8
- {hyper_models-0.3.2 → hyper_models-0.3.4}/pyproject.toml +1 -1
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/encoders.py +21 -4
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/hyper3_clip.py +7 -1
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper_models/models.py +17 -3
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper_models/preprocessing.py +13 -8
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper_models/registry.py +10 -2
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper_models/torch_models.py +45 -5
- {hyper_models-0.3.2 → hyper_models-0.3.4}/.gitignore +0 -0
- {hyper_models-0.3.2 → hyper_models-0.3.4}/LICENSE +0 -0
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/__init__.py +0 -0
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/__init__.py +0 -0
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/experimental.py +0 -0
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/himo.py +0 -0
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/lorentz.py +0 -0
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/losses.py +0 -0
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/objectives.py +0 -0
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/models/tren.py +0 -0
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/training/__init__.py +0 -0
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper3_clip/training/distributed.py +0 -0
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper_models/__init__.py +0 -0
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper_models/loader.py +0 -0
- {hyper_models-0.3.2 → hyper_models-0.3.4}/src/hyper_models/loaders.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: hyper-models
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: A model zoo for non-Euclidean embedding models (hyperbolic, spherical, product manifolds)
|
|
5
5
|
Project-URL: Homepage, https://github.com/Hyper3Labs/hyper-models
|
|
6
6
|
Project-URL: Repository, https://github.com/Hyper3Labs/hyper-models
|
|
@@ -96,7 +96,7 @@ hyper_models.list_loaders()
|
|
|
96
96
|
# Load model (auto-downloads from Hugging Face Hub)
|
|
97
97
|
model = hyper_models.load("hycoclip-vit-s")
|
|
98
98
|
model.geometry # 'hyperboloid'
|
|
99
|
-
model.dim
|
|
99
|
+
model.dim # 513
|
|
100
100
|
|
|
101
101
|
# Encode PIL images
|
|
102
102
|
images = [Image.open("image.jpg")]
|
|
@@ -104,9 +104,9 @@ embeddings = model.encode_images(images) # (1, 513) ndarray
|
|
|
104
104
|
|
|
105
105
|
# Get model info
|
|
106
106
|
info = hyper_models.get_model_info("hycoclip-vit-s")
|
|
107
|
-
info.hub_id
|
|
108
|
-
info.loader
|
|
109
|
-
info.license
|
|
107
|
+
info.hub_id # 'mnm-matin/hyperbolic-clip'
|
|
108
|
+
info.loader # 'onnx'
|
|
109
|
+
info.license # 'CC-BY-NC'
|
|
110
110
|
|
|
111
111
|
# Low-level: preprocess images yourself
|
|
112
112
|
batch = hyper_models.preprocess_images(images) # (B, 3, 224, 224)
|
|
@@ -136,6 +136,11 @@ model = hyper_models.load("hyper3-clip-v1")
|
|
|
136
136
|
Callers do not need to know which internal loader is used, except for optional
|
|
137
137
|
dependency installation when choosing entries that need `hyper-models[ml]`.
|
|
138
138
|
|
|
139
|
+
For `hyper3-clip-v1`, `encode_images(images)` and `encode_texts(texts)` return
|
|
140
|
+
513-coordinate Lorentz embeddings in the same space. The loader downloads the
|
|
141
|
+
model's runtime configuration, weights, and tokenizer together. Complete the
|
|
142
|
+
model's Hugging Face access form and run `hf auth login` before the first download.
|
|
143
|
+
|
|
139
144
|
### HyperView integration
|
|
140
145
|
|
|
141
146
|
HyperView auto-detects `hyper-models` names and routes them to the `hyper-models` provider.
|
|
@@ -144,10 +149,10 @@ HyperView auto-detects `hyper-models` names and routes them to the `hyper-models
|
|
|
144
149
|
import hyperview as hv
|
|
145
150
|
|
|
146
151
|
dataset = hv.Dataset.from_huggingface(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
name="demo",
|
|
153
|
+
hf_dataset="uoft-cs/cifar10",
|
|
154
|
+
split="train",
|
|
155
|
+
image_key="img",
|
|
151
156
|
)
|
|
152
157
|
|
|
153
158
|
# Uses provider='hyper-models' automatically.
|
|
@@ -55,7 +55,7 @@ hyper_models.list_loaders()
|
|
|
55
55
|
# Load model (auto-downloads from Hugging Face Hub)
|
|
56
56
|
model = hyper_models.load("hycoclip-vit-s")
|
|
57
57
|
model.geometry # 'hyperboloid'
|
|
58
|
-
model.dim
|
|
58
|
+
model.dim # 513
|
|
59
59
|
|
|
60
60
|
# Encode PIL images
|
|
61
61
|
images = [Image.open("image.jpg")]
|
|
@@ -63,9 +63,9 @@ embeddings = model.encode_images(images) # (1, 513) ndarray
|
|
|
63
63
|
|
|
64
64
|
# Get model info
|
|
65
65
|
info = hyper_models.get_model_info("hycoclip-vit-s")
|
|
66
|
-
info.hub_id
|
|
67
|
-
info.loader
|
|
68
|
-
info.license
|
|
66
|
+
info.hub_id # 'mnm-matin/hyperbolic-clip'
|
|
67
|
+
info.loader # 'onnx'
|
|
68
|
+
info.license # 'CC-BY-NC'
|
|
69
69
|
|
|
70
70
|
# Low-level: preprocess images yourself
|
|
71
71
|
batch = hyper_models.preprocess_images(images) # (B, 3, 224, 224)
|
|
@@ -95,6 +95,11 @@ model = hyper_models.load("hyper3-clip-v1")
|
|
|
95
95
|
Callers do not need to know which internal loader is used, except for optional
|
|
96
96
|
dependency installation when choosing entries that need `hyper-models[ml]`.
|
|
97
97
|
|
|
98
|
+
For `hyper3-clip-v1`, `encode_images(images)` and `encode_texts(texts)` return
|
|
99
|
+
513-coordinate Lorentz embeddings in the same space. The loader downloads the
|
|
100
|
+
model's runtime configuration, weights, and tokenizer together. Complete the
|
|
101
|
+
model's Hugging Face access form and run `hf auth login` before the first download.
|
|
102
|
+
|
|
98
103
|
### HyperView integration
|
|
99
104
|
|
|
100
105
|
HyperView auto-detects `hyper-models` names and routes them to the `hyper-models` provider.
|
|
@@ -103,10 +108,10 @@ HyperView auto-detects `hyper-models` names and routes them to the `hyper-models
|
|
|
103
108
|
import hyperview as hv
|
|
104
109
|
|
|
105
110
|
dataset = hv.Dataset.from_huggingface(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
name="demo",
|
|
112
|
+
hf_dataset="uoft-cs/cifar10",
|
|
113
|
+
split="train",
|
|
114
|
+
image_key="img",
|
|
110
115
|
)
|
|
111
116
|
|
|
112
117
|
# Uses provider='hyper-models' automatically.
|
|
@@ -10,6 +10,7 @@ from transformers import (
|
|
|
10
10
|
CLIPTextConfig,
|
|
11
11
|
CLIPTextModel,
|
|
12
12
|
CLIPTextModelWithProjection,
|
|
13
|
+
CLIPTokenizerFast,
|
|
13
14
|
CLIPVisionConfig,
|
|
14
15
|
CLIPVisionModel,
|
|
15
16
|
CLIPVisionModelWithProjection,
|
|
@@ -99,7 +100,14 @@ class VisionEncoder(nn.Module):
|
|
|
99
100
|
|
|
100
101
|
|
|
101
102
|
class TextEncoder(nn.Module):
|
|
102
|
-
def __init__(
|
|
103
|
+
def __init__(
|
|
104
|
+
self,
|
|
105
|
+
model_name: str,
|
|
106
|
+
pretrained: bool = True,
|
|
107
|
+
pooling: str = "auto",
|
|
108
|
+
text_config: dict | None = None,
|
|
109
|
+
tokenizer_name_or_path: str | None = None,
|
|
110
|
+
) -> None:
|
|
103
111
|
super().__init__()
|
|
104
112
|
if pooling not in {"auto", "pooler", "cls", "mean"}:
|
|
105
113
|
raise ValueError(
|
|
@@ -107,10 +115,19 @@ class TextEncoder(nn.Module):
|
|
|
107
115
|
)
|
|
108
116
|
self.kind = "hf_text"
|
|
109
117
|
self.pooling = pooling
|
|
110
|
-
tokenizer_name = model_name.removeprefix("hf_clip_projected:")
|
|
111
|
-
self.tokenizer =
|
|
118
|
+
tokenizer_name = tokenizer_name_or_path or model_name.removeprefix("hf_clip_projected:")
|
|
119
|
+
self.tokenizer = (
|
|
120
|
+
CLIPTokenizerFast.from_pretrained(tokenizer_name)
|
|
121
|
+
if text_config is not None
|
|
122
|
+
else AutoTokenizer.from_pretrained(tokenizer_name)
|
|
123
|
+
)
|
|
112
124
|
model_name_lower = model_name.lower()
|
|
113
|
-
if
|
|
125
|
+
if text_config is not None:
|
|
126
|
+
if pretrained or text_config.get("model_type") != "clip_text_model":
|
|
127
|
+
raise ValueError("Bundled text_config requires a non-pretrained CLIP text encoder")
|
|
128
|
+
self.backbone = CLIPTextModel(CLIPTextConfig.from_dict(text_config))
|
|
129
|
+
self.output_dim = self.backbone.config.hidden_size
|
|
130
|
+
elif model_name.startswith("hf_clip_projected:"):
|
|
114
131
|
self.kind = "hf_clip_projected"
|
|
115
132
|
projected_model_name = model_name.removeprefix("hf_clip_projected:")
|
|
116
133
|
if pretrained:
|
|
@@ -101,6 +101,8 @@ class Hyper3CLIP(ExperimentalObjectiveMixin, nn.Module):
|
|
|
101
101
|
fuse_beta_query_encoder_forwards: bool = False,
|
|
102
102
|
group_beta_query_pooling: bool = False,
|
|
103
103
|
objective_autocast_dtype: str = "float32",
|
|
104
|
+
text_config: dict | None = None,
|
|
105
|
+
tokenizer_name_or_path: str | None = None,
|
|
104
106
|
) -> None:
|
|
105
107
|
super().__init__()
|
|
106
108
|
if objective not in {"hycoclip", "uncha", "proclip"}:
|
|
@@ -206,7 +208,11 @@ class Hyper3CLIP(ExperimentalObjectiveMixin, nn.Module):
|
|
|
206
208
|
self.phyclip_num_factors = 0
|
|
207
209
|
self.vision_encoder = VisionEncoder(vision_backbone, pretrained=vision_pretrained)
|
|
208
210
|
self.text_encoder = TextEncoder(
|
|
209
|
-
text_model_name,
|
|
211
|
+
text_model_name,
|
|
212
|
+
pretrained=text_pretrained,
|
|
213
|
+
pooling=text_pooling,
|
|
214
|
+
text_config=text_config,
|
|
215
|
+
tokenizer_name_or_path=tokenizer_name_or_path,
|
|
210
216
|
)
|
|
211
217
|
self.tokenizer = self.text_encoder.tokenizer
|
|
212
218
|
self.embed_dim = embed_dim
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
|
+
from shutil import copyfile
|
|
7
|
+
from tempfile import TemporaryDirectory
|
|
6
8
|
|
|
7
9
|
import numpy as np
|
|
8
10
|
from PIL import Image
|
|
@@ -32,14 +34,26 @@ class ONNXModel:
|
|
|
32
34
|
self._output_name = output_name
|
|
33
35
|
self._image_config = image_config or ImageConfig()
|
|
34
36
|
self._session = None
|
|
37
|
+
self._artifact_directory = None
|
|
35
38
|
|
|
36
39
|
def _ensure_session(self) -> None:
|
|
37
40
|
if self._session is None:
|
|
38
41
|
import onnxruntime as ort
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
path = self._path
|
|
44
|
+
# Hub snapshots link the graph and external weights to different
|
|
45
|
+
# blob directories. New ONNX Runtime versions correctly reject
|
|
46
|
+
# external data outside the resolved graph directory. Materialize
|
|
47
|
+
# our catalog's graph and sidecars together, keeping validation on.
|
|
48
|
+
artifacts = [path, *path.parent.glob(f"{path.name}.*")]
|
|
49
|
+
if any(artifact.is_symlink() for artifact in artifacts):
|
|
50
|
+
self._artifact_directory = TemporaryDirectory(prefix="hyper-models-onnx-")
|
|
51
|
+
directory = Path(self._artifact_directory.name)
|
|
52
|
+
for artifact in artifacts:
|
|
53
|
+
if artifact.is_file():
|
|
54
|
+
copyfile(artifact, directory / artifact.name)
|
|
55
|
+
path = directory / path.name
|
|
56
|
+
self._session = ort.InferenceSession(str(path), providers=["CPUExecutionProvider"])
|
|
43
57
|
|
|
44
58
|
def encode(self, inputs: np.ndarray) -> np.ndarray:
|
|
45
59
|
"""Encode preprocessed inputs (B, C, H, W) to embeddings (B, D)."""
|
|
@@ -27,6 +27,7 @@ class ImageConfig:
|
|
|
27
27
|
rescale: float = 1.0 / 255.0
|
|
28
28
|
mean: tuple[float, float, float] | None = None
|
|
29
29
|
std: tuple[float, float, float] | None = None
|
|
30
|
+
resize_mode: Literal["center_crop", "squash"] = "center_crop"
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
def preprocess_images(images: list[Image.Image], config: ImageConfig | None = None) -> np.ndarray:
|
|
@@ -47,14 +48,18 @@ def preprocess_images(images: list[Image.Image], config: ImageConfig | None = No
|
|
|
47
48
|
if img.mode != "RGB":
|
|
48
49
|
img = img.convert("RGB")
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
if config.resize_mode == "squash":
|
|
52
|
+
img = img.resize((config.size, config.size), resample=resample)
|
|
53
|
+
elif config.resize_mode == "center_crop":
|
|
54
|
+
# Resize shortest side, then center crop.
|
|
55
|
+
w, h = img.size
|
|
56
|
+
scale = config.size / min(w, h)
|
|
57
|
+
img = img.resize((int(round(w * scale)), int(round(h * scale))), resample=resample)
|
|
58
|
+
w, h = img.size
|
|
59
|
+
left, top = (w - config.size) // 2, (h - config.size) // 2
|
|
60
|
+
img = img.crop((left, top, left + config.size, top + config.size))
|
|
61
|
+
else:
|
|
62
|
+
raise ValueError(f"Unknown resize mode: {config.resize_mode!r}")
|
|
58
63
|
|
|
59
64
|
# To float32 CHW
|
|
60
65
|
arr = np.asarray(img, dtype=np.float32) * config.rescale
|
|
@@ -106,8 +106,15 @@ _MODELS: dict[str, ModelInfo] = {
|
|
|
106
106
|
dim=513,
|
|
107
107
|
hub_id="hyper3labs/hyper3-clip-v1",
|
|
108
108
|
hub_path="model.safetensors",
|
|
109
|
-
hub_patterns=(
|
|
110
|
-
|
|
109
|
+
hub_patterns=(
|
|
110
|
+
"config.json",
|
|
111
|
+
"model.safetensors",
|
|
112
|
+
"tokenizer.json",
|
|
113
|
+
"tokenizer_config.json",
|
|
114
|
+
"vocab.json",
|
|
115
|
+
"merges.txt",
|
|
116
|
+
),
|
|
117
|
+
license="OpenMDW-1.0",
|
|
111
118
|
loader="hyper3-clip-torch",
|
|
112
119
|
optional_dependencies=("ml",),
|
|
113
120
|
modalities=("image", "text"),
|
|
@@ -115,6 +122,7 @@ _MODELS: dict[str, ModelInfo] = {
|
|
|
115
122
|
image_config=ImageConfig(
|
|
116
123
|
size=224,
|
|
117
124
|
interpolation="bicubic",
|
|
125
|
+
resize_mode="squash",
|
|
118
126
|
mean=(0.485, 0.456, 0.406),
|
|
119
127
|
std=(0.229, 0.224, 0.225),
|
|
120
128
|
),
|
|
@@ -6,6 +6,8 @@ They do not create a separate public provider surface.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import json
|
|
10
|
+
from dataclasses import replace
|
|
9
11
|
from pathlib import Path
|
|
10
12
|
from typing import Any
|
|
11
13
|
|
|
@@ -257,7 +259,7 @@ def _align_text_tower_keys(state: dict[str, Any]) -> dict[str, Any]:
|
|
|
257
259
|
|
|
258
260
|
|
|
259
261
|
class Hyper3ClipTorchModel:
|
|
260
|
-
"""Hyper3-CLIP
|
|
262
|
+
"""Hyper3-CLIP image/text inference returning native Lorentz embeddings."""
|
|
261
263
|
|
|
262
264
|
def __init__(
|
|
263
265
|
self,
|
|
@@ -269,11 +271,14 @@ class Hyper3ClipTorchModel:
|
|
|
269
271
|
device: str | None = None,
|
|
270
272
|
) -> None:
|
|
271
273
|
self._checkpoint_path = checkpoint_path
|
|
272
|
-
self._config_path = checkpoint_path.with_name("config.
|
|
274
|
+
self._config_path = checkpoint_path.with_name("config.json")
|
|
275
|
+
if not self._config_path.is_file():
|
|
276
|
+
self._config_path = checkpoint_path.with_name("config.yaml")
|
|
273
277
|
self.geometry = geometry
|
|
274
278
|
self.dim = dim
|
|
275
279
|
self._image_config = image_config or ImageConfig()
|
|
276
280
|
self._device_override = device
|
|
281
|
+
self._max_text_length = 77
|
|
277
282
|
|
|
278
283
|
self._torch = None
|
|
279
284
|
self._device = None
|
|
@@ -316,11 +321,41 @@ class Hyper3ClipTorchModel:
|
|
|
316
321
|
self._torch = torch
|
|
317
322
|
self._device = self._resolve_device(torch)
|
|
318
323
|
|
|
319
|
-
|
|
320
|
-
|
|
324
|
+
if self._config_path.suffix == ".json":
|
|
325
|
+
config = json.loads(self._config_path.read_text(encoding="utf-8"))
|
|
326
|
+
model_config = {
|
|
327
|
+
key: config[key]
|
|
328
|
+
for key in (
|
|
329
|
+
"vision_backbone",
|
|
330
|
+
"text_model_name",
|
|
331
|
+
"embed_dim",
|
|
332
|
+
"curv_init",
|
|
333
|
+
"learn_curv",
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
# These constructor arguments affect training losses only.
|
|
337
|
+
model_config.update(
|
|
338
|
+
entail_weight=0.0, inter_aperture_scale=0.0, intra_aperture_scale=0.0
|
|
339
|
+
)
|
|
340
|
+
model_config["text_config"] = config["text_config"]
|
|
341
|
+
model_config["tokenizer_name_or_path"] = str(self._checkpoint_path.parent)
|
|
342
|
+
else:
|
|
343
|
+
config = yaml.safe_load(self._config_path.read_text(encoding="utf-8"))
|
|
344
|
+
model_config = dict(config["model"])
|
|
345
|
+
data_config = config.get("data", config)
|
|
346
|
+
self._image_config = replace(
|
|
347
|
+
self._image_config,
|
|
348
|
+
size=int(data_config.get("image_size", self._image_config.size)),
|
|
349
|
+
resize_mode="squash",
|
|
350
|
+
)
|
|
351
|
+
self._max_text_length = int(data_config.get("max_text_length", 77))
|
|
321
352
|
model_config["vision_pretrained"] = False
|
|
322
353
|
model_config["text_pretrained"] = False
|
|
323
354
|
model = Hyper3CLIP(**model_config)
|
|
355
|
+
if model.embed_dim + 1 != self.dim:
|
|
356
|
+
raise ValueError("Hyper3-CLIP embedding dimension does not match the catalog entry")
|
|
357
|
+
model.curv_min = float(config.get("curvature_min", model.curv_min))
|
|
358
|
+
model.curv_max = float(config.get("curvature_max", model.curv_max))
|
|
324
359
|
state = _align_text_tower_keys(load_file(self._checkpoint_path, device="cpu"))
|
|
325
360
|
load_result = model.load_state_dict(state, strict=False)
|
|
326
361
|
if load_result.missing_keys:
|
|
@@ -346,6 +381,7 @@ class Hyper3ClipTorchModel:
|
|
|
346
381
|
|
|
347
382
|
def encode_images(self, images: list[Image.Image]) -> np.ndarray:
|
|
348
383
|
"""Encode PIL images to Hyper3-CLIP embeddings (B, D)."""
|
|
384
|
+
self._ensure_model()
|
|
349
385
|
return self.encode(preprocess_images(images, self._image_config))
|
|
350
386
|
|
|
351
387
|
def encode_texts(self, texts: list[str]) -> np.ndarray:
|
|
@@ -357,7 +393,11 @@ class Hyper3ClipTorchModel:
|
|
|
357
393
|
assert self._device is not None
|
|
358
394
|
|
|
359
395
|
encoded = self._model.tokenizer(
|
|
360
|
-
list(texts),
|
|
396
|
+
list(texts),
|
|
397
|
+
padding=True,
|
|
398
|
+
truncation=True,
|
|
399
|
+
max_length=self._max_text_length,
|
|
400
|
+
return_tensors="pt",
|
|
361
401
|
)
|
|
362
402
|
input_ids = encoded["input_ids"].to(self._device)
|
|
363
403
|
attention_mask = encoded["attention_mask"].to(self._device)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|