slide2vec 4.5.1__tar.gz → 4.5.2__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.
- {slide2vec-4.5.1 → slide2vec-4.5.2}/PKG-INFO +1 -1
- {slide2vec-4.5.1 → slide2vec-4.5.2}/pyproject.toml +2 -2
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/__init__.py +1 -1
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/distributed.py +90 -30
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec.egg-info/PKG-INFO +1 -1
- {slide2vec-4.5.1 → slide2vec-4.5.2}/tests/test_progress.py +58 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/LICENSE +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/README.md +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/setup.cfg +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/__main__.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/api.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/artifacts.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/cli.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/configs/__init__.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/configs/default.yaml +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/configs/resources.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/data/__init__.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/data/dataset.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/data/tile_reader.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/data/tile_store.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/distributed/__init__.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/distributed/direct_embed_worker.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/distributed/pipeline_worker.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/__init__.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/base.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/__init__.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/conch.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/gigapath.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/hibou.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/hoptimus.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/lunit.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/midnight.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/moozy/__init__.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/moozy/blocks.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/moozy/case.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/moozy/loading.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/moozy/slide.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/moozy/types.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/musk.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/phikon.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/prism.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/prost40m.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/titan.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/uni.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/models/virchow.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/registry.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/encoders/validation.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/inference.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/progress.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/__init__.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/artifacts_collect.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/batching.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/cpu_budget.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/distributed_stage.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/embedding.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/embedding_persist.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/embedding_pipeline.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/hierarchical.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/manifest.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/model_settings.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/patient_pipeline.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/persist_callbacks.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/persistence.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/process_list.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/progress_bridge.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/registry.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/serialization.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/slide_encode.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/tiling.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/tiling_pipeline.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/types.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/runtime/worker_io.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/utils/__init__.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/utils/config.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/utils/coordinates.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/utils/log_utils.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/utils/tiling_io.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec/utils/utils.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec.egg-info/SOURCES.txt +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec.egg-info/dependency_links.txt +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec.egg-info/entry_points.txt +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec.egg-info/not-zip-safe +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec.egg-info/requires.txt +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/slide2vec.egg-info/top_level.txt +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/tests/test_architecture_runtime_split.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/tests/test_encoder_registry.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/tests/test_hs2p_package_cutover.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/tests/test_output_consistency.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/tests/test_regression_core.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/tests/test_regression_inference.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/tests/test_regression_models.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/tests/test_runtime_batching.py +0 -0
- {slide2vec-4.5.1 → slide2vec-4.5.2}/tests/test_tile_store.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "slide2vec"
|
|
7
|
-
version = "4.5.
|
|
7
|
+
version = "4.5.2"
|
|
8
8
|
description = "Embedding of whole slide images with Foundation Models"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -164,7 +164,7 @@ no_implicit_reexport = true
|
|
|
164
164
|
max-line-length = 160
|
|
165
165
|
|
|
166
166
|
[tool.bumpver]
|
|
167
|
-
current_version = "4.5.
|
|
167
|
+
current_version = "4.5.2"
|
|
168
168
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
169
169
|
commit = false # We do version bumping in CI, not as a commit
|
|
170
170
|
tag = false # Git tag already exists — we don't auto-tag
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import heapq
|
|
4
|
+
import os
|
|
4
5
|
import shutil
|
|
6
|
+
import signal
|
|
5
7
|
import subprocess
|
|
6
8
|
import sys
|
|
7
9
|
import tempfile
|
|
@@ -55,6 +57,42 @@ def write_worker_logs(module: str, output_dir: Path, stdout_text: str, stderr_te
|
|
|
55
57
|
return stdout_log_path, stderr_log_path
|
|
56
58
|
|
|
57
59
|
|
|
60
|
+
def terminate_process_group(process, *, grace_seconds: float = 10.0) -> None:
|
|
61
|
+
"""SIGTERM then SIGKILL the worker's whole process group.
|
|
62
|
+
|
|
63
|
+
The torchrun *agent* and the GPU worker processes it spawns share the session
|
|
64
|
+
we start the agent in (``start_new_session=True``), so signalling the group id
|
|
65
|
+
reaps the agent and every worker at once — including the elastic agent, which
|
|
66
|
+
would otherwise respawn workers if we only killed them individually. A no-op if
|
|
67
|
+
the process already exited or the platform lacks process groups.
|
|
68
|
+
"""
|
|
69
|
+
if process.poll() is not None:
|
|
70
|
+
return
|
|
71
|
+
pid = getattr(process, "pid", None)
|
|
72
|
+
if pid is None or not hasattr(os, "killpg"):
|
|
73
|
+
process.terminate()
|
|
74
|
+
return
|
|
75
|
+
try:
|
|
76
|
+
pgid = os.getpgid(pid)
|
|
77
|
+
except (ProcessLookupError, OSError):
|
|
78
|
+
return
|
|
79
|
+
try:
|
|
80
|
+
os.killpg(pgid, signal.SIGTERM)
|
|
81
|
+
except (ProcessLookupError, OSError):
|
|
82
|
+
return
|
|
83
|
+
try:
|
|
84
|
+
process.wait(timeout=grace_seconds)
|
|
85
|
+
except subprocess.TimeoutExpired:
|
|
86
|
+
try:
|
|
87
|
+
os.killpg(pgid, signal.SIGKILL)
|
|
88
|
+
except (ProcessLookupError, OSError):
|
|
89
|
+
pass
|
|
90
|
+
try:
|
|
91
|
+
process.wait(timeout=5.0)
|
|
92
|
+
except subprocess.TimeoutExpired:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
|
|
58
96
|
def run_torchrun_worker(
|
|
59
97
|
*,
|
|
60
98
|
module: str,
|
|
@@ -79,6 +117,19 @@ def run_torchrun_worker(
|
|
|
79
117
|
"--request-path",
|
|
80
118
|
str(request_path),
|
|
81
119
|
]
|
|
120
|
+
# Run the agent in its own session so a single killpg reaps agent + workers
|
|
121
|
+
# (see terminate_process_group). A bare SIGTERM to *this* process would skip
|
|
122
|
+
# the finally block, so while the agent is alive we convert SIGTERM into a
|
|
123
|
+
# KeyboardInterrupt — but only from the main thread, where signal.signal is
|
|
124
|
+
# allowed; the original handler is restored in finally.
|
|
125
|
+
previous_sigterm = None
|
|
126
|
+
if threading.current_thread() is threading.main_thread():
|
|
127
|
+
def _raise_on_sigterm(signum, frame): # noqa: ANN001
|
|
128
|
+
raise KeyboardInterrupt
|
|
129
|
+
try:
|
|
130
|
+
previous_sigterm = signal.signal(signal.SIGTERM, _raise_on_sigterm)
|
|
131
|
+
except (ValueError, OSError):
|
|
132
|
+
previous_sigterm = None
|
|
82
133
|
process = popen_factory(
|
|
83
134
|
command,
|
|
84
135
|
cwd=str(Path(__file__).resolve().parents[2]),
|
|
@@ -86,43 +137,52 @@ def run_torchrun_worker(
|
|
|
86
137
|
stderr=subprocess.PIPE,
|
|
87
138
|
text=True,
|
|
88
139
|
bufsize=1,
|
|
140
|
+
start_new_session=True,
|
|
89
141
|
)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
142
|
+
try:
|
|
143
|
+
stdout_chunks: list[str] = []
|
|
144
|
+
stderr_chunks: list[str] = []
|
|
145
|
+
stdout_thread = threading.Thread(target=drain_stream_to_buffer, args=(process.stdout, stdout_chunks), daemon=True)
|
|
146
|
+
stderr_thread = threading.Thread(target=drain_stream_to_buffer, args=(process.stderr, stderr_chunks), daemon=True)
|
|
147
|
+
stdout_thread.start()
|
|
148
|
+
stderr_thread.start()
|
|
149
|
+
offsets: dict[Path, int] = {}
|
|
150
|
+
while process.poll() is None:
|
|
151
|
+
if progress_events_path is not None:
|
|
152
|
+
events, offsets = read_progress_events(progress_events_path, offsets=offsets)
|
|
153
|
+
for event in events:
|
|
154
|
+
emit_progress_event(event)
|
|
155
|
+
if progress_event_callback is not None:
|
|
156
|
+
progress_event_callback(event)
|
|
157
|
+
time.sleep(0.1)
|
|
98
158
|
if progress_events_path is not None:
|
|
99
159
|
events, offsets = read_progress_events(progress_events_path, offsets=offsets)
|
|
100
160
|
for event in events:
|
|
101
161
|
emit_progress_event(event)
|
|
102
162
|
if progress_event_callback is not None:
|
|
103
163
|
progress_event_callback(event)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
164
|
+
returncode = process.wait()
|
|
165
|
+
stdout_thread.join(timeout=1.0)
|
|
166
|
+
stderr_thread.join(timeout=1.0)
|
|
167
|
+
stdout_text = "".join(stdout_chunks)
|
|
168
|
+
stderr_text = "".join(stderr_chunks)
|
|
169
|
+
stdout_log_path, stderr_log_path = write_worker_logs(module, output_dir, stdout_text, stderr_text)
|
|
170
|
+
if returncode != 0:
|
|
171
|
+
raise RuntimeError(
|
|
172
|
+
f"{failure_title}.\n"
|
|
173
|
+
f"See logs:\n"
|
|
174
|
+
f"stdout: {stdout_log_path}\n"
|
|
175
|
+
f"stderr: {stderr_log_path}\n"
|
|
176
|
+
f"stdout:\n{stdout_text}\n"
|
|
177
|
+
f"stderr:\n{stderr_text}"
|
|
178
|
+
)
|
|
179
|
+
finally:
|
|
180
|
+
# On any early exit (Ctrl-C, converted SIGTERM, RuntimeError) reap the
|
|
181
|
+
# whole worker group so no orphaned agent/workers keep holding the GPUs.
|
|
182
|
+
# No-op on the normal path: the agent has already exited.
|
|
183
|
+
terminate_process_group(process)
|
|
184
|
+
if previous_sigterm is not None:
|
|
185
|
+
signal.signal(signal.SIGTERM, previous_sigterm)
|
|
126
186
|
|
|
127
187
|
|
|
128
188
|
def assign_slides_to_ranks(
|
|
@@ -684,6 +684,64 @@ def test_run_torchrun_worker_uses_standalone_rendezvous(monkeypatch, tmp_path: P
|
|
|
684
684
|
assert "--rdzv-endpoint" not in " ".join(command)
|
|
685
685
|
|
|
686
686
|
|
|
687
|
+
def test_run_torchrun_worker_starts_new_session(monkeypatch, tmp_path: Path):
|
|
688
|
+
# The agent must run in its own session so terminate_process_group can reap
|
|
689
|
+
# the agent and every worker it spawns with a single killpg.
|
|
690
|
+
request_path = tmp_path / "request.json"
|
|
691
|
+
request_path.write_text("{}", encoding="utf-8")
|
|
692
|
+
output_dir = tmp_path / "output"
|
|
693
|
+
output_dir.mkdir()
|
|
694
|
+
|
|
695
|
+
observed = {}
|
|
696
|
+
|
|
697
|
+
class FakePopen:
|
|
698
|
+
def __init__(self, command, **kwargs):
|
|
699
|
+
observed["kwargs"] = kwargs
|
|
700
|
+
self.stdout = io.StringIO("")
|
|
701
|
+
self.stderr = io.StringIO("")
|
|
702
|
+
|
|
703
|
+
def poll(self):
|
|
704
|
+
return 0
|
|
705
|
+
|
|
706
|
+
def wait(self, timeout=None):
|
|
707
|
+
return 0
|
|
708
|
+
|
|
709
|
+
monkeypatch.setattr(distributed.time, "sleep", lambda _seconds: None)
|
|
710
|
+
|
|
711
|
+
distributed.run_torchrun_worker(
|
|
712
|
+
module="slide2vec.distributed.direct_embed_worker",
|
|
713
|
+
num_gpus=2,
|
|
714
|
+
output_dir=output_dir,
|
|
715
|
+
request_path=request_path,
|
|
716
|
+
failure_title="boom",
|
|
717
|
+
popen_factory=FakePopen,
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
assert observed["kwargs"].get("start_new_session") is True
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def test_terminate_process_group_reaps_whole_group():
|
|
724
|
+
# A real grandchild in the same session must die when we tear down the group.
|
|
725
|
+
parent = subprocess.Popen(
|
|
726
|
+
[
|
|
727
|
+
sys.executable,
|
|
728
|
+
"-c",
|
|
729
|
+
"import subprocess, sys, time; "
|
|
730
|
+
"subprocess.Popen([sys.executable, '-c', 'import time; time.sleep(60)']); "
|
|
731
|
+
"time.sleep(60)",
|
|
732
|
+
],
|
|
733
|
+
start_new_session=True,
|
|
734
|
+
)
|
|
735
|
+
import os
|
|
736
|
+
|
|
737
|
+
pgid = os.getpgid(parent.pid)
|
|
738
|
+
distributed.terminate_process_group(parent, grace_seconds=5.0)
|
|
739
|
+
assert parent.poll() is not None
|
|
740
|
+
# The whole group is gone: signalling it with 0 must raise.
|
|
741
|
+
with pytest.raises(ProcessLookupError):
|
|
742
|
+
os.killpg(pgid, 0)
|
|
743
|
+
|
|
744
|
+
|
|
687
745
|
def test_reset_progress_event_logs_is_idempotent(tmp_path: Path):
|
|
688
746
|
import slide2vec.runtime.distributed as distributed
|
|
689
747
|
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|