Skip to content
This repository was archived by the owner on Apr 10, 2024. It is now read-only.

Commit 041bc45

Browse files
author
Ludwig Schubert
committed
Support saving and loading txt files, fix minor bugs in error reporting
1 parent b689bb8 commit 041bc45

6 files changed

Lines changed: 181 additions & 126 deletions

File tree

lucid/misc/io/loading.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,15 @@ def _load_json(handle, **kwargs):
7373
return json.load(handle)
7474

7575

76-
def _load_text(handle, encoding="utf-8"):
76+
def _load_text(handle, split=False, encoding="utf-8"):
7777
"""Load and decode a string."""
78-
return handle.read().decode(encoding)
78+
79+
string = handle.read().decode(encoding)
80+
81+
if split:
82+
return string.splitlines()
83+
else:
84+
return string
7985

8086

8187
def _load_graphdef_protobuf(handle, **kwargs):

lucid/misc/io/reading.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,6 @@ def read_handle(url, cache=None, mode="rb"):
110110
else:
111111
if scheme in ("http", "https"):
112112
handle = _handle_web_url(url, mode=mode)
113-
elif scheme == "gs":
114-
handle = _handle_gcs_url(url, mode=mode)
115113
else:
116114
handle = _handle_gfile(url, mode=mode)
117115

@@ -130,14 +128,6 @@ def _handle_web_url(url, mode="r"):
130128
return request.urlopen(url)
131129

132130

133-
def _handle_gcs_url(url, mode="r"):
134-
# TODO: transparently allow authenticated access through storage API
135-
_, resource_name = url.split("://")
136-
base_url = "https://storage.googleapis.com/"
137-
url = urljoin(base_url, resource_name)
138-
return _handle_web_url(url, mode=mode)
139-
140-
141131
# Helper Functions
142132

143133

lucid/misc/io/saving.py

Lines changed: 86 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,26 @@
1616
"""Method for saving arbitrary data to arbitrary destinations.
1717
1818
This module takes an object and URL, infers how to serialize and how to write
19-
it out to the destination.
19+
it out to the destination. The intention is to preserve your work under most
20+
circumstances, so sometimes this will convert values by default and warn rather than
21+
error out immediately. This sometimes means less predictable behavior.
2022
21-
If an object could have multiple representations, this tries to infer the
22-
intended representation from the URL's file extension.
23+
If an object could have multiple serializations, this tries to infer the
24+
intended serializations from the URL's file extension.
2325
2426
Possible extension: if not given a URL this could create one and return it?
2527
"""
2628

2729
from __future__ import absolute_import, division, print_function
2830

2931
import logging
32+
import warnings
3033
import os.path
3134
import json
3235
import numpy as np
3336
import PIL.Image
3437

35-
from lucid.misc.io.writing import write, write_handle
38+
from lucid.misc.io.writing import write_handle
3639
from lucid.misc.io.serialize_array import _normalize_array
3740

3841

@@ -41,82 +44,100 @@
4144

4245

4346
def save_json(object, handle, indent=2):
44-
"""Save object as json on CNS."""
45-
obj_json = json.dumps(object, indent=indent)
46-
handle.write(obj_json)
47+
"""Save object as json on CNS."""
48+
obj_json = json.dumps(object, indent=indent)
49+
handle.write(obj_json)
4750

4851

4952
def save_npy(object, handle):
50-
"""Save numpy array as npy file."""
51-
np.save(handle, object)
53+
"""Save numpy array as npy file."""
54+
np.save(handle, object)
5255

5356

5457
def save_npz(object, handle):
55-
"""Save dict of numpy array as npz file."""
56-
# there is a bug where savez doesn't actually accept a file handle.
57-
log.warning("Saving npz files currently only works locally. :/")
58-
path = handle.name
59-
handle.close()
60-
if type(object) is dict:
61-
np.savez(path, **object)
62-
elif type(object) is list:
63-
np.savez(path, *object)
64-
else:
65-
log.warning("Saving non dict or list as npz file, did you maybe want npy?")
66-
np.savez(path, object)
58+
"""Save dict of numpy array as npz file."""
59+
# there is a bug where savez doesn't actually accept a file handle.
60+
log.warning("Saving npz files currently only works locally. :/")
61+
path = handle.name
62+
handle.close()
63+
if type(object) is dict:
64+
np.savez(path, **object)
65+
elif type(object) is list:
66+
np.savez(path, *object)
67+
else:
68+
log.warning("Saving non dict or list as npz file, did you maybe want npy?")
69+
np.savez(path, object)
6770

6871

6972
def save_img(object, handle, **kwargs):
70-
"""Save numpy array as image file on CNS."""
71-
if isinstance(object, np.ndarray):
72-
normalized = _normalize_array(object)
73-
image = PIL.Image.fromarray(normalized)
74-
elif isinstance(object, PIL.Image):
75-
image = object
76-
else:
77-
raise ValueError("Can only save_img for numpy arrays or PIL.Images!")
78-
image.save(handle, **kwargs) # will infer format from handle's url ext.
73+
"""Save numpy array as image file on CNS."""
74+
75+
if isinstance(object, np.ndarray):
76+
normalized = _normalize_array(object)
77+
object = PIL.Image.fromarray(normalized)
78+
79+
if isinstance(object, PIL.Image.Image):
80+
object.save(handle, **kwargs) # will infer format from handle's url ext.
81+
else:
82+
raise ValueError("Can only save_img for numpy arrays or PIL.Images!")
83+
84+
85+
def save_txt(object, handle, **kwargs):
86+
if isinstance(object, str):
87+
handle.write(object)
88+
elif isinstance(object, list):
89+
for line in object:
90+
if not isinstance(line, str):
91+
line = repr(line)
92+
warnings.warn(
93+
"`save_txt` was called with an array containing objects other than "
94+
"strings; using `repr` to convert values to string."
95+
)
96+
if not line.endswith("\n"):
97+
line += "\n"
98+
handle.write(line)
7999

80100

81101
savers = {
82-
".png": save_img,
83-
".jpg": save_img,
84-
".jpeg": save_img,
85-
".npy": save_npy,
86-
".npz": save_npz,
87-
".json": save_json,
102+
".png": save_img,
103+
".jpg": save_img,
104+
".jpeg": save_img,
105+
".npy": save_npy,
106+
".npz": save_npz,
107+
".json": save_json,
108+
".txt": save_txt,
88109
}
89110

90111

91112
def save(thing, url_or_handle, **kwargs):
92-
"""Save object to file on CNS.
93-
94-
File format is inferred from path. Use save_img(), save_npy(), or save_json()
95-
if you need to force a particular format.
96-
97-
Args:
98-
obj: object to save.
99-
path: CNS path.
100-
101-
Raises:
102-
RuntimeError: If file extension not supported.
103-
"""
104-
is_handle = hasattr(url_or_handle, 'write') and hasattr(url_or_handle, 'name')
105-
if is_handle:
106-
_, ext = os.path.splitext(url_or_handle.name)
107-
else:
108-
_, ext = os.path.splitext(url_or_handle)
109-
if not ext:
110-
raise RuntimeError("No extension in URL: " + url_or_handle)
111-
112-
if ext in savers:
113-
saver = savers[ext]
113+
"""Save object to file on CNS.
114+
115+
File format is inferred from path. Use save_img(), save_npy(), or save_json()
116+
if you need to force a particular format.
117+
118+
Args:
119+
obj: object to save.
120+
path: CNS path.
121+
122+
Raises:
123+
RuntimeError: If file extension not supported.
124+
"""
125+
is_handle = hasattr(url_or_handle, "write") and hasattr(url_or_handle, "name")
114126
if is_handle:
115-
saver(thing, url_or_handle, **kwargs)
127+
_, ext = os.path.splitext(url_or_handle.name)
128+
else:
129+
_, ext = os.path.splitext(url_or_handle)
130+
if not ext:
131+
raise RuntimeError("No extension in URL: " + url_or_handle)
132+
133+
if ext in savers:
134+
saver = savers[ext]
135+
if is_handle:
136+
saver(thing, url_or_handle, **kwargs)
137+
else:
138+
with write_handle(url_or_handle) as handle:
139+
saver(thing, handle, **kwargs)
116140
else:
117-
with write_handle(url_or_handle) as handle:
118-
saver(thing, handle, **kwargs)
119-
else:
120-
saver_names = [(key, function.__name__) for (key, value) in savers.items()]
121-
message = "Unknown extension '{}', supports {}."
122-
raise RuntimeError(message.format(ext, saver_names))
141+
saver_names = [(key, fn.__name__) for (key, fn) in savers.items()]
142+
message = "Unknown extension '{}', supports {}."
143+
raise ValueError(message.format(ext, saver_names))

tests/fixtures/multiline.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Line 0
2+
Line 1
3+
Line 2
4+
Line 3
5+
Line 4
6+
Line 5
7+
Line 6
8+
Line 7
9+
Line 8
10+
Line 9

tests/misc/io/test_loading.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ def test_load_text():
2121
assert u"🐕" in string
2222

2323

24+
def test_load_multiline_text_as_list():
25+
path = "./tests/fixtures/multiline.txt"
26+
string_list = load(path, split=True)
27+
assert isinstance(string_list, list)
28+
assert all(isinstance(string, str) for string in string_list)
29+
30+
2431
def test_load_npy():
2532
path = "./tests/fixtures/array.npy"
2633
array = load(path)

0 commit comments

Comments
 (0)