Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/imageio-2.35.1-py3.8.egg/imageio/plugins/bsdf.py: 19%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

137 statements  

1# -*- coding: utf-8 -*- 

2# imageio is distributed under the terms of the (new) BSD License. 

3 

4""" Read/Write BSDF files. 

5 

6Backend Library: internal 

7 

8The BSDF format enables reading and writing of image data in the 

9BSDF serialization format. This format allows storage of images, volumes, 

10and series thereof. Data can be of any numeric data type, and can 

11optionally be compressed. Each image/volume can have associated 

12meta data, which can consist of any data type supported by BSDF. 

13 

14By default, image data is lazily loaded; the actual image data is 

15not read until it is requested. This allows storing multiple images 

16in a single file and still have fast access to individual images. 

17Alternatively, a series of images can be read in streaming mode, reading 

18images as they are read (e.g. from http). 

19 

20BSDF is a simple generic binary format. It is easy to extend and there 

21are standard extension definitions for 2D and 3D image data. 

22Read more at http://bsdf.io. 

23 

24 

25Parameters 

26---------- 

27random_access : bool 

28 Whether individual images in the file can be read in random order. 

29 Defaults to True for normal files, and to False when reading from HTTP. 

30 If False, the file is read in "streaming mode", allowing reading 

31 files as they are read, but without support for "rewinding". 

32 Note that setting this to True when reading from HTTP, the whole file 

33 is read upon opening it (since lazy loading is not possible over HTTP). 

34 

35compression : int 

36 Use ``0`` or "no" for no compression, ``1`` or "zlib" for Zlib 

37 compression (same as zip files and PNG), and ``2`` or "bz2" for Bz2 

38 compression (more compact but slower). Default 1 (zlib). 

39 Note that some BSDF implementations may not support compression 

40 (e.g. JavaScript). 

41 

42""" 

43 

44import numpy as np 

45 

46from ..core import Format 

47 

48 

49def get_bsdf_serializer(options): 

50 from . import _bsdf as bsdf 

51 

52 class NDArrayExtension(bsdf.Extension): 

53 """Copy of BSDF's NDArrayExtension but deal with lazy blobs.""" 

54 

55 name = "ndarray" 

56 cls = np.ndarray 

57 

58 def encode(self, s, v): 

59 return dict(shape=v.shape, dtype=str(v.dtype), data=v.tobytes()) 

60 

61 def decode(self, s, v): 

62 return v # return as dict, because of lazy blobs, decode in Image 

63 

64 class ImageExtension(bsdf.Extension): 

65 """We implement two extensions that trigger on the Image classes.""" 

66 

67 def encode(self, s, v): 

68 return dict(array=v.array, meta=v.meta) 

69 

70 def decode(self, s, v): 

71 return Image(v["array"], v["meta"]) 

72 

73 class Image2DExtension(ImageExtension): 

74 name = "image2d" 

75 cls = Image2D 

76 

77 class Image3DExtension(ImageExtension): 

78 name = "image3d" 

79 cls = Image3D 

80 

81 exts = [NDArrayExtension, Image2DExtension, Image3DExtension] 

82 serializer = bsdf.BsdfSerializer(exts, **options) 

83 

84 return bsdf, serializer 

85 

86 

87class Image: 

88 """Class in which we wrap the array and meta data. By using an extension 

89 we can make BSDF trigger on these classes and thus encode the images. 

90 as actual images. 

91 """ 

92 

93 def __init__(self, array, meta): 

94 self.array = array 

95 self.meta = meta 

96 

97 def get_array(self): 

98 if not isinstance(self.array, np.ndarray): 

99 v = self.array 

100 blob = v["data"] 

101 if not isinstance(blob, bytes): # then it's a lazy bsdf.Blob 

102 blob = blob.get_bytes() 

103 self.array = np.frombuffer(blob, dtype=v["dtype"]) 

104 self.array.shape = v["shape"] 

105 return self.array 

106 

107 def get_meta(self): 

108 return self.meta 

109 

110 

111class Image2D(Image): 

112 pass 

113 

114 

115class Image3D(Image): 

116 pass 

117 

118 

119class BsdfFormat(Format): 

120 """The BSDF format enables reading and writing of image data in the 

121 BSDF serialization format. This format allows storage of images, volumes, 

122 and series thereof. Data can be of any numeric data type, and can 

123 optionally be compressed. Each image/volume can have associated 

124 meta data, which can consist of any data type supported by BSDF. 

125 

126 By default, image data is lazily loaded; the actual image data is 

127 not read until it is requested. This allows storing multiple images 

128 in a single file and still have fast access to individual images. 

129 Alternatively, a series of images can be read in streaming mode, reading 

130 images as they are read (e.g. from http). 

131 

132 BSDF is a simple generic binary format. It is easy to extend and there 

133 are standard extension definitions for 2D and 3D image data. 

134 Read more at http://bsdf.io. 

135 

136 Parameters for reading 

137 ---------------------- 

138 random_access : bool 

139 Whether individual images in the file can be read in random order. 

140 Defaults to True for normal files, and to False when reading from HTTP. 

141 If False, the file is read in "streaming mode", allowing reading 

142 files as they are read, but without support for "rewinding". 

143 Note that setting this to True when reading from HTTP, the whole file 

144 is read upon opening it (since lazy loading is not possible over HTTP). 

145 

146 Parameters for saving 

147 --------------------- 

148 compression : {0, 1, 2} 

149 Use ``0`` or "no" for no compression, ``1`` or "zlib" for Zlib 

150 compression (same as zip files and PNG), and ``2`` or "bz2" for Bz2 

151 compression (more compact but slower). Default 1 (zlib). 

152 Note that some BSDF implementations may not support compression 

153 (e.g. JavaScript). 

154 

155 """ 

156 

157 def _can_read(self, request): 

158 if request.mode[1] in (self.modes + "?"): 

159 # if request.extension in self.extensions: 

160 # return True 

161 if request.firstbytes.startswith(b"BSDF"): 

162 return True 

163 

164 def _can_write(self, request): 

165 if request.mode[1] in (self.modes + "?"): 

166 if request.extension in self.extensions: 

167 return True 

168 

169 # -- reader 

170 

171 class Reader(Format.Reader): 

172 def _open(self, random_access=None): 

173 # Validate - we need a BSDF file consisting of a list of images 

174 # The list is typically a stream, but does not have to be. 

175 assert self.request.firstbytes[:4] == b"BSDF", "Not a BSDF file" 

176 # self.request.firstbytes[5:6] == major and minor version 

177 if not ( 

178 self.request.firstbytes[6:15] == b"M\x07image2D" 

179 or self.request.firstbytes[6:15] == b"M\x07image3D" 

180 or self.request.firstbytes[6:7] == b"l" 

181 ): 

182 pass # Actually, follow a more duck-type approach ... 

183 # raise RuntimeError('BSDF file does not look like an ' 

184 # 'image container.') 

185 # Set options. If we think that seeking is allowed, we lazily load 

186 # blobs, and set streaming to False (i.e. the whole file is read, 

187 # but we skip over binary blobs), so that we subsequently allow 

188 # random access to the images. 

189 # If seeking is not allowed (e.g. with a http request), we cannot 

190 # lazily load blobs, but we can still load streaming from the web. 

191 options = {} 

192 if self.request.filename.startswith(("http://", "https://")): 

193 ra = False if random_access is None else bool(random_access) 

194 options["lazy_blob"] = False # Because we cannot seek now 

195 options["load_streaming"] = not ra # Load as a stream? 

196 else: 

197 ra = True if random_access is None else bool(random_access) 

198 options["lazy_blob"] = ra # Don't read data until needed 

199 options["load_streaming"] = not ra 

200 

201 file = self.request.get_file() 

202 bsdf, self._serializer = get_bsdf_serializer(options) 

203 self._stream = self._serializer.load(file) 

204 # Another validation 

205 if ( 

206 isinstance(self._stream, dict) 

207 and "meta" in self._stream 

208 and "array" in self._stream 

209 ): 

210 self._stream = Image(self._stream["array"], self._stream["meta"]) 

211 if not isinstance(self._stream, (Image, list, bsdf.ListStream)): 

212 raise RuntimeError( 

213 "BSDF file does not look seem to have an " "image container." 

214 ) 

215 

216 def _close(self): 

217 pass 

218 

219 def _get_length(self): 

220 if isinstance(self._stream, Image): 

221 return 1 

222 elif isinstance(self._stream, list): 

223 return len(self._stream) 

224 elif self._stream.count < 0: 

225 return np.inf 

226 return self._stream.count 

227 

228 def _get_data(self, index): 

229 # Validate 

230 if index < 0 or index >= self.get_length(): 

231 raise IndexError( 

232 "Image index %i not in [0 %i]." % (index, self.get_length()) 

233 ) 

234 # Get Image object 

235 if isinstance(self._stream, Image): 

236 image_ob = self._stream # singleton 

237 elif isinstance(self._stream, list): 

238 # Easy when we have random access 

239 image_ob = self._stream[index] 

240 else: 

241 # For streaming, we need to skip over frames 

242 if index < self._stream.index: 

243 raise IndexError( 

244 "BSDF file is being read in streaming " 

245 "mode, thus does not allow rewinding." 

246 ) 

247 while index > self._stream.index: 

248 self._stream.next() 

249 image_ob = self._stream.next() # Can raise StopIteration 

250 # Is this an image? 

251 if ( 

252 isinstance(image_ob, dict) 

253 and "meta" in image_ob 

254 and "array" in image_ob 

255 ): 

256 image_ob = Image(image_ob["array"], image_ob["meta"]) 

257 if isinstance(image_ob, Image): 

258 # Return as array (if we have lazy blobs, they are read now) 

259 return image_ob.get_array(), image_ob.get_meta() 

260 else: 

261 r = repr(image_ob) 

262 r = r if len(r) < 200 else r[:197] + "..." 

263 raise RuntimeError("BSDF file contains non-image " + r) 

264 

265 def _get_meta_data(self, index): # pragma: no cover 

266 return {} # This format does not support global meta data 

267 

268 # -- writer 

269 

270 class Writer(Format.Writer): 

271 def _open(self, compression=1): 

272 options = {"compression": compression} 

273 bsdf, self._serializer = get_bsdf_serializer(options) 

274 if self.request.mode[1] in "iv": 

275 self._stream = None # Singleton image 

276 self._written = False 

277 else: 

278 # Series (stream) of images 

279 file = self.request.get_file() 

280 self._stream = bsdf.ListStream() 

281 self._serializer.save(file, self._stream) 

282 

283 def _close(self): 

284 # We close the stream here, which will mark the number of written 

285 # elements. If we would not close it, the file would be fine, it's 

286 # just that upon reading it would not be known how many items are 

287 # in there. 

288 if self._stream is not None: 

289 self._stream.close(False) # False says "keep this a stream" 

290 

291 def _append_data(self, im, meta): 

292 # Determine dimension 

293 ndim = None 

294 if self.request.mode[1] in "iI": 

295 ndim = 2 

296 elif self.request.mode[1] in "vV": 

297 ndim = 3 

298 else: 

299 ndim = 3 # Make an educated guess 

300 if im.ndim == 2 or (im.ndim == 3 and im.shape[-1] <= 4): 

301 ndim = 2 

302 # Validate shape 

303 assert ndim in (2, 3) 

304 if ndim == 2: 

305 assert im.ndim == 2 or (im.ndim == 3 and im.shape[-1] <= 4) 

306 else: 

307 assert im.ndim == 3 or (im.ndim == 4 and im.shape[-1] <= 4) 

308 # Wrap data and meta data in our special class that will trigger 

309 # the BSDF image2D or image3D extension. 

310 if ndim == 2: 

311 ob = Image2D(im, meta) 

312 else: 

313 ob = Image3D(im, meta) 

314 # Write directly or to stream 

315 if self._stream is None: 

316 assert not self._written, "Cannot write singleton image twice" 

317 self._written = True 

318 file = self.request.get_file() 

319 self._serializer.save(file, ob) 

320 else: 

321 self._stream.append(ob) 

322 

323 def set_meta_data(self, meta): # pragma: no cover 

324 raise RuntimeError("The BSDF format only supports " "per-image meta data.")