mlvbench 1.0.0__py3-none-any.whl
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.
- mlvbench/__init__.py +1 -0
- mlvbench/cache.py +508 -0
- mlvbench/datasets/__init__.py +16 -0
- mlvbench/datasets/_base.py +380 -0
- mlvbench/datasets/figure_ground_convexity.py +154 -0
- mlvbench/datasets/figure_ground_surroundedness.py +110 -0
- mlvbench/datasets/figure_ground_symmetry.py +274 -0
- mlvbench/datasets/msra10k.py +195 -0
- mlvbench/download.py +203 -0
- mlvbench/infrastructure.py +136 -0
- mlvbench/models/__init__.py +230 -0
- mlvbench/models/__main__.py +76 -0
- mlvbench/models/_base.py +544 -0
- mlvbench/models/_bundles.py +62 -0
- mlvbench/models/diffusers.py +231 -0
- mlvbench/models/franca.py +245 -0
- mlvbench/models/timm.py +534 -0
- mlvbench/models/webssl.py +188 -0
- mlvbench/monitoring.py +174 -0
- mlvbench/probes.py +227 -0
- mlvbench/stimuli/__init__.py +1 -0
- mlvbench/stimuli/shapes.py +238 -0
- mlvbench/stimuli/textures.py +155 -0
- mlvbench/stimuli/transforms.py +146 -0
- mlvbench/tasks/__init__.py +34 -0
- mlvbench/tasks/_base.py +136 -0
- mlvbench/tasks/binary_segmentation.py +268 -0
- mlvbench/trainer.py +657 -0
- mlvbench-1.0.0.dist-info/METADATA +144 -0
- mlvbench-1.0.0.dist-info/RECORD +32 -0
- mlvbench-1.0.0.dist-info/WHEEL +4 -0
- mlvbench-1.0.0.dist-info/licenses/LICENSE +7 -0
mlvbench/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""MLV-Bench: Human vs. Machine Mid-Level Vision."""
|
mlvbench/cache.py
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
"""Caching utilities for reusable files.
|
|
2
|
+
|
|
3
|
+
[`mlvbench.cache`][] provides utilities for caching files and reusing them across
|
|
4
|
+
experiments. The following example illustrates how the cache is typically used:
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from mlvbench.cache import (
|
|
10
|
+
get_cached_resource,
|
|
11
|
+
create_cached_resource,
|
|
12
|
+
finalize_cached_resource,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
def my_data(seed: int = 2) -> Path:
|
|
16
|
+
# A cached resource is identified by a name, version and the parameters that where
|
|
17
|
+
# used to generate the resource:
|
|
18
|
+
name = "my_data"
|
|
19
|
+
version = "1"
|
|
20
|
+
parameters = { "seed": seed }
|
|
21
|
+
|
|
22
|
+
# If the resource with the given parameters already exists, get_cached_resource will
|
|
23
|
+
# return its path.
|
|
24
|
+
path = get_cached_resource(name, version, parameters)
|
|
25
|
+
if path is not None:
|
|
26
|
+
return path
|
|
27
|
+
|
|
28
|
+
# If the resource doesn't exist yet, create_cached_resource will create a temporary
|
|
29
|
+
# directory where we can prepare the data.
|
|
30
|
+
path = create_cached_resource(name, version, parameters)
|
|
31
|
+
|
|
32
|
+
# Prepare the data at the given path
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
# Once all files have been generated, finalize_cached_resource will move the data to
|
|
36
|
+
# its final location. This method takes care of race conditions: If a different
|
|
37
|
+
# process has generated the same resource in the meantime, the newly created data
|
|
38
|
+
# will be discarded and the cached data is used.
|
|
39
|
+
path = finalize_cached_resource(path)
|
|
40
|
+
|
|
41
|
+
return path
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The location of the cache defaults to "~/.cache/mlvbench/cache" and can be configured
|
|
45
|
+
via the `MLVBENCH_CACHE_PATH` environment variable.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
import hashlib
|
|
49
|
+
import json
|
|
50
|
+
import os
|
|
51
|
+
import shutil
|
|
52
|
+
import time
|
|
53
|
+
import uuid
|
|
54
|
+
import warnings
|
|
55
|
+
from pathlib import Path
|
|
56
|
+
from typing import Any
|
|
57
|
+
|
|
58
|
+
import numpy as np
|
|
59
|
+
import torch
|
|
60
|
+
from einops import rearrange
|
|
61
|
+
from torch.utils.data import Dataset
|
|
62
|
+
from tqdm import tqdm
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _get_cache_path() -> Path:
|
|
66
|
+
cache_path = Path(os.environ.get("MLVBENCH_CACHE_PATH", "~/.cache/mlvbench/cache"))
|
|
67
|
+
return cache_path.expanduser().resolve()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_cached_resource(
|
|
71
|
+
name: str,
|
|
72
|
+
version: str,
|
|
73
|
+
parameters: dict[str, Any],
|
|
74
|
+
) -> Path | None:
|
|
75
|
+
"""Return the data path of a complete cache entry, or None if not ready.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
name: Name of the cached resource.
|
|
79
|
+
version: Version string of the resource.
|
|
80
|
+
parameters: Parameter dict that identifies this particular resource.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Path to the `data/` subdirectory if the entry is complete, else None.
|
|
84
|
+
"""
|
|
85
|
+
path = _get_cached_resource_path(name, version, parameters) / "data"
|
|
86
|
+
return path if path.exists() else None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def create_cached_resource(
|
|
90
|
+
name: str,
|
|
91
|
+
version: str,
|
|
92
|
+
parameters: dict[str, Any],
|
|
93
|
+
) -> Path:
|
|
94
|
+
"""Create a new cache entry and return a temporary data path.
|
|
95
|
+
|
|
96
|
+
This will create a new cached resource at a temporary location. Call
|
|
97
|
+
[`finalize_cached_resource`][mlvbench.cache.finalize_cached_resource] to move it to
|
|
98
|
+
its final location and make the data available for future runs.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
name: Name of the cached resource.
|
|
102
|
+
version: Version string of the resource.
|
|
103
|
+
parameters: Parameter dict that identifies this particular resource.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Path to the directory where resource data should be written.
|
|
107
|
+
"""
|
|
108
|
+
final_root = _get_cached_resource_path(name, version, parameters)
|
|
109
|
+
|
|
110
|
+
if final_root.exists():
|
|
111
|
+
warnings.warn(
|
|
112
|
+
f"Cache entry '{name}' ({version}) already exists at {final_root}. " +
|
|
113
|
+
"Call get_cached_resource() first to reuse it.",
|
|
114
|
+
stacklevel=2,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
tmp_id = uuid.uuid4().hex[:8]
|
|
118
|
+
tmp_root = final_root.parent / f"{final_root.name}_tmp_{tmp_id}"
|
|
119
|
+
data_path = tmp_root / "data"
|
|
120
|
+
data_path.mkdir(parents=True, exist_ok=True)
|
|
121
|
+
|
|
122
|
+
with open(tmp_root / "metadata.json", "w") as f:
|
|
123
|
+
json.dump({"parameters": parameters, "created_at": time.time()}, f, indent=2)
|
|
124
|
+
|
|
125
|
+
return data_path
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def finalize_cached_resource(path: Path) -> Path:
|
|
129
|
+
"""Atomically finalize a cached resource by moving it to its final path.
|
|
130
|
+
|
|
131
|
+
If the target already exists (e.g., another worker finalized first), this emits a
|
|
132
|
+
warning and deletes the newly created data.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
path: The data path returned by
|
|
136
|
+
[`create_cached_resource`][mlvbench.cache.create_cached_resource].
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
The updated data path.
|
|
140
|
+
|
|
141
|
+
Raises:
|
|
142
|
+
ValueError: If `path` is not located within the cache directory.
|
|
143
|
+
"""
|
|
144
|
+
cache_path = _get_cache_path()
|
|
145
|
+
|
|
146
|
+
if not path.is_relative_to(cache_path):
|
|
147
|
+
raise ValueError(f"Path {path} is not within the cache directory {cache_path}.")
|
|
148
|
+
|
|
149
|
+
tmp_root = path.parent
|
|
150
|
+
hash_str = tmp_root.name.split("_tmp_")[0]
|
|
151
|
+
final_root = tmp_root.parent / hash_str
|
|
152
|
+
|
|
153
|
+
metadata_path = tmp_root / "metadata.json"
|
|
154
|
+
with open(metadata_path) as f:
|
|
155
|
+
metadata = json.load(f)
|
|
156
|
+
|
|
157
|
+
finalized_at = time.time()
|
|
158
|
+
data_files = list(path.rglob("*"))
|
|
159
|
+
metadata["finalized_at"] = finalized_at
|
|
160
|
+
metadata["duration"] = finalized_at - metadata["created_at"]
|
|
161
|
+
metadata["num_files"] = sum(1 for p in data_files if p.is_file())
|
|
162
|
+
metadata["total_size"] = sum(p.stat().st_size for p in data_files if p.is_file())
|
|
163
|
+
|
|
164
|
+
with open(metadata_path, "w") as f:
|
|
165
|
+
json.dump(metadata, f, indent=2)
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
tmp_root.rename(final_root)
|
|
169
|
+
except OSError:
|
|
170
|
+
warnings.warn(
|
|
171
|
+
f"Cache entry at {final_root} already exists. " +
|
|
172
|
+
"Discarding the newly created resource.",
|
|
173
|
+
stacklevel=2,
|
|
174
|
+
)
|
|
175
|
+
shutil.rmtree(tmp_root)
|
|
176
|
+
|
|
177
|
+
return final_root / "data"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _get_cached_resource_path(
|
|
181
|
+
name: str,
|
|
182
|
+
version: str,
|
|
183
|
+
parameters: dict[str, Any],
|
|
184
|
+
) -> Path:
|
|
185
|
+
"""Return the path to the cached resource."""
|
|
186
|
+
payload = {"name": name, "version": version, "parameters": parameters}
|
|
187
|
+
encoded = json.dumps(payload, sort_keys=True).encode()
|
|
188
|
+
hash_str = hashlib.sha256(encoded).hexdigest()[:8]
|
|
189
|
+
return _get_cache_path() / name / version / hash_str
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def precompute_dataset(
|
|
193
|
+
dataset: Dataset,
|
|
194
|
+
output_path: Path,
|
|
195
|
+
progress_bar: bool = True,
|
|
196
|
+
) -> "PrecomputedDataset":
|
|
197
|
+
"""Precompute the dataset and store it as np.memmap.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
dataset: The dataset to precompute.
|
|
201
|
+
output_path: The path to store the precomputed dataset.
|
|
202
|
+
progress_bar: Whether to show a progress bar.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
A PrecomputedDataset object.
|
|
206
|
+
"""
|
|
207
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
208
|
+
|
|
209
|
+
num_samples = len(dataset)
|
|
210
|
+
|
|
211
|
+
buffers = dict()
|
|
212
|
+
metadata = dict()
|
|
213
|
+
keys = []
|
|
214
|
+
|
|
215
|
+
for key, value in dataset[0].items():
|
|
216
|
+
if key == "__key__":
|
|
217
|
+
continue
|
|
218
|
+
|
|
219
|
+
buffers[key] = np.memmap(
|
|
220
|
+
output_path / f"{key}.memmap",
|
|
221
|
+
dtype=value.dtype,
|
|
222
|
+
mode="w+",
|
|
223
|
+
shape=(num_samples, *value.shape),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
metadata[key] = {
|
|
227
|
+
"shape": buffers[key].shape,
|
|
228
|
+
"dtype": buffers[key].dtype.name,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
with open(output_path / "metadata.json", "w") as f:
|
|
232
|
+
json.dump(metadata, f, indent=2)
|
|
233
|
+
|
|
234
|
+
if progress_bar:
|
|
235
|
+
dataset = tqdm(dataset, desc="Precomputing dataset", total=num_samples)
|
|
236
|
+
|
|
237
|
+
for sample_index, sample in enumerate(dataset):
|
|
238
|
+
for key, value in sample.items():
|
|
239
|
+
if key == "__key__":
|
|
240
|
+
keys.append(value)
|
|
241
|
+
else:
|
|
242
|
+
buffers[key][sample_index] = value
|
|
243
|
+
|
|
244
|
+
if len(keys) > 0:
|
|
245
|
+
with open(output_path / "keys", "w") as f:
|
|
246
|
+
f.write("\n".join(keys))
|
|
247
|
+
|
|
248
|
+
return PrecomputedDataset(output_path)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class PrecomputedDataset(Dataset):
|
|
252
|
+
"""Precomputed dataset."""
|
|
253
|
+
|
|
254
|
+
def __init__(
|
|
255
|
+
self,
|
|
256
|
+
path: Path,
|
|
257
|
+
feature_map: dict[str, str] | None = None,
|
|
258
|
+
mmap: bool = True,
|
|
259
|
+
):
|
|
260
|
+
"""Initialize the dataset.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
path: Path to the precomputed dataset directory.
|
|
264
|
+
feature_map: Optional mapping from stored feature names to output
|
|
265
|
+
feature names, e.g. `{"image_consistent": "image"}`. When
|
|
266
|
+
provided, only the keys present in the map are loaded; all
|
|
267
|
+
other stored features are ignored.
|
|
268
|
+
mmap: If True (default), buffers are memory-mapped from disk. If
|
|
269
|
+
False, the entire arrays are loaded into RAM at construction
|
|
270
|
+
time.
|
|
271
|
+
"""
|
|
272
|
+
with open(path / "metadata.json", "r") as f:
|
|
273
|
+
metadata = json.load(f)
|
|
274
|
+
|
|
275
|
+
self.mmap = mmap
|
|
276
|
+
self.buffers = dict()
|
|
277
|
+
for key, info in metadata.items():
|
|
278
|
+
if feature_map is not None:
|
|
279
|
+
if key not in feature_map:
|
|
280
|
+
continue
|
|
281
|
+
out_key = feature_map[key]
|
|
282
|
+
else:
|
|
283
|
+
out_key = key
|
|
284
|
+
buffer = np.memmap(
|
|
285
|
+
path / f"{key}.memmap",
|
|
286
|
+
dtype=info["dtype"],
|
|
287
|
+
mode="r",
|
|
288
|
+
shape=info["shape"],
|
|
289
|
+
)
|
|
290
|
+
if not mmap:
|
|
291
|
+
buffer = torch.from_numpy(np.array(buffer))
|
|
292
|
+
buffer = rearrange(buffer, "B H W C -> B C H W")
|
|
293
|
+
self.buffers[out_key] = buffer
|
|
294
|
+
|
|
295
|
+
if (path / "keys").exists():
|
|
296
|
+
with open(path / "keys", "r") as f:
|
|
297
|
+
self.keys = f.read().splitlines()
|
|
298
|
+
else:
|
|
299
|
+
self.keys = None
|
|
300
|
+
|
|
301
|
+
def __len__(self):
|
|
302
|
+
"""Return the number of samples in the dataset."""
|
|
303
|
+
return next(iter(self.buffers.values())).shape[0]
|
|
304
|
+
|
|
305
|
+
def __getitem__(self, index: int) -> dict[str, np.ndarray]:
|
|
306
|
+
"""Return the sample at the given index.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
A dictionary mapping each feature name to its value for this sample. Each
|
|
310
|
+
value has the shape and dtype of the corresponding cached feature with
|
|
311
|
+
the leading sample dimension removed.
|
|
312
|
+
"""
|
|
313
|
+
sample = dict()
|
|
314
|
+
|
|
315
|
+
for key, buffer in self.buffers.items():
|
|
316
|
+
if isinstance(buffer, torch.Tensor):
|
|
317
|
+
sample[key] = buffer[index]
|
|
318
|
+
else:
|
|
319
|
+
sample[key] = torch.from_numpy(buffer[index]).permute(2, 0, 1)
|
|
320
|
+
|
|
321
|
+
if self.keys is not None:
|
|
322
|
+
sample["__key__"] = self.keys[index]
|
|
323
|
+
else:
|
|
324
|
+
sample["__key__"] = str(index)
|
|
325
|
+
|
|
326
|
+
return sample
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _human_size(num_bytes: int) -> str:
|
|
330
|
+
"""Return a human-readable string for a byte count."""
|
|
331
|
+
for unit in ("B", "KB", "MB", "GB", "TB"):
|
|
332
|
+
if num_bytes < 1024:
|
|
333
|
+
return f"{num_bytes:.1f} {unit}"
|
|
334
|
+
num_bytes /= 1024
|
|
335
|
+
return f"{num_bytes:.1f} PB"
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _human_duration(seconds: float) -> str:
|
|
339
|
+
"""Return a human-readable string for a duration in seconds."""
|
|
340
|
+
if seconds < 60:
|
|
341
|
+
return f"{seconds:.1f}s"
|
|
342
|
+
minutes, secs = divmod(int(seconds), 60)
|
|
343
|
+
if minutes < 60:
|
|
344
|
+
return f"{minutes}m {secs}s"
|
|
345
|
+
hours, minutes = divmod(minutes, 60)
|
|
346
|
+
return f"{hours}h {minutes}m"
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
if __name__ == "__main__":
|
|
350
|
+
import argparse
|
|
351
|
+
import sys
|
|
352
|
+
from datetime import datetime
|
|
353
|
+
|
|
354
|
+
import tabulate
|
|
355
|
+
|
|
356
|
+
parser = argparse.ArgumentParser(description="Manage cached resources.")
|
|
357
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
358
|
+
|
|
359
|
+
list_parser = subparsers.add_parser("list", help="List cached resources.")
|
|
360
|
+
list_parser.add_argument(
|
|
361
|
+
"--sort",
|
|
362
|
+
choices=["name", "size", "created", "duration", "files"],
|
|
363
|
+
default="name",
|
|
364
|
+
help="Sort order (default: name)",
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
remove_parser = subparsers.add_parser("remove", help="Remove a cached resource.")
|
|
368
|
+
remove_parser.add_argument("hash", help="Hash of the cache entry to remove.")
|
|
369
|
+
|
|
370
|
+
subparsers.add_parser("prune", help="Remove all incomplete resources.")
|
|
371
|
+
|
|
372
|
+
args = parser.parse_args()
|
|
373
|
+
|
|
374
|
+
cache_path = _get_cache_path()
|
|
375
|
+
|
|
376
|
+
if args.command == "prune":
|
|
377
|
+
incomplete_paths = (
|
|
378
|
+
[
|
|
379
|
+
path
|
|
380
|
+
for path in cache_path.rglob("*")
|
|
381
|
+
if path.is_dir() and "_tmp_" in path.name
|
|
382
|
+
]
|
|
383
|
+
if cache_path.exists()
|
|
384
|
+
else []
|
|
385
|
+
)
|
|
386
|
+
for path in incomplete_paths:
|
|
387
|
+
shutil.rmtree(path)
|
|
388
|
+
print(f"Removed {path}") # noqa: T201
|
|
389
|
+
print(f"\nRemoved {len(incomplete_paths)} incomplete resource(s).") # noqa: T201
|
|
390
|
+
|
|
391
|
+
elif args.command == "remove":
|
|
392
|
+
if cache_path.exists():
|
|
393
|
+
matches = list(cache_path.glob(f"*/*/{args.hash}"))
|
|
394
|
+
else:
|
|
395
|
+
matches = []
|
|
396
|
+
if len(matches) == 0:
|
|
397
|
+
print(f"No cache entry found with hash '{args.hash}'.", file=sys.stderr) # noqa: T201
|
|
398
|
+
sys.exit(1)
|
|
399
|
+
for path in matches:
|
|
400
|
+
shutil.rmtree(path)
|
|
401
|
+
print(f"Removed {path}") # noqa: T201
|
|
402
|
+
|
|
403
|
+
elif args.command == "list":
|
|
404
|
+
rows = []
|
|
405
|
+
|
|
406
|
+
if cache_path.exists():
|
|
407
|
+
for name_dir in cache_path.iterdir():
|
|
408
|
+
if not name_dir.is_dir():
|
|
409
|
+
continue
|
|
410
|
+
for version_dir in name_dir.iterdir():
|
|
411
|
+
if not version_dir.is_dir():
|
|
412
|
+
continue
|
|
413
|
+
for hash_dir in version_dir.iterdir():
|
|
414
|
+
if not hash_dir.is_dir() or "_tmp_" in hash_dir.name:
|
|
415
|
+
continue
|
|
416
|
+
if not (hash_dir / "data").exists():
|
|
417
|
+
continue
|
|
418
|
+
metadata_file = hash_dir / "metadata.json"
|
|
419
|
+
if not metadata_file.exists():
|
|
420
|
+
continue
|
|
421
|
+
with open(metadata_file) as f:
|
|
422
|
+
meta = json.load(f)
|
|
423
|
+
rows.append(
|
|
424
|
+
{
|
|
425
|
+
"hash": hash_dir.name,
|
|
426
|
+
"name": name_dir.name,
|
|
427
|
+
"version": version_dir.name,
|
|
428
|
+
"parameters": json.dumps(
|
|
429
|
+
meta["parameters"], sort_keys=True
|
|
430
|
+
),
|
|
431
|
+
"created": meta.get("created_at"),
|
|
432
|
+
"duration": meta.get("duration"),
|
|
433
|
+
"num_files": meta.get("num_files"),
|
|
434
|
+
"size": meta.get("total_size"),
|
|
435
|
+
}
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
sort_keys = {
|
|
439
|
+
"name": lambda row: (row["name"], row["version"], row["parameters"]),
|
|
440
|
+
"size": lambda row: row["size"],
|
|
441
|
+
"created": lambda row: row["created"],
|
|
442
|
+
"duration": lambda row: row["duration"],
|
|
443
|
+
"files": lambda row: row["num_files"],
|
|
444
|
+
}
|
|
445
|
+
rows.sort(key=sort_keys[args.sort])
|
|
446
|
+
|
|
447
|
+
table = [
|
|
448
|
+
[
|
|
449
|
+
row["hash"],
|
|
450
|
+
row["name"],
|
|
451
|
+
row["version"],
|
|
452
|
+
row["parameters"],
|
|
453
|
+
datetime.fromtimestamp(row["created"]).strftime("%Y-%m-%d %H:%M"),
|
|
454
|
+
_human_duration(row["duration"]),
|
|
455
|
+
row["num_files"],
|
|
456
|
+
_human_size(row["size"]),
|
|
457
|
+
]
|
|
458
|
+
for row in rows
|
|
459
|
+
]
|
|
460
|
+
headers = [
|
|
461
|
+
"Hash",
|
|
462
|
+
"Name",
|
|
463
|
+
"Version",
|
|
464
|
+
"Parameters",
|
|
465
|
+
"Created",
|
|
466
|
+
"Duration",
|
|
467
|
+
"Files",
|
|
468
|
+
"Size",
|
|
469
|
+
]
|
|
470
|
+
total_files = sum(row["num_files"] for row in rows)
|
|
471
|
+
total_size = sum(row["size"] for row in rows)
|
|
472
|
+
footer = ["", "", "", "", "", "", total_files, _human_size(total_size)]
|
|
473
|
+
print( # noqa: T201
|
|
474
|
+
tabulate.tabulate(
|
|
475
|
+
table + [tabulate.SEPARATING_LINE, footer],
|
|
476
|
+
headers=headers,
|
|
477
|
+
tablefmt="simple",
|
|
478
|
+
)
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
incomplete_paths = (
|
|
482
|
+
[
|
|
483
|
+
path
|
|
484
|
+
for path in cache_path.rglob("*")
|
|
485
|
+
if path.is_dir() and "_tmp_" in path.name
|
|
486
|
+
]
|
|
487
|
+
if cache_path.exists()
|
|
488
|
+
else []
|
|
489
|
+
)
|
|
490
|
+
if len(incomplete_paths) > 0:
|
|
491
|
+
incomplete_files = sum(
|
|
492
|
+
1
|
|
493
|
+
for incomplete_path in incomplete_paths
|
|
494
|
+
for path in incomplete_path.rglob("*")
|
|
495
|
+
if path.is_file()
|
|
496
|
+
)
|
|
497
|
+
incomplete_size = sum(
|
|
498
|
+
path.stat().st_size
|
|
499
|
+
for incomplete_path in incomplete_paths
|
|
500
|
+
for path in incomplete_path.rglob("*")
|
|
501
|
+
if path.is_file()
|
|
502
|
+
)
|
|
503
|
+
print( # noqa: T201
|
|
504
|
+
f"\n{len(incomplete_paths)} incomplete resource(s): " +
|
|
505
|
+
f"{incomplete_files} files, {_human_size(incomplete_size)}"
|
|
506
|
+
)
|
|
507
|
+
else:
|
|
508
|
+
print("\nNo incomplete resources found.") # noqa: T201
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Datasets."""
|
|
2
|
+
|
|
3
|
+
from ._base import DataModule
|
|
4
|
+
from .figure_ground_convexity import FigureGroundConvexityDataModule
|
|
5
|
+
from .figure_ground_surroundedness import FigureGroundSurroundednessDataModule
|
|
6
|
+
from .figure_ground_symmetry import FigureGroundSymmetryDataModule
|
|
7
|
+
from .msra10k import MSRA10KDataModule, PrecomputedMSRA10KDataModule
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"DataModule",
|
|
11
|
+
"FigureGroundConvexityDataModule",
|
|
12
|
+
"FigureGroundSurroundednessDataModule",
|
|
13
|
+
"FigureGroundSymmetryDataModule",
|
|
14
|
+
"MSRA10KDataModule",
|
|
15
|
+
"PrecomputedMSRA10KDataModule",
|
|
16
|
+
]
|