scope 0.2.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.
- scope-0.2.0/PKG-INFO +9 -0
- scope-0.2.0/README.md +0 -0
- scope-0.2.0/pyproject.toml +5 -0
- scope-0.2.0/scope/__init__.py +4 -0
- scope-0.2.0/scope/columns.py +184 -0
- scope-0.2.0/scope/reader.py +43 -0
- scope-0.2.0/scope/writer.py +71 -0
- scope-0.2.0/scope.egg-info/PKG-INFO +9 -0
- scope-0.2.0/scope.egg-info/SOURCES.txt +14 -0
- scope-0.2.0/scope.egg-info/dependency_links.txt +1 -0
- scope-0.2.0/scope.egg-info/top_level.txt +1 -0
- scope-0.2.0/setup.cfg +4 -0
- scope-0.2.0/setup.py +34 -0
- scope-0.2.0/tests/test_float.py +70 -0
- scope-0.2.0/tests/test_image.py +84 -0
- scope-0.2.0/tests/test_video.py +25 -0
scope-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: scope
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Metrics logging and analysis.
|
|
5
|
+
Home-page: http://github.com/danijar/scope
|
|
6
|
+
Classifier: Intended Audience :: Science/Research
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Description-Content-Type: text/markdown
|
scope-0.2.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import struct
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import av
|
|
6
|
+
import numpy as np
|
|
7
|
+
from PIL import Image
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def table_length(filename, fmt):
|
|
11
|
+
return filename.stat().st_size // struct.calcsize(fmt)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def table_write(filename, fmt, *cols):
|
|
15
|
+
rows = tuple(zip(*cols))
|
|
16
|
+
size = struct.calcsize(fmt)
|
|
17
|
+
buffer = bytearray(len(rows) * size)
|
|
18
|
+
for index, row in enumerate(rows):
|
|
19
|
+
struct.pack_into(fmt, buffer, index * size, *row)
|
|
20
|
+
with filename.open('ab') as f:
|
|
21
|
+
f.write(buffer)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def table_read(filename, fmt, start=0, stop=None):
|
|
25
|
+
assert stop is None or start < stop, (start, stop)
|
|
26
|
+
size = struct.calcsize(fmt)
|
|
27
|
+
with filename.open('rb') as f:
|
|
28
|
+
start and f.seek(start * size)
|
|
29
|
+
buffer = f.read((stop - start) * size if stop else None)
|
|
30
|
+
rows = struct.iter_unpack(fmt, buffer)
|
|
31
|
+
cols = tuple(zip(*rows))
|
|
32
|
+
return cols
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FloatColumn:
|
|
36
|
+
|
|
37
|
+
def __init__(self, logdir, key):
|
|
38
|
+
name = key.replace('/', '-') + '.float'
|
|
39
|
+
self.filename = logdir / name
|
|
40
|
+
|
|
41
|
+
def validate(self, value):
|
|
42
|
+
assert value.dtype in (float, int) and value.ndim == 0, (
|
|
43
|
+
value.dtype, value.shape)
|
|
44
|
+
return value
|
|
45
|
+
|
|
46
|
+
def write(self, values):
|
|
47
|
+
steps, values = zip(*values)
|
|
48
|
+
table_write(self.filename, '>qd', steps, values)
|
|
49
|
+
|
|
50
|
+
def length(self):
|
|
51
|
+
return table_length(self.filename, '>qd')
|
|
52
|
+
|
|
53
|
+
def read(self, start, stop):
|
|
54
|
+
steps, values = table_read(self.filename, '>qd')
|
|
55
|
+
filtered = [(s, v) for s, v in zip(steps, values) if start <= s < stop]
|
|
56
|
+
steps, values = zip(*filtered) if filtered else ([], [])
|
|
57
|
+
steps = np.array(steps, np.int64)
|
|
58
|
+
values = np.array(values, np.float64)
|
|
59
|
+
return steps, values
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class FileColumn:
|
|
63
|
+
|
|
64
|
+
def __init__(self, logdir, key, ext, encfn, decfn):
|
|
65
|
+
name = key.replace('/', '-') + '.' + ext
|
|
66
|
+
self.folder = logdir / name
|
|
67
|
+
self.folder.mkdir(exist_ok=True)
|
|
68
|
+
self.index = self.folder / 'index'
|
|
69
|
+
self.rng = np.random.default_rng(seed=None)
|
|
70
|
+
self.ext = ext
|
|
71
|
+
self.encfn = encfn
|
|
72
|
+
self.decfn = decfn
|
|
73
|
+
|
|
74
|
+
def validate(self, value):
|
|
75
|
+
raise NotImplementedError
|
|
76
|
+
|
|
77
|
+
def write(self, values):
|
|
78
|
+
prefix = int(time.time()).to_bytes(4, 'big')
|
|
79
|
+
steps, values = zip(*values)
|
|
80
|
+
idents = [prefix + self.rng.bytes(4) for _ in range(len(steps))]
|
|
81
|
+
for ident, step, value in zip(idents, steps, values):
|
|
82
|
+
buffer = self.encfn(value)
|
|
83
|
+
with self._filename(step, ident).open('wb') as f:
|
|
84
|
+
f.write(buffer)
|
|
85
|
+
table_write(self.index, 'q8s', steps, idents)
|
|
86
|
+
|
|
87
|
+
def length(self):
|
|
88
|
+
return table_length(self.index, 'q8s')
|
|
89
|
+
|
|
90
|
+
def read(self, start, stop):
|
|
91
|
+
steps, idents = table_read(self.index, 'q8s')
|
|
92
|
+
filtered = [(s, v) for s, v in zip(steps, idents) if start <= s < stop]
|
|
93
|
+
steps, idents = zip(*filtered) if filtered else ([], [])
|
|
94
|
+
values = []
|
|
95
|
+
for step, ident in zip(steps, idents):
|
|
96
|
+
with self._filename(step, ident).open('rb') as f:
|
|
97
|
+
buffer = f.read()
|
|
98
|
+
values.append(self.decfn(buffer))
|
|
99
|
+
steps = np.array(steps, np.int64)
|
|
100
|
+
values = tuple(values)
|
|
101
|
+
return steps, values
|
|
102
|
+
|
|
103
|
+
def _filename(self, step, ident):
|
|
104
|
+
return self.folder / f'{step:020}-{ident.hex()}.{self.ext}'
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class TextColumn(FileColumn):
|
|
108
|
+
|
|
109
|
+
def __init__(self, logdir, key, fmt='txt'):
|
|
110
|
+
super().__init__(logdir, key, fmt, self.encode, self.decode)
|
|
111
|
+
self.fmt = fmt
|
|
112
|
+
|
|
113
|
+
def validate(self, value):
|
|
114
|
+
assert isinstance(value, str), type(value)
|
|
115
|
+
return value
|
|
116
|
+
|
|
117
|
+
def encode(self, value):
|
|
118
|
+
return value.encode('utf-8')
|
|
119
|
+
|
|
120
|
+
def decode(self, buffer):
|
|
121
|
+
return buffer.decode('utf-8')
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ImageColumn(FileColumn):
|
|
125
|
+
|
|
126
|
+
def __init__(self, logdir, key, fmt='png', quality=None):
|
|
127
|
+
super().__init__(logdir, key, fmt, self.encode, self.decode)
|
|
128
|
+
self.fmt = fmt
|
|
129
|
+
self.quality = quality
|
|
130
|
+
|
|
131
|
+
def validate(self, value):
|
|
132
|
+
assert (
|
|
133
|
+
value.dtype == np.uint8 and value.ndim == 3 and
|
|
134
|
+
value.shape[-1] == 3), (value.dtype, value.shape)
|
|
135
|
+
return value
|
|
136
|
+
|
|
137
|
+
def encode(self, value):
|
|
138
|
+
fmt = ('jpeg' if self.fmt == 'jpg' else self.fmt).upper()
|
|
139
|
+
fp = io.BytesIO()
|
|
140
|
+
Image.fromarray(value).save(fp, format=fmt, quality=self.quality)
|
|
141
|
+
return fp.getvalue()
|
|
142
|
+
|
|
143
|
+
def decode(self, buffer):
|
|
144
|
+
return np.asarray(Image.open(io.BytesIO(buffer)).convert('RGB'))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class VideoColumn(FileColumn):
|
|
148
|
+
|
|
149
|
+
def __init__(self, logdir, key, fmt='mp4', fps=20, codec='h264'):
|
|
150
|
+
super().__init__(logdir, key, fmt, self.encode, self.decode)
|
|
151
|
+
self.fmt = fmt
|
|
152
|
+
self.fps = fps
|
|
153
|
+
self.codec = codec
|
|
154
|
+
|
|
155
|
+
def validate(self, value):
|
|
156
|
+
assert (
|
|
157
|
+
value.dtype == np.uint8 and value.ndim == 4 and
|
|
158
|
+
value.shape[-1] == 3), (value.dtype, value.shape)
|
|
159
|
+
return value
|
|
160
|
+
|
|
161
|
+
def encode(self, array):
|
|
162
|
+
T, H, W, C = array.shape
|
|
163
|
+
fp = io.BytesIO()
|
|
164
|
+
output = av.open(fp, mode='w', format=self.fmt)
|
|
165
|
+
stream = output.add_stream(self.codec, rate=float(self.fps))
|
|
166
|
+
stream.width = W
|
|
167
|
+
stream.height = H
|
|
168
|
+
stream.pix_fmt = 'yuv420p'
|
|
169
|
+
for t in range(T):
|
|
170
|
+
frame = av.VideoFrame.from_ndarray(array[t], format='rgb24')
|
|
171
|
+
frame.pts = t
|
|
172
|
+
output.mux(stream.encode(frame))
|
|
173
|
+
output.mux(stream.encode(None))
|
|
174
|
+
output.close()
|
|
175
|
+
return fp.getvalue()
|
|
176
|
+
|
|
177
|
+
def decode(self, buffer):
|
|
178
|
+
container = av.open(io.BytesIO(buffer))
|
|
179
|
+
array = []
|
|
180
|
+
for frame in container.decode(video=0):
|
|
181
|
+
array.append(frame.to_ndarray(format='rgb24'))
|
|
182
|
+
array = np.stack(array)
|
|
183
|
+
container.close()
|
|
184
|
+
return array
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import pathlib
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from . import columns
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Reader:
|
|
10
|
+
|
|
11
|
+
def __init__(self, logdir):
|
|
12
|
+
if isinstance(logdir, str):
|
|
13
|
+
logdir = pathlib.Path(logdir)
|
|
14
|
+
self.coltypes = {
|
|
15
|
+
'float': columns.FloatColumn,
|
|
16
|
+
'png': columns.ImageColumn,
|
|
17
|
+
'mp4': columns.VideoColumn,
|
|
18
|
+
}
|
|
19
|
+
self.columns = {}
|
|
20
|
+
for child in sorted(logdir.glob('*')):
|
|
21
|
+
name, ext = child.name.rsplit('.', 1)
|
|
22
|
+
key = name.replace('-', '/')
|
|
23
|
+
assert re.match(r'[a-z0-9_]+(/[a-z0-9_]+)?', key), key
|
|
24
|
+
self.columns[key] = self.coltypes[ext](logdir, key)
|
|
25
|
+
|
|
26
|
+
def keys(self):
|
|
27
|
+
return tuple(self.columns.keys())
|
|
28
|
+
|
|
29
|
+
def length(self, key):
|
|
30
|
+
return self.columns[key].length()
|
|
31
|
+
|
|
32
|
+
def __getitem__(self, index):
|
|
33
|
+
if isinstance(index, str):
|
|
34
|
+
key, start, stop = index, -np.inf, +np.inf
|
|
35
|
+
else:
|
|
36
|
+
key, pos = index
|
|
37
|
+
if isinstance(pos, int):
|
|
38
|
+
start, stop = pos, pos + 1
|
|
39
|
+
else:
|
|
40
|
+
assert pos.step is None
|
|
41
|
+
start = -np.inf if pos.start is None else pos.start
|
|
42
|
+
stop = +np.inf if pos.stop is None else pos.stop
|
|
43
|
+
return self.columns[key].read(start, stop)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import concurrent.futures
|
|
2
|
+
import pathlib
|
|
3
|
+
import re
|
|
4
|
+
from functools import partial as bind
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from . import columns
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Writer:
|
|
12
|
+
|
|
13
|
+
def __init__(self, logdir, fps=20, workers=32):
|
|
14
|
+
if isinstance(logdir, str):
|
|
15
|
+
logdir = pathlib.Path(logdir)
|
|
16
|
+
self.logdir = logdir
|
|
17
|
+
self.logdir.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
self.fps = fps
|
|
19
|
+
self.workers = workers
|
|
20
|
+
self.coltypes = [
|
|
21
|
+
(lambda x: isinstance(x, str), columns.TextColumn),
|
|
22
|
+
(lambda x: x.ndim == 0, columns.FloatColumn),
|
|
23
|
+
(lambda x: x.ndim == 3, bind(columns.ImageColumn, fmt='png')),
|
|
24
|
+
(lambda x: x.ndim == 4, bind(columns.VideoColumn, fmt='mp4', fps=fps)),
|
|
25
|
+
]
|
|
26
|
+
self.columns = {}
|
|
27
|
+
self.values = {}
|
|
28
|
+
if workers:
|
|
29
|
+
self.pool = concurrent.futures.ThreadPoolExecutor(workers, 'writer')
|
|
30
|
+
self.futures = []
|
|
31
|
+
|
|
32
|
+
def add(self, step, *args, **kwargs):
|
|
33
|
+
step = int(step)
|
|
34
|
+
mapping = dict(*args, **kwargs)
|
|
35
|
+
for key, value in mapping.items():
|
|
36
|
+
value = value if isinstance(value, str) else np.asarray(value)
|
|
37
|
+
if key not in self.columns:
|
|
38
|
+
assert re.match(r'[a-z0-9_]+(/[a-z0-9_]+)?', key), key
|
|
39
|
+
for applies, coltype in self.coltypes:
|
|
40
|
+
if applies(value):
|
|
41
|
+
break
|
|
42
|
+
else:
|
|
43
|
+
raise NotImplementedError((
|
|
44
|
+
key, value,
|
|
45
|
+
getattr(value, 'shape', None),
|
|
46
|
+
getattr(value, 'dtype', None)))
|
|
47
|
+
self.columns[key] = coltype(self.logdir, key)
|
|
48
|
+
self.values[key] = []
|
|
49
|
+
column = self.columns[key]
|
|
50
|
+
try:
|
|
51
|
+
value = column.validate(value)
|
|
52
|
+
except Exception:
|
|
53
|
+
print(f"Error validating key '{key}' with value '{value}'.")
|
|
54
|
+
raise
|
|
55
|
+
self.values[key].append((step, value))
|
|
56
|
+
|
|
57
|
+
def flush(self):
|
|
58
|
+
keys = [key for key, values in self.values.items() if values]
|
|
59
|
+
if self.workers:
|
|
60
|
+
list(self.futures)
|
|
61
|
+
columns = [self.columns[x] for x in keys]
|
|
62
|
+
values = [self.values[x] for x in keys]
|
|
63
|
+
self.futures = self.pool.map(lambda x, y: x.write(y), columns, values)
|
|
64
|
+
else:
|
|
65
|
+
for key in keys:
|
|
66
|
+
try:
|
|
67
|
+
self.columns[key].write(self.values[key])
|
|
68
|
+
except Exception:
|
|
69
|
+
print(f"Exception writing '{key}' column.")
|
|
70
|
+
raise
|
|
71
|
+
self.values = {key: [] for key in self.values.keys()}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: scope
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Metrics logging and analysis.
|
|
5
|
+
Home-page: http://github.com/danijar/scope
|
|
6
|
+
Classifier: Intended Audience :: Science/Research
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
scope/__init__.py
|
|
5
|
+
scope/columns.py
|
|
6
|
+
scope/reader.py
|
|
7
|
+
scope/writer.py
|
|
8
|
+
scope.egg-info/PKG-INFO
|
|
9
|
+
scope.egg-info/SOURCES.txt
|
|
10
|
+
scope.egg-info/dependency_links.txt
|
|
11
|
+
scope.egg-info/top_level.txt
|
|
12
|
+
tests/test_float.py
|
|
13
|
+
tests/test_image.py
|
|
14
|
+
tests/test_video.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
scope
|
scope-0.2.0/setup.cfg
ADDED
scope-0.2.0/setup.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import re
|
|
3
|
+
import setuptools
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse_requirements(filename):
|
|
7
|
+
requirements = pathlib.Path(filename)
|
|
8
|
+
requirements = requirements.read_text().split('\n')
|
|
9
|
+
requirements = [x for x in requirements if x.strip()]
|
|
10
|
+
return requirements
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_version(filename):
|
|
14
|
+
text = (pathlib.Path(__file__).parent / filename).read_text()
|
|
15
|
+
version = re.search(r"__version__ = '(.*)'", text).group(1)
|
|
16
|
+
return version
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
setuptools.setup(
|
|
20
|
+
name='scope',
|
|
21
|
+
version=parse_version('scope/__init__.py'),
|
|
22
|
+
description='Metrics logging and analysis.',
|
|
23
|
+
url='http://github.com/danijar/scope',
|
|
24
|
+
long_description=pathlib.Path('README.md').read_text(),
|
|
25
|
+
long_description_content_type='text/markdown',
|
|
26
|
+
packages=['scope'],
|
|
27
|
+
include_package_data=True,
|
|
28
|
+
install_requires=parse_requirements('requirements.txt'),
|
|
29
|
+
classifiers=[
|
|
30
|
+
'Intended Audience :: Science/Research',
|
|
31
|
+
'License :: OSI Approved :: MIT License',
|
|
32
|
+
'Programming Language :: Python :: 3',
|
|
33
|
+
],
|
|
34
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
|
|
3
|
+
import scope
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestFloat:
|
|
8
|
+
|
|
9
|
+
def test_roundtrip(self, tmpdir):
|
|
10
|
+
logdir = pathlib.Path(tmpdir)
|
|
11
|
+
writer = scope.Writer(logdir, workers=0)
|
|
12
|
+
writer.add(0, {'foo': 12})
|
|
13
|
+
writer.add(5, {'foo': 42, 'bar': np.float64(np.pi)})
|
|
14
|
+
writer.flush()
|
|
15
|
+
assert {x.name for x in logdir.glob('*')} == {'foo.float', 'bar.float'}
|
|
16
|
+
assert (logdir / 'foo.float').stat().st_size == (8 + 8) * 2
|
|
17
|
+
assert (logdir / 'bar.float').stat().st_size == (8 + 8) * 1
|
|
18
|
+
reader = scope.Reader(logdir)
|
|
19
|
+
assert reader.keys() == tuple(sorted(['foo', 'bar']))
|
|
20
|
+
assert reader.length('foo') == 2
|
|
21
|
+
assert reader.length('bar') == 1
|
|
22
|
+
assert equal(reader['foo'], ([0, 5], [12, 42]), (np.int64, np.float64))
|
|
23
|
+
assert equal(reader['bar'], ([5], [np.pi]), (np.int64, np.float64))
|
|
24
|
+
|
|
25
|
+
def test_slicing(self, tmpdir):
|
|
26
|
+
logdir = pathlib.Path(tmpdir)
|
|
27
|
+
writer = scope.Writer(logdir, workers=0)
|
|
28
|
+
writer.add(0, {'foo': 12})
|
|
29
|
+
writer.add(5, {'foo': 42})
|
|
30
|
+
writer.flush()
|
|
31
|
+
reader = scope.Reader(logdir)
|
|
32
|
+
assert equal(reader['foo', 0], ([0], [12]))
|
|
33
|
+
assert equal(reader['foo', :2], ([0], [12]))
|
|
34
|
+
assert equal(reader['foo', :5], ([0], [12]))
|
|
35
|
+
assert equal(reader['foo', :6], ([0, 5], [12, 42]))
|
|
36
|
+
assert equal(reader['foo', 1:6], ([5], [42]))
|
|
37
|
+
assert equal(reader['foo', :-1], ([], []))
|
|
38
|
+
assert equal(reader['foo', 7:], ([], []))
|
|
39
|
+
|
|
40
|
+
def test_workers(self, tmpdir):
|
|
41
|
+
logdir = pathlib.Path(tmpdir)
|
|
42
|
+
writer = scope.Writer(logdir, workers=8)
|
|
43
|
+
for step in range(10):
|
|
44
|
+
writer.add(step, {'foo': step, 'bar': step})
|
|
45
|
+
writer.flush()
|
|
46
|
+
writer.flush() # Block until previous flush is done.
|
|
47
|
+
assert {x.name for x in logdir.glob('*')} == {'foo.float', 'bar.float'}
|
|
48
|
+
assert (logdir / 'foo.float').stat().st_size == (8 + 8) * 10
|
|
49
|
+
assert (logdir / 'bar.float').stat().st_size == (8 + 8) * 10
|
|
50
|
+
reader = scope.Reader(logdir)
|
|
51
|
+
assert equal(reader['foo'], (np.arange(10), np.arange(10)))
|
|
52
|
+
assert equal(reader['bar'], (np.arange(10), np.arange(10)))
|
|
53
|
+
|
|
54
|
+
def test_namescopes(self, tmpdir):
|
|
55
|
+
logdir = pathlib.Path(tmpdir)
|
|
56
|
+
writer = scope.Writer(logdir, workers=0)
|
|
57
|
+
writer.add(0, {'foo/bar': 12})
|
|
58
|
+
writer.flush()
|
|
59
|
+
assert {x.name for x in logdir.glob('*')} == {'foo-bar.float'}
|
|
60
|
+
reader = scope.Reader(logdir)
|
|
61
|
+
assert reader.keys() == ('foo/bar',)
|
|
62
|
+
assert reader.length('foo/bar') == 1
|
|
63
|
+
assert equal(reader['foo/bar'], ([0], [12]), (np.int64, np.float64))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def equal(actuals, references, dtypes=None):
|
|
67
|
+
dtypes = dtypes or [x.dtype for x in actuals]
|
|
68
|
+
assert len(actuals) == len(references) == len(dtypes)
|
|
69
|
+
references = [np.asarray(x, d) for x, d in zip(actuals, dtypes)]
|
|
70
|
+
return all((x == y).all() for x, y in zip(actuals, references))
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
|
|
3
|
+
import scope
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestImage:
|
|
8
|
+
|
|
9
|
+
def test_roundtrip(self, tmpdir):
|
|
10
|
+
logdir = pathlib.Path(tmpdir)
|
|
11
|
+
writer = scope.Writer(logdir, workers=0)
|
|
12
|
+
img1 = np.ones((64, 128, 3), np.uint8) + 12
|
|
13
|
+
img2 = np.ones((64, 128, 3), np.uint8) + 255
|
|
14
|
+
writer.add(0, {'foo': img1})
|
|
15
|
+
writer.add(5, {'foo': img2})
|
|
16
|
+
writer.flush()
|
|
17
|
+
assert {x.name for x in logdir.glob('*')} == {'foo.png'}
|
|
18
|
+
assert (logdir / 'foo.png' / 'index').stat().st_size == (8 + 8) * 2
|
|
19
|
+
assert len(list((logdir / 'foo.png').glob('*'))) == 1 + 2
|
|
20
|
+
reader = scope.Reader(logdir)
|
|
21
|
+
assert reader.keys() == ('foo',)
|
|
22
|
+
assert reader.length('foo') == 2
|
|
23
|
+
steps, values = reader['foo']
|
|
24
|
+
assert (steps == np.array([0, 5])).all()
|
|
25
|
+
assert (values == np.array([img1, img2])).all()
|
|
26
|
+
|
|
27
|
+
def test_slicing(self, tmpdir):
|
|
28
|
+
logdir = pathlib.Path(tmpdir)
|
|
29
|
+
writer = scope.Writer(logdir, workers=0)
|
|
30
|
+
img1 = np.ones((64, 128, 3), np.uint8) + 12
|
|
31
|
+
img2 = np.ones((64, 128, 3), np.uint8) + 255
|
|
32
|
+
writer.add(0, {'foo': img1})
|
|
33
|
+
writer.add(5, {'foo': img2})
|
|
34
|
+
writer.flush()
|
|
35
|
+
assert {x.name for x in logdir.glob('*')} == {'foo.png'}
|
|
36
|
+
assert (logdir / 'foo.png' / 'index').stat().st_size == (8 + 8) * 2
|
|
37
|
+
reader = scope.Reader(logdir)
|
|
38
|
+
assert reader.keys() == ('foo',)
|
|
39
|
+
assert reader.length('foo') == 2
|
|
40
|
+
steps, values = reader['foo']
|
|
41
|
+
assert (steps == np.array([0, 5])).all()
|
|
42
|
+
assert (values == np.array([img1, img2])).all()
|
|
43
|
+
assert (reader['foo', 0][1] == img1[None]).all()
|
|
44
|
+
assert (reader['foo', :5][1] == img1[None]).all()
|
|
45
|
+
assert (reader['foo', :6][1] == np.array([img1, img2])).all()
|
|
46
|
+
assert (reader['foo', 1:6][1] == img2[None]).all()
|
|
47
|
+
assert reader['foo', :-1][1] == ()
|
|
48
|
+
assert reader['foo', 6:][1] == ()
|
|
49
|
+
|
|
50
|
+
def test_workers(self, tmpdir):
|
|
51
|
+
logdir = pathlib.Path(tmpdir)
|
|
52
|
+
writer = scope.Writer(logdir, workers=8)
|
|
53
|
+
for step in range(5):
|
|
54
|
+
for key in ('foo', 'bar', 'baz'):
|
|
55
|
+
writer.add(step, {key: np.full((64, 128, 3), step, np.uint8)})
|
|
56
|
+
writer.flush()
|
|
57
|
+
writer.flush() # Block until previous flush is done.
|
|
58
|
+
assert {x.name for x in logdir.glob('*')} == {
|
|
59
|
+
'foo.png', 'bar.png', 'baz.png'}
|
|
60
|
+
for key in ('foo', 'bar', 'baz'):
|
|
61
|
+
assert (logdir / f'{key}.png' / 'index').stat().st_size == (8 + 8) * 5
|
|
62
|
+
assert len(list((logdir / f'{key}.png').glob('*'))) == 1 + 5
|
|
63
|
+
reader = scope.Reader(logdir)
|
|
64
|
+
assert reader.keys() == tuple(sorted(['foo', 'bar', 'baz']))
|
|
65
|
+
for key in ('foo', 'bar', 'baz'):
|
|
66
|
+
assert reader.length(key) == 5
|
|
67
|
+
steps, values = reader[key]
|
|
68
|
+
assert (steps == np.arange(5)).all()
|
|
69
|
+
assert all(x.dtype == np.uint8 for x in values)
|
|
70
|
+
reference = np.arange(5, dtype=np.uint8)[:, None, None, None]
|
|
71
|
+
assert (np.array(values) == reference).all()
|
|
72
|
+
|
|
73
|
+
def test_namescopes(self, tmpdir):
|
|
74
|
+
img = np.ones((64, 128, 3), np.uint8) + 12
|
|
75
|
+
logdir = pathlib.Path(tmpdir)
|
|
76
|
+
writer = scope.Writer(logdir, workers=0)
|
|
77
|
+
writer.add(0, {'foo/bar': img})
|
|
78
|
+
writer.flush()
|
|
79
|
+
assert {x.name for x in logdir.glob('*')} == {'foo-bar.png'}
|
|
80
|
+
assert len(list((logdir / 'foo-bar.png').glob('*'))) == 1 + 1
|
|
81
|
+
reader = scope.Reader(logdir)
|
|
82
|
+
assert reader.keys() == ('foo/bar',)
|
|
83
|
+
assert reader.length('foo/bar') == 1
|
|
84
|
+
assert (reader['foo/bar'][1] == img).all()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
|
|
3
|
+
import scope
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestVideo:
|
|
8
|
+
|
|
9
|
+
def test_roundtrip(self, tmpdir):
|
|
10
|
+
logdir = pathlib.Path(tmpdir)
|
|
11
|
+
writer = scope.Writer(logdir, workers=0)
|
|
12
|
+
vid1 = np.ones((5, 64, 128, 3), np.uint8) + 12
|
|
13
|
+
vid2 = np.ones((5, 64, 128, 3), np.uint8) + 255
|
|
14
|
+
writer.add(0, {'foo': vid1})
|
|
15
|
+
writer.add(5, {'foo': vid2})
|
|
16
|
+
writer.flush()
|
|
17
|
+
assert {x.name for x in logdir.glob('*')} == {'foo.mp4'}
|
|
18
|
+
assert (logdir / 'foo.mp4' / 'index').stat().st_size == (8 + 8) * 2
|
|
19
|
+
assert len(list((logdir / 'foo.mp4').glob('*'))) == 1 + 2
|
|
20
|
+
reader = scope.Reader(logdir)
|
|
21
|
+
assert reader.keys() == ('foo',)
|
|
22
|
+
assert reader.length('foo') == 2
|
|
23
|
+
steps, values = reader['foo']
|
|
24
|
+
assert (steps == np.array([0, 5])).all()
|
|
25
|
+
assert np.allclose(values, [vid1, vid2], rtol=0.1)
|