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

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

109 statements  

1"""Read/Write TIFF files using tifffile. 

2 

3.. note:: 

4 To use this plugin you need to have `tifffile 

5 <https://github.com/cgohlke/tifffile>`_ installed:: 

6 

7 pip install tifffile 

8 

9This plugin wraps tifffile, a powerful library to manipulate TIFF files. It 

10superseeds our previous tifffile plugin and aims to expose all the features of 

11tifffile. 

12 

13The plugin treats individual TIFF series as ndimages. A series is a sequence of 

14TIFF pages that, when combined describe a meaningful unit, e.g., a volumetric 

15image (where each slice is stored on an individual page) or a multi-color 

16staining picture (where each stain is stored on an individual page). Different 

17TIFF flavors/variants use series in different ways and, as such, the resulting 

18reading behavior may vary depending on the program used while creating a 

19particular TIFF file. 

20 

21Methods 

22------- 

23.. note:: 

24 Check the respective function for a list of supported kwargs and detailed 

25 documentation. 

26 

27.. autosummary:: 

28 :toctree: 

29 

30 TifffilePlugin.read 

31 TifffilePlugin.iter 

32 TifffilePlugin.write 

33 TifffilePlugin.properties 

34 TifffilePlugin.metadata 

35 

36Additional methods available inside the :func:`imopen <imageio.v3.imopen>` 

37context: 

38 

39.. autosummary:: 

40 :toctree: 

41 

42 TifffilePlugin.iter_pages 

43 

44""" 

45 

46from io import BytesIO 

47from typing import Any, Dict, Optional, cast 

48import warnings 

49 

50import numpy as np 

51import tifffile 

52 

53from ..core.request import URI_BYTES, InitializationError, Request 

54from ..core.v3_plugin_api import ImageProperties, PluginV3 

55from ..typing import ArrayLike 

56 

57 

58def _get_resolution(page: tifffile.TiffPage) -> Dict[str, Any]: 

59 metadata = {} 

60 

61 try: 

62 metadata["resolution_unit"] = page.tags[296].value.value 

63 except KeyError: 

64 # tag 296 missing 

65 return metadata 

66 

67 try: 

68 resolution_x = page.tags[282].value 

69 resolution_y = page.tags[283].value 

70 

71 metadata["resolution"] = ( 

72 resolution_x[0] / resolution_x[1], 

73 resolution_y[0] / resolution_y[1], 

74 ) 

75 except KeyError: 

76 # tag 282 or 283 missing 

77 pass 

78 except ZeroDivisionError: 

79 warnings.warn( 

80 "Ignoring resolution metadata because at least one direction has a 0 " 

81 "denominator.", 

82 RuntimeWarning, 

83 ) 

84 

85 return metadata 

86 

87 

88class TifffilePlugin(PluginV3): 

89 """Support for tifffile as backend. 

90 

91 Parameters 

92 ---------- 

93 request : iio.Request 

94 A request object that represents the users intent. It provides a 

95 standard interface for a plugin to access the various ImageResources. 

96 Check the docs for details. 

97 kwargs : Any 

98 Additional kwargs are forwarded to tifffile's constructor, i.e. 

99 to ``TiffFile`` for reading or ``TiffWriter`` for writing. 

100 

101 """ 

102 

103 def __init__(self, request: Request, **kwargs) -> None: 

104 super().__init__(request) 

105 self._fh = None 

106 

107 if request.mode.io_mode == "r": 

108 try: 

109 self._fh = tifffile.TiffFile(request.get_file(), **kwargs) 

110 except tifffile.tifffile.TiffFileError: 

111 raise InitializationError("Tifffile can not read this file.") 

112 else: 

113 self._fh = tifffile.TiffWriter(request.get_file(), **kwargs) 

114 

115 # --------------------- 

116 # Standard V3 Interface 

117 # --------------------- 

118 

119 def read(self, *, index: int = None, page: int = None, **kwargs) -> np.ndarray: 

120 """Read a ndimage or page. 

121 

122 The ndimage returned depends on the value of both ``index`` and 

123 ``page``. ``index`` selects the series to read and ``page`` allows 

124 selecting a single page from the selected series. If ``index=None``, 

125 ``page`` is understood as a flat index, i.e., the selection ignores 

126 individual series inside the file. If both ``index`` and ``page`` are 

127 ``None``, then all the series are read and returned as a batch. 

128 

129 Parameters 

130 ---------- 

131 index : int 

132 If ``int``, select the ndimage (series) located at that index inside 

133 the file and return ``page`` from it. If ``None`` and ``page`` is 

134 ``int`` read the page located at that (flat) index inside the file. 

135 If ``None`` and ``page=None``, read all ndimages from the file and 

136 return them as a batch. 

137 page : int 

138 If ``None`` return the full selected ndimage. If ``int``, read the 

139 page at the selected index and return it. 

140 kwargs : Any 

141 Additional kwargs are forwarded to TiffFile's ``as_array`` method. 

142 

143 Returns 

144 ------- 

145 ndarray : np.ndarray 

146 The decoded ndimage or page. 

147 """ 

148 

149 if "key" not in kwargs: 

150 kwargs["key"] = page 

151 elif page is not None: 

152 raise ValueError("Can't use `page` and `key` at the same time.") 

153 

154 # set plugin default for ``index`` 

155 if index is not None and "series" in kwargs: 

156 raise ValueError("Can't use `series` and `index` at the same time.") 

157 elif "series" in kwargs: 

158 index = kwargs.pop("series") 

159 elif index is not None: 

160 pass 

161 else: 

162 index = 0 

163 

164 if index is Ellipsis and page is None: 

165 # read all series in the file and return them as a batch 

166 ndimage = np.stack([x for x in self.iter(**kwargs)]) 

167 else: 

168 index = None if index is Ellipsis else index 

169 ndimage = self._fh.asarray(series=index, **kwargs) 

170 

171 return ndimage 

172 

173 def iter(self, **kwargs) -> np.ndarray: 

174 """Yield ndimages from the TIFF. 

175 

176 Parameters 

177 ---------- 

178 kwargs : Any 

179 Additional kwargs are forwarded to the TiffPageSeries' ``as_array`` 

180 method. 

181 

182 Yields 

183 ------ 

184 ndimage : np.ndarray 

185 A decoded ndimage. 

186 """ 

187 

188 for sequence in self._fh.series: 

189 yield sequence.asarray(**kwargs) 

190 

191 def write( 

192 self, ndimage: ArrayLike, *, is_batch: bool = False, **kwargs 

193 ) -> Optional[bytes]: 

194 """Save a ndimage as TIFF. 

195 

196 Parameters 

197 ---------- 

198 ndimage : ArrayLike 

199 The ndimage to encode and write to the ImageResource. 

200 is_batch : bool 

201 If True, the first dimension of the given ndimage is treated as a 

202 batch dimension and each element will create a new series. 

203 kwargs : Any 

204 Additional kwargs are forwarded to TiffWriter's ``write`` method. 

205 

206 Returns 

207 ------- 

208 encoded_image : bytes 

209 If the ImageResource is ``"<bytes>"``, return the encoded bytes. 

210 Otherwise write returns None. 

211 

212 Notes 

213 ----- 

214 Incremental writing is supported. Subsequent calls to ``write`` will 

215 create new series unless ``contiguous=True`` is used, in which case the 

216 call to write will append to the current series. 

217 

218 """ 

219 

220 if not is_batch: 

221 ndimage = np.asarray(ndimage)[None, :] 

222 

223 for image in ndimage: 

224 self._fh.write(image, **kwargs) 

225 

226 if self._request._uri_type == URI_BYTES: 

227 self._fh.close() 

228 file = cast(BytesIO, self._request.get_file()) 

229 return file.getvalue() 

230 

231 def metadata( 

232 self, *, index: int = Ellipsis, page: int = None, exclude_applied: bool = True 

233 ) -> Dict[str, Any]: 

234 """Format-Specific TIFF metadata. 

235 

236 The metadata returned depends on the value of both ``index`` and 

237 ``page``. ``index`` selects a series and ``page`` allows selecting a 

238 single page from the selected series. If ``index=Ellipsis``, ``page`` is 

239 understood as a flat index, i.e., the selection ignores individual 

240 series inside the file. If ``index=Ellipsis`` and ``page=None`` then 

241 global (file-level) metadata is returned. 

242 

243 Parameters 

244 ---------- 

245 index : int 

246 Select the series of which to extract metadata from. If Ellipsis, treat 

247 page as a flat index into the file's pages. 

248 page : int 

249 If not None, select the page of which to extract metadata from. If 

250 None, read series-level metadata or, if ``index=...`` global, 

251 file-level metadata. 

252 exclude_applied : bool 

253 For API compatibility. Currently ignored. 

254 

255 Returns 

256 ------- 

257 metadata : dict 

258 A dictionary with information regarding the tiff flavor (file-level) 

259 or tiff tags (page-level). 

260 """ 

261 

262 if index is not Ellipsis and page is not None: 

263 target = self._fh.series[index].pages[page] 

264 elif index is not Ellipsis and page is None: 

265 # This is based on my understanding that series-level metadata is 

266 # stored in the first TIFF page. 

267 target = self._fh.series[index].pages[0] 

268 elif index is Ellipsis and page is not None: 

269 target = self._fh.pages[page] 

270 else: 

271 target = None 

272 

273 metadata = {} 

274 if target is None: 

275 # return file-level metadata 

276 metadata["byteorder"] = self._fh.byteorder 

277 

278 for flag in tifffile.TIFF.FILE_FLAGS: 

279 flag_value = getattr(self._fh, "is_" + flag) 

280 metadata["is_" + flag] = flag_value 

281 

282 if flag_value and hasattr(self._fh, flag + "_metadata"): 

283 flavor_metadata = getattr(self._fh, flag + "_metadata") 

284 if isinstance(flavor_metadata, tuple): 

285 metadata.update(flavor_metadata[0]) 

286 else: 

287 metadata.update(flavor_metadata) 

288 else: 

289 # tifffile may return a TiffFrame instead of a page 

290 target = target.keyframe 

291 

292 metadata.update({tag.name: tag.value for tag in target.tags}) 

293 metadata.update( 

294 { 

295 "planar_configuration": target.planarconfig, 

296 "compression": target.compression, 

297 "predictor": target.predictor, 

298 "orientation": None, # TODO 

299 "description1": target.description1, 

300 "description": target.description, 

301 "software": target.software, 

302 **_get_resolution(target), 

303 "datetime": target.datetime, 

304 } 

305 ) 

306 

307 return metadata 

308 

309 def properties(self, *, index: int = None, page: int = None) -> ImageProperties: 

310 """Standardized metadata. 

311 

312 The properties returned depend on the value of both ``index`` and 

313 ``page``. ``index`` selects a series and ``page`` allows selecting a 

314 single page from the selected series. If ``index=Ellipsis``, ``page`` is 

315 understood as a flat index, i.e., the selection ignores individual 

316 series inside the file. If ``index=Ellipsis`` and ``page=None`` then 

317 global (file-level) properties are returned. If ``index=Ellipsis`` 

318 and ``page=...``, file-level properties for the flattened index are 

319 returned. 

320 

321 Parameters 

322 ---------- 

323 index : int 

324 If ``int``, select the ndimage (series) located at that index inside 

325 the file. If ``Ellipsis`` and ``page`` is ``int`` extract the 

326 properties of the page located at that (flat) index inside the file. 

327 If ``Ellipsis`` and ``page=None``, return the properties for the 

328 batch of all ndimages in the file. 

329 page : int 

330 If ``None`` return the properties of the full ndimage. If ``...`` 

331 return the properties of the flattened index. If ``int``, 

332 return the properties of the page at the selected index only. 

333 

334 Returns 

335 ------- 

336 image_properties : ImageProperties 

337 The standardized metadata (properties) of the selected ndimage or series. 

338 

339 """ 

340 index = index or 0 

341 page_idx = 0 if page in (None, Ellipsis) else page 

342 

343 if index is Ellipsis: 

344 target_page = self._fh.pages[page_idx] 

345 else: 

346 target_page = self._fh.series[index].pages[page_idx] 

347 

348 if index is Ellipsis and page is None: 

349 n_series = len(self._fh.series) 

350 props = ImageProperties( 

351 shape=(n_series, *target_page.shape), 

352 dtype=target_page.dtype, 

353 n_images=n_series, 

354 is_batch=True, 

355 spacing=_get_resolution(target_page).get("resolution"), 

356 ) 

357 elif index is Ellipsis and page is Ellipsis: 

358 n_pages = len(self._fh.pages) 

359 props = ImageProperties( 

360 shape=(n_pages, *target_page.shape), 

361 dtype=target_page.dtype, 

362 n_images=n_pages, 

363 is_batch=True, 

364 spacing=_get_resolution(target_page).get("resolution"), 

365 ) 

366 else: 

367 props = ImageProperties( 

368 shape=target_page.shape, 

369 dtype=target_page.dtype, 

370 is_batch=False, 

371 spacing=_get_resolution(target_page).get("resolution"), 

372 ) 

373 

374 return props 

375 

376 def close(self) -> None: 

377 if self._fh is not None: 

378 self._fh.close() 

379 

380 super().close() 

381 

382 # ------------------------------ 

383 # Add-on Interface inside imopen 

384 # ------------------------------ 

385 

386 def iter_pages(self, index=..., **kwargs): 

387 """Yield pages from a TIFF file. 

388 

389 This generator walks over the flat index of the pages inside an 

390 ImageResource and yields them in order. 

391 

392 Parameters 

393 ---------- 

394 index : int 

395 The index of the series to yield pages from. If Ellipsis, walk over 

396 the file's flat index (and ignore individual series). 

397 kwargs : Any 

398 Additional kwargs are passed to TiffPage's ``as_array`` method. 

399 

400 Yields 

401 ------ 

402 page : np.ndarray 

403 A page stored inside the TIFF file. 

404 

405 """ 

406 

407 if index is Ellipsis: 

408 pages = self._fh.pages 

409 else: 

410 pages = self._fh.series[index] 

411 

412 for page in pages: 

413 yield page.asarray(**kwargs)